database_statements.rb 14.6 KB
Newer Older
1 2 3
module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module DatabaseStatements
4 5 6 7 8 9
      def initialize
        super
        @_current_transaction_records = []
        @transaction_joinable         = nil
      end

10
      # Converts an arel AST to SQL
11
      def to_sql(arel, binds = [])
12
        if arel.respond_to?(:ast)
13
          binds = binds.dup
14 15 16
          visitor.accept(arel.ast) do
            quote(*binds.shift.reverse)
          end
17 18 19 20 21
        else
          arel
        end
      end

22 23
      # Returns an array of record hashes with the column names as keys and
      # column values as values.
24
      def select_all(arel, name = nil, binds = [])
25
        select(to_sql(arel, binds), name, binds)
26
      end
27

28 29
      # Returns a record hash with the column names as keys and column values
      # as values.
30 31
      def select_one(arel, name = nil, binds = [])
        result = select_all(arel, name, binds)
32
        result.first if result
33
      end
34 35

      # Returns a single value from a record
36 37
      def select_value(arel, name = nil, binds = [])
        if result = select_one(arel, name, binds)
38 39
          result.values.first
        end
40 41 42 43
      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]
44
      def select_values(arel, name = nil)
45
        result = select_rows(to_sql(arel, []), name)
46
        result.map { |v| v[0] }
47 48
      end

49
      # Returns an array of arrays containing the field values.
P
Pratik Naik 已提交
50
      # Order is the same as that returned by +columns+.
51 52
      def select_rows(sql, name = nil)
      end
53
      undef_method :select_rows
54

55
      # Executes the SQL statement in the context of this connection.
A
Aaron Patterson 已提交
56
      def execute(sql, name = nil)
57
      end
58
      undef_method :execute
59

A
Aaron Patterson 已提交
60
      # Executes +sql+ statement in the context of this connection using
61
      # +binds+ as the bind substitutes. +name+ is logged along with
A
Aaron Patterson 已提交
62
      # the executed +sql+ statement.
63
      def exec_query(sql, name = 'SQL', binds = [])
A
Aaron Patterson 已提交
64 65
      end

66
      # Executes insert +sql+ statement in the context of this connection using
W
Waseem Ahmad 已提交
67
      # +binds+ as the bind substitutes. +name+ is logged along with
68
      # the executed +sql+ statement.
69
      def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
70 71 72
        exec_query(sql, name, binds)
      end

73
      # Executes delete +sql+ statement in the context of this connection using
W
Waseem Ahmad 已提交
74
      # +binds+ as the bind substitutes. +name+ is logged along with
75 76 77 78 79
      # the executed +sql+ statement.
      def exec_delete(sql, name, binds)
        exec_query(sql, name, binds)
      end

80
      # Executes update +sql+ statement in the context of this connection using
W
Waseem Ahmad 已提交
81
      # +binds+ as the bind substitutes. +name+ is logged along with
82 83 84 85 86
      # the executed +sql+ statement.
      def exec_update(sql, name, binds)
        exec_query(sql, name, binds)
      end

87
      # Returns the last auto-generated ID from the affected table.
88 89 90 91 92 93 94
      #
      # +id_value+ will be returned unless the value is nil, in
      # which case the database will attempt to calculate the last inserted
      # id and return that value.
      #
      # If the next id was calculated in advance (as in Oracle), it should be
      # passed in as +id_value+.
95
      def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
96
        sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
97
        value      = exec_insert(sql, name, binds, pk, sequence_name)
98
        id_value || last_inserted_id(value)
99
      end
100 101

      # Executes the update statement and returns the number of rows affected.
102
      def update(arel, name = nil, binds = [])
103
        exec_update(to_sql(arel, binds), name, binds)
104
      end
105 106

      # Executes the delete statement and returns the number of rows affected.
107
      def delete(arel, name = nil, binds = [])
108
        exec_delete(to_sql(arel, binds), name, binds)
109
      end
110

111 112 113 114 115 116 117 118 119 120 121 122 123
      # 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
124

125 126 127 128 129 130
      # Returns +true+ when the connection adapter supports prepared statement
      # caching, otherwise returns +false+
      def supports_statement_cache?
        false
      end

131 132 133 134 135 136 137 138 139 140 141
      # 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:
V
Vijay Dev 已提交
142
      # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
143 144 145 146 147 148 149 150 151
      # 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.
152
      # - However, if +:requires_new+ is set, the block will be wrapped in a
153
      #   database savepoint acting as a sub-transaction.
154 155 156 157 158 159 160 161 162 163 164 165 166 167
      #
      # === 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
168
      #     Model.connection.transaction(:requires_new => true) do  # CREATE SAVEPOINT active_record_1
169
      #       Model.connection.create_table(...)
170 171
      #       # active_record_1 now automatically released
      #     end  # RELEASE SAVEPOINT active_record_1  <--- BOOM! database error!
172
      #   end
173 174 175
      def transaction(options = {})
        options.assert_valid_keys :requires_new, :joinable

176
        last_transaction_joinable = @transaction_joinable
177 178 179
        @transaction_joinable     = options.fetch(:joinable, true)
        requires_new              = options[:requires_new] || !last_transaction_joinable
        transaction_open          = false
180

181
        begin
182 183 184 185 186
          if requires_new || open_transactions == 0
            if open_transactions == 0
              begin_db_transaction
            elsif requires_new
              create_savepoint
187
            end
188 189 190
            increment_open_transactions
            transaction_open = true
            @_current_transaction_records.push([])
191
          end
192
          yield
193
        rescue Exception => database_transaction_rollback
194
          if transaction_open && !outside_transaction?
195
            transaction_open = false
196
            decrement_open_transactions
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
          end
205
          raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
206
        end
207
      ensure
208 209
        @transaction_joinable = last_transaction_joinable

210
        if outside_transaction?
211
          @open_transactions = 0
212
        elsif transaction_open
213
          decrement_open_transactions
214
          begin
215 216
            if open_transactions == 0
              commit_db_transaction
217
              commit_transaction_records
218 219
            else
              release_savepoint
220 221 222 223 224
              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
225
            end
A
Aaron Patterson 已提交
226
          rescue Exception
227 228
            if open_transactions == 0
              rollback_db_transaction
229
              rollback_transaction_records(true)
230 231
            else
              rollback_to_savepoint
232
              rollback_transaction_records(false)
233
            end
234 235 236
            raise
          end
        end
237
      end
238

239 240 241 242 243 244 245
      # 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

246 247 248 249 250 251
      # 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

252 253
      # Rolls back the transaction (and turns on auto-committing). Must be
      # done if the transaction block raises an exception or returns false.
254 255
      def rollback_db_transaction() end

256 257 258 259 260 261
      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)
262
        # Do nothing by default. Implement for PostgreSQL, Oracle, ...
263
      end
264

265
      # Inserts the given fixture into the table. Overridden in adapters that require
266 267
      # something beyond a simple insert (eg. Oracle).
      def insert_fixture(fixture, table_name)
268 269 270 271 272 273 274 275 276
        columns = Hash[columns(table_name).map { |c| [c.name, c] }]

        key_list   = []
        value_list = fixture.map do |name, value|
          key_list << quote_column_name(name)
          quote(value, columns[name])
        end

        execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
277 278
      end

279 280
      def empty_insert_statement_value
        "VALUES(DEFAULT)"
281 282
      end

283 284 285 286
      def case_sensitive_equality_operator
        "="
      end

287 288 289 290
      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

291 292
      # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
      #
293
      # The +limit+ may be anything that can evaluate to a string via #to_s. It
294
      # should look like an integer, or a comma-delimited list of integers, or
295
      # an Arel SQL literal.
296
      #
297
      # Returns Integer and Arel::Nodes::SqlLiteral limits as is.
298 299 300
      # 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)
301 302 303
        if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
          limit
        elsif limit.to_s =~ /,/
304 305 306 307 308 309
          Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
        else
          Integer(limit)
        end
      end

310 311 312 313
      # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
      # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
      # an UPDATE statement, so in the mysql adapters we redefine this to do that.
      def join_to_update(update, select) #:nodoc:
314 315
        key = update.key
        subselect = subquery_for(key, select)
316

317 318 319 320 321 322 323
        update.where key.in(subselect)
      end

      def join_to_delete(delete, select, key) #:nodoc:
        subselect = subquery_for(key, select)

        delete.where key.in(subselect)
324 325
      end

326
      protected
327 328 329 330 331 332 333 334

        # Return a subquery for the given key using the join information.
        def subquery_for(key, select)
          subselect = select.clone
          subselect.projections = [key]
          subselect
        end

335 336
        # Returns an array of record hashes with the column names as keys and
        # column values as values.
337
        def select(sql, name = nil, binds = [])
338
        end
339
        undef_method :select
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355

        # 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
356

357 358
        # 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.
V
Vijay Dev 已提交
359
        def rollback_transaction_records(rollback)
360 361 362 363 364 365 366 367 368 369 370
          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)
371
              rescue => e
372
                record.logger.error(e) if record.respond_to?(:logger) && record.logger
373 374 375 376 377 378
              end
            end
          end
        end

        # Send a commit message to all records after they have been committed.
V
Vijay Dev 已提交
379
        def commit_transaction_records
380 381 382 383 384 385
          records = @_current_transaction_records.flatten
          @_current_transaction_records.clear
          unless records.blank?
            records.uniq.each do |record|
              begin
                record.committed!
386
              rescue => e
387
                record.logger.error(e) if record.respond_to?(:logger) && record.logger
388 389 390 391
              end
            end
          end
        end
392 393 394 395 396 397 398 399 400

      def sql_for_insert(sql, pk, id_value, sequence_name, binds)
        [sql, binds]
      end

      def last_inserted_id(result)
        row = result.rows.first
        row && row.first
      end
401 402
    end
  end
403
end