sequel 5.9.0 → 5.10.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.
- checksums.yaml +4 -4
- data/CHANGELOG +34 -0
- data/doc/release_notes/5.10.0.txt +84 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +20 -4
- data/lib/sequel/adapters/shared/postgres.rb +12 -2
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -0
- data/lib/sequel/database/schema_generator.rb +3 -0
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/dataset/actions.rb +7 -6
- data/lib/sequel/dataset/misc.rb +14 -0
- data/lib/sequel/extensions/pg_array.rb +83 -79
- data/lib/sequel/extensions/pg_extended_date_support.rb +11 -4
- data/lib/sequel/extensions/pg_range.rb +4 -2
- data/lib/sequel/model/associations.rb +10 -2
- data/lib/sequel/plugins/list.rb +18 -8
- data/lib/sequel/plugins/pg_array_associations.rb +2 -2
- data/lib/sequel/plugins/tree.rb +28 -13
- data/lib/sequel/sql.rb +24 -4
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +94 -26
- data/spec/bin_spec.rb +2 -0
- data/spec/core/dataset_spec.rb +58 -32
- data/spec/core/expression_filters_spec.rb +16 -0
- data/spec/core/spec_helper.rb +1 -0
- data/spec/core_extensions_spec.rb +1 -0
- data/spec/extensions/list_spec.rb +16 -0
- data/spec/extensions/pg_array_associations_spec.rb +10 -10
- data/spec/extensions/pg_range_spec.rb +34 -2
- data/spec/extensions/spec_helper.rb +1 -0
- data/spec/extensions/tree_spec.rb +40 -0
- data/spec/guards_helper.rb +1 -0
- data/spec/model/associations_spec.rb +21 -0
- data/spec/model/spec_helper.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 629d77185f762e5399741ae9b1caf03c8cd2bbe5f72fd9487900dc0c4bfc2e7a
|
4
|
+
data.tar.gz: ee46b9c3180a67eb356b1f2ae7b274cc293687b333bf216b76a3e55def081c57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b5a5c9ed14dcd9bc6c98286bb4c22637052bb2f152f352d5002b096e2d361e2ac6ec15dcd44881e3e96e37b6acd8896161ad94cac4b674d55a6f54a9d43a948
|
7
|
+
data.tar.gz: 847891aeebf175eca649d58461c38d3127311594bb3906bb73429726230b57dd1555ffbd760e3d5f7b30bb9907d5c6d461af1ac0d3b8e166b8dcbf61787fea97
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
=== 5.10.0 (2018-07-01)
|
2
|
+
|
3
|
+
* Use input type casts when using the postgres adapter with pg 0.18+ to reduce string allocations for some primitive types used as prepared statement arguments (jeremyevans)
|
4
|
+
|
5
|
+
* Assume local time if database timezone not specified when handling BC timestamps on JRuby 9.2.0.0 in the pg_extended_date_support extension (jeremyevans)
|
6
|
+
|
7
|
+
* Fix parsing of timetz types in the jdbc/postgresql adapter (jeremyevans)
|
8
|
+
|
9
|
+
* Make SQLTime.parse respect SQLTime.date and Sequel.application_timezone (jeremyevans)
|
10
|
+
|
11
|
+
* Add :top as an option in the list plugin (celsworth) (#1526)
|
12
|
+
|
13
|
+
* Fix Model#{ancestors,descendants,self_and_siblings} in the tree plugin when custom parent/children association names are used (jeremyevans) (#1525)
|
14
|
+
|
15
|
+
* Treat read-only mode error as disconnect error on mysql and mysql2 adapters, for better behavior on AWS Aurora cluster (jeremyevans)
|
16
|
+
|
17
|
+
* Don't use cached placeholder literalizers for in Dataset#{first,where_all,where_each,where_single_value} if argument is empty array or hash (jeremyevans)
|
18
|
+
|
19
|
+
* Support :tablespace option when adding tables, indexes, and materialized views on PostgreSQL (jeremyevans)
|
20
|
+
|
21
|
+
* Support :include option for indexes on PostgreSQL 11+ (jeremyevans)
|
22
|
+
|
23
|
+
* Allow the use of IN/NOT IN operators with set returning functions for Sequel::Model datasets (jeremyevans)
|
24
|
+
|
25
|
+
* Make many_to_pg_array associations in the pg_array_associations plugin work on PostgreSQL 11 (jeremyevans)
|
26
|
+
|
27
|
+
* Only load strscan library in pg_array extension if it is needed (jeremyevans)
|
28
|
+
|
29
|
+
* Don't remove related many_to_one associations from cache when setting column value to existing value for model instances that have not been persisted (jeremyevans) (#1521)
|
30
|
+
|
31
|
+
* Support ruby 2.6+ endless ranges in the pg_range extension (jeremyevans)
|
32
|
+
|
33
|
+
* Support ruby 2.6+ endless ranges in filters, using just a >= operator for them (jeremyevans)
|
34
|
+
|
1
35
|
=== 5.9.0 (2018-06-01)
|
2
36
|
|
3
37
|
* Support generated columns on MySQL 5.7+ and MariaDB 5.2+ (wjordan, jeremyevans) (#1517)
|
@@ -0,0 +1,84 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* Ruby 2.6+ endless ranges are now supported as condition specifier
|
4
|
+
values, using a >= operator for them:
|
5
|
+
|
6
|
+
DB[:t].where(c: 1...)
|
7
|
+
# SELECT * FROM t WHERE (c >= 1)
|
8
|
+
|
9
|
+
* Ruby 2.6+ endless ranges are now supported in the pg_range
|
10
|
+
extension:
|
11
|
+
|
12
|
+
DB[:t].where(id: 1).update(r: 1...)
|
13
|
+
# UPDATE t SET r = '[1,)' WHERE (id = 1)
|
14
|
+
|
15
|
+
* The :include option when creating indexes is now supported on
|
16
|
+
PostgreSQL 11, specifying additional columns to include in the index
|
17
|
+
without indexing them. This is useful to allow index only scans in
|
18
|
+
additional cases.
|
19
|
+
|
20
|
+
* The :tablespace option is now supported when creating tables,
|
21
|
+
indexes, and materialized views on PostgreSQL.
|
22
|
+
|
23
|
+
* The list plugin now supports a :top option, which can be used to
|
24
|
+
specify the top of the list. The default value for the top of the
|
25
|
+
list is 1, but using this option you can make the top of the list be
|
26
|
+
0.
|
27
|
+
|
28
|
+
= Other Improvements
|
29
|
+
|
30
|
+
* In the pg_array_associations plugin, filtering by associations for
|
31
|
+
many_to_pg_array associations now works correctly on PostgreSQL 11.
|
32
|
+
Previously it did not work on PostgreSQL 11 due to new restrictions
|
33
|
+
on using set returning functions in the the SELECT list.
|
34
|
+
|
35
|
+
* When setting the value of a column to the same value the column
|
36
|
+
already has, for a new model object that has not yet been persisted,
|
37
|
+
where the column is used as the foreign key for at least one
|
38
|
+
many_to_one association, do not clear any related associations from
|
39
|
+
the associations cache.
|
40
|
+
|
41
|
+
* In the pg_array extension, if there are separate conversion procs for
|
42
|
+
timetz and time types, the conversion proc for the timetz[] type now
|
43
|
+
correctly uses the conversion proc for the timetz type to convert
|
44
|
+
scalar values, instead of the conversion proc for the time type.
|
45
|
+
|
46
|
+
* Empty arrays and hashes are now correctly handled in
|
47
|
+
Dataset#{first,where_all,where_each,where_single_value} when a
|
48
|
+
cached placeholder literalizer is used.
|
49
|
+
|
50
|
+
* In the tree plugin, Model#{ancestors,descendants,self_and_siblings}
|
51
|
+
now work correctly when custom parent/children association names
|
52
|
+
are used.
|
53
|
+
|
54
|
+
* The inner loop of the postgres adapter row fetching code is now
|
55
|
+
2-3% faster.
|
56
|
+
|
57
|
+
* When using the postgres adapter with pg-0.18+, set a
|
58
|
+
type_map_for_queries for the connection to allow it to handle input
|
59
|
+
type casts for Integer, Float, TrueClass, and FalseClass values
|
60
|
+
without allocating strings.
|
61
|
+
|
62
|
+
* SQLTime.parse (and therefore Sequel.string_to_time) now respects the
|
63
|
+
SQLTime.date and Sequel.application_timezone settings.
|
64
|
+
|
65
|
+
* The jdbc/postgresql adapter now correctly parses timetz types.
|
66
|
+
|
67
|
+
* On JRuby 9.2.0.0, when handling BC timestamps without timezones in
|
68
|
+
the pg_extended_date_support extension, assume local time and not
|
69
|
+
UTC time if the database timezone is not specified and
|
70
|
+
Sequel.datetime_class is Time.
|
71
|
+
|
72
|
+
* Errors indicating that a MySQL database is in read-only mode are
|
73
|
+
now treated as disconnect errors in the mysql and mysql2 adapters,
|
74
|
+
for better behavior in failover scenarios.
|
75
|
+
|
76
|
+
* Sequel::Model datasets now support the use of IN/NOT IN operators
|
77
|
+
where the second argument for the operator (the right hand side) is
|
78
|
+
a set returning function. Previously, the Sequel::Model code
|
79
|
+
assumed the right hand side of an IN/NOT IN operator was a datasets
|
80
|
+
or array, since those are the only values where Sequel will
|
81
|
+
automatically create such an operator.
|
82
|
+
|
83
|
+
* Sequel no longer loads the strscan library in the pg_array extension
|
84
|
+
if it is not necessary because the parser from sequel_pg is used.
|
@@ -214,7 +214,7 @@ module Sequel
|
|
214
214
|
STRING_TYPE = Java::JavaSQL::Types::VARCHAR
|
215
215
|
ARRAY_TYPE = Java::JavaSQL::Types::ARRAY
|
216
216
|
ARRAY_METHOD = Postgres.method(:RubyPGArray)
|
217
|
-
PG_SPECIFIC_TYPES = [ARRAY_TYPE, Java::JavaSQL::Types::OTHER, Java::JavaSQL::Types::STRUCT].freeze
|
217
|
+
PG_SPECIFIC_TYPES = [ARRAY_TYPE, Java::JavaSQL::Types::OTHER, Java::JavaSQL::Types::STRUCT, Java::JavaSQL::Types::TIME_WITH_TIMEZONE, Java::JavaSQL::Types::TIME].freeze
|
218
218
|
HSTORE_METHOD = Postgres.method(:RubyPGHstore)
|
219
219
|
|
220
220
|
def type_convertor(map, meta, type, i)
|
@@ -15,6 +15,12 @@ begin
|
|
15
15
|
end
|
16
16
|
|
17
17
|
Sequel::Postgres::USES_PG = true
|
18
|
+
if defined?(PG::TypeMapByClass)
|
19
|
+
type_map = Sequel::Postgres::PG_QUERY_TYPE_MAP = PG::TypeMapByClass.new
|
20
|
+
type_map[Integer] = PG::TextEncoder::Integer.new
|
21
|
+
type_map[FalseClass] = type_map[TrueClass] = PG::TextEncoder::Boolean.new
|
22
|
+
type_map[Float] = PG::TextEncoder::Float.new
|
23
|
+
end
|
18
24
|
rescue LoadError => e
|
19
25
|
begin
|
20
26
|
require 'postgres-pr/postgres-compat'
|
@@ -211,6 +217,9 @@ module Sequel
|
|
211
217
|
end
|
212
218
|
|
213
219
|
conn.instance_variable_set(:@db, self)
|
220
|
+
if USES_PG && conn.respond_to?(:type_map_for_queries=) && defined?(PG_QUERY_TYPE_MAP)
|
221
|
+
conn.type_map_for_queries = PG_QUERY_TYPE_MAP
|
222
|
+
end
|
214
223
|
|
215
224
|
if encoding = opts[:encoding] || opts[:charset]
|
216
225
|
if conn.respond_to?(:set_client_encoding)
|
@@ -737,9 +746,9 @@ module Sequel
|
|
737
746
|
cols = []
|
738
747
|
procs = db.conversion_procs
|
739
748
|
res.nfields.times do |fieldnum|
|
740
|
-
cols << [
|
749
|
+
cols << [procs[res.ftype(fieldnum)], output_identifier(res.fname(fieldnum))]
|
741
750
|
end
|
742
|
-
self.columns = cols.map{|c| c[
|
751
|
+
self.columns = cols.map{|c| c[1]}
|
743
752
|
cols
|
744
753
|
end
|
745
754
|
|
@@ -756,13 +765,20 @@ module Sequel
|
|
756
765
|
# For each row in the result set, yield a hash with column name symbol
|
757
766
|
# keys and typecasted values.
|
758
767
|
def yield_hash_rows(res, cols)
|
759
|
-
res.ntuples
|
768
|
+
ntuples = res.ntuples
|
769
|
+
recnum = 0
|
770
|
+
while recnum < ntuples
|
771
|
+
fieldnum = 0
|
772
|
+
nfields = cols.length
|
760
773
|
converted_rec = {}
|
761
|
-
|
774
|
+
while fieldnum < nfields
|
775
|
+
type_proc, fieldsym = cols[fieldnum]
|
762
776
|
value = res.getvalue(recnum, fieldnum)
|
763
777
|
converted_rec[fieldsym] = (value && type_proc) ? type_proc.call(value) : value
|
778
|
+
fieldnum += 1
|
764
779
|
end
|
765
780
|
yield converted_rec
|
781
|
+
recnum += 1
|
766
782
|
end
|
767
783
|
end
|
768
784
|
end
|
@@ -1042,6 +1042,10 @@ module Sequel
|
|
1042
1042
|
sql += " ON COMMIT #{ON_COMMIT[on_commit]}"
|
1043
1043
|
end
|
1044
1044
|
|
1045
|
+
if tablespace = options[:tablespace]
|
1046
|
+
sql += " TABLESPACE #{quote_identifier(tablespace)}"
|
1047
|
+
end
|
1048
|
+
|
1045
1049
|
if server = options[:foreign]
|
1046
1050
|
sql += " SERVER #{quote_identifier(server)}"
|
1047
1051
|
if foreign_opts = options[:options]
|
@@ -1077,7 +1081,13 @@ module Sequel
|
|
1077
1081
|
|
1078
1082
|
# DDL fragment for initial part of CREATE VIEW statement
|
1079
1083
|
def create_view_prefix_sql(name, options)
|
1080
|
-
create_view_sql_append_columns("CREATE #{'OR REPLACE 'if options[:replace]}#{'TEMPORARY 'if options[:temp]}#{'RECURSIVE ' if options[:recursive]}#{'MATERIALIZED ' if options[:materialized]}VIEW #{quote_schema_table(name)}", options[:columns] || options[:recursive])
|
1084
|
+
sql = create_view_sql_append_columns("CREATE #{'OR REPLACE 'if options[:replace]}#{'TEMPORARY 'if options[:temp]}#{'RECURSIVE ' if options[:recursive]}#{'MATERIALIZED ' if options[:materialized]}VIEW #{quote_schema_table(name)}", options[:columns] || options[:recursive])
|
1085
|
+
|
1086
|
+
if tablespace = options[:tablespace]
|
1087
|
+
sql += " TABLESPACE #{quote_identifier(tablespace)}"
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
sql
|
1081
1091
|
end
|
1082
1092
|
|
1083
1093
|
# SQL for dropping a function from the database.
|
@@ -1147,7 +1157,7 @@ module Sequel
|
|
1147
1157
|
when :spatial
|
1148
1158
|
index_type = :gist
|
1149
1159
|
end
|
1150
|
-
"CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
|
1160
|
+
"CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{" INCLUDE #{literal(Array(index[:include]))}" if index[:include]}#{" TABLESPACE #{quote_identifier(index[:tablespace])}" if index[:tablespace]}#{filter}"
|
1151
1161
|
end
|
1152
1162
|
|
1153
1163
|
# Setup datastructures shared by all postgres adapters.
|
@@ -17,6 +17,7 @@ module Sequel
|
|
17
17
|
MySQL client is not connected
|
18
18
|
This connection is still waiting for a result, try again once you have the result
|
19
19
|
closed MySQL connection
|
20
|
+
The MySQL server is running with the --read-only option so it cannot execute this statement
|
20
21
|
END
|
21
22
|
# Error messages for mysql and mysql2 that indicate the current connection should be disconnected
|
22
23
|
MYSQL_DATABASE_DISCONNECT_ERRORS = /\A#{Regexp.union(disconnect_errors)}/
|
@@ -222,6 +222,9 @@ module Sequel
|
|
222
222
|
# operations on the table while the index is being
|
223
223
|
# built.
|
224
224
|
# :opclass :: Use a specific operator class in the index.
|
225
|
+
# :include :: Include additional column values in the index, without
|
226
|
+
# actually indexing on those values (PostgreSQL 11+).
|
227
|
+
# :tablespace :: Specify tablespace for index.
|
225
228
|
#
|
226
229
|
# Microsoft SQL Server specific options:
|
227
230
|
#
|
@@ -181,6 +181,7 @@ module Sequel
|
|
181
181
|
# where keys are option names and values are option values. Note
|
182
182
|
# that option names are unquoted, so you should not use untrusted
|
183
183
|
# keys.
|
184
|
+
# :tablespace :: The tablespace to use for the table.
|
184
185
|
#
|
185
186
|
# See <tt>Schema::CreateTableGenerator</tt> and the {"Schema Modification" guide}[rdoc-ref:doc/schema_modification.rdoc].
|
186
187
|
def create_table(name, options=OPTS, &block)
|
@@ -281,6 +282,7 @@ module Sequel
|
|
281
282
|
# in a subquery, if you are providing a Dataset as the source
|
282
283
|
# argument, if should probably call the union method with the
|
283
284
|
# all: true and from_self: false options.
|
285
|
+
# :tablespace :: The tablespace to use for materialized views.
|
284
286
|
def create_view(name, source, options = OPTS)
|
285
287
|
execute_ddl(create_view_sql(name, source, options))
|
286
288
|
remove_cached_schema(name)
|
@@ -231,10 +231,11 @@ module Sequel
|
|
231
231
|
|
232
232
|
return res
|
233
233
|
end
|
234
|
+
where_args = args
|
234
235
|
args = arg
|
235
236
|
end
|
236
237
|
|
237
|
-
if loader =
|
238
|
+
if loader = cached_where_placeholder_literalizer(where_args||args, block, :_first_cond_loader) do |pl|
|
238
239
|
_single_record_ds.where(pl.arg)
|
239
240
|
end
|
240
241
|
|
@@ -875,7 +876,7 @@ module Sequel
|
|
875
876
|
# DB[:table].where_all(id: [1,2,3])
|
876
877
|
# # SELECT * FROM table WHERE (id IN (1, 2, 3))
|
877
878
|
def where_all(cond, &block)
|
878
|
-
if loader = _where_loader
|
879
|
+
if loader = _where_loader([cond], nil)
|
879
880
|
loader.all(filter_expr(cond), &block)
|
880
881
|
else
|
881
882
|
where(cond).all(&block)
|
@@ -889,7 +890,7 @@ module Sequel
|
|
889
890
|
# DB[:table].where_each(id: [1,2,3]){|row| p row}
|
890
891
|
# # SELECT * FROM table WHERE (id IN (1, 2, 3))
|
891
892
|
def where_each(cond, &block)
|
892
|
-
if loader = _where_loader
|
893
|
+
if loader = _where_loader([cond], nil)
|
893
894
|
loader.each(filter_expr(cond), &block)
|
894
895
|
else
|
895
896
|
where(cond).each(&block)
|
@@ -904,7 +905,7 @@ module Sequel
|
|
904
905
|
# DB[:table].select(:name).where_single_value(id: 1)
|
905
906
|
# # SELECT name FROM table WHERE (id = 1) LIMIT 1
|
906
907
|
def where_single_value(cond)
|
907
|
-
if loader =
|
908
|
+
if loader = cached_where_placeholder_literalizer([cond], nil, :_where_single_value_loader) do |pl|
|
908
909
|
single_value_ds.where(pl.arg)
|
909
910
|
end
|
910
911
|
|
@@ -1039,8 +1040,8 @@ module Sequel
|
|
1039
1040
|
end
|
1040
1041
|
|
1041
1042
|
# Loader used for where_all and where_each.
|
1042
|
-
def _where_loader
|
1043
|
-
|
1043
|
+
def _where_loader(where_args, where_block)
|
1044
|
+
cached_where_placeholder_literalizer(where_args, where_block, :_where_loader) do |pl|
|
1044
1045
|
where(pl.arg)
|
1045
1046
|
end
|
1046
1047
|
end
|
data/lib/sequel/dataset/misc.rb
CHANGED
@@ -309,6 +309,20 @@ module Sequel
|
|
309
309
|
loader
|
310
310
|
end
|
311
311
|
|
312
|
+
# Return a cached placeholder literalizer for the key, unless where_block is
|
313
|
+
# nil and where_args is an empty array or hash. This is designed to guard
|
314
|
+
# against placeholder literalizer use when passing arguments to where
|
315
|
+
# in the uncached case and filter_expr if a cached placeholder literalizer
|
316
|
+
# is used.
|
317
|
+
def cached_where_placeholder_literalizer(where_args, where_block, key, &block)
|
318
|
+
where_args = where_args[0] if where_args.length == 1
|
319
|
+
unless where_block
|
320
|
+
return if where_args == OPTS || where_args == EMPTY_ARRAY
|
321
|
+
end
|
322
|
+
|
323
|
+
cached_placeholder_literalizer(key, &block)
|
324
|
+
end
|
325
|
+
|
312
326
|
# Set the columns for the current dataset.
|
313
327
|
def columns=(v)
|
314
328
|
cache_set(:_columns, v)
|
@@ -65,12 +65,12 @@
|
|
65
65
|
# If you want an easy way to call PostgreSQL array functions and
|
66
66
|
# operators, look into the pg_array_ops extension.
|
67
67
|
#
|
68
|
-
# This extension requires the
|
68
|
+
# This extension requires the delegate library, and the strscan library
|
69
|
+
# sequel_pg has not been loaded.
|
69
70
|
#
|
70
71
|
# Related module: Sequel::Postgres::PGArray
|
71
72
|
|
72
73
|
require 'delegate'
|
73
|
-
require 'strscan'
|
74
74
|
|
75
75
|
module Sequel
|
76
76
|
module Postgres
|
@@ -99,7 +99,7 @@ module Sequel
|
|
99
99
|
register_array_type('bytea', :oid=>1001, :scalar_oid=>17, :type_symbol=>:blob)
|
100
100
|
register_array_type('date', :oid=>1182, :scalar_oid=>1082)
|
101
101
|
register_array_type('time without time zone', :oid=>1183, :scalar_oid=>1083, :type_symbol=>:time)
|
102
|
-
register_array_type('time with time zone', :oid=>1270, :scalar_oid=>
|
102
|
+
register_array_type('time with time zone', :oid=>1270, :scalar_oid=>1266, :type_symbol=>:time_timezone, :scalar_typecast=>:time)
|
103
103
|
|
104
104
|
register_array_type('smallint', :oid=>1005, :scalar_oid=>21, :scalar_typecast=>:integer)
|
105
105
|
register_array_type('oid', :oid=>1028, :scalar_oid=>26, :scalar_typecast=>:integer)
|
@@ -300,93 +300,97 @@ module Sequel
|
|
300
300
|
end
|
301
301
|
end
|
302
302
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
#
|
308
|
-
#
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
303
|
+
unless Sequel::Postgres.respond_to?(:parse_pg_array)
|
304
|
+
require 'strscan'
|
305
|
+
|
306
|
+
# PostgreSQL array parser that handles PostgreSQL array output format.
|
307
|
+
# Note that does not handle all forms out input that PostgreSQL will
|
308
|
+
# accept, and it will not raise an error for all forms of invalid input.
|
309
|
+
class Parser < StringScanner
|
310
|
+
# Set the source for the input, and any converter callable
|
311
|
+
# to call with objects to be created. For nested parsers
|
312
|
+
# the source may contain text after the end current parse,
|
313
|
+
# which will be ignored.
|
314
|
+
def initialize(source, converter=nil)
|
315
|
+
super(source)
|
316
|
+
@converter = converter
|
317
|
+
@stack = [[]]
|
318
|
+
@encoding = string.encoding
|
319
|
+
@recorded = String.new.force_encoding(@encoding)
|
320
|
+
end
|
318
321
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
322
|
+
# Take the buffer of recorded characters and add it to the array
|
323
|
+
# of entries, and use a new buffer for recorded characters.
|
324
|
+
def new_entry(include_empty=false)
|
325
|
+
if !@recorded.empty? || include_empty
|
326
|
+
entry = @recorded
|
327
|
+
if entry == 'NULL' && !include_empty
|
328
|
+
entry = nil
|
329
|
+
elsif @converter
|
330
|
+
entry = @converter.call(entry)
|
331
|
+
end
|
332
|
+
@stack.last.push(entry)
|
333
|
+
@recorded = String.new.force_encoding(@encoding)
|
328
334
|
end
|
329
|
-
@stack.last.push(entry)
|
330
|
-
@recorded = String.new.force_encoding(@encoding)
|
331
335
|
end
|
332
|
-
end
|
333
336
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
337
|
+
# Parse the input character by character, returning an array
|
338
|
+
# of parsed (and potentially converted) objects.
|
339
|
+
def parse
|
340
|
+
raise Sequel::Error, "invalid array, empty string" if eos?
|
341
|
+
raise Sequel::Error, "invalid array, doesn't start with {" unless scan(/((\[\d+:\d+\])+=)?\{/)
|
342
|
+
|
343
|
+
while !eos?
|
344
|
+
char = scan(/[{}",]|[^{}",]+/)
|
345
|
+
if char == ','
|
346
|
+
# Comma outside quoted string indicates end of current entry
|
347
|
+
new_entry
|
348
|
+
elsif char == '"'
|
349
|
+
raise Sequel::Error, "invalid array, opening quote with existing recorded data" unless @recorded.empty?
|
350
|
+
while true
|
351
|
+
char = scan(/["\\]|[^"\\]+/)
|
352
|
+
if char == '\\'
|
353
|
+
@recorded << getch
|
354
|
+
elsif char == '"'
|
355
|
+
n = peek(1)
|
356
|
+
raise Sequel::Error, "invalid array, closing quote not followed by comma or closing brace" unless n == ',' || n == '}'
|
357
|
+
break
|
358
|
+
else
|
359
|
+
@recorded << char
|
360
|
+
end
|
361
|
+
end
|
362
|
+
new_entry(true)
|
363
|
+
elsif char == '{'
|
364
|
+
raise Sequel::Error, "invalid array, opening brace with existing recorded data" unless @recorded.empty?
|
365
|
+
|
366
|
+
# Start of new array, add it to the stack
|
367
|
+
new = []
|
368
|
+
@stack.last << new
|
369
|
+
@stack << new
|
370
|
+
elsif char == '}'
|
371
|
+
# End of current array, add current entry to the current array
|
372
|
+
new_entry
|
373
|
+
|
374
|
+
if @stack.length == 1
|
375
|
+
raise Sequel::Error, "array parsing finished without parsing entire string" unless eos?
|
376
|
+
|
377
|
+
# Top level of array, parsing should be over.
|
378
|
+
# Pop current array off stack and return it as result
|
379
|
+
return @stack.pop
|
355
380
|
else
|
356
|
-
|
381
|
+
# Nested array, pop current array off stack
|
382
|
+
@stack.pop
|
357
383
|
end
|
358
|
-
end
|
359
|
-
new_entry(true)
|
360
|
-
elsif char == '{'
|
361
|
-
raise Sequel::Error, "invalid array, opening brace with existing recorded data" unless @recorded.empty?
|
362
|
-
|
363
|
-
# Start of new array, add it to the stack
|
364
|
-
new = []
|
365
|
-
@stack.last << new
|
366
|
-
@stack << new
|
367
|
-
elsif char == '}'
|
368
|
-
# End of current array, add current entry to the current array
|
369
|
-
new_entry
|
370
|
-
|
371
|
-
if @stack.length == 1
|
372
|
-
raise Sequel::Error, "array parsing finished without parsing entire string" unless eos?
|
373
|
-
|
374
|
-
# Top level of array, parsing should be over.
|
375
|
-
# Pop current array off stack and return it as result
|
376
|
-
return @stack.pop
|
377
384
|
else
|
378
|
-
#
|
379
|
-
@
|
385
|
+
# Add the character to the recorded character buffer.
|
386
|
+
@recorded << char
|
380
387
|
end
|
381
|
-
else
|
382
|
-
# Add the character to the recorded character buffer.
|
383
|
-
@recorded << char
|
384
388
|
end
|
385
|
-
end
|
386
389
|
|
387
|
-
|
390
|
+
raise Sequel::Error, "array parsing finished with array unclosed"
|
391
|
+
end
|
388
392
|
end
|
389
|
-
end
|
393
|
+
end
|
390
394
|
|
391
395
|
# Callable object that takes the input string and parses it using Parser.
|
392
396
|
class Creator
|