sequel 5.45.0 → 5.77.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 +434 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +59 -27
- data/bin/sequel +11 -3
- data/doc/advanced_associations.rdoc +16 -14
- data/doc/association_basics.rdoc +119 -24
- data/doc/cheat_sheet.rdoc +11 -3
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +27 -6
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +28 -12
- data/doc/postgresql.rdoc +16 -8
- data/doc/querying.rdoc +5 -3
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/release_notes/5.55.0.txt +21 -0
- data/doc/release_notes/5.56.0.txt +51 -0
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- 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/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sharding.rdoc +3 -1
- data/doc/sql.rdoc +27 -15
- data/doc/testing.rdoc +23 -13
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +1 -1
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +3 -3
- data/lib/sequel/adapters/jdbc/derby.rb +8 -0
- data/lib/sequel/adapters/jdbc/h2.rb +63 -10
- data/lib/sequel/adapters/jdbc/hsqldb.rb +8 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
- data/lib/sequel/adapters/jdbc.rb +24 -22
- data/lib/sequel/adapters/mysql.rb +92 -67
- data/lib/sequel/adapters/mysql2.rb +56 -51
- data/lib/sequel/adapters/odbc/mssql.rb +1 -1
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +4 -3
- data/lib/sequel/adapters/postgres.rb +89 -45
- data/lib/sequel/adapters/shared/access.rb +11 -1
- data/lib/sequel/adapters/shared/db2.rb +42 -0
- data/lib/sequel/adapters/shared/mssql.rb +91 -10
- data/lib/sequel/adapters/shared/mysql.rb +78 -3
- data/lib/sequel/adapters/shared/oracle.rb +86 -7
- data/lib/sequel/adapters/shared/postgres.rb +576 -171
- data/lib/sequel/adapters/shared/sqlanywhere.rb +21 -5
- data/lib/sequel/adapters/shared/sqlite.rb +92 -8
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +99 -18
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/trilogy.rb +117 -0
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- 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/single.rb +6 -8
- 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/core.rb +17 -18
- data/lib/sequel/database/connecting.rb +27 -3
- data/lib/sequel/database/dataset.rb +16 -6
- data/lib/sequel/database/misc.rb +70 -14
- data/lib/sequel/database/query.rb +73 -2
- data/lib/sequel/database/schema_generator.rb +11 -6
- data/lib/sequel/database/schema_methods.rb +23 -4
- data/lib/sequel/database/transactions.rb +6 -0
- data/lib/sequel/dataset/actions.rb +111 -15
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
- data/lib/sequel/dataset/features.rb +20 -1
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/query.rb +170 -41
- data/lib/sequel/dataset/sql.rb +190 -71
- data/lib/sequel/dataset.rb +4 -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 +14 -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/core_refinements.rb +36 -11
- data/lib/sequel/extensions/date_arithmetic.rb +36 -8
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +11 -10
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/inflector.rb +1 -1
- data/lib/sequel/extensions/is_distinct_from.rb +141 -0
- data/lib/sequel/extensions/looser_typecasting.rb +3 -0
- data/lib/sequel/extensions/migration.rb +57 -15
- data/lib/sequel/extensions/named_timezones.rb +22 -6
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +33 -4
- data/lib/sequel/extensions/pg_array_ops.rb +2 -2
- 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 +39 -28
- data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
- data/lib/sequel/extensions/pg_hstore.rb +6 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
- data/lib/sequel/extensions/pg_inet.rb +10 -11
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +11 -11
- data/lib/sequel/extensions/pg_json.rb +13 -15
- data/lib/sequel/extensions/pg_json_ops.rb +125 -2
- data/lib/sequel/extensions/pg_multirange.rb +367 -0
- data/lib/sequel/extensions/pg_range.rb +13 -26
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row.rb +20 -19
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
- data/lib/sequel/extensions/round_timestamps.rb +1 -1
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/schema_caching.rb +1 -1
- data/lib/sequel/extensions/schema_dumper.rb +45 -11
- data/lib/sequel/extensions/server_block.rb +10 -13
- data/lib/sequel/extensions/set_literalizer.rb +58 -0
- data/lib/sequel/extensions/sql_comments.rb +110 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- 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 +286 -92
- data/lib/sequel/model/base.rb +53 -33
- data/lib/sequel/model/dataset_module.rb +3 -0
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/model/exceptions.rb +15 -3
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +74 -16
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/column_encryption.rb +29 -8
- data/lib/sequel/plugins/composition.rb +3 -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/enum.rb +124 -0
- data/lib/sequel/plugins/finder.rb +4 -2
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/instance_specific_default.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +2 -2
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/list.rb +8 -3
- data/lib/sequel/plugins/many_through_many.rb +109 -10
- 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_array_associations.rb +46 -34
- 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 +12 -2
- 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/serialization.rb +1 -0
- data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +39 -1
- data/lib/sequel/plugins/static_cache_cache.rb +5 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validate_associated.rb +22 -12
- data/lib/sequel/plugins/validation_helpers.rb +41 -11
- data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +12 -14
- data/lib/sequel/version.rb +1 -1
- metadata +109 -19
@@ -3,7 +3,8 @@
|
|
3
3
|
# The pg_json_ops extension adds support to Sequel's DSL to make
|
4
4
|
# it easier to call PostgreSQL JSON functions and operators (added
|
5
5
|
# first in PostgreSQL 9.3). It also supports the JSONB functions
|
6
|
-
# and operators added in PostgreSQL 9.4
|
6
|
+
# and operators added in PostgreSQL 9.4, as well as additional
|
7
|
+
# functions and operators added in later versions.
|
7
8
|
#
|
8
9
|
# To load the extension:
|
9
10
|
#
|
@@ -101,6 +102,36 @@
|
|
101
102
|
# substituted in +path+. +silent+ specifies whether errors are suppressed. By default,
|
102
103
|
# errors are not suppressed.
|
103
104
|
#
|
105
|
+
# On PostgreSQL 14+, The JSONB <tt>[]</tt> method will use subscripts instead of being
|
106
|
+
# the same as +get+, if the value being wrapped is an identifer:
|
107
|
+
#
|
108
|
+
# Sequel.pg_jsonb_op(:jsonb_column)[1] # jsonb_column[1]
|
109
|
+
# Sequel.pg_jsonb_op(:jsonb_column)[1][2] # jsonb_column[1][2]
|
110
|
+
# Sequel.pg_jsonb_op(Sequel[:j][:b])[1] # j.b[1]
|
111
|
+
#
|
112
|
+
# This support allows you to use JSONB subscripts in UPDATE statements to update only
|
113
|
+
# part of a column:
|
114
|
+
#
|
115
|
+
# c = Sequel.pg_jsonb_op(:c)
|
116
|
+
# DB[:t].update(c['key1'] => '1', c['key2'] => '"a"')
|
117
|
+
# # UPDATE "t" SET "c"['key1'] = '1', "c"['key2'] = '"a"'
|
118
|
+
#
|
119
|
+
# Note that you have to provide the value of a JSONB subscript as a JSONB value, so this
|
120
|
+
# will update +key1+ to use the number <tt>1</tt>, and +key2+ to use the string <tt>a</tt>.
|
121
|
+
# For this reason it may be simpler to use +to_json+:
|
122
|
+
#
|
123
|
+
# c = Sequel.pg_jsonb_op(:c)
|
124
|
+
# DB[:t].update(c['key1'] => 1.to_json, c['key2'] => "a".to_json)
|
125
|
+
#
|
126
|
+
# On PostgreSQL 16+, the <tt>IS [NOT] JSON</tt> operator is supported:
|
127
|
+
#
|
128
|
+
# j.is_json # j IS JSON
|
129
|
+
# j.is_json(type: :object) # j IS JSON OBJECT
|
130
|
+
# j.is_json(type: :object, unique: true) # j IS JSON OBJECT WITH UNIQUE
|
131
|
+
# j.is_not_json # j IS NOT JSON
|
132
|
+
# j.is_not_json(type: :array) # j IS NOT JSON ARRAY
|
133
|
+
# j.is_not_json(unique: true) # j IS NOT JSON WITH UNIQUE
|
134
|
+
#
|
104
135
|
# If you are also using the pg_json extension, you should load it before
|
105
136
|
# loading this extension. Doing so will allow you to use the #op method on
|
106
137
|
# JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
|
@@ -129,6 +160,18 @@ module Sequel
|
|
129
160
|
GET_PATH = ["(".freeze, " #> ".freeze, ")".freeze].freeze
|
130
161
|
GET_PATH_TEXT = ["(".freeze, " #>> ".freeze, ")".freeze].freeze
|
131
162
|
|
163
|
+
IS_JSON = ["(".freeze, " IS JSON".freeze, "".freeze, ")".freeze].freeze
|
164
|
+
IS_NOT_JSON = ["(".freeze, " IS NOT JSON".freeze, "".freeze, ")".freeze].freeze
|
165
|
+
EMPTY_STRING = Sequel::LiteralString.new('').freeze
|
166
|
+
WITH_UNIQUE = Sequel::LiteralString.new(' WITH UNIQUE').freeze
|
167
|
+
IS_JSON_MAP = {
|
168
|
+
nil => EMPTY_STRING,
|
169
|
+
:value => Sequel::LiteralString.new(' VALUE').freeze,
|
170
|
+
:scalar => Sequel::LiteralString.new(' SCALAR').freeze,
|
171
|
+
:object => Sequel::LiteralString.new(' OBJECT').freeze,
|
172
|
+
:array => Sequel::LiteralString.new(' ARRAY').freeze
|
173
|
+
}.freeze
|
174
|
+
|
132
175
|
# Get JSON array element or object field as json. If an array is given,
|
133
176
|
# gets the object at the specified path.
|
134
177
|
#
|
@@ -211,6 +254,30 @@ module Sequel
|
|
211
254
|
end
|
212
255
|
end
|
213
256
|
|
257
|
+
# Return whether the json object can be parsed as JSON.
|
258
|
+
#
|
259
|
+
# Options:
|
260
|
+
# :type :: Check whether the json object can be parsed as a specific type
|
261
|
+
# of JSON (:value, :scalar, :object, :array).
|
262
|
+
# :unique :: Check JSON objects for unique keys.
|
263
|
+
#
|
264
|
+
# json_op.is_json # json IS JSON
|
265
|
+
# json_op.is_json(type: :object) # json IS JSON OBJECT
|
266
|
+
# json_op.is_json(unique: true) # json IS JSON WITH UNIQUE
|
267
|
+
def is_json(opts=OPTS)
|
268
|
+
_is_json(IS_JSON, opts)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Return whether the json object cannot be parsed as JSON. The opposite
|
272
|
+
# of #is_json. See #is_json for options.
|
273
|
+
#
|
274
|
+
# json_op.is_not_json # json IS NOT JSON
|
275
|
+
# json_op.is_not_json(type: :object) # json IS NOT JSON OBJECT
|
276
|
+
# json_op.is_not_json(unique: true) # json IS NOT JSON WITH UNIQUE
|
277
|
+
def is_not_json(opts=OPTS)
|
278
|
+
_is_json(IS_NOT_JSON, opts)
|
279
|
+
end
|
280
|
+
|
214
281
|
# Returns a set of keys AS text in the json object.
|
215
282
|
#
|
216
283
|
# json_op.keys # json_object_keys(json)
|
@@ -264,6 +331,13 @@ module Sequel
|
|
264
331
|
|
265
332
|
private
|
266
333
|
|
334
|
+
# Internals of IS [NOT] JSON support
|
335
|
+
def _is_json(lit_array, opts)
|
336
|
+
raise Error, "invalid is_json :type option: #{opts[:type].inspect}" unless type = IS_JSON_MAP[opts[:type]]
|
337
|
+
unique = opts[:unique] ? WITH_UNIQUE : EMPTY_STRING
|
338
|
+
Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(lit_array, [self, type, unique]))
|
339
|
+
end
|
340
|
+
|
267
341
|
# Return a placeholder literal with the given str and args, wrapped
|
268
342
|
# in an JSONOp or JSONBOp, used by operators that return json or jsonb.
|
269
343
|
def json_op(str, args)
|
@@ -323,6 +397,24 @@ module Sequel
|
|
323
397
|
PATH_EXISTS = ["(".freeze, " @? ".freeze, ")".freeze].freeze
|
324
398
|
PATH_MATCH = ["(".freeze, " @@ ".freeze, ")".freeze].freeze
|
325
399
|
|
400
|
+
# Support subscript syntax for JSONB.
|
401
|
+
def [](key)
|
402
|
+
if is_array?(key)
|
403
|
+
super
|
404
|
+
else
|
405
|
+
case @value
|
406
|
+
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, JSONBSubscriptOp
|
407
|
+
# Only use subscripts for identifiers. In other cases, switching from
|
408
|
+
# the -> operator to [] for subscripts causes SQL syntax issues. You
|
409
|
+
# only need the [] for subscripting when doing assignment, and
|
410
|
+
# assignment is generally done on identifiers.
|
411
|
+
self.class.new(JSONBSubscriptOp.new(self, key))
|
412
|
+
else
|
413
|
+
super
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
326
418
|
# jsonb expression for deletion of the given argument from the
|
327
419
|
# current jsonb.
|
328
420
|
#
|
@@ -582,6 +674,37 @@ module Sequel
|
|
582
674
|
end
|
583
675
|
end
|
584
676
|
|
677
|
+
# Represents JSONB subscripts. This is abstracted because the
|
678
|
+
# subscript support depends on the database version.
|
679
|
+
class JSONBSubscriptOp < SQL::Expression
|
680
|
+
SUBSCRIPT = ["".freeze, "[".freeze, "]".freeze].freeze
|
681
|
+
|
682
|
+
# The expression being subscripted
|
683
|
+
attr_reader :expression
|
684
|
+
|
685
|
+
# The subscript to use
|
686
|
+
attr_reader :sub
|
687
|
+
|
688
|
+
# Set the expression and subscript to the given arguments
|
689
|
+
def initialize(expression, sub)
|
690
|
+
@expression = expression
|
691
|
+
@sub = sub
|
692
|
+
freeze
|
693
|
+
end
|
694
|
+
|
695
|
+
# Use subscripts instead of -> operator on PostgreSQL 14+
|
696
|
+
def to_s_append(ds, sql)
|
697
|
+
server_version = ds.db.server_version
|
698
|
+
frag = server_version && server_version >= 140000 ? SUBSCRIPT : JSONOp::GET
|
699
|
+
ds.literal_append(sql, Sequel::SQL::PlaceholderLiteralString.new(frag, [@expression, @sub]))
|
700
|
+
end
|
701
|
+
|
702
|
+
# Support transforming of jsonb subscripts
|
703
|
+
def sequel_ast_transform(transformer)
|
704
|
+
self.class.new(transformer.call(@expression), transformer.call(@sub))
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
585
708
|
module JSONOpMethods
|
586
709
|
# Wrap the receiver in an JSONOp so you can easily use the PostgreSQL
|
587
710
|
# json functions and operators with it.
|
@@ -674,7 +797,7 @@ end
|
|
674
797
|
if defined?(Sequel::CoreRefinements)
|
675
798
|
module Sequel::CoreRefinements
|
676
799
|
refine Symbol do
|
677
|
-
|
800
|
+
send INCLUDE_METH, Sequel::Postgres::JSONOpMethods
|
678
801
|
end
|
679
802
|
end
|
680
803
|
end
|
@@ -0,0 +1,367 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The pg_multirange extension adds support for the PostgreSQL 14+ multirange
|
4
|
+
# types to Sequel. PostgreSQL multirange types are similar to an array of
|
5
|
+
# ranges, where a match against the multirange is a match against any of the
|
6
|
+
# ranges in the multirange.
|
7
|
+
#
|
8
|
+
# When PostgreSQL multirange values are retrieved, they are parsed and returned
|
9
|
+
# as instances of Sequel::Postgres::PGMultiRange. PGMultiRange mostly acts
|
10
|
+
# like an array of Sequel::Postgres::PGRange (see the pg_range extension).
|
11
|
+
#
|
12
|
+
# In addition to the parser, this extension comes with literalizers
|
13
|
+
# for PGMultiRanges, so they can be used in queries and as bound variables.
|
14
|
+
#
|
15
|
+
# To turn an existing array of Ranges into a PGMultiRange, use Sequel.pg_multirange.
|
16
|
+
# You must provide the type of multirange when creating the multirange:
|
17
|
+
#
|
18
|
+
# Sequel.pg_multirange(array_of_date_ranges, :datemultirange)
|
19
|
+
#
|
20
|
+
# To use this extension, load it into the Database instance:
|
21
|
+
#
|
22
|
+
# DB.extension :pg_multirange
|
23
|
+
#
|
24
|
+
# See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
|
25
|
+
# for details on using multirange type columns in CREATE/ALTER TABLE statements.
|
26
|
+
#
|
27
|
+
# This extension makes it easy to add support for other multirange types. In
|
28
|
+
# general, you just need to make sure that the subtype is handled and has the
|
29
|
+
# appropriate converter installed. For user defined
|
30
|
+
# types, you can do this via:
|
31
|
+
#
|
32
|
+
# DB.add_conversion_proc(subtype_oid){|string| }
|
33
|
+
#
|
34
|
+
# Then you can call
|
35
|
+
# Sequel::Postgres::PGMultiRange::DatabaseMethods#register_multirange_type
|
36
|
+
# to automatically set up a handler for the range type. So if you
|
37
|
+
# want to support the timemultirange type (assuming the time type is already
|
38
|
+
# supported):
|
39
|
+
#
|
40
|
+
# DB.register_multirange_type('timerange')
|
41
|
+
#
|
42
|
+
# This extension integrates with the pg_array extension. If you plan
|
43
|
+
# to use arrays of multirange types, load the pg_array extension before the
|
44
|
+
# pg_multirange extension:
|
45
|
+
#
|
46
|
+
# DB.extension :pg_array, :pg_multirange
|
47
|
+
#
|
48
|
+
# The pg_multirange extension will automatically load the pg_range extension.
|
49
|
+
#
|
50
|
+
# Related module: Sequel::Postgres::PGMultiRange
|
51
|
+
|
52
|
+
require 'delegate'
|
53
|
+
require 'strscan'
|
54
|
+
|
55
|
+
module Sequel
|
56
|
+
module Postgres
|
57
|
+
class PGMultiRange < DelegateClass(Array)
|
58
|
+
include Sequel::SQL::AliasMethods
|
59
|
+
|
60
|
+
# Converts strings into PGMultiRange instances.
|
61
|
+
class Parser < StringScanner
|
62
|
+
def initialize(source, converter)
|
63
|
+
super(source)
|
64
|
+
@converter = converter
|
65
|
+
end
|
66
|
+
|
67
|
+
# Parse the multirange type input string into a PGMultiRange value.
|
68
|
+
def parse
|
69
|
+
raise Sequel::Error, "invalid multirange, doesn't start with {" unless get_byte == '{'
|
70
|
+
ranges = []
|
71
|
+
|
72
|
+
unless scan(/\}/)
|
73
|
+
while true
|
74
|
+
raise Sequel::Error, "unfinished multirange" unless range_string = scan_until(/[\]\)]/)
|
75
|
+
ranges << @converter.call(range_string)
|
76
|
+
|
77
|
+
case sep = get_byte
|
78
|
+
when '}'
|
79
|
+
break
|
80
|
+
when ','
|
81
|
+
# nothing
|
82
|
+
else
|
83
|
+
raise Sequel::Error, "invalid multirange separator: #{sep.inspect}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
raise Sequel::Error, "invalid multirange, remaining data after }" unless eos?
|
89
|
+
ranges
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Callable object that takes the input string and parses it using Parser.
|
94
|
+
class Creator
|
95
|
+
# The database type to set on the PGMultiRange instances returned.
|
96
|
+
attr_reader :type
|
97
|
+
|
98
|
+
def initialize(type, converter=nil)
|
99
|
+
@type = type
|
100
|
+
@converter = converter
|
101
|
+
end
|
102
|
+
|
103
|
+
# Parse the string using Parser with the appropriate
|
104
|
+
# converter, and return a PGMultiRange with the appropriate database
|
105
|
+
# type.
|
106
|
+
def call(string)
|
107
|
+
PGMultiRange.new(Parser.new(string, @converter).parse, @type)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
module DatabaseMethods
|
112
|
+
# Add the default multirange conversion procs to the database
|
113
|
+
def self.extended(db)
|
114
|
+
db.instance_exec do
|
115
|
+
raise Error, "multiranges not supported on this database" unless server_version >= 140000
|
116
|
+
|
117
|
+
extension :pg_range
|
118
|
+
@pg_multirange_schema_types ||= {}
|
119
|
+
|
120
|
+
register_multirange_type('int4multirange', :range_oid=>3904, :oid=>4451)
|
121
|
+
register_multirange_type('nummultirange', :range_oid=>3906, :oid=>4532)
|
122
|
+
register_multirange_type('tsmultirange', :range_oid=>3908, :oid=>4533)
|
123
|
+
register_multirange_type('tstzmultirange', :range_oid=>3910, :oid=>4534)
|
124
|
+
register_multirange_type('datemultirange', :range_oid=>3912, :oid=>4535)
|
125
|
+
register_multirange_type('int8multirange', :range_oid=>3926, :oid=>4536)
|
126
|
+
|
127
|
+
if respond_to?(:register_array_type)
|
128
|
+
register_array_type('int4multirange', :oid=>6150, :scalar_oid=>4451, :scalar_typecast=>:int4multirange)
|
129
|
+
register_array_type('nummultirange', :oid=>6151, :scalar_oid=>4532, :scalar_typecast=>:nummultirange)
|
130
|
+
register_array_type('tsmultirange', :oid=>6152, :scalar_oid=>4533, :scalar_typecast=>:tsmultirange)
|
131
|
+
register_array_type('tstzmultirange', :oid=>6153, :scalar_oid=>4534, :scalar_typecast=>:tstzmultirange)
|
132
|
+
register_array_type('datemultirange', :oid=>6155, :scalar_oid=>4535, :scalar_typecast=>:datemultirange)
|
133
|
+
register_array_type('int8multirange', :oid=>6157, :scalar_oid=>4536, :scalar_typecast=>:int8multirange)
|
134
|
+
end
|
135
|
+
|
136
|
+
[:int4multirange, :nummultirange, :tsmultirange, :tstzmultirange, :datemultirange, :int8multirange].each do |v|
|
137
|
+
@schema_type_classes[v] = PGMultiRange
|
138
|
+
end
|
139
|
+
|
140
|
+
procs = conversion_procs
|
141
|
+
add_conversion_proc(4533, PGMultiRange::Creator.new("tsmultirange", procs[3908]))
|
142
|
+
add_conversion_proc(4534, PGMultiRange::Creator.new("tstzmultirange", procs[3910]))
|
143
|
+
|
144
|
+
if respond_to?(:register_array_type) && defined?(PGArray::Creator)
|
145
|
+
add_conversion_proc(6152, PGArray::Creator.new("tsmultirange", procs[4533]))
|
146
|
+
add_conversion_proc(6153, PGArray::Creator.new("tstzmultirange", procs[4534]))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Handle PGMultiRange values in bound variables
|
152
|
+
def bound_variable_arg(arg, conn)
|
153
|
+
case arg
|
154
|
+
when PGMultiRange
|
155
|
+
arg.unquoted_literal(schema_utility_dataset)
|
156
|
+
else
|
157
|
+
super
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Freeze the pg multirange schema types to prevent adding new ones.
|
162
|
+
def freeze
|
163
|
+
@pg_multirange_schema_types.freeze
|
164
|
+
super
|
165
|
+
end
|
166
|
+
|
167
|
+
# Register a database specific multirange type. This can be used to support
|
168
|
+
# different multirange types per Database. Options:
|
169
|
+
#
|
170
|
+
# :converter :: A callable object (e.g. Proc), that is called with the PostgreSQL range string,
|
171
|
+
# and should return a PGRange instance.
|
172
|
+
# :oid :: The PostgreSQL OID for the multirange type. This is used by Sequel to set up automatic type
|
173
|
+
# conversion on retrieval from the database.
|
174
|
+
# :range_oid :: Should be the PostgreSQL OID for the multirange subtype (the range type). If given,
|
175
|
+
# automatically sets the :converter option by looking for scalar conversion
|
176
|
+
# proc.
|
177
|
+
#
|
178
|
+
# If a block is given, it is treated as the :converter option.
|
179
|
+
def register_multirange_type(db_type, opts=OPTS, &block)
|
180
|
+
oid = opts[:oid]
|
181
|
+
soid = opts[:range_oid]
|
182
|
+
|
183
|
+
if has_converter = opts.has_key?(:converter)
|
184
|
+
raise Error, "can't provide both a block and :converter option to register_multirange_type" if block
|
185
|
+
converter = opts[:converter]
|
186
|
+
else
|
187
|
+
has_converter = true if block
|
188
|
+
converter = block
|
189
|
+
end
|
190
|
+
|
191
|
+
unless (soid || has_converter) && oid
|
192
|
+
range_oid, subtype_oid = from(:pg_range).join(:pg_type, :oid=>:rngmultitypid).where(:typname=>db_type.to_s).get([:rngmultitypid, :rngtypid])
|
193
|
+
soid ||= subtype_oid unless has_converter
|
194
|
+
oid ||= range_oid
|
195
|
+
end
|
196
|
+
|
197
|
+
db_type = db_type.to_s.dup.freeze
|
198
|
+
|
199
|
+
if soid
|
200
|
+
raise Error, "can't provide both a converter and :range_oid option to register" if has_converter
|
201
|
+
raise Error, "no conversion proc for :range_oid=>#{soid.inspect} in conversion_procs" unless converter = conversion_procs[soid]
|
202
|
+
end
|
203
|
+
|
204
|
+
raise Error, "cannot add a multirange type without a convertor (use :converter or :range_oid option or pass block)" unless converter
|
205
|
+
creator = Creator.new(db_type, converter)
|
206
|
+
add_conversion_proc(oid, creator)
|
207
|
+
|
208
|
+
@pg_multirange_schema_types[db_type] = db_type.to_sym
|
209
|
+
|
210
|
+
singleton_class.class_eval do
|
211
|
+
meth = :"typecast_value_#{db_type}"
|
212
|
+
scalar_typecast_method = :"typecast_value_#{opts.fetch(:scalar_typecast, db_type.sub('multirange', 'range'))}"
|
213
|
+
define_method(meth){|v| typecast_value_pg_multirange(v, creator, scalar_typecast_method)}
|
214
|
+
private meth
|
215
|
+
end
|
216
|
+
|
217
|
+
@schema_type_classes[db_type] = PGMultiRange
|
218
|
+
nil
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
# Recognize the registered database multirange types.
|
224
|
+
def schema_multirange_type(db_type)
|
225
|
+
@pg_multirange_schema_types[db_type] || super
|
226
|
+
end
|
227
|
+
|
228
|
+
# Set the :ruby_default value if the default value is recognized as a multirange.
|
229
|
+
def schema_post_process(_)
|
230
|
+
super.each do |a|
|
231
|
+
h = a[1]
|
232
|
+
db_type = h[:db_type]
|
233
|
+
if @pg_multirange_schema_types[db_type] && h[:default] =~ /\A#{db_type}\(.*\)\z/
|
234
|
+
h[:ruby_default] = get(Sequel.lit(h[:default]))
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Given a value to typecast and the type of PGMultiRange subclass:
|
240
|
+
# * If given a PGMultiRange with a matching type, use it directly.
|
241
|
+
# * If given a PGMultiRange with a different type, return a PGMultiRange
|
242
|
+
# with the creator's type.
|
243
|
+
# * If given an Array, create a new PGMultiRange instance for it, typecasting
|
244
|
+
# each instance using the scalar_typecast_method.
|
245
|
+
def typecast_value_pg_multirange(value, creator, scalar_typecast_method=nil)
|
246
|
+
case value
|
247
|
+
when PGMultiRange
|
248
|
+
return value if value.db_type == creator.type
|
249
|
+
when Array
|
250
|
+
# nothing
|
251
|
+
else
|
252
|
+
raise Sequel::InvalidValue, "invalid value for multirange type: #{value.inspect}"
|
253
|
+
end
|
254
|
+
|
255
|
+
if scalar_typecast_method && respond_to?(scalar_typecast_method, true)
|
256
|
+
value = value.map{|v| send(scalar_typecast_method, v)}
|
257
|
+
end
|
258
|
+
PGMultiRange.new(value, creator.type)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# The type of this multirange (e.g. 'int4multirange').
|
263
|
+
attr_accessor :db_type
|
264
|
+
|
265
|
+
# Set the array of ranges to delegate to, and the database type.
|
266
|
+
def initialize(ranges, db_type)
|
267
|
+
super(ranges)
|
268
|
+
@db_type = db_type.to_s
|
269
|
+
end
|
270
|
+
|
271
|
+
# Append the multirange SQL to the given sql string.
|
272
|
+
def sql_literal_append(ds, sql)
|
273
|
+
sql << db_type << '('
|
274
|
+
joiner = nil
|
275
|
+
conversion_meth = nil
|
276
|
+
each do |range|
|
277
|
+
if joiner
|
278
|
+
sql << joiner
|
279
|
+
else
|
280
|
+
joiner = ', '
|
281
|
+
end
|
282
|
+
|
283
|
+
unless range.is_a?(PGRange)
|
284
|
+
conversion_meth ||= :"typecast_value_#{db_type.sub('multi', '')}"
|
285
|
+
range = ds.db.send(conversion_meth, range)
|
286
|
+
end
|
287
|
+
|
288
|
+
ds.literal_append(sql, range)
|
289
|
+
end
|
290
|
+
sql << ')'
|
291
|
+
end
|
292
|
+
|
293
|
+
# Return whether the value is inside any of the ranges in the multirange.
|
294
|
+
def cover?(value)
|
295
|
+
any?{|range| range.cover?(value)}
|
296
|
+
end
|
297
|
+
alias === cover?
|
298
|
+
|
299
|
+
# Don't consider multiranges with different database types equal.
|
300
|
+
def eql?(other)
|
301
|
+
if PGMultiRange === other
|
302
|
+
return false unless other.db_type == db_type
|
303
|
+
other = other.__getobj__
|
304
|
+
end
|
305
|
+
__getobj__.eql?(other)
|
306
|
+
end
|
307
|
+
|
308
|
+
# Don't consider multiranges with different database types equal.
|
309
|
+
def ==(other)
|
310
|
+
return false if PGMultiRange === other && other.db_type != db_type
|
311
|
+
super
|
312
|
+
end
|
313
|
+
|
314
|
+
# Return a string containing the unescaped version of the multirange.
|
315
|
+
# Separated out for use by the bound argument code.
|
316
|
+
def unquoted_literal(ds)
|
317
|
+
val = String.new
|
318
|
+
val << "{"
|
319
|
+
|
320
|
+
joiner = nil
|
321
|
+
conversion_meth = nil
|
322
|
+
each do |range|
|
323
|
+
if joiner
|
324
|
+
val << joiner
|
325
|
+
else
|
326
|
+
joiner = ', '
|
327
|
+
end
|
328
|
+
|
329
|
+
unless range.is_a?(PGRange)
|
330
|
+
conversion_meth ||= :"typecast_value_#{db_type.sub('multi', '')}"
|
331
|
+
range = ds.db.send(conversion_meth, range)
|
332
|
+
end
|
333
|
+
|
334
|
+
val << range.unquoted_literal(ds)
|
335
|
+
end
|
336
|
+
|
337
|
+
val << "}"
|
338
|
+
end
|
339
|
+
|
340
|
+
# Allow automatic parameterization.
|
341
|
+
def sequel_auto_param_type(ds)
|
342
|
+
"::#{db_type}"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
module SQL::Builders
|
348
|
+
# Convert the object to a Postgres::PGMultiRange.
|
349
|
+
def pg_multirange(v, db_type)
|
350
|
+
case v
|
351
|
+
when Postgres::PGMultiRange
|
352
|
+
if v.db_type == db_type
|
353
|
+
v
|
354
|
+
else
|
355
|
+
Postgres::PGMultiRange.new(v, db_type)
|
356
|
+
end
|
357
|
+
when Array
|
358
|
+
Postgres::PGMultiRange.new(v, db_type)
|
359
|
+
else
|
360
|
+
# May not be defined unless the pg_range_ops extension is used
|
361
|
+
pg_range_op(v)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
Database.register_extension(:pg_multirange, Postgres::PGMultiRange::DatabaseMethods)
|
367
|
+
end
|
@@ -4,12 +4,9 @@
|
|
4
4
|
# types to Sequel. PostgreSQL range types are similar to ruby's
|
5
5
|
# Range class, representating an array of values. However, they
|
6
6
|
# are more flexible than ruby's ranges, allowing exclusive beginnings
|
7
|
-
# and endings (ruby's range only allows exclusive endings)
|
8
|
-
# unbounded beginnings and endings (which ruby's range does not
|
9
|
-
# support).
|
7
|
+
# and endings (ruby's range only allows exclusive endings).
|
10
8
|
#
|
11
|
-
#
|
12
|
-
# that when range type values are retrieved, they are parsed and returned
|
9
|
+
# When PostgreSQL range values are retreived, they are parsed and returned
|
13
10
|
# as instances of Sequel::Postgres::PGRange. PGRange mostly acts
|
14
11
|
# like a Range, but it's not a Range as not all PostgreSQL range
|
15
12
|
# type values would be valid ruby ranges. If the range type value
|
@@ -19,8 +16,7 @@
|
|
19
16
|
# exception will be raised.
|
20
17
|
#
|
21
18
|
# In addition to the parser, this extension comes with literalizers
|
22
|
-
# for
|
23
|
-
# callbacks, so they work on all adapters.
|
19
|
+
# for PGRange and Range, so they can be used in queries and as bound variables.
|
24
20
|
#
|
25
21
|
# To turn an existing Range into a PGRange, use Sequel.pg_range:
|
26
22
|
#
|
@@ -237,23 +233,9 @@ module Sequel
|
|
237
233
|
|
238
234
|
private
|
239
235
|
|
240
|
-
# Handle arrays of range types in bound variables.
|
241
|
-
def bound_variable_array(a)
|
242
|
-
case a
|
243
|
-
when PGRange, Range
|
244
|
-
"\"#{bound_variable_arg(a, nil)}\""
|
245
|
-
else
|
246
|
-
super
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
236
|
# Recognize the registered database range types.
|
251
|
-
def
|
252
|
-
|
253
|
-
type
|
254
|
-
else
|
255
|
-
super
|
256
|
-
end
|
237
|
+
def schema_range_type(db_type)
|
238
|
+
@pg_range_schema_types[db_type] || super
|
257
239
|
end
|
258
240
|
|
259
241
|
# Set the :ruby_default value if the default value is recognized as a range.
|
@@ -290,7 +272,7 @@ module Sequel
|
|
290
272
|
when Range
|
291
273
|
PGRange.from_range(value, parser.db_type)
|
292
274
|
when String
|
293
|
-
parser.call(value)
|
275
|
+
parser.call(typecast_check_string_length(value, 100))
|
294
276
|
else
|
295
277
|
raise Sequel::InvalidValue, "invalid value for range type: #{value.inspect}"
|
296
278
|
end
|
@@ -499,6 +481,11 @@ module Sequel
|
|
499
481
|
end
|
500
482
|
end
|
501
483
|
|
484
|
+
# Allow automatic parameterization for ranges with types.
|
485
|
+
def sequel_auto_param_type(ds)
|
486
|
+
"::#{db_type}" if db_type
|
487
|
+
end
|
488
|
+
|
502
489
|
private
|
503
490
|
|
504
491
|
# Escape common range types. Instead of quoting, just backslash escape all
|
@@ -507,8 +494,8 @@ module Sequel
|
|
507
494
|
case k
|
508
495
|
when nil
|
509
496
|
''
|
510
|
-
when
|
511
|
-
ds.
|
497
|
+
when Time, Date
|
498
|
+
ds.literal_date_or_time(k, true)
|
512
499
|
when Integer, Float
|
513
500
|
k.to_s
|
514
501
|
when BigDecimal
|