sequel 5.39.0 → 5.62.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +294 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +57 -25
- data/bin/sequel +11 -3
- data/doc/advanced_associations.rdoc +13 -13
- data/doc/association_basics.rdoc +89 -24
- data/doc/cheat_sheet.rdoc +11 -3
- data/doc/migration.rdoc +12 -6
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +18 -11
- data/doc/postgresql.rdoc +16 -8
- data/doc/querying.rdoc +5 -3
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/release_notes/5.45.0.txt +34 -0
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/release_notes/5.55.0.txt +21 -0
- data/doc/release_notes/5.56.0.txt +51 -0
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.60.0.txt +22 -0
- data/doc/release_notes/5.61.0.txt +43 -0
- data/doc/release_notes/5.62.0.txt +132 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sql.rdoc +27 -15
- data/doc/testing.rdoc +22 -11
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +17 -17
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +8 -0
- data/lib/sequel/adapters/jdbc/h2.rb +60 -10
- data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
- data/lib/sequel/adapters/jdbc.rb +16 -18
- data/lib/sequel/adapters/mysql.rb +80 -67
- data/lib/sequel/adapters/mysql2.rb +54 -49
- data/lib/sequel/adapters/odbc.rb +6 -2
- data/lib/sequel/adapters/oracle.rb +3 -3
- data/lib/sequel/adapters/postgres.rb +83 -40
- data/lib/sequel/adapters/shared/access.rb +11 -1
- data/lib/sequel/adapters/shared/db2.rb +30 -0
- data/lib/sequel/adapters/shared/mssql.rb +58 -7
- data/lib/sequel/adapters/shared/mysql.rb +40 -2
- data/lib/sequel/adapters/shared/oracle.rb +76 -0
- data/lib/sequel/adapters/shared/postgres.rb +418 -174
- data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -0
- data/lib/sequel/adapters/shared/sqlite.rb +102 -11
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +60 -18
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- data/lib/sequel/connection_pool/single.rb +6 -8
- data/lib/sequel/connection_pool.rb +42 -28
- data/lib/sequel/core.rb +28 -18
- data/lib/sequel/database/connecting.rb +26 -2
- data/lib/sequel/database/misc.rb +69 -14
- data/lib/sequel/database/query.rb +38 -1
- data/lib/sequel/database/schema_generator.rb +45 -52
- data/lib/sequel/database/schema_methods.rb +17 -1
- data/lib/sequel/dataset/actions.rb +82 -13
- data/lib/sequel/dataset/features.rb +20 -0
- data/lib/sequel/dataset/misc.rb +1 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/query.rb +118 -16
- data/lib/sequel/dataset/sql.rb +177 -47
- data/lib/sequel/extensions/_model_pg_row.rb +0 -12
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/any_not_empty.rb +1 -1
- data/lib/sequel/extensions/async_thread_pool.rb +438 -0
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/blank.rb +8 -0
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/core_refinements.rb +36 -11
- data/lib/sequel/extensions/date_arithmetic.rb +71 -31
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/inflector.rb +9 -1
- data/lib/sequel/extensions/is_distinct_from.rb +141 -0
- data/lib/sequel/extensions/looser_typecasting.rb +3 -0
- data/lib/sequel/extensions/migration.rb +7 -2
- data/lib/sequel/extensions/named_timezones.rb +22 -6
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +23 -3
- data/lib/sequel/extensions/pg_array_ops.rb +2 -2
- data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
- data/lib/sequel/extensions/pg_enum.rb +1 -1
- data/lib/sequel/extensions/pg_extended_date_support.rb +28 -25
- data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
- data/lib/sequel/extensions/pg_hstore.rb +6 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
- data/lib/sequel/extensions/pg_inet.rb +10 -11
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +45 -19
- data/lib/sequel/extensions/pg_json.rb +13 -15
- data/lib/sequel/extensions/pg_json_ops.rb +73 -2
- data/lib/sequel/extensions/pg_loose_count.rb +3 -1
- data/lib/sequel/extensions/pg_multirange.rb +367 -0
- data/lib/sequel/extensions/pg_range.rb +10 -23
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row.rb +19 -13
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/query.rb +2 -0
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/schema_dumper.rb +13 -2
- data/lib/sequel/extensions/server_block.rb +8 -12
- data/lib/sequel/extensions/sql_comments.rb +110 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/extensions/symbol_aref.rb +2 -0
- data/lib/sequel/model/associations.rb +324 -95
- data/lib/sequel/model/base.rb +51 -27
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model/plugins.rb +5 -0
- data/lib/sequel/plugins/association_proxies.rb +2 -0
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +87 -15
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/column_encryption.rb +728 -0
- data/lib/sequel/plugins/composition.rb +10 -4
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/constraint_validations.rb +2 -1
- data/lib/sequel/plugins/dataset_associations.rb +4 -1
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/enum.rb +124 -0
- data/lib/sequel/plugins/finder.rb +3 -1
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/instance_specific_default.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +39 -24
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/list.rb +3 -1
- data/lib/sequel/plugins/many_through_many.rb +108 -9
- data/lib/sequel/plugins/nested_attributes.rb +12 -7
- data/lib/sequel/plugins/pg_array_associations.rb +56 -38
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -1
- data/lib/sequel/plugins/prepared_statements.rb +10 -1
- data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
- data/lib/sequel/plugins/rcte_tree.rb +27 -19
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/serialization.rb +9 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +1 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +15 -2
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validation_helpers.rb +38 -11
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +12 -14
- data/lib/sequel/version.rb +1 -1
- metadata +94 -43
@@ -51,6 +51,11 @@ module Sequel
|
|
51
51
|
false
|
52
52
|
end
|
53
53
|
|
54
|
+
# Whether deleting from joined datasets is supported, false by default.
|
55
|
+
def supports_deleting_joins?
|
56
|
+
supports_modifying_joins?
|
57
|
+
end
|
58
|
+
|
54
59
|
# Whether the database supports derived column lists (e.g.
|
55
60
|
# "table_expr AS table_alias(column_alias1, column_alias2, ...)"), true by
|
56
61
|
# default.
|
@@ -120,6 +125,11 @@ module Sequel
|
|
120
125
|
false
|
121
126
|
end
|
122
127
|
|
128
|
+
# Whether the MERGE statement is supported, false by default.
|
129
|
+
def supports_merge?
|
130
|
+
false
|
131
|
+
end
|
132
|
+
|
123
133
|
# Whether modifying joined datasets is supported, false by default.
|
124
134
|
def supports_modifying_joins?
|
125
135
|
false
|
@@ -142,6 +152,11 @@ module Sequel
|
|
142
152
|
supports_distinct_on?
|
143
153
|
end
|
144
154
|
|
155
|
+
# Whether placeholder literalizers are supported, true by default.
|
156
|
+
def supports_placeholder_literalizer?
|
157
|
+
true
|
158
|
+
end
|
159
|
+
|
145
160
|
# Whether the dataset supports pattern matching by regular expressions, false by default.
|
146
161
|
def supports_regexp?
|
147
162
|
false
|
@@ -178,6 +193,11 @@ module Sequel
|
|
178
193
|
true
|
179
194
|
end
|
180
195
|
|
196
|
+
# Whether updating joined datasets is supported, false by default.
|
197
|
+
def supports_updating_joins?
|
198
|
+
supports_modifying_joins?
|
199
|
+
end
|
200
|
+
|
181
201
|
# Whether the dataset supports the WINDOW clause to define windows used by multiple
|
182
202
|
# window functions, false by default.
|
183
203
|
def supports_window_clause?
|
data/lib/sequel/dataset/misc.rb
CHANGED
@@ -201,7 +201,9 @@ module Sequel
|
|
201
201
|
when :insert_pk
|
202
202
|
fetch_rows(prepared_sql){|r| return r.values.first}
|
203
203
|
when Array
|
204
|
+
# :nocov:
|
204
205
|
case prepared_type[0]
|
206
|
+
# :nocov:
|
205
207
|
when :map, :as_hash, :to_hash, :to_hash_groups
|
206
208
|
public_send(*prepared_type, &block)
|
207
209
|
end
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -65,7 +65,7 @@ module Sequel
|
|
65
65
|
Sequel.synchronize{EXTENSIONS[ext] = block}
|
66
66
|
end
|
67
67
|
|
68
|
-
# On Ruby 2.4+, use clone(:
|
68
|
+
# On Ruby 2.4+, use clone(freeze: false) to create clones, because
|
69
69
|
# we use true freezing in that case, and we need to modify the opts
|
70
70
|
# in the frozen copy.
|
71
71
|
#
|
@@ -116,7 +116,7 @@ module Sequel
|
|
116
116
|
# DB[:items].order(:id).distinct(:id) # SQL: SELECT DISTINCT ON (id) * FROM items ORDER BY id
|
117
117
|
# DB[:items].order(:id).distinct{func(:id)} # SQL: SELECT DISTINCT ON (func(id)) * FROM items ORDER BY id
|
118
118
|
#
|
119
|
-
# There is support for
|
119
|
+
# There is support for emulating the DISTINCT ON support in MySQL, but it
|
120
120
|
# does not support the ORDER of the dataset, and also doesn't work in many
|
121
121
|
# cases if the ONLY_FULL_GROUP_BY sql_mode is used, which is the default on
|
122
122
|
# MySQL 5.7.5+.
|
@@ -508,6 +508,7 @@ module Sequel
|
|
508
508
|
# argument.
|
509
509
|
# :implicit_qualifier :: The name to use for qualifying implicit conditions. By default,
|
510
510
|
# the last joined or primary table is used.
|
511
|
+
# :join_using :: Force the using of JOIN USING, even if +expr+ is not an array of symbols.
|
511
512
|
# :reset_implicit_qualifier :: Can set to false to ignore this join when future joins determine qualifier
|
512
513
|
# for implicit conditions.
|
513
514
|
# :qualify :: Can be set to false to not do any implicit qualification. Can be set
|
@@ -541,7 +542,7 @@ module Sequel
|
|
541
542
|
return s.join_table(type, ds, expr, options, &block)
|
542
543
|
end
|
543
544
|
|
544
|
-
using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
|
545
|
+
using_join = options[:join_using] || (expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)})
|
545
546
|
if using_join && !supports_join_using?
|
546
547
|
h = {}
|
547
548
|
expr.each{|e| h[e] = e}
|
@@ -616,7 +617,7 @@ module Sequel
|
|
616
617
|
UNCONDITIONED_JOIN_TYPES.each do |jtype|
|
617
618
|
class_eval(<<-END, __FILE__, __LINE__+1)
|
618
619
|
def #{jtype}_join(table, opts=Sequel::OPTS)
|
619
|
-
raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if
|
620
|
+
raise(Sequel::Error, '#{jtype}_join does not accept join table blocks') if defined?(yield)
|
620
621
|
raise(Sequel::Error, '#{jtype}_join 2nd argument should be an options hash, not conditions') unless opts.is_a?(Hash)
|
621
622
|
join_table(:#{jtype}, table, nil, opts)
|
622
623
|
end
|
@@ -677,6 +678,56 @@ module Sequel
|
|
677
678
|
clone(:lock => style)
|
678
679
|
end
|
679
680
|
|
681
|
+
# Return a dataset with a WHEN MATCHED THEN DELETE clause added to the
|
682
|
+
# MERGE statement. If a block is passed, treat it as a virtual row and
|
683
|
+
# use it as additional conditions for the match.
|
684
|
+
#
|
685
|
+
# merge_delete
|
686
|
+
# # WHEN MATCHED THEN DELETE
|
687
|
+
#
|
688
|
+
# merge_delete{a > 30}
|
689
|
+
# # WHEN MATCHED AND (a > 30) THEN DELETE
|
690
|
+
def merge_delete(&block)
|
691
|
+
_merge_when(:type=>:delete, &block)
|
692
|
+
end
|
693
|
+
|
694
|
+
# Return a dataset with a WHEN NOT MATCHED THEN INSERT clause added to the
|
695
|
+
# MERGE statement. If a block is passed, treat it as a virtual row and
|
696
|
+
# use it as additional conditions for the match.
|
697
|
+
#
|
698
|
+
# The arguments provided can be any arguments that would be accepted by
|
699
|
+
# #insert.
|
700
|
+
#
|
701
|
+
# merge_insert(i1: :i2, a: Sequel[:b]+11)
|
702
|
+
# # WHEN NOT MATCHED THEN INSERT (i1, a) VALUES (i2, (b + 11))
|
703
|
+
#
|
704
|
+
# merge_insert(:i2, Sequel[:b]+11){a > 30}
|
705
|
+
# # WHEN NOT MATCHED AND (a > 30) THEN INSERT VALUES (i2, (b + 11))
|
706
|
+
def merge_insert(*values, &block)
|
707
|
+
_merge_when(:type=>:insert, :values=>values, &block)
|
708
|
+
end
|
709
|
+
|
710
|
+
# Return a dataset with a WHEN MATCHED THEN UPDATE clause added to the
|
711
|
+
# MERGE statement. If a block is passed, treat it as a virtual row and
|
712
|
+
# use it as additional conditions for the match.
|
713
|
+
#
|
714
|
+
# merge_update(i1: Sequel[:i1]+:i2+10, a: Sequel[:a]+:b+20)
|
715
|
+
# # WHEN MATCHED THEN UPDATE SET i1 = (i1 + i2 + 10), a = (a + b + 20)
|
716
|
+
#
|
717
|
+
# merge_update(i1: :i2){a > 30}
|
718
|
+
# # WHEN MATCHED AND (a > 30) THEN UPDATE SET i1 = i2
|
719
|
+
def merge_update(values, &block)
|
720
|
+
_merge_when(:type=>:update, :values=>values, &block)
|
721
|
+
end
|
722
|
+
|
723
|
+
# Return a dataset with the source and join condition to use for the MERGE statement.
|
724
|
+
#
|
725
|
+
# merge_using(:m2, i1: :i2)
|
726
|
+
# # USING m2 ON (i1 = i2)
|
727
|
+
def merge_using(source, join_condition)
|
728
|
+
clone(:merge_using => [source, join_condition].freeze)
|
729
|
+
end
|
730
|
+
|
680
731
|
# Returns a cloned dataset without a row_proc.
|
681
732
|
#
|
682
733
|
# ds = DB[:items].with_row_proc(:invert.to_proc)
|
@@ -699,7 +750,7 @@ module Sequel
|
|
699
750
|
end
|
700
751
|
|
701
752
|
# Returns a copy of the dataset with a specified order. Can be safely combined with limit.
|
702
|
-
# If you call limit with an offset, it will override
|
753
|
+
# If you call limit with an offset, it will override the offset if you've called
|
703
754
|
# offset first.
|
704
755
|
#
|
705
756
|
# DB[:items].offset(10) # SELECT * FROM items OFFSET 10
|
@@ -736,7 +787,7 @@ module Sequel
|
|
736
787
|
# DB[:items].order(Sequel.lit('a + b')) # SELECT * FROM items ORDER BY a + b
|
737
788
|
# DB[:items].order(Sequel[:a] + :b) # SELECT * FROM items ORDER BY (a + b)
|
738
789
|
# DB[:items].order(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name DESC
|
739
|
-
# DB[:items].order(Sequel.asc(:name, :
|
790
|
+
# DB[:items].order(Sequel.asc(:name, nulls: :last)) # SELECT * FROM items ORDER BY name ASC NULLS LAST
|
740
791
|
# DB[:items].order{sum(name).desc} # SELECT * FROM items ORDER BY sum(name) DESC
|
741
792
|
# DB[:items].order(nil) # SELECT * FROM items
|
742
793
|
def order(*columns, &block)
|
@@ -806,13 +857,13 @@ module Sequel
|
|
806
857
|
# DB[:items].returning(nil) # RETURNING NULL
|
807
858
|
# DB[:items].returning(:id, :name) # RETURNING id, name
|
808
859
|
#
|
809
|
-
# DB[:items].returning.insert(:
|
860
|
+
# DB[:items].returning.insert(a: 1) do |hash|
|
810
861
|
# # hash for each row inserted, with values for all columns
|
811
862
|
# end
|
812
|
-
# DB[:items].returning.update(:
|
863
|
+
# DB[:items].returning.update(a: 1) do |hash|
|
813
864
|
# # hash for each row updated, with values for all columns
|
814
865
|
# end
|
815
|
-
# DB[:items].returning.delete(:
|
866
|
+
# DB[:items].returning.delete(a: 1) do |hash|
|
816
867
|
# # hash for each row deleted, with values for all columns
|
817
868
|
# end
|
818
869
|
def returning(*values)
|
@@ -1051,7 +1102,7 @@ module Sequel
|
|
1051
1102
|
# referenced in window functions. See Sequel::SQL::Window for a list of
|
1052
1103
|
# options that can be passed in. Example:
|
1053
1104
|
#
|
1054
|
-
# DB[:items].window(:w, :
|
1105
|
+
# DB[:items].window(:w, partition: :c1, order: :c2)
|
1055
1106
|
# # SELECT * FROM items WINDOW w AS (PARTITION BY c1 ORDER BY c2)
|
1056
1107
|
def window(name, opts)
|
1057
1108
|
clone(:window=>((@opts[:window]||EMPTY_ARRAY) + [[name, SQL::Window.new(opts)].freeze]).freeze)
|
@@ -1059,13 +1110,12 @@ module Sequel
|
|
1059
1110
|
|
1060
1111
|
# Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
|
1061
1112
|
# A common table expression acts as an inline view for the query.
|
1113
|
+
#
|
1062
1114
|
# Options:
|
1063
1115
|
# :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
|
1064
1116
|
# :recursive :: Specify that this is a recursive CTE
|
1065
|
-
#
|
1066
|
-
# PostgreSQL Specific Options:
|
1067
1117
|
# :materialized :: Set to false to force inlining of the CTE, or true to force not inlining
|
1068
|
-
# the CTE (PostgreSQL 12+).
|
1118
|
+
# the CTE (PostgreSQL 12+/SQLite 3.35+).
|
1069
1119
|
#
|
1070
1120
|
# DB[:items].with(:items, DB[:syx].where(Sequel[:name].like('A%')))
|
1071
1121
|
# # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%' ESCAPE '\')) SELECT * FROM items
|
@@ -1081,20 +1131,60 @@ module Sequel
|
|
1081
1131
|
|
1082
1132
|
# Add a recursive common table expression (CTE) with the given name, a dataset that
|
1083
1133
|
# defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
|
1084
|
-
# of the CTE.
|
1134
|
+
# of the CTE.
|
1135
|
+
#
|
1136
|
+
# Options:
|
1085
1137
|
# :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
|
1086
1138
|
# :union_all :: Set to false to use UNION instead of UNION ALL combining the nonrecursive and recursive parts.
|
1087
1139
|
#
|
1140
|
+
# PostgreSQL 14+ Options:
|
1141
|
+
# :cycle :: Stop recursive searching when a cycle is detected. Includes two columns in the
|
1142
|
+
# result of the CTE, a cycle column indicating whether a cycle was detected for
|
1143
|
+
# the current row, and a path column for the path traversed to get to the current
|
1144
|
+
# row. If given, must be a hash with the following keys:
|
1145
|
+
# :columns :: (required) The column or array of columns to use to detect a cycle.
|
1146
|
+
# If the value of these columns match columns already traversed, then
|
1147
|
+
# a cycle is detected, and recursive searching will not traverse beyond
|
1148
|
+
# the cycle (the CTE will include the row where the cycle was detected).
|
1149
|
+
# :cycle_column :: The name of the cycle column in the output, defaults to :is_cycle.
|
1150
|
+
# :cycle_value :: The value of the cycle column in the output if the current row was
|
1151
|
+
# detected as a cycle, defaults to true.
|
1152
|
+
# :noncycle_value :: The value of the cycle column in the output if the current row
|
1153
|
+
# was not detected as a cycle, defaults to false. Only respected
|
1154
|
+
# if :cycle_value is given.
|
1155
|
+
# :path_column :: The name of the path column in the output, defaults to :path.
|
1156
|
+
# :search :: Include an order column in the result of the CTE that allows for breadth or
|
1157
|
+
# depth first searching. If given, must be a hash with the following keys:
|
1158
|
+
# :by :: (required) The column or array of columns to search by.
|
1159
|
+
# :order_column :: The name of the order column in the output, defaults to :ordercol.
|
1160
|
+
# :type :: Set to :breadth to use breadth-first searching (depth-first searching
|
1161
|
+
# is the default).
|
1162
|
+
#
|
1088
1163
|
# DB[:t].with_recursive(:t,
|
1089
1164
|
# DB[:i1].select(:id, :parent_id).where(parent_id: nil),
|
1090
1165
|
# DB[:i1].join(:t, id: :parent_id).select(Sequel[:i1][:id], Sequel[:i1][:parent_id]),
|
1091
|
-
# :
|
1166
|
+
# args: [:id, :parent_id])
|
1092
1167
|
#
|
1093
1168
|
# # WITH RECURSIVE t(id, parent_id) AS (
|
1094
1169
|
# # SELECT id, parent_id FROM i1 WHERE (parent_id IS NULL)
|
1095
1170
|
# # UNION ALL
|
1096
1171
|
# # SELECT i1.id, i1.parent_id FROM i1 INNER JOIN t ON (t.id = i1.parent_id)
|
1097
1172
|
# # ) SELECT * FROM t
|
1173
|
+
#
|
1174
|
+
# DB[:t].with_recursive(:t,
|
1175
|
+
# DB[:i1].where(parent_id: nil),
|
1176
|
+
# DB[:i1].join(:t, id: :parent_id).select_all(:i1),
|
1177
|
+
# search: {by: :id, type: :breadth},
|
1178
|
+
# cycle: {columns: :id, cycle_value: 1, noncycle_value: 2})
|
1179
|
+
#
|
1180
|
+
# # WITH RECURSIVE t AS (
|
1181
|
+
# # SELECT * FROM i1 WHERE (parent_id IS NULL)
|
1182
|
+
# # UNION ALL
|
1183
|
+
# # (SELECT i1.* FROM i1 INNER JOIN t ON (t.id = i1.parent_id))
|
1184
|
+
# # )
|
1185
|
+
# # SEARCH BREADTH FIRST BY id SET ordercol
|
1186
|
+
# # CYCLE id SET is_cycle TO 1 DEFAULT 2 USING path
|
1187
|
+
# # SELECT * FROM t
|
1098
1188
|
def with_recursive(name, nonrecursive, recursive, opts=OPTS)
|
1099
1189
|
raise(Error, 'This dataset does not support common table expressions') unless supports_cte?
|
1100
1190
|
if hoist_cte?(nonrecursive)
|
@@ -1151,7 +1241,7 @@ module Sequel
|
|
1151
1241
|
#
|
1152
1242
|
# You can also provide a method name and arguments to call to get the SQL:
|
1153
1243
|
#
|
1154
|
-
# DB[:items].with_sql(:insert_sql, :
|
1244
|
+
# DB[:items].with_sql(:insert_sql, b: 1) # INSERT INTO items (b) VALUES (1)
|
1155
1245
|
#
|
1156
1246
|
# Note that datasets that specify custom SQL using this method will generally
|
1157
1247
|
# ignore future dataset methods that modify the SQL used, as specifying custom SQL
|
@@ -1247,6 +1337,18 @@ module Sequel
|
|
1247
1337
|
end
|
1248
1338
|
end
|
1249
1339
|
|
1340
|
+
# Append to the current MERGE WHEN clauses.
|
1341
|
+
# Mutates the hash to add the conditions, if a virtual row block is passed.
|
1342
|
+
def _merge_when(hash, &block)
|
1343
|
+
hash[:conditions] = Sequel.virtual_row(&block) if block
|
1344
|
+
|
1345
|
+
if merge_when = @opts[:merge_when]
|
1346
|
+
clone(:merge_when => (merge_when.dup << hash.freeze).freeze)
|
1347
|
+
else
|
1348
|
+
clone(:merge_when => [hash.freeze].freeze)
|
1349
|
+
end
|
1350
|
+
end
|
1351
|
+
|
1250
1352
|
# Add the given filter condition. Arguments:
|
1251
1353
|
# clause :: Symbol or which SQL clause to effect, should be :where or :having
|
1252
1354
|
# cond :: The filter condition to add
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -22,31 +22,9 @@ module Sequel
|
|
22
22
|
def insert_sql(*values)
|
23
23
|
return static_sql(@opts[:sql]) if @opts[:sql]
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
columns = []
|
28
|
-
|
29
|
-
case values.size
|
30
|
-
when 0
|
31
|
-
return insert_sql(OPTS)
|
32
|
-
when 1
|
33
|
-
case vals = values[0]
|
34
|
-
when Hash
|
35
|
-
values = []
|
36
|
-
vals.each do |k,v|
|
37
|
-
columns << k
|
38
|
-
values << v
|
39
|
-
end
|
40
|
-
when Dataset, Array, LiteralString
|
41
|
-
values = vals
|
42
|
-
end
|
43
|
-
when 2
|
44
|
-
if (v0 = values[0]).is_a?(Array) && ((v1 = values[1]).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
|
45
|
-
columns, values = v0, v1
|
46
|
-
raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length
|
47
|
-
end
|
48
|
-
end
|
25
|
+
check_insert_allowed!
|
49
26
|
|
27
|
+
columns, values = _parse_insert_sql_args(values)
|
50
28
|
if values.is_a?(Array) && values.empty? && !insert_supports_empty_values?
|
51
29
|
columns, values = insert_empty_columns_values
|
52
30
|
elsif values.is_a?(Dataset) && hoist_cte?(values) && supports_cte?(:insert)
|
@@ -112,6 +90,31 @@ module Sequel
|
|
112
90
|
end
|
113
91
|
end
|
114
92
|
|
93
|
+
# The SQL to use for the MERGE statement.
|
94
|
+
def merge_sql
|
95
|
+
raise Error, "This database doesn't support MERGE" unless supports_merge?
|
96
|
+
if sql = opts[:sql]
|
97
|
+
return static_sql(sql)
|
98
|
+
end
|
99
|
+
if sql = cache_get(:_merge_sql)
|
100
|
+
return sql
|
101
|
+
end
|
102
|
+
source, join_condition = @opts[:merge_using]
|
103
|
+
raise Error, "No USING clause for MERGE" unless source
|
104
|
+
sql = @opts[:append_sql] || sql_string_origin
|
105
|
+
|
106
|
+
select_with_sql(sql)
|
107
|
+
sql << "MERGE INTO "
|
108
|
+
source_list_append(sql, @opts[:from])
|
109
|
+
sql << " USING "
|
110
|
+
identifier_append(sql, source)
|
111
|
+
sql << " ON "
|
112
|
+
literal_append(sql, join_condition)
|
113
|
+
_merge_when_sql(sql)
|
114
|
+
cache_set(:_merge_sql, sql) if cache_sql?
|
115
|
+
sql
|
116
|
+
end
|
117
|
+
|
115
118
|
# Returns an array of insert statements for inserting multiple records.
|
116
119
|
# This method is used by +multi_insert+ to format insert statements and
|
117
120
|
# expects a keys array and and an array of value arrays.
|
@@ -172,7 +175,7 @@ module Sequel
|
|
172
175
|
# than one table.
|
173
176
|
def update_sql(values = OPTS)
|
174
177
|
return static_sql(opts[:sql]) if opts[:sql]
|
175
|
-
|
178
|
+
check_update_allowed!
|
176
179
|
check_not_limited!(:update)
|
177
180
|
|
178
181
|
case values
|
@@ -215,7 +218,7 @@ module Sequel
|
|
215
218
|
lines << "def #{'_' if priv}#{type}_sql"
|
216
219
|
lines << 'if sql = opts[:sql]; return static_sql(sql) end' unless priv
|
217
220
|
lines << "if sql = cache_get(:_#{type}_sql); return sql end" if cacheable
|
218
|
-
lines << '
|
221
|
+
lines << 'check_delete_allowed!' << 'check_not_limited!(:delete)' if type == :delete
|
219
222
|
lines << 'sql = @opts[:append_sql] || sql_string_origin'
|
220
223
|
|
221
224
|
if clauses.all?{|c| c.is_a?(Array)}
|
@@ -559,11 +562,9 @@ module Sequel
|
|
559
562
|
# Append literalization of JOIN USING clause to SQL string.
|
560
563
|
def join_using_clause_sql_append(sql, jc)
|
561
564
|
join_clause_sql_append(sql, jc)
|
562
|
-
sql
|
563
|
-
column_list_append(sql, jc.using)
|
564
|
-
sql << ')'
|
565
|
+
join_using_clause_using_sql_append(sql, jc.using)
|
565
566
|
end
|
566
|
-
|
567
|
+
|
567
568
|
# Append literalization of negative boolean constant to SQL string.
|
568
569
|
def negative_boolean_constant_sql_append(sql, constant)
|
569
570
|
sql << 'NOT '
|
@@ -852,6 +853,83 @@ module Sequel
|
|
852
853
|
|
853
854
|
private
|
854
855
|
|
856
|
+
# Append the INSERT sql used in a MERGE
|
857
|
+
def _merge_insert_sql(sql, data)
|
858
|
+
sql << " THEN INSERT"
|
859
|
+
columns, values = _parse_insert_sql_args(data[:values])
|
860
|
+
_insert_columns_sql(sql, columns)
|
861
|
+
_insert_values_sql(sql, values)
|
862
|
+
end
|
863
|
+
|
864
|
+
def _merge_update_sql(sql, data)
|
865
|
+
sql << " THEN UPDATE SET "
|
866
|
+
update_sql_values_hash(sql, data[:values])
|
867
|
+
end
|
868
|
+
|
869
|
+
def _merge_delete_sql(sql, data)
|
870
|
+
sql << " THEN DELETE"
|
871
|
+
end
|
872
|
+
|
873
|
+
# Mapping of merge types to related SQL
|
874
|
+
MERGE_TYPE_SQL = {
|
875
|
+
:insert => ' WHEN NOT MATCHED',
|
876
|
+
:delete => ' WHEN MATCHED',
|
877
|
+
:update => ' WHEN MATCHED',
|
878
|
+
:matched => ' WHEN MATCHED',
|
879
|
+
:not_matched => ' WHEN NOT MATCHED',
|
880
|
+
}.freeze
|
881
|
+
private_constant :MERGE_TYPE_SQL
|
882
|
+
|
883
|
+
# Add the WHEN clauses to the MERGE SQL
|
884
|
+
def _merge_when_sql(sql)
|
885
|
+
raise Error, "no WHEN [NOT] MATCHED clauses provided for MERGE" unless merge_when = @opts[:merge_when]
|
886
|
+
merge_when.each do |data|
|
887
|
+
type = data[:type]
|
888
|
+
sql << MERGE_TYPE_SQL[type]
|
889
|
+
_merge_when_conditions_sql(sql, data)
|
890
|
+
send(:"_merge_#{type}_sql", sql, data)
|
891
|
+
end
|
892
|
+
end
|
893
|
+
|
894
|
+
# Append MERGE WHEN conditions, if there are conditions provided.
|
895
|
+
def _merge_when_conditions_sql(sql, data)
|
896
|
+
if data.has_key?(:conditions)
|
897
|
+
sql << " AND "
|
898
|
+
literal_append(sql, data[:conditions])
|
899
|
+
end
|
900
|
+
end
|
901
|
+
|
902
|
+
# Parse the values passed to insert_sql, returning columns and values
|
903
|
+
# to use for the INSERT. Returned columns is always an array, but can be empty
|
904
|
+
# for an INSERT without explicit column references. Returned values can be an
|
905
|
+
# array, dataset, or literal string.
|
906
|
+
def _parse_insert_sql_args(values)
|
907
|
+
columns = []
|
908
|
+
|
909
|
+
case values.size
|
910
|
+
when 0
|
911
|
+
values = []
|
912
|
+
when 1
|
913
|
+
case vals = values[0]
|
914
|
+
when Hash
|
915
|
+
values = []
|
916
|
+
vals.each do |k,v|
|
917
|
+
columns << k
|
918
|
+
values << v
|
919
|
+
end
|
920
|
+
when Dataset, Array, LiteralString
|
921
|
+
values = vals
|
922
|
+
end
|
923
|
+
when 2
|
924
|
+
if (v0 = values[0]).is_a?(Array) && ((v1 = values[1]).is_a?(Array) || v1.is_a?(Dataset) || v1.is_a?(LiteralString))
|
925
|
+
columns, values = v0, v1
|
926
|
+
raise(Error, "Different number of values and columns given to insert_sql") if values.is_a?(Array) and columns.length != values.length
|
927
|
+
end
|
928
|
+
end
|
929
|
+
|
930
|
+
[columns, values]
|
931
|
+
end
|
932
|
+
|
855
933
|
# Formats the truncate statement. Assumes the table given has already been
|
856
934
|
# literalized.
|
857
935
|
def _truncate_sql(table)
|
@@ -896,9 +974,10 @@ module Sequel
|
|
896
974
|
# Clone of this dataset usable in aggregate operations. Does
|
897
975
|
# a from_self if dataset contains any parameters that would
|
898
976
|
# affect normal aggregation, or just removes an existing
|
899
|
-
# order if not.
|
977
|
+
# order if not. Also removes the row_proc, which isn't needed
|
978
|
+
# for aggregate calculations.
|
900
979
|
def aggregate_dataset
|
901
|
-
options_overlap(COUNT_FROM_SELF_OPTS) ? from_self : unordered
|
980
|
+
(options_overlap(COUNT_FROM_SELF_OPTS) ? from_self : unordered).naked
|
902
981
|
end
|
903
982
|
|
904
983
|
# Append aliasing expression to SQL string.
|
@@ -918,10 +997,35 @@ module Sequel
|
|
918
997
|
!@opts[:no_cache_sql] && !cache_get(:_no_cache_sql)
|
919
998
|
end
|
920
999
|
|
921
|
-
# Raise an InvalidOperation exception if
|
1000
|
+
# Raise an InvalidOperation exception if modification is not allowed for this dataset.
|
1001
|
+
# Check whether it is allowed to insert into this dataset.
|
1002
|
+
# Only for backwards compatibility with older external adapters.
|
922
1003
|
def check_modification_allowed!
|
1004
|
+
# SEQUEL6: Remove
|
1005
|
+
Sequel::Deprecation.deprecate("Dataset#check_modification_allowed!", "Use check_{insert,delete,update,truncation}_allowed! instead")
|
1006
|
+
_check_modification_allowed!(supports_modifying_joins?)
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
# Check whether it is allowed to insert into this dataset.
|
1010
|
+
def check_insert_allowed!
|
1011
|
+
_check_modification_allowed!(false)
|
1012
|
+
end
|
1013
|
+
alias check_truncation_allowed! check_insert_allowed!
|
1014
|
+
|
1015
|
+
# Check whether it is allowed to delete from this dataset.
|
1016
|
+
def check_delete_allowed!
|
1017
|
+
_check_modification_allowed!(supports_deleting_joins?)
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
# Check whether it is allowed to update this dataset.
|
1021
|
+
def check_update_allowed!
|
1022
|
+
_check_modification_allowed!(supports_updating_joins?)
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
# Internals of the check_*_allowed! methods
|
1026
|
+
def _check_modification_allowed!(modifying_joins_supported)
|
923
1027
|
raise(InvalidOperation, "Grouped datasets cannot be modified") if opts[:group]
|
924
|
-
raise(InvalidOperation, "Joined datasets cannot be modified") if !
|
1028
|
+
raise(InvalidOperation, "Joined datasets cannot be modified") if !modifying_joins_supported && joined_dataset?
|
925
1029
|
end
|
926
1030
|
|
927
1031
|
# Raise error if the dataset uses limits or offsets.
|
@@ -930,11 +1034,6 @@ module Sequel
|
|
930
1034
|
raise InvalidOperation, "Dataset##{type} not supported on datasets with limits or offsets" if opts[:limit] || opts[:offset]
|
931
1035
|
end
|
932
1036
|
|
933
|
-
# Alias of check_modification_allowed!
|
934
|
-
def check_truncation_allowed!
|
935
|
-
check_modification_allowed!
|
936
|
-
end
|
937
|
-
|
938
1037
|
# Append column list to SQL string.
|
939
1038
|
# If the column list is empty, a wildcard (*) is appended.
|
940
1039
|
def column_list_append(sql, columns)
|
@@ -971,7 +1070,9 @@ module Sequel
|
|
971
1070
|
# operators unsupported by some databases. Used by adapters for databases
|
972
1071
|
# that don't support the operators natively.
|
973
1072
|
def complex_expression_emulate_append(sql, op, args)
|
1073
|
+
# :nocov:
|
974
1074
|
case op
|
1075
|
+
# :nocov:
|
975
1076
|
when :%
|
976
1077
|
complex_expression_arg_pairs_append(sql, args){|a, b| Sequel.function(:MOD, a, b)}
|
977
1078
|
when :>>
|
@@ -1144,7 +1245,10 @@ module Sequel
|
|
1144
1245
|
end
|
1145
1246
|
|
1146
1247
|
def insert_columns_sql(sql)
|
1147
|
-
|
1248
|
+
_insert_columns_sql(sql, opts[:columns])
|
1249
|
+
end
|
1250
|
+
|
1251
|
+
def _insert_columns_sql(sql, columns)
|
1148
1252
|
if columns && !columns.empty?
|
1149
1253
|
sql << ' ('
|
1150
1254
|
identifier_list_append(sql, columns)
|
@@ -1163,7 +1267,11 @@ module Sequel
|
|
1163
1267
|
end
|
1164
1268
|
|
1165
1269
|
def insert_values_sql(sql)
|
1166
|
-
|
1270
|
+
_insert_values_sql(sql, opts[:values])
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
def _insert_values_sql(sql, values)
|
1274
|
+
case values
|
1167
1275
|
when Array
|
1168
1276
|
if values.empty?
|
1169
1277
|
sql << " DEFAULT VALUES"
|
@@ -1196,6 +1304,13 @@ module Sequel
|
|
1196
1304
|
"#{join_type.to_s.gsub('_', ' ').upcase} JOIN"
|
1197
1305
|
end
|
1198
1306
|
|
1307
|
+
# Append USING clause for JOIN USING
|
1308
|
+
def join_using_clause_using_sql_append(sql, using_columns)
|
1309
|
+
sql << ' USING ('
|
1310
|
+
column_list_append(sql, using_columns)
|
1311
|
+
sql << ')'
|
1312
|
+
end
|
1313
|
+
|
1199
1314
|
# Append a literalization of the array to SQL string.
|
1200
1315
|
# Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
|
1201
1316
|
def literal_array_append(sql, v)
|
@@ -1516,15 +1631,14 @@ module Sequel
|
|
1516
1631
|
|
1517
1632
|
def select_with_sql(sql)
|
1518
1633
|
return unless supports_cte?
|
1519
|
-
|
1520
|
-
return if !
|
1634
|
+
ctes = opts[:with]
|
1635
|
+
return if !ctes || ctes.empty?
|
1521
1636
|
sql << select_with_sql_base
|
1522
1637
|
c = false
|
1523
1638
|
comma = ', '
|
1524
|
-
|
1639
|
+
ctes.each do |cte|
|
1525
1640
|
sql << comma if c
|
1526
|
-
|
1527
|
-
literal_dataset_append(sql, w[:dataset])
|
1641
|
+
select_with_sql_cte(sql, cte)
|
1528
1642
|
c ||= true
|
1529
1643
|
end
|
1530
1644
|
sql << ' '
|
@@ -1537,6 +1651,11 @@ module Sequel
|
|
1537
1651
|
"WITH "
|
1538
1652
|
end
|
1539
1653
|
|
1654
|
+
def select_with_sql_cte(sql, cte)
|
1655
|
+
select_with_sql_prefix(sql, cte)
|
1656
|
+
literal_dataset_append(sql, cte[:dataset])
|
1657
|
+
end
|
1658
|
+
|
1540
1659
|
def select_with_sql_prefix(sql, w)
|
1541
1660
|
quote_identifier_append(sql, w[:name])
|
1542
1661
|
if args = w[:args]
|
@@ -1545,6 +1664,13 @@ module Sequel
|
|
1545
1664
|
sql << ')'
|
1546
1665
|
end
|
1547
1666
|
sql << ' AS '
|
1667
|
+
|
1668
|
+
case w[:materialized]
|
1669
|
+
when true
|
1670
|
+
sql << "MATERIALIZED "
|
1671
|
+
when false
|
1672
|
+
sql << "NOT MATERIALIZED "
|
1673
|
+
end
|
1548
1674
|
end
|
1549
1675
|
|
1550
1676
|
# Whether the symbol cache should be skipped when literalizing the dataset
|
@@ -1599,7 +1725,7 @@ module Sequel
|
|
1599
1725
|
# Append literalization of the subselect to SQL string.
|
1600
1726
|
def subselect_sql_append(sql, ds)
|
1601
1727
|
sds = subselect_sql_dataset(sql, ds)
|
1602
|
-
sds
|
1728
|
+
subselect_sql_append_sql(sql, sds)
|
1603
1729
|
unless sds.send(:cache_sql?)
|
1604
1730
|
# If subquery dataset does not allow caching SQL,
|
1605
1731
|
# then this dataset should not allow caching SQL.
|
@@ -1611,6 +1737,10 @@ module Sequel
|
|
1611
1737
|
ds.clone(:append_sql=>sql)
|
1612
1738
|
end
|
1613
1739
|
|
1740
|
+
def subselect_sql_append_sql(sql, ds)
|
1741
|
+
ds.sql
|
1742
|
+
end
|
1743
|
+
|
1614
1744
|
# The number of decimal digits of precision to use in timestamps.
|
1615
1745
|
def timestamp_precision
|
1616
1746
|
supports_timestamp_usecs? ? 6 : 0
|
@@ -23,18 +23,6 @@ module Sequel
|
|
23
23
|
super
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
# Handle Sequel::Model instances in bound variable arrays.
|
30
|
-
def bound_variable_array(arg)
|
31
|
-
case arg
|
32
|
-
when Sequel::Model
|
33
|
-
"\"(#{arg.values.values_at(*arg.columns).map{|v| bound_variable_array(v)}.join(',').gsub(/("|\\)/, '\\\\\1')})\""
|
34
|
-
else
|
35
|
-
super
|
36
|
-
end
|
37
|
-
end
|
38
26
|
end
|
39
27
|
end
|
40
28
|
end
|