sequel 5.48.0 → 5.52.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +80 -0
  3. data/README.rdoc +12 -5
  4. data/doc/migration.rdoc +1 -1
  5. data/doc/opening_databases.rdoc +1 -1
  6. data/doc/postgresql.rdoc +8 -0
  7. data/doc/release_notes/5.49.0.txt +59 -0
  8. data/doc/release_notes/5.50.0.txt +78 -0
  9. data/doc/release_notes/5.51.0.txt +47 -0
  10. data/doc/release_notes/5.52.0.txt +87 -0
  11. data/doc/testing.rdoc +3 -1
  12. data/lib/sequel/adapters/ado/access.rb +1 -1
  13. data/lib/sequel/adapters/ado.rb +1 -1
  14. data/lib/sequel/adapters/amalgalite.rb +3 -5
  15. data/lib/sequel/adapters/ibmdb.rb +2 -2
  16. data/lib/sequel/adapters/jdbc/derby.rb +3 -0
  17. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  18. data/lib/sequel/adapters/jdbc.rb +9 -11
  19. data/lib/sequel/adapters/mysql.rb +80 -67
  20. data/lib/sequel/adapters/mysql2.rb +42 -44
  21. data/lib/sequel/adapters/odbc.rb +1 -1
  22. data/lib/sequel/adapters/oracle.rb +3 -3
  23. data/lib/sequel/adapters/postgres.rb +27 -29
  24. data/lib/sequel/adapters/shared/access.rb +2 -0
  25. data/lib/sequel/adapters/shared/db2.rb +2 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +4 -2
  27. data/lib/sequel/adapters/shared/postgres.rb +59 -6
  28. data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
  29. data/lib/sequel/adapters/shared/sqlite.rb +1 -1
  30. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  31. data/lib/sequel/adapters/sqlite.rb +16 -18
  32. data/lib/sequel/adapters/tinytds.rb +1 -1
  33. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  34. data/lib/sequel/ast_transformer.rb +6 -0
  35. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  36. data/lib/sequel/connection_pool/single.rb +6 -8
  37. data/lib/sequel/core.rb +17 -18
  38. data/lib/sequel/database/connecting.rb +2 -2
  39. data/lib/sequel/database/misc.rb +6 -0
  40. data/lib/sequel/database/query.rb +1 -1
  41. data/lib/sequel/dataset/actions.rb +2 -2
  42. data/lib/sequel/dataset/query.rb +45 -3
  43. data/lib/sequel/dataset/sql.rb +18 -9
  44. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  45. data/lib/sequel/extensions/core_refinements.rb +36 -11
  46. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  47. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  48. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  49. data/lib/sequel/extensions/inflector.rb +1 -1
  50. data/lib/sequel/extensions/migration.rb +4 -1
  51. data/lib/sequel/extensions/pagination.rb +1 -1
  52. data/lib/sequel/extensions/pg_array_ops.rb +1 -1
  53. data/lib/sequel/extensions/pg_extended_date_support.rb +1 -1
  54. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  55. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  56. data/lib/sequel/extensions/pg_interval.rb +1 -0
  57. data/lib/sequel/extensions/pg_json.rb +3 -5
  58. data/lib/sequel/extensions/pg_json_ops.rb +71 -1
  59. data/lib/sequel/extensions/pg_multirange.rb +372 -0
  60. data/lib/sequel/extensions/pg_range.rb +4 -12
  61. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  62. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  63. data/lib/sequel/extensions/s.rb +2 -1
  64. data/lib/sequel/extensions/server_block.rb +8 -12
  65. data/lib/sequel/extensions/sql_comments.rb +108 -3
  66. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  67. data/lib/sequel/extensions/string_agg.rb +1 -1
  68. data/lib/sequel/extensions/string_date_time.rb +19 -23
  69. data/lib/sequel/model/associations.rb +3 -1
  70. data/lib/sequel/model/base.rb +9 -13
  71. data/lib/sequel/model/inflections.rb +1 -1
  72. data/lib/sequel/plugins/auto_validations.rb +25 -5
  73. data/lib/sequel/plugins/column_encryption.rb +1 -1
  74. data/lib/sequel/plugins/composition.rb +1 -0
  75. data/lib/sequel/plugins/json_serializer.rb +2 -2
  76. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  77. data/lib/sequel/plugins/serialization.rb +1 -0
  78. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
  79. data/lib/sequel/plugins/sql_comments.rb +189 -0
  80. data/lib/sequel/plugins/static_cache.rb +1 -1
  81. data/lib/sequel/plugins/subclasses.rb +28 -11
  82. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
  83. data/lib/sequel/plugins/unused_associations.rb +2 -2
  84. data/lib/sequel/plugins/update_or_create.rb +1 -1
  85. data/lib/sequel/plugins/validation_helpers.rb +7 -1
  86. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  87. data/lib/sequel/sql.rb +1 -1
  88. data/lib/sequel/timezones.rb +12 -14
  89. data/lib/sequel/version.rb +1 -1
  90. 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(v)
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
- include Sequel::Postgres::HStoreOpMethods
409
+ send INCLUDE_METH, Sequel::Postgres::HStoreOpMethods
360
410
  end
361
411
  end
362
412
  end
@@ -197,7 +197,7 @@ end
197
197
  if defined?(Sequel::CoreRefinements)
198
198
  module Sequel::CoreRefinements
199
199
  refine Symbol do
200
- include Sequel::Postgres::InetOpMethods
200
+ send INCLUDE_METH, Sequel::Postgres::InetOpMethods
201
201
  end
202
202
  end
203
203
  end
@@ -32,6 +32,7 @@
32
32
  #
33
33
  # Related module: Sequel::Postgres::IntervalDatabaseMethods
34
34
 
35
+ require 'active_support'
35
36
  require 'active_support/duration'
36
37
 
37
38
  # :nocov:
@@ -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
- begin
391
- Sequel.parse_json(s)
392
- rescue Sequel.json_parser_error_class => e
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
- include Sequel::Postgres::JSONOpMethods
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), and
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
- # This extension integrates with Sequel's native postgres and jdbc/postgresql adapters, so
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 both PGRange and Range that use the standard Sequel literalization
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
- if type = @pg_range_schema_types[db_type]
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.