sequel 5.58.0 → 5.78.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 +288 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +24 -23
- data/bin/sequel +11 -3
- data/doc/advanced_associations.rdoc +16 -14
- data/doc/association_basics.rdoc +53 -17
- data/doc/cheat_sheet.rdoc +3 -3
- 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 +20 -12
- data/doc/postgresql.rdoc +8 -8
- data/doc/querying.rdoc +1 -1
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.60.0.txt +22 -0
- 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/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 +62 -16
- 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 +80 -1
- data/lib/sequel/adapters/shared/oracle.rb +17 -7
- data/lib/sequel/adapters/shared/postgres.rb +494 -164
- data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
- data/lib/sequel/adapters/shared/sqlite.rb +40 -4
- 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 +2 -1
- data/lib/sequel/database/schema_methods.rb +13 -3
- data/lib/sequel/database/transactions.rb +6 -0
- data/lib/sequel/dataset/actions.rb +60 -13
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
- data/lib/sequel/dataset/features.rb +15 -1
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/query.rb +62 -37
- 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/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 +36 -8
- data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/is_distinct_from.rb +3 -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 +52 -0
- 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/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/symbol_aref.rb +2 -0
- data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
- data/lib/sequel/model/associations.rb +50 -11
- data/lib/sequel/model/base.rb +45 -21
- 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 +27 -6
- 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/list.rb +8 -3
- 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/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +5 -5
- 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 +21 -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 +1 -1
- metadata +76 -6
@@ -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,78 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The transaction_connection_validator extension automatically
|
4
|
+
# retries a transaction on a connection if an disconnect error
|
5
|
+
# is raised when sending the statement to begin a new
|
6
|
+
# transaction, as long as the user has not already checked out
|
7
|
+
# a connection. This is safe to do because no other queries
|
8
|
+
# have been issued on the connection, and no user-level code
|
9
|
+
# is run before retrying.
|
10
|
+
#
|
11
|
+
# This approach to connection validation can be significantly
|
12
|
+
# lower overhead than the connection_validator extension,
|
13
|
+
# though it does not handle all cases handled by the
|
14
|
+
# connection_validator extension. However, it performs the
|
15
|
+
# validation checks on every new transaction, so it will
|
16
|
+
# automatically handle disconnected connections in some cases
|
17
|
+
# where the connection_validator extension will not by default
|
18
|
+
# (as the connection_validator extension only checks
|
19
|
+
# connections if they have not been used in the last hour by
|
20
|
+
# default).
|
21
|
+
#
|
22
|
+
# Related module: Sequel::TransactionConnectionValidator
|
23
|
+
|
24
|
+
#
|
25
|
+
module Sequel
|
26
|
+
module TransactionConnectionValidator
|
27
|
+
class DisconnectRetry < DatabaseDisconnectError
|
28
|
+
# The connection that raised the disconnect error
|
29
|
+
attr_accessor :connection
|
30
|
+
|
31
|
+
# The underlying disconnect error, in case it needs to be reraised.
|
32
|
+
attr_accessor :database_error
|
33
|
+
end
|
34
|
+
|
35
|
+
# Rescue disconnect errors raised when beginning a new transaction. If there
|
36
|
+
# is a disconnnect error, it should be safe to retry the transaction using a
|
37
|
+
# new connection, as we haven't yielded control to the user yet.
|
38
|
+
def transaction(opts=OPTS)
|
39
|
+
super
|
40
|
+
rescue DisconnectRetry => e
|
41
|
+
if synchronize(opts[:server]){|conn| conn.equal?(e.connection)}
|
42
|
+
# If retrying would use the same connection, that means the
|
43
|
+
# connection was not removed from the pool, which means the caller has
|
44
|
+
# already checked out the connection, and retrying will not be successful.
|
45
|
+
# In this case, we can only reraise the exception.
|
46
|
+
raise e.database_error
|
47
|
+
end
|
48
|
+
|
49
|
+
num_retries ||= 0
|
50
|
+
num_retries += 1
|
51
|
+
retry if num_retries < 5
|
52
|
+
|
53
|
+
raise e.database_error
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Reraise disconnect errors as DisconnectRetry so they can be retried.
|
59
|
+
def begin_new_transaction(conn, opts)
|
60
|
+
super
|
61
|
+
rescue Sequel::DatabaseDisconnectError, *database_error_classes => e
|
62
|
+
if e.is_a?(Sequel::DatabaseDisconnectError) || disconnect_error?(e, OPTS)
|
63
|
+
exception = DisconnectRetry.new(e.message)
|
64
|
+
exception.set_backtrace([])
|
65
|
+
exception.connection = conn
|
66
|
+
unless e.is_a?(Sequel::DatabaseError)
|
67
|
+
e = Sequel.convert_exception_class(e, database_error_class(e, OPTS))
|
68
|
+
end
|
69
|
+
exception.database_error = e
|
70
|
+
raise exception
|
71
|
+
end
|
72
|
+
|
73
|
+
raise
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
Database.register_extension(:transaction_connection_validator, TransactionConnectionValidator)
|
78
|
+
end
|
@@ -276,7 +276,7 @@ module Sequel
|
|
276
276
|
|
277
277
|
if eo[:no_results]
|
278
278
|
no_results = true
|
279
|
-
elsif eo[:eager_block] || eo[:loader] == false
|
279
|
+
elsif eo[:eager_block] || eo[:loader] == false || !use_placeholder_loader?
|
280
280
|
ds = eager_loading_dataset(eo)
|
281
281
|
|
282
282
|
strategy = ds.opts[:eager_limit_strategy] || strategy
|
@@ -299,13 +299,28 @@ module Sequel
|
|
299
299
|
strategy = :ruby if strategy == :correlated_subquery
|
300
300
|
strategy = nil if strategy == :ruby && assign_singular?
|
301
301
|
objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
|
302
|
+
|
303
|
+
if strategy == :window_function
|
304
|
+
delete_rn = ds.row_number_column
|
305
|
+
objects.each{|obj| obj.values.delete(delete_rn)}
|
306
|
+
end
|
302
307
|
elsif strategy == :union
|
303
308
|
objects = []
|
304
309
|
ds = associated_dataset
|
305
310
|
loader = union_eager_loader
|
306
311
|
joiner = " UNION ALL "
|
307
312
|
ids.each_slice(subqueries_per_union).each do |slice|
|
308
|
-
|
313
|
+
sql = loader.send(:sql_origin)
|
314
|
+
join = false
|
315
|
+
slice.each do |k|
|
316
|
+
if join
|
317
|
+
sql << joiner
|
318
|
+
else
|
319
|
+
join = true
|
320
|
+
end
|
321
|
+
loader.append_sql(sql, *k)
|
322
|
+
end
|
323
|
+
objects.concat(ds.with_sql(sql).to_a)
|
309
324
|
end
|
310
325
|
ds = ds.eager(cascade) if cascade
|
311
326
|
ds.send(:post_load, objects)
|
@@ -441,7 +456,7 @@ module Sequel
|
|
441
456
|
def placeholder_loader
|
442
457
|
if use_placeholder_loader?
|
443
458
|
cached_fetch(:placeholder_loader) do
|
444
|
-
|
459
|
+
associated_dataset.placeholder_literalizer_loader do |pl, ds|
|
445
460
|
ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
|
446
461
|
if self[:block]
|
447
462
|
ds = self[:block].call(ds)
|
@@ -743,8 +758,8 @@ module Sequel
|
|
743
758
|
# A placeholder literalizer used to speed up eager loading.
|
744
759
|
def placeholder_eager_loader
|
745
760
|
cached_fetch(:placeholder_eager_loader) do
|
746
|
-
|
747
|
-
apply_eager_limit_strategy(
|
761
|
+
eager_loading_dataset.placeholder_literalizer_loader do |pl, ds|
|
762
|
+
apply_eager_limit_strategy(ds.where(predicate_key=>pl.arg), eager_limit_strategy)
|
748
763
|
end
|
749
764
|
end
|
750
765
|
end
|
@@ -803,7 +818,7 @@ module Sequel
|
|
803
818
|
# loading a limited association.
|
804
819
|
def union_eager_loader
|
805
820
|
cached_fetch(:union_eager_loader) do
|
806
|
-
|
821
|
+
associated_dataset.placeholder_literalizer_loader do |pl, ds|
|
807
822
|
ds = self[:eager_block].call(ds) if self[:eager_block]
|
808
823
|
keys = predicate_keys
|
809
824
|
ds = ds.where(keys.map{pl.arg}.zip(keys))
|
@@ -817,7 +832,7 @@ module Sequel
|
|
817
832
|
|
818
833
|
# Whether the placeholder loader can be used to load the association.
|
819
834
|
def use_placeholder_loader?
|
820
|
-
self[:use_placeholder_loader]
|
835
|
+
self[:use_placeholder_loader] && _associated_dataset.supports_placeholder_literalizer?
|
821
836
|
end
|
822
837
|
end
|
823
838
|
|
@@ -1717,6 +1732,10 @@ module Sequel
|
|
1717
1732
|
# :graph_select :: A column or array of columns to select from the associated table
|
1718
1733
|
# when eagerly loading the association via +eager_graph+. Defaults to all
|
1719
1734
|
# columns in the associated table.
|
1735
|
+
# :graph_use_association_block :: Makes eager_graph consider the association block. Without this, eager_graph
|
1736
|
+
# ignores the bock and only use the :graph_* options.
|
1737
|
+
# :instance_specific :: Marks the association as instance specific. Should be used if the association block
|
1738
|
+
# uses instance specific state, or transient state (accessing current date/time, etc.).
|
1720
1739
|
# :limit :: Limit the number of records to the provided value. Use
|
1721
1740
|
# an array with two elements for the value to specify a
|
1722
1741
|
# limit (first element) and an offset (second element).
|
@@ -1856,6 +1875,16 @@ module Sequel
|
|
1856
1875
|
# in certain places to disable optimizations.
|
1857
1876
|
opts[:instance_specific] = _association_instance_specific_default(name)
|
1858
1877
|
end
|
1878
|
+
if (orig_opts[:instance_specific] || orig_opts[:dataset]) && !opts.has_key?(:allow_eager) && !opts[:eager_loader]
|
1879
|
+
# For associations explicitly marked as instance specific, or that use the
|
1880
|
+
# :dataset option, where :allow_eager is not set, and no :eager_loader is
|
1881
|
+
# provided, disallow eager loading. In these cases, eager loading is
|
1882
|
+
# unlikely to work. This is not done for implicit setting of :instance_specific,
|
1883
|
+
# because implicit use is done by default for all associations with blocks,
|
1884
|
+
# and the vast majority of associations with blocks use the block for filtering
|
1885
|
+
# in a manner compatible with eager loading.
|
1886
|
+
opts[:allow_eager] = false
|
1887
|
+
end
|
1859
1888
|
opts = assoc_class.new.merge!(opts)
|
1860
1889
|
|
1861
1890
|
if opts[:clone] && !opts.cloneable?(cloned_assoc)
|
@@ -2444,6 +2473,9 @@ module Sequel
|
|
2444
2473
|
# Return dataset to graph into given the association reflection, applying the :callback option if set.
|
2445
2474
|
def eager_graph_dataset(opts, eager_options)
|
2446
2475
|
ds = opts.associated_class.dataset
|
2476
|
+
if opts[:graph_use_association_block] && (b = opts[:block])
|
2477
|
+
ds = b.call(ds)
|
2478
|
+
end
|
2447
2479
|
if cb = eager_options[:callback]
|
2448
2480
|
ds = cb.call(ds)
|
2449
2481
|
end
|
@@ -3355,8 +3387,15 @@ module Sequel
|
|
3355
3387
|
local_opts = ds.opts[:eager_graph][:local]
|
3356
3388
|
limit_strategy = r.eager_graph_limit_strategy(local_opts[:limit_strategy])
|
3357
3389
|
|
3358
|
-
|
3359
|
-
|
3390
|
+
# SEQUEL6: remove and integrate the auto_restrict_eager_graph plugin
|
3391
|
+
if !r[:orig_opts].has_key?(:graph_conditions) && !r[:orig_opts].has_key?(:graph_only_conditions) && !r.has_key?(:graph_block) && !r[:allow_eager_graph]
|
3392
|
+
if r[:conditions] && !Sequel.condition_specifier?(r[:conditions])
|
3393
|
+
raise Error, "Cannot eager_graph association when :conditions specified and not a hash or an array of pairs. Specify :graph_conditions, :graph_only_conditions, or :graph_block for the association. Model: #{r[:model]}, association: #{r[:name]}"
|
3394
|
+
end
|
3395
|
+
|
3396
|
+
if r[:block] && !r[:graph_use_association_block]
|
3397
|
+
warn "eager_graph used for association when association given a block without graph options. The block is ignored in this case. This will result in an exception starting in Sequel 6. Model: #{r[:model]}, association: #{r[:name]}"
|
3398
|
+
end
|
3360
3399
|
end
|
3361
3400
|
|
3362
3401
|
ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>join_type || local_opts[:join_type], :join_only=>local_opts[:join_only], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
|
@@ -3535,11 +3574,11 @@ module Sequel
|
|
3535
3574
|
end
|
3536
3575
|
|
3537
3576
|
# Eagerly load all specified associations.
|
3538
|
-
def eager_load(a, eager_assoc=@opts[:eager])
|
3577
|
+
def eager_load(a, eager_assoc=@opts[:eager], m=model)
|
3539
3578
|
return if a.empty?
|
3540
3579
|
|
3541
3580
|
# Reflections for all associations to eager load
|
3542
|
-
reflections = eager_assoc.keys.map{|assoc|
|
3581
|
+
reflections = eager_assoc.keys.map{|assoc| m.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
|
3543
3582
|
|
3544
3583
|
perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
|
3545
3584
|
|