sequel 3.26.0 → 3.27.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGELOG +26 -0
  2. data/Rakefile +2 -3
  3. data/doc/mass_assignment.rdoc +54 -0
  4. data/doc/migration.rdoc +9 -533
  5. data/doc/prepared_statements.rdoc +8 -7
  6. data/doc/release_notes/3.27.0.txt +82 -0
  7. data/doc/schema_modification.rdoc +547 -0
  8. data/doc/testing.rdoc +64 -0
  9. data/lib/sequel/adapters/amalgalite.rb +4 -0
  10. data/lib/sequel/adapters/jdbc.rb +3 -1
  11. data/lib/sequel/adapters/jdbc/h2.rb +11 -5
  12. data/lib/sequel/adapters/mysql.rb +4 -122
  13. data/lib/sequel/adapters/mysql2.rb +4 -13
  14. data/lib/sequel/adapters/odbc.rb +4 -1
  15. data/lib/sequel/adapters/odbc/db2.rb +21 -0
  16. data/lib/sequel/adapters/shared/mysql.rb +12 -0
  17. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
  18. data/lib/sequel/adapters/tinytds.rb +122 -3
  19. data/lib/sequel/core.rb +4 -3
  20. data/lib/sequel/database/misc.rb +7 -10
  21. data/lib/sequel/dataset/misc.rb +1 -1
  22. data/lib/sequel/dataset/sql.rb +7 -0
  23. data/lib/sequel/model/associations.rb +2 -2
  24. data/lib/sequel/model/base.rb +60 -10
  25. data/lib/sequel/plugins/prepared_statements_safe.rb +17 -7
  26. data/lib/sequel/sql.rb +5 -0
  27. data/lib/sequel/timezones.rb +12 -3
  28. data/lib/sequel/version.rb +1 -1
  29. data/spec/adapters/mysql_spec.rb +25 -21
  30. data/spec/core/database_spec.rb +200 -0
  31. data/spec/core/dataset_spec.rb +6 -0
  32. data/spec/extensions/prepared_statements_safe_spec.rb +10 -0
  33. data/spec/extensions/schema_dumper_spec.rb +2 -2
  34. data/spec/integration/schema_test.rb +30 -1
  35. data/spec/integration/type_test.rb +10 -3
  36. data/spec/model/base_spec.rb +44 -0
  37. data/spec/model/model_spec.rb +14 -0
  38. data/spec/model/record_spec.rb +131 -12
  39. 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.
@@ -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, Java::JavaSQL::Time
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 expects hexadecimal strings for blob values
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/mysql utils/stored_procedures', 'adapters'
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
- class << conn
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 StoredProcedures
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
@@ -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.database_to_application_timestamp([now.year, now.month, now.day, v.hour, v.minute, v.second])
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