sequel 4.42.1 → 4.43.0

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