sequel 3.26.0 → 3.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +26 -0
- data/Rakefile +2 -3
- data/doc/mass_assignment.rdoc +54 -0
- data/doc/migration.rdoc +9 -533
- data/doc/prepared_statements.rdoc +8 -7
- data/doc/release_notes/3.27.0.txt +82 -0
- data/doc/schema_modification.rdoc +547 -0
- data/doc/testing.rdoc +64 -0
- data/lib/sequel/adapters/amalgalite.rb +4 -0
- data/lib/sequel/adapters/jdbc.rb +3 -1
- data/lib/sequel/adapters/jdbc/h2.rb +11 -5
- data/lib/sequel/adapters/mysql.rb +4 -122
- data/lib/sequel/adapters/mysql2.rb +4 -13
- data/lib/sequel/adapters/odbc.rb +4 -1
- data/lib/sequel/adapters/odbc/db2.rb +21 -0
- data/lib/sequel/adapters/shared/mysql.rb +12 -0
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
- data/lib/sequel/adapters/tinytds.rb +122 -3
- data/lib/sequel/core.rb +4 -3
- data/lib/sequel/database/misc.rb +7 -10
- data/lib/sequel/dataset/misc.rb +1 -1
- data/lib/sequel/dataset/sql.rb +7 -0
- data/lib/sequel/model/associations.rb +2 -2
- data/lib/sequel/model/base.rb +60 -10
- data/lib/sequel/plugins/prepared_statements_safe.rb +17 -7
- data/lib/sequel/sql.rb +5 -0
- data/lib/sequel/timezones.rb +12 -3
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mysql_spec.rb +25 -21
- data/spec/core/database_spec.rb +200 -0
- data/spec/core/dataset_spec.rb +6 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +10 -0
- data/spec/extensions/schema_dumper_spec.rb +2 -2
- data/spec/integration/schema_test.rb +30 -1
- data/spec/integration/type_test.rb +10 -3
- data/spec/model/base_spec.rb +44 -0
- data/spec/model/model_spec.rb +14 -0
- data/spec/model/record_spec.rb +131 -12
- metadata +14 -4
    
        data/doc/testing.rdoc
    ADDED
    
    | @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            = Testing with Sequel
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Whether or not you use Sequel in your application, you are usually going to want to have tests that ensure that your code works.  When you are using Sequel, it's helpful to integrate it into your testing framework, and it's generally best to run each test in its own transaction if possible.  That keeps all tests isolated from each other, and it's simple as it handles all of the cleanup for you.  Sequel doesn't ship with helpers for common libraries, as the exact code you need is often application-specific, but this page offers some examples that you can either use directly or build on.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            == Transactional tests
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            These run each test in its own transaction, the recommended way to test.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            === RSpec 1
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              class Spec::Example::ExampleGroup
         | 
| 12 | 
            +
                def execute(*args, &block)
         | 
| 13 | 
            +
                  x = nil
         | 
| 14 | 
            +
                  Sequel::Model.db.transaction{x = super(*args, &block); raise Sequel::Rollback}
         | 
| 15 | 
            +
                  x
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            === RSpec 2
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              class Spec::Example::ExampleGroup
         | 
| 22 | 
            +
                around do |example|
         | 
| 23 | 
            +
                  Sequel::Model.db.transaction{example.call; raise Sequel::Rollback}
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            === Test::Unit
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              # Must use this class as the base class for your tests
         | 
| 30 | 
            +
              class SequelTestCase < Test::Unit::TestCase
         | 
| 31 | 
            +
                def run(*args, &block)
         | 
| 32 | 
            +
                  Sequel::Model.db.transaction do
         | 
| 33 | 
            +
                    super
         | 
| 34 | 
            +
                    raise Sequel::Rollback
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            == Nontransactional tests
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            In some cases, it is not possible to use transactions.  For example, if you are testing a web application that is running in a separate process, you don't have access to that process's database connections, so you can't run your examples in transactions.  In that case, the best way to handle things is to cleanup after each test by deleting or truncating the database tables used in the test.
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            The order in which you delete/truncate the tables is important if you are using referential integrity in your database (which you probably should be doing).  If you are using referential integrity, you need to make sure to delete in tables referencing other tables before the tables that are being referenced.  For example, if you have an +albums+ table with an +artist_id+ field referencing the +artists+ table, you want to delete/truncate the +albums+ table before the +artists+ table.  Note that if you have cyclic references in your database, you will probably need to write your own custom cleaning code.
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            === RSpec
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              class Spec::Example::ExampleGroup
         | 
| 48 | 
            +
                after do
         | 
| 49 | 
            +
                  [:table1, :table2].each{|x| Sequel::Model.db.from(x).truncate}
         | 
| 50 | 
            +
                  # or
         | 
| 51 | 
            +
                  [:table1, :table2].each{|x| Sequel::Model.db.from(x).delete}
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            === Test::Unit
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              # Must use this class as the base class for your tests
         | 
| 58 | 
            +
              class SequelTestCase < Test::Unit::TestCase
         | 
| 59 | 
            +
                def teardown
         | 
| 60 | 
            +
                  [:table1, :table2].each{|x| Sequel::Model.db.from(x).truncate}
         | 
| 61 | 
            +
                  # or
         | 
| 62 | 
            +
                  [:table1, :table2].each{|x| Sequel::Model.db.from(x).delete}
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| @@ -31,6 +31,10 @@ module Sequel | |
| 31 31 | 
             
                  def datetime(s)
         | 
| 32 32 | 
             
                    Sequel.database_to_application_timestamp(s)
         | 
| 33 33 | 
             
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def time(s)
         | 
| 36 | 
            +
                    Sequel.string_to_time(s)
         | 
| 37 | 
            +
                  end
         | 
| 34 38 |  | 
| 35 39 | 
             
                  # Don't raise an error if the value is a string and the declared
         | 
| 36 40 | 
             
                  # type doesn't match a known type, just return the value.
         | 
    
        data/lib/sequel/adapters/jdbc.rb
    CHANGED
    
    | @@ -579,8 +579,10 @@ module Sequel | |
| 579 579 | 
             
                  # Convert the type.  Used for converting Java types to ruby types.
         | 
| 580 580 | 
             
                  def convert_type(v)
         | 
| 581 581 | 
             
                    case v
         | 
| 582 | 
            -
                    when Java::JavaSQL::Timestamp | 
| 582 | 
            +
                    when Java::JavaSQL::Timestamp
         | 
| 583 583 | 
             
                      Sequel.database_to_application_timestamp(v.to_string)
         | 
| 584 | 
            +
                    when Java::JavaSQL::Time
         | 
| 585 | 
            +
                      Sequel.string_to_time(v.to_string)
         | 
| 584 586 | 
             
                    when Java::JavaSQL::Date
         | 
| 585 587 | 
             
                      Sequel.string_to_date(v.to_string)
         | 
| 586 588 | 
             
                    when Java::JavaIo::BufferedReader
         | 
| @@ -151,11 +151,7 @@ module Sequel | |
| 151 151 |  | 
| 152 152 | 
             
                    private
         | 
| 153 153 |  | 
| 154 | 
            -
                    # H2  | 
| 155 | 
            -
                    def literal_blob(v)
         | 
| 156 | 
            -
                      literal_string v.unpack("H*").first
         | 
| 157 | 
            -
                    end
         | 
| 158 | 
            -
                    
         | 
| 154 | 
            +
                    # Handle H2 specific clobs as strings.
         | 
| 159 155 | 
             
                    def convert_type(v)
         | 
| 160 156 | 
             
                      case v
         | 
| 161 157 | 
             
                      when Java::OrgH2Jdbc::JdbcClob
         | 
| @@ -165,6 +161,16 @@ module Sequel | |
| 165 161 | 
             
                      end
         | 
| 166 162 | 
             
                    end
         | 
| 167 163 |  | 
| 164 | 
            +
                    # H2 expects hexadecimal strings for blob values
         | 
| 165 | 
            +
                    def literal_blob(v)
         | 
| 166 | 
            +
                      literal_string v.unpack("H*").first
         | 
| 167 | 
            +
                    end
         | 
| 168 | 
            +
                    
         | 
| 169 | 
            +
                    # H2 handles fractional seconds in timestamps, but not in times
         | 
| 170 | 
            +
                    def literal_sqltime(v)
         | 
| 171 | 
            +
                      v.strftime("'%H:%M:%S'")
         | 
| 172 | 
            +
                    end
         | 
| 173 | 
            +
             | 
| 168 174 | 
             
                    def select_clause_methods
         | 
| 169 175 | 
             
                      SELECT_CLAUSE_METHODS
         | 
| 170 176 | 
             
                    end
         | 
| @@ -5,7 +5,7 @@ rescue LoadError | |
| 5 5 | 
             
            end
         | 
| 6 6 | 
             
            raise(LoadError, "require 'mysql' did not define Mysql::CLIENT_MULTI_RESULTS!\n  You are probably using the pure ruby mysql.rb driver,\n  which Sequel does not support. You need to install\n  the C based adapter, and make sure that the mysql.so\n  file is loaded instead of the mysql.rb file.\n") unless defined?(Mysql::CLIENT_MULTI_RESULTS)
         | 
| 7 7 |  | 
| 8 | 
            -
            Sequel.require %w'shared/ | 
| 8 | 
            +
            Sequel.require %w'shared/mysql_prepared_statements', 'adapters'
         | 
| 9 9 |  | 
| 10 10 | 
             
            module Sequel
         | 
| 11 11 | 
             
              # Module for holding all MySQL-related classes and modules for Sequel.
         | 
| @@ -84,18 +84,13 @@ module Sequel | |
| 84 84 | 
             
                # Database class for MySQL databases used with Sequel.
         | 
| 85 85 | 
             
                class Database < Sequel::Database
         | 
| 86 86 | 
             
                  include Sequel::MySQL::DatabaseMethods
         | 
| 87 | 
            +
                  include Sequel::MySQL::PreparedStatements::DatabaseMethods
         | 
| 87 88 |  | 
| 88 89 | 
             
                  # Mysql::Error messages that indicate the current connection should be disconnected
         | 
| 89 90 | 
             
                  MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away|Lost connection to MySQL server during query)/
         | 
| 90 91 |  | 
| 91 92 | 
             
                  set_adapter_scheme :mysql
         | 
| 92 93 |  | 
| 93 | 
            -
                  # Support stored procedures on MySQL
         | 
| 94 | 
            -
                  def call_sproc(name, opts={}, &block)
         | 
| 95 | 
            -
                    args = opts[:args] || [] 
         | 
| 96 | 
            -
                    execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block)
         | 
| 97 | 
            -
                  end
         | 
| 98 | 
            -
                  
         | 
| 99 94 | 
             
                  # Connect to the database.  In addition to the usual database options,
         | 
| 100 95 | 
             
                  # the following options have effect:
         | 
| 101 96 | 
             
                  #
         | 
| @@ -161,10 +156,7 @@ module Sequel | |
| 161 156 |  | 
| 162 157 | 
             
                    sqls.each{|sql| log_yield(sql){conn.query(sql)}}
         | 
| 163 158 |  | 
| 164 | 
            -
                     | 
| 165 | 
            -
                      attr_accessor :prepared_statements
         | 
| 166 | 
            -
                    end
         | 
| 167 | 
            -
                    conn.prepared_statements = {}
         | 
| 159 | 
            +
                    add_prepared_statements_cache(conn)
         | 
| 168 160 | 
             
                    conn
         | 
| 169 161 | 
             
                  end
         | 
| 170 162 |  | 
| @@ -173,18 +165,6 @@ module Sequel | |
| 173 165 | 
             
                    MySQL::Dataset.new(self, opts)
         | 
| 174 166 | 
             
                  end
         | 
| 175 167 |  | 
| 176 | 
            -
                  # Executes the given SQL using an available connection, yielding the
         | 
| 177 | 
            -
                  # connection if the block is given.
         | 
| 178 | 
            -
                  def execute(sql, opts={}, &block)
         | 
| 179 | 
            -
                    if opts[:sproc]
         | 
| 180 | 
            -
                      call_sproc(sql, opts, &block)
         | 
| 181 | 
            -
                    elsif sql.is_a?(Symbol)
         | 
| 182 | 
            -
                      execute_prepared_statement(sql, opts, &block)
         | 
| 183 | 
            -
                    else
         | 
| 184 | 
            -
                      synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
         | 
| 185 | 
            -
                    end
         | 
| 186 | 
            -
                  end
         | 
| 187 | 
            -
                  
         | 
| 188 168 | 
             
                  # Return the version of the MySQL server two which we are connecting.
         | 
| 189 169 | 
             
                  def server_version(server=nil)
         | 
| 190 170 | 
             
                    @server_version ||= (synchronize(server){|conn| conn.server_version if conn.respond_to?(:server_version)} || super)
         | 
| @@ -268,27 +248,6 @@ module Sequel | |
| 268 248 | 
             
                    nil
         | 
| 269 249 | 
             
                  end
         | 
| 270 250 |  | 
| 271 | 
            -
                  # Executes a prepared statement on an available connection.  If the
         | 
| 272 | 
            -
                  # prepared statement already exists for the connection and has the same
         | 
| 273 | 
            -
                  # SQL, reuse it, otherwise, prepare the new statement.  Because of the
         | 
| 274 | 
            -
                  # usual MySQL stupidity, we are forced to name arguments via separate
         | 
| 275 | 
            -
                  # SET queries.  Use @sequel_arg_N (for N starting at 1) for these
         | 
| 276 | 
            -
                  # arguments.
         | 
| 277 | 
            -
                  def execute_prepared_statement(ps_name, opts, &block)
         | 
| 278 | 
            -
                    args = opts[:arguments]
         | 
| 279 | 
            -
                    ps = prepared_statements[ps_name]
         | 
| 280 | 
            -
                    sql = ps.prepared_sql
         | 
| 281 | 
            -
                    synchronize(opts[:server]) do |conn|
         | 
| 282 | 
            -
                      unless conn.prepared_statements[ps_name] == sql
         | 
| 283 | 
            -
                        conn.prepared_statements[ps_name] = sql
         | 
| 284 | 
            -
                        _execute(conn, "PREPARE #{ps_name} FROM '#{::Mysql.quote(sql)}'", opts)
         | 
| 285 | 
            -
                      end
         | 
| 286 | 
            -
                      i = 0
         | 
| 287 | 
            -
                      _execute(conn, "SET " + args.map {|arg| "@sequel_arg_#{i+=1} = #{literal(arg)}"}.join(", "), opts) unless args.empty?
         | 
| 288 | 
            -
                      _execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
         | 
| 289 | 
            -
                    end
         | 
| 290 | 
            -
                  end
         | 
| 291 | 
            -
                  
         | 
| 292 251 | 
             
                  # Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
         | 
| 293 252 | 
             
                  def schema_column_type(db_type)
         | 
| 294 253 | 
             
                    Sequel::MySQL.convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
         | 
| @@ -298,67 +257,7 @@ module Sequel | |
| 298 257 | 
             
                # Dataset class for MySQL datasets accessed via the native driver.
         | 
| 299 258 | 
             
                class Dataset < Sequel::Dataset
         | 
| 300 259 | 
             
                  include Sequel::MySQL::DatasetMethods
         | 
| 301 | 
            -
                  include  | 
| 302 | 
            -
                  
         | 
| 303 | 
            -
                  # Methods to add to MySQL prepared statement calls without using a
         | 
| 304 | 
            -
                  # real database prepared statement and bound variables.
         | 
| 305 | 
            -
                  module CallableStatementMethods
         | 
| 306 | 
            -
                    # Extend given dataset with this module so subselects inside subselects in
         | 
| 307 | 
            -
                    # prepared statements work.
         | 
| 308 | 
            -
                    def subselect_sql(ds)
         | 
| 309 | 
            -
                      ps = ds.to_prepared_statement(:select)
         | 
| 310 | 
            -
                      ps.extend(CallableStatementMethods)
         | 
| 311 | 
            -
                      ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
         | 
| 312 | 
            -
                      ps.prepared_args = prepared_args
         | 
| 313 | 
            -
                      ps.prepared_sql
         | 
| 314 | 
            -
                    end
         | 
| 315 | 
            -
                  end
         | 
| 316 | 
            -
                  
         | 
| 317 | 
            -
                  # Methods for MySQL prepared statements using the native driver.
         | 
| 318 | 
            -
                  module PreparedStatementMethods
         | 
| 319 | 
            -
                    include Sequel::Dataset::UnnumberedArgumentMapper
         | 
| 320 | 
            -
                    
         | 
| 321 | 
            -
                    private
         | 
| 322 | 
            -
                    
         | 
| 323 | 
            -
                    # Execute the prepared statement with the bind arguments instead of
         | 
| 324 | 
            -
                    # the given SQL.
         | 
| 325 | 
            -
                    def execute(sql, opts={}, &block)
         | 
| 326 | 
            -
                      super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
         | 
| 327 | 
            -
                    end
         | 
| 328 | 
            -
                    
         | 
| 329 | 
            -
                    # Same as execute, explicit due to intricacies of alias and super.
         | 
| 330 | 
            -
                    def execute_dui(sql, opts={}, &block)
         | 
| 331 | 
            -
                      super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
         | 
| 332 | 
            -
                    end
         | 
| 333 | 
            -
                  end
         | 
| 334 | 
            -
                  
         | 
| 335 | 
            -
                  # Methods for MySQL stored procedures using the native driver.
         | 
| 336 | 
            -
                  module StoredProcedureMethods
         | 
| 337 | 
            -
                    include Sequel::Dataset::StoredProcedureMethods
         | 
| 338 | 
            -
                    
         | 
| 339 | 
            -
                    private
         | 
| 340 | 
            -
                    
         | 
| 341 | 
            -
                    # Execute the database stored procedure with the stored arguments.
         | 
| 342 | 
            -
                    def execute(sql, opts={}, &block)
         | 
| 343 | 
            -
                      super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
         | 
| 344 | 
            -
                    end
         | 
| 345 | 
            -
                    
         | 
| 346 | 
            -
                    # Same as execute, explicit due to intricacies of alias and super.
         | 
| 347 | 
            -
                    def execute_dui(sql, opts={}, &block)
         | 
| 348 | 
            -
                      super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
         | 
| 349 | 
            -
                    end
         | 
| 350 | 
            -
                  end
         | 
| 351 | 
            -
                  
         | 
| 352 | 
            -
                  # MySQL is different in that it supports prepared statements but not bound
         | 
| 353 | 
            -
                  # variables outside of prepared statements.  The default implementation
         | 
| 354 | 
            -
                  # breaks the use of subselects in prepared statements, so extend the
         | 
| 355 | 
            -
                  # temporary prepared statement that this creates with a module that
         | 
| 356 | 
            -
                  # fixes it.
         | 
| 357 | 
            -
                  def call(type, bind_arguments={}, *values, &block)
         | 
| 358 | 
            -
                    ps = to_prepared_statement(type, values)
         | 
| 359 | 
            -
                    ps.extend(CallableStatementMethods)
         | 
| 360 | 
            -
                    ps.call(bind_arguments, &block)
         | 
| 361 | 
            -
                  end
         | 
| 260 | 
            +
                  include Sequel::MySQL::PreparedStatements::DatasetMethods
         | 
| 362 261 |  | 
| 363 262 | 
             
                  # Delete rows matching this dataset
         | 
| 364 263 | 
             
                  def delete
         | 
| @@ -401,18 +300,6 @@ module Sequel | |
| 401 300 | 
             
                    execute_dui(insert_sql(*values)){|c| return c.insert_id}
         | 
| 402 301 | 
             
                  end
         | 
| 403 302 |  | 
| 404 | 
            -
                  # Store the given type of prepared statement in the associated database
         | 
| 405 | 
            -
                  # with the given name.
         | 
| 406 | 
            -
                  def prepare(type, name=nil, *values)
         | 
| 407 | 
            -
                    ps = to_prepared_statement(type, values)
         | 
| 408 | 
            -
                    ps.extend(PreparedStatementMethods)
         | 
| 409 | 
            -
                    if name
         | 
| 410 | 
            -
                      ps.prepared_statement_name = name
         | 
| 411 | 
            -
                      db.prepared_statements[name] = ps
         | 
| 412 | 
            -
                    end
         | 
| 413 | 
            -
                    ps
         | 
| 414 | 
            -
                  end
         | 
| 415 | 
            -
                  
         | 
| 416 303 | 
             
                  # Replace (update or insert) the matching row.
         | 
| 417 304 | 
             
                  def replace(*args)
         | 
| 418 305 | 
             
                    execute_dui(replace_sql(*args)){|c| return c.insert_id}
         | 
| @@ -456,11 +343,6 @@ module Sequel | |
| 456 343 | 
             
                    "'#{::Mysql.quote(v)}'"
         | 
| 457 344 | 
             
                  end
         | 
| 458 345 |  | 
| 459 | 
            -
                  # Extend the dataset with the MySQL stored procedure methods.
         | 
| 460 | 
            -
                  def prepare_extend_sproc(ds)
         | 
| 461 | 
            -
                    ds.extend(StoredProcedureMethods)
         | 
| 462 | 
            -
                  end
         | 
| 463 | 
            -
                  
         | 
| 464 346 | 
             
                  # Yield each row of the given result set r with columns cols
         | 
| 465 347 | 
             
                  # as a hash with symbol keys
         | 
| 466 348 | 
             
                  def yield_rows(r, cols)
         | 
| @@ -1,6 +1,5 @@ | |
| 1 1 | 
             
            require 'mysql2' unless defined? Mysql2
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            Sequel.require %w'shared/mysql', 'adapters'
         | 
| 2 | 
            +
            Sequel.require %w'shared/mysql_prepared_statements', 'adapters'
         | 
| 4 3 |  | 
| 5 4 | 
             
            module Sequel
         | 
| 6 5 | 
             
              # Module for holding all Mysql2-related classes and modules for Sequel.
         | 
| @@ -8,6 +7,7 @@ module Sequel | |
| 8 7 | 
             
                # Database class for MySQL databases used with Sequel.
         | 
| 9 8 | 
             
                class Database < Sequel::Database
         | 
| 10 9 | 
             
                  include Sequel::MySQL::DatabaseMethods
         | 
| 10 | 
            +
                  include Sequel::MySQL::PreparedStatements::DatabaseMethods
         | 
| 11 11 |  | 
| 12 12 | 
             
                  # Mysql::Error messages that indicate the current connection should be disconnected
         | 
| 13 13 | 
             
                  MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away)/
         | 
| @@ -21,7 +21,6 @@ module Sequel | |
| 21 21 | 
             
                  #   a filter for an autoincrement column equals NULL to return the last
         | 
| 22 22 | 
             
                  #   inserted row.
         | 
| 23 23 | 
             
                  # * :charset - Same as :encoding (:encoding takes precendence)
         | 
| 24 | 
            -
                  # * :compress - Set to false to not compress results from the server
         | 
| 25 24 | 
             
                  # * :config_default_group - The default group to read from the in
         | 
| 26 25 | 
             
                  #   the MySQL config file.
         | 
| 27 26 | 
             
                  # * :config_local_infile - If provided, sets the Mysql::OPT_LOCAL_INFILE
         | 
| @@ -57,6 +56,7 @@ module Sequel | |
| 57 56 |  | 
| 58 57 | 
             
                    sqls.each{|sql| log_yield(sql){conn.query(sql)}}
         | 
| 59 58 |  | 
| 59 | 
            +
                    add_prepared_statements_cache(conn)
         | 
| 60 60 | 
             
                    conn
         | 
| 61 61 | 
             
                  end
         | 
| 62 62 |  | 
| @@ -65,16 +65,6 @@ module Sequel | |
| 65 65 | 
             
                    Mysql2::Dataset.new(self, opts)
         | 
| 66 66 | 
             
                  end
         | 
| 67 67 |  | 
| 68 | 
            -
                  # Executes the given SQL using an available connection, yielding the
         | 
| 69 | 
            -
                  # connection if the block is given.
         | 
| 70 | 
            -
                  def execute(sql, opts={}, &block)
         | 
| 71 | 
            -
                    if opts[:sproc]
         | 
| 72 | 
            -
                      call_sproc(sql, opts, &block)
         | 
| 73 | 
            -
                    else
         | 
| 74 | 
            -
                      synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
         | 
| 75 | 
            -
                    end
         | 
| 76 | 
            -
                  end
         | 
| 77 | 
            -
             | 
| 78 68 | 
             
                  # Return the version of the MySQL server two which we are connecting.
         | 
| 79 69 | 
             
                  def server_version(server=nil)
         | 
| 80 70 | 
             
                    @server_version ||= (synchronize(server){|conn| conn.server_info[:id]} || super)
         | 
| @@ -132,6 +122,7 @@ module Sequel | |
| 132 122 | 
             
                # Dataset class for MySQL datasets accessed via the native driver.
         | 
| 133 123 | 
             
                class Dataset < Sequel::Dataset
         | 
| 134 124 | 
             
                  include Sequel::MySQL::DatasetMethods
         | 
| 125 | 
            +
                  include Sequel::MySQL::PreparedStatements::DatasetMethods
         | 
| 135 126 |  | 
| 136 127 | 
             
                  # Delete rows matching this dataset
         | 
| 137 128 | 
             
                  def delete
         | 
    
        data/lib/sequel/adapters/odbc.rb
    CHANGED
    
    | @@ -19,6 +19,9 @@ module Sequel | |
| 19 19 | 
             
                    when 'progress'
         | 
| 20 20 | 
             
                      Sequel.ts_require 'adapters/shared/progress'
         | 
| 21 21 | 
             
                      extend Sequel::Progress::DatabaseMethods
         | 
| 22 | 
            +
                    when 'db2'
         | 
| 23 | 
            +
                      Sequel.ts_require 'adapters/odbc/db2'
         | 
| 24 | 
            +
                      extend Sequel::ODBC::DB2::DatabaseMethods
         | 
| 22 25 | 
             
                    end
         | 
| 23 26 | 
             
                  end
         | 
| 24 27 |  | 
| @@ -126,7 +129,7 @@ module Sequel | |
| 126 129 | 
             
                      Sequel.database_to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second])
         | 
| 127 130 | 
             
                    when ::ODBC::Time
         | 
| 128 131 | 
             
                      now = ::Time.now
         | 
| 129 | 
            -
                      Sequel. | 
| 132 | 
            +
                      Sequel::SQLTime.local(now.year, now.month, now.day, v.hour, v.minute, v.second)
         | 
| 130 133 | 
             
                    when ::ODBC::Date
         | 
| 131 134 | 
             
                      Date.new(v.year, v.month, v.day)
         | 
| 132 135 | 
             
                    else
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module Sequel
         | 
| 2 | 
            +
              module ODBC
         | 
| 3 | 
            +
                # Database and Dataset instance methods for DB2 specific
         | 
| 4 | 
            +
                # support via ODBC.
         | 
| 5 | 
            +
                module DB2
         | 
| 6 | 
            +
                  module DatabaseMethods
         | 
| 7 | 
            +
                    def dataset(opts=nil)
         | 
| 8 | 
            +
                      Sequel::ODBC::DB2::Dataset.new(self, opts)
         | 
| 9 | 
            +
                    end
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                  
         | 
| 12 | 
            +
                  class Dataset < ODBC::Dataset
         | 
| 13 | 
            +
                    def select_limit_sql(sql)
         | 
| 14 | 
            +
                      if l = @opts[:limit]
         | 
| 15 | 
            +
                        sql << " FETCH FIRST #{l == 1 ? 'ROW' : "#{literal(l)} ROWS"} ONLY"
         | 
| 16 | 
            +
                      end
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -154,6 +154,18 @@ module Sequel | |
| 154 154 | 
             
                      "ALTER TABLE #{quote_schema_table(table)} CHANGE COLUMN #{quote_identifier(op[:name])} #{column_definition_sql(op.merge(opts))}"
         | 
| 155 155 | 
             
                    when :drop_index
         | 
| 156 156 | 
             
                      "#{drop_index_sql(table, op)} ON #{quote_schema_table(table)}"
         | 
| 157 | 
            +
                    when :drop_constraint
         | 
| 158 | 
            +
                      type = case op[:type]
         | 
| 159 | 
            +
                      when :primary_key
         | 
| 160 | 
            +
                        return "ALTER TABLE #{quote_schema_table(table)} DROP PRIMARY KEY"
         | 
| 161 | 
            +
                      when :foreign_key
         | 
| 162 | 
            +
                        'FOREIGN KEY'
         | 
| 163 | 
            +
                      when :unique
         | 
| 164 | 
            +
                        'INDEX'
         | 
| 165 | 
            +
                      else
         | 
| 166 | 
            +
                        raise(Error, "must specify constraint type via :type=>(:foreign_key|:primary_key|:unique) when dropping constraints on MySQL")
         | 
| 167 | 
            +
                      end
         | 
| 168 | 
            +
                      "ALTER TABLE #{quote_schema_table(table)} DROP #{type} #{quote_identifier(op[:name])}"
         | 
| 157 169 | 
             
                    else
         | 
| 158 170 | 
             
                      super(table, op)
         | 
| 159 171 | 
             
                    end
         | 
| @@ -0,0 +1,143 @@ | |
| 1 | 
            +
            Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Sequel
         | 
| 4 | 
            +
              module MySQL
         | 
| 5 | 
            +
                # This module is used by the mysql and mysql2 adapters to support
         | 
| 6 | 
            +
                # prepared statements and stored procedures.
         | 
| 7 | 
            +
                module PreparedStatements
         | 
| 8 | 
            +
                  module DatabaseMethods
         | 
| 9 | 
            +
                    # Support stored procedures on MySQL
         | 
| 10 | 
            +
                    def call_sproc(name, opts={}, &block)
         | 
| 11 | 
            +
                      args = opts[:args] || [] 
         | 
| 12 | 
            +
                      execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block)
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                    
         | 
| 15 | 
            +
                    # Executes the given SQL using an available connection, yielding the
         | 
| 16 | 
            +
                    # connection if the block is given.
         | 
| 17 | 
            +
                    def execute(sql, opts={}, &block)
         | 
| 18 | 
            +
                      if opts[:sproc]
         | 
| 19 | 
            +
                        call_sproc(sql, opts, &block)
         | 
| 20 | 
            +
                      elsif sql.is_a?(Symbol)
         | 
| 21 | 
            +
                        execute_prepared_statement(sql, opts, &block)
         | 
| 22 | 
            +
                      else
         | 
| 23 | 
            +
                        synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                    
         | 
| 27 | 
            +
                    private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    def add_prepared_statements_cache(conn)
         | 
| 30 | 
            +
                      class << conn
         | 
| 31 | 
            +
                        attr_accessor :prepared_statements
         | 
| 32 | 
            +
                      end
         | 
| 33 | 
            +
                      conn.prepared_statements = {}
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    # Executes a prepared statement on an available connection.  If the
         | 
| 37 | 
            +
                    # prepared statement already exists for the connection and has the same
         | 
| 38 | 
            +
                    # SQL, reuse it, otherwise, prepare the new statement.  Because of the
         | 
| 39 | 
            +
                    # usual MySQL stupidity, we are forced to name arguments via separate
         | 
| 40 | 
            +
                    # SET queries.  Use @sequel_arg_N (for N starting at 1) for these
         | 
| 41 | 
            +
                    # arguments.
         | 
| 42 | 
            +
                    def execute_prepared_statement(ps_name, opts, &block)
         | 
| 43 | 
            +
                      args = opts[:arguments]
         | 
| 44 | 
            +
                      ps = prepared_statements[ps_name]
         | 
| 45 | 
            +
                      sql = ps.prepared_sql
         | 
| 46 | 
            +
                      synchronize(opts[:server]) do |conn|
         | 
| 47 | 
            +
                        unless conn.prepared_statements[ps_name] == sql
         | 
| 48 | 
            +
                          conn.prepared_statements[ps_name] = sql
         | 
| 49 | 
            +
                          _execute(conn, "PREPARE #{ps_name} FROM #{literal(sql)}", opts)
         | 
| 50 | 
            +
                        end
         | 
| 51 | 
            +
                        i = 0
         | 
| 52 | 
            +
                        _execute(conn, "SET " + args.map {|arg| "@sequel_arg_#{i+=1} = #{literal(arg)}"}.join(", "), opts) unless args.empty?
         | 
| 53 | 
            +
                        _execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
         | 
| 54 | 
            +
                      end
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
                    
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                  module DatasetMethods
         | 
| 59 | 
            +
                    include Sequel::Dataset::StoredProcedures
         | 
| 60 | 
            +
                   
         | 
| 61 | 
            +
                    # Methods to add to MySQL prepared statement calls without using a
         | 
| 62 | 
            +
                    # real database prepared statement and bound variables.
         | 
| 63 | 
            +
                    module CallableStatementMethods
         | 
| 64 | 
            +
                      # Extend given dataset with this module so subselects inside subselects in
         | 
| 65 | 
            +
                      # prepared statements work.
         | 
| 66 | 
            +
                      def subselect_sql(ds)
         | 
| 67 | 
            +
                        ps = ds.to_prepared_statement(:select)
         | 
| 68 | 
            +
                        ps.extend(CallableStatementMethods)
         | 
| 69 | 
            +
                        ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
         | 
| 70 | 
            +
                        ps.prepared_args = prepared_args
         | 
| 71 | 
            +
                        ps.prepared_sql
         | 
| 72 | 
            +
                      end
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
                    
         | 
| 75 | 
            +
                    # Methods for MySQL prepared statements using the native driver.
         | 
| 76 | 
            +
                    module PreparedStatementMethods
         | 
| 77 | 
            +
                      include Sequel::Dataset::UnnumberedArgumentMapper
         | 
| 78 | 
            +
                      
         | 
| 79 | 
            +
                      private
         | 
| 80 | 
            +
                      
         | 
| 81 | 
            +
                      # Execute the prepared statement with the bind arguments instead of
         | 
| 82 | 
            +
                      # the given SQL.
         | 
| 83 | 
            +
                      def execute(sql, opts={}, &block)
         | 
| 84 | 
            +
                        super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
         | 
| 85 | 
            +
                      end
         | 
| 86 | 
            +
                      
         | 
| 87 | 
            +
                      # Same as execute, explicit due to intricacies of alias and super.
         | 
| 88 | 
            +
                      def execute_dui(sql, opts={}, &block)
         | 
| 89 | 
            +
                        super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
         | 
| 90 | 
            +
                      end
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
                    
         | 
| 93 | 
            +
                    # Methods for MySQL stored procedures using the native driver.
         | 
| 94 | 
            +
                    module StoredProcedureMethods
         | 
| 95 | 
            +
                      include Sequel::Dataset::StoredProcedureMethods
         | 
| 96 | 
            +
                      
         | 
| 97 | 
            +
                      private
         | 
| 98 | 
            +
                      
         | 
| 99 | 
            +
                      # Execute the database stored procedure with the stored arguments.
         | 
| 100 | 
            +
                      def execute(sql, opts={}, &block)
         | 
| 101 | 
            +
                        super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
         | 
| 102 | 
            +
                      end
         | 
| 103 | 
            +
                      
         | 
| 104 | 
            +
                      # Same as execute, explicit due to intricacies of alias and super.
         | 
| 105 | 
            +
                      def execute_dui(sql, opts={}, &block)
         | 
| 106 | 
            +
                        super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
         | 
| 107 | 
            +
                      end
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
                    
         | 
| 110 | 
            +
                    # MySQL is different in that it supports prepared statements but not bound
         | 
| 111 | 
            +
                    # variables outside of prepared statements.  The default implementation
         | 
| 112 | 
            +
                    # breaks the use of subselects in prepared statements, so extend the
         | 
| 113 | 
            +
                    # temporary prepared statement that this creates with a module that
         | 
| 114 | 
            +
                    # fixes it.
         | 
| 115 | 
            +
                    def call(type, bind_arguments={}, *values, &block)
         | 
| 116 | 
            +
                      ps = to_prepared_statement(type, values)
         | 
| 117 | 
            +
                      ps.extend(CallableStatementMethods)
         | 
| 118 | 
            +
                      ps.call(bind_arguments, &block)
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
                    
         | 
| 121 | 
            +
                    # Store the given type of prepared statement in the associated database
         | 
| 122 | 
            +
                    # with the given name.
         | 
| 123 | 
            +
                    def prepare(type, name=nil, *values)
         | 
| 124 | 
            +
                      ps = to_prepared_statement(type, values)
         | 
| 125 | 
            +
                      ps.extend(PreparedStatementMethods)
         | 
| 126 | 
            +
                      if name
         | 
| 127 | 
            +
                        ps.prepared_statement_name = name
         | 
| 128 | 
            +
                        db.prepared_statements[name] = ps
         | 
| 129 | 
            +
                      end
         | 
| 130 | 
            +
                      ps
         | 
| 131 | 
            +
                    end
         | 
| 132 | 
            +
                    
         | 
| 133 | 
            +
                    private
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    # Extend the dataset with the MySQL stored procedure methods.
         | 
| 136 | 
            +
                    def prepare_extend_sproc(ds)
         | 
| 137 | 
            +
                      ds.extend(StoredProcedureMethods)
         | 
| 138 | 
            +
                    end
         | 
| 139 | 
            +
                    
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
              end
         | 
| 143 | 
            +
            end
         |