sequel 4.38.0 → 4.39.0

Sign up to get free protection for your applications and to get access to all the features.
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.