sequel 3.37.0 → 3.38.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +56 -0
- data/README.rdoc +82 -58
- data/Rakefile +6 -5
- data/bin/sequel +1 -1
- data/doc/active_record.rdoc +67 -52
- data/doc/advanced_associations.rdoc +33 -48
- data/doc/association_basics.rdoc +41 -51
- data/doc/cheat_sheet.rdoc +21 -21
- data/doc/core_extensions.rdoc +374 -0
- data/doc/dataset_basics.rdoc +5 -5
- data/doc/dataset_filtering.rdoc +47 -43
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +4 -5
- data/doc/model_hooks.rdoc +3 -3
- data/doc/object_model.rdoc +31 -25
- data/doc/opening_databases.rdoc +19 -5
- data/doc/prepared_statements.rdoc +2 -2
- data/doc/querying.rdoc +109 -52
- data/doc/reflection.rdoc +6 -6
- data/doc/release_notes/3.38.0.txt +234 -0
- data/doc/schema_modification.rdoc +22 -13
- data/doc/sharding.rdoc +8 -9
- data/doc/sql.rdoc +154 -112
- data/doc/testing.rdoc +47 -7
- data/doc/thread_safety.rdoc +1 -1
- data/doc/transactions.rdoc +1 -1
- data/doc/validations.rdoc +1 -1
- data/doc/virtual_rows.rdoc +29 -43
- data/lib/sequel/adapters/do/postgres.rb +1 -4
- data/lib/sequel/adapters/jdbc.rb +14 -3
- data/lib/sequel/adapters/jdbc/db2.rb +9 -0
- data/lib/sequel/adapters/jdbc/derby.rb +41 -4
- data/lib/sequel/adapters/jdbc/jtds.rb +11 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -6
- data/lib/sequel/adapters/mock.rb +10 -4
- data/lib/sequel/adapters/postgres.rb +1 -28
- data/lib/sequel/adapters/shared/mssql.rb +23 -13
- data/lib/sequel/adapters/shared/postgres.rb +46 -0
- data/lib/sequel/adapters/swift.rb +21 -13
- data/lib/sequel/adapters/swift/mysql.rb +1 -0
- data/lib/sequel/adapters/swift/postgres.rb +4 -5
- data/lib/sequel/adapters/swift/sqlite.rb +2 -1
- data/lib/sequel/adapters/tinytds.rb +14 -2
- data/lib/sequel/adapters/utils/pg_types.rb +5 -0
- data/lib/sequel/core.rb +29 -17
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +3 -0
- data/lib/sequel/dataset/actions.rb +5 -6
- data/lib/sequel/dataset/query.rb +7 -7
- data/lib/sequel/dataset/sql.rb +5 -18
- data/lib/sequel/extensions/core_extensions.rb +8 -12
- data/lib/sequel/extensions/pg_array.rb +59 -33
- data/lib/sequel/extensions/pg_array_ops.rb +32 -4
- data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -1
- data/lib/sequel/extensions/pg_hstore.rb +32 -17
- data/lib/sequel/extensions/pg_hstore_ops.rb +32 -3
- data/lib/sequel/extensions/pg_inet.rb +1 -2
- data/lib/sequel/extensions/pg_interval.rb +0 -1
- data/lib/sequel/extensions/pg_json.rb +41 -23
- data/lib/sequel/extensions/pg_range.rb +36 -11
- data/lib/sequel/extensions/pg_range_ops.rb +32 -4
- data/lib/sequel/extensions/pg_row.rb +572 -0
- data/lib/sequel/extensions/pg_row_ops.rb +164 -0
- data/lib/sequel/extensions/query.rb +3 -3
- data/lib/sequel/extensions/schema_dumper.rb +7 -8
- data/lib/sequel/extensions/select_remove.rb +1 -1
- data/lib/sequel/model/base.rb +1 -0
- data/lib/sequel/no_core_ext.rb +1 -1
- data/lib/sequel/plugins/pg_row.rb +121 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
- data/lib/sequel/plugins/validation_helpers.rb +31 -0
- data/lib/sequel/sql.rb +64 -44
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +37 -12
- data/spec/adapters/mysql_spec.rb +39 -75
- data/spec/adapters/oracle_spec.rb +11 -11
- data/spec/adapters/postgres_spec.rb +414 -237
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +14 -14
- data/spec/core/database_spec.rb +6 -6
- data/spec/core/dataset_spec.rb +169 -205
- data/spec/core/expression_filters_spec.rb +182 -295
- data/spec/core/object_graph_spec.rb +6 -6
- data/spec/core/schema_spec.rb +14 -14
- data/spec/core/spec_helper.rb +1 -0
- data/spec/{extensions/core_extensions_spec.rb → core_extensions_spec.rb} +208 -14
- data/spec/extensions/columns_introspection_spec.rb +5 -5
- data/spec/extensions/hook_class_methods_spec.rb +28 -36
- data/spec/extensions/many_through_many_spec.rb +4 -4
- data/spec/extensions/pg_array_ops_spec.rb +15 -7
- data/spec/extensions/pg_array_spec.rb +81 -48
- data/spec/extensions/pg_auto_parameterize_spec.rb +2 -2
- data/spec/extensions/pg_hstore_ops_spec.rb +13 -9
- data/spec/extensions/pg_hstore_spec.rb +66 -65
- data/spec/extensions/pg_inet_spec.rb +2 -4
- data/spec/extensions/pg_interval_spec.rb +2 -3
- data/spec/extensions/pg_json_spec.rb +20 -18
- data/spec/extensions/pg_range_ops_spec.rb +11 -4
- data/spec/extensions/pg_range_spec.rb +30 -7
- data/spec/extensions/pg_row_ops_spec.rb +48 -0
- data/spec/extensions/pg_row_plugin_spec.rb +45 -0
- data/spec/extensions/pg_row_spec.rb +323 -0
- data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
- data/spec/extensions/query_literals_spec.rb +11 -11
- data/spec/extensions/query_spec.rb +3 -3
- data/spec/extensions/schema_dumper_spec.rb +20 -4
- data/spec/extensions/schema_spec.rb +18 -41
- data/spec/extensions/select_remove_spec.rb +4 -4
- data/spec/extensions/spec_helper.rb +4 -8
- data/spec/extensions/to_dot_spec.rb +5 -5
- data/spec/extensions/validation_class_methods_spec.rb +28 -16
- data/spec/integration/associations_test.rb +20 -20
- data/spec/integration/dataset_test.rb +98 -98
- data/spec/integration/eager_loader_test.rb +13 -27
- data/spec/integration/plugin_test.rb +5 -5
- data/spec/integration/prepared_statement_test.rb +22 -13
- data/spec/integration/schema_test.rb +28 -18
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/timezone_test.rb +2 -2
- data/spec/integration/type_test.rb +15 -6
- data/spec/model/association_reflection_spec.rb +1 -1
- data/spec/model/associations_spec.rb +4 -4
- data/spec/model/base_spec.rb +5 -5
- data/spec/model/eager_loading_spec.rb +15 -15
- data/spec/model/model_spec.rb +32 -32
- data/spec/model/record_spec.rb +16 -0
- data/spec/model/spec_helper.rb +2 -6
- data/spec/model/validations_spec.rb +1 -1
- metadata +16 -4
@@ -33,7 +33,7 @@
|
|
33
33
|
# manually or use a literal string:
|
34
34
|
#
|
35
35
|
# DB[:table].select(:a, :b).order(:b, :a)
|
36
|
-
# DB[:table].select(:a, :b).order('2, 1'
|
36
|
+
# DB[:table].select(:a, :b).order(Sequel.lit('2, 1'))
|
37
37
|
#
|
38
38
|
# 2. In order to avoid many type errors, it attempts to guess the
|
39
39
|
# appropriate type and automatically casts all placeholders.
|
@@ -15,8 +15,13 @@
|
|
15
15
|
# for HStore using the standard Sequel literalization callbacks, so
|
16
16
|
# they work with on all adapters.
|
17
17
|
#
|
18
|
-
# To turn an existing Hash into an HStore:
|
18
|
+
# To turn an existing Hash into an HStore, use Sequel.hstore:
|
19
19
|
#
|
20
|
+
# Sequel.hstore(hash)
|
21
|
+
#
|
22
|
+
# If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
|
23
|
+
# you can also use Hash#hstore:
|
24
|
+
#
|
20
25
|
# hash.hstore
|
21
26
|
#
|
22
27
|
# Since the hstore type only supports strings, non string keys and
|
@@ -128,11 +133,6 @@ module Sequel
|
|
128
133
|
end
|
129
134
|
|
130
135
|
module DatabaseMethods
|
131
|
-
# Reset the conversion procs if using the native postgres adapter.
|
132
|
-
def self.extended(db)
|
133
|
-
db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
|
134
|
-
end
|
135
|
-
|
136
136
|
# Handle hstores in bound variables
|
137
137
|
def bound_variable_arg(arg, conn)
|
138
138
|
case arg
|
@@ -163,8 +163,6 @@ module Sequel
|
|
163
163
|
value
|
164
164
|
when Hash
|
165
165
|
HStore.new(value)
|
166
|
-
when String
|
167
|
-
HStore.parse(value)
|
168
166
|
else
|
169
167
|
raise Sequel::InvalidValue, "invalid value for hstore: #{value.inspect}"
|
170
168
|
end
|
@@ -282,17 +280,34 @@ module Sequel
|
|
282
280
|
PG_NAMED_TYPES[:hstore] = HStore.method(:parse)
|
283
281
|
end
|
284
282
|
|
283
|
+
module SQL::Builders
|
284
|
+
# Return a Postgres::HStore proxy for the given hash.
|
285
|
+
def hstore(v)
|
286
|
+
case v
|
287
|
+
when Postgres::HStore
|
288
|
+
v
|
289
|
+
when Hash
|
290
|
+
Postgres::HStore.new(v)
|
291
|
+
else
|
292
|
+
# May not be defined unless the pg_hstore_ops extension is used
|
293
|
+
hstore_op(v)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
285
298
|
Database.register_extension(:pg_hstore, Postgres::HStore::DatabaseMethods)
|
286
299
|
end
|
287
300
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
301
|
+
if Sequel.core_extensions?
|
302
|
+
class Hash
|
303
|
+
# Create a new HStore using the receiver as the input
|
304
|
+
# hash. Note that the HStore created will not use the
|
305
|
+
# receiver as the backing store, since it has to
|
306
|
+
# modify the hash. To get the new backing store, use:
|
307
|
+
#
|
308
|
+
# hash.hstore.to_hash
|
309
|
+
def hstore
|
310
|
+
Sequel::Postgres::HStore.new(self)
|
311
|
+
end
|
297
312
|
end
|
298
313
|
end
|
@@ -6,7 +6,22 @@
|
|
6
6
|
# Sequel.extension :pg_hstore_ops
|
7
7
|
#
|
8
8
|
# The most common usage is taking an object that represents an SQL
|
9
|
-
# expression (such as a :symbol), and calling
|
9
|
+
# expression (such as a :symbol), and calling Sequel.hstore_op with it:
|
10
|
+
#
|
11
|
+
# h = Sequel.hstore_op(:hstore_column)
|
12
|
+
#
|
13
|
+
# If you have also loaded the pg_hstore extension, you can use
|
14
|
+
# Sequel.hstore as well:
|
15
|
+
#
|
16
|
+
# h = Sequel.hstore(:hstore_column)
|
17
|
+
#
|
18
|
+
# Also, on most Sequel expression objects, you can call the hstore
|
19
|
+
# method:
|
20
|
+
#
|
21
|
+
# h = Sequel.expr(:hstore_column).hstore
|
22
|
+
#
|
23
|
+
# If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
|
24
|
+
# you can also call Symbol#hstore:
|
10
25
|
#
|
11
26
|
# h = :hstore_column.hstore
|
12
27
|
#
|
@@ -250,6 +265,18 @@ module Sequel
|
|
250
265
|
end
|
251
266
|
end
|
252
267
|
|
268
|
+
module SQL::Builders
|
269
|
+
# Return the object wrapped in an Postgres::HStoreOp.
|
270
|
+
def hstore_op(v)
|
271
|
+
case v
|
272
|
+
when Postgres::HStoreOp
|
273
|
+
v
|
274
|
+
else
|
275
|
+
Postgres::HStoreOp.new(v)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
253
280
|
class SQL::GenericExpression
|
254
281
|
include Sequel::Postgres::HStoreOpMethods
|
255
282
|
end
|
@@ -259,6 +286,8 @@ module Sequel
|
|
259
286
|
end
|
260
287
|
end
|
261
288
|
|
262
|
-
|
263
|
-
|
289
|
+
if Sequel.core_extensions?
|
290
|
+
class Symbol
|
291
|
+
include Sequel::Postgres::HStoreOpMethods
|
292
|
+
end
|
264
293
|
end
|
@@ -35,10 +35,9 @@ module Sequel
|
|
35
35
|
module InetDatabaseMethods
|
36
36
|
|
37
37
|
# Reset the conversion procs when extending the Database object, so
|
38
|
-
# it will pick up the inet/cidr
|
38
|
+
# it will pick up the inet/cidr converter. Also, extend the datasets
|
39
39
|
# with support for literalizing the IPAddr types.
|
40
40
|
def self.extended(db)
|
41
|
-
db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
|
42
41
|
db.extend_datasets(InetDatasetMethods)
|
43
42
|
end
|
44
43
|
|
@@ -119,7 +119,6 @@ module Sequel
|
|
119
119
|
# Reset the conversion procs if using the native postgres adapter,
|
120
120
|
# and extend the datasets to correctly literalize ActiveSupport::Duration values.
|
121
121
|
def self.extended(db)
|
122
|
-
db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
|
123
122
|
db.extend_datasets(IntervalDatasetMethods)
|
124
123
|
end
|
125
124
|
|
@@ -17,15 +17,22 @@
|
|
17
17
|
# This is done so that Sequel does not treat JSONArray and JSONHash
|
18
18
|
# like Array and Hash by default, which would cause issues.
|
19
19
|
#
|
20
|
-
# To turn an existing Array or Hash into a JSONArray or JSONHash
|
20
|
+
# To turn an existing Array or Hash into a JSONArray or JSONHash,
|
21
|
+
# use Sequel.pg_json:
|
22
|
+
#
|
23
|
+
# Sequel.pg_json(array)
|
24
|
+
# Sequel.pg_json(hash)
|
25
|
+
#
|
26
|
+
# If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
|
27
|
+
# you can also use Array#pg_json and Hash#pg_json:
|
21
28
|
#
|
22
29
|
# array.pg_json
|
23
30
|
# hash.pg_json
|
24
31
|
#
|
25
32
|
# So if you want to insert an array or hash into an json database column:
|
26
33
|
#
|
27
|
-
# DB[:table].insert(:column=>[1, 2, 3]
|
28
|
-
# DB[:table].insert(:column=>{'a'=>1, 'b'=>2}
|
34
|
+
# DB[:table].insert(:column=>Sequel.pg_json([1, 2, 3]))
|
35
|
+
# DB[:table].insert(:column=>Sequel.pg_json({'a'=>1, 'b'=>2}))
|
29
36
|
#
|
30
37
|
# If you would like to use PostgreSQL json columns in your model
|
31
38
|
# objects, you probably want to modify the schema parsing/typecasting
|
@@ -101,13 +108,6 @@ module Sequel
|
|
101
108
|
end
|
102
109
|
end
|
103
110
|
|
104
|
-
# Reset the conversion procs when extending the Database object, so
|
105
|
-
# it will pick up the json convertor. This is only done for the native
|
106
|
-
# postgres adapter.
|
107
|
-
def self.extended(db)
|
108
|
-
db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
|
109
|
-
end
|
110
|
-
|
111
111
|
# Handle JSONArray and JSONHash in bound variables
|
112
112
|
def bound_variable_arg(arg, conn)
|
113
113
|
case arg
|
@@ -168,23 +168,41 @@ module Sequel
|
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
171
|
+
module SQL::Builders
|
172
|
+
# Wrap the array or hash in a Postgres::JSONArray or Postgres::JSONHash.
|
173
|
+
def pg_json(v)
|
174
|
+
case v
|
175
|
+
when Postgres::JSONArray, Postgres::JSONHash
|
176
|
+
v
|
177
|
+
when Array
|
178
|
+
Postgres::JSONArray.new(v)
|
179
|
+
when Hash
|
180
|
+
Postgres::JSONHash.new(v)
|
181
|
+
else
|
182
|
+
raise Error, "Sequel.pg_json requires a hash or array argument"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
171
187
|
Database.register_extension(:pg_json, Postgres::JSONDatabaseMethods)
|
172
188
|
end
|
173
189
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
190
|
+
if Sequel.core_extensions?
|
191
|
+
class Array
|
192
|
+
# Return a Sequel::Postgres::JSONArray proxy to the receiver.
|
193
|
+
# This is mostly useful as a short cut for creating JSONArray
|
194
|
+
# objects that didn't come from the database.
|
195
|
+
def pg_json
|
196
|
+
Sequel::Postgres::JSONArray.new(self)
|
197
|
+
end
|
180
198
|
end
|
181
|
-
end
|
182
199
|
|
183
|
-
class Hash
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
200
|
+
class Hash
|
201
|
+
# Return a Sequel::Postgres::JSONHash proxy to the receiver.
|
202
|
+
# This is mostly useful as a short cut for creating JSONHash
|
203
|
+
# objects that didn't come from the database.
|
204
|
+
def pg_json
|
205
|
+
Sequel::Postgres::JSONHash.new(self)
|
206
|
+
end
|
189
207
|
end
|
190
208
|
end
|
@@ -20,12 +20,18 @@
|
|
20
20
|
# for both PGRange and Range that use the standard Sequel literalization
|
21
21
|
# callbacks, so they work on all adapters.
|
22
22
|
#
|
23
|
-
# To turn an existing Range into a PGRange:
|
23
|
+
# To turn an existing Range into a PGRange, use Sequel.pg_range:
|
24
|
+
#
|
25
|
+
# Sequel.pg_range(range)
|
26
|
+
#
|
27
|
+
# If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
|
28
|
+
# you can also use Range#pg_range:
|
24
29
|
#
|
25
30
|
# range.pg_range
|
26
31
|
#
|
27
32
|
# You may want to specify a specific range type:
|
28
33
|
#
|
34
|
+
# Sequel.pg_range(range, :daterange)
|
29
35
|
# range.pg_range(:daterange)
|
30
36
|
#
|
31
37
|
# If you specify the range database type, Sequel will automatically cast
|
@@ -177,7 +183,6 @@ module Sequel
|
|
177
183
|
# Reset the conversion procs if using the native postgres adapter,
|
178
184
|
# and extend the datasets to correctly literalize ruby Range values.
|
179
185
|
def self.extended(db)
|
180
|
-
db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
|
181
186
|
db.extend_datasets(DatasetMethods)
|
182
187
|
end
|
183
188
|
|
@@ -216,12 +221,11 @@ module Sequel
|
|
216
221
|
# Manually override the typecasting for tsrange and tstzrange types so that
|
217
222
|
# they use the database's timezone instead of the global Sequel
|
218
223
|
# timezone.
|
219
|
-
def get_conversion_procs
|
224
|
+
def get_conversion_procs
|
220
225
|
procs = super
|
221
226
|
|
222
|
-
|
223
|
-
procs[
|
224
|
-
procs[3910] = Parser.new("tstzrange", converter)
|
227
|
+
procs[3908] = Parser.new("tsrange", procs[1114])
|
228
|
+
procs[3910] = Parser.new("tstzrange", procs[1184])
|
225
229
|
if defined?(PGArray::Creator)
|
226
230
|
procs[3909] = PGArray::Creator.new("tsrange", procs[3908])
|
227
231
|
procs[3911] = PGArray::Creator.new("tstzrange", procs[3910])
|
@@ -475,13 +479,34 @@ module Sequel
|
|
475
479
|
end
|
476
480
|
end
|
477
481
|
|
482
|
+
module SQL::Builders
|
483
|
+
# Convert the object to a Postgres::PGRange.
|
484
|
+
def pg_range(v, db_type=nil)
|
485
|
+
case v
|
486
|
+
when Postgres::PGRange
|
487
|
+
if db_type.nil? || v.db_type == db_type
|
488
|
+
v
|
489
|
+
else
|
490
|
+
Postgres::PGRange.new(v.begin, v.end, :exclude_begin=>v.exclude_begin?, :exclude_end=>v.exclude_end?, :db_type=>db_type)
|
491
|
+
end
|
492
|
+
when Range
|
493
|
+
Postgres::PGRange.from_range(v, db_type)
|
494
|
+
else
|
495
|
+
# May not be defined unless the pg_range_ops extension is used
|
496
|
+
pg_range_op(v)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
478
501
|
Database.register_extension(:pg_range, Postgres::PGRange::DatabaseMethods)
|
479
502
|
end
|
480
503
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
504
|
+
if Sequel.core_extensions?
|
505
|
+
class Range
|
506
|
+
# Create a new PGRange using the receiver as the input range,
|
507
|
+
# with the given database type.
|
508
|
+
def pg_range(db_type=nil)
|
509
|
+
Sequel::Postgres::PGRange.from_range(self, db_type)
|
510
|
+
end
|
486
511
|
end
|
487
512
|
end
|
@@ -5,8 +5,22 @@
|
|
5
5
|
#
|
6
6
|
# Sequel.extension :pg_range_ops
|
7
7
|
#
|
8
|
-
# The most common usage is
|
9
|
-
#
|
8
|
+
# The most common usage is passing an expression to Sequel.pg_range_op:
|
9
|
+
#
|
10
|
+
# r = Sequel.pg_range_op(:range)
|
11
|
+
#
|
12
|
+
# If you have also loaded the pg_range extension, you can use
|
13
|
+
# Sequel.pg_range as well:
|
14
|
+
#
|
15
|
+
# r = Sequel.pg_range(:range)
|
16
|
+
#
|
17
|
+
# Also, on most Sequel expression objects, you can call the pg_range
|
18
|
+
# method:
|
19
|
+
#
|
20
|
+
# r = Sequel.expr(:range).pg_range
|
21
|
+
#
|
22
|
+
# If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
|
23
|
+
# you can also call Symbol#pg_range:
|
10
24
|
#
|
11
25
|
# r = :range.pg_range
|
12
26
|
#
|
@@ -108,6 +122,18 @@ module Sequel
|
|
108
122
|
end
|
109
123
|
end
|
110
124
|
|
125
|
+
module SQL::Builders
|
126
|
+
# Return the expression wrapped in the Postgres::RangeOp.
|
127
|
+
def pg_range_op(v)
|
128
|
+
case v
|
129
|
+
when Postgres::RangeOp
|
130
|
+
v
|
131
|
+
else
|
132
|
+
Postgres::RangeOp.new(v)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
111
137
|
class SQL::GenericExpression
|
112
138
|
include Sequel::Postgres::RangeOpMethods
|
113
139
|
end
|
@@ -117,6 +143,8 @@ module Sequel
|
|
117
143
|
end
|
118
144
|
end
|
119
145
|
|
120
|
-
|
121
|
-
|
146
|
+
if Sequel.core_extensions?
|
147
|
+
class Symbol
|
148
|
+
include Sequel::Postgres::RangeOpMethods
|
149
|
+
end
|
122
150
|
end
|
@@ -0,0 +1,572 @@
|
|
1
|
+
# The pg_row extension adds support for Sequel to handle
|
2
|
+
# PostgreSQL's row-valued/composite types.
|
3
|
+
#
|
4
|
+
# This extension integrates with Sequel's native postgres adapter, so
|
5
|
+
# that when composite fields are retrieved, they are parsed and returned
|
6
|
+
# as instances of Sequel::Postgres::PGRow::(HashRow|ArrayRow), or
|
7
|
+
# optionally a custom type. HashRow and ArrayRow are DelegateClasses of
|
8
|
+
# of Hash and Array, so they mostly act like a hash or array, but not
|
9
|
+
# completely (is_a?(Hash) and is_a?(Array) are false). If you want the
|
10
|
+
# actual hash for a HashRow, call HashRow#to_hash, and if you want the
|
11
|
+
# actual array for an ArrayRow, call ArrayRow#to_a. This is done so
|
12
|
+
# that Sequel does not treat a values like an Array or Hash by default,
|
13
|
+
# which would cause issues.
|
14
|
+
#
|
15
|
+
# In addition to the parsers, this extension comes with literalizers
|
16
|
+
# for HashRow and ArrayRow using the standard Sequel literalization callbacks, so
|
17
|
+
# they work with on all adapters.
|
18
|
+
#
|
19
|
+
# The first thing you are going to want to do is to load the extension into
|
20
|
+
# your Database object. Make sure you load the :pg_array extension first
|
21
|
+
# if you plan to use composite types in bound variables:
|
22
|
+
#
|
23
|
+
# DB.extension(:pg_array, :pg_row)
|
24
|
+
#
|
25
|
+
# You can create an anonymous row type by calling the Sequel.pg_row with
|
26
|
+
# an array:
|
27
|
+
#
|
28
|
+
# Sequel.pg_row(array)
|
29
|
+
#
|
30
|
+
# If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
|
31
|
+
# you can also use Array#pg_row:
|
32
|
+
#
|
33
|
+
# array.pg_row
|
34
|
+
#
|
35
|
+
# However, in most cases you are going to want something beyond anonymous
|
36
|
+
# row types. This extension allows you to register row types on a per
|
37
|
+
# database basis, using Database#register_row_type:
|
38
|
+
#
|
39
|
+
# DB.register_row_type(:foo)
|
40
|
+
#
|
41
|
+
# When you register the row type, Sequel will query the PostgreSQL
|
42
|
+
# system tables to find the related metadata, and will setup
|
43
|
+
# a custom HashRow subclass for that type. This includes looking up
|
44
|
+
# conversion procs for each column in the type, so that when the composite
|
45
|
+
# type is returned from the database, the members of the type have
|
46
|
+
# the correct type. Additionally, if the composite type also has an
|
47
|
+
# array form, Sequel registers an array type for the composite type,
|
48
|
+
# so that array columns of the composite type are converted correctly.
|
49
|
+
#
|
50
|
+
# You can then create values of that type by using Database#row_type:
|
51
|
+
#
|
52
|
+
# DB.row_type(:address, ['123 Sesame St.', 'Some City', '12345'])
|
53
|
+
#
|
54
|
+
# Let's say table address has columns street, city, and zip. This would return
|
55
|
+
# something similar to:
|
56
|
+
#
|
57
|
+
# {:street=>'123 Sesame St.', :city=>'Some City', :zip=>'12345'}
|
58
|
+
#
|
59
|
+
# You can also use a hash:
|
60
|
+
#
|
61
|
+
# DB.row_type(:address, :street=>'123 Sesame St.', :city=>'Some City', :zip=>'12345')
|
62
|
+
#
|
63
|
+
# So if you have a person table that has an address column, here's how you
|
64
|
+
# could insert into the column:
|
65
|
+
#
|
66
|
+
# DB[:table].insert(:address=>DB.row_type(:address, :street=>'123 Sesame St.', :city=>'Some City', :zip=>'12345'))
|
67
|
+
#
|
68
|
+
# This extension requires both the strscan and delegate libraries.
|
69
|
+
|
70
|
+
require 'delegate'
|
71
|
+
require 'strscan'
|
72
|
+
Sequel.require 'adapters/utils/pg_types'
|
73
|
+
|
74
|
+
module Sequel
|
75
|
+
module Postgres
|
76
|
+
module PGRow
|
77
|
+
ROW = 'ROW'.freeze
|
78
|
+
CAST = '::'.freeze
|
79
|
+
|
80
|
+
# Class for row-valued/composite types that are treated as arrays. By default,
|
81
|
+
# this is only used for generic PostgreSQL record types, as registered
|
82
|
+
# types use HashRow by default.
|
83
|
+
class ArrayRow < DelegateClass(Array)
|
84
|
+
class << self
|
85
|
+
# The database type for this class. May be nil if this class
|
86
|
+
# done not have a specific database type.
|
87
|
+
attr_accessor :db_type
|
88
|
+
|
89
|
+
# Alias new to call, so that the class itself can be used
|
90
|
+
# directly as a converter.
|
91
|
+
alias call new
|
92
|
+
end
|
93
|
+
|
94
|
+
# Create a subclass associated with a specific database type.
|
95
|
+
# This is done so that instances of this subclass are
|
96
|
+
# automatically casted to the database type when literalizing.
|
97
|
+
def self.subclass(db_type)
|
98
|
+
sc = Class.new(self) do
|
99
|
+
@db_type = db_type
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Sets the database type associated with this instance. This is
|
104
|
+
# used to override the class's default database type.
|
105
|
+
attr_writer :db_type
|
106
|
+
|
107
|
+
# Return the instance's database type, or the class's database
|
108
|
+
# type if the instance has not overridden it.
|
109
|
+
def db_type
|
110
|
+
@db_type || self.class.db_type
|
111
|
+
end
|
112
|
+
|
113
|
+
# Append SQL fragment related to this object to the sql.
|
114
|
+
def sql_literal_append(ds, sql)
|
115
|
+
sql << ROW
|
116
|
+
ds.literal_append(sql, to_a)
|
117
|
+
if db_type
|
118
|
+
sql << CAST
|
119
|
+
ds.quote_schema_table_append(sql, db_type)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Class for row-valued/composite types that are treated as hashes.
|
125
|
+
# Types registered via Database#register_row_type will use this
|
126
|
+
# class by default.
|
127
|
+
class HashRow < DelegateClass(Hash)
|
128
|
+
class << self
|
129
|
+
# The columns associated with this class.
|
130
|
+
attr_accessor :columns
|
131
|
+
|
132
|
+
# The database type for this class. May be nil if this class
|
133
|
+
# done not have a specific database type.
|
134
|
+
attr_accessor :db_type
|
135
|
+
|
136
|
+
# Alias new to call, so that the class itself can be used
|
137
|
+
# directly as a converter.
|
138
|
+
alias call new
|
139
|
+
end
|
140
|
+
|
141
|
+
# Create a new subclass of this class with the given database
|
142
|
+
# type and columns.
|
143
|
+
def self.subclass(db_type, columns)
|
144
|
+
sc = Class.new(self) do
|
145
|
+
@db_type = db_type
|
146
|
+
@columns = columns
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return the underlying hash for this delegate object.
|
151
|
+
alias to_hash __getobj__
|
152
|
+
|
153
|
+
# Sets the columns associated with this instance. This is
|
154
|
+
# used to override the class's default columns.
|
155
|
+
attr_writer :columns
|
156
|
+
|
157
|
+
# Sets the database type associated with this instance. This is
|
158
|
+
# used to override the class's default database type.
|
159
|
+
attr_writer :db_type
|
160
|
+
|
161
|
+
# Return the instance's columns, or the class's columns
|
162
|
+
# if the instance has not overridden it.
|
163
|
+
def columns
|
164
|
+
@columns || self.class.columns
|
165
|
+
end
|
166
|
+
|
167
|
+
# Return the instance's database type, or the class's columns
|
168
|
+
# if the instance has not overridden it.
|
169
|
+
def db_type
|
170
|
+
@db_type || self.class.db_type
|
171
|
+
end
|
172
|
+
|
173
|
+
# Check that the HashRow has valid columns. This should be used
|
174
|
+
# before all attempts to literalize the object, since literalization
|
175
|
+
# depends on the columns to get the column order.
|
176
|
+
def check_columns!
|
177
|
+
if columns.nil? || columns.empty?
|
178
|
+
raise Error, 'cannot literalize HashRow without columns'
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Append SQL fragment related to this object to the sql.
|
183
|
+
def sql_literal_append(ds, sql)
|
184
|
+
check_columns!
|
185
|
+
sql << ROW
|
186
|
+
ds.literal_append(sql, values_at(*columns))
|
187
|
+
if db_type
|
188
|
+
sql << CAST
|
189
|
+
ds.quote_schema_table_append(sql, db_type)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# This parser-like class splits the PostgreSQL
|
195
|
+
# row-valued/composite type output string format
|
196
|
+
# into an array of strings. Note this class makes
|
197
|
+
# no attempt to handle all input formats that PostgreSQL
|
198
|
+
# will accept, it only handles the output format that
|
199
|
+
# PostgreSQL uses.
|
200
|
+
class Splitter < StringScanner
|
201
|
+
OPEN_PAREN = /\(/.freeze
|
202
|
+
CLOSE_PAREN = /\)/.freeze
|
203
|
+
UNQUOTED_RE = /[^,)]*/.freeze
|
204
|
+
SEP_RE = /[,)]/.freeze
|
205
|
+
QUOTE_RE = /"/.freeze
|
206
|
+
QUOTE_SEP_RE = /"[,)]/.freeze
|
207
|
+
QUOTED_RE = /(\\.|""|[^"])*/.freeze
|
208
|
+
REPLACE_RE = /\\(.)|"(")/.freeze
|
209
|
+
REPLACE_WITH = '\1\2'.freeze
|
210
|
+
|
211
|
+
# Split the stored string into an array of strings, handling
|
212
|
+
# the different types of quoting.
|
213
|
+
def parse
|
214
|
+
return @result if @result
|
215
|
+
values = []
|
216
|
+
skip(OPEN_PAREN)
|
217
|
+
if skip(CLOSE_PAREN)
|
218
|
+
values << nil
|
219
|
+
else
|
220
|
+
until eos?
|
221
|
+
if skip(QUOTE_RE)
|
222
|
+
values << scan(QUOTED_RE).gsub(REPLACE_RE, REPLACE_WITH)
|
223
|
+
skip(QUOTE_SEP_RE)
|
224
|
+
else
|
225
|
+
v = scan(UNQUOTED_RE)
|
226
|
+
values << (v unless v.empty?)
|
227
|
+
skip(SEP_RE)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
values
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# The Parser is responsible for taking the input string
|
236
|
+
# from PostgreSQL, and returning an appropriate ruby
|
237
|
+
# object that the type represents, such as an ArrayRow or
|
238
|
+
# HashRow.
|
239
|
+
class Parser
|
240
|
+
# The columns for the parser, if any. If the parser has
|
241
|
+
# no columns, it will treat the input as an array. If
|
242
|
+
# it has columns, it will treat the input as a hash.
|
243
|
+
# If present, should be an array of strings.
|
244
|
+
attr_reader :columns
|
245
|
+
|
246
|
+
# Converters for each member in the composite type. If
|
247
|
+
# not present, no conversion will be done, so values will
|
248
|
+
# remain strings. If present, should be an array of
|
249
|
+
# callable objects.
|
250
|
+
attr_reader :column_converters
|
251
|
+
|
252
|
+
# The OIDs for each member in the composite type. Not
|
253
|
+
# currently used, but made available for user code.
|
254
|
+
attr_reader :column_oids
|
255
|
+
|
256
|
+
# A converter for the object as a whole. Used to wrap
|
257
|
+
# the returned array/hash in another object, such as an
|
258
|
+
# ArrayRow or HashRow. If present, should be callable.
|
259
|
+
attr_reader :converter
|
260
|
+
|
261
|
+
# The oid for the composite type itself.
|
262
|
+
attr_reader :oid
|
263
|
+
|
264
|
+
# A callable object used for typecasting the object. This
|
265
|
+
# is similar to the converter, but it is called by the
|
266
|
+
# typecasting code, which has different assumptions than
|
267
|
+
# the converter. For instance, the converter should be
|
268
|
+
# called with all of the member values already typecast,
|
269
|
+
# but the typecaster may not be.
|
270
|
+
attr_reader :typecaster
|
271
|
+
|
272
|
+
# Sets each of the parser's attributes, using options with
|
273
|
+
# the same name (e.g. :columns sets the columns attribute).
|
274
|
+
def initialize(h={})
|
275
|
+
@columns = h[:columns]
|
276
|
+
@column_converters = h[:column_converters]
|
277
|
+
@column_oids = h[:column_oids]
|
278
|
+
@converter = h[:converter]
|
279
|
+
@typecaster = h[:typecaster]
|
280
|
+
@oid = h[:oid]
|
281
|
+
end
|
282
|
+
|
283
|
+
# Convert the PostgreSQL composite type input format into
|
284
|
+
# an appropriate ruby object.
|
285
|
+
def call(s)
|
286
|
+
convert(convert_format(convert_columns(Splitter.new(s).parse)))
|
287
|
+
end
|
288
|
+
|
289
|
+
# Typecast the given object to the appropriate type using the
|
290
|
+
# typecaster. Note that this does not conversion for the members
|
291
|
+
# of the composite type, since those conversion expect strings and
|
292
|
+
# strings may not be provided.
|
293
|
+
def typecast(obj)
|
294
|
+
case obj
|
295
|
+
when Array
|
296
|
+
_typecast(convert_format(obj))
|
297
|
+
when Hash
|
298
|
+
unless @columns
|
299
|
+
raise Error, 'PGRow::Parser without columns cannot typecast from a hash'
|
300
|
+
end
|
301
|
+
_typecast(obj)
|
302
|
+
else
|
303
|
+
raise Error, 'PGRow::Parser can only typecast arrays and hashes'
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
private
|
308
|
+
|
309
|
+
# If the parser has a typecaster, call it with
|
310
|
+
# the object, otherwise return the object as is.
|
311
|
+
def _typecast(obj)
|
312
|
+
if t = @typecaster
|
313
|
+
t.call(obj)
|
314
|
+
else
|
315
|
+
obj
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
# If the parser has column converters, map the
|
320
|
+
# array of strings input to a array of appropriate
|
321
|
+
# ruby objects, one for each converter.
|
322
|
+
def convert_columns(arr)
|
323
|
+
if ccs = @column_converters
|
324
|
+
arr.zip(ccs).map{|v, pr| pr ? pr.call(v) : v}
|
325
|
+
else
|
326
|
+
arr
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# If the parser has columns, return a hash assuming
|
331
|
+
# that the array is ordered by the columns.
|
332
|
+
def convert_format(arr)
|
333
|
+
if cs = @columns
|
334
|
+
h = {}
|
335
|
+
arr.zip(cs).each{|v, c| h[c] = v}
|
336
|
+
h
|
337
|
+
else
|
338
|
+
arr
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# If the parser has a converter, call it with the object,
|
343
|
+
# otherwise return the object as is.
|
344
|
+
def convert(obj)
|
345
|
+
if c = @converter
|
346
|
+
c.call(obj)
|
347
|
+
else
|
348
|
+
obj
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
module DatabaseMethods
|
354
|
+
ESCAPE_RE = /("|\\)/.freeze
|
355
|
+
ESCAPE_REPLACEMENT = '\\\\\1'.freeze
|
356
|
+
COMMA = ','.freeze
|
357
|
+
|
358
|
+
# A hash mapping row type keys (usually symbols), to option
|
359
|
+
# hashes. At the least, the values will contain the :parser
|
360
|
+
# option for the Parser instance that the type will use.
|
361
|
+
attr_reader :row_types
|
362
|
+
|
363
|
+
# Do some setup for the data structures the module uses.
|
364
|
+
def self.extended(db)
|
365
|
+
# Return right away if row_types has already been set. This
|
366
|
+
# makes things not break if a user extends the database with
|
367
|
+
# this module more than once (since extended is called every
|
368
|
+
# time).
|
369
|
+
return if db.row_types
|
370
|
+
|
371
|
+
db.instance_eval do
|
372
|
+
@row_types = {}
|
373
|
+
@row_schema_types = {}
|
374
|
+
extend(@row_type_method_module = Module.new)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# Handle ArrayRow and HashRow values in bound variables.
|
379
|
+
def bound_variable_arg(arg, conn)
|
380
|
+
case arg
|
381
|
+
when ArrayRow
|
382
|
+
"(#{arg.map{|v| bound_variable_array(v)}.join(COMMA)})"
|
383
|
+
when HashRow
|
384
|
+
arg.check_columns!
|
385
|
+
"(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA)})"
|
386
|
+
else
|
387
|
+
super
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
# Register a new row type for the Database instance. db_type should be the type
|
392
|
+
# symbol. This parses the PostgreSQL system tables to get information the
|
393
|
+
# composite type, and by default has the type return instances of a subclass
|
394
|
+
# of HashRow.
|
395
|
+
#
|
396
|
+
# The following options are supported:
|
397
|
+
#
|
398
|
+
# :converter :: Use a custom converter for the parser.
|
399
|
+
# :typecaster :: Use a custom typecaster for the parser.
|
400
|
+
def register_row_type(db_type, opts={})
|
401
|
+
procs = @conversion_procs
|
402
|
+
rel_oid = nil
|
403
|
+
array_oid = nil
|
404
|
+
parser_opts = {}
|
405
|
+
|
406
|
+
# Try to handle schema-qualified types.
|
407
|
+
type_schema, type_name = schema_and_table(db_type)
|
408
|
+
schema_type_string = type_name.to_s
|
409
|
+
|
410
|
+
# Get basic oid information for the composite type.
|
411
|
+
ds = from(:pg_type).
|
412
|
+
select(:pg_type__oid, :typrelid, :typarray).
|
413
|
+
where([[:typtype, 'c'], [:typname, type_name.to_s]])
|
414
|
+
if type_schema
|
415
|
+
ds = ds.join(:pg_namespace, [[:oid, :typnamespace], [:nspname, type_schema.to_s]])
|
416
|
+
schema_type_symbol = :"pg_row_#{type_schema}__#{type_name}"
|
417
|
+
else
|
418
|
+
schema_type_symbol = :"pg_row_#{type_name}"
|
419
|
+
end
|
420
|
+
unless row = ds.first
|
421
|
+
raise Error, "row type #{db_type.inspect} not found in database"
|
422
|
+
end
|
423
|
+
# Manually cast to integer using to_i, because adapter may not cast oid type
|
424
|
+
# correctly (e.g. swift)
|
425
|
+
parser_opts[:oid], rel_oid, array_oid = row.values_at(:oid, :typrelid, :typarray).map{|i| i.to_i}
|
426
|
+
|
427
|
+
# Get column names and oids for each of the members of the composite type.
|
428
|
+
res = from(:pg_attribute).
|
429
|
+
where(:attrelid=>rel_oid).
|
430
|
+
where{attnum > 0}.
|
431
|
+
exclude(:attisdropped).
|
432
|
+
order(:attnum).
|
433
|
+
select_map([:attname, :atttypid])
|
434
|
+
if res.empty?
|
435
|
+
raise Error, "no columns for row type #{db_type.inspect} in database"
|
436
|
+
end
|
437
|
+
parser_opts[:columns] = res.map{|r| r[0].to_sym}
|
438
|
+
parser_opts[:column_oids] = res.map{|r| r[1].to_i}
|
439
|
+
|
440
|
+
# Using the conversion_procs, lookup converters for each member of the composite type
|
441
|
+
parser_opts[:column_converters] = parser_opts[:column_oids].map do |oid|
|
442
|
+
if pr = procs[oid]
|
443
|
+
pr
|
444
|
+
elsif !Sequel::Postgres::STRING_TYPES.include?(oid)
|
445
|
+
# It's not a string type, and it's possible a conversion proc for this
|
446
|
+
# oid will be added later, so do a runtime check for it.
|
447
|
+
lambda{|s| (pr = procs[oid]) ? pr.call(s) : s}
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
# Setup the converter and typecaster
|
452
|
+
parser_opts[:converter] = opts.fetch(:converter){HashRow.subclass(db_type, parser_opts[:columns])}
|
453
|
+
parser_opts[:typecaster] = opts.fetch(:typecaster, parser_opts[:converter])
|
454
|
+
|
455
|
+
parser = Parser.new(parser_opts)
|
456
|
+
@conversion_procs[parser.oid] = parser
|
457
|
+
|
458
|
+
if defined?(PGArray) && PGArray.respond_to?(:register) && array_oid && array_oid > 0
|
459
|
+
PGArray.register(db_type, :oid=>array_oid, :converter=>parser, :type_procs=>@conversion_procs, :scalar_typecast=>schema_type_symbol)
|
460
|
+
end
|
461
|
+
|
462
|
+
@row_types[db_type] = opts.merge(:parser=>parser)
|
463
|
+
@row_schema_types[schema_type_string] = schema_type_symbol
|
464
|
+
@row_type_method_module.class_eval do
|
465
|
+
meth = :"typecast_value_#{schema_type_symbol}"
|
466
|
+
define_method(meth) do |v|
|
467
|
+
row_type(db_type, v)
|
468
|
+
end
|
469
|
+
private meth
|
470
|
+
end
|
471
|
+
|
472
|
+
nil
|
473
|
+
end
|
474
|
+
|
475
|
+
# When reseting conversion procs, reregister all the row types so that
|
476
|
+
# the system tables are introspected again, picking up database changes.
|
477
|
+
def reset_conversion_procs
|
478
|
+
procs = super
|
479
|
+
|
480
|
+
row_types.each do |db_type, opts|
|
481
|
+
register_row_type(db_type, opts)
|
482
|
+
end
|
483
|
+
|
484
|
+
procs
|
485
|
+
end
|
486
|
+
|
487
|
+
# Handle typecasting of the given object to the given database type.
|
488
|
+
# In general, the given database type should already be registered,
|
489
|
+
# but if obj is an array, this will handled unregistered types.
|
490
|
+
def row_type(db_type, obj)
|
491
|
+
(type_hash = @row_types[db_type]) &&
|
492
|
+
(parser = type_hash[:parser])
|
493
|
+
|
494
|
+
case obj
|
495
|
+
when ArrayRow, HashRow
|
496
|
+
obj
|
497
|
+
when Array
|
498
|
+
if parser
|
499
|
+
parser.typecast(obj)
|
500
|
+
else
|
501
|
+
obj = ArrayRow.new(obj)
|
502
|
+
obj.db_type = db_type
|
503
|
+
obj
|
504
|
+
end
|
505
|
+
when Hash
|
506
|
+
if parser
|
507
|
+
parser.typecast(obj)
|
508
|
+
else
|
509
|
+
raise InvalidValue, "Database#row_type requires the #{db_type.inspect} type have a registered parser and typecaster when called with a hash"
|
510
|
+
end
|
511
|
+
else
|
512
|
+
raise InvalidValue, "cannot convert #{obj.inspect} to row type #{db_type.inspect}"
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
# Make the column type detection handle registered row types.
|
517
|
+
def schema_column_type(db_type)
|
518
|
+
if type = @row_schema_types[db_type]
|
519
|
+
type
|
520
|
+
else
|
521
|
+
super
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
private
|
526
|
+
|
527
|
+
# Format composite types used in bound variable arrays.
|
528
|
+
def bound_variable_array(arg)
|
529
|
+
case arg
|
530
|
+
when ArrayRow
|
531
|
+
"\"(#{arg.map{|v| bound_variable_array(v)}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
|
532
|
+
when HashRow
|
533
|
+
arg.check_columns!
|
534
|
+
"\"(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
|
535
|
+
else
|
536
|
+
super
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
# Register the default anonymous record type
|
543
|
+
PG_TYPES[2249] = PGRow::Parser.new(:converter=>PGRow::ArrayRow)
|
544
|
+
if defined?(PGArray) && PGArray.respond_to?(:register)
|
545
|
+
PGArray.register('record', :oid=>2287, :scalar_oid=>2249)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
module SQL::Builders
|
550
|
+
# Wraps the expr array in an anonymous Postgres::PGRow::ArrayRow instance.
|
551
|
+
def pg_row(expr)
|
552
|
+
case expr
|
553
|
+
when Array
|
554
|
+
Postgres::PGRow::ArrayRow.new(expr)
|
555
|
+
else
|
556
|
+
# Will only work if pg_row_ops extension is loaded
|
557
|
+
pg_row_op(expr)
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
Database.register_extension(:pg_row, Postgres::PGRow::DatabaseMethods)
|
563
|
+
end
|
564
|
+
|
565
|
+
if Sequel.core_extensions?
|
566
|
+
class Array
|
567
|
+
# Wraps the receiver in an anonymous Sequel::Postgres::PGRow::ArrayRow instance.
|
568
|
+
def pg_row
|
569
|
+
Sequel::Postgres::PGRow::ArrayRow.new(self)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|