sequel 3.36.1 → 3.37.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +84 -0
- data/Rakefile +13 -0
- data/bin/sequel +12 -16
- data/doc/advanced_associations.rdoc +36 -67
- data/doc/association_basics.rdoc +11 -16
- data/doc/release_notes/3.37.0.txt +338 -0
- data/doc/schema_modification.rdoc +4 -0
- data/lib/sequel/adapters/jdbc/h2.rb +1 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +26 -8
- data/lib/sequel/adapters/mysql2.rb +4 -3
- data/lib/sequel/adapters/odbc/mssql.rb +2 -2
- data/lib/sequel/adapters/postgres.rb +4 -60
- data/lib/sequel/adapters/shared/mssql.rb +2 -1
- data/lib/sequel/adapters/shared/mysql.rb +0 -5
- data/lib/sequel/adapters/shared/postgres.rb +68 -2
- data/lib/sequel/adapters/shared/sqlite.rb +17 -1
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +12 -1
- data/lib/sequel/adapters/utils/pg_types.rb +76 -0
- data/lib/sequel/core.rb +13 -0
- data/lib/sequel/database/misc.rb +41 -1
- data/lib/sequel/database/schema_generator.rb +23 -10
- data/lib/sequel/database/schema_methods.rb +26 -4
- data/lib/sequel/dataset/graph.rb +2 -1
- data/lib/sequel/dataset/query.rb +62 -2
- data/lib/sequel/extensions/_pretty_table.rb +7 -3
- data/lib/sequel/extensions/arbitrary_servers.rb +5 -4
- data/lib/sequel/extensions/blank.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +13 -2
- data/lib/sequel/extensions/core_extensions.rb +6 -0
- data/lib/sequel/extensions/eval_inspect.rb +158 -0
- data/lib/sequel/extensions/inflector.rb +4 -0
- data/lib/sequel/extensions/looser_typecasting.rb +5 -4
- data/lib/sequel/extensions/migration.rb +4 -1
- data/lib/sequel/extensions/named_timezones.rb +4 -0
- data/lib/sequel/extensions/null_dataset.rb +4 -0
- data/lib/sequel/extensions/pagination.rb +4 -0
- data/lib/sequel/extensions/pg_array.rb +219 -168
- data/lib/sequel/extensions/pg_array_ops.rb +7 -2
- data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -4
- data/lib/sequel/extensions/pg_hstore.rb +3 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +7 -2
- data/lib/sequel/extensions/pg_inet.rb +28 -3
- data/lib/sequel/extensions/pg_interval.rb +192 -0
- data/lib/sequel/extensions/pg_json.rb +21 -9
- data/lib/sequel/extensions/pg_range.rb +487 -0
- data/lib/sequel/extensions/pg_range_ops.rb +122 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +3 -2
- data/lib/sequel/extensions/pretty_table.rb +12 -1
- data/lib/sequel/extensions/query.rb +4 -0
- data/lib/sequel/extensions/query_literals.rb +6 -6
- data/lib/sequel/extensions/schema_dumper.rb +39 -38
- data/lib/sequel/extensions/select_remove.rb +4 -0
- data/lib/sequel/extensions/server_block.rb +3 -2
- data/lib/sequel/extensions/split_array_nil.rb +65 -0
- data/lib/sequel/extensions/sql_expr.rb +4 -0
- data/lib/sequel/extensions/string_date_time.rb +4 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +9 -3
- data/lib/sequel/extensions/to_dot.rb +4 -0
- data/lib/sequel/model/associations.rb +150 -91
- data/lib/sequel/plugins/identity_map.rb +2 -2
- data/lib/sequel/plugins/list.rb +1 -0
- data/lib/sequel/plugins/many_through_many.rb +33 -32
- data/lib/sequel/plugins/nested_attributes.rb +11 -3
- data/lib/sequel/plugins/rcte_tree.rb +2 -2
- data/lib/sequel/plugins/schema.rb +1 -1
- data/lib/sequel/sql.rb +14 -14
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/mysql_spec.rb +25 -0
- data/spec/adapters/postgres_spec.rb +572 -28
- data/spec/adapters/sqlite_spec.rb +16 -1
- data/spec/core/database_spec.rb +61 -2
- data/spec/core/dataset_spec.rb +92 -0
- data/spec/core/expression_filters_spec.rb +12 -0
- data/spec/extensions/arbitrary_servers_spec.rb +1 -1
- data/spec/extensions/boolean_readers_spec.rb +25 -25
- data/spec/extensions/eval_inspect_spec.rb +58 -0
- data/spec/extensions/json_serializer_spec.rb +0 -6
- data/spec/extensions/list_spec.rb +1 -1
- data/spec/extensions/looser_typecasting_spec.rb +7 -7
- data/spec/extensions/many_through_many_spec.rb +81 -0
- data/spec/extensions/nested_attributes_spec.rb +21 -4
- data/spec/extensions/pg_array_ops_spec.rb +1 -11
- data/spec/extensions/pg_array_spec.rb +181 -90
- data/spec/extensions/pg_auto_parameterize_spec.rb +3 -3
- data/spec/extensions/pg_hstore_spec.rb +1 -3
- data/spec/extensions/pg_inet_spec.rb +6 -1
- data/spec/extensions/pg_interval_spec.rb +73 -0
- data/spec/extensions/pg_json_spec.rb +5 -9
- data/spec/extensions/pg_range_ops_spec.rb +49 -0
- data/spec/extensions/pg_range_spec.rb +372 -0
- data/spec/extensions/pg_statement_cache_spec.rb +1 -2
- data/spec/extensions/query_literals_spec.rb +1 -2
- data/spec/extensions/schema_dumper_spec.rb +48 -89
- data/spec/extensions/serialization_spec.rb +1 -5
- data/spec/extensions/server_block_spec.rb +2 -2
- data/spec/extensions/spec_helper.rb +12 -2
- data/spec/extensions/split_array_nil_spec.rb +24 -0
- data/spec/integration/associations_test.rb +4 -4
- data/spec/integration/database_test.rb +2 -2
- data/spec/integration/dataset_test.rb +4 -4
- data/spec/integration/eager_loader_test.rb +6 -6
- data/spec/integration/plugin_test.rb +2 -2
- data/spec/integration/spec_helper.rb +2 -2
- data/spec/model/association_reflection_spec.rb +5 -0
- data/spec/model/associations_spec.rb +156 -49
- data/spec/model/eager_loading_spec.rb +137 -2
- data/spec/model/model_spec.rb +10 -10
- metadata +15 -2
@@ -1,6 +1,11 @@
|
|
1
1
|
# The pg_array_ops extension adds support to Sequel's DSL to make
|
2
|
-
# it easier to call PostgreSQL array functions and operators.
|
3
|
-
#
|
2
|
+
# it easier to call PostgreSQL array functions and operators.
|
3
|
+
#
|
4
|
+
# To load the extension:
|
5
|
+
#
|
6
|
+
# Sequel.extension :pg_array_ops
|
7
|
+
#
|
8
|
+
# The most common usage is taking an object that represents an SQL
|
4
9
|
# identifier (such as a :symbol), and calling #pg_array on it:
|
5
10
|
#
|
6
11
|
# ia = :int_array_column.pg_array
|
@@ -9,7 +9,7 @@
|
|
9
9
|
# DB[:test].where(:a=>1)
|
10
10
|
# # SQL: SELECT * FROM test WHERE a = 1
|
11
11
|
#
|
12
|
-
# DB.
|
12
|
+
# DB.extension :pg_auto_parameterize
|
13
13
|
# DB[:test].where(:a=>1)
|
14
14
|
# # SQL: SELECT * FROM test WHERE a = $1 (args: [1])
|
15
15
|
#
|
@@ -66,16 +66,20 @@ module Sequel
|
|
66
66
|
module AutoParameterize
|
67
67
|
# String that holds an array of parameters
|
68
68
|
class StringWithArray < ::String
|
69
|
+
PLACEHOLDER = '$'.freeze
|
70
|
+
CAST = '::'.freeze
|
71
|
+
|
69
72
|
# The array of parameters used by this query.
|
70
73
|
attr_reader :args
|
71
74
|
|
72
75
|
# Add a new parameter to this query, which adds
|
73
76
|
# the parameter to the array of parameters, and an
|
74
77
|
# SQL placeholder to the query itself.
|
75
|
-
def add_arg(s, type)
|
78
|
+
def add_arg(s, type=nil)
|
76
79
|
@args ||= []
|
77
80
|
@args << s
|
78
|
-
self <<
|
81
|
+
self << PLACEHOLDER << @args.length.to_s
|
82
|
+
self << CAST << type.to_s if type
|
79
83
|
end
|
80
84
|
|
81
85
|
# Show args when the string is inspected
|
@@ -120,7 +124,7 @@ module Sequel
|
|
120
124
|
when Sequel::SQL::Blob
|
121
125
|
sql.add_arg(v, :bytea)
|
122
126
|
else
|
123
|
-
sql.add_arg(v
|
127
|
+
sql.add_arg(v)
|
124
128
|
end
|
125
129
|
when Bignum
|
126
130
|
sql.add_arg(v, :int8)
|
@@ -166,4 +170,6 @@ module Sequel
|
|
166
170
|
end
|
167
171
|
end
|
168
172
|
end
|
173
|
+
|
174
|
+
Database.register_extension(:pg_auto_parameterize, Postgres::AutoParameterize::DatabaseMethods)
|
169
175
|
end
|
@@ -67,7 +67,7 @@
|
|
67
67
|
# recognizes and correctly handles the hstore columns, which you can
|
68
68
|
# do by:
|
69
69
|
#
|
70
|
-
# DB.
|
70
|
+
# DB.extension :pg_hstore
|
71
71
|
#
|
72
72
|
# If you are not using the native postgres adapter, you probably
|
73
73
|
# also want to use the typecast_on_load plugin in the model, and
|
@@ -281,6 +281,8 @@ module Sequel
|
|
281
281
|
# Associate the named types by default.
|
282
282
|
PG_NAMED_TYPES[:hstore] = HStore.method(:parse)
|
283
283
|
end
|
284
|
+
|
285
|
+
Database.register_extension(:pg_hstore, Postgres::HStore::DatabaseMethods)
|
284
286
|
end
|
285
287
|
|
286
288
|
class Hash
|
@@ -1,6 +1,11 @@
|
|
1
1
|
# The pg_hstore_ops extension adds support to Sequel's DSL to make
|
2
|
-
# it easier to call PostgreSQL hstore functions and operators.
|
3
|
-
#
|
2
|
+
# it easier to call PostgreSQL hstore functions and operators.
|
3
|
+
#
|
4
|
+
# To load the extension:
|
5
|
+
#
|
6
|
+
# Sequel.extension :pg_hstore_ops
|
7
|
+
#
|
8
|
+
# The most common usage is taking an object that represents an SQL
|
4
9
|
# expression (such as a :symbol), and calling #hstore on it:
|
5
10
|
#
|
6
11
|
# h = :hstore_column.hstore
|
@@ -8,17 +8,26 @@
|
|
8
8
|
# After loading the extension, you should extend your dataset
|
9
9
|
# with a module so that it correctly handles the inet/cidr type:
|
10
10
|
#
|
11
|
-
# DB.
|
11
|
+
# DB.extension :pg_inet
|
12
12
|
#
|
13
13
|
# If you are not using the native postgres adapter, you probably
|
14
14
|
# also want to use the typecast_on_load plugin in the model, and
|
15
15
|
# set it to typecast the inet/cidr column(s) on load.
|
16
16
|
#
|
17
|
+
# This extension integrates with the pg_array extension. If you plan
|
18
|
+
# to use the inet[] or cidr[] types, load the pg_array extension before
|
19
|
+
# the pg_inet extension:
|
20
|
+
#
|
21
|
+
# DB.extension :pg_array, :pg_inet
|
22
|
+
#
|
17
23
|
# This extension does not add special support for the macaddr
|
18
24
|
# type. Ruby doesn't have a stdlib class that represents mac
|
19
|
-
# addresses, so these will still be returned as strings.
|
25
|
+
# addresses, so these will still be returned as strings. The exception
|
26
|
+
# to this is that the pg_array extension integration will recognize
|
27
|
+
# macaddr[] types return them as arrays of strings.
|
20
28
|
|
21
29
|
require 'ipaddr'
|
30
|
+
Sequel.require 'adapters/utils/pg_types'
|
22
31
|
|
23
32
|
module Sequel
|
24
33
|
module Postgres
|
@@ -56,6 +65,16 @@ module Sequel
|
|
56
65
|
|
57
66
|
private
|
58
67
|
|
68
|
+
# Handle inet[]/cidr[] types in bound variables.
|
69
|
+
def bound_variable_array(a)
|
70
|
+
case a
|
71
|
+
when IPAddr
|
72
|
+
"\"#{a.to_s}/#{a.instance_variable_get(:@mask_addr).to_s(2).count('1')}\""
|
73
|
+
else
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
59
78
|
# Typecast the given value to an IPAddr object.
|
60
79
|
def typecast_value_ipaddr(value)
|
61
80
|
case value
|
@@ -83,7 +102,13 @@ module Sequel
|
|
83
102
|
end
|
84
103
|
end
|
85
104
|
|
86
|
-
PG_TYPES = {} unless defined?(PG_TYPES)
|
87
105
|
PG_TYPES[869] = PG_TYPES[650] = IPAddr.method(:new)
|
106
|
+
if defined?(PGArray) && PGArray.respond_to?(:register)
|
107
|
+
PGArray.register('inet', :oid=>1041, :scalar_oid=>869)
|
108
|
+
PGArray.register('cidr', :oid=>651, :scalar_oid=>650)
|
109
|
+
PGArray.register('macaddr', :oid=>1040)
|
110
|
+
end
|
88
111
|
end
|
112
|
+
|
113
|
+
Database.register_extension(:pg_inet, Postgres::InetDatabaseMethods)
|
89
114
|
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# The pg_interval extension adds support for PostgreSQL's interval type.
|
2
|
+
#
|
3
|
+
# This extension integrates with Sequel's native postgres adapter, so
|
4
|
+
# that when interval type values are retrieved, they are parsed and returned
|
5
|
+
# as instances of ActiveSupport::Duration.
|
6
|
+
#
|
7
|
+
# In addition to the parser, this extension adds literalizers for
|
8
|
+
# ActiveSupport::Duration ithat use the standard Sequel literalization
|
9
|
+
# callbacks, so they work on all adapters.
|
10
|
+
#
|
11
|
+
# If you would like to use interval columns in your model objects, you
|
12
|
+
# probably want to modify the typecasting so that it
|
13
|
+
# recognizes and correctly handles the interval columns, which you can
|
14
|
+
# do by:
|
15
|
+
#
|
16
|
+
# DB.extension :pg_interval
|
17
|
+
#
|
18
|
+
# If you are not using the native postgres adapter, you probably
|
19
|
+
# also want to use the typecast_on_load plugin in the model, and
|
20
|
+
# set it to typecast the interval type column(s) on load.
|
21
|
+
#
|
22
|
+
# This extension integrates with the pg_array extension. If you plan
|
23
|
+
# to use arrays of interval types, load the pg_array extension before the
|
24
|
+
# pg_interval extension:
|
25
|
+
#
|
26
|
+
# DB.extension :pg_array, :pg_interval
|
27
|
+
#
|
28
|
+
# The parser this extension uses requires that IntervalStyle for PostgreSQL
|
29
|
+
# is set to postgres (the default setting). If IntervalStyle is changed from
|
30
|
+
# the default setting, the parser will probably not work. The parser used is
|
31
|
+
# very simple, and is only designed to parse PostgreSQL's default output
|
32
|
+
# format, it is not designed to support all input formats that PostgreSQL
|
33
|
+
# supports.
|
34
|
+
|
35
|
+
require 'active_support/duration'
|
36
|
+
Sequel.require 'adapters/utils/pg_types'
|
37
|
+
|
38
|
+
module Sequel
|
39
|
+
module Postgres
|
40
|
+
module IntervalDatabaseMethods
|
41
|
+
EMPTY_INTERVAL = '0'.freeze
|
42
|
+
DURATION_UNITS = [:years, :months, :days, :minutes, :seconds].freeze
|
43
|
+
|
44
|
+
# Return an unquoted string version of the duration object suitable for
|
45
|
+
# use as a bound variable.
|
46
|
+
def self.literal_duration(duration)
|
47
|
+
h = Hash.new(0)
|
48
|
+
duration.parts.each{|unit, value| h[unit] += value}
|
49
|
+
s = ''
|
50
|
+
|
51
|
+
DURATION_UNITS.each do |unit|
|
52
|
+
if (v = h[unit]) != 0
|
53
|
+
s << "#{v.is_a?(Integer) ? v : sprintf('%0.6f', v)} #{unit} "
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if s.empty?
|
58
|
+
EMPTY_INTERVAL
|
59
|
+
else
|
60
|
+
s
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates callable objects that convert strings into ActiveSupport::Duration instances.
|
65
|
+
class Parser
|
66
|
+
# Regexp that parses the full range of PostgreSQL interval type output.
|
67
|
+
PARSER = /\A([+-]?\d+ years?\s?)?([+-]?\d+ mons?\s?)?([+-]?\d+ days?\s?)?(?:(?:([+-])?(\d\d):(\d\d):(\d\d(\.\d+)?))|([+-]?\d+ hours?\s?)?([+-]?\d+ mins?\s?)?([+-]?\d+(\.\d+)? secs?\s?)?)?\z/o
|
68
|
+
|
69
|
+
# Parse the interval input string into an ActiveSupport::Duration instance.
|
70
|
+
def call(string)
|
71
|
+
raise(InvalidValue, "invalid or unhandled interval format: #{string.inspect}") unless matches = PARSER.match(string)
|
72
|
+
|
73
|
+
value = 0
|
74
|
+
parts = []
|
75
|
+
|
76
|
+
if v = matches[1]
|
77
|
+
v = v.to_i
|
78
|
+
value += 31557600 * v
|
79
|
+
parts << [:years, v]
|
80
|
+
end
|
81
|
+
if v = matches[2]
|
82
|
+
v = v.to_i
|
83
|
+
value += 2592000 * v
|
84
|
+
parts << [:months, v]
|
85
|
+
end
|
86
|
+
if v = matches[3]
|
87
|
+
v = v.to_i
|
88
|
+
value += 86400 * v
|
89
|
+
parts << [:days, v]
|
90
|
+
end
|
91
|
+
if matches[5]
|
92
|
+
seconds = matches[5].to_i * 3600 + matches[6].to_i * 60
|
93
|
+
seconds += matches[8] ? matches[7].to_f : matches[7].to_i
|
94
|
+
seconds *= -1 if matches[4] == '-'
|
95
|
+
value += seconds
|
96
|
+
parts << [:seconds, seconds]
|
97
|
+
elsif matches[9] || matches[10] || matches[11]
|
98
|
+
seconds = 0
|
99
|
+
if v = matches[9]
|
100
|
+
seconds += v.to_i * 3600
|
101
|
+
end
|
102
|
+
if v = matches[10]
|
103
|
+
seconds += v.to_i * 60
|
104
|
+
end
|
105
|
+
if v = matches[11]
|
106
|
+
seconds += matches[12] ? v.to_f : v.to_i
|
107
|
+
end
|
108
|
+
value += seconds
|
109
|
+
parts << [:seconds, seconds]
|
110
|
+
end
|
111
|
+
|
112
|
+
ActiveSupport::Duration.new(value, parts)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Single instance of Parser used for parsing, to save on memory (since the parser has no state).
|
117
|
+
PARSER = Parser.new
|
118
|
+
|
119
|
+
# Reset the conversion procs if using the native postgres adapter,
|
120
|
+
# and extend the datasets to correctly literalize ActiveSupport::Duration values.
|
121
|
+
def self.extended(db)
|
122
|
+
db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
|
123
|
+
db.extend_datasets(IntervalDatasetMethods)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Handle ActiveSupport::Duration values in bound variables
|
127
|
+
def bound_variable_arg(arg, conn)
|
128
|
+
case arg
|
129
|
+
when ActiveSupport::Duration
|
130
|
+
IntervalDatabaseMethods.literal_duration(arg)
|
131
|
+
else
|
132
|
+
super
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
# Handle arrays of interval types in bound variables.
|
139
|
+
def bound_variable_array(a)
|
140
|
+
case a
|
141
|
+
when ActiveSupport::Duration
|
142
|
+
"\"#{IntervalDatabaseMethods.literal_duration(a)}\""
|
143
|
+
else
|
144
|
+
super
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Typecast value correctly to an ActiveSupport::Duration instance.
|
149
|
+
# If already an ActiveSupport::Duration, return it.
|
150
|
+
# If a numeric argument is given, assume it represents a number
|
151
|
+
# of seconds, and create a new ActiveSupport::Duration instance
|
152
|
+
# representing that number of seconds.
|
153
|
+
# If a String, assume it is in PostgreSQL interval output format
|
154
|
+
# and attempt to parse it.
|
155
|
+
def typecast_value_interval(value)
|
156
|
+
case value
|
157
|
+
when ActiveSupport::Duration
|
158
|
+
value
|
159
|
+
when Numeric
|
160
|
+
ActiveSupport::Duration.new(value, [[:seconds, value]])
|
161
|
+
when String
|
162
|
+
PARSER.call(value)
|
163
|
+
else
|
164
|
+
raise Sequel::InvalidValue, "invalid value for interval type: #{value.inspect}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
module IntervalDatasetMethods
|
170
|
+
CAST_INTERVAL = '::interval'.freeze
|
171
|
+
|
172
|
+
# Handle literalization of ActiveSupport::Duration objects, treating them as
|
173
|
+
# PostgreSQL intervals.
|
174
|
+
def literal_other_append(sql, v)
|
175
|
+
case v
|
176
|
+
when ActiveSupport::Duration
|
177
|
+
literal_append(sql, IntervalDatabaseMethods.literal_duration(v))
|
178
|
+
sql << CAST_INTERVAL
|
179
|
+
else
|
180
|
+
super
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
PG_TYPES[1186] = Postgres::IntervalDatabaseMethods::PARSER
|
186
|
+
if defined?(PGArray) && PGArray.respond_to?(:register)
|
187
|
+
PGArray.register('interval', :oid=>1187, :scalar_oid=>1186)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
Database.register_extension(:pg_interval, Postgres::IntervalDatabaseMethods)
|
192
|
+
end
|
@@ -32,25 +32,23 @@
|
|
32
32
|
# so that it recognizes and correctly handles the json type, which
|
33
33
|
# you can do by:
|
34
34
|
#
|
35
|
-
# DB.
|
35
|
+
# DB.extension :pg_json
|
36
36
|
#
|
37
37
|
# If you are not using the native postgres adapter, you probably
|
38
38
|
# also want to use the typecast_on_load plugin in the model, and
|
39
39
|
# set it to typecast the json column(s) on load.
|
40
40
|
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# this extension support that type in the native adapter, do the
|
45
|
-
# following after loading this extension:
|
41
|
+
# This extension integrates with the pg_array extension. If you plan
|
42
|
+
# to use the json[] type, load the pg_array extension before the
|
43
|
+
# pg_json extension:
|
46
44
|
#
|
47
|
-
#
|
48
|
-
# Sequel::Postgres::PG_NAMED_TYPES[:json] = Sequel::Postgres::PG_TYPES[114]
|
45
|
+
# DB.extension :pg_array, :pg_json
|
49
46
|
#
|
50
47
|
# This extension requires both the json and delegate libraries.
|
51
48
|
|
52
49
|
require 'delegate'
|
53
50
|
require 'json'
|
51
|
+
Sequel.require 'adapters/utils/pg_types'
|
54
52
|
|
55
53
|
module Sequel
|
56
54
|
module Postgres
|
@@ -132,6 +130,16 @@ module Sequel
|
|
132
130
|
|
133
131
|
private
|
134
132
|
|
133
|
+
# Handle json[] types in bound variables.
|
134
|
+
def bound_variable_array(a)
|
135
|
+
case a
|
136
|
+
when JSONHash, JSONArray
|
137
|
+
"\"#{a.to_json.gsub('"', '\\"')}\""
|
138
|
+
else
|
139
|
+
super
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
135
143
|
# Given a value to typecast to the json column
|
136
144
|
# * If given a JSONArray or JSONHash, just return the value
|
137
145
|
# * If given an Array, return a JSONArray
|
@@ -154,9 +162,13 @@ module Sequel
|
|
154
162
|
end
|
155
163
|
end
|
156
164
|
|
157
|
-
PG_TYPES = {} unless defined?(PG_TYPES)
|
158
165
|
PG_TYPES[114] = JSONDatabaseMethods.method(:parse_json)
|
166
|
+
if defined?(PGArray) && PGArray.respond_to?(:register)
|
167
|
+
PGArray.register('json', :oid=>199, :scalar_oid=>114)
|
168
|
+
end
|
159
169
|
end
|
170
|
+
|
171
|
+
Database.register_extension(:pg_json, Postgres::JSONDatabaseMethods)
|
160
172
|
end
|
161
173
|
|
162
174
|
class Array
|
@@ -0,0 +1,487 @@
|
|
1
|
+
# The pg_range extension adds support for the PostgreSQL 9.2+ range
|
2
|
+
# types to Sequel. PostgreSQL range types are similar to ruby's
|
3
|
+
# Range class, representating an array of values. However, they
|
4
|
+
# are more flexible than ruby's ranges, allowing exclusive beginnings
|
5
|
+
# and endings (ruby's range only allows exclusive endings), and
|
6
|
+
# unbounded beginnings and endings (which ruby's range does not
|
7
|
+
# support).
|
8
|
+
#
|
9
|
+
# This extension integrates with Sequel's native postgres adapter, so
|
10
|
+
# that when range type values are retrieved, they are parsed and returned
|
11
|
+
# as instances of Sequel::Postgres::PGRange. PGRange mostly acts
|
12
|
+
# like a Range, but it's not a Range as not all PostgreSQL range
|
13
|
+
# type values would be valid ruby ranges. If the range type value
|
14
|
+
# you are using is a valid ruby range, you can call PGRange#to_range
|
15
|
+
# to get a Range. However, if you call PGRange#to_range on a range
|
16
|
+
# type value uses features that ruby's Range does not support, an
|
17
|
+
# exception will be raised.
|
18
|
+
#
|
19
|
+
# In addition to the parser, this extension comes with literalizers
|
20
|
+
# for both PGRange and Range that use the standard Sequel literalization
|
21
|
+
# callbacks, so they work on all adapters.
|
22
|
+
#
|
23
|
+
# To turn an existing Range into a PGRange:
|
24
|
+
#
|
25
|
+
# range.pg_range
|
26
|
+
#
|
27
|
+
# You may want to specify a specific range type:
|
28
|
+
#
|
29
|
+
# range.pg_range(:daterange)
|
30
|
+
#
|
31
|
+
# If you specify the range database type, Sequel will automatically cast
|
32
|
+
# the value to that type when literalizing.
|
33
|
+
#
|
34
|
+
# If you would like to use range columns in your model objects, you
|
35
|
+
# probably want to modify the schema parsing/typecasting so that it
|
36
|
+
# recognizes and correctly handles the range type columns, which you can
|
37
|
+
# do by:
|
38
|
+
#
|
39
|
+
# DB.extension :pg_range
|
40
|
+
#
|
41
|
+
# If you are not using the native postgres adapter, you probably
|
42
|
+
# also want to use the typecast_on_load plugin in the model, and
|
43
|
+
# set it to typecast the range type column(s) on load.
|
44
|
+
#
|
45
|
+
# This extension integrates with the pg_array extension. If you plan
|
46
|
+
# to use arrays of range types, load the pg_array extension before the
|
47
|
+
# pg_range extension:
|
48
|
+
#
|
49
|
+
# DB.extension :pg_array, :pg_range
|
50
|
+
|
51
|
+
Sequel.require 'adapters/utils/pg_types'
|
52
|
+
|
53
|
+
module Sequel
|
54
|
+
module Postgres
|
55
|
+
class PGRange
|
56
|
+
|
57
|
+
# Map of string database type names to type symbols (e.g. 'int4range' => :int4range),
|
58
|
+
# used in the schema parsing.
|
59
|
+
RANGE_TYPES = {}
|
60
|
+
|
61
|
+
EMPTY = 'empty'.freeze
|
62
|
+
EMPTY_STRING = ''.freeze
|
63
|
+
QUOTED_EMPTY_STRING = '""'.freeze
|
64
|
+
OPEN_PAREN = "(".freeze
|
65
|
+
CLOSE_PAREN = ")".freeze
|
66
|
+
OPEN_BRACKET = "[".freeze
|
67
|
+
CLOSE_BRACKET = "]".freeze
|
68
|
+
ESCAPE_RE = /("|,|\\|\[|\]|\(|\))/.freeze
|
69
|
+
ESCAPE_REPLACE = '\\\\\1'.freeze
|
70
|
+
CAST = '::'.freeze
|
71
|
+
|
72
|
+
# Registers a range type that the extension should handle. Makes a Database instance that
|
73
|
+
# has been extended with DatabaseMethods recognize the range type given and set up the
|
74
|
+
# appropriate typecasting. Also sets up automatic typecasting for the native postgres
|
75
|
+
# adapter, so that on retrieval, the values are automatically converted to PGRange instances.
|
76
|
+
# The db_type argument should be the name of the range type. Accepts the following options:
|
77
|
+
#
|
78
|
+
# :converter :: A callable object (e.g. Proc), that is called with the start or end of the range
|
79
|
+
# (usually a string), and should return the appropriate typecasted object.
|
80
|
+
# :oid :: The PostgreSQL OID for the range type. This is used by the Sequel postgres adapter
|
81
|
+
# to set up automatic type conversion on retrieval from the database.
|
82
|
+
# :subtype_oid :: Should be the PostgreSQL OID for the range's subtype. If given,
|
83
|
+
# automatically sets the :converter option by looking for scalar conversion
|
84
|
+
# proc.
|
85
|
+
#
|
86
|
+
# If a block is given, it is treated as the :converter option.
|
87
|
+
def self.register(db_type, opts={}, &block)
|
88
|
+
db_type = db_type.to_s.dup.freeze
|
89
|
+
|
90
|
+
if converter = opts[:converter]
|
91
|
+
raise Error, "can't provide both a block and :converter option to register" if block
|
92
|
+
else
|
93
|
+
converter = block
|
94
|
+
end
|
95
|
+
|
96
|
+
if soid = opts[:subtype_oid]
|
97
|
+
raise Error, "can't provide both a converter and :scalar_oid option to register" if converter
|
98
|
+
raise Error, "no conversion proc for :scalar_oid=>#{soid.inspect} in PG_TYPES" unless converter = PG_TYPES[soid]
|
99
|
+
end
|
100
|
+
|
101
|
+
parser = Parser.new(db_type, converter)
|
102
|
+
|
103
|
+
RANGE_TYPES[db_type] = db_type.to_sym
|
104
|
+
|
105
|
+
DatabaseMethods.define_range_typecast_method(db_type, parser)
|
106
|
+
|
107
|
+
if oid = opts[:oid]
|
108
|
+
Sequel::Postgres::PG_TYPES[oid] = parser
|
109
|
+
end
|
110
|
+
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
# Creates callable objects that convert strings into PGRange instances.
|
115
|
+
class Parser
|
116
|
+
# Regexp that parses the full range of PostgreSQL range type output,
|
117
|
+
# except for empty ranges.
|
118
|
+
PARSER = /\A(\[|\()("((?:\\"|[^"])*)"|[^"]*),("((?:\\"|[^"])*)"|[^"]*)(\]|\))\z/o
|
119
|
+
|
120
|
+
REPLACE_RE = /\\(.)/.freeze
|
121
|
+
REPLACE_WITH = '\1'.freeze
|
122
|
+
|
123
|
+
# The database range type for this parser (e.g. 'int4range'),
|
124
|
+
# automatically setting the db_type for the returned PGRange instances.
|
125
|
+
attr_reader :db_type
|
126
|
+
|
127
|
+
# A callable object to convert the beginning and ending of the range into
|
128
|
+
# the appropriate ruby type.
|
129
|
+
attr_reader :converter
|
130
|
+
|
131
|
+
# Set the db_type and converter on initialization.
|
132
|
+
def initialize(db_type, converter=nil)
|
133
|
+
@db_type = db_type.to_s.dup.freeze if db_type
|
134
|
+
@converter = converter
|
135
|
+
end
|
136
|
+
|
137
|
+
# Parse the range type input string into a PGRange value.
|
138
|
+
def call(string)
|
139
|
+
if string == EMPTY
|
140
|
+
return PGRange.empty(db_type)
|
141
|
+
end
|
142
|
+
|
143
|
+
raise(InvalidValue, "invalid or unhandled range format: #{string.inspect}") unless matches = PARSER.match(string)
|
144
|
+
|
145
|
+
exclude_begin = matches[1] == '('
|
146
|
+
exclude_end = matches[6] == ')'
|
147
|
+
|
148
|
+
# If the input is quoted, it needs to be unescaped. Also, quoted input isn't
|
149
|
+
# checked for emptiness, since the empty quoted string is considered an
|
150
|
+
# element that happens to be the empty string, while an unquoted empty string
|
151
|
+
# is considered unbounded.
|
152
|
+
#
|
153
|
+
# While PostgreSQL allows pure escaping for input (without quoting), it appears
|
154
|
+
# to always use the quoted output form when characters need to be escaped, so
|
155
|
+
# there isn't a need to unescape unquoted output.
|
156
|
+
if beg = matches[3]
|
157
|
+
beg.gsub!(REPLACE_RE, REPLACE_WITH)
|
158
|
+
else
|
159
|
+
beg = matches[2] unless matches[2].empty?
|
160
|
+
end
|
161
|
+
if en = matches[5]
|
162
|
+
en.gsub!(REPLACE_RE, REPLACE_WITH)
|
163
|
+
else
|
164
|
+
en = matches[4] unless matches[4].empty?
|
165
|
+
end
|
166
|
+
|
167
|
+
if c = converter
|
168
|
+
beg = c.call(beg) if beg
|
169
|
+
en = c.call(en) if en
|
170
|
+
end
|
171
|
+
|
172
|
+
PGRange.new(beg, en, :exclude_begin=>exclude_begin, :exclude_end=>exclude_end, :db_type=>db_type)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
module DatabaseMethods
|
177
|
+
# Reset the conversion procs if using the native postgres adapter,
|
178
|
+
# and extend the datasets to correctly literalize ruby Range values.
|
179
|
+
def self.extended(db)
|
180
|
+
db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
|
181
|
+
db.extend_datasets(DatasetMethods)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Define a private range typecasting method for the given type that uses
|
185
|
+
# the parser argument to do the type conversion.
|
186
|
+
def self.define_range_typecast_method(type, parser)
|
187
|
+
meth = :"typecast_value_#{type}"
|
188
|
+
define_method(meth){|v| typecast_value_pg_range(v, parser)}
|
189
|
+
private meth
|
190
|
+
end
|
191
|
+
|
192
|
+
# Handle Range and PGRange values in bound variables
|
193
|
+
def bound_variable_arg(arg, conn)
|
194
|
+
case arg
|
195
|
+
when PGRange
|
196
|
+
arg.unquoted_literal(schema_utility_dataset)
|
197
|
+
when Range
|
198
|
+
PGRange.from_range(arg).unquoted_literal(schema_utility_dataset)
|
199
|
+
else
|
200
|
+
super
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
# Handle arrays of range types in bound variables.
|
207
|
+
def bound_variable_array(a)
|
208
|
+
case a
|
209
|
+
when PGRange, Range
|
210
|
+
"\"#{bound_variable_arg(a, nil)}\""
|
211
|
+
else
|
212
|
+
super
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Manually override the typecasting for tsrange and tstzrange types so that
|
217
|
+
# they use the database's timezone instead of the global Sequel
|
218
|
+
# timezone.
|
219
|
+
def get_conversion_procs(conn)
|
220
|
+
procs = super
|
221
|
+
|
222
|
+
converter = method(:to_application_timestamp)
|
223
|
+
procs[3908] = Parser.new("tsrange", converter)
|
224
|
+
procs[3910] = Parser.new("tstzrange", converter)
|
225
|
+
if defined?(PGArray::Creator)
|
226
|
+
procs[3909] = PGArray::Creator.new("tsrange", procs[3908])
|
227
|
+
procs[3911] = PGArray::Creator.new("tstzrange", procs[3910])
|
228
|
+
end
|
229
|
+
|
230
|
+
procs
|
231
|
+
end
|
232
|
+
|
233
|
+
# Recognize the registered database range types.
|
234
|
+
def schema_column_type(db_type)
|
235
|
+
if type = RANGE_TYPES[db_type]
|
236
|
+
type
|
237
|
+
else
|
238
|
+
super
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Typecast value correctly to a PGRange. If already an
|
243
|
+
# PGRange instance with the same db_type, return as is.
|
244
|
+
# If a PGRange with a different subtype, return a new
|
245
|
+
# PGRange with the same values and the expected subtype.
|
246
|
+
# If a Range object, create a PGRange with the given
|
247
|
+
# db_type. If a string, assume it is in PostgreSQL
|
248
|
+
# output format and parse it using the parser.
|
249
|
+
def typecast_value_pg_range(value, parser)
|
250
|
+
case value
|
251
|
+
when PGRange
|
252
|
+
if value.db_type.to_s == parser.db_type
|
253
|
+
value
|
254
|
+
elsif value.empty?
|
255
|
+
PGRange.empty(parser.db_type)
|
256
|
+
else
|
257
|
+
PGRange.new(value.begin, value.end, :exclude_begin=>value.exclude_begin?, :exclude_end=>value.exclude_end?, :db_type=>parser.db_type)
|
258
|
+
end
|
259
|
+
when Range
|
260
|
+
PGRange.from_range(value, parser.db_type)
|
261
|
+
when String
|
262
|
+
parser.call(value)
|
263
|
+
else
|
264
|
+
raise Sequel::InvalidValue, "invalid value for range type: #{value.inspect}"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
module DatasetMethods
|
270
|
+
# Handle literalization of ruby Range objects, treating them as
|
271
|
+
# PostgreSQL ranges.
|
272
|
+
def literal_other_append(sql, v)
|
273
|
+
case v
|
274
|
+
when Range
|
275
|
+
super(sql, Sequel::Postgres::PGRange.from_range(v))
|
276
|
+
else
|
277
|
+
super
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
include Enumerable
|
283
|
+
|
284
|
+
# The beginning of the range. If nil, the range has an unbounded beginning.
|
285
|
+
attr_reader :begin
|
286
|
+
|
287
|
+
# The end of the range. If nil, the range has an unbounded ending.
|
288
|
+
attr_reader :end
|
289
|
+
|
290
|
+
# The PostgreSQL database type for the range (e.g. 'int4range').
|
291
|
+
attr_reader :db_type
|
292
|
+
|
293
|
+
# Create a new PGRange instance using the beginning and ending of the ruby Range,
|
294
|
+
# with the given db_type.
|
295
|
+
def self.from_range(range, db_type=nil)
|
296
|
+
new(range.begin, range.end, :exclude_end=>range.exclude_end?, :db_type=>db_type)
|
297
|
+
end
|
298
|
+
|
299
|
+
# Create an empty PGRange with the given database type.
|
300
|
+
def self.empty(db_type=nil)
|
301
|
+
new(nil, nil, :empty=>true, :db_type=>db_type)
|
302
|
+
end
|
303
|
+
|
304
|
+
# Initialize a new PGRange instance. Accepts the following options:
|
305
|
+
#
|
306
|
+
# :db_type :: The PostgreSQL database type for the range.
|
307
|
+
# :empty :: Whether the range is empty (has no points)
|
308
|
+
# :exclude_begin :: Whether the beginning element is excluded from the range.
|
309
|
+
# :exclude_end :: Whether the ending element is excluded from the range.
|
310
|
+
def initialize(beg, en, opts={})
|
311
|
+
@begin = beg
|
312
|
+
@end = en
|
313
|
+
@empty = !!opts[:empty]
|
314
|
+
@exclude_begin = !!opts[:exclude_begin]
|
315
|
+
@exclude_end = !!opts[:exclude_end]
|
316
|
+
@db_type = opts[:db_type]
|
317
|
+
if @empty
|
318
|
+
raise(Error, 'cannot have an empty range with either a beginning or ending') unless @begin.nil? && @end.nil? && opts[:exclude_begin].nil? && opts[:exclude_end].nil?
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Delegate to the ruby range object so that the object mostly acts like a range.
|
323
|
+
range_methods = %w'each last first step'
|
324
|
+
range_methods << 'cover?' if RUBY_VERSION >= '1.9'
|
325
|
+
range_methods.each do |m|
|
326
|
+
class_eval("def #{m}(*a, &block) to_range.#{m}(*a, &block) end", __FILE__, __LINE__)
|
327
|
+
end
|
328
|
+
|
329
|
+
# Consider the receiver equal to other PGRange instances with the
|
330
|
+
# same beginning, ending, exclusions, and database type. Also consider
|
331
|
+
# it equal to Range instances if this PGRange can be converted to a
|
332
|
+
# a Range and those ranges are equal.
|
333
|
+
def eql?(other)
|
334
|
+
case other
|
335
|
+
when PGRange
|
336
|
+
if db_type == other.db_type
|
337
|
+
if empty?
|
338
|
+
other.empty?
|
339
|
+
elsif other.empty?
|
340
|
+
false
|
341
|
+
else
|
342
|
+
[:@begin, :@end, :@exclude_begin, :@exclude_end].all?{|v| instance_variable_get(v) == other.instance_variable_get(v)}
|
343
|
+
end
|
344
|
+
else
|
345
|
+
false
|
346
|
+
end
|
347
|
+
when Range
|
348
|
+
if valid_ruby_range?
|
349
|
+
to_range.eql?(other)
|
350
|
+
else
|
351
|
+
false
|
352
|
+
end
|
353
|
+
else
|
354
|
+
false
|
355
|
+
end
|
356
|
+
end
|
357
|
+
alias == eql?
|
358
|
+
|
359
|
+
# Allow PGRange values in case statements, where they return true if they
|
360
|
+
# are equal to each other using eql?, or if this PGRange can be converted
|
361
|
+
# to a Range, delegating to that range.
|
362
|
+
def ===(other)
|
363
|
+
if eql?(other)
|
364
|
+
true
|
365
|
+
else
|
366
|
+
if valid_ruby_range?
|
367
|
+
to_range === other
|
368
|
+
else
|
369
|
+
false
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
# Whether this range is empty (has no points).
|
375
|
+
def empty?
|
376
|
+
@empty
|
377
|
+
end
|
378
|
+
|
379
|
+
# Whether the beginning element is excluded from the range.
|
380
|
+
def exclude_begin?
|
381
|
+
@exclude_begin
|
382
|
+
end
|
383
|
+
|
384
|
+
# Whether the ending element is excluded from the range.
|
385
|
+
def exclude_end?
|
386
|
+
@exclude_end
|
387
|
+
end
|
388
|
+
|
389
|
+
# Append a literalize version of the receiver to the sql.
|
390
|
+
def sql_literal_append(ds, sql)
|
391
|
+
ds.literal_append(sql, unquoted_literal(ds))
|
392
|
+
if s = @db_type
|
393
|
+
sql << CAST << s.to_s
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# Return a ruby Range object for this instance, if one can be created.
|
398
|
+
def to_range
|
399
|
+
return @range if @range
|
400
|
+
raise(Error, "cannot create ruby range for an empty PostgreSQL range") if empty?
|
401
|
+
raise(Error, "cannot create ruby range when PostgreSQL range excludes beginning element") if exclude_begin?
|
402
|
+
raise(Error, "cannot create ruby range when PostgreSQL range has unbounded beginning") unless self.begin
|
403
|
+
raise(Error, "cannot create ruby range when PostgreSQL range has unbounded ending") unless self.end
|
404
|
+
@range = Range.new(self.begin, self.end, exclude_end?)
|
405
|
+
end
|
406
|
+
|
407
|
+
# Whether or not this PGRange is a valid ruby range. In order to be a valid ruby range,
|
408
|
+
# it must have a beginning and an ending (no unbounded ranges), and it cannot exclude
|
409
|
+
# the beginning element.
|
410
|
+
def valid_ruby_range?
|
411
|
+
!(empty? || exclude_begin? || !self.begin || !self.end)
|
412
|
+
end
|
413
|
+
|
414
|
+
# Whether the beginning of the range is unbounded.
|
415
|
+
def unbounded_begin?
|
416
|
+
self.begin.nil? && !empty?
|
417
|
+
end
|
418
|
+
|
419
|
+
# Whether the end of the range is unbounded.
|
420
|
+
def unbounded_end?
|
421
|
+
self.end.nil? && !empty?
|
422
|
+
end
|
423
|
+
|
424
|
+
# Return a string containing the unescaped version of the range.
|
425
|
+
# Separated out for use by the bound argument code.
|
426
|
+
def unquoted_literal(ds)
|
427
|
+
if empty?
|
428
|
+
EMPTY
|
429
|
+
else
|
430
|
+
"#{exclude_begin? ? OPEN_PAREN : OPEN_BRACKET}#{escape_value(self.begin, ds)},#{escape_value(self.end, ds)}#{exclude_end? ? CLOSE_PAREN : CLOSE_BRACKET}"
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
private
|
435
|
+
|
436
|
+
# Escape common range types. Instead of quoting, just backslash escape all
|
437
|
+
# special characters.
|
438
|
+
def escape_value(k, ds)
|
439
|
+
case k
|
440
|
+
when nil
|
441
|
+
EMPTY_STRING
|
442
|
+
when Date, Time
|
443
|
+
ds.literal(k)[1...-1]
|
444
|
+
when Integer, Float
|
445
|
+
k.to_s
|
446
|
+
when BigDecimal
|
447
|
+
k.to_s('F')
|
448
|
+
when LiteralString
|
449
|
+
k
|
450
|
+
when String
|
451
|
+
if k.empty?
|
452
|
+
QUOTED_EMPTY_STRING
|
453
|
+
else
|
454
|
+
k.gsub(ESCAPE_RE, ESCAPE_REPLACE)
|
455
|
+
end
|
456
|
+
else
|
457
|
+
ds.literal(k).gsub(ESCAPE_RE, ESCAPE_REPLACE)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
PGRange.register('int4range', :oid=>3904, :subtype_oid=>23)
|
463
|
+
PGRange.register('numrange', :oid=>3906, :subtype_oid=>1700)
|
464
|
+
PGRange.register('tsrange', :oid=>3908, :subtype_oid=>1114)
|
465
|
+
PGRange.register('tstzrange', :oid=>3910, :subtype_oid=>1184)
|
466
|
+
PGRange.register('daterange', :oid=>3912, :subtype_oid=>1082)
|
467
|
+
PGRange.register('int8range', :oid=>3926, :subtype_oid=>20)
|
468
|
+
if defined?(PGArray) && PGArray.respond_to?(:register)
|
469
|
+
PGArray.register('int4range', :oid=>3905, :scalar_oid=>3904, :scalar_typecast=>:int4range)
|
470
|
+
PGArray.register('numrange', :oid=>3907, :scalar_oid=>3906, :scalar_typecast=>:numrange)
|
471
|
+
PGArray.register('tsrange', :oid=>3909, :scalar_oid=>3908, :scalar_typecast=>:tsrange)
|
472
|
+
PGArray.register('tstzrange', :oid=>3911, :scalar_oid=>3910, :scalar_typecast=>:tstzrange)
|
473
|
+
PGArray.register('daterange', :oid=>3913, :scalar_oid=>3912, :scalar_typecast=>:daterange)
|
474
|
+
PGArray.register('int8range', :oid=>3927, :scalar_oid=>3926, :scalar_typecast=>:int8range)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
Database.register_extension(:pg_range, Postgres::PGRange::DatabaseMethods)
|
479
|
+
end
|
480
|
+
|
481
|
+
class Range
|
482
|
+
# Create a new PGRange using the receiver as the input range,
|
483
|
+
# with the given database type.
|
484
|
+
def pg_range(db_type=nil)
|
485
|
+
Sequel::Postgres::PGRange.from_range(self, db_type)
|
486
|
+
end
|
487
|
+
end
|