database_statements.rb 13.7 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
      # Returns an array of record hashes with the column names as keys and
      # column values as values.
23
      def select_all(arel, name = nil, binds = [])
24
        select(to_sql(arel, binds), name, binds)
25
      end
26

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

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

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

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

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

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

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

79
      # Executes update +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_update(sql, name, binds)
        exec_query(sql, name, binds)
      end

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

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

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

110 111 112 113 114 115
      # Returns +true+ when the connection adapter supports prepared statement
      # caching, otherwise returns +false+
      def supports_statement_cache?
        false
      end

116 117 118 119 120 121 122 123 124 125 126
      # 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 已提交
127
      # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
128 129 130 131 132 133 134 135 136
      # 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.
137
      # - However, if +:requires_new+ is set, the block will be wrapped in a
138
      #   database savepoint acting as a sub-transaction.
139 140 141 142 143 144 145 146 147 148 149 150 151 152
      #
      # === 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
153
      #     Model.connection.transaction(:requires_new => true) do  # CREATE SAVEPOINT active_record_1
154
      #       Model.connection.create_table(...)
155 156
      #       # active_record_1 now automatically released
      #     end  # RELEASE SAVEPOINT active_record_1  <--- BOOM! database error!
157
      #   end
158 159 160 161 162 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
      #
      # == 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.
191
      def transaction(options = {})
192
        options.assert_valid_keys :requires_new, :joinable, :isolation
193

194
        if !options[:requires_new] && current_transaction.joinable?
195 196 197 198
          if options[:isolation]
            raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
          end

199
          yield
200 201
        else
          within_new_transaction(options) { yield }
202
        end
J
Jon Leighton 已提交
203 204
      rescue ActiveRecord::Rollback
        # rollbacks are silently swallowed
205
      end
206

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

222
      def current_transaction #:nodoc:
223 224 225
        @transaction
      end

226 227 228 229 230
      def transaction_open?
        @transaction.open?
      end

      def begin_transaction(options = {}) #:nodoc:
231
        @transaction = @transaction.begin(options)
232 233
      end

J
DRY  
Jon Leighton 已提交
234 235 236 237
      def commit_transaction #:nodoc:
        @transaction = @transaction.commit
      end

238 239 240 241
      def rollback_transaction #:nodoc:
        @transaction = @transaction.rollback
      end

242
      def reset_transaction #:nodoc:
243 244 245
        @transaction = ClosedTransaction.new(self)
      end

246 247 248
      # Register a record with the current transaction so that its after_commit and after_rollback callbacks
      # can be called.
      def add_transaction_record(record)
249
        @transaction.add_record(record)
250 251
      end

252 253 254
      # Begins the transaction (and turns off auto-committing).
      def begin_db_transaction()    end

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
      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

271 272 273
      # Commits the transaction (and turns on auto-committing).
      def commit_db_transaction()   end

274 275
      # Rolls back the transaction (and turns on auto-committing). Must be
      # done if the transaction block raises an exception or returns false.
276 277
      def rollback_db_transaction() end

278 279 280 281 282 283
      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)
284
        # Do nothing by default. Implement for PostgreSQL, Oracle, ...
285
      end
286

287
      # Inserts the given fixture into the table. Overridden in adapters that require
288 289
      # something beyond a simple insert (eg. Oracle).
      def insert_fixture(fixture, table_name)
290 291 292 293 294 295 296 297 298
        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'
299 300
      end

301 302
      def empty_insert_statement_value
        "VALUES(DEFAULT)"
303 304
      end

305 306 307 308
      def case_sensitive_equality_operator
        "="
      end

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

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

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

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

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

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

348
      protected
349 350 351 352 353 354 355 356

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

357 358
        # Returns an array of record hashes with the column names as keys and
        # column values as values.
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

379 380 381 382 383 384 385 386
      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
387 388
    end
  end
389
end