database_statements.rb 10.5 KB
Newer Older
1 2 3
module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module DatabaseStatements
4 5
      # Returns an array of record hashes with the column names as keys and
      # column values as values.
6
      def select_all(sql, name = nil)
7
        select(sql, name)
8
      end
9

10 11
      # Returns a record hash with the column names as keys and column values
      # as values.
12
      def select_one(sql, name = nil)
13
        result = select_all(sql, name)
14
        result.first if result
15
      end
16 17 18

      # Returns a single value from a record
      def select_value(sql, name = nil)
19 20 21
        if result = select_one(sql, name)
          result.values.first
        end
22 23 24 25 26
      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)
27 28
        result = select_rows(sql, name)
        result.map { |v| v[0] }
29 30
      end

31
      # Returns an array of arrays containing the field values.
P
Pratik Naik 已提交
32
      # Order is the same as that returned by +columns+.
33 34
      def select_rows(sql, name = nil)
      end
35
      undef_method :select_rows
36

37
      # Executes the SQL statement in the context of this connection.
38
      def execute(sql, name = nil, skip_logging = false)
39
      end
40
      undef_method :execute
41 42

      # Returns the last auto-generated ID from the affected table.
43
      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
44
        insert_sql(sql, name, pk, id_value, sequence_name)
45
      end
46 47

      # Executes the update statement and returns the number of rows affected.
48
      def update(sql, name = nil)
49
        update_sql(sql, name)
50
      end
51 52

      # Executes the delete statement and returns the number of rows affected.
53
      def delete(sql, name = nil)
54
        delete_sql(sql, name)
55
      end
56 57 58 59 60 61 62 63 64 65 66 67 68 69
      
      # 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
70

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
      # 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.
      # - However, if +start_db_transaction+ is set to true, then the block will
      #   be run inside a new database savepoint, effectively making the block
      #   a sub-transaction.
      # - If the #transactional_fixtures attribute is set to true, then the first
      #   nested call to #transaction will create a new savepoint instead of
      #   doing nothing. This makes it possible for toplevel transactions in unit
      #   tests to behave like real transactions, even though a database
      #   transaction has already been opened.
      #
      # === 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
      #     Model.connection.transaction(true) do  # CREATE SAVEPOINT rails_savepoint_1
      #       Model.connection.create_table(...)
      #       # rails_savepoint_1 now automatically released
      #     end  # RELEASE savepoint rails_savepoint_1  <--- BOOM! database error!
      #   end
J
Jonathan Viney 已提交
119
      def transaction(start_db_transaction = false)
120
        start_db_transaction ||= open_transactions == 0 || (open_transactions == 1 && transactional_fixtures)
121
        transaction_open = false
122 123
        begin
          if block_given?
124
            if start_db_transaction
125 126 127 128 129
              if open_transactions == 0
                begin_db_transaction
              else
                create_savepoint
              end
J
Jonathan Viney 已提交
130
              increment_open_transactions
131 132
              transaction_open = true
            end
133
            yield
134
          end
135
        rescue Exception => database_transaction_rollback
136
          if transaction_open && !outside_transaction?
137
            transaction_open = false
J
Jonathan Viney 已提交
138
            decrement_open_transactions
139 140 141 142 143
            if open_transactions == 0
              rollback_db_transaction
            else
              rollback_to_savepoint
            end
144
          end
145
          raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
146
        end
147
      ensure
148 149 150
        if outside_transaction?
          @open_transactions = 0
        elsif transaction_open
J
Jonathan Viney 已提交
151
          decrement_open_transactions
152
          begin
153 154 155 156 157
            if open_transactions == 0
              commit_db_transaction
            else
              release_savepoint
            end
158
          rescue Exception => database_transaction_rollback
159 160 161 162 163
            if open_transactions == 0
              rollback_db_transaction
            else
              rollback_to_savepoint
            end
164 165 166
            raise
          end
        end
167
      end
J
Jonathan Viney 已提交
168
      
169 170 171 172 173 174
      # 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

175 176
      # Rolls back the transaction (and turns on auto-committing). Must be
      # done if the transaction block raises an exception or returns false.
177 178
      def rollback_db_transaction() end

P
Pratik Naik 已提交
179
      # Alias for <tt>add_limit_offset!</tt>.
180
      def add_limit!(sql, options)
181
        add_limit_offset!(sql, options) if options
182 183
      end

P
Pratik Naik 已提交
184 185 186 187 188 189
      # 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 (required)
      # and an +:offset+ option (optional).
      #
190
      # This method *modifies* the +sql+ parameter.
P
Pratik Naik 已提交
191
      #
192 193 194 195 196
      # ===== 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)
197
        if limit = options[:limit]
198
          sql << " LIMIT #{sanitize_limit(limit)}"
199
          if offset = options[:offset]
200
            sql << " OFFSET #{offset.to_i}"
201 202
          end
        end
203 204 205
        sql
      end

206 207
      # Appends a locking clause to an SQL statement.
      # This method *modifies* the +sql+ parameter.
208 209 210 211 212
      #   # SELECT * FROM suppliers FOR UPDATE
      #   add_lock! 'SELECT * FROM suppliers', :lock => true
      #   add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE'
      def add_lock!(sql, options)
        case lock = options[:lock]
213 214
          when true;   sql << ' FOR UPDATE'
          when String; sql << " #{lock}"
215 216 217
        end
      end

218 219 220 221 222 223 224 225
      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
226

227
      # Inserts the given fixture into the table. Overridden in adapters that require
228 229
      # something beyond a simple insert (eg. Oracle).
      def insert_fixture(fixture, table_name)
230
        execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
231 232
      end

233
      def empty_insert_statement(table_name)
234
        "INSERT INTO #{quote_table_name(table_name)} VALUES(DEFAULT)"
235 236
      end

237 238 239 240
      def case_sensitive_equality_operator
        "="
      end

241 242 243 244
      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

245 246 247 248 249
      protected
        # Returns an array of record hashes with the column names as keys and
        # column values as values.
        def select(sql, name = nil)
        end
250
        undef_method :select
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266

        # 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
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281

        # 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
282 283
    end
  end
284
end