database_statements.rb 14.2 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 13
          collected = visitor.accept(arel.ast, collector)
          collected.compile(binds.dup, self)
14 15 16 17 18
        else
          arel
        end
      end

19 20 21
      # 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:
22 23 24 25 26
        if prepared_statements
          ActiveRecord::StatementCache.query visitor, arel.ast
        else
          ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector
        end
27 28
      end

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

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

      # Returns a single value from a record
42 43
      def select_value(arel, name = nil, binds = [])
        if result = select_one(arel, name, binds)
44 45
          result.values.first
        end
46 47 48 49
      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]
50
      def select_values(arel, name = nil)
51
        arel, binds = binds_from_relation arel, []
52
        select_rows(to_sql(arel, binds), name, binds).map(&:first)
53 54
      end

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

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

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

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

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

86 87 88 89 90
      # Executes the truncate statement.
      def truncate(table_name, name = nil)
        raise NotImplementedError
      end

91
      # Executes update +sql+ statement in the context of this connection using
W
Waseem Ahmad 已提交
92
      # +binds+ as the bind substitutes. +name+ is logged along with
93 94 95 96 97
      # the executed +sql+ statement.
      def exec_update(sql, name, binds)
        exec_query(sql, name, binds)
      end

98
      # Returns the last auto-generated ID from the affected table.
99 100 101 102 103 104 105
      #
      # +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+.
106
      def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
107
        sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
108
        value      = exec_insert(sql, name, binds, pk, sequence_name)
109
        id_value || last_inserted_id(value)
110
      end
111 112

      # Executes the update statement and returns the number of rows affected.
113
      def update(arel, name = nil, binds = [])
114
        exec_update(to_sql(arel, binds), name, binds)
115
      end
116 117

      # Executes the delete statement and returns the number of rows affected.
118
      def delete(arel, name = nil, binds = [])
119
        exec_delete(to_sql(arel, binds), name, binds)
120
      end
121

122 123 124 125 126 127
      # Returns +true+ when the connection adapter supports prepared statement
      # caching, otherwise returns +false+
      def supports_statement_cache?
        false
      end

128 129 130 131 132 133 134 135 136 137 138
      # 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 已提交
139
      # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
140 141
      # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8'
      # supports savepoints.
142 143 144 145 146 147 148 149
      #
      # 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.
150
      # - However, if +:requires_new+ is set, the block will be wrapped in a
151
      #   database savepoint acting as a sub-transaction.
152 153 154 155 156 157 158 159 160 161 162 163 164 165
      #
      # === 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 已提交
166
      #     Model.connection.transaction(requires_new: true) do  # CREATE SAVEPOINT active_record_1
167
      #       Model.connection.create_table(...)
168 169
      #       # active_record_1 now automatically released
      #     end  # RELEASE SAVEPOINT active_record_1  <--- BOOM! database error!
170
      #   end
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 196 197 198 199 200
      #
      # == 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
201
      # isolation level. However, support is disabled for MySQL versions below 5,
202 203
      # 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.
204
      def transaction(options = {})
205
        options.assert_valid_keys :requires_new, :joinable, :isolation
206

207
        if !options[:requires_new] && current_transaction.joinable?
208 209 210
          if options[:isolation]
            raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
          end
211
          yield
212
        else
A
Arthur Neves 已提交
213
          transaction_manager.within_new_transaction(options) { yield }
214
        end
J
Jon Leighton 已提交
215 216
      rescue ActiveRecord::Rollback
        # rollbacks are silently swallowed
217
      end
218

A
Arthur Neves 已提交
219
      attr_reader :transaction_manager #:nodoc:
220

A
Arthur Neves 已提交
221
      delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager
222

223
      def transaction_open?
A
Arthur Neves 已提交
224
        current_transaction.open?
225 226
      end

227
      def reset_transaction #:nodoc:
A
Arthur Neves 已提交
228
        @transaction_manager = TransactionManager.new(self)
229 230
      end

231 232 233
      # Register a record with the current transaction so that its after_commit and after_rollback callbacks
      # can be called.
      def add_transaction_record(record)
A
Arthur Neves 已提交
234
        current_transaction.add_record(record)
235 236
      end

237 238 239
      # Begins the transaction (and turns off auto-committing).
      def begin_db_transaction()    end

240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
      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

256 257 258
      # Commits the transaction (and turns on auto-committing).
      def commit_db_transaction()   end

259 260
      # Rolls back the transaction (and turns on auto-committing). Must be
      # done if the transaction block raises an exception or returns false.
261 262 263 264 265 266 267 268 269 270 271 272
      def rollback_db_transaction
        exec_rollback_db_transaction
      end

      def exec_rollback_db_transaction() end #:nodoc:

      def rollback_to_savepoint(name = nil)
        exec_rollback_to_savepoint(name)
      end

      def exec_rollback_to_savepoint(name = nil) #:nodoc:
      end
273

274 275 276 277 278 279
      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)
280
        # Do nothing by default. Implement for PostgreSQL, Oracle, ...
281
      end
282

283
      # Inserts the given fixture into the table. Overridden in adapters that require
284 285
      # something beyond a simple insert (eg. Oracle).
      def insert_fixture(fixture, table_name)
286
        columns = schema_cache.columns_hash(table_name)
287

288
        binds = fixture.map do |name, value|
S
Sean Griffin 已提交
289
          Relation::QueryAttribute.new(name, value, columns[name].cast_type)
290
        end
291
        key_list = fixture.keys.map { |name| quote_column_name(name) }
S
Sean Griffin 已提交
292
        value_list = prepare_binds_for_database(binds).map do |value|
293 294 295 296 297 298
          begin
            quote(value)
          rescue TypeError
            quote(YAML.dump(value))
          end
        end
299

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

303
      def empty_insert_statement_value
J
Jon Leighton 已提交
304
        "DEFAULT VALUES"
305 306
      end

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

326
      # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
327 328
      # 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.
329
      def join_to_update(update, select) #:nodoc:
330 331
        key = update.key
        subselect = subquery_for(key, select)
332

333 334 335 336 337 338 339
        update.where key.in(subselect)
      end

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

        delete.where key.in(subselect)
340 341
      end

342
      protected
343

344
        # Returns a subquery for the given key using the join information.
345 346 347 348 349 350
        def subquery_for(key, select)
          subselect = select.clone
          subselect.projections = [key]
          subselect
        end

351
        # Returns an ActiveRecord::Result instance.
352
        def select(sql, name = nil, binds = [])
353
          exec_query(sql, name, binds)
354
        end
355

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371

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

K
kennyj 已提交
373 374 375
        def sql_for_insert(sql, pk, id_value, sequence_name, binds)
          [sql, binds]
        end
376

K
kennyj 已提交
377 378 379 380
        def last_inserted_id(result)
          row = result.rows.first
          row && row.first
        end
381

382
        def binds_from_relation(relation, binds)
383
          if relation.is_a?(Relation) && binds.empty?
S
Sean Griffin 已提交
384
            relation, binds = relation.arel, relation.bound_attributes
385 386 387
          end
          [relation, binds]
        end
388 389
    end
  end
390
end