database_statements.rb 13.8 KB
Newer Older
1 2 3
module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module DatabaseStatements
4 5
      def initialize
        super
6
        reset_transaction
7 8
      end

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

21 22 23
      # This is used in the StatementCache object. It returns an object that
      # can be used to query the database repeatedly.
      def cacheable_query(arel) # :nodoc:
A
Aaron Patterson 已提交
24
        ActiveRecord::StatementCache.query visitor, arel.ast
25 26
      end

27
      # Returns an ActiveRecord::Result instance.
28
      def select_all(arel, name = nil, binds = [])
29
        select(to_sql(arel, binds), name, binds)
30
      end
31

32 33
      # Returns a record hash with the column names as keys and column values
      # as values.
34
      def select_one(arel, name = nil, binds = [])
K
kennyj 已提交
35
        select_all(arel, name, binds).first
36
      end
37 38

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

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

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

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

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

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

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

90
      # Returns the last auto-generated ID from the affected table.
91 92 93 94 95 96 97
      #
      # +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+.
98
      def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
99
        sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
100
        value      = exec_insert(sql, name, binds, pk, sequence_name)
101
        id_value || last_inserted_id(value)
102
      end
103 104

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

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

114 115 116 117 118 119
      # Returns +true+ when the connection adapter supports prepared statement
      # caching, otherwise returns +false+
      def supports_statement_cache?
        false
      end

120 121 122 123 124 125 126 127 128 129 130
      # 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 已提交
131
      # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
132 133
      # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8'
      # supports savepoints.
134 135 136 137 138 139 140 141
      #
      # 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.
142
      # - However, if +:requires_new+ is set, the block will be wrapped in a
143
      #   database savepoint acting as a sub-transaction.
144 145 146 147 148 149 150 151 152 153 154 155 156 157
      #
      # === 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
A
AvnerCohen 已提交
158
      #     Model.connection.transaction(requires_new: true) do  # CREATE SAVEPOINT active_record_1
159
      #       Model.connection.create_table(...)
160 161
      #       # active_record_1 now automatically released
      #     end  # RELEASE SAVEPOINT active_record_1  <--- BOOM! database error!
162
      #   end
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
      #
      # == Transaction isolation
      #
      # If your database supports setting the isolation level for a transaction, you can set
      # it like so:
      #
      #   Post.transaction(isolation: :serializable) do
      #     # ...
      #   end
      #
      # Valid isolation levels are:
      #
      # * <tt>:read_uncommitted</tt>
      # * <tt>:read_committed</tt>
      # * <tt>:repeatable_read</tt>
      # * <tt>:serializable</tt>
      #
      # You should consult the documentation for your database to understand the
      # semantics of these different levels:
      #
      # * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
      # * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
      #
      # An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if:
      #
      # * The adapter does not support setting the isolation level
      # * You are joining an existing open transaction
      # * You are creating a nested (savepoint) transaction
      #
      # The mysql, mysql2 and postgresql adapters support setting the transaction
      # isolation level. However, support is disabled for mysql versions below 5,
      # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
      # which means the isolation level gets persisted outside the transaction.
196
      def transaction(options = {})
197
        options.assert_valid_keys :requires_new, :joinable, :isolation
198

199
        if !options[:requires_new] && current_transaction.joinable?
200 201 202 203
          if options[:isolation]
            raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
          end

204
          yield
205 206
        else
          within_new_transaction(options) { yield }
207
        end
J
Jon Leighton 已提交
208 209
      rescue ActiveRecord::Rollback
        # rollbacks are silently swallowed
210
      end
211

212
      def within_new_transaction(options = {}) #:nodoc:
213
        transaction = begin_transaction(options)
214 215
        yield
      rescue Exception => error
216
        rollback_transaction if transaction
217
        raise
218
      ensure
219 220
        begin
          commit_transaction unless error
J
Jon Leighton 已提交
221
        rescue Exception
222 223
          rollback_transaction
          raise
224
        end
225
      end
226

227
      def current_transaction #:nodoc:
228 229 230
        @transaction
      end

231 232 233 234 235
      def transaction_open?
        @transaction.open?
      end

      def begin_transaction(options = {}) #:nodoc:
236
        @transaction = @transaction.begin(options)
237 238
      end

J
DRY  
Jon Leighton 已提交
239 240 241 242
      def commit_transaction #:nodoc:
        @transaction = @transaction.commit
      end

243 244 245 246
      def rollback_transaction #:nodoc:
        @transaction = @transaction.rollback
      end

247
      def reset_transaction #:nodoc:
248 249 250
        @transaction = ClosedTransaction.new(self)
      end

251 252 253
      # Register a record with the current transaction so that its after_commit and after_rollback callbacks
      # can be called.
      def add_transaction_record(record)
254
        @transaction.add_record(record)
255 256
      end

257 258 259
      # Begins the transaction (and turns off auto-committing).
      def begin_db_transaction()    end

260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
      def transaction_isolation_levels
        {
          read_uncommitted: "READ UNCOMMITTED",
          read_committed:   "READ COMMITTED",
          repeatable_read:  "REPEATABLE READ",
          serializable:     "SERIALIZABLE"
        }
      end

      # Begins the transaction with the isolation level set. Raises an error by
      # default; adapters that support setting the isolation level should implement
      # this method.
      def begin_isolated_db_transaction(isolation)
        raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
      end

276 277 278
      # Commits the transaction (and turns on auto-committing).
      def commit_db_transaction()   end

279 280
      # Rolls back the transaction (and turns on auto-committing). Must be
      # done if the transaction block raises an exception or returns false.
281 282
      def rollback_db_transaction() end

283 284 285 286 287 288
      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)
289
        # Do nothing by default. Implement for PostgreSQL, Oracle, ...
290
      end
291

292
      # Inserts the given fixture into the table. Overridden in adapters that require
293 294
      # something beyond a simple insert (eg. Oracle).
      def insert_fixture(fixture, table_name)
295
        columns = schema_cache.columns_hash(table_name)
296 297 298 299 300 301 302

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

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

306
      def empty_insert_statement_value
J
Jon Leighton 已提交
307
        "DEFAULT VALUES"
308 309
      end

310 311 312 313
      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

314 315
      # Sanitizes the given LIMIT parameter in order to prevent SQL injection.
      #
316
      # The +limit+ may be anything that can evaluate to a string via #to_s. It
317
      # should look like an integer, or a comma-delimited list of integers, or
318
      # an Arel SQL literal.
319
      #
320
      # Returns Integer and Arel::Nodes::SqlLiteral limits as is.
321 322 323
      # 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)
324 325 326
        if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
          limit
        elsif limit.to_s =~ /,/
327 328 329 330 331 332
          Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
        else
          Integer(limit)
        end
      end

333 334 335 336
      # 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:
337 338
        key = update.key
        subselect = subquery_for(key, select)
339

340 341 342 343 344 345 346
        update.where key.in(subselect)
      end

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

        delete.where key.in(subselect)
347 348
      end

349
      protected
350

351
        # Returns a subquery for the given key using the join information.
352 353 354 355 356 357
        def subquery_for(key, select)
          subselect = select.clone
          subselect.projections = [key]
          subselect
        end

358
        # Returns an ActiveRecord::Result instance.
359
        def select(sql, name = nil, binds = [])
360
        end
361
        undef_method :select
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377

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

K
kennyj 已提交
379 380 381
        def sql_for_insert(sql, pk, id_value, sequence_name, binds)
          [sql, binds]
        end
382

K
kennyj 已提交
383 384 385 386
        def last_inserted_id(result)
          row = result.rows.first
          row && row.first
        end
387 388
    end
  end
389
end