sequel 5.61.0 → 5.63.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +46 -0
  3. data/README.rdoc +20 -19
  4. data/doc/advanced_associations.rdoc +13 -13
  5. data/doc/association_basics.rdoc +21 -15
  6. data/doc/cheat_sheet.rdoc +3 -3
  7. data/doc/model_hooks.rdoc +1 -1
  8. data/doc/object_model.rdoc +8 -8
  9. data/doc/opening_databases.rdoc +4 -4
  10. data/doc/postgresql.rdoc +8 -8
  11. data/doc/querying.rdoc +1 -1
  12. data/doc/release_notes/5.62.0.txt +132 -0
  13. data/doc/release_notes/5.63.0.txt +33 -0
  14. data/doc/schema_modification.rdoc +1 -1
  15. data/doc/security.rdoc +9 -9
  16. data/doc/sql.rdoc +13 -13
  17. data/doc/testing.rdoc +13 -11
  18. data/doc/transactions.rdoc +6 -6
  19. data/doc/virtual_rows.rdoc +1 -1
  20. data/lib/sequel/adapters/postgres.rb +4 -0
  21. data/lib/sequel/adapters/shared/access.rb +9 -1
  22. data/lib/sequel/adapters/shared/mssql.rb +9 -5
  23. data/lib/sequel/adapters/shared/mysql.rb +7 -0
  24. data/lib/sequel/adapters/shared/oracle.rb +7 -0
  25. data/lib/sequel/adapters/shared/postgres.rb +275 -152
  26. data/lib/sequel/adapters/shared/sqlanywhere.rb +7 -0
  27. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  28. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  29. data/lib/sequel/connection_pool/threaded.rb +8 -8
  30. data/lib/sequel/connection_pool/timed_queue.rb +257 -0
  31. data/lib/sequel/connection_pool.rb +47 -30
  32. data/lib/sequel/database/connecting.rb +24 -0
  33. data/lib/sequel/database/misc.rb +8 -2
  34. data/lib/sequel/database/query.rb +37 -0
  35. data/lib/sequel/dataset/actions.rb +56 -11
  36. data/lib/sequel/dataset/features.rb +5 -0
  37. data/lib/sequel/dataset/misc.rb +1 -1
  38. data/lib/sequel/dataset/query.rb +9 -9
  39. data/lib/sequel/dataset/sql.rb +5 -1
  40. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  41. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  42. data/lib/sequel/extensions/async_thread_pool.rb +11 -11
  43. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  44. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  45. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  46. data/lib/sequel/extensions/migration.rb +1 -1
  47. data/lib/sequel/extensions/named_timezones.rb +21 -5
  48. data/lib/sequel/extensions/pg_array.rb +22 -3
  49. data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
  50. data/lib/sequel/extensions/pg_extended_date_support.rb +12 -0
  51. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  52. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  53. data/lib/sequel/extensions/pg_inet.rb +9 -10
  54. data/lib/sequel/extensions/pg_interval.rb +9 -10
  55. data/lib/sequel/extensions/pg_json.rb +10 -10
  56. data/lib/sequel/extensions/pg_multirange.rb +5 -10
  57. data/lib/sequel/extensions/pg_range.rb +5 -10
  58. data/lib/sequel/extensions/pg_row.rb +18 -13
  59. data/lib/sequel/model/associations.rb +9 -4
  60. data/lib/sequel/model/base.rb +6 -5
  61. data/lib/sequel/plugins/auto_validations.rb +53 -15
  62. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  63. data/lib/sequel/plugins/composition.rb +2 -2
  64. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  65. data/lib/sequel/plugins/dirty.rb +1 -1
  66. data/lib/sequel/plugins/finder.rb +3 -1
  67. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  68. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +1 -1
  69. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  70. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  71. data/lib/sequel/plugins/sql_comments.rb +1 -1
  72. data/lib/sequel/plugins/tactical_eager_loading.rb +14 -14
  73. data/lib/sequel/plugins/validate_associated.rb +22 -12
  74. data/lib/sequel/plugins/validation_helpers.rb +20 -0
  75. data/lib/sequel/version.rb +1 -1
  76. metadata +14 -6
@@ -0,0 +1,257 @@
1
+ # frozen-string-literal: true
2
+
3
+ # :nocov:
4
+ raise LoadError, "Sequel::TimedQueueConnectionPool is only available on Ruby 3.2+" unless RUBY_VERSION >= '3.2'
5
+ # :nocov:
6
+
7
+ # A connection pool allowing multi-threaded access to a pool of connections,
8
+ # using a timed queue (only available in Ruby 3.2+).
9
+ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
10
+ # The maximum number of connections this pool will create.
11
+ attr_reader :max_size
12
+
13
+ # The following additional options are respected:
14
+ # :max_connections :: The maximum number of connections the connection pool
15
+ # will open (default 4)
16
+ # :pool_timeout :: The amount of seconds to wait to acquire a connection
17
+ # before raising a PoolTimeout (default 5)
18
+ def initialize(db, opts = OPTS)
19
+ super
20
+ @max_size = Integer(opts[:max_connections] || 4)
21
+ raise(Sequel::Error, ':max_connections must be positive') if @max_size < 1
22
+ @mutex = Mutex.new
23
+ # Size inside array so this still works while the pool is frozen.
24
+ @size = [0]
25
+ @allocated = {}
26
+ @allocated.compare_by_identity
27
+ @timeout = Float(opts[:pool_timeout] || 5)
28
+ @queue = Queue.new
29
+ end
30
+
31
+ # Yield all of the available connections, and the one currently allocated to
32
+ # this thread. This will not yield connections currently allocated to other
33
+ # threads, as it is not safe to operate on them.
34
+ def all_connections
35
+ hold do |conn|
36
+ yield conn
37
+
38
+ # Use a hash to record all connections already seen. As soon as we
39
+ # come across a connection we've already seen, we stop the loop.
40
+ conns = {}
41
+ conns.compare_by_identity
42
+ while true
43
+ conn = nil
44
+ begin
45
+ break unless (conn = @queue.pop(timeout: 0)) && !conns[conn]
46
+ conns[conn] = true
47
+ yield conn
48
+ ensure
49
+ @queue.push(conn) if conn
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # Removes all connections currently in the pool's queue. This method has the effect of
56
+ # disconnecting from the database, assuming that no connections are currently
57
+ # being used.
58
+ #
59
+ # Once a connection is requested using #hold, the connection pool
60
+ # creates new connections to the database.
61
+ def disconnect(opts=OPTS)
62
+ while conn = @queue.pop(timeout: 0)
63
+ disconnect_connection(conn)
64
+ end
65
+ fill_queue
66
+ nil
67
+ end
68
+
69
+ # Chooses the first available connection, or if none are
70
+ # available, creates a new connection. Passes the connection to the supplied
71
+ # block:
72
+ #
73
+ # pool.hold {|conn| conn.execute('DROP TABLE posts')}
74
+ #
75
+ # Pool#hold is re-entrant, meaning it can be called recursively in
76
+ # the same thread without blocking.
77
+ #
78
+ # If no connection is immediately available and the pool is already using the maximum
79
+ # number of connections, Pool#hold will block until a connection
80
+ # is available or the timeout expires. If the timeout expires before a
81
+ # connection can be acquired, a Sequel::PoolTimeout is raised.
82
+ def hold(server=nil)
83
+ t = Sequel.current
84
+ if conn = sync{@allocated[t]}
85
+ return yield(conn)
86
+ end
87
+
88
+ begin
89
+ conn = acquire(t)
90
+ yield conn
91
+ rescue Sequel::DatabaseDisconnectError, *@error_classes => e
92
+ if disconnect_error?(e)
93
+ oconn = conn
94
+ conn = nil
95
+ disconnect_connection(oconn) if oconn
96
+ sync{@allocated.delete(t)}
97
+ fill_queue
98
+ end
99
+ raise
100
+ ensure
101
+ release(t) if conn
102
+ end
103
+ end
104
+
105
+ def pool_type
106
+ :timed_queue
107
+ end
108
+
109
+ # The total number of connections in the pool.
110
+ def size
111
+ sync{@size[0]}
112
+ end
113
+
114
+ private
115
+
116
+ # Create a new connection, after the pool's current size has already
117
+ # been updated to account for the new connection. If there is an exception
118
+ # when creating the connection, decrement the current size.
119
+ #
120
+ # This should only be called after can_make_new?. If there is an exception
121
+ # between when can_make_new? is called and when preallocated_make_new
122
+ # is called, it has the effect of reducing the maximum size of the
123
+ # connection pool by 1, since the current size of the pool will show a
124
+ # higher number than the number of connections allocated or
125
+ # in the queue.
126
+ #
127
+ # Calling code should not have the mutex when calling this.
128
+ def preallocated_make_new
129
+ make_new(:default)
130
+ rescue Exception
131
+ sync{@size[0] -= 1}
132
+ raise
133
+ end
134
+
135
+ # Decrement the current size of the pool when disconnecting connections.
136
+ #
137
+ # Calling code should not have the mutex when calling this.
138
+ def disconnect_connection(conn)
139
+ sync{@size[0] -= 1}
140
+ super
141
+ end
142
+
143
+ # If there are any threads waiting on the queue, try to create
144
+ # new connections in a separate thread if the pool is not yet at the
145
+ # maximum size.
146
+ #
147
+ # The reason for this method is to handle cases where acquire
148
+ # could not retrieve a connection immediately, and the pool
149
+ # was already at the maximum size. In that case, the acquire will
150
+ # wait on the queue until the timeout. This method is called
151
+ # after disconnecting to potentially add new connections to the
152
+ # pool, so the threads that are currently waiting for connections
153
+ # do not timeout after the pool is no longer full.
154
+ def fill_queue
155
+ if @queue.num_waiting > 0
156
+ Thread.new do
157
+ while @queue.num_waiting > 0 && (conn = try_make_new)
158
+ @queue.push(conn)
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ # Whether the given size is less than the maximum size of the pool.
165
+ # In that case, the pool's current size is incremented. If this
166
+ # method returns true, space in the pool for the connection is
167
+ # preallocated, and preallocated_make_new should be called to
168
+ # create the connection.
169
+ #
170
+ # Calling code should have the mutex when calling this.
171
+ def can_make_new?(current_size)
172
+ if @max_size > current_size
173
+ @size[0] += 1
174
+ end
175
+ end
176
+
177
+ # Try to make a new connection if there is space in the pool.
178
+ # If the pool is already full, look for dead threads/fibers and
179
+ # disconnect the related connections.
180
+ #
181
+ # Calling code should not have the mutex when calling this.
182
+ def try_make_new
183
+ return preallocated_make_new if sync{can_make_new?(@size[0])}
184
+
185
+ to_disconnect = nil
186
+ do_make_new = false
187
+
188
+ sync do
189
+ current_size = @size[0]
190
+ @allocated.keys.each do |t|
191
+ unless t.alive?
192
+ (to_disconnect ||= []) << @allocated.delete(t)
193
+ current_size -= 1
194
+ end
195
+ end
196
+
197
+ do_make_new = true if can_make_new?(current_size)
198
+ end
199
+
200
+ begin
201
+ preallocated_make_new if do_make_new
202
+ ensure
203
+ if to_disconnect
204
+ to_disconnect.each{|conn| disconnect_connection(conn)}
205
+ fill_queue
206
+ end
207
+ end
208
+ end
209
+
210
+ # Assigns a connection to the supplied thread, if one
211
+ # is available.
212
+ #
213
+ # This should return a connection is one is available within the timeout,
214
+ # or raise PoolTimeout if a connection could not be acquired within the timeout.
215
+ #
216
+ # Calling code should not have the mutex when calling this.
217
+ def acquire(thread)
218
+ if conn = @queue.pop(timeout: 0) || try_make_new || @queue.pop(timeout: @timeout)
219
+ sync{@allocated[thread] = conn}
220
+ else
221
+ name = db.opts[:name]
222
+ raise ::Sequel::PoolTimeout, "timeout: #{@timeout}#{", database name: #{name}" if name}"
223
+ end
224
+ end
225
+
226
+ # Create the maximum number of connections immediately. This should not be called
227
+ # with a true argument unles no code is currently operating on the database.
228
+ #
229
+ # Calling code should not have the mutex when calling this.
230
+ def preconnect(concurrent = false)
231
+ if concurrent
232
+ if times = sync{@max_size > (size = @size[0]) ? @max_size - size : false}
233
+ times.times.map{Thread.new{if conn = try_make_new; @queue.push(conn) end}}.map(&:value)
234
+ end
235
+ else
236
+ while conn = try_make_new
237
+ @queue.push(conn)
238
+ end
239
+ end
240
+
241
+ nil
242
+ end
243
+
244
+ # Releases the connection assigned to the supplied thread back to the pool.
245
+ #
246
+ # Calling code should not have the mutex when calling this.
247
+ def release(thread)
248
+ @queue.push(sync{@allocated.delete(thread)})
249
+ end
250
+
251
+ # Yield to the block while inside the mutex.
252
+ #
253
+ # Calling code should not have the mutex when calling this.
254
+ def sync
255
+ @mutex.synchronize{yield}
256
+ end
257
+ end
@@ -30,8 +30,11 @@ class Sequel::ConnectionPool
30
30
  :threaded => :ThreadedConnectionPool,
31
31
  :single => :SingleConnectionPool,
32
32
  :sharded_threaded => :ShardedThreadedConnectionPool,
33
- :sharded_single => :ShardedSingleConnectionPool
34
- }.freeze
33
+ :sharded_single => :ShardedSingleConnectionPool,
34
+ :timed_queue => :TimedQueueConnectionPool,
35
+ }
36
+ POOL_CLASS_MAP.to_a.each{|k, v| POOL_CLASS_MAP[k.to_s] = v}
37
+ POOL_CLASS_MAP.freeze
35
38
 
36
39
  # Class methods used to return an appropriate pool subclass, separated
37
40
  # into a module for easier overridding by extensions.
@@ -74,26 +77,35 @@ class Sequel::ConnectionPool
74
77
 
75
78
  # The after_connect proc used for this pool. This is called with each new
76
79
  # connection made, and is usually used to set custom per-connection settings.
77
- attr_accessor :after_connect
80
+ # Deprecated.
81
+ attr_reader :after_connect # SEQUEL6: Remove
78
82
 
79
- # An array of sql strings to execute on each new connection.
80
- attr_accessor :connect_sqls
83
+ # Override the after_connect proc for the connection pool. Deprecated.
84
+ # Disables support for shard-specific :after_connect and :connect_sqls if used.
85
+ def after_connect=(v) # SEQUEL6: Remove
86
+ @use_old_connect_api = true
87
+ @after_connect = v
88
+ end
89
+
90
+ # An array of sql strings to execute on each new connection. Deprecated.
91
+ attr_reader :connect_sqls # SEQUEL6: Remove
92
+
93
+ # Override the connect_sqls for the connection pool. Deprecated.
94
+ # Disables support for shard-specific :after_connect and :connect_sqls if used.
95
+ def connect_sqls=(v) # SEQUEL6: Remove
96
+ @use_old_connect_api = true
97
+ @connect_sqls = v
98
+ end
81
99
 
82
100
  # The Sequel::Database object tied to this connection pool.
83
101
  attr_accessor :db
84
102
 
85
- # Instantiates a connection pool with the given options. The block is called
86
- # with a single symbol (specifying the server/shard to use) every time a new
87
- # connection is needed. The following options are respected for all connection
88
- # pools:
89
- # :after_connect :: A callable object called after each new connection is made, with the
90
- # connection object (and server argument if the callable accepts 2 arguments),
91
- # useful for customizations that you want to apply to all connections.
92
- # :connect_sqls :: An array of sql strings to execute on each new connection, after :after_connect runs.
93
- def initialize(db, opts=OPTS)
103
+ # Instantiates a connection pool with the given Database and options.
104
+ def initialize(db, opts=OPTS) # SEQUEL6: Remove second argument, always use db.opts
94
105
  @db = db
95
- @after_connect = opts[:after_connect]
96
- @connect_sqls = opts[:connect_sqls]
106
+ @use_old_connect_api = false # SEQUEL6: Remove
107
+ @after_connect = opts[:after_connect] # SEQUEL6: Remove
108
+ @connect_sqls = opts[:connect_sqls] # SEQUEL6: Remove
97
109
  @error_classes = db.send(:database_error_classes).dup.freeze
98
110
  end
99
111
 
@@ -119,25 +131,30 @@ class Sequel::ConnectionPool
119
131
  # and checking for connection errors.
120
132
  def make_new(server)
121
133
  begin
122
- conn = @db.connect(server)
134
+ if @use_old_connect_api
135
+ # SEQUEL6: Remove block
136
+ conn = @db.connect(server)
123
137
 
124
- if ac = @after_connect
125
- if ac.arity == 2
126
- ac.call(conn, server)
127
- else
128
- ac.call(conn)
138
+ if ac = @after_connect
139
+ if ac.arity == 2
140
+ ac.call(conn, server)
141
+ else
142
+ ac.call(conn)
143
+ end
129
144
  end
130
- end
131
-
132
- if cs = @connect_sqls
133
- cs.each do |sql|
134
- db.send(:log_connection_execute, conn, sql)
145
+
146
+ if cs = @connect_sqls
147
+ cs.each do |sql|
148
+ db.send(:log_connection_execute, conn, sql)
149
+ end
135
150
  end
151
+
152
+ conn
153
+ else
154
+ @db.new_connection(server)
136
155
  end
137
156
  rescue Exception=>exception
138
157
  raise Sequel.convert_exception_class(exception, Sequel::DatabaseConnectionError)
139
- end
140
- raise(Sequel::DatabaseConnectionError, "Connection parameters not valid") unless conn
141
- conn
158
+ end || raise(Sequel::DatabaseConnectionError, "Connection parameters not valid")
142
159
  end
143
160
  end
@@ -241,6 +241,30 @@ module Sequel
241
241
  pool.servers
242
242
  end
243
243
 
244
+ # Connect to the given server/shard. Handles database-generic post-connection
245
+ # setup not handled by #connect, using the :after_connect and :connect_sqls
246
+ # options.
247
+ def new_connection(server)
248
+ conn = connect(server)
249
+ opts = server_opts(server)
250
+
251
+ if ac = opts[:after_connect]
252
+ if ac.arity == 2
253
+ ac.call(conn, server)
254
+ else
255
+ ac.call(conn)
256
+ end
257
+ end
258
+
259
+ if cs = opts[:connect_sqls]
260
+ cs.each do |sql|
261
+ log_connection_execute(conn, sql)
262
+ end
263
+ end
264
+
265
+ conn
266
+ end
267
+
244
268
  # Returns true if the database is using a single-threaded connection pool.
245
269
  def single_threaded?
246
270
  @single_threaded
@@ -100,10 +100,14 @@ module Sequel
100
100
  # options hash.
101
101
  #
102
102
  # Accepts the following options:
103
+ # :after_connect :: A callable object called after each new connection is made, with the
104
+ # connection object (and server argument if the callable accepts 2 arguments),
105
+ # useful for customizations that you want to apply to all connections.
103
106
  # :before_preconnect :: Callable that runs after extensions from :preconnect_extensions are loaded,
104
107
  # but before any connections are created.
105
108
  # :cache_schema :: Whether schema should be cached for this Database instance
106
109
  # :check_string_typecast_bytesize :: Whether to check the bytesize of strings before typecasting.
110
+ # :connect_sqls :: An array of sql strings to execute on each new connection, after :after_connect runs.
107
111
  # :default_string_column_size :: The default size of string columns, 255 by default.
108
112
  # :extensions :: Extensions to load into this Database instance. Can be a symbol, array of symbols,
109
113
  # or string with extensions separated by columns. These extensions are loaded after
@@ -126,9 +130,11 @@ module Sequel
126
130
  # :single_threaded :: Whether to use a single-threaded connection pool.
127
131
  # :sql_log_level :: Method to use to log SQL to a logger, :info by default.
128
132
  #
133
+ # For sharded connection pools, :after_connect and :connect_sqls can be specified per-shard.
134
+ #
129
135
  # All options given are also passed to the connection pool. Additional options respected by
130
- # the connection pool are :after_connect, :connect_sqls, :max_connections, :pool_timeout,
131
- # :servers, and :servers_hash. See the connection pool documentation for details.
136
+ # the connection pool are :max_connections, :pool_timeout, :servers, and :servers_hash. See the
137
+ # connection pool documentation for details.
132
138
  def initialize(opts = OPTS)
133
139
  @opts ||= opts
134
140
  @opts = connection_pool_default_options.merge(@opts)
@@ -175,6 +175,9 @@ module Sequel
175
175
  if !c[:max_length] && c[:type] == :string && (max_length = column_schema_max_length(c[:db_type]))
176
176
  c[:max_length] = max_length
177
177
  end
178
+ if !c[:max_value] && !c[:min_value] && c[:type] == :integer && (min_max = column_schema_integer_min_max_values(c[:db_type]))
179
+ c[:min_value], c[:max_value] = min_max
180
+ end
178
181
  end
179
182
  schema_post_process(cols)
180
183
 
@@ -272,6 +275,40 @@ module Sequel
272
275
  column_schema_default_to_ruby_value(default, type) rescue nil
273
276
  end
274
277
 
278
+ INTEGER1_MIN_MAX = [-128, 127].freeze
279
+ INTEGER2_MIN_MAX = [-32768, 32767].freeze
280
+ INTEGER3_MIN_MAX = [-8388608, 8388607].freeze
281
+ INTEGER4_MIN_MAX = [-2147483648, 2147483647].freeze
282
+ INTEGER8_MIN_MAX = [-9223372036854775808, 9223372036854775807].freeze
283
+ UNSIGNED_INTEGER1_MIN_MAX = [0, 255].freeze
284
+ UNSIGNED_INTEGER2_MIN_MAX = [0, 65535].freeze
285
+ UNSIGNED_INTEGER3_MIN_MAX = [0, 16777215].freeze
286
+ UNSIGNED_INTEGER4_MIN_MAX = [0, 4294967295].freeze
287
+ UNSIGNED_INTEGER8_MIN_MAX = [0, 18446744073709551615].freeze
288
+
289
+ # Look at the db_type and guess the minimum and maximum integer values for
290
+ # the column.
291
+ def column_schema_integer_min_max_values(db_type)
292
+ unsigned = /unsigned/i =~ db_type
293
+ case db_type
294
+ when /big|int8/i
295
+ unsigned ? UNSIGNED_INTEGER8_MIN_MAX : INTEGER8_MIN_MAX
296
+ when /medium/i
297
+ unsigned ? UNSIGNED_INTEGER3_MIN_MAX : INTEGER3_MIN_MAX
298
+ when /small|int2/i
299
+ unsigned ? UNSIGNED_INTEGER2_MIN_MAX : INTEGER2_MIN_MAX
300
+ when /tiny/i
301
+ (unsigned || column_schema_tinyint_type_is_unsigned?) ? UNSIGNED_INTEGER1_MIN_MAX : INTEGER1_MIN_MAX
302
+ else
303
+ unsigned ? UNSIGNED_INTEGER4_MIN_MAX : INTEGER4_MIN_MAX
304
+ end
305
+ end
306
+
307
+ # Whether the tinyint type (if supported by the database) is unsigned by default.
308
+ def column_schema_tinyint_type_is_unsigned?
309
+ false
310
+ end
311
+
275
312
  # Look at the db_type and guess the maximum length of the column.
276
313
  # This assumes types such as varchar(255).
277
314
  def column_schema_max_length(db_type)
@@ -127,6 +127,18 @@ module Sequel
127
127
  #
128
128
  # DB[:table].delete # DELETE * FROM table
129
129
  # # => 3
130
+ #
131
+ # Some databases support using multiple tables in a DELETE query. This requires
132
+ # multiple FROM tables (JOINs can also be used). As multiple FROM tables use
133
+ # an implicit CROSS JOIN, you should make sure your WHERE condition uses the
134
+ # appropriate filters for the FROM tables:
135
+ #
136
+ # DB.from(:a, :b).join(:c, :d=>Sequel[:b][:e]).where{{a[:f]=>b[:g], a[:id]=>c[:h]}}.
137
+ # delete
138
+ # # DELETE FROM a
139
+ # # USING b
140
+ # # INNER JOIN c ON (c.d = b.e)
141
+ # # WHERE ((a.f = b.g) AND (a.id = c.h))
130
142
  def delete(&block)
131
143
  sql = delete_sql
132
144
  if uses_returning?(:delete)
@@ -313,14 +325,18 @@ module Sequel
313
325
 
314
326
  # Inserts multiple records into the associated table. This method can be
315
327
  # used to efficiently insert a large number of records into a table in a
316
- # single query if the database supports it. Inserts
317
- # are automatically wrapped in a transaction.
328
+ # single query if the database supports it. Inserts are automatically
329
+ # wrapped in a transaction if necessary.
318
330
  #
319
331
  # This method is called with a columns array and an array of value arrays:
320
332
  #
321
333
  # DB[:table].import([:x, :y], [[1, 2], [3, 4]])
322
334
  # # INSERT INTO table (x, y) VALUES (1, 2)
323
- # # INSERT INTO table (x, y) VALUES (3, 4)
335
+ # # INSERT INTO table (x, y) VALUES (3, 4)
336
+ #
337
+ # or, if the database supports it:
338
+ #
339
+ # # INSERT INTO table (x, y) VALUES (1, 2), (3, 4)
324
340
  #
325
341
  # This method also accepts a dataset instead of an array of value arrays:
326
342
  #
@@ -328,9 +344,13 @@ module Sequel
328
344
  # # INSERT INTO table (x, y) SELECT a, b FROM table2
329
345
  #
330
346
  # Options:
331
- # :commit_every :: Open a new transaction for every given number of records.
332
- # For example, if you provide a value of 50, will commit
333
- # after every 50 records.
347
+ # :commit_every :: Open a new transaction for every given number of
348
+ # records. For example, if you provide a value of 50,
349
+ # will commit after every 50 records. When a
350
+ # transaction is not required, this option controls
351
+ # the maximum number of values to insert with a single
352
+ # statement; it does not force the use of a
353
+ # transaction.
334
354
  # :return :: When this is set to :primary_key, returns an array of
335
355
  # autoincremented primary key values for the rows inserted.
336
356
  # This does not have an effect if +values+ is a Dataset.
@@ -576,7 +596,7 @@ module Sequel
576
596
  # # SELECT * FROM table ORDER BY id LIMIT 1000 OFFSET 1000
577
597
  # # ...
578
598
  #
579
- # DB[:table].order(:id).paged_each(:rows_per_fetch=>100){|row| }
599
+ # DB[:table].order(:id).paged_each(rows_per_fetch: 100){|row| }
580
600
  # # SELECT * FROM table ORDER BY id LIMIT 100
581
601
  # # SELECT * FROM table ORDER BY id LIMIT 100 OFFSET 100
582
602
  # # ...
@@ -918,6 +938,19 @@ module Sequel
918
938
  #
919
939
  # DB[:table].update(x: Sequel[:x]+1, y: 0) # UPDATE table SET x = (x + 1), y = 0
920
940
  # # => 10
941
+ #
942
+ # Some databases support using multiple tables in an UPDATE query. This requires
943
+ # multiple FROM tables (JOINs can also be used). As multiple FROM tables use
944
+ # an implicit CROSS JOIN, you should make sure your WHERE condition uses the
945
+ # appropriate filters for the FROM tables:
946
+ #
947
+ # DB.from(:a, :b).join(:c, :d=>Sequel[:b][:e]).where{{a[:f]=>b[:g], a[:id]=>10}}.
948
+ # update(:f=>Sequel[:c][:h])
949
+ # # UPDATE a
950
+ # # SET f = c.h
951
+ # # FROM b
952
+ # # INNER JOIN c ON (c.d = b.e)
953
+ # # WHERE ((a.f = b.g) AND (a.id = 10))
921
954
  def update(values=OPTS, &block)
922
955
  sql = update_sql(values)
923
956
  if uses_returning?(:update)
@@ -1023,18 +1056,19 @@ module Sequel
1023
1056
 
1024
1057
  # Internals of #import. If primary key values are requested, use
1025
1058
  # separate insert commands for each row. Otherwise, call #multi_insert_sql
1026
- # and execute each statement it gives separately.
1059
+ # and execute each statement it gives separately. A transaction is only used
1060
+ # if there are multiple statements to execute.
1027
1061
  def _import(columns, values, opts)
1028
1062
  trans_opts = Hash[opts]
1029
1063
  trans_opts[:server] = @opts[:server]
1030
1064
  if opts[:return] == :primary_key
1031
- @db.transaction(trans_opts){values.map{|v| insert(columns, v)}}
1065
+ _import_transaction(values, trans_opts){values.map{|v| insert(columns, v)}}
1032
1066
  else
1033
1067
  stmts = multi_insert_sql(columns, values)
1034
- @db.transaction(trans_opts){stmts.each{|st| execute_dui(st)}}
1068
+ _import_transaction(stmts, trans_opts){stmts.each{|st| execute_dui(st)}}
1035
1069
  end
1036
1070
  end
1037
-
1071
+
1038
1072
  # Return an array of arrays of values given by the symbols in ret_cols.
1039
1073
  def _select_map_multiple(ret_cols)
1040
1074
  map{|r| r.values_at(*ret_cols)}
@@ -1073,6 +1107,17 @@ module Sequel
1073
1107
  end
1074
1108
  end
1075
1109
 
1110
+ # Use a transaction when yielding to the block if multiple values/statements
1111
+ # are provided. When only a single value or statement is provided, then yield
1112
+ # without using a transaction.
1113
+ def _import_transaction(values, trans_opts, &block)
1114
+ if values.length > 1
1115
+ @db.transaction(trans_opts, &block)
1116
+ else
1117
+ yield
1118
+ end
1119
+ end
1120
+
1076
1121
  # Internals of +select_hash+ and +select_hash_groups+
1077
1122
  def _select_hash(meth, key_column, value_column, opts=OPTS)
1078
1123
  select(*(key_column.is_a?(Array) ? key_column : [key_column]) + (value_column.is_a?(Array) ? value_column : [value_column])).
@@ -152,6 +152,11 @@ module Sequel
152
152
  supports_distinct_on?
153
153
  end
154
154
 
155
+ # Whether placeholder literalizers are supported, true by default.
156
+ def supports_placeholder_literalizer?
157
+ true
158
+ end
159
+
155
160
  # Whether the dataset supports pattern matching by regular expressions, false by default.
156
161
  def supports_regexp?
157
162
  false
@@ -302,7 +302,7 @@ module Sequel
302
302
  cache_set(key, loader + 1)
303
303
  loader = nil
304
304
  end
305
- elsif cache_sql?
305
+ elsif cache_sql? && supports_placeholder_literalizer?
306
306
  cache_set(key, 1)
307
307
  end
308
308