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