require 'active_support/core_ext/module/deprecation' module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseStatements # Returns an array of record hashes with the column names as keys and # column values as values. def select_all(sql, name = nil, binds = []) if supports_statement_cache? select(sql, name, binds) else return select(sql, name) if binds.empty? binds = binds.dup select sql.gsub('?') { quote(*binds.shift.reverse) }, name end end # Returns a record hash with the column names as keys and column values # as values. def select_one(sql, name = nil) result = select_all(sql, name) result.first if result end # Returns a single value from a record def select_value(sql, name = nil) if result = select_one(sql, name) result.values.first end end # Returns an array of the values of the first column in a select: # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3] def select_values(sql, name = nil) result = select_rows(sql, name) result.map { |v| v[0] } end # Returns an array of arrays containing the field values. # Order is the same as that returned by +columns+. def select_rows(sql, name = nil) end undef_method :select_rows # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) end undef_method :execute # Executes +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. def exec_query(sql, name = 'SQL', binds = []) end # Returns the last auto-generated ID from the affected table. def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) insert_sql(sql, name, pk, id_value, sequence_name) end # Executes the update statement and returns the number of rows affected. def update(sql, name = nil) update_sql(sql, name) end # Executes the delete statement and returns the number of rows affected. def delete(sql, name = nil) delete_sql(sql, name) end # Checks whether there is currently no transaction active. This is done # by querying the database driver, and does not use the transaction # house-keeping information recorded by #increment_open_transactions and # friends. # # Returns true if there is no transaction active, false if there is a # transaction active, and nil if this information is unknown. # # Not all adapters supports transaction state introspection. Currently, # only the PostgreSQL adapter supports this. def outside_transaction? nil end # Returns +true+ when the connection adapter supports prepared statement # caching, otherwise returns +false+ def supports_statement_cache? false end # Runs the given block in a database transaction, and returns the result # of the block. # # == Nested transactions support # # Most databases don't support true nested transactions. At the time of # writing, the only database that supports true nested transactions that # we're aware of, is MS-SQL. # # In order to get around this problem, #transaction will emulate the effect # of nested transactions, by using savepoints: # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html # Savepoints are supported by MySQL and PostgreSQL, but not SQLite3. # # It is safe to call this method if a database transaction is already open, # i.e. if #transaction is called within another #transaction block. In case # of a nested call, #transaction will behave as follows: # # - The block will be run without doing anything. All database statements # that happen within the block are effectively appended to the already # open database transaction. # - However, if +:requires_new+ is set, the block will be wrapped in a # database savepoint acting as a sub-transaction. # # === Caveats # # MySQL doesn't support DDL transactions. If you perform a DDL operation, # then any created savepoints will be automatically released. For example, # if you've created a savepoint, then you execute a CREATE TABLE statement, # then the savepoint that was created will be automatically released. # # This means that, on MySQL, you shouldn't execute DDL operations inside # a #transaction call that you know might create a savepoint. Otherwise, # #transaction will raise exceptions when it tries to release the # already-automatically-released savepoints: # # Model.connection.transaction do # BEGIN # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1 # Model.connection.create_table(...) # # active_record_1 now automatically released # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error! # end def transaction(options = {}) options.assert_valid_keys :requires_new, :joinable last_transaction_joinable = defined?(@transaction_joinable) ? @transaction_joinable : nil if options.has_key?(:joinable) @transaction_joinable = options[:joinable] else @transaction_joinable = true end requires_new = options[:requires_new] || !last_transaction_joinable transaction_open = false @_current_transaction_records ||= [] begin if block_given? if requires_new || open_transactions == 0 if open_transactions == 0 begin_db_transaction elsif requires_new create_savepoint end increment_open_transactions transaction_open = true @_current_transaction_records.push([]) end yield end rescue Exception => database_transaction_rollback if transaction_open && !outside_transaction? transaction_open = false decrement_open_transactions if open_transactions == 0 rollback_db_transaction rollback_transaction_records(true) else rollback_to_savepoint rollback_transaction_records(false) end end raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback) end ensure @transaction_joinable = last_transaction_joinable if outside_transaction? @open_transactions = 0 elsif transaction_open decrement_open_transactions begin if open_transactions == 0 commit_db_transaction commit_transaction_records else release_savepoint save_point_records = @_current_transaction_records.pop unless save_point_records.blank? @_current_transaction_records.push([]) if @_current_transaction_records.empty? @_current_transaction_records.last.concat(save_point_records) end end rescue Exception => database_transaction_rollback if open_transactions == 0 rollback_db_transaction rollback_transaction_records(true) else rollback_to_savepoint rollback_transaction_records(false) end raise end end end # Register a record with the current transaction so that its after_commit and after_rollback callbacks # can be called. def add_transaction_record(record) last_batch = @_current_transaction_records.last last_batch << record if last_batch end # Begins the transaction (and turns off auto-committing). def begin_db_transaction() end # Commits the transaction (and turns on auto-committing). def commit_db_transaction() end # Rolls back the transaction (and turns on auto-committing). Must be # done if the transaction block raises an exception or returns false. def rollback_db_transaction() end # Appends +LIMIT+ and +OFFSET+ options to an SQL statement, or some SQL # fragment that has the same semantics as LIMIT and OFFSET. # # +options+ must be a Hash which contains a +:limit+ option # and an +:offset+ option. # # This method *modifies* the +sql+ parameter. # # This method is deprecated!! Stop using it! # # ===== Examples # add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50}) # generates # SELECT * FROM suppliers LIMIT 10 OFFSET 50 def add_limit_offset!(sql, options) if limit = options[:limit] sql << " LIMIT #{sanitize_limit(limit)}" end if offset = options[:offset] sql << " OFFSET #{offset.to_i}" end sql end deprecate :add_limit_offset! def default_sequence_name(table, column) nil end # Set the sequence to the max value of the table's column. def reset_sequence!(table, column, sequence = nil) # Do nothing by default. Implement for PostgreSQL, Oracle, ... end # Inserts the given fixture into the table. Overridden in adapters that require # something beyond a simple insert (eg. Oracle). def insert_fixture(fixture, table_name) execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' end def empty_insert_statement_value "VALUES(DEFAULT)" end def case_sensitive_equality_operator "=" end def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) "WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})" end protected # Returns an array of record hashes with the column names as keys and # column values as values. def select(sql, name = nil, binds = []) end undef_method :select # Returns the last auto-generated ID from the affected table. def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) execute(sql, name) id_value end # Executes the update statement and returns the number of rows affected. def update_sql(sql, name = nil) execute(sql, name) end # Executes the delete statement and returns the number of rows affected. def delete_sql(sql, name = nil) update_sql(sql, name) end # Sanitizes the given LIMIT parameter in order to prevent SQL injection. # # +limit+ may be anything that can evaluate to a string via #to_s. It # should look like an integer, or a comma-delimited list of integers. # # Returns the sanitized limit parameter, either as an integer, or as a # string which contains a comma-delimited list of integers. def sanitize_limit(limit) if limit.to_s =~ /,/ limit.to_s.split(',').map{ |i| i.to_i }.join(',') else limit.to_i end end # Send a rollback message to all records after they have been rolled back. If rollback # is false, only rollback records since the last save point. def rollback_transaction_records(rollback) #:nodoc if rollback records = @_current_transaction_records.flatten @_current_transaction_records.clear else records = @_current_transaction_records.pop end unless records.blank? records.uniq.each do |record| begin record.rolledback!(rollback) rescue Exception => e record.logger.error(e) if record.respond_to?(:logger) && record.logger end end end end # Send a commit message to all records after they have been committed. def commit_transaction_records #:nodoc records = @_current_transaction_records.flatten @_current_transaction_records.clear unless records.blank? records.uniq.each do |record| begin record.committed! rescue Exception => e record.logger.error(e) if record.respond_to?(:logger) && record.logger end end end end end end end