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