sequel 3.23.0 → 3.24.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.
- data/CHANGELOG +64 -0
- data/doc/association_basics.rdoc +43 -5
- data/doc/model_hooks.rdoc +64 -27
- data/doc/prepared_statements.rdoc +8 -4
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.23.0.txt +1 -1
- data/doc/release_notes/3.24.0.txt +420 -0
- data/lib/sequel/adapters/db2.rb +8 -1
- data/lib/sequel/adapters/firebird.rb +25 -9
- data/lib/sequel/adapters/informix.rb +4 -19
- data/lib/sequel/adapters/jdbc.rb +34 -17
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/informix.rb +31 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
- data/lib/sequel/adapters/postgres.rb +30 -1
- data/lib/sequel/adapters/shared/access.rb +10 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +82 -8
- data/lib/sequel/adapters/shared/mysql.rb +25 -7
- data/lib/sequel/adapters/shared/postgres.rb +39 -6
- data/lib/sequel/adapters/shared/sqlite.rb +57 -5
- data/lib/sequel/adapters/sqlite.rb +8 -3
- data/lib/sequel/adapters/swift/mysql.rb +9 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +33 -3
- data/lib/sequel/database/schema_methods.rb +6 -2
- data/lib/sequel/dataset/features.rb +6 -0
- data/lib/sequel/dataset/prepared_statements.rb +17 -2
- data/lib/sequel/dataset/query.rb +17 -0
- data/lib/sequel/dataset/sql.rb +2 -53
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model.rb +5 -0
- data/lib/sequel/model/associations.rb +80 -14
- data/lib/sequel/model/base.rb +182 -55
- data/lib/sequel/model/exceptions.rb +3 -1
- data/lib/sequel/plugins/association_pks.rb +6 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/many_through_many.rb +8 -3
- data/lib/sequel/plugins/prepared_statements.rb +140 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/sql.rb +8 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +43 -18
- data/spec/core/connection_pool_spec.rb +56 -77
- data/spec/core/database_spec.rb +25 -0
- data/spec/core/dataset_spec.rb +127 -16
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/core/schema_spec.rb +6 -1
- data/spec/extensions/association_pks_spec.rb +7 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/many_through_many_spec.rb +60 -4
- data/spec/extensions/nested_attributes_spec.rb +1 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/integration/associations_test.rb +155 -1
- data/spec/integration/dataset_test.rb +8 -1
- data/spec/integration/plugin_test.rb +119 -0
- data/spec/integration/prepared_statement_test.rb +72 -1
- data/spec/integration/schema_test.rb +66 -8
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/model/associations_spec.rb +349 -8
- data/spec/model/base_spec.rb +59 -0
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/record_spec.rb +24 -0
- metadata +21 -4
@@ -173,7 +173,7 @@ module Sequel
|
|
173
173
|
end
|
174
174
|
end
|
175
175
|
unless cps
|
176
|
-
cps = conn.prepare(sql)
|
176
|
+
cps = log_yield("Preparing #{name}: #{sql}"){conn.prepare(sql)}
|
177
177
|
conn.prepared_statements[name] = [cps, sql]
|
178
178
|
end
|
179
179
|
if block
|
@@ -217,7 +217,7 @@ module Sequel
|
|
217
217
|
# but with the keys converted to strings.
|
218
218
|
def map_to_prepared_args(hash)
|
219
219
|
args = {}
|
220
|
-
hash.each{|k,v| args[k.to_s] = v}
|
220
|
+
hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
|
221
221
|
args
|
222
222
|
end
|
223
223
|
|
@@ -226,7 +226,12 @@ module Sequel
|
|
226
226
|
# SQLite uses a : before the name of the argument for named
|
227
227
|
# arguments.
|
228
228
|
def prepared_arg(k)
|
229
|
-
LiteralString.new("#{prepared_arg_placeholder}#{k}")
|
229
|
+
LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
|
230
|
+
end
|
231
|
+
|
232
|
+
# Always assume a prepared argument.
|
233
|
+
def prepared_arg?(k)
|
234
|
+
true
|
230
235
|
end
|
231
236
|
end
|
232
237
|
|
@@ -25,6 +25,15 @@ module Sequel
|
|
25
25
|
def schema_column_type(db_type)
|
26
26
|
db_type == 'tinyint(1)' ? :boolean : super
|
27
27
|
end
|
28
|
+
|
29
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
30
|
+
# Turn that off unless explicitly enabled.
|
31
|
+
def setup_connection(conn)
|
32
|
+
super
|
33
|
+
sql = "SET SQL_AUTO_IS_NULL=0"
|
34
|
+
log_yield(sql){conn.execute(sql)} unless opts[:auto_is_null]
|
35
|
+
conn
|
36
|
+
end
|
28
37
|
end
|
29
38
|
|
30
39
|
# Dataset class for MySQL datasets accessed via Swift.
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Sequel
|
2
|
+
# The +ASTTransformer+ class is designed to handle the abstract syntax trees
|
3
|
+
# that Sequel uses internally and produce modified copies of them. By itself
|
4
|
+
# it only produces a straight copy. It's designed to be subclassed and have
|
5
|
+
# subclasses returned modified copies of the specific nodes that need to
|
6
|
+
# be modified.
|
7
|
+
class ASTTransformer
|
8
|
+
# Return +obj+ or a potentially transformed version of it.
|
9
|
+
def transform(obj)
|
10
|
+
v(obj)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# Recursive version that handles all of Sequel's internal object types
|
16
|
+
# and produces copies of them.
|
17
|
+
def v(o)
|
18
|
+
case o
|
19
|
+
when Symbol, Numeric, String, Class, TrueClass, FalseClass, NilClass
|
20
|
+
o
|
21
|
+
when Array
|
22
|
+
o.map{|x| v(x)}
|
23
|
+
when Hash
|
24
|
+
h = {}
|
25
|
+
o.each{|k, val| h[v(k)] = v(val)}
|
26
|
+
h
|
27
|
+
when SQL::ComplexExpression
|
28
|
+
SQL::ComplexExpression.new(o.op, *v(o.args))
|
29
|
+
when SQL::Identifier
|
30
|
+
SQL::Identifier.new(v(o.value))
|
31
|
+
when SQL::QualifiedIdentifier
|
32
|
+
SQL::QualifiedIdentifier.new(v(o.table), v(o.column))
|
33
|
+
when SQL::OrderedExpression
|
34
|
+
SQL::OrderedExpression.new(v(o.expression), o.descending, :nulls=>o.nulls)
|
35
|
+
when SQL::AliasedExpression
|
36
|
+
SQL::AliasedExpression.new(v(o.expression), o.aliaz)
|
37
|
+
when SQL::CaseExpression
|
38
|
+
args = [v(o.conditions), v(o.default)]
|
39
|
+
args << v(o.expression) if o.expression?
|
40
|
+
SQL::CaseExpression.new(*args)
|
41
|
+
when SQL::Cast
|
42
|
+
SQL::Cast.new(v(o.expr), o.type)
|
43
|
+
when SQL::Function
|
44
|
+
SQL::Function.new(o.f, *v(o.args))
|
45
|
+
when SQL::Subscript
|
46
|
+
SQL::Subscript.new(v(o.f), v(o.sub))
|
47
|
+
when SQL::WindowFunction
|
48
|
+
SQL::WindowFunction.new(v(o.function), v(o.window))
|
49
|
+
when SQL::Window
|
50
|
+
opts = o.opts.dup
|
51
|
+
opts[:partition] = v(opts[:partition]) if opts[:partition]
|
52
|
+
opts[:order] = v(opts[:order]) if opts[:order]
|
53
|
+
SQL::Window.new(opts)
|
54
|
+
when SQL::PlaceholderLiteralString
|
55
|
+
args = if o.args.is_a?(Hash)
|
56
|
+
h = {}
|
57
|
+
o.args.each{|k,val| h[k] = v(val)}
|
58
|
+
h
|
59
|
+
else
|
60
|
+
v(o.args)
|
61
|
+
end
|
62
|
+
SQL::PlaceholderLiteralString.new(o.str, args, o.parens)
|
63
|
+
when SQL::JoinOnClause
|
64
|
+
SQL::JoinOnClause.new(v(o.on), o.join_type, v(o.table), v(o.table_alias))
|
65
|
+
when SQL::JoinUsingClause
|
66
|
+
SQL::JoinUsingClause.new(v(o.using), o.join_type, v(o.table), v(o.table_alias))
|
67
|
+
when SQL::JoinClause
|
68
|
+
SQL::JoinClause.new(o.join_type, v(o.table), v(o.table_alias))
|
69
|
+
else
|
70
|
+
o
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Handles qualifying existing datasets, so that unqualified columns
|
76
|
+
# in the dataset are qualified with a given table name.
|
77
|
+
class Qualifier < ASTTransformer
|
78
|
+
# Store the dataset to use as the basis for qualification,
|
79
|
+
# and the table used to qualify unqualified columns.
|
80
|
+
def initialize(ds, table)
|
81
|
+
@ds = ds
|
82
|
+
@table = table
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Turn <tt>SQL::Identifier</tt>s and symbols that aren't implicitly
|
88
|
+
# qualified into <tt>SQL::QualifiedIdentifier</tt>s. For symbols that
|
89
|
+
# are not implicitly qualified by are implicitly aliased, return an
|
90
|
+
# <tt>SQL::AliasedExpression</tt>s with a qualified version of the symbol.
|
91
|
+
def v(o)
|
92
|
+
case o
|
93
|
+
when Symbol
|
94
|
+
t, column, aliaz = @ds.send(:split_symbol, o)
|
95
|
+
if t
|
96
|
+
o
|
97
|
+
elsif aliaz
|
98
|
+
SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(@table, SQL::Identifier.new(column)), aliaz)
|
99
|
+
else
|
100
|
+
SQL::QualifiedIdentifier.new(@table, o)
|
101
|
+
end
|
102
|
+
when SQL::Identifier
|
103
|
+
SQL::QualifiedIdentifier.new(@table, o)
|
104
|
+
when SQL::QualifiedIdentifier, SQL::JoinClause
|
105
|
+
# Return these directly, so we don't accidentally qualify symbols in them.
|
106
|
+
o
|
107
|
+
else
|
108
|
+
super
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# +Unbinder+ is used to take a dataset filter and return a modified version
|
114
|
+
# that unbinds already bound values and returns a dataset with bound value
|
115
|
+
# placeholders and a hash of bind values. You can then prepare the dataset
|
116
|
+
# and use the bound variables to execute it with the same values.
|
117
|
+
#
|
118
|
+
# This class only does a limited form of unbinding where the variable names
|
119
|
+
# and values can be associated unambiguously. The only cases it handles
|
120
|
+
# are <tt>SQL::ComplexExpression<tt> with an operator in +UNBIND_OPS+, a
|
121
|
+
# first argument that's an instance of a member of +UNBIND_KEY_CLASSES+, and
|
122
|
+
# a second argument that's an instance of a member of +UNBIND_VALUE_CLASSES+.
|
123
|
+
#
|
124
|
+
# So it can handle cases like:
|
125
|
+
#
|
126
|
+
# DB.filter(:a=>1).exclude(:b=>2).where{c > 3}
|
127
|
+
#
|
128
|
+
# But it cannot handle cases like:
|
129
|
+
#
|
130
|
+
# DB.filter(:a + 1 < 0)
|
131
|
+
class Unbinder < ASTTransformer
|
132
|
+
# The <tt>SQL::ComplexExpression<tt> operates that will be considered
|
133
|
+
# for transformation.
|
134
|
+
UNBIND_OPS = [:'=', :'!=', :<, :>, :<=, :>=]
|
135
|
+
|
136
|
+
# The key classes (first argument of the ComplexExpression) that will
|
137
|
+
# considered for transformation.
|
138
|
+
UNBIND_KEY_CLASSES = [Symbol, SQL::Identifier, SQL::QualifiedIdentifier]
|
139
|
+
|
140
|
+
# The value classes (second argument of the ComplexExpression) that
|
141
|
+
# will be considered for transformation.
|
142
|
+
UNBIND_VALUE_CLASSES = [Numeric, String, Date, Time]
|
143
|
+
|
144
|
+
# The hash of bind variables that were extracted from the dataset filter.
|
145
|
+
attr_reader :binds
|
146
|
+
|
147
|
+
# Intialize an empty +binds+ hash.
|
148
|
+
def initialize
|
149
|
+
@binds = {}
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
# Create a suitable bound variable key for the object, which should be
|
155
|
+
# an instance of one of the +UNBIND_KEY_CLASSES+.
|
156
|
+
def bind_key(obj)
|
157
|
+
case obj
|
158
|
+
when Symbol, String
|
159
|
+
obj
|
160
|
+
when SQL::Identifier
|
161
|
+
bind_key(obj.value)
|
162
|
+
when SQL::QualifiedIdentifier
|
163
|
+
:"#{bind_key(obj.table)}.#{bind_key(obj.column)}"
|
164
|
+
else
|
165
|
+
raise Error, "unhandled object in Sequel::Unbinder#bind_key: #{obj}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Handle <tt>SQL::ComplexExpression</tt> instances with suitable ops
|
170
|
+
# and arguments, substituting the value with a bound variable placeholder
|
171
|
+
# and assigning it an entry in the +binds+ hash with a matching key.
|
172
|
+
def v(o)
|
173
|
+
if o.is_a?(SQL::ComplexExpression) && UNBIND_OPS.include?(o.op)
|
174
|
+
l, r = o.args
|
175
|
+
if UNBIND_KEY_CLASSES.any?{|c| l.is_a?(c)} && UNBIND_VALUE_CLASSES.any?{|c| r.is_a?(c)} && !r.is_a?(LiteralString)
|
176
|
+
key = bind_key(l)
|
177
|
+
if (old = binds[key]) && old != r
|
178
|
+
raise UnbindDuplicate, "two different values for #{key.inspect}: #{[r, old].inspect}"
|
179
|
+
end
|
180
|
+
binds[key] = r
|
181
|
+
SQL::ComplexExpression.new(o.op, l, :"$#{key}")
|
182
|
+
else
|
183
|
+
super
|
184
|
+
end
|
185
|
+
else
|
186
|
+
super
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
data/lib/sequel/core.rb
CHANGED
@@ -292,7 +292,7 @@ module Sequel
|
|
292
292
|
|
293
293
|
private_class_method :adapter_method, :def_adapter_method
|
294
294
|
|
295
|
-
require(%w"metaprogramming sql connection_pool exceptions dataset database timezones version")
|
295
|
+
require(%w"metaprogramming sql connection_pool exceptions dataset database timezones ast_transformer version")
|
296
296
|
require('core_sql') if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
|
297
297
|
|
298
298
|
# Add the database adapter class methods to Sequel via metaprogramming
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -90,6 +90,12 @@ module Sequel
|
|
90
90
|
{:primary_key => true, :type => Integer, :auto_increment => true}
|
91
91
|
end
|
92
92
|
|
93
|
+
# Whether the database supports CREATE TABLE IF NOT EXISTS syntax,
|
94
|
+
# false by default.
|
95
|
+
def supports_create_table_if_not_exists?
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
93
99
|
# Whether the database and adapter support prepared transactions
|
94
100
|
# (two-phase commit), false by default.
|
95
101
|
def supports_prepared_transactions?
|
@@ -210,6 +210,13 @@ module Sequel
|
|
210
210
|
end
|
211
211
|
end
|
212
212
|
|
213
|
+
# Return all views in the database as an array of symbols.
|
214
|
+
#
|
215
|
+
# DB.views # => [:gold_albums, :artists_with_many_albums]
|
216
|
+
def views(opts={})
|
217
|
+
raise NotImplemented, "#views should be overridden by adapters"
|
218
|
+
end
|
219
|
+
|
213
220
|
private
|
214
221
|
|
215
222
|
# Internal generic transaction method. Any exception raised by the given
|
@@ -226,7 +233,7 @@ module Sequel
|
|
226
233
|
transaction_error(e)
|
227
234
|
ensure
|
228
235
|
begin
|
229
|
-
|
236
|
+
commit_or_rollback_transaction(e, t, opts)
|
230
237
|
rescue Exception => e
|
231
238
|
raise_error(e, :classes=>database_error_classes)
|
232
239
|
ensure
|
@@ -234,7 +241,7 @@ module Sequel
|
|
234
241
|
end
|
235
242
|
end
|
236
243
|
end
|
237
|
-
|
244
|
+
|
238
245
|
# Add the current thread to the list of active transactions
|
239
246
|
def add_transaction
|
240
247
|
th = Thread.current
|
@@ -335,6 +342,29 @@ module Sequel
|
|
335
342
|
end
|
336
343
|
end
|
337
344
|
|
345
|
+
if (! defined?(RUBY_ENGINE) or RUBY_ENGINE == 'ruby' or RUBY_ENGINE == 'rbx') and RUBY_VERSION < '1.9'
|
346
|
+
# Whether to commit the current transaction. On ruby 1.8 and rubinius,
|
347
|
+
# Thread.current.status is checked because Thread#kill skips rescue
|
348
|
+
# blocks (so exception would be nil), but the transaction should
|
349
|
+
# still be rolled back.
|
350
|
+
def commit_or_rollback_transaction(exception, thread, opts)
|
351
|
+
unless exception
|
352
|
+
if Thread.current.status == 'aborting'
|
353
|
+
rollback_transaction(thread, opts)
|
354
|
+
else
|
355
|
+
commit_transaction(thread, opts)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
else
|
360
|
+
# Whether to commit the current transaction. On ruby 1.9 and JRuby,
|
361
|
+
# transactions will be committed if Thread#kill is used on an thread
|
362
|
+
# that has a transaction open, and there isn't a work around.
|
363
|
+
def commit_or_rollback_transaction(exception, thread, opts)
|
364
|
+
commit_transaction(thread, opts) unless exception
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
338
368
|
# SQL to commit a savepoint
|
339
369
|
def commit_savepoint_sql(depth)
|
340
370
|
SQL_RELEASE_SAVEPOINT % depth
|
@@ -434,7 +464,7 @@ module Sequel
|
|
434
464
|
:datetime
|
435
465
|
when /\Atime( with(out)? time zone)?\z/io
|
436
466
|
:time
|
437
|
-
when /\A(
|
467
|
+
when /\A(bool(ean)?)\z/io
|
438
468
|
:boolean
|
439
469
|
when /\A(real|float|double( precision)?)\z/io
|
440
470
|
:float
|
@@ -111,7 +111,11 @@ module Sequel
|
|
111
111
|
|
112
112
|
# Creates the table unless the table already exists
|
113
113
|
def create_table?(name, options={}, &block)
|
114
|
-
|
114
|
+
if supports_create_table_if_not_exists?
|
115
|
+
create_table(name, options.merge(:if_not_exists=>true), &block)
|
116
|
+
elsif !table_exists?(name)
|
117
|
+
create_table(name, options, &block)
|
118
|
+
end
|
115
119
|
end
|
116
120
|
|
117
121
|
# Creates a view, replacing it if it already exists:
|
@@ -371,7 +375,7 @@ module Sequel
|
|
371
375
|
|
372
376
|
# DDL statement for creating a table with the given name, columns, and options
|
373
377
|
def create_table_sql(name, generator, options)
|
374
|
-
"CREATE #{temporary_table_sql if options[:temp]}TABLE #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)} (#{column_list_sql(generator)})"
|
378
|
+
"CREATE #{temporary_table_sql if options[:temp]}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)} (#{column_list_sql(generator)})"
|
375
379
|
end
|
376
380
|
|
377
381
|
# Default index name for the table and columns, may be too long
|
@@ -37,6 +37,12 @@ module Sequel
|
|
37
37
|
false
|
38
38
|
end
|
39
39
|
|
40
|
+
# Whether this dataset supports the +insert_select+ method for returning all columns values
|
41
|
+
# directly from an insert query.
|
42
|
+
def supports_insert_select?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
40
46
|
# Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
|
41
47
|
def supports_intersect_except?
|
42
48
|
true
|
@@ -81,6 +81,8 @@ module Sequel
|
|
81
81
|
select_sql
|
82
82
|
when :first
|
83
83
|
clone(:limit=>1).select_sql
|
84
|
+
when :insert_select
|
85
|
+
clone(:returning=>nil).insert_sql(*@prepared_modify_values)
|
84
86
|
when :insert
|
85
87
|
insert_sql(*@prepared_modify_values)
|
86
88
|
when :update
|
@@ -95,8 +97,8 @@ module Sequel
|
|
95
97
|
# and they are substituted using prepared_arg.
|
96
98
|
def literal_symbol(v)
|
97
99
|
if @opts[:bind_vars] and match = PLACEHOLDER_RE.match(v.to_s)
|
98
|
-
|
99
|
-
|
100
|
+
s = match[1].to_sym
|
101
|
+
prepared_arg?(s) ? literal(prepared_arg(s)) : v
|
100
102
|
else
|
101
103
|
super
|
102
104
|
end
|
@@ -118,6 +120,9 @@ module Sequel
|
|
118
120
|
case @prepared_type
|
119
121
|
when :select, :all
|
120
122
|
all(&block)
|
123
|
+
when :insert_select
|
124
|
+
meta_def(:select_sql){prepared_sql}
|
125
|
+
first
|
121
126
|
when :first
|
122
127
|
first
|
123
128
|
when :insert
|
@@ -136,6 +141,11 @@ module Sequel
|
|
136
141
|
@opts[:bind_vars][k]
|
137
142
|
end
|
138
143
|
|
144
|
+
# Whether there is a bound value for the given key.
|
145
|
+
def prepared_arg?(k)
|
146
|
+
@opts[:bind_vars].has_key?(k)
|
147
|
+
end
|
148
|
+
|
139
149
|
# Use a clone of the dataset extended with prepared statement
|
140
150
|
# support and using the same argument hash so that you can use
|
141
151
|
# bind variables/prepared arguments in subselects.
|
@@ -171,6 +181,11 @@ module Sequel
|
|
171
181
|
prepared_args << k
|
172
182
|
prepared_arg_placeholder
|
173
183
|
end
|
184
|
+
|
185
|
+
# Always assume there is a prepared arg in the argument mapper.
|
186
|
+
def prepared_arg?(k)
|
187
|
+
true
|
188
|
+
end
|
174
189
|
end
|
175
190
|
|
176
191
|
# Set the bind variables to use for the call. If bind variables have
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -431,6 +431,7 @@ module Sequel
|
|
431
431
|
v = qualified_column_name(v, last_alias) if v.is_a?(Symbol)
|
432
432
|
[k,v]
|
433
433
|
end
|
434
|
+
expr = SQL::BooleanExpression.from_value_pairs(expr)
|
434
435
|
end
|
435
436
|
if block
|
436
437
|
expr2 = yield(table_name, last_alias, @opts[:join] || [])
|
@@ -702,6 +703,22 @@ module Sequel
|
|
702
703
|
clone(:overrides=>hash.merge(@opts[:overrides]||{}))
|
703
704
|
end
|
704
705
|
|
706
|
+
# Unbind bound variables from this dataset's filter and return an array of two
|
707
|
+
# objects. The first object is a modified dataset where the filter has been
|
708
|
+
# replaced with one that uses bound variable placeholders. The second object
|
709
|
+
# is the hash of unbound variables. You can then prepare and execute (or just
|
710
|
+
# call) the dataset with the bound variables to get results.
|
711
|
+
#
|
712
|
+
# ds, bv = DB[:items].filter(:a=>1).unbind
|
713
|
+
# ds # SELECT * FROM items WHERE (a = $a)
|
714
|
+
# bv # {:a => 1}
|
715
|
+
# ds.call(:select, bv)
|
716
|
+
def unbind
|
717
|
+
u = Unbinder.new
|
718
|
+
ds = clone(:where=>u.transform(opts[:where]), :join=>u.transform(opts[:join]))
|
719
|
+
[ds, u.binds]
|
720
|
+
end
|
721
|
+
|
705
722
|
# Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
|
706
723
|
#
|
707
724
|
# DB[:items].group(:a).having(:a=>1).where(:b).unfiltered
|