sequel 5.48.0 → 5.52.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 +80 -0
- data/README.rdoc +12 -5
- data/doc/migration.rdoc +1 -1
- data/doc/opening_databases.rdoc +1 -1
- data/doc/postgresql.rdoc +8 -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/testing.rdoc +3 -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 +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +3 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
- data/lib/sequel/adapters/jdbc.rb +9 -11
- data/lib/sequel/adapters/mysql.rb +80 -67
- data/lib/sequel/adapters/mysql2.rb +42 -44
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +3 -3
- data/lib/sequel/adapters/postgres.rb +27 -29
- data/lib/sequel/adapters/shared/access.rb +2 -0
- data/lib/sequel/adapters/shared/db2.rb +2 -0
- data/lib/sequel/adapters/shared/mysql.rb +4 -2
- data/lib/sequel/adapters/shared/postgres.rb +59 -6
- data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
- data/lib/sequel/adapters/shared/sqlite.rb +1 -1
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +16 -18
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- data/lib/sequel/connection_pool/single.rb +6 -8
- data/lib/sequel/core.rb +17 -18
- data/lib/sequel/database/connecting.rb +2 -2
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/dataset/actions.rb +2 -2
- data/lib/sequel/dataset/query.rb +45 -3
- data/lib/sequel/dataset/sql.rb +18 -9
- data/lib/sequel/extensions/any_not_empty.rb +1 -1
- data/lib/sequel/extensions/core_refinements.rb +36 -11
- 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 +1 -1
- data/lib/sequel/extensions/inflector.rb +1 -1
- data/lib/sequel/extensions/migration.rb +4 -1
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array_ops.rb +1 -1
- data/lib/sequel/extensions/pg_extended_date_support.rb +1 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +1 -0
- data/lib/sequel/extensions/pg_json.rb +3 -5
- data/lib/sequel/extensions/pg_json_ops.rb +71 -1
- data/lib/sequel/extensions/pg_multirange.rb +372 -0
- data/lib/sequel/extensions/pg_range.rb +4 -12
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/server_block.rb +8 -12
- data/lib/sequel/extensions/sql_comments.rb +108 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/model/associations.rb +3 -1
- data/lib/sequel/model/base.rb +9 -13
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/plugins/auto_validations.rb +25 -5
- data/lib/sequel/plugins/column_encryption.rb +1 -1
- data/lib/sequel/plugins/composition.rb +1 -0
- data/lib/sequel/plugins/json_serializer.rb +2 -2
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/serialization.rb +1 -0
- data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +1 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
- data/lib/sequel/plugins/unused_associations.rb +2 -2
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validation_helpers.rb +7 -1
- 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 +17 -4
@@ -62,6 +62,19 @@
|
|
62
62
|
# # Delete a key
|
63
63
|
# DB[:tab].update(h: Sequel.hstore_op(:h).delete('k1'))
|
64
64
|
#
|
65
|
+
# On PostgreSQL 14+, The hstore <tt>[]</tt> method will use subscripts instead of being
|
66
|
+
# the same as +get+, if the value being wrapped is an identifer:
|
67
|
+
#
|
68
|
+
# Sequel.hstore_op(:hstore_column)['a'] # hstore_column['a']
|
69
|
+
# Sequel.hstore_op(Sequel[:h][:s])['a'] # h.s['a']
|
70
|
+
#
|
71
|
+
# This support allows you to use hstore subscripts in UPDATE statements to update only
|
72
|
+
# part of a column:
|
73
|
+
#
|
74
|
+
# h = Sequel.hstore_op(:h)
|
75
|
+
# DB[:t].update(h['key1'] => 'val1', h['key2'] => 'val2')
|
76
|
+
# # UPDATE "t" SET "h"['key1'] = 'val1', "h"['key2'] = 'val2'
|
77
|
+
#
|
65
78
|
# See the PostgreSQL hstore function and operator documentation for more
|
66
79
|
# details on what these functions and operators do.
|
67
80
|
#
|
@@ -114,10 +127,15 @@ module Sequel
|
|
114
127
|
#
|
115
128
|
# hstore_op['a'] # (hstore -> 'a')
|
116
129
|
def [](key)
|
117
|
-
v = Sequel::SQL::PlaceholderLiteralString.new(LOOKUP, [value, wrap_input_array(key)])
|
118
130
|
if key.is_a?(Array) || (defined?(Sequel::Postgres::PGArray) && key.is_a?(Sequel::Postgres::PGArray)) || (defined?(Sequel::Postgres::ArrayOp) && key.is_a?(Sequel::Postgres::ArrayOp))
|
119
|
-
wrap_output_array(
|
131
|
+
wrap_output_array(Sequel::SQL::PlaceholderLiteralString.new(LOOKUP, [value, wrap_input_array(key)]))
|
120
132
|
else
|
133
|
+
v = case @value
|
134
|
+
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
|
135
|
+
HStoreSubscriptOp.new(self, key)
|
136
|
+
else
|
137
|
+
Sequel::SQL::PlaceholderLiteralString.new(LOOKUP, [value, key])
|
138
|
+
end
|
121
139
|
Sequel::SQL::StringExpression.new(:NOOP, v)
|
122
140
|
end
|
123
141
|
end
|
@@ -304,6 +322,38 @@ module Sequel
|
|
304
322
|
end
|
305
323
|
end
|
306
324
|
|
325
|
+
# Represents hstore subscripts. This is abstracted because the
|
326
|
+
# subscript support depends on the database version.
|
327
|
+
class HStoreSubscriptOp < SQL::Expression
|
328
|
+
SUBSCRIPT = ["".freeze, "[".freeze, "]".freeze].freeze
|
329
|
+
|
330
|
+
# The expression being subscripted
|
331
|
+
attr_reader :expression
|
332
|
+
|
333
|
+
# The subscript to use
|
334
|
+
attr_reader :sub
|
335
|
+
|
336
|
+
# Set the expression and subscript to the given arguments
|
337
|
+
def initialize(expression, sub)
|
338
|
+
@expression = expression
|
339
|
+
@sub = sub
|
340
|
+
freeze
|
341
|
+
end
|
342
|
+
|
343
|
+
# Use subscripts instead of -> operator on PostgreSQL 14+
|
344
|
+
def to_s_append(ds, sql)
|
345
|
+
server_version = ds.db.server_version
|
346
|
+
frag = server_version && server_version >= 140000 ? SUBSCRIPT : HStoreOp::LOOKUP
|
347
|
+
ds.literal_append(sql, Sequel::SQL::PlaceholderLiteralString.new(frag, [@expression, @sub]))
|
348
|
+
end
|
349
|
+
|
350
|
+
# Support transforming of hstore subscripts
|
351
|
+
def sequel_ast_transform(transformer)
|
352
|
+
self.class.new(transformer.call(@expression), transformer.call(@sub))
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
|
307
357
|
module HStoreOpMethods
|
308
358
|
# Wrap the receiver in an HStoreOp so you can easily use the PostgreSQL
|
309
359
|
# hstore functions and operators with it.
|
@@ -356,7 +406,7 @@ end
|
|
356
406
|
if defined?(Sequel::CoreRefinements)
|
357
407
|
module Sequel::CoreRefinements
|
358
408
|
refine Symbol do
|
359
|
-
|
409
|
+
send INCLUDE_METH, Sequel::Postgres::HStoreOpMethods
|
360
410
|
end
|
361
411
|
end
|
362
412
|
end
|
@@ -387,11 +387,9 @@ module Sequel
|
|
387
387
|
# argument is true), or a String, Numeric, true, false, or nil
|
388
388
|
# if the json library used supports that.
|
389
389
|
def _parse_json(s)
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
394
|
-
end
|
390
|
+
Sequel.parse_json(s)
|
391
|
+
rescue Sequel.json_parser_error_class => e
|
392
|
+
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
395
393
|
end
|
396
394
|
|
397
395
|
# Wrap the parsed JSON value in the appropriate JSON wrapper class.
|
@@ -101,6 +101,27 @@
|
|
101
101
|
# substituted in +path+. +silent+ specifies whether errors are suppressed. By default,
|
102
102
|
# errors are not suppressed.
|
103
103
|
#
|
104
|
+
# On PostgreSQL 14+, The JSONB <tt>[]</tt> method will use subscripts instead of being
|
105
|
+
# the same as +get+, if the value being wrapped is an identifer:
|
106
|
+
#
|
107
|
+
# Sequel.pg_jsonb_op(:jsonb_column)[1] # jsonb_column[1]
|
108
|
+
# Sequel.pg_jsonb_op(:jsonb_column)[1][2] # jsonb_column[1][2]
|
109
|
+
# Sequel.pg_jsonb_op(Sequel[:j][:b])[1] # j.b[1]
|
110
|
+
#
|
111
|
+
# This support allows you to use JSONB subscripts in UPDATE statements to update only
|
112
|
+
# part of a column:
|
113
|
+
#
|
114
|
+
# c = Sequel.pg_jsonb_op(:c)
|
115
|
+
# DB[:t].update(c['key1'] => '1', c['key2'] => '"a"')
|
116
|
+
# # UPDATE "t" SET "c"['key1'] = '1', "c"['key2'] = '"a"'
|
117
|
+
#
|
118
|
+
# Note that you have to provide the value of a JSONB subscript as a JSONB value, so this
|
119
|
+
# will update +key1+ to use the number <tt>1</tt>, and +key2+ to use the string <tt>a</tt>.
|
120
|
+
# For this reason it may be simpler to use +to_json+:
|
121
|
+
#
|
122
|
+
# c = Sequel.pg_jsonb_op(:c)
|
123
|
+
# DB[:t].update(c['key1'] => 1.to_json, c['key2'] => "a".to_json)
|
124
|
+
#
|
104
125
|
# If you are also using the pg_json extension, you should load it before
|
105
126
|
# loading this extension. Doing so will allow you to use the #op method on
|
106
127
|
# JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
|
@@ -323,6 +344,24 @@ module Sequel
|
|
323
344
|
PATH_EXISTS = ["(".freeze, " @? ".freeze, ")".freeze].freeze
|
324
345
|
PATH_MATCH = ["(".freeze, " @@ ".freeze, ")".freeze].freeze
|
325
346
|
|
347
|
+
# Support subscript syntax for JSONB.
|
348
|
+
def [](key)
|
349
|
+
if is_array?(key)
|
350
|
+
super
|
351
|
+
else
|
352
|
+
case @value
|
353
|
+
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, JSONBSubscriptOp
|
354
|
+
# Only use subscripts for identifiers. In other cases, switching from
|
355
|
+
# the -> operator to [] for subscripts causes SQL syntax issues. You
|
356
|
+
# only need the [] for subscripting when doing assignment, and
|
357
|
+
# assignment is generally done on identifiers.
|
358
|
+
self.class.new(JSONBSubscriptOp.new(self, key))
|
359
|
+
else
|
360
|
+
super
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
326
365
|
# jsonb expression for deletion of the given argument from the
|
327
366
|
# current jsonb.
|
328
367
|
#
|
@@ -582,6 +621,37 @@ module Sequel
|
|
582
621
|
end
|
583
622
|
end
|
584
623
|
|
624
|
+
# Represents JSONB subscripts. This is abstracted because the
|
625
|
+
# subscript support depends on the database version.
|
626
|
+
class JSONBSubscriptOp < SQL::Expression
|
627
|
+
SUBSCRIPT = ["".freeze, "[".freeze, "]".freeze].freeze
|
628
|
+
|
629
|
+
# The expression being subscripted
|
630
|
+
attr_reader :expression
|
631
|
+
|
632
|
+
# The subscript to use
|
633
|
+
attr_reader :sub
|
634
|
+
|
635
|
+
# Set the expression and subscript to the given arguments
|
636
|
+
def initialize(expression, sub)
|
637
|
+
@expression = expression
|
638
|
+
@sub = sub
|
639
|
+
freeze
|
640
|
+
end
|
641
|
+
|
642
|
+
# Use subscripts instead of -> operator on PostgreSQL 14+
|
643
|
+
def to_s_append(ds, sql)
|
644
|
+
server_version = ds.db.server_version
|
645
|
+
frag = server_version && server_version >= 140000 ? SUBSCRIPT : JSONOp::GET
|
646
|
+
ds.literal_append(sql, Sequel::SQL::PlaceholderLiteralString.new(frag, [@expression, @sub]))
|
647
|
+
end
|
648
|
+
|
649
|
+
# Support transforming of jsonb subscripts
|
650
|
+
def sequel_ast_transform(transformer)
|
651
|
+
self.class.new(transformer.call(@expression), transformer.call(@sub))
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
585
655
|
module JSONOpMethods
|
586
656
|
# Wrap the receiver in an JSONOp so you can easily use the PostgreSQL
|
587
657
|
# json functions and operators with it.
|
@@ -674,7 +744,7 @@ end
|
|
674
744
|
if defined?(Sequel::CoreRefinements)
|
675
745
|
module Sequel::CoreRefinements
|
676
746
|
refine Symbol do
|
677
|
-
|
747
|
+
send INCLUDE_METH, Sequel::Postgres::JSONOpMethods
|
678
748
|
end
|
679
749
|
end
|
680
750
|
end
|
@@ -0,0 +1,372 @@
|
|
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
|
+
# Handle arrays of multirange types in bound variables.
|
224
|
+
def bound_variable_array(a)
|
225
|
+
case a
|
226
|
+
when PGMultiRange
|
227
|
+
"\"#{bound_variable_arg(a, nil)}\""
|
228
|
+
else
|
229
|
+
super
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Recognize the registered database multirange types.
|
234
|
+
def schema_column_type(db_type)
|
235
|
+
@pg_multirange_schema_types[db_type] || super
|
236
|
+
end
|
237
|
+
|
238
|
+
# Set the :ruby_default value if the default value is recognized as a multirange.
|
239
|
+
def schema_post_process(_)
|
240
|
+
super.each do |a|
|
241
|
+
h = a[1]
|
242
|
+
db_type = h[:db_type]
|
243
|
+
if @pg_multirange_schema_types[db_type] && h[:default] =~ /\A#{db_type}\(.*\)\z/
|
244
|
+
h[:ruby_default] = get(Sequel.lit(h[:default]))
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Given a value to typecast and the type of PGMultiRange subclass:
|
250
|
+
# * If given a PGMultiRange with a matching type, use it directly.
|
251
|
+
# * If given a PGMultiRange with a different type, return a PGMultiRange
|
252
|
+
# with the creator's type.
|
253
|
+
# * If given an Array, create a new PGMultiRange instance for it, typecasting
|
254
|
+
# each instance using the scalar_typecast_method.
|
255
|
+
def typecast_value_pg_multirange(value, creator, scalar_typecast_method=nil)
|
256
|
+
case value
|
257
|
+
when PGMultiRange
|
258
|
+
return value if value.db_type == creator.type
|
259
|
+
when Array
|
260
|
+
# nothing
|
261
|
+
else
|
262
|
+
raise Sequel::InvalidValue, "invalid value for multirange type: #{value.inspect}"
|
263
|
+
end
|
264
|
+
|
265
|
+
if scalar_typecast_method && respond_to?(scalar_typecast_method, true)
|
266
|
+
value = value.map{|v| send(scalar_typecast_method, v)}
|
267
|
+
end
|
268
|
+
PGMultiRange.new(value, creator.type)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# The type of this multirange (e.g. 'int4multirange').
|
273
|
+
attr_accessor :db_type
|
274
|
+
|
275
|
+
# Set the array of ranges to delegate to, and the database type.
|
276
|
+
def initialize(ranges, db_type)
|
277
|
+
super(ranges)
|
278
|
+
@db_type = db_type.to_s
|
279
|
+
end
|
280
|
+
|
281
|
+
# Append the multirange SQL to the given sql string.
|
282
|
+
def sql_literal_append(ds, sql)
|
283
|
+
sql << db_type << '('
|
284
|
+
joiner = nil
|
285
|
+
conversion_meth = nil
|
286
|
+
each do |range|
|
287
|
+
if joiner
|
288
|
+
sql << joiner
|
289
|
+
else
|
290
|
+
joiner = ', '
|
291
|
+
end
|
292
|
+
|
293
|
+
unless range.is_a?(PGRange)
|
294
|
+
conversion_meth ||= :"typecast_value_#{db_type.sub('multi', '')}"
|
295
|
+
range = ds.db.send(conversion_meth, range)
|
296
|
+
end
|
297
|
+
|
298
|
+
ds.literal_append(sql, range)
|
299
|
+
end
|
300
|
+
sql << ')'
|
301
|
+
end
|
302
|
+
|
303
|
+
# Return whether the value is inside any of the ranges in the multirange.
|
304
|
+
def cover?(value)
|
305
|
+
any?{|range| range.cover?(value)}
|
306
|
+
end
|
307
|
+
alias === cover?
|
308
|
+
|
309
|
+
# Don't consider multiranges with different database types equal.
|
310
|
+
def eql?(other)
|
311
|
+
if PGMultiRange === other
|
312
|
+
return false unless other.db_type == db_type
|
313
|
+
other = other.__getobj__
|
314
|
+
end
|
315
|
+
__getobj__.eql?(other)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Don't consider multiranges with different database types equal.
|
319
|
+
def ==(other)
|
320
|
+
return false if PGMultiRange === other && other.db_type != db_type
|
321
|
+
super
|
322
|
+
end
|
323
|
+
|
324
|
+
# Return a string containing the unescaped version of the multirange.
|
325
|
+
# Separated out for use by the bound argument code.
|
326
|
+
def unquoted_literal(ds)
|
327
|
+
val = String.new
|
328
|
+
val << "{"
|
329
|
+
|
330
|
+
joiner = nil
|
331
|
+
conversion_meth = nil
|
332
|
+
each do |range|
|
333
|
+
if joiner
|
334
|
+
val << joiner
|
335
|
+
else
|
336
|
+
joiner = ', '
|
337
|
+
end
|
338
|
+
|
339
|
+
unless range.is_a?(PGRange)
|
340
|
+
conversion_meth ||= :"typecast_value_#{db_type.sub('multi', '')}"
|
341
|
+
range = ds.db.send(conversion_meth, range)
|
342
|
+
end
|
343
|
+
|
344
|
+
val << range.unquoted_literal(ds)
|
345
|
+
end
|
346
|
+
|
347
|
+
val << "}"
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
module SQL::Builders
|
353
|
+
# Convert the object to a Postgres::PGMultiRange.
|
354
|
+
def pg_multirange(v, db_type)
|
355
|
+
case v
|
356
|
+
when Postgres::PGMultiRange
|
357
|
+
if v.db_type == db_type
|
358
|
+
v
|
359
|
+
else
|
360
|
+
Postgres::PGMultiRange.new(v, db_type)
|
361
|
+
end
|
362
|
+
when Array
|
363
|
+
Postgres::PGMultiRange.new(v, db_type)
|
364
|
+
else
|
365
|
+
# May not be defined unless the pg_range_ops extension is used
|
366
|
+
pg_range_op(v)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
Database.register_extension(:pg_multirange, Postgres::PGMultiRange::DatabaseMethods)
|
372
|
+
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
|
#
|
@@ -249,11 +245,7 @@ module Sequel
|
|
249
245
|
|
250
246
|
# Recognize the registered database range types.
|
251
247
|
def schema_column_type(db_type)
|
252
|
-
|
253
|
-
type
|
254
|
-
else
|
255
|
-
super
|
256
|
-
end
|
248
|
+
@pg_range_schema_types[db_type] || super
|
257
249
|
end
|
258
250
|
|
259
251
|
# Set the :ruby_default value if the default value is recognized as a range.
|