sequel 4.42.1 → 4.43.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +35 -1
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/doc/release_notes/4.43.0.txt +87 -0
  6. data/doc/sql.rdoc +26 -27
  7. data/doc/testing.rdoc +2 -0
  8. data/doc/validations.rdoc +1 -1
  9. data/lib/sequel/adapters/ado.rb +5 -0
  10. data/lib/sequel/adapters/cubrid.rb +5 -0
  11. data/lib/sequel/adapters/ibmdb.rb +5 -0
  12. data/lib/sequel/adapters/jdbc.rb +6 -0
  13. data/lib/sequel/adapters/jdbc/derby.rb +5 -0
  14. data/lib/sequel/adapters/jdbc/hsqldb.rb +9 -5
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -1
  16. data/lib/sequel/adapters/jdbc/sqlite.rb +5 -0
  17. data/lib/sequel/adapters/jdbc/transactions.rb +5 -0
  18. data/lib/sequel/adapters/mock.rb +12 -9
  19. data/lib/sequel/adapters/mysql.rb +6 -0
  20. data/lib/sequel/adapters/mysql2.rb +7 -2
  21. data/lib/sequel/adapters/oracle.rb +5 -0
  22. data/lib/sequel/adapters/shared/db2.rb +7 -1
  23. data/lib/sequel/adapters/shared/mssql.rb +5 -0
  24. data/lib/sequel/adapters/shared/mysql.rb +8 -1
  25. data/lib/sequel/adapters/shared/oracle.rb +20 -12
  26. data/lib/sequel/adapters/shared/postgres.rb +11 -2
  27. data/lib/sequel/adapters/shared/sqlanywhere.rb +6 -0
  28. data/lib/sequel/adapters/shared/sqlite.rb +29 -0
  29. data/lib/sequel/adapters/sqlanywhere.rb +5 -0
  30. data/lib/sequel/adapters/sqlite.rb +13 -0
  31. data/lib/sequel/connection_pool/sharded_single.rb +5 -0
  32. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -0
  33. data/lib/sequel/connection_pool/single.rb +15 -6
  34. data/lib/sequel/database/dataset.rb +3 -0
  35. data/lib/sequel/database/misc.rb +22 -1
  36. data/lib/sequel/database/query.rb +2 -4
  37. data/lib/sequel/dataset/actions.rb +0 -1
  38. data/lib/sequel/dataset/misc.rb +2 -4
  39. data/lib/sequel/dataset/query.rb +23 -6
  40. data/lib/sequel/extensions/_model_constraint_validations.rb +16 -0
  41. data/lib/sequel/extensions/_model_pg_row.rb +47 -0
  42. data/lib/sequel/extensions/looser_typecasting.rb +2 -0
  43. data/lib/sequel/extensions/migration.rb +12 -1
  44. data/lib/sequel/extensions/pg_array.rb +6 -0
  45. data/lib/sequel/extensions/pg_enum.rb +2 -1
  46. data/lib/sequel/extensions/pg_range.rb +6 -0
  47. data/lib/sequel/extensions/pg_row.rb +8 -0
  48. data/lib/sequel/model/associations.rb +3 -1
  49. data/lib/sequel/model/base.rb +14 -3
  50. data/lib/sequel/plugins/constraint_validations.rb +1 -8
  51. data/lib/sequel/plugins/instance_filters.rb +1 -1
  52. data/lib/sequel/plugins/pg_row.rb +1 -40
  53. data/lib/sequel/plugins/prepared_statements.rb +51 -20
  54. data/lib/sequel/plugins/prepared_statements_associations.rb +22 -4
  55. data/lib/sequel/plugins/prepared_statements_with_pk.rb +9 -1
  56. data/lib/sequel/plugins/sharding.rb +5 -0
  57. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  58. data/lib/sequel/version.rb +2 -2
  59. data/spec/adapters/spec_helper.rb +4 -0
  60. data/spec/core/connection_pool_spec.rb +10 -0
  61. data/spec/core/database_spec.rb +29 -0
  62. data/spec/extensions/blacklist_security_spec.rb +4 -4
  63. data/spec/extensions/defaults_setter_spec.rb +1 -1
  64. data/spec/extensions/force_encoding_spec.rb +3 -2
  65. data/spec/extensions/identifier_mangling_spec.rb +7 -0
  66. data/spec/extensions/instance_filters_spec.rb +1 -0
  67. data/spec/extensions/migration_spec.rb +19 -0
  68. data/spec/extensions/pg_array_spec.rb +5 -0
  69. data/spec/extensions/pg_range_spec.rb +5 -0
  70. data/spec/extensions/pg_row_spec.rb +9 -0
  71. data/spec/extensions/prepared_statements_associations_spec.rb +45 -1
  72. data/spec/extensions/prepared_statements_spec.rb +138 -41
  73. data/spec/extensions/prepared_statements_with_pk_spec.rb +7 -0
  74. data/spec/extensions/serialization_spec.rb +6 -6
  75. data/spec/extensions/single_table_inheritance_spec.rb +3 -3
  76. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  77. data/spec/integration/associations_test.rb +2 -2
  78. data/spec/integration/dataset_test.rb +0 -4
  79. data/spec/integration/eager_loader_test.rb +5 -5
  80. data/spec/integration/plugin_test.rb +8 -6
  81. data/spec/integration/schema_test.rb +2 -2
  82. data/spec/integration/spec_helper.rb +10 -0
  83. data/spec/integration/timezone_test.rb +1 -1
  84. data/spec/integration/transaction_test.rb +5 -5
  85. data/spec/model/associations_spec.rb +13 -6
  86. data/spec/model/base_spec.rb +1 -1
  87. data/spec/model/hooks_spec.rb +4 -4
  88. data/spec/model/model_spec.rb +2 -2
  89. data/spec/model/record_spec.rb +17 -18
  90. metadata +6 -2
@@ -103,6 +103,11 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
103
103
  end
104
104
  end
105
105
 
106
+ def freeze
107
+ @servers.freeze
108
+ super
109
+ end
110
+
106
111
  # Chooses the first available connection to the given server, or if none are
107
112
  # available, creates a new connection. Passes the connection to the supplied
108
113
  # block:
@@ -4,22 +4,31 @@
4
4
  # It is just a wrapper around a single connection that uses the connection pool
5
5
  # API.
6
6
  class Sequel::SingleConnectionPool < Sequel::ConnectionPool
7
+ def initialize(db, opts=OPTS)
8
+ super
9
+ @conn = []
10
+ end
11
+
7
12
  # Yield the connection if one has been made.
8
13
  def all_connections
9
- yield @conn if @conn
14
+ yield @conn.first if @conn
10
15
  end
11
16
 
12
17
  # Disconnect the connection from the database.
13
18
  def disconnect(opts=nil)
14
19
  return unless @conn
15
- disconnect_connection(@conn)
16
- @conn = nil
20
+ disconnect_connection(@conn.first)
21
+ @conn.clear
22
+ nil
17
23
  end
18
-
24
+
19
25
  # Yield the connection to the block.
20
26
  def hold(server=nil)
21
27
  begin
22
- yield(@conn ||= make_new(DEFAULT_SERVER))
28
+ unless c = @conn.first
29
+ @conn.replace([c = make_new(DEFAULT_SERVER)])
30
+ end
31
+ yield c
23
32
  rescue Sequel::DatabaseDisconnectError, *@error_classes => e
24
33
  disconnect if disconnect_error?(e)
25
34
  raise
@@ -38,7 +47,7 @@ class Sequel::SingleConnectionPool < Sequel::ConnectionPool
38
47
  # The SingleConnectionPool always has a size of 1 if connected
39
48
  # and 0 if not.
40
49
  def size
41
- @conn ? 1 : 0
50
+ @conn.empty? ? 0 : 1
42
51
  end
43
52
 
44
53
  private
@@ -43,6 +43,9 @@ module Sequel
43
43
  # injection:
44
44
  #
45
45
  # DB.fetch('SELECT * FROM items WHERE name = ?', my_name).all
46
+ #
47
+ # See caveats listed in Dataset#with_sql regarding datasets using custom
48
+ # SQL and the methods that can be called on them.
46
49
  def fetch(sql, *args, &block)
47
50
  ds = @default_dataset.with_sql(sql, *args)
48
51
  ds.each(&block) if block
@@ -138,6 +138,7 @@ module Sequel
138
138
  @dataset_class = dataset_class_default
139
139
  @cache_schema = typecast_value_boolean(@opts.fetch(:cache_schema, true))
140
140
  @dataset_modules = []
141
+ @loaded_extensions = []
141
142
  @schema_type_classes = SCHEMA_TYPE_CLASSES.dup
142
143
 
143
144
  self.sql_log_level = @opts[:sql_log_level] ? @opts[:sql_log_level].to_sym : :info
@@ -162,6 +163,23 @@ module Sequel
162
163
  end
163
164
  end
164
165
 
166
+ # Freeze internal data structures for the Database instance.
167
+ def freeze
168
+ valid_connection_sql
169
+ metadata_dataset
170
+ @opts.freeze
171
+ @loggers.freeze
172
+ @pool.freeze
173
+ @dataset_class.freeze
174
+ @dataset_modules.freeze
175
+ @schema_type_classes.freeze
176
+ @loaded_extensions.freeze
177
+ # SEQUEL5: Frozen by default, remove this
178
+ @default_dataset.freeze
179
+ metadata_dataset.freeze
180
+ super
181
+ end
182
+
165
183
  # Cast the given type to a literal type
166
184
  #
167
185
  # DB.cast_type_literal(Float) # double precision
@@ -179,7 +197,10 @@ module Sequel
179
197
  Sequel.extension(*exts)
180
198
  exts.each do |ext|
181
199
  if pr = Sequel.synchronize{EXTENSIONS[ext]}
182
- pr.call(self)
200
+ unless Sequel.synchronize{@loaded_extensions.include?(ext)}
201
+ Sequel.synchronize{@loaded_extensions << ext}
202
+ pr.call(self)
203
+ end
183
204
  else
184
205
  raise(Error, "Extension #{ext} does not have specific support handling individual databases (try: Sequel.extension #{ext.inspect})")
185
206
  end
@@ -308,10 +308,8 @@ module Sequel
308
308
 
309
309
  # Remove the cached schema for the given schema name
310
310
  def remove_cached_schema(table)
311
- if @schemas
312
- k = quote_schema_table(table)
313
- Sequel.synchronize{@schemas.delete(k)}
314
- end
311
+ k = quote_schema_table(table)
312
+ Sequel.synchronize{@schemas.delete(k)}
315
313
  end
316
314
 
317
315
  # Match the database's column type to a ruby type via a
@@ -848,7 +848,6 @@ module Sequel
848
848
  # :hash :: The object into which the values will be placed. If this is not
849
849
  # given, an empty hash is used. This can be used to use a hash with
850
850
  # a default value or default proc.
851
- # to start with a new, empty hash.
852
851
  def to_hash_groups(key_column, value_column = nil, opts = OPTS)
853
852
  h = opts[:hash] || {}
854
853
  meth = opts[:all] ? :all : :each
@@ -91,14 +91,12 @@ module Sequel
91
91
  end
92
92
  else
93
93
  # :nocov:
94
- # :nodoc:
95
- def freeze
94
+ def freeze # :nodoc:
96
95
  @opts.freeze
97
96
  self
98
97
  end
99
98
 
100
- # :nodoc:
101
- def frozen?
99
+ def frozen? # :nodoc:
102
100
  @opts.frozen?
103
101
  end
104
102
  # :nocov:
@@ -96,8 +96,7 @@ module Sequel
96
96
  end
97
97
  else
98
98
  # :nocov:
99
- # :nodoc:
100
- def clone(opts = OPTS)
99
+ def clone(opts = OPTS) # :nodoc:
101
100
  c = super()
102
101
  c.opts.merge!(opts)
103
102
  unless opts.each_key{|o| break if COLUMN_CHANGE_OPTS.include?(o)}
@@ -204,8 +203,7 @@ module Sequel
204
203
  end
205
204
  else
206
205
  # :nocov:
207
- # :nodoc:
208
- def extension(*exts)
206
+ def extension(*exts) # :nodoc:
209
207
  c = clone
210
208
  c.send(:_extension!, exts)
211
209
  c
@@ -1085,8 +1083,7 @@ module Sequel
1085
1083
  end
1086
1084
  else
1087
1085
  # :nocov:
1088
- # :nodoc:
1089
- def with_extend(*mods, &block)
1086
+ def with_extend(*mods, &block) # :nodoc:
1090
1087
  c = clone
1091
1088
  c.extend(*mods) unless mods.empty?
1092
1089
  c.extend(Module.new(&block)) if block
@@ -1116,6 +1113,26 @@ module Sequel
1116
1113
  # You can also provide a method name and arguments to call to get the SQL:
1117
1114
  #
1118
1115
  # DB[:items].with_sql(:insert_sql, :b=>1) # INSERT INTO items (b) VALUES (1)
1116
+ #
1117
+ # Note that datasets that specify custom SQL using this method will generally
1118
+ # ignore future dataset methods that modify the SQL used, as specifying custom SQL
1119
+ # overrides Sequel's SQL generator. You should probably limit yourself to the following
1120
+ # dataset methods when using this method:
1121
+ #
1122
+ # * each
1123
+ # * all
1124
+ # * single_record (if only one record could be returned)
1125
+ # * single_value (if only one record could be returned, and a single column is selected)
1126
+ # * map
1127
+ # * to_hash
1128
+ # * delete (if a DELETE statement)
1129
+ # * update (if an UPDATE statement, with no arguments)
1130
+ # * insert (if an INSERT statement, with no arguments)
1131
+ # * truncate (if a TRUNCATE statement, with no arguments)
1132
+ #
1133
+ # If you want to use arbitrary dataset methods on a dataset that uses custom SQL, call
1134
+ # from_self on the dataset, which wraps the custom SQL in a subquery, and allows normal
1135
+ # dataset methods that modify the SQL to work.
1119
1136
  def with_sql(sql, *args)
1120
1137
  if sql.is_a?(Symbol)
1121
1138
  sql = send(sql, *args)
@@ -0,0 +1,16 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ module ConstraintValidations
6
+ module DatabaseMethods
7
+ # A hash of validation method call metadata for all tables in the database.
8
+ # The hash is keyed by table name string and contains arrays of validation
9
+ # method call arrays.
10
+ attr_accessor :constraint_validations
11
+ end
12
+ end
13
+ end
14
+
15
+ Database.register_extension(:_model_constraint_validations, Plugins::ConstraintValidations::DatabaseMethods)
16
+ end
@@ -0,0 +1,47 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ module PgRow
6
+ module DatabaseMethods
7
+ ESCAPE_RE = /("|\\)/.freeze
8
+ ESCAPE_REPLACEMENT = '\\\\\1'.freeze
9
+ COMMA = ','
10
+
11
+ # Handle Sequel::Model instances in bound variables.
12
+ def bound_variable_arg(arg, conn)
13
+ case arg
14
+ when Sequel::Model
15
+ "(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA)})"
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ # If a Sequel::Model instance is given, return it as-is
22
+ # instead of attempting to convert it.
23
+ def row_type(db_type, v)
24
+ if v.is_a?(Sequel::Model)
25
+ v
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # Handle Sequel::Model instances in bound variable arrays.
34
+ def bound_variable_array(arg)
35
+ case arg
36
+ when Sequel::Model
37
+ "\"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
38
+ else
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ Database.register_extension(:_model_pg_row, Plugins::PgRow::DatabaseMethods)
47
+ end
@@ -41,9 +41,11 @@ module Sequel
41
41
  BigDecimal.new('0.0')
42
42
  end
43
43
  else
44
+ # :nocov:
44
45
  def _typecast_value_string_to_decimal(value)
45
46
  BigDecimal.new(value)
46
47
  end
48
+ # :nocov:
47
49
  end
48
50
  end
49
51
 
@@ -760,7 +760,18 @@ module Sequel
760
760
  c = column
761
761
  ds = db.from(table)
762
762
  if !db.table_exists?(table)
763
- db.create_table(table){String c, :primary_key=>true}
763
+ begin
764
+ db.create_table(table){String c, :primary_key=>true}
765
+ rescue Sequel::DatabaseError => e
766
+ if db.database_type == :mysql && e.message =~ /max key length/
767
+ # Handle case where MySQL is used with utf8mb4 charset default, which
768
+ # only allows a maximum length of about 190 characters for string
769
+ # primary keys due to InnoDB limitations.
770
+ db.create_table(table){String c, :primary_key=>true, :size=>190}
771
+ else
772
+ raise e
773
+ end
774
+ end
764
775
  if db.table_exists?(:schema_info) and vha = db[:schema_info].all and vha.length == 1 and
765
776
  vha.first.keys == [:version] and vha.first.values.first.is_a?(Integer)
766
777
  convert_from_schema_info
@@ -221,6 +221,12 @@ module Sequel
221
221
  end
222
222
  end
223
223
 
224
+ # Freeze the pg array schema types to prevent adding new ones.
225
+ def freeze
226
+ @pg_array_schema_types.freeze
227
+ super
228
+ end
229
+
224
230
  # Register a database specific array type. This can be used to support
225
231
  # different array types per Database. Use of this method does not
226
232
  # affect global state, unlike PGArray.register. See PGArray.register for
@@ -111,7 +111,8 @@ module Sequel
111
111
  def parse_enum_labels
112
112
  @enum_labels = metadata_dataset.from(:pg_enum).
113
113
  order(:enumtypid, :enumsortorder).
114
- select_hash_groups(Sequel.cast(:enumtypid, Integer).as(:v), :enumlabel)
114
+ select_hash_groups(Sequel.cast(:enumtypid, Integer).as(:v), :enumlabel).freeze
115
+ @enum_labels.each_value(&:freeze)
115
116
 
116
117
  if respond_to?(:register_array_type)
117
118
  array_types = metadata_dataset.
@@ -269,6 +269,12 @@ module Sequel
269
269
  end
270
270
  end
271
271
 
272
+ # Freeze the pg range schema types to prevent adding new ones.
273
+ def freeze
274
+ @pg_range_schema_types.freeze
275
+ super
276
+ end
277
+
272
278
  # Register a database specific range type. This can be used to support
273
279
  # different range types per Database. Use of this method does not
274
280
  # affect global state, unlike PGRange.register. See PGRange.register for
@@ -411,6 +411,14 @@ module Sequel
411
411
  end
412
412
  end
413
413
 
414
+ # Freeze the row types and row schema types to prevent adding new ones.
415
+ def freeze
416
+ @row_types.freeze
417
+ @row_schema_types.freeze
418
+ @row_type_method_module.freeze
419
+ super
420
+ end
421
+
414
422
  # Register a new row type for the Database instance. db_type should be the type
415
423
  # symbol. This parses the PostgreSQL system tables to get information the
416
424
  # composite type, and by default has the type return instances of a subclass
@@ -2119,8 +2119,10 @@ module Sequel
2119
2119
  # retrieving associations after freezing will still work in most cases,
2120
2120
  # but the associations will not be cached in the association cache.
2121
2121
  def freeze
2122
- associations.freeze
2122
+ associations
2123
2123
  super
2124
+ associations.freeze
2125
+ self
2124
2126
  end
2125
2127
 
2126
2128
  private
@@ -32,6 +32,10 @@ module Sequel
32
32
  # model instances, or nil if the optimization should not be used. For internal use only.
33
33
  attr_reader :fast_instance_delete_sql
34
34
 
35
+ # SQL string fragment used for faster lookups by primary key, or nil if the optimization
36
+ # should not be used. For internal use only.
37
+ attr_reader :fast_pk_lookup_sql
38
+
35
39
  # The dataset that instance datasets (#this) are based on. Generally a naked version of
36
40
  # the model's dataset limited to one row. For internal use only.
37
41
  attr_reader :instance_dataset
@@ -416,8 +420,8 @@ module Sequel
416
420
  # Artist.by_name.where(:name.like('A%'))
417
421
  #
418
422
  # # Just add a class method that calls an existing dataset method
419
- # Artist.def_dataset_method(:server!)
420
- # Artist.server!(:server1)
423
+ # Artist.def_dataset_method(:paginate)
424
+ # Artist.paginate(2, 10)
421
425
  def def_dataset_method(*args, &block)
422
426
  raise(Error, "No arguments given") if args.empty?
423
427
 
@@ -2038,7 +2042,14 @@ module Sequel
2038
2042
 
2039
2043
  # Get the row of column data from the database.
2040
2044
  def _refresh_get(dataset)
2041
- dataset.first
2045
+ if (sql = (m = model).fast_pk_lookup_sql) && !dataset.opts[:lock]
2046
+ sql = sql.dup
2047
+ ds = use_server(dataset)
2048
+ ds.literal_append(sql, pk)
2049
+ ds.with_sql_first(sql)
2050
+ else
2051
+ dataset.first
2052
+ end
2042
2053
  end
2043
2054
 
2044
2055
  # Set the refreshed values after
@@ -72,13 +72,6 @@ module Sequel
72
72
  end
73
73
  end
74
74
 
75
- module DatabaseMethods
76
- # A hash of validation method call metadata for all tables in the database.
77
- # The hash is keyed by table name string and contains arrays of validation
78
- # method call arrays.
79
- attr_accessor :constraint_validations
80
- end
81
-
82
75
  module ClassMethods
83
76
  # An array of validation method call arrays. Each array is an array that
84
77
  # is splatted to send to perform a validation via validation_helpers.
@@ -106,7 +99,7 @@ module Sequel
106
99
  # If this model has associated dataset, use the model's table name
107
100
  # to get the validations for just this model.
108
101
  def parse_constraint_validations
109
- db.extend(DatabaseMethods)
102
+ db.extension(:_model_constraint_validations)
110
103
 
111
104
  unless hash = Sequel.synchronize{db.constraint_validations}
112
105
  hash = {}
@@ -124,7 +124,7 @@ module Sequel
124
124
  if (type == :update || type == :delete) && !instance_filters.empty?
125
125
  false
126
126
  else
127
- super
127
+ super if defined?(super)
128
128
  end
129
129
  end
130
130
  end