sequel 4.38.0 → 4.39.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +28 -0
  3. data/doc/association_basics.rdoc +1 -1
  4. data/doc/core_extensions.rdoc +8 -8
  5. data/doc/object_model.rdoc +7 -8
  6. data/doc/querying.rdoc +2 -2
  7. data/doc/release_notes/4.39.0.txt +127 -0
  8. data/doc/sql.rdoc +31 -31
  9. data/doc/transactions.rdoc +1 -1
  10. data/doc/virtual_rows.rdoc +1 -1
  11. data/lib/sequel/adapters/mock.rb +22 -66
  12. data/lib/sequel/adapters/shared/access.rb +2 -0
  13. data/lib/sequel/adapters/shared/cubrid.rb +2 -0
  14. data/lib/sequel/adapters/shared/db2.rb +2 -0
  15. data/lib/sequel/adapters/shared/firebird.rb +2 -0
  16. data/lib/sequel/adapters/shared/informix.rb +2 -0
  17. data/lib/sequel/adapters/shared/mssql.rb +10 -2
  18. data/lib/sequel/adapters/shared/mysql.rb +17 -4
  19. data/lib/sequel/adapters/shared/oracle.rb +9 -0
  20. data/lib/sequel/adapters/shared/postgres.rb +34 -1
  21. data/lib/sequel/adapters/shared/sqlanywhere.rb +1 -0
  22. data/lib/sequel/adapters/shared/sqlite.rb +8 -0
  23. data/lib/sequel/core.rb +10 -0
  24. data/lib/sequel/database.rb +5 -0
  25. data/lib/sequel/database/connecting.rb +28 -0
  26. data/lib/sequel/database/misc.rb +0 -51
  27. data/lib/sequel/database/query.rb +1 -1
  28. data/lib/sequel/database/schema_generator.rb +9 -0
  29. data/lib/sequel/database/transactions.rb +65 -0
  30. data/lib/sequel/dataset/actions.rb +1 -1
  31. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  32. data/lib/sequel/extensions/core_extensions.rb +1 -1
  33. data/lib/sequel/extensions/core_refinements.rb +1 -1
  34. data/lib/sequel/extensions/migration.rb +17 -1
  35. data/lib/sequel/extensions/no_auto_literal_strings.rb +1 -1
  36. data/lib/sequel/extensions/pg_array_ops.rb +1 -1
  37. data/lib/sequel/extensions/pg_hstore_ops.rb +1 -1
  38. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  39. data/lib/sequel/extensions/pg_interval.rb +1 -1
  40. data/lib/sequel/extensions/pg_json_ops.rb +28 -15
  41. data/lib/sequel/extensions/pg_range_ops.rb +1 -1
  42. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  43. data/lib/sequel/extensions/select_remove.rb +1 -1
  44. data/lib/sequel/extensions/sql_expr.rb +1 -1
  45. data/lib/sequel/model/associations.rb +1 -1
  46. data/lib/sequel/plugins/active_model.rb +19 -8
  47. data/lib/sequel/plugins/dataset_associations.rb +1 -1
  48. data/lib/sequel/plugins/hook_class_methods.rb +38 -2
  49. data/lib/sequel/plugins/list.rb +1 -1
  50. data/lib/sequel/plugins/pg_array_associations.rb +3 -3
  51. data/lib/sequel/plugins/timestamps.rb +15 -2
  52. data/lib/sequel/plugins/touch.rb +6 -0
  53. data/lib/sequel/sql.rb +15 -11
  54. data/lib/sequel/version.rb +1 -1
  55. data/spec/adapters/mysql_spec.rb +18 -0
  56. data/spec/adapters/postgres_spec.rb +30 -1
  57. data/spec/core/database_spec.rb +29 -0
  58. data/spec/core/mock_adapter_spec.rb +32 -1
  59. data/spec/extensions/migration_spec.rb +46 -0
  60. data/spec/extensions/pg_interval_spec.rb +1 -0
  61. data/spec/extensions/pg_json_ops_spec.rb +13 -0
  62. data/spec/extensions/timestamps_spec.rb +11 -0
  63. data/spec/extensions/touch_spec.rb +8 -0
  64. metadata +4 -2
@@ -168,4 +168,4 @@ behavior to execute multiple times. For example, with the following code:
168
168
 
169
169
  The logger.info method will be called multiple times if there is a serialization failure.
170
170
 
171
- The :num_retries option can be used to set the maxmimum number of times to retry. It is set to 5 times by default.
171
+ The :num_retries option can be used to set the maximum number of times to retry. It is set to 5 times by default.
@@ -23,7 +23,7 @@ SQL function expression.
23
23
  It's possible to use Sequel's DSL to represent such expressions, but it is a
24
24
  little verbose:
25
25
 
26
- dataset.where(Sequel.expr(:a) > Sequel.function(:b, :c))
26
+ dataset.where(Sequel[:a] > Sequel.function(:b, :c))
27
27
  # WHERE a > b(c)
28
28
 
29
29
  The virtual row DSL makes such code more concise:
@@ -32,63 +32,6 @@ module Sequel
32
32
  class Database < Sequel::Database
33
33
  set_adapter_scheme :mock
34
34
 
35
- # Map of database type names to module names, used for handling
36
- # mock adapters for specific database types.
37
- SHARED_ADAPTERS = {
38
- 'access'=>'Access',
39
- 'cubrid'=>'Cubrid',
40
- 'db2'=>'DB2',
41
- 'firebird'=>'Firebird',
42
- 'informix'=>'Informix',
43
- 'mssql'=>'MSSQL',
44
- 'mysql'=>'MySQL',
45
- 'oracle'=>'Oracle',
46
- 'postgres'=>'Postgres',
47
- 'sqlanywhere'=>'SqlAnywhere',
48
- 'sqlite'=>'SQLite'
49
- }
50
-
51
- # Procs to run for specific database types to get the mock adapter
52
- # to work with the shared adapter
53
- SHARED_ADAPTER_SETUP = {
54
- 'postgres' => lambda do |db|
55
- db.instance_eval do
56
- @server_version = 90500
57
- initialize_postgres_adapter
58
- end
59
- db.extend(Module.new do
60
- def bound_variable_arg(arg, conn)
61
- arg
62
- end
63
-
64
- def primary_key(table)
65
- :id
66
- end
67
- end)
68
- end,
69
- 'oracle' => lambda do |db|
70
- db.instance_eval do
71
- @server_version = 11000000
72
- @primary_key_sequences = {}
73
- end
74
- end,
75
- 'mysql' => lambda do |db|
76
- db.instance_eval do
77
- @server_version = 50617
78
- end
79
- end,
80
- 'mssql' => lambda do |db|
81
- db.instance_eval do
82
- @server_version = 11000000
83
- end
84
- end,
85
- 'sqlite' => lambda do |db|
86
- db.instance_eval do
87
- @sqlite_version = 30903
88
- end
89
- end
90
- }
91
-
92
35
  # Set the autogenerated primary key integer
93
36
  # to be returned when running an insert query.
94
37
  # Argument types supported:
@@ -292,17 +235,30 @@ module Sequel
292
235
  def adapter_initialize
293
236
  opts = @opts
294
237
  @sqls = opts[:sqls] || []
295
- if mod_name = SHARED_ADAPTERS[opts[:host]]
296
- @shared_adapter = true
297
- require "sequel/adapters/shared/#{opts[:host]}"
298
- extend Sequel.const_get(mod_name)::DatabaseMethods
299
- extend_datasets Sequel.const_get(mod_name)::DatasetMethods
300
- if pr = SHARED_ADAPTER_SETUP[opts[:host]]
301
- pr.call(self)
238
+ @shared_adapter = false
239
+
240
+ case db_type = opts[:host]
241
+ when String, Symbol
242
+ db_type = db_type.to_sym
243
+ unless mod = Sequel.synchronize{SHARED_ADAPTER_MAP[db_type]}
244
+ begin
245
+ require "sequel/adapters/shared/#{db_type}"
246
+ rescue LoadError
247
+ else
248
+ mod = Sequel.synchronize{SHARED_ADAPTER_MAP[db_type]}
249
+ end
250
+ end
251
+
252
+ if mod
253
+ @shared_adapter = true
254
+ extend(mod::DatabaseMethods)
255
+ extend_datasets(mod::DatasetMethods)
256
+ if mod.respond_to?(:mock_adapter_setup)
257
+ mod.mock_adapter_setup(self)
258
+ end
302
259
  end
303
- else
304
- @shared_adapter = false
305
260
  end
261
+
306
262
  self.autoid = opts[:autoid]
307
263
  self.columns = opts[:columns]
308
264
  self.fetch = opts[:fetch]
@@ -4,6 +4,8 @@ module Sequel
4
4
  require 'adapters/utils/emulate_offset_with_reverse_and_count'
5
5
 
6
6
  module Access
7
+ Sequel::Database.set_shared_adapter_scheme(:access, self)
8
+
7
9
  module DatabaseMethods
8
10
  extend Sequel::Database::ResetIdentifierMangling
9
11
 
@@ -4,6 +4,8 @@ Sequel.require 'adapters/utils/split_alter_table'
4
4
 
5
5
  module Sequel
6
6
  module Cubrid
7
+ Sequel::Database.set_shared_adapter_scheme(:cubrid, self)
8
+
7
9
  module DatabaseMethods
8
10
  extend Sequel::Database::ResetIdentifierMangling
9
11
 
@@ -4,6 +4,8 @@ Sequel.require 'adapters/utils/emulate_offset_with_row_number'
4
4
 
5
5
  module Sequel
6
6
  module DB2
7
+ Sequel::Database.set_shared_adapter_scheme(:db2, self)
8
+
7
9
  @use_clob_as_blob = false
8
10
 
9
11
  class << self
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Sequel
4
4
  module Firebird
5
+ Sequel::Database.set_shared_adapter_scheme(:firebird, self)
6
+
5
7
  module DatabaseMethods
6
8
  extend Sequel::Database::ResetIdentifierMangling
7
9
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Sequel
4
4
  module Informix
5
+ Sequel::Database.set_shared_adapter_scheme(:informix, self)
6
+
5
7
  module DatabaseMethods
6
8
  extend Sequel::Database::ResetIdentifierMangling
7
9
 
@@ -5,6 +5,14 @@ Sequel.require %w'emulate_offset_with_row_number split_alter_table', 'adapters/u
5
5
  module Sequel
6
6
  Dataset::NON_SQL_OPTIONS << :disable_insert_output
7
7
  module MSSQL
8
+ Sequel::Database.set_shared_adapter_scheme(:mssql, self)
9
+
10
+ def self.mock_adapter_setup(db)
11
+ db.instance_eval do
12
+ @server_version = 11000000
13
+ end
14
+ end
15
+
8
16
  module DatabaseMethods
9
17
  extend Sequel::Database::ResetIdentifierMangling
10
18
 
@@ -433,8 +441,8 @@ module Sequel
433
441
  tn = m2.call(table_name.to_s)
434
442
  table_id = get(Sequel.function(:object_id, tn))
435
443
  info_sch_sch = opts[:information_schema_schema]
436
- inf_sch_qual = lambda{|s| info_sch_sch ? Sequel.qualify(info_sch_sch, s) : Sequel.expr(s)}
437
- sys_qual = lambda{|s| info_sch_sch ? Sequel.qualify(info_sch_sch, Sequel.qualify(Sequel.lit(''), s)) : Sequel.expr(s)}
444
+ inf_sch_qual = lambda{|s| info_sch_sch ? Sequel.qualify(info_sch_sch, s) : Sequel[s]}
445
+ sys_qual = lambda{|s| info_sch_sch ? Sequel.qualify(info_sch_sch, Sequel.qualify(Sequel.lit(''), s)) : Sequel[s]}
438
446
 
439
447
  identity_cols = metadata_dataset.from(Sequel.lit('[sys].[columns]')).
440
448
  where(:object_id=>table_id, :is_identity=>true).
@@ -9,6 +9,14 @@ module Sequel
9
9
  Dataset::NON_SQL_OPTIONS << :on_duplicate_key_update
10
10
 
11
11
  module MySQL
12
+ Sequel::Database.set_shared_adapter_scheme(:mysql, self)
13
+
14
+ def self.mock_adapter_setup(db)
15
+ db.instance_eval do
16
+ @server_version = 50617
17
+ end
18
+ end
19
+
12
20
  @convert_tinyint_to_bool = true
13
21
 
14
22
  class << self
@@ -188,17 +196,22 @@ module Sequel
188
196
  private
189
197
 
190
198
  def alter_table_add_column_sql(table, op)
191
- if related = op.delete(:table)
192
- sql = super.dup
199
+ pos = if after_col = op[:after]
200
+ " AFTER #{quote_identifier(after_col)}"
201
+ elsif op[:first]
202
+ " FIRST"
203
+ end
204
+
205
+ sql = if related = op.delete(:table)
206
+ sql = super + "#{pos}, ADD "
193
207
  op[:table] = related
194
208
  op[:key] ||= primary_key_from_schema(related)
195
- sql << ", ADD "
196
209
  if constraint_name = op.delete(:foreign_key_constraint_name)
197
210
  sql << "CONSTRAINT #{quote_identifier(constraint_name)} "
198
211
  end
199
212
  sql << "FOREIGN KEY (#{quote_identifier(op[:name])})#{column_references_sql(op)}"
200
213
  else
201
- super
214
+ "#{super}#{pos}"
202
215
  end
203
216
  end
204
217
 
@@ -4,6 +4,15 @@ Sequel.require 'adapters/utils/emulate_offset_with_row_number'
4
4
 
5
5
  module Sequel
6
6
  module Oracle
7
+ Sequel::Database.set_shared_adapter_scheme(:oracle, self)
8
+
9
+ def self.mock_adapter_setup(db)
10
+ db.instance_eval do
11
+ @server_version = 11000000
12
+ @primary_key_sequences = {}
13
+ end
14
+ end
15
+
7
16
  module DatabaseMethods
8
17
  extend Sequel::Database::ResetIdentifierMangling
9
18
 
@@ -25,6 +25,26 @@ module Sequel
25
25
  # qualification, and in which order to check the schemas when
26
26
  # an unqualified object is referenced.
27
27
  module Postgres
28
+ Sequel::Database.set_shared_adapter_scheme(:postgres, self)
29
+
30
+ module MockAdapterDatabaseMethods
31
+ def bound_variable_arg(arg, conn)
32
+ arg
33
+ end
34
+
35
+ def primary_key(table)
36
+ :id
37
+ end
38
+ end
39
+
40
+ def self.mock_adapter_setup(db)
41
+ db.instance_eval do
42
+ @server_version = 90500
43
+ initialize_postgres_adapter
44
+ extend(MockAdapterDatabaseMethods)
45
+ end
46
+ end
47
+
28
48
  # Array of exceptions that need to be converted. JDBC
29
49
  # uses NativeExceptions, the native adapter uses PGError.
30
50
  CONVERTED_EXCEPTIONS = []
@@ -577,6 +597,10 @@ module Sequel
577
597
  end
578
598
  end
579
599
 
600
+ def alter_table_add_column_sql(table, op)
601
+ "ADD COLUMN#{' IF NOT EXISTS' if op[:if_not_exists]} #{column_definition_sql(op)}"
602
+ end
603
+
580
604
  # Use a PostgreSQL-specific alter table generator
581
605
  def alter_table_generator_class
582
606
  Postgres::AlterTableGenerator
@@ -1315,6 +1339,8 @@ module Sequel
1315
1339
  # :phrase :: Similar to :plain, but also adding an ILIKE filter to ensure that
1316
1340
  # returned rows also include the exact phrase used.
1317
1341
  # :rank :: Set to true to order by the rank, so that closer matches are returned first.
1342
+ # :to_tsquery :: Can be set to :plain or :phrase to specify the function to use to
1343
+ # convert the terms to a ts_query.
1318
1344
  # :tsquery :: Specifies the terms argument is already a valid SQL expression returning a
1319
1345
  # tsquery, and can be used directly in the query.
1320
1346
  # :tsvector :: Specifies the cols argument is already a valid SQL expression returning a
@@ -1329,7 +1355,14 @@ module Sequel
1329
1355
 
1330
1356
  unless opts[:tsquery]
1331
1357
  phrase_terms = terms.is_a?(Array) ? terms.join(' | ') : terms
1332
- query_func = (opts[:phrase] || opts[:plain]) ? :plainto_tsquery : :to_tsquery
1358
+
1359
+ query_func = case to_tsquery = opts[:to_tsquery]
1360
+ when :phrase, :plain
1361
+ :"#{to_tsquery}to_tsquery"
1362
+ else
1363
+ (opts[:phrase] || opts[:plain]) ? :plainto_tsquery : :to_tsquery
1364
+ end
1365
+
1333
1366
  terms = Sequel.function(query_func, lang, phrase_terms)
1334
1367
  end
1335
1368
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Sequel
4
4
  module SqlAnywhere
5
+ Sequel::Database.set_shared_adapter_scheme(:sqlanywhere, self)
5
6
 
6
7
  @convert_smallint_to_bool = true
7
8
 
@@ -4,6 +4,14 @@ Sequel.require 'adapters/utils/replace'
4
4
 
5
5
  module Sequel
6
6
  module SQLite
7
+ Sequel::Database.set_shared_adapter_scheme(:sqlite, self)
8
+
9
+ def self.mock_adapter_setup(db)
10
+ db.instance_eval do
11
+ @sqlite_version = 30903
12
+ end
13
+ end
14
+
7
15
  # No matter how you connect to SQLite, the following Database options
8
16
  # can be used to set PRAGMAs on connections in a thread-safe manner:
9
17
  # :auto_vacuum, :foreign_keys, :synchronous, and :temp_store.
@@ -392,6 +392,16 @@ module Sequel
392
392
 
393
393
  require(%w"deprecated sql connection_pool exceptions dataset database timezones ast_transformer version")
394
394
 
395
+ class << self
396
+ # Allow nicer syntax for creating Sequel expressions:
397
+ #
398
+ # Sequel[1] # => Sequel::SQL::NumericExpression: 1
399
+ # Sequel["a"] # => Sequel::SQL::StringExpression: 'a'
400
+ # Sequel[:a] # => Sequel::SQL::Identifier: "a"
401
+ # Sequel[:a=>1] # => Sequel::SQL::BooleanExpression: ("a" = 1)
402
+ alias_method :[], :expr
403
+ end
404
+
395
405
  # Add the database adapter class methods to Sequel via metaprogramming
396
406
  def_adapter_method(*Database::ADAPTERS)
397
407
  end
@@ -5,6 +5,11 @@ module Sequel
5
5
  # symbol, and the value is the Database subclass.
6
6
  ADAPTER_MAP = {}
7
7
 
8
+ # Hash of shared adapters that have been registered. The key is the
9
+ # adapter scheme symbol, and the value is the Sequel module containing
10
+ # the shared adapter.
11
+ SHARED_ADAPTER_MAP = {}
12
+
8
13
  # Array of all databases to which Sequel has connected. If you are
9
14
  # developing an application that can connect to an arbitrary number of
10
15
  # databases, delete the database objects from this or they will not get
@@ -129,6 +129,34 @@ module Sequel
129
129
  Sequel.synchronize{ADAPTER_MAP[scheme] = self}
130
130
  end
131
131
  private_class_method :set_adapter_scheme
132
+
133
+ # Sets the given module as the shared adapter module for the given scheme.
134
+ # Used to register shared adapters for use by the mock adapter. Example:
135
+ #
136
+ # # in file sequel/adapters/shared/mydb.rb
137
+ # module Sequel::MyDB
138
+ # Sequel::Database.set_shared_adapter_scheme :mydb, :self
139
+ #
140
+ # def self.mock_adapter_setup(db)
141
+ # # ...
142
+ # end
143
+ #
144
+ # module DatabaseMethods
145
+ # # ...
146
+ # end
147
+ #
148
+ # module DatasetMethods
149
+ # # ...
150
+ # end
151
+ # end
152
+ #
153
+ # would allow the mock adapter to return a Database instance that supports
154
+ # the MyDB syntax via:
155
+ #
156
+ # Sequel.connect('mock://mydb')
157
+ def self.set_shared_adapter_scheme(scheme, mod)
158
+ Sequel.synchronize{SHARED_ADAPTER_MAP[scheme] = mod}
159
+ end
132
160
 
133
161
  # The connection pool for this Database instance. All Database instances have
134
162
  # their own connection pools.
@@ -160,38 +160,6 @@ module Sequel
160
160
  end
161
161
  end
162
162
 
163
- # If a transaction is not currently in process, yield to the block immediately.
164
- # Otherwise, add the block to the list of blocks to call after the currently
165
- # in progress transaction commits (and only if it commits).
166
- # Options:
167
- # :server :: The server/shard to use.
168
- def after_commit(opts=OPTS, &block)
169
- raise Error, "must provide block to after_commit" unless block
170
- synchronize(opts[:server]) do |conn|
171
- if h = _trans(conn)
172
- raise Error, "cannot call after_commit in a prepared transaction" if h[:prepare]
173
- add_transaction_hook(conn, :after_commit, block)
174
- else
175
- yield
176
- end
177
- end
178
- end
179
-
180
- # If a transaction is not currently in progress, ignore the block.
181
- # Otherwise, add the block to the list of the blocks to call after the currently
182
- # in progress transaction rolls back (and only if it rolls back).
183
- # Options:
184
- # :server :: The server/shard to use.
185
- def after_rollback(opts=OPTS, &block)
186
- raise Error, "must provide block to after_rollback" unless block
187
- synchronize(opts[:server]) do |conn|
188
- if h = _trans(conn)
189
- raise Error, "cannot call after_rollback in a prepared transaction" if h[:prepare]
190
- add_transaction_hook(conn, :after_rollback, block)
191
- end
192
- end
193
- end
194
-
195
163
  # Cast the given type to a literal type
196
164
  #
197
165
  # DB.cast_type_literal(Float) # double precision
@@ -224,13 +192,6 @@ module Sequel
224
192
  Sequel.convert_output_timestamp(v, timezone)
225
193
  end
226
194
 
227
- # Return true if already in a transaction given the options,
228
- # false otherwise. Respects the :server option for selecting
229
- # a shard.
230
- def in_transaction?(opts=OPTS)
231
- synchronize(opts[:server]){|conn| !!_trans(conn)}
232
- end
233
-
234
195
  # Returns a string representation of the database object including the
235
196
  # class name and connection URI and options used when connecting (if any).
236
197
  def inspect
@@ -341,13 +302,6 @@ module Sequel
341
302
  def adapter_initialize
342
303
  end
343
304
 
344
- # Set the given callable as a hook to be called. Type should be either
345
- # :after_commit or :after_rollback.
346
- def add_transaction_hook(conn, type, block)
347
- hooks = _trans(conn)[type] ||= []
348
- hooks << block
349
- end
350
-
351
305
  # Returns true when the object is considered blank.
352
306
  # The only objects that are blank are nil, false,
353
307
  # strings with all whitespace, and ones that respond
@@ -366,11 +320,6 @@ module Sequel
366
320
  end
367
321
  end
368
322
 
369
- # Which transaction errors to translate, blank by default.
370
- def database_error_classes
371
- []
372
- end
373
-
374
323
  # An enumerable yielding pairs of regexps and exception classes, used
375
324
  # to match against underlying driver exception messages in
376
325
  # order to raise a more specific Sequel::DatabaseError subclass.