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 +4 -4
- data/CHANGELOG +16 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/lib/sequel/adapters/jdbc.rb +7 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
- data/lib/sequel/adapters/mysql2.rb +0 -1
- data/lib/sequel/adapters/shared/postgres.rb +15 -7
- data/lib/sequel/database/logging.rb +7 -1
- data/lib/sequel/database/schema_generator.rb +11 -2
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
- data/lib/sequel/plugins/static_cache.rb +8 -3
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +63 -0
- data/spec/core/database_spec.rb +19 -0
- data/spec/core/schema_spec.rb +18 -0
- data/spec/extensions/insert_conflict_spec.rb +26 -0
- data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
- data/spec/extensions/static_cache_cache_spec.rb +35 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ea0327d7fbbc76458fc9dc6a088069f04bd100b37f91288e9db9f37db84bbb7
|
4
|
+
data.tar.gz: 6195027a34de796b5109ee2b2ed57407ce9a58f75b1661a3be0ab9b2acbba885
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
data/lib/sequel/adapters/jdbc.rb
CHANGED
@@ -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 = [
|
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]
|
840
|
-
|
841
|
-
|
842
|
-
|
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
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
-
|
146
|
-
|
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
|
-
|
214
|
+
@all = load_static_cache_rows
|
215
215
|
h = {}
|
216
|
-
|
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
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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)
|
data/spec/core/database_spec.rb
CHANGED
@@ -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
|
data/spec/core/schema_spec.rb
CHANGED
@@ -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.
|
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-
|
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
|