sequel 5.9.0 → 5.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|