sequel 5.60.1 → 5.81.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 +280 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +27 -24
- data/doc/advanced_associations.rdoc +16 -14
- data/doc/association_basics.rdoc +53 -17
- data/doc/cheat_sheet.rdoc +3 -3
- data/doc/code_order.rdoc +5 -3
- data/doc/dataset_basics.rdoc +1 -1
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +15 -0
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +18 -7
- data/doc/postgresql.rdoc +8 -8
- data/doc/querying.rdoc +7 -2
- data/doc/release_notes/5.61.0.txt +43 -0
- data/doc/release_notes/5.62.0.txt +132 -0
- data/doc/release_notes/5.63.0.txt +33 -0
- data/doc/release_notes/5.64.0.txt +50 -0
- data/doc/release_notes/5.65.0.txt +21 -0
- data/doc/release_notes/5.66.0.txt +24 -0
- data/doc/release_notes/5.67.0.txt +32 -0
- data/doc/release_notes/5.68.0.txt +61 -0
- data/doc/release_notes/5.69.0.txt +26 -0
- data/doc/release_notes/5.70.0.txt +35 -0
- data/doc/release_notes/5.71.0.txt +21 -0
- data/doc/release_notes/5.72.0.txt +33 -0
- data/doc/release_notes/5.73.0.txt +66 -0
- data/doc/release_notes/5.74.0.txt +45 -0
- data/doc/release_notes/5.75.0.txt +35 -0
- data/doc/release_notes/5.76.0.txt +86 -0
- data/doc/release_notes/5.77.0.txt +63 -0
- data/doc/release_notes/5.78.0.txt +67 -0
- data/doc/release_notes/5.79.0.txt +28 -0
- data/doc/release_notes/5.80.0.txt +40 -0
- data/doc/release_notes/5.81.0.txt +31 -0
- data/doc/schema_modification.rdoc +3 -3
- data/doc/security.rdoc +9 -9
- data/doc/sharding.rdoc +3 -1
- data/doc/sql.rdoc +14 -14
- data/doc/testing.rdoc +16 -12
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/ibmdb.rb +1 -1
- data/lib/sequel/adapters/jdbc/h2.rb +3 -0
- data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
- data/lib/sequel/adapters/jdbc.rb +10 -6
- data/lib/sequel/adapters/mysql.rb +19 -7
- data/lib/sequel/adapters/mysql2.rb +2 -2
- data/lib/sequel/adapters/odbc/mssql.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +1 -0
- data/lib/sequel/adapters/postgres.rb +10 -5
- data/lib/sequel/adapters/shared/access.rb +9 -1
- data/lib/sequel/adapters/shared/db2.rb +12 -0
- data/lib/sequel/adapters/shared/mssql.rb +71 -9
- data/lib/sequel/adapters/shared/mysql.rb +82 -3
- data/lib/sequel/adapters/shared/oracle.rb +17 -7
- data/lib/sequel/adapters/shared/postgres.rb +469 -160
- data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
- data/lib/sequel/adapters/shared/sqlite.rb +39 -3
- data/lib/sequel/adapters/sqlite.rb +42 -3
- data/lib/sequel/adapters/trilogy.rb +117 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
- data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
- data/lib/sequel/connection_pool/threaded.rb +14 -8
- data/lib/sequel/connection_pool/timed_queue.rb +270 -0
- data/lib/sequel/connection_pool.rb +57 -31
- data/lib/sequel/database/connecting.rb +25 -1
- data/lib/sequel/database/dataset.rb +16 -6
- data/lib/sequel/database/misc.rb +65 -14
- data/lib/sequel/database/query.rb +72 -1
- data/lib/sequel/database/schema_generator.rb +1 -1
- data/lib/sequel/database/schema_methods.rb +12 -5
- data/lib/sequel/database/transactions.rb +6 -0
- data/lib/sequel/dataset/actions.rb +60 -13
- data/lib/sequel/dataset/dataset_module.rb +1 -1
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
- data/lib/sequel/dataset/features.rb +15 -1
- data/lib/sequel/dataset/graph.rb +1 -0
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/query.rb +119 -45
- data/lib/sequel/dataset/sql.rb +58 -36
- data/lib/sequel/dataset.rb +4 -0
- data/lib/sequel/exceptions.rb +5 -0
- data/lib/sequel/extensions/_model_pg_row.rb +0 -12
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/any_not_empty.rb +2 -2
- data/lib/sequel/extensions/async_thread_pool.rb +21 -13
- data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/caller_logging.rb +4 -1
- data/lib/sequel/extensions/connection_expiration.rb +15 -9
- data/lib/sequel/extensions/connection_validator.rb +16 -11
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/date_arithmetic.rb +1 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/looser_typecasting.rb +3 -0
- data/lib/sequel/extensions/migration.rb +65 -15
- data/lib/sequel/extensions/named_timezones.rb +22 -6
- data/lib/sequel/extensions/pg_array.rb +33 -4
- data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
- data/lib/sequel/extensions/pg_enum.rb +1 -2
- data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
- data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
- data/lib/sequel/extensions/pg_hstore.rb +5 -0
- data/lib/sequel/extensions/pg_inet.rb +10 -11
- data/lib/sequel/extensions/pg_interval.rb +10 -11
- data/lib/sequel/extensions/pg_json.rb +10 -10
- data/lib/sequel/extensions/pg_json_ops.rb +3 -3
- data/lib/sequel/extensions/pg_multirange.rb +6 -11
- data/lib/sequel/extensions/pg_range.rb +9 -14
- data/lib/sequel/extensions/pg_row.rb +20 -19
- data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
- data/lib/sequel/extensions/provenance.rb +110 -0
- data/lib/sequel/extensions/round_timestamps.rb +1 -1
- data/lib/sequel/extensions/schema_caching.rb +1 -1
- data/lib/sequel/extensions/schema_dumper.rb +32 -9
- data/lib/sequel/extensions/server_block.rb +2 -1
- data/lib/sequel/extensions/set_literalizer.rb +58 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
- data/lib/sequel/extensions/temporarily_release_connection.rb +178 -0
- data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
- data/lib/sequel/model/associations.rb +38 -11
- data/lib/sequel/model/base.rb +32 -18
- data/lib/sequel/model/dataset_module.rb +3 -0
- data/lib/sequel/model/exceptions.rb +15 -3
- data/lib/sequel/plugins/auto_validations.rb +53 -15
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/column_encryption.rb +28 -7
- data/lib/sequel/plugins/composition.rb +2 -2
- data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
- data/lib/sequel/plugins/constraint_validations.rb +8 -5
- data/lib/sequel/plugins/defaults_setter.rb +16 -0
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/finder.rb +4 -2
- data/lib/sequel/plugins/input_transformer.rb +1 -1
- data/lib/sequel/plugins/list.rb +5 -2
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
- data/lib/sequel/plugins/nested_attributes.rb +4 -4
- data/lib/sequel/plugins/optimistic_locking.rb +9 -42
- data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
- data/lib/sequel/plugins/paged_operations.rb +181 -0
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
- data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
- data/lib/sequel/plugins/prepared_statements.rb +2 -1
- data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
- data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
- data/lib/sequel/plugins/rcte_tree.rb +7 -4
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +1 -1
- data/lib/sequel/plugins/static_cache.rb +38 -0
- data/lib/sequel/plugins/static_cache_cache.rb +5 -1
- data/lib/sequel/plugins/tactical_eager_loading.rb +14 -14
- data/lib/sequel/plugins/validate_associated.rb +22 -12
- data/lib/sequel/plugins/validation_helpers.rb +29 -2
- data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
- data/lib/sequel/version.rb +2 -2
- metadata +79 -6
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
#
|
|
3
|
+
# The provenance dataset extension tracks the locations of all
|
|
4
|
+
# dataset clones that resulted in the current dataset, and includes
|
|
5
|
+
# the information as a comment in the dataset's SQL. This makes it
|
|
6
|
+
# possible to see how a query was built, which can aid debugging.
|
|
7
|
+
# Example:
|
|
8
|
+
#
|
|
9
|
+
# DB[:table].
|
|
10
|
+
# select(:a).
|
|
11
|
+
# where{b > 10}.
|
|
12
|
+
# order(:c).
|
|
13
|
+
# limit(10)
|
|
14
|
+
# # SQL:
|
|
15
|
+
# # SELECT a FROM table WHERE (b > 10) ORDER BY c LIMIT 10 --
|
|
16
|
+
# # -- Dataset Provenance
|
|
17
|
+
# # -- Keys:[:from] Source:(eval at bin/sequel:257):2:in `<main>'
|
|
18
|
+
# # -- Keys:[:select] Source:(eval at bin/sequel:257):3:in `<main>'
|
|
19
|
+
# # -- Keys:[:where] Source:(eval at bin/sequel:257):4:in `<main>'
|
|
20
|
+
# # -- Keys:[:order] Source:(eval at bin/sequel:257):5:in `<main>'
|
|
21
|
+
# # -- Keys:[:limit] Source:(eval at bin/sequel:257):6:in `<main>'
|
|
22
|
+
#
|
|
23
|
+
# With the above example, the source is fairly obvious and not helpful,
|
|
24
|
+
# but in real applications, where datasets can be built from multiple
|
|
25
|
+
# files, seeing where each dataset clone was made can be helpful.
|
|
26
|
+
#
|
|
27
|
+
# The Source listed will skip locations in the Ruby standard library
|
|
28
|
+
# as well as Sequel itself. Other locations can be skipped by
|
|
29
|
+
# providing a Database :provenance_caller_ignore Regexp option:
|
|
30
|
+
#
|
|
31
|
+
# DB.opts[:provenance_caller_ignore] = /\/gems\/library_name-/
|
|
32
|
+
#
|
|
33
|
+
# Related module: Sequel::Dataset::Provenance
|
|
34
|
+
|
|
35
|
+
#
|
|
36
|
+
module Sequel
|
|
37
|
+
class Dataset
|
|
38
|
+
module Provenance
|
|
39
|
+
SEQUEL_LIB_PATH = (File.expand_path('../../..', __FILE__) + '/').freeze
|
|
40
|
+
RUBY_STDLIB = RbConfig::CONFIG["rubylibdir"]
|
|
41
|
+
INTERNAL = '<internal'
|
|
42
|
+
|
|
43
|
+
if TRUE_FREEZE
|
|
44
|
+
# Include provenance information when cloning datasets.
|
|
45
|
+
def clone(opts = nil || (return self))
|
|
46
|
+
super(provenance_opts(opts))
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
# :nocov:
|
|
50
|
+
def clone(opts = OPTS) # :nodoc:
|
|
51
|
+
super(provenance_opts(opts))
|
|
52
|
+
end
|
|
53
|
+
# :nocov:
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
%w'select insert update delete'.each do |type|
|
|
57
|
+
# Include the provenance information as a comment when preparing dataset SQL
|
|
58
|
+
define_method(:"#{type}_sql") do |*a|
|
|
59
|
+
sql = super(*a)
|
|
60
|
+
|
|
61
|
+
if provenance = @opts[:provenance]
|
|
62
|
+
comment = provenance.map do |hash|
|
|
63
|
+
" -- Keys:#{hash[:keys].inspect} Source:#{hash[:source]}".to_s.gsub(/\s+/, ' ')
|
|
64
|
+
end
|
|
65
|
+
comment << ""
|
|
66
|
+
comment.unshift " -- Dataset Provenance"
|
|
67
|
+
comment.unshift " -- "
|
|
68
|
+
comment = comment.join("\n")
|
|
69
|
+
|
|
70
|
+
if sql.frozen?
|
|
71
|
+
sql += comment
|
|
72
|
+
sql.freeze
|
|
73
|
+
elsif @opts[:append_sql] || @opts[:placeholder_literalizer]
|
|
74
|
+
sql << comment
|
|
75
|
+
else
|
|
76
|
+
sql += comment
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
sql
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
# Return a copy of opts with provenance information added.
|
|
87
|
+
def provenance_opts(opts)
|
|
88
|
+
provenance = {source: provenance_source, keys: opts.keys.freeze}.freeze
|
|
89
|
+
opts = opts.dup
|
|
90
|
+
opts[:provenance] = ((@opts[:provenance] || EMPTY_ARRAY).dup << provenance).freeze
|
|
91
|
+
opts
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Return the caller line for the provenance change. This skips
|
|
95
|
+
# Sequel itself and the standard library. Additional locations
|
|
96
|
+
# can be skipped using the :provenance_caller_ignore Dataset option.
|
|
97
|
+
def provenance_source
|
|
98
|
+
ignore = db.opts[:provenance_caller_ignore]
|
|
99
|
+
caller.find do |line|
|
|
100
|
+
!(line.start_with?(SEQUEL_LIB_PATH) ||
|
|
101
|
+
line.start_with?(RUBY_STDLIB) ||
|
|
102
|
+
line.start_with?(INTERNAL) ||
|
|
103
|
+
(ignore && line =~ ignore))
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
register_extension(:provenance, Provenance)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -88,11 +88,11 @@ module Sequel
|
|
|
88
88
|
# Note that the migration this produces does not have a down
|
|
89
89
|
# block, so you cannot reverse it.
|
|
90
90
|
def dump_foreign_key_migration(options=OPTS)
|
|
91
|
-
ts =
|
|
91
|
+
ts = _dump_tables(options)
|
|
92
92
|
<<END_MIG
|
|
93
93
|
Sequel.migration do
|
|
94
94
|
change do
|
|
95
|
-
#{ts.
|
|
95
|
+
#{ts.map{|t| dump_table_foreign_keys(t)}.reject{|x| x == ''}.join("\n\n").gsub(/^/, ' ')}
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
END_MIG
|
|
@@ -106,11 +106,11 @@ END_MIG
|
|
|
106
106
|
# set to :namespace, prepend the table name to the index name if the
|
|
107
107
|
# database does not use a global index namespace.
|
|
108
108
|
def dump_indexes_migration(options=OPTS)
|
|
109
|
-
ts =
|
|
109
|
+
ts = _dump_tables(options)
|
|
110
110
|
<<END_MIG
|
|
111
111
|
Sequel.migration do
|
|
112
112
|
change do
|
|
113
|
-
#{ts.
|
|
113
|
+
#{ts.map{|t| dump_table_indexes(t, :add_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/, ' ')}
|
|
114
114
|
end
|
|
115
115
|
end
|
|
116
116
|
END_MIG
|
|
@@ -138,7 +138,7 @@ END_MIG
|
|
|
138
138
|
options[:foreign_keys] = false
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
ts = sort_dumped_tables(
|
|
141
|
+
ts = sort_dumped_tables(_dump_tables(options), options)
|
|
142
142
|
skipped_fks = if sfk = options[:skipped_foreign_keys]
|
|
143
143
|
# Handle skipped foreign keys by adding them at the end via
|
|
144
144
|
# alter_table/add_foreign_key. Note that skipped foreign keys
|
|
@@ -166,6 +166,21 @@ END_MIG
|
|
|
166
166
|
|
|
167
167
|
private
|
|
168
168
|
|
|
169
|
+
# Handle schema option to dump tables in a different schema. Such
|
|
170
|
+
# tables must be schema qualified for this to work correctly.
|
|
171
|
+
def _dump_tables(opts)
|
|
172
|
+
if opts[:schema]
|
|
173
|
+
_literal_table_sort(tables(opts.merge(:qualify=>true)))
|
|
174
|
+
else
|
|
175
|
+
tables(opts).sort
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Sort the given table by the literalized value.
|
|
180
|
+
def _literal_table_sort(tables)
|
|
181
|
+
tables.sort_by{|s| literal(s)}
|
|
182
|
+
end
|
|
183
|
+
|
|
169
184
|
# If a database default exists and can't be converted, and we are dumping with :same_db,
|
|
170
185
|
# return a string with the inspect method modified a literal string is created if the code is evaled.
|
|
171
186
|
def column_schema_to_ruby_default_fallback(default, options)
|
|
@@ -204,12 +219,20 @@ END_MIG
|
|
|
204
219
|
if database_type == :mysql && h[:type] =~ /\Atimestamp/
|
|
205
220
|
h[:null] = true
|
|
206
221
|
end
|
|
222
|
+
if database_type == :mssql && schema[:max_length]
|
|
223
|
+
h[:size] = schema[:max_length]
|
|
224
|
+
end
|
|
207
225
|
h
|
|
208
226
|
else
|
|
209
227
|
column_schema_to_ruby_type(schema)
|
|
210
228
|
end
|
|
211
229
|
type = col_opts.delete(:type)
|
|
212
|
-
col_opts.
|
|
230
|
+
if col_opts.key?(:size) && col_opts[:size].nil?
|
|
231
|
+
col_opts.delete(:size)
|
|
232
|
+
if max_length = schema[:max_length]
|
|
233
|
+
col_opts[:size] = max_length
|
|
234
|
+
end
|
|
235
|
+
end
|
|
213
236
|
if schema[:generated]
|
|
214
237
|
if options[:same_db] && database_type == :postgres
|
|
215
238
|
col_opts[:generated_always_as] = column_schema_to_ruby_default_fallback(schema[:default], options)
|
|
@@ -352,7 +375,7 @@ END_MIG
|
|
|
352
375
|
options[:skipped_foreign_keys] = skipped_foreign_keys
|
|
353
376
|
tables
|
|
354
377
|
else
|
|
355
|
-
tables
|
|
378
|
+
tables
|
|
356
379
|
end
|
|
357
380
|
end
|
|
358
381
|
|
|
@@ -377,14 +400,14 @@ END_MIG
|
|
|
377
400
|
# outstanding foreign keys and skipping those foreign keys.
|
|
378
401
|
# The skipped foreign keys will be added at the end of the
|
|
379
402
|
# migration.
|
|
380
|
-
skip_table, skip_fks = table_fks.sort_by{|table, fks| [fks.length, table]}.first
|
|
403
|
+
skip_table, skip_fks = table_fks.sort_by{|table, fks| [fks.length, literal(table)]}.first
|
|
381
404
|
skip_fks_hash = skipped_foreign_keys[skip_table] = {}
|
|
382
405
|
skip_fks.each{|fk| skip_fks_hash[fk[:columns]] = fk}
|
|
383
406
|
this_loop << skip_table
|
|
384
407
|
end
|
|
385
408
|
|
|
386
409
|
# Add sorted tables from this loop to the final list
|
|
387
|
-
sorted_tables.concat(this_loop
|
|
410
|
+
sorted_tables.concat(_literal_table_sort(this_loop))
|
|
388
411
|
|
|
389
412
|
# Remove tables that were handled this loop
|
|
390
413
|
this_loop.each{|t| table_fks.delete(t)}
|
|
@@ -69,7 +69,8 @@ module Sequel
|
|
|
69
69
|
# Also defines the with_server method on the receiver for easy use.
|
|
70
70
|
def self.extended(db)
|
|
71
71
|
pool = db.pool
|
|
72
|
-
|
|
72
|
+
case pool.pool_type
|
|
73
|
+
when :sharded_threaded, :sharded_timed_queue
|
|
73
74
|
pool.extend(ThreadedServerBlock)
|
|
74
75
|
pool.instance_variable_set(:@default_servers, {})
|
|
75
76
|
else
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
#
|
|
3
|
+
# The set_literalizer extension allows for using Set instances in many of the
|
|
4
|
+
# same places that you would use Array instances:
|
|
5
|
+
#
|
|
6
|
+
# DB[:table].where(column: Set.new([1, 2, 3]))
|
|
7
|
+
# # SELECT FROM table WHERE (column IN (1, 2, 3))
|
|
8
|
+
#
|
|
9
|
+
# To load the extension into all datasets created from a given Database:
|
|
10
|
+
#
|
|
11
|
+
# DB.extension :set_literalizer
|
|
12
|
+
#
|
|
13
|
+
# Related module: Sequel::Dataset::SetLiteralizer
|
|
14
|
+
|
|
15
|
+
require 'set'
|
|
16
|
+
|
|
17
|
+
module Sequel
|
|
18
|
+
class Dataset
|
|
19
|
+
module SetLiteralizer
|
|
20
|
+
# Try to generate the same SQL for Set instances used in datasets
|
|
21
|
+
# that would be used for equivalent Array instances.
|
|
22
|
+
def complex_expression_sql_append(sql, op, args)
|
|
23
|
+
# Array instances are treated specially by
|
|
24
|
+
# Sequel::SQL::BooleanExpression.from_value_pairs. That cannot
|
|
25
|
+
# be modified by a dataset extension, so this tries to convert
|
|
26
|
+
# the complex expression values generated by default to what would
|
|
27
|
+
# be the complex expression values used for the equivalent array.
|
|
28
|
+
case op
|
|
29
|
+
when :'=', :'!='
|
|
30
|
+
if (set = args[1]).is_a?(Set)
|
|
31
|
+
op = op == :'=' ? :IN : :'NOT IN'
|
|
32
|
+
col = args[0]
|
|
33
|
+
array = set.to_a
|
|
34
|
+
if Sequel.condition_specifier?(array) && col.is_a?(Array)
|
|
35
|
+
array = Sequel.value_list(array)
|
|
36
|
+
end
|
|
37
|
+
args = [col, array]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Literalize Set instances by converting the set to array.
|
|
47
|
+
def literal_other_append(sql, v)
|
|
48
|
+
if Set === v
|
|
49
|
+
literal_append(sql, v.to_a)
|
|
50
|
+
else
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
register_extension(:set_literalizer, SetLiteralizer)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -2,27 +2,34 @@
|
|
|
2
2
|
#
|
|
3
3
|
# The sqlite_json_ops extension adds support to Sequel's DSL to make
|
|
4
4
|
# it easier to call SQLite JSON functions and operators (added
|
|
5
|
-
# first in SQLite 3.38.0).
|
|
5
|
+
# first in SQLite 3.38.0). It also supports the SQLite JSONB functions
|
|
6
|
+
# added in SQLite 3.45.0.
|
|
6
7
|
#
|
|
7
8
|
# To load the extension:
|
|
8
9
|
#
|
|
9
10
|
# Sequel.extension :sqlite_json_ops
|
|
10
11
|
#
|
|
11
|
-
# This extension works by calling methods on Sequel::SQLite::JSONOp
|
|
12
|
-
# which you can create
|
|
12
|
+
# This extension works by calling methods on Sequel::SQLite::JSONOp and
|
|
13
|
+
# Sequel::SQLite::JSONBOp objects, which you can create using
|
|
14
|
+
# Sequel.sqlite_json_op and Sequel.sqlite_jsonb_op:
|
|
13
15
|
#
|
|
14
16
|
# j = Sequel.sqlite_json_op(:json_column)
|
|
17
|
+
# jb = Sequel.sqlite_jsonb_op(:jsonb_column)
|
|
15
18
|
#
|
|
16
|
-
# Also, on most Sequel expression objects, you can call the sqlite_json_op
|
|
17
|
-
# to create a Sequel::SQLite::JSONOp
|
|
19
|
+
# Also, on most Sequel expression objects, you can call the sqlite_json_op or
|
|
20
|
+
# sqlite_jsonb_op method to create a Sequel::SQLite::JSONOp or
|
|
21
|
+
# Sequel::SQLite::JSONBOp object:
|
|
18
22
|
#
|
|
19
23
|
# j = Sequel[:json_column].sqlite_json_op
|
|
24
|
+
# jb = Sequel[:jsonb_column].sqlite_jsonb_op
|
|
20
25
|
#
|
|
21
26
|
# If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
|
|
22
27
|
# or you have loaded the core_refinements extension
|
|
23
28
|
# and have activated refinements for the file, you can also use Symbol#sqlite_json_op:
|
|
29
|
+
# or Symbol#sqlite_jsonb_op:
|
|
24
30
|
#
|
|
25
31
|
# j = :json_column.sqlite_json_op
|
|
32
|
+
# jb = :json_column.sqlite_jsonb_op
|
|
26
33
|
#
|
|
27
34
|
# The following methods are available for Sequel::SQLite::JSONOp instances:
|
|
28
35
|
#
|
|
@@ -30,11 +37,13 @@
|
|
|
30
37
|
# j.get(1) # (json_column ->> 1)
|
|
31
38
|
# j.get_text(1) # (json_column -> 1)
|
|
32
39
|
# j.extract('$.a') # json_extract(json_column, '$.a')
|
|
40
|
+
# jb.extract('$.a') # jsonb_extract(jsonb_column, '$.a')
|
|
33
41
|
#
|
|
34
42
|
# j.array_length # json_array_length(json_column)
|
|
35
43
|
# j.type # json_type(json_column)
|
|
36
44
|
# j.valid # json_valid(json_column)
|
|
37
|
-
#
|
|
45
|
+
# jb.json # json(jsonb_column)
|
|
46
|
+
# j.jsonb # jsonb(json_column)
|
|
38
47
|
#
|
|
39
48
|
# j.insert('$.a', 1) # json_insert(json_column, '$.a', 1)
|
|
40
49
|
# j.set('$.a', 1) # json_set(json_column, '$.a', 1)
|
|
@@ -42,22 +51,30 @@
|
|
|
42
51
|
# j.remove('$.a') # json_remove(json_column, '$.a')
|
|
43
52
|
# j.patch('{"a":2}') # json_patch(json_column, '{"a":2}')
|
|
44
53
|
#
|
|
54
|
+
# jb.insert('$.a', 1) # jsonb_insert(jsonb_column, '$.a', 1)
|
|
55
|
+
# jb.set('$.a', 1) # jsonb_set(jsonb_column, '$.a', 1)
|
|
56
|
+
# jb.replace('$.a', 1) # jsonb_replace(jsonb_column, '$.a', 1)
|
|
57
|
+
# jb.remove('$.a') # jsonb_remove(jsonb_column, '$.a')
|
|
58
|
+
# jb.patch('{"a":2}') # jsonb_patch(jsonb_column, '{"a":2}')
|
|
59
|
+
#
|
|
45
60
|
# j.each # json_each(json_column)
|
|
46
61
|
# j.tree # json_tree(json_column)
|
|
47
62
|
#
|
|
48
|
-
# Related modules: Sequel::SQLite::JSONOp
|
|
63
|
+
# Related modules: Sequel::SQLite::JSONBaseOp, Sequel::SQLite::JSONOp,
|
|
64
|
+
# Sequel::SQLite::JSONBOp
|
|
49
65
|
|
|
50
66
|
#
|
|
51
67
|
module Sequel
|
|
52
68
|
module SQLite
|
|
53
|
-
#
|
|
54
|
-
# defines methods that
|
|
55
|
-
# SQLite json operators and functions.
|
|
69
|
+
# JSONBaseOp is an abstract base wrapper class for a object that
|
|
70
|
+
# defines methods that return Sequel expression objects representing
|
|
71
|
+
# SQLite json operators and functions. It is subclassed by both
|
|
72
|
+
# JSONOp and JSONBOp for json and jsonb specific behavior.
|
|
56
73
|
#
|
|
57
74
|
# In the method documentation examples, assume that:
|
|
58
75
|
#
|
|
59
76
|
# json_op = Sequel.sqlite_json_op(:json)
|
|
60
|
-
class
|
|
77
|
+
class JSONBaseOp < Sequel::SQL::Wrapper
|
|
61
78
|
GET = ["(".freeze, " ->> ".freeze, ")".freeze].freeze
|
|
62
79
|
private_constant :GET
|
|
63
80
|
|
|
@@ -82,7 +99,7 @@ module Sequel
|
|
|
82
99
|
# json_op.array_length # json_array_length(json)
|
|
83
100
|
# json_op.array_length('$[1]') # json_array_length(json, '$[1]')
|
|
84
101
|
def array_length(*args)
|
|
85
|
-
Sequel::SQL::NumericExpression.new(:NOOP,
|
|
102
|
+
Sequel::SQL::NumericExpression.new(:NOOP, SQL::Function.new(:json_array_length, self, *args))
|
|
86
103
|
end
|
|
87
104
|
|
|
88
105
|
# Returns an expression for a set of information extracted from the top-level
|
|
@@ -92,7 +109,7 @@ module Sequel
|
|
|
92
109
|
# json_op.each # json_each(json)
|
|
93
110
|
# json_op.each('$.a') # json_each(json, '$.a')
|
|
94
111
|
def each(*args)
|
|
95
|
-
|
|
112
|
+
SQL::Function.new(:json_each, self, *args)
|
|
96
113
|
end
|
|
97
114
|
|
|
98
115
|
# Returns an expression for the JSON array element or object field at the specified
|
|
@@ -129,10 +146,17 @@ module Sequel
|
|
|
129
146
|
#
|
|
130
147
|
# json_op.json # json(json)
|
|
131
148
|
def json
|
|
132
|
-
|
|
149
|
+
JSONOp.new(SQL::Function.new(:json, self))
|
|
133
150
|
end
|
|
134
151
|
alias minify json
|
|
135
152
|
|
|
153
|
+
# Returns the JSONB format of the JSON.
|
|
154
|
+
#
|
|
155
|
+
# json_op.jsonb # jsonb(json)
|
|
156
|
+
def jsonb
|
|
157
|
+
JSONBOp.new(SQL::Function.new(:jsonb, self))
|
|
158
|
+
end
|
|
159
|
+
|
|
136
160
|
# Returns an expression for updating the JSON object using the RFC 7396 MergePatch algorithm
|
|
137
161
|
#
|
|
138
162
|
# json_op.patch('{"a": 1, "b": null}') # json_patch(json, '{"a": 1, "b": null}')
|
|
@@ -172,7 +196,7 @@ module Sequel
|
|
|
172
196
|
# json_op.tree # json_tree(json)
|
|
173
197
|
# json_op.tree('$.a') # json_tree(json, '$.a')
|
|
174
198
|
def tree(*args)
|
|
175
|
-
|
|
199
|
+
SQL::Function.new(:json_tree, self, *args)
|
|
176
200
|
end
|
|
177
201
|
|
|
178
202
|
# Returns an expression for the type of the JSON value or the JSON value at the given path.
|
|
@@ -180,13 +204,13 @@ module Sequel
|
|
|
180
204
|
# json_op.type # json_type(json)
|
|
181
205
|
# json_op.type('$[1]') # json_type(json, '$[1]')
|
|
182
206
|
def type(*args)
|
|
183
|
-
Sequel::SQL::StringExpression.new(:NOOP,
|
|
207
|
+
Sequel::SQL::StringExpression.new(:NOOP, SQL::Function.new(:json_type, self, *args))
|
|
184
208
|
end
|
|
185
209
|
alias typeof type
|
|
186
210
|
|
|
187
211
|
# Returns a boolean expression for whether the JSON is valid or not.
|
|
188
212
|
def valid
|
|
189
|
-
Sequel::SQL::BooleanExpression.new(:NOOP,
|
|
213
|
+
Sequel::SQL::BooleanExpression.new(:NOOP, SQL::Function.new(:json_valid, self))
|
|
190
214
|
end
|
|
191
215
|
|
|
192
216
|
private
|
|
@@ -198,7 +222,7 @@ module Sequel
|
|
|
198
222
|
|
|
199
223
|
# Internals of the methods that return functions prefixed with +json_+.
|
|
200
224
|
def function(name, *args)
|
|
201
|
-
SQL::Function.new("
|
|
225
|
+
SQL::Function.new("#{function_prefix}_#{name}", self, *args)
|
|
202
226
|
end
|
|
203
227
|
|
|
204
228
|
# Internals of the methods that return functions prefixed with +json_+, that
|
|
@@ -208,12 +232,36 @@ module Sequel
|
|
|
208
232
|
end
|
|
209
233
|
end
|
|
210
234
|
|
|
235
|
+
# JSONOp is used for SQLite json-specific functions and operators.
|
|
236
|
+
class JSONOp < JSONBaseOp
|
|
237
|
+
private
|
|
238
|
+
|
|
239
|
+
def function_prefix
|
|
240
|
+
"json"
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# JSONOp is used for SQLite jsonb-specific functions and operators.
|
|
245
|
+
class JSONBOp < JSONBaseOp
|
|
246
|
+
private
|
|
247
|
+
|
|
248
|
+
def function_prefix
|
|
249
|
+
"jsonb"
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
211
253
|
module JSONOpMethods
|
|
212
254
|
# Wrap the receiver in an JSONOp so you can easily use the SQLite
|
|
213
255
|
# json functions and operators with it.
|
|
214
256
|
def sqlite_json_op
|
|
215
257
|
JSONOp.new(self)
|
|
216
258
|
end
|
|
259
|
+
|
|
260
|
+
# Wrap the receiver in an JSONBOp so you can easily use the SQLite
|
|
261
|
+
# jsonb functions and operators with it.
|
|
262
|
+
def sqlite_jsonb_op
|
|
263
|
+
JSONBOp.new(self)
|
|
264
|
+
end
|
|
217
265
|
end
|
|
218
266
|
end
|
|
219
267
|
|
|
@@ -227,6 +275,16 @@ module Sequel
|
|
|
227
275
|
SQLite::JSONOp.new(v)
|
|
228
276
|
end
|
|
229
277
|
end
|
|
278
|
+
|
|
279
|
+
# Return the object wrapped in an SQLite::JSONBOp.
|
|
280
|
+
def sqlite_jsonb_op(v)
|
|
281
|
+
case v
|
|
282
|
+
when SQLite::JSONBOp
|
|
283
|
+
v
|
|
284
|
+
else
|
|
285
|
+
SQLite::JSONBOp.new(v)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
230
288
|
end
|
|
231
289
|
|
|
232
290
|
class SQL::GenericExpression
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
#
|
|
3
|
+
# The temporarily_release_connection extension adds support for temporarily
|
|
4
|
+
# releasing a checked out connection back to the connection pool. It is
|
|
5
|
+
# designed for use in multithreaded transactional integration tests, allowing
|
|
6
|
+
# a connection to start a transaction in one thread, but be temporarily
|
|
7
|
+
# released back to the connection pool, so it can be operated on safely
|
|
8
|
+
# by multiple threads inside a block. For example, the main thread could be
|
|
9
|
+
# running tests that send web requests, and a separate thread running a web
|
|
10
|
+
# server that is responding to those requests, and the same connection and
|
|
11
|
+
# transaction would be used for both.
|
|
12
|
+
#
|
|
13
|
+
# To load the extension into the database:
|
|
14
|
+
#
|
|
15
|
+
# DB.extension :temporarily_release_connection
|
|
16
|
+
#
|
|
17
|
+
# After the extension is loaded, call the +temporarily_release_connection+
|
|
18
|
+
# method with the connection object to temporarily release the connection
|
|
19
|
+
# back to the pool. Example:
|
|
20
|
+
#
|
|
21
|
+
# DB.transaction(rollback: :always, auto_savepoint: true) do |conn|
|
|
22
|
+
# DB.temporarily_release_connection(conn) do
|
|
23
|
+
# # Other threads can operate on connection safely inside the transaction
|
|
24
|
+
# yield
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# For sharded connection pools, the second argument to +temporarily_release_connection+
|
|
29
|
+
# is respected, and specifies the server on which to temporarily release the connection.
|
|
30
|
+
#
|
|
31
|
+
# The temporarily_release_connection extension is only supported with the
|
|
32
|
+
# threaded and timed_queue connection pools that ship with Sequel (and the sharded
|
|
33
|
+
# versions of each). To make sure that same connection object can be reacquired, it
|
|
34
|
+
# is only supported if the maximum connection pool size is 1, so set the Database
|
|
35
|
+
# :max_connections option to 1 if you plan to use this extension.
|
|
36
|
+
#
|
|
37
|
+
# If the +temporarily_release_connection+ method cannot reacquire the same connection
|
|
38
|
+
# it released to the pool, it will raise a Sequel::UnableToReacquireConnectionError
|
|
39
|
+
# exception. This should only happen if the connection has been disconnected
|
|
40
|
+
# while it was temporarily released. If this error is raised, Database#transaction
|
|
41
|
+
# will not rollback the transaction, since the connection object is likely no longer
|
|
42
|
+
# valid, and on poorly written database drivers, that could cause the process to crash.
|
|
43
|
+
#
|
|
44
|
+
# Related modules: Sequel::TemporarilyReleaseConnection,
|
|
45
|
+
# Sequel::UnableToReacquireConnectionError
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
module Sequel
|
|
49
|
+
# Error class raised if the connection pool does not provide the same connection
|
|
50
|
+
# object when checking a temporarily released connection out.
|
|
51
|
+
class UnableToReacquireConnectionError < Error
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
module TemporarilyReleaseConnection
|
|
55
|
+
module DatabaseMethods
|
|
56
|
+
# Temporarily release the connection back to the connection pool for the
|
|
57
|
+
# duration of the block.
|
|
58
|
+
def temporarily_release_connection(conn, server=:default, &block)
|
|
59
|
+
pool.temporarily_release_connection(conn, server, &block)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# Do nothing if UnableToReacquireConnectionError is raised, as it is
|
|
65
|
+
# likely the connection is not in a usable state.
|
|
66
|
+
def rollback_transaction(conn, opts)
|
|
67
|
+
return if UnableToReacquireConnectionError === $!
|
|
68
|
+
super
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
module PoolMethods
|
|
73
|
+
# Temporarily release a currently checked out connection, then yield to the block. Reacquire the same
|
|
74
|
+
# connection upon the exit of the block.
|
|
75
|
+
def temporarily_release_connection(conn, server)
|
|
76
|
+
t = Sequel.current
|
|
77
|
+
raise Error, "connection not currently checked out" unless conn.equal?(trc_owned_connection(t, server))
|
|
78
|
+
|
|
79
|
+
begin
|
|
80
|
+
trc_release(t, conn, server)
|
|
81
|
+
yield
|
|
82
|
+
ensure
|
|
83
|
+
c = trc_acquire(t, server)
|
|
84
|
+
unless conn.equal?(c)
|
|
85
|
+
raise UnableToReacquireConnectionError, "reacquired connection not the same as initial connection"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
module TimedQueue
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def trc_owned_connection(t, server)
|
|
95
|
+
owned_connection(t)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def trc_release(t, conn, server)
|
|
99
|
+
release(t)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def trc_acquire(t, server)
|
|
103
|
+
acquire(t)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
module ShardedTimedQueue
|
|
108
|
+
# Normalize the server name for sharded connection pools
|
|
109
|
+
def temporarily_release_connection(conn, server)
|
|
110
|
+
server = pick_server(server)
|
|
111
|
+
super
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def trc_owned_connection(t, server)
|
|
117
|
+
owned_connection(t, server)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def trc_release(t, conn, server)
|
|
121
|
+
release(t, conn, server)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def trc_acquire(t, server)
|
|
125
|
+
acquire(t, server)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
module ThreadedBase
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def trc_release(t, conn, server)
|
|
133
|
+
sync{super}
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
module Threaded
|
|
138
|
+
include TimedQueue
|
|
139
|
+
include ThreadedBase
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
module ShardedThreaded
|
|
143
|
+
include ShardedTimedQueue
|
|
144
|
+
include ThreadedBase
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
trc = TemporarilyReleaseConnection
|
|
149
|
+
trc_map = {
|
|
150
|
+
:threaded => trc::Threaded,
|
|
151
|
+
:sharded_threaded => trc::ShardedThreaded,
|
|
152
|
+
:timed_queue => trc::TimedQueue,
|
|
153
|
+
:sharded_timed_queue => trc::ShardedTimedQueue,
|
|
154
|
+
}.freeze
|
|
155
|
+
|
|
156
|
+
Database.register_extension(:temporarily_release_connection) do |db|
|
|
157
|
+
unless pool_mod = trc_map[db.pool.pool_type]
|
|
158
|
+
raise(Error, "temporarily_release_connection extension not supported for connection pool type #{db.pool.pool_type}")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
case db.pool.pool_type
|
|
162
|
+
when :threaded, :sharded_threaded
|
|
163
|
+
if db.opts[:connection_handling] == :disconnect
|
|
164
|
+
raise Error, "temporarily_release_connection extension not supported with connection_handling: :disconnect option"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
unless db.pool.max_size == 1
|
|
169
|
+
raise Error, "temporarily_release_connection extension not supported unless :max_connections option is 1"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
db.extend(trc::DatabaseMethods)
|
|
173
|
+
db.pool.extend(trc::PoolMethods)
|
|
174
|
+
db.pool.extend(pool_mod)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
private_constant :TemporarilyReleaseConnection
|
|
178
|
+
end
|