sequel 5.23.0 → 5.24.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dfbb05761d35a09172ba5f1d4d7847ae58f152e50648e1fa63d4b284f1999ad5
4
- data.tar.gz: f7d4b90e74df4096171baba005935648519a0333aff77c5ba840bb634be8a4fd
3
+ metadata.gz: 7ea0327d7fbbc76458fc9dc6a088069f04bd100b37f91288e9db9f37db84bbb7
4
+ data.tar.gz: 6195027a34de796b5109ee2b2ed57407ce9a58f75b1661a3be0ab9b2acbba885
5
5
  SHA512:
6
- metadata.gz: 345e4d6691206c1a353a2d103dc835b1fcaebed9f274717aa7356a3caf0da237aa14f168c659774261d02621934229966f9f75ad901eb663806359cb924df532
7
- data.tar.gz: 0db48e9f07d0dbd598742e85640b8dba965d27bf476e120bf1568e61f7fb19f513e8d9d82b8d69310d1bfde44333553490e14e354a69ce2623943297fe17649f
6
+ metadata.gz: aaa9a71ee0562ccedaeee6144c4764a8024699214dfd4fd7ce46e8a301d8df72f1a709d373f7f5d47c455ff70407b61e1e0bf05ee7f30e20c49a3be51a1bbea2
7
+ data.tar.gz: d8343280ef3c09e930df1db0e12eeac349ce23f396fccf1dfcc055a5e760d5dbb50fd5d0a4f8ce591ac4d12644833b9afea6a94fa1b52991c580baba28c26926
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ === 5.24.0 (2019-09-01)
2
+
3
+ * Add Database#skip_logging? private method designed for extensions to force query timing even if no logger is present (adam12) (#1640)
4
+
5
+ * Allow a hostname specified in a defaults_file in the mysql2 adapter, by not explicitly setting :host (sapio-bdeamer) (#1638)
6
+
7
+ * Convert all database array types to Ruby arrays in the jdbc adapter (jeremyevans)
8
+
9
+ * Add static_cache_cache plugin for caching rows for static_cache models to a file to avoid database queries during model initialization (jeremyevans)
10
+
11
+ * Add :cache_file plugin option to pg_auto_constraint_validations plugin, for caching metadata to a file for faster initialization (jeremyevans)
12
+
13
+ * Support :unique_deferrable and :primary_key_deferrable column options (jeremyevans)
14
+
15
+ * Support :generated_always_as column option on PostgreSQL 12+ (jeremyevans)
16
+
1
17
  === 5.23.0 (2019-08-01)
2
18
 
3
19
  * Work around a bug on jdbc-sqlite3 3.27.2.1 when parsing schema for tables with columns with default values (jeremyevans)
@@ -0,0 +1,56 @@
1
+ = New Features
2
+
3
+ * A :cache_file plugin option has been added to the
4
+ pg_auto_constraint_validations plugin. This option specifies
5
+ a file to use to cache the metadata the plugin uses, so the
6
+ plugin does not need to run 5 queries per model at startup to
7
+ load the metadata. This can dramatically improve startup time
8
+ when using the plugin with a large number of models.
9
+
10
+ To create the metadata file, load the plugin into Sequel::Model
11
+ (or whatever class you are using as the base class for your
12
+ model classes) with the :cache_file option, and after loading
13
+ all of the subclasses of that class, run:
14
+
15
+ Sequel::Model.dump_pg_auto_constraint_validations_cache
16
+
17
+ As when using the schema_caching and index_caching extensions,
18
+ it is up to the user to ensure that the cached metadata matches
19
+ the current database schema. Sequel does no checking of this,
20
+ as checking would take more time, and the point of this plugin
21
+ is to improve startup performance.
22
+
23
+ * A static_cache_cache plugin has been added. This plugin allows
24
+ for caching rows for models using the static_cache plugin. This
25
+ prevents the need to issue a query at model creation time to
26
+ get the rows. This plugin should be loaded into Sequel::Model
27
+ (or whatever class you are using as the base class for your
28
+ model classes) before loading the models using the static_cache
29
+ plugin. To create the metadata file, after all subclasses of
30
+ that class have been loaded, run:
31
+
32
+ Sequel::Model.dump_static_cache_cache
33
+
34
+ * :unique_deferrable and :primary_key_deferrable column
35
+ options are now supported on PostgreSQL 9+ and Oracle. This
36
+ allows you to created deferrable unique and primary key
37
+ column constraints. You could already create deferrable
38
+ table constraints using the :deferrable option to the primary_key
39
+ and unique methods.
40
+
41
+ * A :generated_always_as column option is now supported on
42
+ PostgreSQL 12+, for creating generated columns.
43
+
44
+ * A Database#skip_logging? private method has been added. This
45
+ is designed for use in extensions, to force log timing even
46
+ when no loggers are configured.
47
+
48
+ = Other Improvements
49
+
50
+ * Sequel no longer sets the :host option to localhost by default
51
+ in the mysql2 adapter. This prevents Sequel from overriding
52
+ a host specified in the defaults_file.
53
+
54
+ * All database array types are converted to Ruby arrays in the
55
+ jdbc adapter. Previously, this was only done in the
56
+ jdbc/postgresql subadapter.
@@ -102,12 +102,17 @@ module Sequel
102
102
  v.getSubString(1, v.length)
103
103
  end
104
104
  end
105
+ x = convertors[:RubyArray] = Object.new
106
+ def x.call(r, i)
107
+ if v = r.getArray(i)
108
+ v.array.to_ary
109
+ end
110
+ end
105
111
 
106
112
  MAP = Hash.new(convertors[:Object])
107
113
  types = Java::JavaSQL::Types
108
114
 
109
115
  {
110
- :ARRAY => :Array,
111
116
  :BOOLEAN => :Boolean,
112
117
  :CHAR => :String,
113
118
  :DOUBLE => :Double,
@@ -126,6 +131,7 @@ module Sequel
126
131
  BASIC_MAP = MAP.dup
127
132
 
128
133
  {
134
+ :ARRAY => :Array,
129
135
  :BINARY => :Blob,
130
136
  :BLOB => :Blob,
131
137
  :CLOB => :Clob,
@@ -195,17 +195,7 @@ module Sequel
195
195
 
196
196
  STRING_TYPE = Java::JavaSQL::Types::VARCHAR
197
197
  ARRAY_TYPE = Java::JavaSQL::Types::ARRAY
198
- PG_SPECIFIC_TYPES = [ARRAY_TYPE, Java::JavaSQL::Types::OTHER, Java::JavaSQL::Types::STRUCT, Java::JavaSQL::Types::TIME_WITH_TIMEZONE, Java::JavaSQL::Types::TIME].freeze
199
-
200
- # Return PostgreSQL array types as ruby Arrays instead of
201
- # JDBC PostgreSQL driver-specific array type. Only used if the
202
- # database does not have a conversion proc for the type.
203
- ARRAY_METHOD = Object.new
204
- def ARRAY_METHOD.call(r, i)
205
- if v = r.getArray(i)
206
- v.array.to_ary
207
- end
208
- end
198
+ PG_SPECIFIC_TYPES = [Java::JavaSQL::Types::ARRAY, Java::JavaSQL::Types::OTHER, Java::JavaSQL::Types::STRUCT, Java::JavaSQL::Types::TIME_WITH_TIMEZONE, Java::JavaSQL::Types::TIME].freeze
209
199
 
210
200
  # Return PostgreSQL hstore types as ruby Hashes instead of
211
201
  # Java HashMaps. Only used if the database does not have a
@@ -223,8 +213,6 @@ module Sequel
223
213
  oid = meta.getField(i).getOID
224
214
  if pr = db.oid_convertor_proc(oid)
225
215
  pr
226
- elsif type == ARRAY_TYPE
227
- ARRAY_METHOD
228
216
  elsif oid == 2950 # UUID
229
217
  map[STRING_TYPE]
230
218
  elsif meta.getPGType(i) == 'hstore'
@@ -36,7 +36,6 @@ module Sequel
36
36
  # options such as :local_infile.
37
37
  def connect(server)
38
38
  opts = server_opts(server)
39
- opts[:host] ||= 'localhost'
40
39
  opts[:username] ||= opts.delete(:user)
41
40
  opts[:flags] ||= 0
42
41
  opts[:flags] |= ::Mysql2::Client::FOUND_ROWS if ::Mysql2::Client.const_defined?(:FOUND_ROWS)
@@ -97,13 +97,17 @@ module Sequel
97
97
  # Add an exclusion constraint when creating the table. Elements should be
98
98
  # an array of 2 element arrays, with the first element being the column or
99
99
  # expression the exclusion constraint is applied to, and the second element
100
- # being the operator to use for the column/expression to check for exclusion.
101
- #
102
- # Example:
100
+ # being the operator to use for the column/expression to check for exclusion:
103
101
  #
104
102
  # exclude([[:col1, '&&'], [:col2, '=']])
105
103
  # # EXCLUDE USING gist (col1 WITH &&, col2 WITH =)
106
104
  #
105
+ # To use a custom operator class, you need to use Sequel.lit with the expression
106
+ # and operator class:
107
+ #
108
+ # exclude([[Sequel.lit('col1 inet_ops'), '&&'], [:col2, '=']])
109
+ # # EXCLUDE USING gist (col1 inet_ops WITH &&, col2 WITH =)
110
+ #
107
111
  # Options supported:
108
112
  #
109
113
  # :name :: Name the constraint with the given name (useful if you may
@@ -836,10 +840,14 @@ module Sequel
836
840
  # default value is given.
837
841
  def column_definition_default_sql(sql, column)
838
842
  super
839
- if !column[:serial] && !['serial', 'bigserial'].include?(column[:type].to_s) && !column[:default] && (identity = column[:identity])
840
- sql << " GENERATED "
841
- sql << (identity == :always ? "ALWAYS" : "BY DEFAULT")
842
- sql << " AS IDENTITY"
843
+ if !column[:serial] && !['serial', 'bigserial'].include?(column[:type].to_s) && !column[:default]
844
+ if (identity = column[:identity])
845
+ sql << " GENERATED "
846
+ sql << (identity == :always ? "ALWAYS" : "BY DEFAULT")
847
+ sql << " AS IDENTITY"
848
+ elsif (generated = column[:generated_always_as])
849
+ sql << " GENERATED ALWAYS AS (#{literal(generated)}) STORED"
850
+ end
843
851
  end
844
852
  end
845
853
 
@@ -35,7 +35,7 @@ module Sequel
35
35
  # Yield to the block, logging any errors at error level to all loggers,
36
36
  # and all other queries with the duration at warn or info level.
37
37
  def log_connection_yield(sql, conn, args=nil)
38
- return yield if @loggers.empty?
38
+ return yield if skip_logging?
39
39
  sql = "#{connection_info(conn) if conn && log_connection_info}#{sql}#{"; #{args.inspect}" if args}"
40
40
  timer = Sequel.start_timer
41
41
 
@@ -58,6 +58,12 @@ module Sequel
58
58
 
59
59
  private
60
60
 
61
+ # Determine if logging should be skipped. Defaults to true if no loggers
62
+ # have been specified.
63
+ def skip_logging?
64
+ @loggers.empty?
65
+ end
66
+
61
67
  # String including information about the connection, for use when logging
62
68
  # connection info.
63
69
  def connection_info(conn)
@@ -110,6 +110,9 @@ module Sequel
110
110
  # yet exist on referenced table (but will exist before the transaction commits).
111
111
  # Basically it adds DEFERRABLE INITIALLY DEFERRED on key creation.
112
112
  # If you use :immediate as the value, uses DEFERRABLE INITIALLY IMMEDIATE.
113
+ # :generated_always_as :: Specify a GENERATED ALWAYS AS column expression,
114
+ # if generated columns are supported (PostgreSQL 12+, MariaDB 5.2.0+,
115
+ # and MySQL 5.7.6+).
113
116
  # :index :: Create an index on this column. If given a hash, use the hash as the
114
117
  # options for the index.
115
118
  # :key :: For foreign key columns, the column in the associated table
@@ -126,15 +129,21 @@ module Sequel
126
129
  # be used if you have a single, nonautoincrementing primary key column
127
130
  # (use the primary_key method in that case).
128
131
  # :primary_key_constraint_name :: The name to give the primary key constraint
132
+ # :primary_key_deferrable :: Similar to :deferrable, but for the primary key constraint
133
+ # if :primary_key is used.
129
134
  # :type :: Overrides the type given as the argument. Generally not used by column
130
135
  # itself, but can be passed as an option to other methods that call column.
131
136
  # :unique :: Mark the column as unique, generally has the same effect as
132
137
  # creating a unique index on the column.
133
138
  # :unique_constraint_name :: The name to give the unique key constraint
139
+ # :unique_deferrable :: Similar to :deferrable, but for the unique constraint if :unique
140
+ # is used.
141
+ #
142
+ # PostgreSQL specific options:
143
+ #
144
+ # :identity :: Create an identity column.
134
145
  #
135
146
  # MySQL specific options:
136
- # :generated_always_as :: Specify a GENERATED ALWAYS AS column expression,
137
- # if generated columns are supported.
138
147
  # :generated_type :: Set the type of column when using :generated_always_as,
139
148
  # should be :virtual or :stored to force a type.
140
149
  def column(name, type, opts = OPTS)
@@ -586,6 +586,7 @@ module Sequel
586
586
  sql << " CONSTRAINT #{quote_identifier(name)}"
587
587
  end
588
588
  sql << ' PRIMARY KEY'
589
+ constraint_deferrable_sql_append(sql, column[:primary_key_deferrable])
589
590
  end
590
591
  end
591
592
 
@@ -606,6 +607,7 @@ module Sequel
606
607
  sql << " CONSTRAINT #{quote_identifier(name)}"
607
608
  end
608
609
  sql << ' UNIQUE'
610
+ constraint_deferrable_sql_append(sql, column[:unique_deferrable])
609
611
  end
610
612
  end
611
613
 
@@ -45,6 +45,31 @@ module Sequel
45
45
  # to be associated to particular column(s), and use a specific error message:
46
46
  #
47
47
  # Album.pg_auto_constraint_validation_override(:constraint_name, [:column1], "validation error message")
48
+ #
49
+ # Using the pg_auto_constraint_validations plugin requires 5 queries per
50
+ # model at load time in order to gather the necessary metadata. For applications
51
+ # with a large number of models, this can result in a noticeable delay during model
52
+ # initialization. To mitigate this issue, you can cache the necessary metadata in
53
+ # a file with the :cache_file option:
54
+ #
55
+ # Sequel::Model.plugin :pg_auto_constraint_validations, cache_file: 'db/pgacv.cache'
56
+ #
57
+ # The file does not have to exist when loading the plugin. If it exists, the plugin
58
+ # will load the cache and use the cached results instead of issuing queries if there
59
+ # is an entry in the cache. If there is no entry in the cache, it will update the
60
+ # in-memory cache with the metadata results. To save the in in-memory cache back to
61
+ # the cache file, run:
62
+ #
63
+ # Sequel::Model.dump_pg_auto_constraint_validations_cache
64
+ #
65
+ # Note that when using the :cache_file option, it is up to the application to ensure
66
+ # that the dumped cached metadata reflects the current state of the database. Sequel
67
+ # does no checking to ensure this, as checking would take time and the
68
+ # purpose of this code is to take a shortcut.
69
+ #
70
+ # The cached schema is dumped in Marshal format, since it is the fastest
71
+ # and it handles all ruby objects used in the metadata. Because of this,
72
+ # you should not attempt to load the metadata from a untrusted file.
48
73
  #
49
74
  # Usage:
50
75
  #
@@ -67,13 +92,28 @@ module Sequel
67
92
  }.freeze).each_value(&:freeze)
68
93
 
69
94
  # Setup the constraint violation metadata. Options:
95
+ # :cache_file :: File storing cached metadata, to avoid queries for each model
70
96
  # :messages :: Override the default error messages for each constraint
71
97
  # violation type (:not_null, :check, :unique, :foreign_key, :referenced_by)
72
98
  def self.configure(model, opts=OPTS)
73
99
  model.instance_exec do
100
+ if @pg_auto_constraint_validations_cache_file = opts[:cache_file]
101
+ @pg_auto_constraint_validations_cache = if ::File.file?(@pg_auto_constraint_validations_cache_file)
102
+ cache = Marshal.load(File.read(@pg_auto_constraint_validations_cache_file))
103
+ cache.each_value do |hash|
104
+ hash.freeze.each_value(&:freeze)
105
+ end
106
+ else
107
+ {}
108
+ end
109
+ else
110
+ @pg_auto_constraint_validations_cache = nil
111
+ end
112
+
74
113
  setup_pg_auto_constraint_validations
75
114
  @pg_auto_constraint_validations_messages = (@pg_auto_constraint_validations_messages || DEFAULT_ERROR_MESSAGES).merge(opts[:messages] || OPTS).freeze
76
115
  end
116
+ nil
77
117
  end
78
118
 
79
119
  module ClassMethods
@@ -85,9 +125,16 @@ module Sequel
85
125
  # generated validation failures.
86
126
  attr_reader :pg_auto_constraint_validations_messages
87
127
 
88
- Plugins.inherited_instance_variables(self, :@pg_auto_constraint_validations=>nil, :@pg_auto_constraint_validations_messages=>nil)
128
+ Plugins.inherited_instance_variables(self, :@pg_auto_constraint_validations=>nil, :@pg_auto_constraint_validations_messages=>nil, :@pg_auto_constraint_validations_cache=>nil, :@pg_auto_constraint_validations_cache_file=>nil)
89
129
  Plugins.after_set_dataset(self, :setup_pg_auto_constraint_validations)
90
130
 
131
+ # Dump the in-memory cached metadata to the cache file.
132
+ def dump_pg_auto_constraint_validations_cache
133
+ raise Error, "No pg_auto_constraint_validations setup" unless file = @pg_auto_constraint_validations_cache_file
134
+ File.open(file, 'wb'){|f| f.write(Marshal.dump(@pg_auto_constraint_validations_cache))}
135
+ nil
136
+ end
137
+
91
138
  # Override the constraint validation columns and message for a given constraint
92
139
  def pg_auto_constraint_validation_override(constraint, columns, message)
93
140
  pgacv = Hash[@pg_auto_constraint_validations]
@@ -122,39 +169,51 @@ module Sequel
122
169
  return
123
170
  end
124
171
 
125
- checks = {}
126
- indexes = {}
127
- foreign_keys = {}
128
- referenced_by = {}
172
+ cache = @pg_auto_constraint_validations_cache
173
+ literal_table_name = dataset.literal(table_name)
174
+ unless cache && (metadata = cache[literal_table_name])
175
+ checks = {}
176
+ indexes = {}
177
+ foreign_keys = {}
178
+ referenced_by = {}
129
179
 
130
- db.check_constraints(table_name).each do |k, v|
131
- checks[k] = v[:columns].dup.freeze unless v[:columns].empty?
132
- end
133
- db.indexes(table_name, :include_partial=>true).each do |k, v|
134
- if v[:unique]
135
- indexes[k] = v[:columns].dup.freeze
180
+ db.check_constraints(table_name).each do |k, v|
181
+ checks[k] = v[:columns].dup.freeze unless v[:columns].empty?
182
+ end
183
+ db.indexes(table_name, :include_partial=>true).each do |k, v|
184
+ if v[:unique]
185
+ indexes[k] = v[:columns].dup.freeze
186
+ end
187
+ end
188
+ db.foreign_key_list(table_name, :schema=>false).each do |fk|
189
+ foreign_keys[fk[:name]] = fk[:columns].dup.freeze
190
+ end
191
+ db.foreign_key_list(table_name, :reverse=>true, :schema=>false).each do |fk|
192
+ referenced_by[[fk[:schema], fk[:table], fk[:name]].freeze] = fk[:key].dup.freeze
193
+ end
194
+
195
+ schema, table = db[:pg_class].
196
+ join(:pg_namespace, :oid=>:relnamespace, db.send(:regclass_oid, table_name)=>:oid).
197
+ get([:nspname, :relname])
198
+
199
+ metadata = {
200
+ :schema=>schema,
201
+ :table=>table,
202
+ :check=>checks,
203
+ :unique=>indexes,
204
+ :foreign_key=>foreign_keys,
205
+ :referenced_by=>referenced_by,
206
+ :overrides=>OPTS
207
+ }.freeze
208
+ metadata.each_value(&:freeze)
209
+
210
+ if cache
211
+ cache[literal_table_name] = metadata
136
212
  end
137
- end
138
- db.foreign_key_list(table_name, :schema=>false).each do |fk|
139
- foreign_keys[fk[:name]] = fk[:columns].dup.freeze
140
- end
141
- db.foreign_key_list(table_name, :reverse=>true, :schema=>false).each do |fk|
142
- referenced_by[[fk[:schema], fk[:table], fk[:name]].freeze] = fk[:key].dup.freeze
143
213
  end
144
214
 
145
- schema, table = db[:pg_class].
146
- join(:pg_namespace, :oid=>:relnamespace, db.send(:regclass_oid, table_name)=>:oid).
147
- get([:nspname, :relname])
148
-
149
- (@pg_auto_constraint_validations = {
150
- :schema=>schema,
151
- :table=>table,
152
- :check=>checks,
153
- :unique=>indexes,
154
- :foreign_key=>foreign_keys,
155
- :referenced_by=>referenced_by,
156
- :overrides=>OPTS
157
- }.freeze).each_value(&:freeze)
215
+ @pg_auto_constraint_validations = metadata
216
+ nil
158
217
  end
159
218
  end
160
219
 
@@ -211,18 +211,23 @@ module Sequel
211
211
  # Reload the cache for this model by retrieving all of the instances in the dataset
212
212
  # freezing them, and populating the cached array and hash.
213
213
  def load_cache
214
- a = dataset.all
214
+ @all = load_static_cache_rows
215
215
  h = {}
216
- a.each do |o|
216
+ @all.each do |o|
217
217
  o.errors.freeze
218
218
  h[o.pk.freeze] = o.freeze
219
219
  end
220
- @all = a.freeze
221
220
  @cache = h.freeze
222
221
  end
223
222
 
224
223
  private
225
224
 
225
+ # Load the static cache rows from the database.
226
+ def load_static_cache_rows
227
+ ret = super if defined?(super)
228
+ ret || dataset.all.freeze
229
+ end
230
+
226
231
  # Return the frozen object with the given pk, or nil if no such object exists
227
232
  # in the cache, without issuing a database query.
228
233
  def primary_key_lookup(pk)
@@ -0,0 +1,53 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The static_cache_cache plugin allows for caching the row content for subclasses
6
+ # that use the static cache plugin (or just the current class). Using this plugin
7
+ # can avoid the need to query the database every time loading the plugin into a
8
+ # model, which can save time when you have a lot of models using the static_cache
9
+ # plugin.
10
+ #
11
+ # Usage:
12
+ #
13
+ # # Make all model subclasses that use the static_cache plugin use
14
+ # # the cached values in the given file
15
+ # Sequel::Model.plugin :static_cache_cache, "static_cache.cache"
16
+ #
17
+ # # Make the AlbumType model the cached values in the given file,
18
+ # # should be loaded before the static_cache plugin
19
+ # AlbumType.plugin :static_cache_cache, "static_cache.cache"
20
+ module StaticCacheCache
21
+ def self.configure(model, file)
22
+ model.instance_variable_set(:@static_cache_cache_file, file)
23
+ model.instance_variable_set(:@static_cache_cache, File.exist?(file) ? Marshal.load(File.read(file)) : {})
24
+ end
25
+
26
+ module ClassMethods
27
+ # Dump the in-memory cached rows to the cache file.
28
+ def dump_static_cache_cache
29
+ File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(@static_cache_cache))}
30
+ nil
31
+ end
32
+
33
+ Plugins.inherited_instance_variables(self, :@static_cache_cache_file=>nil, :@static_cache_cache=>nil)
34
+
35
+ private
36
+
37
+ # Load the rows for the model from the cache if available.
38
+ # If not available, load the rows from the database, and
39
+ # then update the cache with the raw rows.
40
+ def load_static_cache_rows
41
+ if rows = Sequel.synchronize{@static_cache_cache[name]}
42
+ rows.map{|row| call(row)}.freeze
43
+ else
44
+ rows = dataset.all.freeze
45
+ raw_rows = rows.map(&:values)
46
+ Sequel.synchronize{@static_cache_cache[name] = raw_rows}
47
+ rows
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 23
9
+ MINOR = 24
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
@@ -227,6 +227,24 @@ describe "PostgreSQL", '#create_table' do
227
227
  @db.convert_serial_to_identity(:tmp_dolls, :column=>:id)
228
228
  end if DB.server_version >= 100002 && DB.get{current_setting('is_superuser')} == 'on'
229
229
 
230
+ it "should support creating generated columns" do
231
+ @db.create_table(:tmp_dolls){Integer :a; Integer :b; Integer :c, :generated_always_as=>Sequel[:a] * 2 + :b + 1}
232
+ @db[:tmp_dolls].insert(:a=>100, :b=>10)
233
+ @db[:tmp_dolls].select_order_map([:a, :b, :c]).must_equal [[100, 10, 211]]
234
+ end if DB.server_version >= 120000
235
+
236
+ it "should support deferred primary key and unique constraints on columns" do
237
+ @db.create_table(:tmp_dolls){primary_key :id, :primary_key_deferrable=>true; Integer :i, :unique=>true, :unique_deferrable=>true}
238
+ @db[:tmp_dolls].insert(:i=>10)
239
+ DB.transaction do
240
+ @db[:tmp_dolls].insert(:id=>1, :i=>1)
241
+ @db[:tmp_dolls].insert(:id=>10, :i=>10)
242
+ @db[:tmp_dolls].where(:i=>1).update(:id=>2)
243
+ @db[:tmp_dolls].where(:id=>10).update(:i=>2)
244
+ end
245
+ @db[:tmp_dolls].select_order_map([:id, :i]).must_equal [[1, 10], [2, 1], [10, 2]]
246
+ end if DB.server_version >= 90000
247
+
230
248
  it "should support pg_loose_count extension" do
231
249
  @db.extension :pg_loose_count
232
250
  @db.create_table(:tmp_dolls){text :name}
@@ -4345,4 +4363,49 @@ describe "pg_auto_constraint_validations plugin" do
4345
4363
  proc{o.save}.must_raise Sequel::ValidationFailed
4346
4364
  o.errors.must_equal(:i=>['is invalid'], :id=>['is invalid'])
4347
4365
  end
4366
+
4367
+ it "should handle dumping cached metadata and loading metadata from cache" do
4368
+ cache_file = "spec/files/pgacv-#{$$}.cache"
4369
+ begin
4370
+ c = Class.new(Sequel::Model)
4371
+ c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
4372
+ c1 = Class.new(c)
4373
+ def c1.name; 'Foo' end
4374
+ c1.dataset = DB[:test1]
4375
+ c2 = Class.new(c)
4376
+ def c2.name; 'Bar' end
4377
+ c2.dataset = DB[:test2]
4378
+ c1.unrestrict_primary_key
4379
+ c2.unrestrict_primary_key
4380
+
4381
+ o = c1.new(:id=>5, :i=>12)
4382
+ proc{o.save}.must_raise Sequel::ValidationFailed
4383
+ o.errors.must_equal(:i=>['is invalid'])
4384
+ o = c2.new(:test2_id=>4, :test1_id=>2)
4385
+ proc{o.save}.must_raise Sequel::ValidationFailed
4386
+ o.errors.must_equal(:test1_id=>['is invalid'])
4387
+
4388
+ c.dump_pg_auto_constraint_validations_cache
4389
+
4390
+ c = Class.new(Sequel::Model)
4391
+ c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
4392
+ c1 = Class.new(c)
4393
+ def c1.name; 'Foo' end
4394
+ c1.dataset = DB[:test1]
4395
+ c2 = Class.new(c)
4396
+ def c2.name; 'Bar' end
4397
+ c2.dataset = DB[:test2]
4398
+ c1.unrestrict_primary_key
4399
+ c2.unrestrict_primary_key
4400
+
4401
+ o = c1.new(:id=>5, :i=>12)
4402
+ proc{o.save}.must_raise Sequel::ValidationFailed
4403
+ o.errors.must_equal(:i=>['is invalid'])
4404
+ o = c2.new(:test2_id=>4, :test1_id=>2)
4405
+ proc{o.save}.must_raise Sequel::ValidationFailed
4406
+ o.errors.must_equal(:test1_id=>['is invalid'])
4407
+ ensure
4408
+ File.delete(cache_file) if File.file?(cache_file)
4409
+ end
4410
+ end
4348
4411
  end if DB.respond_to?(:error_info)
@@ -264,6 +264,25 @@ describe "Database#log_connection_yield" do
264
264
  @o.logs.first.first.must_equal :info
265
265
  @o.logs.first.last.must_match(/\A\(\d\.\d{6}s\) blah; \[1, 2\]\z/)
266
266
  end
267
+
268
+ it "should log without a logger defined by forcing skip_logging? to return false" do
269
+ @db.logger = nil
270
+ @db.extend(Module.new do
271
+ def skip_logging?
272
+ false
273
+ end
274
+
275
+ def log_duration(*)
276
+ self.did_log = true
277
+ end
278
+
279
+ attr_accessor :did_log
280
+ end)
281
+
282
+ @db.log_connection_yield('some sql', @conn) {}
283
+
284
+ @db.did_log.must_equal true
285
+ end
267
286
  end
268
287
 
269
288
  describe "Database#uri" do
@@ -240,6 +240,24 @@ describe "DB#create_table" do
240
240
  @db.sqls.must_equal ["CREATE TABLE cats (id integer, name text, UNIQUE (name) DEFERRABLE INITIALLY IMMEDIATE)"]
241
241
  end
242
242
 
243
+ it "should handle deferred unique column constraints" do
244
+ @db.create_table(:cats) do
245
+ integer :id, :unique=>true, :unique_deferrable=>true
246
+ integer :i, :unique=>true, :unique_deferrable=>:immediate
247
+ integer :j, :unique=>true, :unique_deferrable=>false
248
+ end
249
+ @db.sqls.must_equal ["CREATE TABLE cats (id integer UNIQUE DEFERRABLE INITIALLY DEFERRED, i integer UNIQUE DEFERRABLE INITIALLY IMMEDIATE, j integer UNIQUE NOT DEFERRABLE)"]
250
+ end
251
+
252
+ it "should handle deferred primary key column constraints" do
253
+ @db.create_table(:cats) do
254
+ integer :id, :primary_key=>true, :primary_key_deferrable=>true
255
+ integer :i, :primary_key=>true, :primary_key_deferrable=>:immediate
256
+ integer :j, :primary_key=>true, :primary_key_deferrable=>false
257
+ end
258
+ @db.sqls.must_equal ["CREATE TABLE cats (id integer PRIMARY KEY DEFERRABLE INITIALLY DEFERRED, i integer PRIMARY KEY DEFERRABLE INITIALLY IMMEDIATE, j integer PRIMARY KEY NOT DEFERRABLE)"]
259
+ end
260
+
243
261
  it "should accept unsigned definition" do
244
262
  @db.create_table(:cats) do
245
263
  integer :value, :unsigned => true
@@ -74,4 +74,30 @@ describe "insert_conflict plugin" do
74
74
  model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT (s) DO UPDATE SET o = excluded.o",
75
75
  "SELECT * FROM t WHERE (id = 2) LIMIT 1"]
76
76
  end
77
+
78
+ it "should work if the prepared_statements plugin is loaded before" do
79
+ db = Sequel.mock(:host=>'sqlite', :fetch=>{:id=>1, :s=>2}, :autoid=>1, :numrows=>1)
80
+ db.extend_datasets{def quote_identifiers?; false end}
81
+ model = Class.new(Sequel::Model)
82
+ model.dataset = db[:t]
83
+ model.columns :id, :s
84
+ model.plugin :prepared_statements
85
+ model.plugin :insert_conflict
86
+ db.sqls
87
+ model.create(:s=>'a').update(:s=>'b')
88
+ db.sqls.must_equal ["INSERT INTO t (s) VALUES ('a')", "SELECT * FROM t WHERE (id = 1) LIMIT 1", "UPDATE t SET s = 'b' WHERE (id = 1)"]
89
+ end
90
+
91
+ it "should work if the prepared_statements plugin is loaded after" do
92
+ db = Sequel.mock(:host=>'postgres', :fetch=>{:id=>1, :s=>2}, :autoid=>1, :numrows=>1)
93
+ db.extend_datasets{def quote_identifiers?; false end}
94
+ model = Class.new(Sequel::Model)
95
+ model.dataset = db[:t]
96
+ model.columns :id, :s
97
+ model.plugin :insert_conflict
98
+ model.plugin :prepared_statements
99
+ db.sqls
100
+ model.create(:s=>'a').update(:s=>'b')
101
+ db.sqls.must_equal ["INSERT INTO t (s) VALUES ('a') RETURNING *", "UPDATE t SET s = 'b' WHERE (id = 1)"]
102
+ end
77
103
  end
@@ -169,4 +169,41 @@ describe "pg_auto_constraint_validations plugin" do
169
169
  proc{o.update(:i=>12)}.must_raise Sequel::ValidationFailed
170
170
  o.errors.must_equal(:i=>['foo bar'])
171
171
  end
172
+
173
+ it "should handle dumping cached metadata and loading metadata from cache" do
174
+ cache_file = "spec/files/pgacv-spec-#{$$}.cache"
175
+ begin
176
+ @ds = @db[:items]
177
+ @ds.send(:columns=, [:id, :i])
178
+ @db.fetch = @metadata_results.dup
179
+ c = Sequel::Model(@ds)
180
+ def c.name; 'Foo' end
181
+ @db.sqls
182
+ c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
183
+ @db.sqls.length.must_equal 5
184
+
185
+ o = c.new(:i=>12)
186
+ @set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_id_check']
187
+ proc{o.save}.must_raise Sequel::ValidationFailed
188
+
189
+ c.dump_pg_auto_constraint_validations_cache
190
+
191
+ @db.fetch = []
192
+ c = Sequel::Model(@ds)
193
+ def c.name; 'Foo' end
194
+ @db.sqls
195
+ c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
196
+ @db.sqls.must_be_empty
197
+
198
+ o = c.new(:i=>12)
199
+ @set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_id_check']
200
+ proc{o.save}.must_raise Sequel::ValidationFailed
201
+ ensure
202
+ File.delete(cache_file) if File.file?(cache_file)
203
+ end
204
+ end
205
+
206
+ it "should raise error if attempting to dump cached metadata when not using caching" do
207
+ proc{@c.dump_pg_auto_constraint_validations_cache}.must_raise Sequel::Error
208
+ end
172
209
  end
@@ -0,0 +1,35 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe "static_cache_cache plugin" do
4
+ before do
5
+ @db = Sequel.mock
6
+ @db.fetch = [{:id=>1, :name=>'A'}, {:id=>2, :name=>'B'}]
7
+ @c = Class.new(Sequel::Model(@db[:t]))
8
+ def @c.name; 'Foo' end
9
+ @c.columns :id, :name
10
+ @file = "spec/files/static_cache_cache-spec-#{$$}.cache"
11
+ end
12
+ after do
13
+ File.delete(@file) if File.file?(@file)
14
+ end
15
+
16
+ it "should allow dumping and loading static cache rows from a cache file" do
17
+ @c.plugin :static_cache_cache, @file
18
+ @db.sqls
19
+ @c.plugin :static_cache
20
+ @db.sqls.must_equal ['SELECT * FROM t']
21
+ @c.all.must_equal [@c.load(:id=>1, :name=>'A'), @c.load(:id=>2, :name=>'B')]
22
+
23
+ @c.dump_static_cache_cache
24
+
25
+ @db.fetch = []
26
+ c = Class.new(Sequel::Model(@db[:t]))
27
+ def c.name; 'Foo' end
28
+ c.columns :id, :name
29
+ @c.plugin :static_cache_cache, @file
30
+ @db.sqls
31
+ @c.plugin :static_cache
32
+ @db.sqls.must_be_empty
33
+ @c.all.must_equal [@c.load(:id=>1, :name=>'A'), @c.load(:id=>2, :name=>'B')]
34
+ end
35
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.23.0
4
+ version: 5.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-01 00:00:00.000000000 Z
11
+ date: 2019-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -207,6 +207,7 @@ extra_rdoc_files:
207
207
  - doc/release_notes/5.21.0.txt
208
208
  - doc/release_notes/5.22.0.txt
209
209
  - doc/release_notes/5.23.0.txt
210
+ - doc/release_notes/5.24.0.txt
210
211
  files:
211
212
  - CHANGELOG
212
213
  - MIT-LICENSE
@@ -301,6 +302,7 @@ files:
301
302
  - doc/release_notes/5.21.0.txt
302
303
  - doc/release_notes/5.22.0.txt
303
304
  - doc/release_notes/5.23.0.txt
305
+ - doc/release_notes/5.24.0.txt
304
306
  - doc/release_notes/5.3.0.txt
305
307
  - doc/release_notes/5.4.0.txt
306
308
  - doc/release_notes/5.5.0.txt
@@ -542,6 +544,7 @@ files:
542
544
  - lib/sequel/plugins/skip_create_refresh.rb
543
545
  - lib/sequel/plugins/split_values.rb
544
546
  - lib/sequel/plugins/static_cache.rb
547
+ - lib/sequel/plugins/static_cache_cache.rb
545
548
  - lib/sequel/plugins/string_stripper.rb
546
549
  - lib/sequel/plugins/subclasses.rb
547
550
  - lib/sequel/plugins/subset_conditions.rb
@@ -712,6 +715,7 @@ files:
712
715
  - spec/extensions/split_values_spec.rb
713
716
  - spec/extensions/sql_comments_spec.rb
714
717
  - spec/extensions/sql_expr_spec.rb
718
+ - spec/extensions/static_cache_cache_spec.rb
715
719
  - spec/extensions/static_cache_spec.rb
716
720
  - spec/extensions/string_agg_spec.rb
717
721
  - spec/extensions/string_date_time_spec.rb