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.
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.