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