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