sequel 5.87.0 → 5.89.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/lib/sequel/adapters/ibmdb.rb +1 -0
- data/lib/sequel/adapters/mysql2.rb +8 -1
- data/lib/sequel/adapters/shared/access.rb +1 -0
- data/lib/sequel/adapters/shared/mssql.rb +1 -0
- data/lib/sequel/adapters/shared/oracle.rb +1 -0
- data/lib/sequel/adapters/shared/postgres.rb +34 -4
- data/lib/sequel/core.rb +15 -0
- data/lib/sequel/database/dataset_defaults.rb +3 -3
- data/lib/sequel/database/misc.rb +5 -1
- data/lib/sequel/database/schema_generator.rb +8 -0
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +1 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -1
- data/lib/sequel/dataset/query.rb +2 -2
- data/lib/sequel/dataset/sql.rb +6 -1
- data/lib/sequel/extensions/connection_validator.rb +15 -10
- data/lib/sequel/extensions/migration.rb +19 -3
- data/lib/sequel/extensions/null_dataset.rb +2 -2
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/extensions/query_blocker.rb +172 -0
- data/lib/sequel/extensions/string_agg.rb +2 -2
- data/lib/sequel/extensions/virtual_row_method_block.rb +1 -0
- data/lib/sequel/model/base.rb +24 -11
- data/lib/sequel/plugins/composition.rb +1 -1
- data/lib/sequel/plugins/enum.rb +1 -1
- data/lib/sequel/plugins/inverted_subsets.rb +1 -0
- data/lib/sequel/plugins/lazy_attributes.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +1 -1
- data/lib/sequel/plugins/serialization.rb +11 -5
- data/lib/sequel/plugins/sql_comments.rb +1 -1
- data/lib/sequel/plugins/static_cache_cache.rb +50 -13
- data/lib/sequel/plugins/subset_conditions.rb +1 -0
- data/lib/sequel/plugins/subset_static_cache.rb +262 -0
- data/lib/sequel/sql.rb +1 -0
- data/lib/sequel/version.rb +1 -1
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8248e272e580f0e89e508e2aa97ebea83ac083b3f1bd347fc9d34700fd98ae42
|
4
|
+
data.tar.gz: a74658ae42f7e087a541056d5f0cac2db4cc67b11ca56367b2bea309e502df49
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2875a0f53a46efa87b3380b3fe2c1bbae46542076e3e5615ddcd53b55ac6dfefa53fc828ec4f1277df85a7852eb0e2934377d074e58a6410837138ed2e2a7db
|
7
|
+
data.tar.gz: 8c1a3591085be13aef51f3bc63f0407249858c57e91c3f314b92d929d362c4aafd0b801ee52ca0f96acb18f6fa133d33c4532b5a0fc13fc61e599ba3e05f4e6c
|
@@ -97,7 +97,14 @@ module Sequel
|
|
97
97
|
synchronize(opts[:server]) do |conn|
|
98
98
|
stmt, ps_sql = conn.prepared_statements[ps_name]
|
99
99
|
unless ps_sql == sql
|
100
|
-
|
100
|
+
if stmt
|
101
|
+
begin
|
102
|
+
stmt.close
|
103
|
+
rescue ::Mysql2::Error
|
104
|
+
# probably Invalid statement handle, can happen from dropping
|
105
|
+
# related table, ignore as we won't be using it again.
|
106
|
+
end
|
107
|
+
end
|
101
108
|
stmt = log_connection_yield("Preparing #{ps_name}: #{sql}", conn){conn.prepare(sql)}
|
102
109
|
conn.prepared_statements[ps_name] = [stmt, sql]
|
103
110
|
end
|
@@ -88,6 +88,7 @@ module Sequel
|
|
88
88
|
|
89
89
|
module DatasetMethods
|
90
90
|
include(Module.new do
|
91
|
+
Sequel.set_temp_name(self){"Sequel::Access::DatasetMethods::_SQLMethods"}
|
91
92
|
Dataset.def_sql_method(self, :select, %w'select distinct limit columns into from join where group order having compounds')
|
92
93
|
end)
|
93
94
|
include EmulateOffsetWithReverseAndCount
|
@@ -574,6 +574,7 @@ module Sequel
|
|
574
574
|
|
575
575
|
module DatasetMethods
|
576
576
|
include(Module.new do
|
577
|
+
Sequel.set_temp_name(self){"Sequel::MSSQL::DatasetMethods::_SQLMethods"}
|
577
578
|
Dataset.def_sql_method(self, :select, %w'with select distinct limit columns into from lock join where group having compounds order')
|
578
579
|
end)
|
579
580
|
include EmulateOffsetWithRowNumber
|
@@ -333,6 +333,7 @@ module Sequel
|
|
333
333
|
BITAND_PROC = lambda{|a, b| Sequel.lit(["CAST(BITAND(", ", ", ") AS INTEGER)"], a, b)}
|
334
334
|
|
335
335
|
include(Module.new do
|
336
|
+
Sequel.set_temp_name(self){"Sequel::Oracle::DatasetMethods::_SQLMethods"}
|
336
337
|
Dataset.def_sql_method(self, :select, %w'with select distinct columns from join where group having compounds order limit lock')
|
337
338
|
end)
|
338
339
|
|
@@ -1271,21 +1271,32 @@ module Sequel
|
|
1271
1271
|
|
1272
1272
|
# Handle exclusion constraints.
|
1273
1273
|
def constraint_definition_sql(constraint)
|
1274
|
-
case constraint[:type]
|
1274
|
+
case type = constraint[:type]
|
1275
1275
|
when :exclude
|
1276
1276
|
elements = constraint[:elements].map{|c, op| "#{literal(c)} WITH #{op}"}.join(', ')
|
1277
1277
|
sql = String.new
|
1278
1278
|
sql << "#{"CONSTRAINT #{quote_identifier(constraint[:name])} " if constraint[:name]}EXCLUDE USING #{constraint[:using]||'gist'} (#{elements})#{" WHERE #{filter_expr(constraint[:where])}" if constraint[:where]}"
|
1279
1279
|
constraint_deferrable_sql_append(sql, constraint[:deferrable])
|
1280
1280
|
sql
|
1281
|
-
when :
|
1281
|
+
when :primary_key, :unique
|
1282
|
+
if using_index = constraint[:using_index]
|
1283
|
+
sql = String.new
|
1284
|
+
sql << "CONSTRAINT #{quote_identifier(constraint[:name])} " if constraint[:name]
|
1285
|
+
if type == :primary_key
|
1286
|
+
sql << primary_key_constraint_sql_fragment(constraint)
|
1287
|
+
else
|
1288
|
+
sql << unique_constraint_sql_fragment(constraint)
|
1289
|
+
end
|
1290
|
+
sql << " USING INDEX " << quote_identifier(using_index)
|
1291
|
+
else
|
1292
|
+
super
|
1293
|
+
end
|
1294
|
+
else # when :foreign_key, :check
|
1282
1295
|
sql = super
|
1283
1296
|
if constraint[:not_valid]
|
1284
1297
|
sql << " NOT VALID"
|
1285
1298
|
end
|
1286
1299
|
sql
|
1287
|
-
else
|
1288
|
-
super
|
1289
1300
|
end
|
1290
1301
|
end
|
1291
1302
|
|
@@ -2388,6 +2399,25 @@ module Sequel
|
|
2388
2399
|
join_from_sql(:USING, sql)
|
2389
2400
|
end
|
2390
2401
|
|
2402
|
+
# Handle column aliases containing data types, useful for selecting from functions
|
2403
|
+
# that return the record data type.
|
2404
|
+
def derived_column_list_sql_append(sql, column_aliases)
|
2405
|
+
c = false
|
2406
|
+
comma = ', '
|
2407
|
+
column_aliases.each do |a|
|
2408
|
+
sql << comma if c
|
2409
|
+
if a.is_a?(Array)
|
2410
|
+
raise Error, "column aliases specified as arrays must have only 2 elements, the first is alias name and the second is data type" unless a.length == 2
|
2411
|
+
a, type = a
|
2412
|
+
identifier_append(sql, a)
|
2413
|
+
sql << " " << db.cast_type_literal(type).to_s
|
2414
|
+
else
|
2415
|
+
identifier_append(sql, a)
|
2416
|
+
end
|
2417
|
+
c ||= true
|
2418
|
+
end
|
2419
|
+
end
|
2420
|
+
|
2391
2421
|
# Add ON CONFLICT clause if it should be used
|
2392
2422
|
def insert_conflict_sql(sql)
|
2393
2423
|
if opts = @opts[:insert_conflict]
|
data/lib/sequel/core.rb
CHANGED
@@ -164,6 +164,21 @@ module Sequel
|
|
164
164
|
JSON::ParserError
|
165
165
|
end
|
166
166
|
|
167
|
+
if RUBY_VERSION >= '3.3'
|
168
|
+
# Create a new module using the block, and set the temporary name
|
169
|
+
# on it using the given a containing module and name.
|
170
|
+
def set_temp_name(mod)
|
171
|
+
mod.set_temporary_name(yield)
|
172
|
+
mod
|
173
|
+
end
|
174
|
+
# :nocov:
|
175
|
+
else
|
176
|
+
def set_temp_name(mod)
|
177
|
+
mod
|
178
|
+
end
|
179
|
+
end
|
180
|
+
# :nocov:
|
181
|
+
|
167
182
|
# Convert given object to json and return the result.
|
168
183
|
# This can be overridden to use an alternative json implementation.
|
169
184
|
def object_to_json(obj, *args, &block)
|
@@ -17,7 +17,7 @@ module Sequel
|
|
17
17
|
# as the dataset class.
|
18
18
|
def dataset_class=(c)
|
19
19
|
unless @dataset_modules.empty?
|
20
|
-
c = Class.new(c)
|
20
|
+
c = Sequel.set_temp_name(Class.new(c)){"Sequel::Dataset::_Subclass"}
|
21
21
|
@dataset_modules.each{|m| c.send(:include, m)}
|
22
22
|
end
|
23
23
|
@dataset_class = c
|
@@ -61,10 +61,10 @@ module Sequel
|
|
61
61
|
# # SELECT id, name FROM table WHERE active ORDER BY id
|
62
62
|
def extend_datasets(mod=nil, &block)
|
63
63
|
raise(Error, "must provide either mod or block, not both") if mod && block
|
64
|
-
mod = Dataset::DatasetModule.new(&block) if block
|
64
|
+
mod = Sequel.set_temp_name(Dataset::DatasetModule.new(&block)){"Sequel::Dataset::_DatasetModule(#{block.source_location.join(':')})"} if block
|
65
65
|
if @dataset_modules.empty?
|
66
66
|
@dataset_modules = [mod]
|
67
|
-
@dataset_class = Class.new(@dataset_class)
|
67
|
+
@dataset_class = Sequel.set_temp_name(Class.new(@dataset_class)){"Sequel::Dataset::_Subclass"}
|
68
68
|
else
|
69
69
|
@dataset_modules << mod
|
70
70
|
end
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -116,6 +116,10 @@ module Sequel
|
|
116
116
|
# :after_connect :: A callable object called after each new connection is made, with the
|
117
117
|
# connection object (and server argument if the callable accepts 2 arguments),
|
118
118
|
# useful for customizations that you want to apply to all connections.
|
119
|
+
# :compare_connections_by_identity :: Whether to use compare_by_identity on hashes that use
|
120
|
+
# connection objects as keys. Defaults to true. This should only
|
121
|
+
# be set to false to work around bugs in libraries or
|
122
|
+
# ruby implementations.
|
119
123
|
# :before_preconnect :: Callable that runs after extensions from :preconnect_extensions are loaded,
|
120
124
|
# but before any connections are created.
|
121
125
|
# :cache_schema :: Whether schema should be cached for this Database instance
|
@@ -165,7 +169,7 @@ module Sequel
|
|
165
169
|
@schemas = {}
|
166
170
|
@prepared_statements = {}
|
167
171
|
@transactions = {}
|
168
|
-
@transactions.compare_by_identity
|
172
|
+
@transactions.compare_by_identity if typecast_value_boolean(@opts.fetch(:compare_connections_by_identity, true))
|
169
173
|
@symbol_literal_cache = {}
|
170
174
|
|
171
175
|
@timezone = nil
|
@@ -430,6 +430,10 @@ module Sequel
|
|
430
430
|
# add_unique_constraint(:name, name: :unique_name) # ADD CONSTRAINT unique_name UNIQUE (name)
|
431
431
|
#
|
432
432
|
# Supports the same :deferrable option as CreateTableGenerator#column.
|
433
|
+
#
|
434
|
+
# PostgreSQL specific options:
|
435
|
+
#
|
436
|
+
# :using_index :: Use the USING INDEX clause to specify an existing unique index
|
433
437
|
def add_unique_constraint(columns, opts = OPTS)
|
434
438
|
@operations << {:op => :add_constraint, :type => :unique, :columns => Array(columns)}.merge!(opts)
|
435
439
|
nil
|
@@ -483,6 +487,10 @@ module Sequel
|
|
483
487
|
#
|
484
488
|
# add_primary_key(:id) # ADD COLUMN id serial PRIMARY KEY
|
485
489
|
# add_primary_key([:artist_id, :name]) # ADD PRIMARY KEY (artist_id, name)
|
490
|
+
#
|
491
|
+
# PostgreSQL specific options:
|
492
|
+
#
|
493
|
+
# :using_index :: Use the USING INDEX clause to specify an existing unique index
|
486
494
|
def add_primary_key(name, opts = OPTS)
|
487
495
|
return add_composite_primary_key(name, opts) if name.is_a?(Array)
|
488
496
|
opts = @db.serial_primary_key_options.merge(opts)
|
@@ -19,7 +19,7 @@ module Sequel
|
|
19
19
|
def with_extend(*mods, &block)
|
20
20
|
c = _clone(:freeze=>false)
|
21
21
|
c.extend(*mods) unless mods.empty?
|
22
|
-
c.extend(DatasetModule.new(&block)) if block
|
22
|
+
c.extend(Sequel.set_temp_name(DatasetModule.new(&block)){"Sequel::Dataset::_DatasetModule(#{block.source_location.join(':')})"}) if block
|
23
23
|
c.freeze
|
24
24
|
end
|
25
25
|
|
@@ -20,7 +20,8 @@ module Sequel
|
|
20
20
|
def self.prepared_statements_module(code, mods, meths=DEFAULT_PREPARED_STATEMENT_MODULE_METHODS, &block)
|
21
21
|
code = PREPARED_STATEMENT_MODULE_CODE[code] || code
|
22
22
|
|
23
|
-
Module.new do
|
23
|
+
Module.new do
|
24
|
+
Sequel.set_temp_name(self){"Sequel::Dataset::_PreparedStatementsModule(#{block.source_location.join(':') if block})"}
|
24
25
|
Array(mods).each do |mod|
|
25
26
|
include mod
|
26
27
|
end
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -1238,9 +1238,9 @@ module Sequel
|
|
1238
1238
|
# Note that like Object#extend, when multiple modules are provided
|
1239
1239
|
# as arguments the subclass includes the modules in reverse order.
|
1240
1240
|
def with_extend(*mods, &block)
|
1241
|
-
c = Class.new(self.class)
|
1241
|
+
c = Sequel.set_temp_name(Class.new(self.class)){"Sequel::Dataset::_Subclass"}
|
1242
1242
|
c.include(*mods) unless mods.empty?
|
1243
|
-
c.include(DatasetModule.new(&block)) if block
|
1243
|
+
c.include(Sequel.set_temp_name(DatasetModule.new(&block)){"Sequel::Dataset::_DatasetModule(#{block.source_location.join(':')})"}) if block
|
1244
1244
|
o = c.freeze.allocate
|
1245
1245
|
o.instance_variable_set(:@db, @db)
|
1246
1246
|
o.instance_variable_set(:@opts, @opts)
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -1032,7 +1032,7 @@ module Sequel
|
|
1032
1032
|
if column_aliases
|
1033
1033
|
raise Error, "#{db.database_type} does not support derived column lists" unless supports_derived_column_lists?
|
1034
1034
|
sql << '('
|
1035
|
-
|
1035
|
+
derived_column_list_sql_append(sql, column_aliases)
|
1036
1036
|
sql << ')'
|
1037
1037
|
end
|
1038
1038
|
end
|
@@ -1165,6 +1165,11 @@ module Sequel
|
|
1165
1165
|
end
|
1166
1166
|
end
|
1167
1167
|
|
1168
|
+
# Append the column aliases to the SQL.
|
1169
|
+
def derived_column_list_sql_append(sql, column_aliases)
|
1170
|
+
identifier_list_append(sql, column_aliases)
|
1171
|
+
end
|
1172
|
+
|
1168
1173
|
# Disable caching of SQL for the current dataset
|
1169
1174
|
def disable_sql_caching!
|
1170
1175
|
cache_set(:_no_cache_sql, true)
|
@@ -105,18 +105,23 @@ module Sequel
|
|
105
105
|
1.times do
|
106
106
|
if (conn = super) &&
|
107
107
|
(timer = sync{@connection_timestamps.delete(conn)}) &&
|
108
|
-
Sequel.elapsed_seconds_since(timer) > @connection_validation_timeout
|
109
|
-
!db.valid_connection?(conn)
|
108
|
+
Sequel.elapsed_seconds_since(timer) > @connection_validation_timeout
|
110
109
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
110
|
+
begin
|
111
|
+
valid = db.valid_connection?(conn)
|
112
|
+
ensure
|
113
|
+
unless valid
|
114
|
+
case pool_type
|
115
|
+
when :sharded_threaded, :sharded_timed_queue
|
116
|
+
sync{@allocated[a.last].delete(Sequel.current)}
|
117
|
+
else
|
118
|
+
sync{@allocated.delete(Sequel.current)}
|
119
|
+
end
|
117
120
|
|
118
|
-
|
119
|
-
|
121
|
+
disconnect_connection(conn)
|
122
|
+
redo
|
123
|
+
end
|
124
|
+
end
|
120
125
|
end
|
121
126
|
end
|
122
127
|
|
@@ -223,7 +223,7 @@ module Sequel
|
|
223
223
|
@actions << [:drop_join_table, *args]
|
224
224
|
end
|
225
225
|
|
226
|
-
def create_table(name, opts=OPTS)
|
226
|
+
def create_table(name, opts=OPTS, &_)
|
227
227
|
@actions << [:drop_table, name, opts]
|
228
228
|
end
|
229
229
|
|
@@ -371,7 +371,7 @@ module Sequel
|
|
371
371
|
#
|
372
372
|
# Part of the +migration+ extension.
|
373
373
|
class Migrator
|
374
|
-
MIGRATION_FILE_PATTERN = /\A(\d+)_
|
374
|
+
MIGRATION_FILE_PATTERN = /\A(\d+)_(.+)\.rb\z/i.freeze
|
375
375
|
|
376
376
|
# Mutex used around migration file loading
|
377
377
|
MUTEX = Mutex.new
|
@@ -791,7 +791,23 @@ module Sequel
|
|
791
791
|
next unless MIGRATION_FILE_PATTERN.match(file)
|
792
792
|
files << File.join(directory, file)
|
793
793
|
end
|
794
|
-
files.
|
794
|
+
files.sort! do |a, b|
|
795
|
+
a_ver, a_name = split_migration_filename(a)
|
796
|
+
b_ver, b_name = split_migration_filename(b)
|
797
|
+
x = a_ver <=> b_ver
|
798
|
+
if x.zero?
|
799
|
+
x = a_name <=> b_name
|
800
|
+
end
|
801
|
+
x
|
802
|
+
end
|
803
|
+
files
|
804
|
+
end
|
805
|
+
|
806
|
+
# Return an integer and name (without extension) for the given path.
|
807
|
+
def split_migration_filename(path)
|
808
|
+
version, name = MIGRATION_FILE_PATTERN.match(File.basename(path)).captures
|
809
|
+
version = version.to_i
|
810
|
+
[version, name]
|
795
811
|
end
|
796
812
|
|
797
813
|
# Returns tuples of migration, filename, and direction
|
@@ -63,12 +63,12 @@ module Sequel
|
|
63
63
|
end
|
64
64
|
|
65
65
|
# Return self without sending a database query, never yielding.
|
66
|
-
def each
|
66
|
+
def each(&_)
|
67
67
|
self
|
68
68
|
end
|
69
69
|
|
70
70
|
# Return nil without sending a database query, never yielding.
|
71
|
-
def fetch_rows(sql)
|
71
|
+
def fetch_rows(sql, &_)
|
72
72
|
nil
|
73
73
|
end
|
74
74
|
|
@@ -113,6 +113,7 @@ module Sequel
|
|
113
113
|
# automatically casted to the database type when literalizing.
|
114
114
|
def self.subclass(db_type)
|
115
115
|
Class.new(self) do
|
116
|
+
Sequel.set_temp_name(self){"Sequel::Postgres::PGRow::ArrayRow::_Subclass(#{db_type})"}
|
116
117
|
@db_type = db_type
|
117
118
|
end
|
118
119
|
end
|
@@ -170,6 +171,7 @@ module Sequel
|
|
170
171
|
# type and columns.
|
171
172
|
def self.subclass(db_type, columns)
|
172
173
|
Class.new(self) do
|
174
|
+
Sequel.set_temp_name(self){"Sequel::Postgres::PGRow::HashRow::_Subclass(#{db_type})"}
|
173
175
|
@db_type = db_type
|
174
176
|
@columns = columns
|
175
177
|
end
|
@@ -391,7 +393,7 @@ module Sequel
|
|
391
393
|
db.instance_exec do
|
392
394
|
@row_types = {}
|
393
395
|
@row_schema_types = {}
|
394
|
-
extend(@row_type_method_module = Module.new)
|
396
|
+
extend(@row_type_method_module = Sequel.set_temp_name(Module.new){"Sequel::Postgres::PGRow::DatabaseMethods::_RowTypeMethodModule"})
|
395
397
|
add_conversion_proc(2249, PGRow::Parser.new(:converter=>PGRow::ArrayRow))
|
396
398
|
if respond_to?(:register_array_type)
|
397
399
|
register_array_type('record', :oid=>2287, :scalar_oid=>2249)
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The query_blocker extension adds Database#block_queries.
|
4
|
+
# Inside the block passed to #block_queries, any attempts to
|
5
|
+
# execute a query/statement on the database will raise a
|
6
|
+
# Sequel::QueryBlocker::BlockedQuery exception.
|
7
|
+
#
|
8
|
+
# DB.extension :query_blocker
|
9
|
+
# DB.block_queries do
|
10
|
+
# ds = DB[:table] # No exception
|
11
|
+
# ds = ds.where(column: 1) # No exception
|
12
|
+
# ds.all # Exception raised
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# To handle concurrency, you can pass a :scope option:
|
16
|
+
#
|
17
|
+
# # Current Thread
|
18
|
+
# DB.block_queries(scope: :thread){}
|
19
|
+
#
|
20
|
+
# # Current Fiber
|
21
|
+
# DB.block_queries(scope: :fiber){}
|
22
|
+
#
|
23
|
+
# # Specific Thread
|
24
|
+
# DB.block_queries(scope: Thread.current){}
|
25
|
+
#
|
26
|
+
# # Specific Fiber
|
27
|
+
# DB.block_queries(scope: Fiber.current){}
|
28
|
+
#
|
29
|
+
# Database#block_queries is useful for blocking queries inside
|
30
|
+
# the block. However, there may be cases where you want to
|
31
|
+
# allow queries in specific places inside a block_queries block.
|
32
|
+
# You can use Database#allow_queries for that:
|
33
|
+
#
|
34
|
+
# DB.block_queries do
|
35
|
+
# DB.allow_queries do
|
36
|
+
# DB[:table].all # Query allowed
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# DB[:table].all # Exception raised
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# When mixing block_queries and allow_queries with scopes, the
|
43
|
+
# narrowest scope has priority. So if you are blocking with
|
44
|
+
# :thread scope, and allowing with :fiber scope, queries in the
|
45
|
+
# current fiber will be allowed, but queries in different fibers of
|
46
|
+
# the current thread will be blocked.
|
47
|
+
#
|
48
|
+
# Note that this should catch all queries executed through the
|
49
|
+
# Database instance. Whether it catches queries executed directly
|
50
|
+
# on a connection object depends on the adapter in use.
|
51
|
+
#
|
52
|
+
# Related module: Sequel::QueryBlocker
|
53
|
+
|
54
|
+
require "fiber"
|
55
|
+
|
56
|
+
#
|
57
|
+
module Sequel
|
58
|
+
module QueryBlocker
|
59
|
+
# Exception class raised if there is an attempt to execute a
|
60
|
+
# query/statement on the database inside a block passed to
|
61
|
+
# block_queries.
|
62
|
+
class BlockedQuery < Sequel::Error
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.extended(db)
|
66
|
+
db.instance_exec do
|
67
|
+
@blocked_query_scopes ||= {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# If checking a connection for validity, and a BlockedQuery exception is
|
72
|
+
# raised, treat it as a valid connection. You cannot check whether the
|
73
|
+
# connection is valid without issuing a query, and if queries are blocked,
|
74
|
+
# you need to assume it is valid or assume it is not. Since it most cases
|
75
|
+
# it will be valid, this assumes validity.
|
76
|
+
def valid_connection?(conn)
|
77
|
+
super
|
78
|
+
rescue BlockedQuery
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
# Check whether queries are blocked before executing them.
|
83
|
+
def log_connection_yield(sql, conn, args=nil)
|
84
|
+
# All database adapters should be calling this method around
|
85
|
+
# query execution (otherwise the queries would not get logged),
|
86
|
+
# ensuring the blocking is checked. Any database adapter issuing
|
87
|
+
# a query without calling this method is considered buggy.
|
88
|
+
check_blocked_queries!
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
# Whether queries are currently blocked.
|
93
|
+
def block_queries?
|
94
|
+
b = @blocked_query_scopes
|
95
|
+
b.fetch(Fiber.current) do
|
96
|
+
b.fetch(Thread.current) do
|
97
|
+
b.fetch(:global, false)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Allow queries inside the block. Only useful if they are already blocked
|
103
|
+
# for the same scope. Useful for blocking queries generally, and only allowing
|
104
|
+
# them in specific places. Takes the same :scope option as #block_queries.
|
105
|
+
def allow_queries(opts=OPTS, &block)
|
106
|
+
_allow_or_block_queries(false, opts, &block)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Reject (raise an BlockedQuery exception) if there is an attempt to execute
|
110
|
+
# a query/statement inside the block.
|
111
|
+
#
|
112
|
+
# The :scope option indicates which queries are rejected inside the block:
|
113
|
+
#
|
114
|
+
# :global :: This is the default, and rejects all queries.
|
115
|
+
# :thread :: Reject all queries in the current thread.
|
116
|
+
# :fiber :: Reject all queries in the current fiber.
|
117
|
+
# Thread :: Reject all queries in the given thread.
|
118
|
+
# Fiber :: Reject all queries in the given fiber.
|
119
|
+
def block_queries(opts=OPTS, &block)
|
120
|
+
_allow_or_block_queries(true, opts, &block)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# Internals of block_queries and allow_queries.
|
126
|
+
def _allow_or_block_queries(value, opts)
|
127
|
+
scope = query_blocker_scope(opts)
|
128
|
+
prev_value = nil
|
129
|
+
scopes = @blocked_query_scopes
|
130
|
+
|
131
|
+
begin
|
132
|
+
Sequel.synchronize do
|
133
|
+
prev_value = scopes[scope]
|
134
|
+
scopes[scope] = value
|
135
|
+
end
|
136
|
+
|
137
|
+
yield
|
138
|
+
ensure
|
139
|
+
Sequel.synchronize do
|
140
|
+
if prev_value.nil?
|
141
|
+
scopes.delete(scope)
|
142
|
+
else
|
143
|
+
scopes[scope] = prev_value
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# The scope for the query block, either :global, or a Thread or Fiber instance.
|
150
|
+
def query_blocker_scope(opts)
|
151
|
+
case scope = opts[:scope]
|
152
|
+
when nil
|
153
|
+
:global
|
154
|
+
when :global, Thread, Fiber
|
155
|
+
scope
|
156
|
+
when :thread
|
157
|
+
Thread.current
|
158
|
+
when :fiber
|
159
|
+
Fiber.current
|
160
|
+
else
|
161
|
+
raise Sequel::Error, "invalid scope given to block_queries: #{scope.inspect}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Raise a BlockQuery exception if queries are currently blocked.
|
166
|
+
def check_blocked_queries!
|
167
|
+
raise BlockedQuery, "cannot execute query inside a block_queries block" if block_queries?
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
Database.register_extension(:query_blocker, QueryBlocker)
|
172
|
+
end
|
@@ -173,7 +173,7 @@ module Sequel
|
|
173
173
|
# Return a modified StringAgg that uses distinct expressions
|
174
174
|
def distinct
|
175
175
|
self.class.new(@expr, @separator) do |sa|
|
176
|
-
sa.instance_variable_set(:@order_expr, @order_expr)
|
176
|
+
sa.instance_variable_set(:@order_expr, @order_expr)
|
177
177
|
sa.instance_variable_set(:@distinct, true)
|
178
178
|
end
|
179
179
|
end
|
@@ -181,8 +181,8 @@ module Sequel
|
|
181
181
|
# Return a modified StringAgg with the given order
|
182
182
|
def order(*o)
|
183
183
|
self.class.new(@expr, @separator) do |sa|
|
184
|
-
sa.instance_variable_set(:@distinct, @distinct) if @distinct
|
185
184
|
sa.instance_variable_set(:@order_expr, o.empty? ? nil : o.freeze)
|
185
|
+
sa.instance_variable_set(:@distinct, @distinct)
|
186
186
|
end
|
187
187
|
end
|
188
188
|
|
@@ -14,6 +14,7 @@ module Sequel
|
|
14
14
|
module SQL
|
15
15
|
class VirtualRow < BasicObject
|
16
16
|
include(Module.new do
|
17
|
+
Sequel.set_temp_name(self){"Sequel::SQL:VirtualRow::_MethodBlockMethodMissing"}
|
17
18
|
# Handle blocks passed to methods and change the behavior.
|
18
19
|
def method_missing(m, *args, &block)
|
19
20
|
if block
|
data/lib/sequel/model/base.rb
CHANGED
@@ -184,7 +184,7 @@ module Sequel
|
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
187
|
-
klass = Class.new(self)
|
187
|
+
klass = Sequel.set_temp_name(Class.new(self)){"Sequel::_Model(#{source.inspect})"}
|
188
188
|
|
189
189
|
if source.is_a?(::Sequel::Database)
|
190
190
|
klass.db = source
|
@@ -762,22 +762,35 @@ module Sequel
|
|
762
762
|
end
|
763
763
|
end
|
764
764
|
end
|
765
|
+
|
766
|
+
# Module that the class methods that call dataset methods are kept in.
|
767
|
+
# This allows the methods to be overridden and call super with the
|
768
|
+
# default behavior.
|
769
|
+
def dataset_methods_module
|
770
|
+
return @dataset_methods_module if defined?(@dataset_methods_module)
|
771
|
+
Sequel.synchronize{@dataset_methods_module ||= Sequel.set_temp_name(Module.new){"#{name}::@dataset_methods_module"}}
|
772
|
+
extend(@dataset_methods_module)
|
773
|
+
@dataset_methods_module
|
774
|
+
end
|
765
775
|
|
766
|
-
# Define a model method that calls the dataset method with the same name
|
767
|
-
# only used for methods with names that can't be represented directly in
|
768
|
-
# ruby code.
|
776
|
+
# Define a model method that calls the dataset method with the same name.
|
769
777
|
def def_model_dataset_method(meth)
|
770
778
|
return if respond_to?(meth, true)
|
771
779
|
|
780
|
+
mod = dataset_methods_module
|
781
|
+
|
772
782
|
if meth.to_s =~ /\A[A-Za-z_][A-Za-z0-9_]*\z/
|
773
|
-
|
783
|
+
mod.module_eval(<<END, __FILE__, __LINE__ + 1)
|
784
|
+
def #{meth}(*args, &block); dataset.#{meth}(*args, &block) end
|
785
|
+
ruby2_keywords :#{meth} if respond_to?(:ruby2_keywords, true)
|
786
|
+
END
|
774
787
|
else
|
775
|
-
|
788
|
+
mod.send(:define_method, meth){|*args, &block| dataset.public_send(meth, *args, &block)}
|
789
|
+
# :nocov:
|
790
|
+
mod.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
|
791
|
+
# :nocov:
|
776
792
|
end
|
777
|
-
|
778
|
-
# :nocov:
|
779
|
-
singleton_class.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
|
780
|
-
# :nocov:
|
793
|
+
mod.send(:alias_method, meth, meth)
|
781
794
|
end
|
782
795
|
|
783
796
|
# Get the schema from the database, fall back on checking the columns
|
@@ -943,7 +956,7 @@ module Sequel
|
|
943
956
|
# Module that the class includes that holds methods the class adds for column accessors and
|
944
957
|
# associations so that the methods can be overridden with +super+.
|
945
958
|
def overridable_methods_module
|
946
|
-
include(@overridable_methods_module = Module.new) unless @overridable_methods_module
|
959
|
+
include(@overridable_methods_module = Sequel.set_temp_name(Module.new){"#{name}::@overridable_methods_module"}) unless @overridable_methods_module
|
947
960
|
@overridable_methods_module
|
948
961
|
end
|
949
962
|
|
data/lib/sequel/plugins/enum.rb
CHANGED
@@ -32,6 +32,7 @@ module Sequel
|
|
32
32
|
def self.apply(model, &block)
|
33
33
|
model.instance_exec do
|
34
34
|
@dataset_module_class = Class.new(@dataset_module_class) do
|
35
|
+
Sequel.set_temp_name(self){"#{model.name}::@dataset_module_class(InvertedSubsets)"}
|
35
36
|
include DatasetModuleMethods
|
36
37
|
if block
|
37
38
|
define_method(:inverted_subset_name, &block)
|
@@ -64,7 +64,7 @@ module Sequel
|
|
64
64
|
# :dataset :: The base dataset to use for the lazy attribute lookup
|
65
65
|
# :table :: The table name to use to qualify the attribute and primary key columns.
|
66
66
|
def define_lazy_attribute_getter(a, opts=OPTS)
|
67
|
-
include(@lazy_attributes_module ||= Module.new) unless @lazy_attributes_module
|
67
|
+
include(@lazy_attributes_module ||= Sequel.set_temp_name(Module.new){"#{name}::@lazy_attributes_module"}) unless @lazy_attributes_module
|
68
68
|
@lazy_attributes_module.class_eval do
|
69
69
|
define_method(a) do
|
70
70
|
if !values.has_key?(a) && !new?
|
@@ -129,7 +129,7 @@ module Sequel
|
|
129
129
|
#
|
130
130
|
# If a block is provided, it is used to set the :reject_if option.
|
131
131
|
def nested_attributes(*associations, &block)
|
132
|
-
include(@nested_attributes_module ||= Module.new) unless @nested_attributes_module
|
132
|
+
include(@nested_attributes_module ||= Sequel.set_temp_name(Module.new){"#{name}::@nested_attributes_module"}) unless @nested_attributes_module
|
133
133
|
opts = associations.last.is_a?(Hash) ? associations.pop : OPTS
|
134
134
|
reflections = associations.map{|a| association_reflection(a) || raise(Error, "no association named #{a} for #{self}")}
|
135
135
|
reflections.each do |r|
|
@@ -81,7 +81,7 @@ module Sequel
|
|
81
81
|
|
82
82
|
opts = opts.dup
|
83
83
|
opts[:class] = model
|
84
|
-
opts[:methods_module] = Module.new
|
84
|
+
opts[:methods_module] = Sequel.set_temp_name(Module.new){"#{model.name}::_rcte_tree[:methods_module]"}
|
85
85
|
opts[:union_all] = opts[:union_all].nil? ? true : opts[:union_all]
|
86
86
|
model.send(:include, opts[:methods_module])
|
87
87
|
|
@@ -37,7 +37,8 @@ module Sequel
|
|
37
37
|
#
|
38
38
|
# # Register custom serializer/deserializer pair, if desired
|
39
39
|
# require 'sequel/plugins/serialization'
|
40
|
-
#
|
40
|
+
# require 'base64'
|
41
|
+
# Sequel::Plugins::Serialization.register_format(:base64, Base64.method(:encode64), Base64.method(:decode64))
|
41
42
|
#
|
42
43
|
# class User < Sequel::Model
|
43
44
|
# # Built-in format support when loading the plugin
|
@@ -48,10 +49,10 @@ module Sequel
|
|
48
49
|
# serialize_attributes :marshal, :permissions
|
49
50
|
#
|
50
51
|
# # Use custom registered serialization format just like built-in format
|
51
|
-
# serialize_attributes :
|
52
|
+
# serialize_attributes :base64, :password
|
52
53
|
#
|
53
54
|
# # Use a custom serializer/deserializer pair without registering
|
54
|
-
# serialize_attributes [:
|
55
|
+
# serialize_attributes [ Base64.method(:encode64), Base64.method(:decode64)], :password
|
55
56
|
# end
|
56
57
|
# user = User.create
|
57
58
|
# user.permissions = {global: 'read-only'}
|
@@ -123,7 +124,12 @@ module Sequel
|
|
123
124
|
end
|
124
125
|
|
125
126
|
# Create instance level reader that deserializes column values on request,
|
126
|
-
# and instance level writer that stores new deserialized values.
|
127
|
+
# and instance level writer that stores new deserialized values. If +format+
|
128
|
+
# is a symbol, it should correspond to a previously-registered format using +register_format+.
|
129
|
+
# Otherwise, +format+ is expected to be a 2-element array of callables,
|
130
|
+
# with the first element being the serializer, used to convert the value used by the application
|
131
|
+
# to the value that will be stored in the database, and the second element being the deserializer,
|
132
|
+
# used to convert the value stored the database to the value used by the application.
|
127
133
|
def serialize_attributes(format, *columns)
|
128
134
|
if format.is_a?(Symbol)
|
129
135
|
unless format = Sequel.synchronize{REGISTERED_FORMATS[format]}
|
@@ -140,7 +146,7 @@ module Sequel
|
|
140
146
|
# Add serializated attribute acessor methods to the serialization_module
|
141
147
|
def define_serialized_attribute_accessor(serializer, deserializer, *columns)
|
142
148
|
m = self
|
143
|
-
include(@serialization_module ||= Module.new) unless @serialization_module
|
149
|
+
include(@serialization_module ||= Sequel.set_temp_name(Module.new){"#{name}::@serialization_module"}) unless @serialization_module
|
144
150
|
@serialization_module.class_eval do
|
145
151
|
columns.each do |column|
|
146
152
|
m.serialization_map[column] = serializer
|
@@ -85,7 +85,7 @@ module Sequel
|
|
85
85
|
# Use automatic SQL comments for the given dataset methods.
|
86
86
|
def sql_comments_dataset_methods(*meths)
|
87
87
|
unless @_sql_comments_dataset_module
|
88
|
-
dataset_module(@_sql_comments_dataset_module = Module.new)
|
88
|
+
dataset_module(@_sql_comments_dataset_module = Sequel.set_temp_name(Module.new){"#{name}::@_sql_comments_dataset_module"})
|
89
89
|
end
|
90
90
|
_sql_comments_methods(@_sql_comments_dataset_module, :dataset, meths)
|
91
91
|
end
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Sequel
|
4
4
|
module Plugins
|
5
|
-
# The static_cache_cache plugin allows for caching the row content for
|
6
|
-
# that use the
|
7
|
-
# can avoid the need to query the database every time loading
|
8
|
-
#
|
9
|
-
# plugin.
|
5
|
+
# The static_cache_cache plugin allows for caching the row content for the current
|
6
|
+
# class and subclasses that use the static_cache or subset_static_cache plugins.
|
7
|
+
# Using this plugin can avoid the need to query the database every time loading
|
8
|
+
# the static_cache plugin into a model (static_cache plugin) or using the
|
9
|
+
# cache_subset method (subset_static_cache plugin).
|
10
10
|
#
|
11
11
|
# Usage:
|
12
12
|
#
|
@@ -26,11 +26,7 @@ module Sequel
|
|
26
26
|
module ClassMethods
|
27
27
|
# Dump the in-memory cached rows to the cache file.
|
28
28
|
def dump_static_cache_cache
|
29
|
-
|
30
|
-
@static_cache_cache.sort.each do |k, v|
|
31
|
-
static_cache_cache[k] = v
|
32
|
-
end
|
33
|
-
File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(static_cache_cache))}
|
29
|
+
File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(sort_static_cache_hash(@static_cache_cache)))}
|
34
30
|
nil
|
35
31
|
end
|
36
32
|
|
@@ -38,16 +34,57 @@ module Sequel
|
|
38
34
|
|
39
35
|
private
|
40
36
|
|
37
|
+
# Sort the given static cache hash in a deterministic way, so that
|
38
|
+
# the same static cache values will result in the same marshal file.
|
39
|
+
def sort_static_cache_hash(cache)
|
40
|
+
cache = cache.sort do |a, b|
|
41
|
+
a, = a
|
42
|
+
b, = b
|
43
|
+
if a.is_a?(Array)
|
44
|
+
if b.is_a?(Array)
|
45
|
+
a_name, a_meth = a
|
46
|
+
b_name, b_meth = b
|
47
|
+
x = a_name <=> b_name
|
48
|
+
if x.zero?
|
49
|
+
x = a_meth <=> b_meth
|
50
|
+
end
|
51
|
+
x
|
52
|
+
else
|
53
|
+
1
|
54
|
+
end
|
55
|
+
elsif b.is_a?(Array)
|
56
|
+
-1
|
57
|
+
else
|
58
|
+
a <=> b
|
59
|
+
end
|
60
|
+
end
|
61
|
+
Hash[cache]
|
62
|
+
end
|
63
|
+
|
41
64
|
# Load the rows for the model from the cache if available.
|
42
65
|
# If not available, load the rows from the database, and
|
43
66
|
# then update the cache with the raw rows.
|
44
67
|
def load_static_cache_rows
|
45
|
-
|
68
|
+
_load_static_cache_rows(dataset, name)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Load the rows for the subset from the cache if available.
|
72
|
+
# If not available, load the rows from the database, and
|
73
|
+
# then update the cache with the raw rows.
|
74
|
+
def load_subset_static_cache_rows(ds, meth)
|
75
|
+
_load_static_cache_rows(ds, [name, meth].freeze)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Check the cache first for the key, and return rows without a database
|
79
|
+
# query if present. Otherwise, get all records in the provided dataset,
|
80
|
+
# and update the cache with them.
|
81
|
+
def _load_static_cache_rows(ds, key)
|
82
|
+
if rows = Sequel.synchronize{@static_cache_cache[key]}
|
46
83
|
rows.map{|row| call(row)}.freeze
|
47
84
|
else
|
48
|
-
rows =
|
85
|
+
rows = ds.all.freeze
|
49
86
|
raw_rows = rows.map(&:values)
|
50
|
-
Sequel.synchronize{@static_cache_cache[
|
87
|
+
Sequel.synchronize{@static_cache_cache[key] = raw_rows}
|
51
88
|
rows
|
52
89
|
end
|
53
90
|
end
|
@@ -48,6 +48,7 @@ module Sequel
|
|
48
48
|
def self.apply(model, &block)
|
49
49
|
model.instance_exec do
|
50
50
|
@dataset_module_class = Class.new(@dataset_module_class) do
|
51
|
+
Sequel.set_temp_name(self){"#{model.name}::@dataset_module_class(SubsetConditions)"}
|
51
52
|
include DatasetModuleMethods
|
52
53
|
end
|
53
54
|
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The subset_static_cache plugin is designed for model subsets that are not modified at all
|
6
|
+
# in production use cases, or at least where modifications to them would usually
|
7
|
+
# coincide with an application restart. When caching a model subset, it
|
8
|
+
# retrieves all rows in the database and statically caches a ruby array and hash
|
9
|
+
# keyed on primary key containing all of the model instances. All of these cached
|
10
|
+
# instances are frozen so they won't be modified unexpectedly.
|
11
|
+
#
|
12
|
+
# With the following code:
|
13
|
+
#
|
14
|
+
# class StatusType < Sequel::Model
|
15
|
+
# dataset_module do
|
16
|
+
# where :available, hidden: false
|
17
|
+
# end
|
18
|
+
# cache_subset :available
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# The following methods will use the cache and not issue a database query:
|
22
|
+
#
|
23
|
+
# * StatusType.available.with_pk
|
24
|
+
# * StatusType.available.all
|
25
|
+
# * StatusType.available.each
|
26
|
+
# * StatusType.available.first (without block, only supporting no arguments or single integer argument)
|
27
|
+
# * StatusType.available.count (without an argument or block)
|
28
|
+
# * StatusType.available.map
|
29
|
+
# * StatusType.available.as_hash
|
30
|
+
# * StatusType.available.to_hash
|
31
|
+
# * StatusType.available.to_hash_groups
|
32
|
+
#
|
33
|
+
# The cache is not used if you chain methods before or after calling the cached
|
34
|
+
# method, as doing so would not be safe:
|
35
|
+
#
|
36
|
+
# StatusType.where{number > 1}.available.all
|
37
|
+
# StatusType.available.where{number > 1}.all
|
38
|
+
#
|
39
|
+
# The cache is also not used if you change the class's dataset after caching
|
40
|
+
# the subset, or in subclasses of the model.
|
41
|
+
#
|
42
|
+
# You should not modify any row that is statically cached when using this plugin,
|
43
|
+
# as otherwise you will get different results for cached and uncached method
|
44
|
+
# calls.
|
45
|
+
module SubsetStaticCache
|
46
|
+
def self.configure(model)
|
47
|
+
model.class_exec do
|
48
|
+
@subset_static_caches ||= ({}.compare_by_identity)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
# Cache the given subset statically, so that calling the subset method on
|
54
|
+
# the model will return a dataset that will return cached results instead
|
55
|
+
# of issuing database queries (assuming the cache has the necessary
|
56
|
+
# information).
|
57
|
+
#
|
58
|
+
# The model must already respond to the given method before cache_subset
|
59
|
+
# is called.
|
60
|
+
def cache_subset(meth)
|
61
|
+
ds = send(meth).with_extend(CachedDatasetMethods)
|
62
|
+
cache = ds.instance_variable_get(:@cache)
|
63
|
+
|
64
|
+
rows, hash = subset_static_cache_rows(ds, meth)
|
65
|
+
cache[:subset_static_cache_all] = rows
|
66
|
+
cache[:subset_static_cache_map] = hash
|
67
|
+
|
68
|
+
caches = @subset_static_caches
|
69
|
+
caches[meth] = ds
|
70
|
+
model = self
|
71
|
+
subset_static_cache_module.send(:define_method, meth) do
|
72
|
+
if (model == self) && (cached_dataset = caches[meth])
|
73
|
+
cached_dataset
|
74
|
+
else
|
75
|
+
super()
|
76
|
+
end
|
77
|
+
end
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
|
81
|
+
Plugins.after_set_dataset(self, :clear_subset_static_caches)
|
82
|
+
Plugins.inherited_instance_variables(self, :@subset_static_caches=>proc{{}.compare_by_identity})
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Clear the subset_static_caches. This is used if the model dataset
|
87
|
+
# changes, to prevent cached values from being used.
|
88
|
+
def clear_subset_static_caches
|
89
|
+
@subset_static_caches.clear
|
90
|
+
end
|
91
|
+
|
92
|
+
# A module for the subset static cache methods, so that you can define
|
93
|
+
# a singleton method in the class with the same name, and call super
|
94
|
+
# to get default behavior.
|
95
|
+
def subset_static_cache_module
|
96
|
+
return @subset_static_cache_module if @subset_static_cache_module
|
97
|
+
|
98
|
+
# Ensure dataset_methods module is defined and class is extended with
|
99
|
+
# it before calling creating this module.
|
100
|
+
dataset_methods_module
|
101
|
+
|
102
|
+
Sequel.synchronize{@subset_static_cache_module ||= Sequel.set_temp_name(Module.new){"#{name}::@subset_static_cache_module"}}
|
103
|
+
extend(@subset_static_cache_module)
|
104
|
+
@subset_static_cache_module
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return the frozen array and hash used for caching the subset
|
108
|
+
# of the given dataset.
|
109
|
+
def subset_static_cache_rows(ds, meth)
|
110
|
+
all = load_subset_static_cache_rows(ds, meth)
|
111
|
+
h = {}
|
112
|
+
all.each do |o|
|
113
|
+
o.errors.freeze
|
114
|
+
h[o.pk.freeze] = o.freeze
|
115
|
+
end
|
116
|
+
[all, h.freeze]
|
117
|
+
end
|
118
|
+
|
119
|
+
# Return a frozen array for all rows in the dataset.
|
120
|
+
def load_subset_static_cache_rows(ds, meth)
|
121
|
+
ret = super if defined?(super)
|
122
|
+
ret || ds.all.freeze
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
module CachedDatasetMethods
|
127
|
+
# An array of all of the dataset's instances, without issuing a database
|
128
|
+
# query. If a block is given, yields each instance to the block.
|
129
|
+
def all(&block)
|
130
|
+
return super unless all = @cache[:subset_static_cache_all]
|
131
|
+
|
132
|
+
array = all.dup
|
133
|
+
array.each(&block) if block
|
134
|
+
array
|
135
|
+
end
|
136
|
+
|
137
|
+
# Get the number of records in the cache, without issuing a database query,
|
138
|
+
# if no arguments or block are provided.
|
139
|
+
def count(*a, &block)
|
140
|
+
if a.empty? && !block && (all = @cache[:subset_static_cache_all])
|
141
|
+
all.size
|
142
|
+
else
|
143
|
+
super
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# If a block is given, multiple arguments are given, or a single
|
148
|
+
# non-Integer argument is given, performs the default behavior of
|
149
|
+
# issuing a database query. Otherwise, uses the cached values
|
150
|
+
# to return either the first cached instance (no arguments) or an
|
151
|
+
# array containing the number of instances specified (single integer
|
152
|
+
# argument).
|
153
|
+
def first(*args)
|
154
|
+
if !defined?(yield) && args.length <= 1 && (args.length == 0 || args[0].is_a?(Integer)) && (all = @cache[:subset_static_cache_all])
|
155
|
+
all.first(*args)
|
156
|
+
else
|
157
|
+
super
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Return the frozen object with the given pk, or nil if no such object exists
|
162
|
+
# in the cache, without issuing a database query.
|
163
|
+
def with_pk(pk)
|
164
|
+
if cache = @cache[:subset_static_cache_map]
|
165
|
+
cache[pk]
|
166
|
+
else
|
167
|
+
super
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Yield each of the dataset's frozen instances to the block, without issuing a database
|
172
|
+
# query.
|
173
|
+
def each(&block)
|
174
|
+
return super unless all = @cache[:subset_static_cache_all]
|
175
|
+
all.each(&block)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Use the cache instead of a query to get the results.
|
179
|
+
def map(column=nil, &block)
|
180
|
+
return super unless all = @cache[:subset_static_cache_all]
|
181
|
+
if column
|
182
|
+
raise(Error, "Cannot provide both column and block to map") if block
|
183
|
+
if column.is_a?(Array)
|
184
|
+
all.map{|r| r.values.values_at(*column)}
|
185
|
+
else
|
186
|
+
all.map{|r| r[column]}
|
187
|
+
end
|
188
|
+
else
|
189
|
+
all.map(&block)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Use the cache instead of a query to get the results if possible
|
194
|
+
def as_hash(key_column = nil, value_column = nil, opts = OPTS)
|
195
|
+
return super unless all = @cache[:subset_static_cache_all]
|
196
|
+
|
197
|
+
if key_column.nil? && value_column.nil?
|
198
|
+
if opts[:hash]
|
199
|
+
key_column = model.primary_key
|
200
|
+
else
|
201
|
+
return Hash[@cache[:subset_static_cache_map]]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
h = opts[:hash] || {}
|
206
|
+
if value_column
|
207
|
+
if value_column.is_a?(Array)
|
208
|
+
if key_column.is_a?(Array)
|
209
|
+
all.each{|r| h[r.values.values_at(*key_column)] = r.values.values_at(*value_column)}
|
210
|
+
else
|
211
|
+
all.each{|r| h[r[key_column]] = r.values.values_at(*value_column)}
|
212
|
+
end
|
213
|
+
else
|
214
|
+
if key_column.is_a?(Array)
|
215
|
+
all.each{|r| h[r.values.values_at(*key_column)] = r[value_column]}
|
216
|
+
else
|
217
|
+
all.each{|r| h[r[key_column]] = r[value_column]}
|
218
|
+
end
|
219
|
+
end
|
220
|
+
elsif key_column.is_a?(Array)
|
221
|
+
all.each{|r| h[r.values.values_at(*key_column)] = r}
|
222
|
+
else
|
223
|
+
all.each{|r| h[r[key_column]] = r}
|
224
|
+
end
|
225
|
+
h
|
226
|
+
end
|
227
|
+
|
228
|
+
# Alias of as_hash for backwards compatibility.
|
229
|
+
def to_hash(*a)
|
230
|
+
as_hash(*a)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Use the cache instead of a query to get the results
|
234
|
+
def to_hash_groups(key_column, value_column = nil, opts = OPTS)
|
235
|
+
return super unless all = @cache[:subset_static_cache_all]
|
236
|
+
|
237
|
+
h = opts[:hash] || {}
|
238
|
+
if value_column
|
239
|
+
if value_column.is_a?(Array)
|
240
|
+
if key_column.is_a?(Array)
|
241
|
+
all.each{|r| (h[r.values.values_at(*key_column)] ||= []) << r.values.values_at(*value_column)}
|
242
|
+
else
|
243
|
+
all.each{|r| (h[r[key_column]] ||= []) << r.values.values_at(*value_column)}
|
244
|
+
end
|
245
|
+
else
|
246
|
+
if key_column.is_a?(Array)
|
247
|
+
all.each{|r| (h[r.values.values_at(*key_column)] ||= []) << r[value_column]}
|
248
|
+
else
|
249
|
+
all.each{|r| (h[r[key_column]] ||= []) << r[value_column]}
|
250
|
+
end
|
251
|
+
end
|
252
|
+
elsif key_column.is_a?(Array)
|
253
|
+
all.each{|r| (h[r.values.values_at(*key_column)] ||= []) << r}
|
254
|
+
else
|
255
|
+
all.each{|r| (h[r[key_column]] ||= []) << r}
|
256
|
+
end
|
257
|
+
h
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
data/lib/sequel/sql.rb
CHANGED
@@ -1915,6 +1915,7 @@ module Sequel
|
|
1915
1915
|
end
|
1916
1916
|
|
1917
1917
|
m = Module.new do
|
1918
|
+
Sequel.set_temp_name(Module.new){"Sequel::SQL::VirtualRow::_BaseMethodMissing"}
|
1918
1919
|
# Return an +Identifier+, +QualifiedIdentifier+, or +Function+, depending
|
1919
1920
|
# on arguments and whether a block is provided. Does not currently call the block.
|
1920
1921
|
# See the class level documentation.
|
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 = 89
|
10
10
|
|
11
11
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
12
12
|
# releases that fix regressions from previous versions.
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.89.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-02-01 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: bigdecimal
|
@@ -274,6 +273,7 @@ files:
|
|
274
273
|
- lib/sequel/extensions/pretty_table.rb
|
275
274
|
- lib/sequel/extensions/provenance.rb
|
276
275
|
- lib/sequel/extensions/query.rb
|
276
|
+
- lib/sequel/extensions/query_blocker.rb
|
277
277
|
- lib/sequel/extensions/round_timestamps.rb
|
278
278
|
- lib/sequel/extensions/run_transaction_hooks.rb
|
279
279
|
- lib/sequel/extensions/s.rb
|
@@ -392,6 +392,7 @@ files:
|
|
392
392
|
- lib/sequel/plugins/string_stripper.rb
|
393
393
|
- lib/sequel/plugins/subclasses.rb
|
394
394
|
- lib/sequel/plugins/subset_conditions.rb
|
395
|
+
- lib/sequel/plugins/subset_static_cache.rb
|
395
396
|
- lib/sequel/plugins/table_select.rb
|
396
397
|
- lib/sequel/plugins/tactical_eager_loading.rb
|
397
398
|
- lib/sequel/plugins/throw_failures.rb
|
@@ -424,7 +425,6 @@ metadata:
|
|
424
425
|
documentation_uri: https://sequel.jeremyevans.net/documentation.html
|
425
426
|
mailing_list_uri: https://github.com/jeremyevans/sequel/discussions
|
426
427
|
source_code_uri: https://github.com/jeremyevans/sequel
|
427
|
-
post_install_message:
|
428
428
|
rdoc_options:
|
429
429
|
- "--quiet"
|
430
430
|
- "--line-numbers"
|
@@ -446,8 +446,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
446
446
|
- !ruby/object:Gem::Version
|
447
447
|
version: '0'
|
448
448
|
requirements: []
|
449
|
-
rubygems_version: 3.
|
450
|
-
signing_key:
|
449
|
+
rubygems_version: 3.6.2
|
451
450
|
specification_version: 4
|
452
451
|
summary: The Database Toolkit for Ruby
|
453
452
|
test_files: []
|