database_statements.rb 12.8 KB
Newer Older
1 2
require 'active_support/core_ext/module/deprecation'

3 4 5
module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module DatabaseStatements
6 7
      # Returns an array of record hashes with the column names as keys and
      # column values as values.
8
      def select_all(sql, name = nil, binds = [])
9
        if supports_statement_cache?
10
          select(sql, name, binds)
11
        else
12 13
          return select(sql, name) if binds.empty?
          binds = binds.dup
14 15 16 17
          select sql.gsub('?') {
            quote(*binds.shift.reverse)
          }, name
        end
18
      end
19

20 21
      # Returns a record hash with the column names as keys and column values
      # as values.
22
      def select_one(sql, name = nil)
23
        result = select_all(sql, name)
24
        result.first if result
25
      end
26 27 28

      # Returns a single value from a record
      def select_value(sql, name = nil)
29 30 31
        if result = select_one(sql, name)
          result.values.first
        end
32 33 34 35 36
      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)
37 38
        result = select_rows(sql, name)
        result.map { |v| v[0] }
39 40
      end

41
      # Returns an array of arrays containing the field values.
P
Pratik Naik 已提交
42
      # Order is the same as that returned by +columns+.
43 44
      def select_rows(sql, name = nil)
      end
45
      undef_method :select_rows
46

47
      # Executes the SQL statement in the context of this connection.
A
Aaron Patterson 已提交
48
      def execute(sql, name = nil)
49
      end
50
      undef_method :execute
51

A
Aaron Patterson 已提交
52
      # Executes +sql+ statement in the context of this connection using
53
      # +binds+ as the bind substitutes.  +name+ is logged along with
A
Aaron Patterson 已提交
54
      # the executed +sql+ statement.
55
      def exec_query(sql, name = 'SQL', binds = [])
A
Aaron Patterson 已提交
56 57
      end

58
      # Returns the last auto-generated ID from the affected table.
59
      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
60
        insert_sql(sql, name, pk, id_value, sequence_name)
61
      end
62 63

      # Executes the update statement and returns the number of rows affected.
64
      def update(sql, name = nil)
65
        update_sql(sql, name)
66
      end
67 68

      # Executes the delete statement and returns the number of rows affected.
69
      def delete(sql, name = nil)
70
        delete_sql(sql, name)
71
      end
72

73 74 75 76 77 78 79 80 81 82 83 84 85
      # 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
86

87 88 89 90 91 92
      # Returns +true+ when the connection adapter supports prepared statement
      # caching, otherwise returns +false+
      def supports_statement_cache?
        false
      end

93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
      # 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.
114
      # - However, if +:requires_new+ is set, the block will be wrapped in a
115
      #   database savepoint acting as a sub-transaction.
116 117 118 119 120 121 122 123 124 125 126 127 128 129
      #
      # === 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
130
      #     Model.connection.transaction(:requires_new => true) do  # CREATE SAVEPOINT active_record_1
131
      #       Model.connection.create_table(...)
132 133
      #       # active_record_1 now automatically released
      #     end  # RELEASE SAVEPOINT active_record_1  <--- BOOM! database error!
134
      #   end
135 136 137
      def transaction(options = {})
        options.assert_valid_keys :requires_new, :joinable

138
        last_transaction_joinable = defined?(@transaction_joinable) ? @transaction_joinable : nil
139 140 141 142 143
        if options.has_key?(:joinable)
          @transaction_joinable = options[:joinable]
        else
          @transaction_joinable = true
        end
144 145
        requires_new = options[:requires_new] || !last_transaction_joinable

146
        transaction_open = false
147 148
        @_current_transaction_records ||= []

149 150
        begin
          if block_given?
151
            if requires_new || open_transactions == 0
152 153
              if open_transactions == 0
                begin_db_transaction
154
              elsif requires_new
155 156
                create_savepoint
              end
J
Jonathan Viney 已提交
157
              increment_open_transactions
158
              transaction_open = true
159
              @_current_transaction_records.push([])
160
            end
161
            yield
162
          end
163
        rescue Exception => database_transaction_rollback
164
          if transaction_open && !outside_transaction?
165
            transaction_open = false
J
Jonathan Viney 已提交
166
            decrement_open_transactions
167 168
            if open_transactions == 0
              rollback_db_transaction
169
              rollback_transaction_records(true)
170 171
            else
              rollback_to_savepoint
172
              rollback_transaction_records(false)
173
            end
174
          end
175
          raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
176
        end
177
      ensure
178 179
        @transaction_joinable = last_transaction_joinable

180 181 182
        if outside_transaction?
          @open_transactions = 0
        elsif transaction_open
J
Jonathan Viney 已提交
183
          decrement_open_transactions
184
          begin
185 186
            if open_transactions == 0
              commit_db_transaction
187
              commit_transaction_records
188 189
            else
              release_savepoint
190 191 192 193 194
              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
195
            end
196
          rescue Exception => database_transaction_rollback
197 198
            if open_transactions == 0
              rollback_db_transaction
199
              rollback_transaction_records(true)
200 201
            else
              rollback_to_savepoint
202
              rollback_transaction_records(false)
203
            end
204 205 206
            raise
          end
        end
207
      end
208

209 210 211 212 213 214 215
      # 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

216 217 218 219 220 221
      # 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

222 223
      # Rolls back the transaction (and turns on auto-committing). Must be
      # done if the transaction block raises an exception or returns false.
224 225
      def rollback_db_transaction() end

226 227 228 229 230 231 232 233
      # 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.
      #
234 235
      # This method is deprecated!! Stop using it!
      #
236 237 238 239 240 241 242 243 244 245 246 247 248 249
      # ===== 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
250
      deprecate :add_limit_offset!
251

252 253 254 255 256 257 258 259
      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
260

261
      # Inserts the given fixture into the table. Overridden in adapters that require
262 263
      # something beyond a simple insert (eg. Oracle).
      def insert_fixture(fixture, table_name)
264
        execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
265 266
      end

267 268
      def empty_insert_statement_value
        "VALUES(DEFAULT)"
269 270
      end

271 272 273 274
      def case_sensitive_equality_operator
        "="
      end

275 276 277 278
      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

279 280 281
      protected
        # Returns an array of record hashes with the column names as keys and
        # column values as values.
282
        def select(sql, name = nil, binds = [])
283
        end
284
        undef_method :select
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300

        # 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
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315

        # 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
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331

        # 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
332
                record.logger.error(e) if record.respond_to?(:logger) && record.logger
333 334 335 336 337 338 339 340 341 342 343 344 345 346
              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
347
                record.logger.error(e) if record.respond_to?(:logger) && record.logger
348 349 350 351
              end
            end
          end
        end
352 353
    end
  end
354
end