sequel 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGELOG +16 -0
  2. data/README +4 -1
  3. data/Rakefile +17 -19
  4. data/doc/prepared_statements.rdoc +104 -0
  5. data/doc/sharding.rdoc +113 -0
  6. data/lib/sequel_core/adapters/ado.rb +24 -17
  7. data/lib/sequel_core/adapters/db2.rb +30 -33
  8. data/lib/sequel_core/adapters/dbi.rb +15 -13
  9. data/lib/sequel_core/adapters/informix.rb +13 -14
  10. data/lib/sequel_core/adapters/jdbc.rb +243 -60
  11. data/lib/sequel_core/adapters/jdbc/mysql.rb +32 -24
  12. data/lib/sequel_core/adapters/jdbc/postgresql.rb +32 -2
  13. data/lib/sequel_core/adapters/jdbc/sqlite.rb +16 -20
  14. data/lib/sequel_core/adapters/mysql.rb +164 -76
  15. data/lib/sequel_core/adapters/odbc.rb +21 -34
  16. data/lib/sequel_core/adapters/openbase.rb +10 -7
  17. data/lib/sequel_core/adapters/oracle.rb +17 -23
  18. data/lib/sequel_core/adapters/postgres.rb +246 -35
  19. data/lib/sequel_core/adapters/shared/mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/shared/mysql.rb +34 -26
  21. data/lib/sequel_core/adapters/shared/postgres.rb +82 -38
  22. data/lib/sequel_core/adapters/shared/sqlite.rb +48 -16
  23. data/lib/sequel_core/adapters/sqlite.rb +141 -44
  24. data/lib/sequel_core/connection_pool.rb +85 -63
  25. data/lib/sequel_core/database.rb +46 -17
  26. data/lib/sequel_core/dataset.rb +21 -40
  27. data/lib/sequel_core/dataset/convenience.rb +3 -3
  28. data/lib/sequel_core/dataset/prepared_statements.rb +218 -0
  29. data/lib/sequel_core/exceptions.rb +0 -12
  30. data/lib/sequel_model/base.rb +1 -2
  31. data/lib/sequel_model/plugins.rb +1 -1
  32. data/spec/adapters/ado_spec.rb +32 -3
  33. data/spec/adapters/mysql_spec.rb +7 -8
  34. data/spec/integration/prepared_statement_test.rb +106 -0
  35. data/spec/sequel_core/connection_pool_spec.rb +105 -3
  36. data/spec/sequel_core/database_spec.rb +41 -3
  37. data/spec/sequel_core/dataset_spec.rb +117 -7
  38. data/spec/sequel_core/spec_helper.rb +2 -2
  39. data/spec/sequel_model/model_spec.rb +0 -6
  40. data/spec/sequel_model/spec_helper.rb +1 -1
  41. metadata +11 -6
  42. data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -54
  43. data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
@@ -40,6 +40,9 @@ module Sequel
40
40
 
41
41
  # Whether to quote identifiers (columns and tables) for this database
42
42
  attr_writer :quote_identifiers
43
+
44
+ # The prepared statement objects for this database, keyed by name
45
+ attr_reader :prepared_statements
43
46
 
44
47
  # Constructs a new instance of a database connection with the specified
45
48
  # options hash.
@@ -51,8 +54,10 @@ module Sequel
51
54
  @quote_identifiers = opts.include?(:quote_identifiers) ? opts[:quote_identifiers] : @@quote_identifiers
52
55
  @single_threaded = opts.include?(:single_threaded) ? opts[:single_threaded] : @@single_threaded
53
56
  @schemas = nil
57
+ @prepared_statements = {}
58
+ @transactions = []
54
59
  @pool = (@single_threaded ? SingleThreadedPool : ConnectionPool).new(connection_pool_default_options.merge(opts), &block)
55
- @pool.connection_proc = proc {connect} unless block
60
+ @pool.connection_proc = proc{|server| connect(server)} unless block
56
61
 
57
62
  @loggers = Array(opts[:logger]) + Array(opts[:loggers])
58
63
  ::Sequel::DATABASES.push(self)
@@ -191,6 +196,12 @@ module Sequel
191
196
  (String === args.first) ? fetch(*args, &block) : from(*args, &block)
192
197
  end
193
198
 
199
+ # Call the prepared statement with the given name with the given hash
200
+ # of arguments.
201
+ def call(ps_name, hash={})
202
+ prepared_statements[ps_name].call(hash)
203
+ end
204
+
194
205
  # Connects to the database. This method should be overridden by descendants.
195
206
  def connect
196
207
  raise NotImplementedError, "#connect should be overridden by adapters"
@@ -208,20 +219,20 @@ module Sequel
208
219
  end
209
220
 
210
221
  # Executes the given SQL. This method should be overridden in descendants.
211
- def execute(sql)
222
+ def execute(sql, opts={})
212
223
  raise NotImplementedError, "#execute should be overridden by adapters"
213
224
  end
214
225
 
215
226
  # Method that should be used when submitting any DDL (Data Definition
216
227
  # Language) SQL. By default, calls execute_dui.
217
- def execute_ddl(sql)
218
- execute_dui(sql)
228
+ def execute_ddl(sql, opts={}, &block)
229
+ execute_dui(sql, opts, &block)
219
230
  end
220
231
 
221
232
  # Method that should be used when issuing a DELETE, UPDATE, or INSERT
222
233
  # statement. By default, calls execute.
223
- def execute_dui(sql)
224
- execute(sql)
234
+ def execute_dui(sql, opts={}, &block)
235
+ execute(sql, opts, &block)
225
236
  end
226
237
 
227
238
  # Fetches records for an arbitrary SQL statement. If a block is given,
@@ -273,7 +284,8 @@ module Sequel
273
284
 
274
285
  # Log a message at level info to all loggers. All SQL logging
275
286
  # goes through this method.
276
- def log_info(message)
287
+ def log_info(message, args=nil)
288
+ message = "#{message}; #{args.inspect}" if args
277
289
  @loggers.each{|logger| logger.info(message)}
278
290
  end
279
291
 
@@ -319,8 +331,8 @@ module Sequel
319
331
  end
320
332
 
321
333
  # Acquires a database connection, yielding it to the passed block.
322
- def synchronize(&block)
323
- @pool.hold(&block)
334
+ def synchronize(server=nil, &block)
335
+ @pool.hold(server || :default, &block)
324
336
  end
325
337
 
326
338
  # Returns true if a table with the given name exists.
@@ -339,8 +351,8 @@ module Sequel
339
351
 
340
352
  # Attempts to acquire a database connection. Returns true if successful.
341
353
  # Will probably raise an error if unsuccessful.
342
- def test_connection
343
- synchronize{|conn|}
354
+ def test_connection(server=nil)
355
+ synchronize(server){|conn|}
344
356
  true
345
357
  end
346
358
 
@@ -348,12 +360,9 @@ module Sequel
348
360
  # supported - calling #transaction within a transaction will reuse the
349
361
  # current transaction. Should be overridden for databases that support nested
350
362
  # transactions.
351
- def transaction
352
- @pool.hold do |conn|
353
- @transactions ||= []
354
- if @transactions.include? Thread.current
355
- return yield(conn)
356
- end
363
+ def transaction(server=nil)
364
+ synchronize(server) do |conn|
365
+ return yield(conn) if @transactions.include?(Thread.current)
357
366
  log_info(SQL_BEGIN)
358
367
  conn.execute(SQL_BEGIN)
359
368
  begin
@@ -461,6 +470,26 @@ module Sequel
461
470
  alias_method :url, :uri
462
471
 
463
472
  private
473
+
474
+ # Return the options for the given server by merging the generic
475
+ # options for all server with the specific options for the given
476
+ # server specified in the :servers option.
477
+ def server_opts(server)
478
+ opts = if @opts[:servers] && server_options = @opts[:servers][server]
479
+ case server_options
480
+ when Hash
481
+ @opts.merge(server_options)
482
+ when Proc
483
+ @opts.merge(server_options.call(self))
484
+ else
485
+ raise Error, 'Server opts should be a hash or proc'
486
+ end
487
+ else
488
+ @opts.dup
489
+ end
490
+ opts.delete(:servers)
491
+ opts
492
+ end
464
493
 
465
494
  # The default options for the connection pool.
466
495
  def connection_pool_default_options
@@ -1,4 +1,4 @@
1
- %w'callback convenience pagination query schema sql'.each do |f|
1
+ %w'callback convenience pagination prepared_statements query schema sql'.each do |f|
2
2
  require "sequel_core/dataset/#{f}"
3
3
  end
4
4
 
@@ -26,42 +26,6 @@ module Sequel
26
26
  # Datasets are Enumerable objects, so they can be manipulated using any
27
27
  # of the Enumerable methods, such as map, inject, etc.
28
28
  #
29
- # === The Dataset Adapter Interface
30
- #
31
- # Each adapter should define its own dataset class as a descendant of
32
- # Sequel::Dataset. The following methods should be overridden by the adapter
33
- # Dataset class (each method with the stock implementation):
34
- #
35
- # # Iterate over the results of the SQL query and call the supplied
36
- # # block with each record (as a hash).
37
- # def fetch_rows(sql, &block)
38
- # @db.synchronize do
39
- # r = @db.execute(sql)
40
- # r.each(&block)
41
- # end
42
- # end
43
- #
44
- # # Insert records.
45
- # def insert(*values)
46
- # @db.synchronize do
47
- # @db.execute(insert_sql(*values)).last_insert_id
48
- # end
49
- # end
50
- #
51
- # # Update records.
52
- # def update(*args)
53
- # @db.synchronize do
54
- # @db.execute(update_sql(*args)).affected_rows
55
- # end
56
- # end
57
- #
58
- # # Delete records.
59
- # def delete(opts = nil)
60
- # @db.synchronize do
61
- # @db.execute(delete_sql(opts)).affected_rows
62
- # end
63
- # end
64
- #
65
29
  # === Methods added via metaprogramming
66
30
  #
67
31
  # Some methods are added via metaprogramming:
@@ -221,7 +185,7 @@ module Sequel
221
185
 
222
186
  # Deletes the records in the dataset. Adapters should override this method.
223
187
  def delete(*args)
224
- @db.execute_dui(delete_sql(*args))
188
+ execute_dui(delete_sql(*args))
225
189
  end
226
190
 
227
191
  # Iterates over the records in the dataset.
@@ -249,7 +213,7 @@ module Sequel
249
213
  # Inserts values into the associated table. Adapters should override this
250
214
  # method.
251
215
  def insert(*values)
252
- @db.execute_dui(insert_sql(*values))
216
+ execute_dui(insert_sql(*values))
253
217
  end
254
218
 
255
219
  # Returns a string representation of the dataset including the class name
@@ -282,6 +246,13 @@ module Sequel
282
246
  def quote_identifiers?
283
247
  @quote_identifiers
284
248
  end
249
+
250
+ # Set the server for this dataset to use. Used to pick a specific database
251
+ # shard to run a query against, or to override the default SELECT uses
252
+ # :read_only database and all other queries use the :default database.
253
+ def server(servr)
254
+ clone(:server=>servr)
255
+ end
285
256
 
286
257
  # Alias for set, but not aliased directly so subclasses
287
258
  # don't have to override both methods.
@@ -430,7 +401,7 @@ module Sequel
430
401
 
431
402
  # Updates values for the dataset. Adapters should override this method.
432
403
  def update(*args)
433
- @db.execute_dui(update_sql(*args))
404
+ execute_dui(update_sql(*args))
434
405
  end
435
406
 
436
407
  # Add the mutation methods via metaprogramming
@@ -444,6 +415,16 @@ module Sequel
444
415
  end
445
416
 
446
417
  private
418
+
419
+ # Execute the given SQL on the database using execute.
420
+ def execute(sql, opts={}, &block)
421
+ @db.execute(sql, {:server=>@opts[:server] || :read_only}.merge(opts), &block)
422
+ end
423
+
424
+ # Execute the given SQL on the database using execute_dui.
425
+ def execute_dui(sql, opts={}, &block)
426
+ @db.execute_dui(sql, {:server=>@opts[:server] || :default}.merge(opts), &block)
427
+ end
447
428
 
448
429
  # Modify the receiver with the results of sending the meth, args, and block
449
430
  # to the receiver and merging the options of the resulting dataset into
@@ -136,7 +136,7 @@ module Sequel
136
136
  table = @opts[:from].first
137
137
  columns, dataset = *args
138
138
  sql = "INSERT INTO #{quote_identifier(table)} #{literal(columns)} VALUES #{literal(dataset)}"
139
- return @db.transaction {@db.execute_dui sql}
139
+ return @db.transaction{execute_dui(sql)}
140
140
  else
141
141
  # we assume that an array of hashes is given
142
142
  hashes, opts = *args
@@ -153,11 +153,11 @@ module Sequel
153
153
  if slice_size
154
154
  values.each_slice(slice_size) do |slice|
155
155
  statements = multi_insert_sql(columns, slice)
156
- @db.transaction {statements.each {|st| @db.execute_dui(st)}}
156
+ @db.transaction{statements.each{|st| execute_dui(st)}}
157
157
  end
158
158
  else
159
159
  statements = multi_insert_sql(columns, values)
160
- @db.transaction {statements.each {|st| @db.execute_dui(st)}}
160
+ @db.transaction{statements.each{|st| execute_dui(st)}}
161
161
  end
162
162
  end
163
163
  alias_method :import, :multi_insert
@@ -0,0 +1,218 @@
1
+ module Sequel
2
+ class Dataset
3
+ PREPARED_ARG_PLACEHOLDER = '?'.lit.freeze
4
+
5
+ # Default implementation of the argument mapper to allow
6
+ # native database support for bind variables and prepared
7
+ # statements (as opposed to the emulated ones used by default).
8
+ module ArgumentMapper
9
+ SQL_QUERY_TYPE = Hash.new{|h,k| h[k] = k}
10
+ SQL_QUERY_TYPE[:first] = SQL_QUERY_TYPE[:all] = :select
11
+
12
+ # The name of the prepared statement, if any.
13
+ attr_accessor :prepared_statement_name
14
+
15
+ # The bind arguments to use for running this prepared statement
16
+ attr_accessor :bind_arguments
17
+
18
+ # Set the bind arguments based on the hash and call super.
19
+ def call(hash, &block)
20
+ ds = clone
21
+ ds.prepared_sql
22
+ ds.bind_arguments = ds.map_to_prepared_args(hash)
23
+ ds.prepared_args = hash
24
+ ds.run(&block)
25
+ end
26
+
27
+ # Override the given *_sql method based on the type, and
28
+ # cache the result of the sql. This requires that the object
29
+ # that includes this module implement prepared_args_hash.
30
+ def prepared_sql
31
+ return @prepared_sql if @prepared_sql
32
+ @prepared_args = prepared_args_hash
33
+ @prepared_sql = super
34
+ meta_def("#{sql_query_type}_sql"){|*args| prepared_sql}
35
+ @prepared_sql
36
+ end
37
+
38
+ private
39
+
40
+ def sql_query_type
41
+ SQL_QUERY_TYPE[@prepared_type]
42
+ end
43
+ end
44
+
45
+ # Backbone of the prepared statement support. Grafts bind variable
46
+ # support into datasets by hijacking #literal and using placeholders.
47
+ # By default, emulates prepared statements and bind variables by
48
+ # taking the hash of bind variables and directly substituting them
49
+ # into the query, which works on all databases, as it is no different
50
+ # from using the dataset without bind variables.
51
+ module PreparedStatementMethods
52
+ PLACEHOLDER_RE = /\A\$(.*)\z/
53
+
54
+ # The type of prepared statement, should be one of :select,
55
+ # :insert, :update, or :delete
56
+ attr_accessor :prepared_type
57
+
58
+ # The bind variable hash to use when substituting
59
+ attr_accessor :prepared_args
60
+
61
+ # The argument to supply to insert and update, which may use
62
+ # placeholders specified by prepared_args
63
+ attr_accessor :prepared_modify_values
64
+
65
+ # Sets the prepared_args to the given hash and runs the
66
+ # prepared statement.
67
+ def call(hash, &block)
68
+ ds = clone
69
+ ds.prepared_args = hash
70
+ ds.run(&block)
71
+ end
72
+
73
+ # Returns the SQL for the prepared statement, depending on
74
+ # the type of the statement and the prepared_modify_values.
75
+ def prepared_sql
76
+ case @prepared_type
77
+ when :select, :all
78
+ select_sql
79
+ when :first
80
+ select_sql(:limit=>1)
81
+ when :insert
82
+ insert_sql(@prepared_modify_values)
83
+ when :update
84
+ update_sql(@prepared_modify_values)
85
+ when :delete
86
+ delete_sql
87
+ end
88
+ end
89
+
90
+ # Changes the values of symbols if they start with $ and
91
+ # prepared_args is present. If so, they are considered placeholders,
92
+ # and they are substituted using prepared_arg.
93
+ def literal(v)
94
+ case v
95
+ when Symbol
96
+ if match = PLACEHOLDER_RE.match(v.to_s) and @prepared_args
97
+ super(prepared_arg(match[1].to_sym))
98
+ else
99
+ super
100
+ end
101
+ else
102
+ super
103
+ end
104
+ end
105
+
106
+ # Programmer friendly string showing this is a prepared statement,
107
+ # with the prepared SQL it represents (which in general won't have
108
+ # substituted variables).
109
+ def inspect
110
+ "<#{self.class.name}/PreparedStatement #{prepared_sql.inspect}>"
111
+ end
112
+
113
+ protected
114
+
115
+ # Run the method based on the type of prepared statement, with
116
+ # :select running #all to get all of the rows, and the other
117
+ # types running the method with the same name as the type.
118
+ def run(&block)
119
+ case @prepared_type
120
+ when :select, :all
121
+ all(&block)
122
+ when :first
123
+ first
124
+ when :insert
125
+ insert(@prepared_modify_values)
126
+ when :update
127
+ update(@prepared_modify_values)
128
+ when :delete
129
+ delete
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ # Returns the value of the prepared_args hash for the given key.
136
+ def prepared_arg(k)
137
+ @prepared_args[k]
138
+ end
139
+ end
140
+
141
+ # Default implementation for an argument mapper that uses
142
+ # unnumbered SQL placeholder arguments. Keeps track of which
143
+ # arguments have been used, and allows arguments to
144
+ # be used more than once.
145
+ module UnnumberedArgumentMapper
146
+ include ArgumentMapper
147
+
148
+ protected
149
+
150
+ # Returns a single output array mapping the values of the input hash.
151
+ # Keys in the input hash that are used more than once in the query
152
+ # have multiple entries in the output array.
153
+ def map_to_prepared_args(hash)
154
+ array = []
155
+ @prepared_args.each{|k,vs| vs.each{|v| array[v] = hash[k]}}
156
+ array
157
+ end
158
+
159
+ private
160
+
161
+ # Uses a separate array of each key, holding the positions
162
+ # in the output array (necessary to support arguments
163
+ # that are used more than once).
164
+ def prepared_args_hash
165
+ Hash.new{|h,k| h[k] = Array.new}
166
+ end
167
+
168
+ # Associates the argument with name k with the next position in
169
+ # the output array.
170
+ def prepared_arg(k)
171
+ @max_prepared_arg ||= 0
172
+ @prepared_args[k] << @max_prepared_arg
173
+ @max_prepared_arg += 1
174
+ prepared_arg_placeholder
175
+ end
176
+ end
177
+
178
+ # For the given type (:select, :insert, :update, or :delete),
179
+ # run the sql with the bind variables
180
+ # specified in the hash. values is a hash of passed to
181
+ # insert or update (if one of those types is used),
182
+ # which may contain placeholders.
183
+ def call(type, bind_variables={}, values=nil)
184
+ to_prepared_statement(type, values).call(bind_variables)
185
+ end
186
+
187
+ # Prepare an SQL statement for later execution. This returns
188
+ # a clone of the dataset extended with PreparedStatementMethods,
189
+ # on which you can call call with the hash of bind variables to
190
+ # do substitution. The prepared statement is also stored in
191
+ # the associated database. The following usage is identical:
192
+ #
193
+ # ps = prepare(:select, :select_by_name)
194
+ # ps.call(:name=>'Blah')
195
+ # db.call(:select_by_name, :name=>'Blah')
196
+ def prepare(type, name, values=nil)
197
+ db.prepared_statements[name] = to_prepared_statement(type, values)
198
+ end
199
+
200
+ private
201
+
202
+ # The argument placeholder. Most databases used unnumbered
203
+ # arguments with question marks, so that is the default.
204
+ def prepared_arg_placeholder
205
+ PREPARED_ARG_PLACEHOLDER
206
+ end
207
+
208
+ # Return a cloned copy of the current dataset extended with
209
+ # PreparedStatementMethods, setting the type and modify values.
210
+ def to_prepared_statement(type, values=nil)
211
+ ps = clone
212
+ ps.extend(PreparedStatementMethods)
213
+ ps.prepared_type = type
214
+ ps.prepared_modify_values = values
215
+ ps
216
+ end
217
+ end
218
+ end