sequel 3.21.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 +112 -0
- data/README.rdoc +15 -1
- data/doc/association_basics.rdoc +159 -40
- 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.22.0.txt +39 -0
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/release_notes/3.24.0.txt +420 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado.rb +2 -1
- 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/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/jdbc.rb +39 -20
- data/lib/sequel/adapters/odbc.rb +2 -0
- data/lib/sequel/adapters/oracle.rb +12 -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 +106 -11
- 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/adapters/tinytds.rb +4 -3
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/connecting.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 +13 -4
- 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/columns_introspection.rb +61 -0
- data/lib/sequel/extensions/migration.rb +4 -3
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model/associations.rb +234 -32
- data/lib/sequel/model/base.rb +187 -60
- data/lib/sequel/model/exceptions.rb +3 -1
- data/lib/sequel/model.rb +5 -0
- data/lib/sequel/plugins/association_pks.rb +6 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/identity_map.rb +2 -2
- data/lib/sequel/plugins/many_through_many.rb +33 -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/plugins/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +8 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +36 -0
- data/spec/adapters/mysql_spec.rb +6 -0
- data/spec/adapters/postgres_spec.rb +43 -18
- data/spec/adapters/spec_helper.rb +5 -0
- data/spec/core/connection_pool_spec.rb +56 -77
- data/spec/core/database_spec.rb +33 -0
- data/spec/core/dataset_spec.rb +127 -16
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/core/schema_spec.rb +13 -1
- data/spec/core/spec_helper.rb +5 -0
- data/spec/extensions/association_pks_spec.rb +7 -0
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/many_through_many_spec.rb +77 -0
- data/spec/extensions/migration_spec.rb +17 -17
- 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/serialization_modification_detection_spec.rb +36 -0
- data/spec/extensions/single_table_inheritance_spec.rb +11 -0
- data/spec/extensions/spec_helper.rb +3 -1
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/extensions/xml_serializer_spec.rb +12 -0
- data/spec/integration/associations_test.rb +212 -0
- data/spec/integration/dataset_test.rb +8 -1
- data/spec/integration/plugin_test.rb +134 -0
- data/spec/integration/prepared_statement_test.rb +72 -1
- data/spec/integration/schema_test.rb +66 -8
- data/spec/integration/spec_helper.rb +5 -0
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/integration/type_test.rb +7 -0
- data/spec/model/associations_spec.rb +463 -5
- data/spec/model/base_spec.rb +59 -0
- data/spec/model/eager_loading_spec.rb +269 -1
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/record_spec.rb +30 -0
- data/spec/model/spec_helper.rb +5 -0
- metadata +29 -4
|
@@ -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
|
|
@@ -54,7 +54,7 @@ module Sequel
|
|
|
54
54
|
c = adapter_class(scheme)
|
|
55
55
|
uri_options = c.send(:uri_to_options, uri)
|
|
56
56
|
uri.query.split('&').collect{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v if k && !k.empty?} unless uri.query.to_s.strip.empty?
|
|
57
|
-
uri_options.
|
|
57
|
+
uri_options.to_a.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
|
|
58
58
|
opts = uri_options.merge(opts)
|
|
59
59
|
opts[:adapter] = scheme
|
|
60
60
|
end
|
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
|
|
@@ -21,7 +21,7 @@ module Sequel
|
|
|
21
21
|
UNSIGNED = ' UNSIGNED'.freeze
|
|
22
22
|
|
|
23
23
|
# The order of column modifiers to use when defining a column.
|
|
24
|
-
COLUMN_DEFINITION_ORDER = [:default, :null, :unique, :primary_key, :auto_increment, :references]
|
|
24
|
+
COLUMN_DEFINITION_ORDER = [:collate, :default, :null, :unique, :primary_key, :auto_increment, :references]
|
|
25
25
|
|
|
26
26
|
# Adds a column to the specified table. This method expects a column name,
|
|
27
27
|
# a datatype and optionally a hash with additional constraints and options:
|
|
@@ -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:
|
|
@@ -275,7 +279,12 @@ module Sequel
|
|
|
275
279
|
def column_definition_auto_increment_sql(sql, column)
|
|
276
280
|
sql << " #{auto_increment_sql}" if column[:auto_increment]
|
|
277
281
|
end
|
|
278
|
-
|
|
282
|
+
|
|
283
|
+
# Add collate SQL fragment to column creation SQL.
|
|
284
|
+
def column_definition_collate_sql(sql, column)
|
|
285
|
+
sql << " COLLATE #{column[:collate]}" if column[:collate]
|
|
286
|
+
end
|
|
287
|
+
|
|
279
288
|
# Add default SQL fragment to column creation SQL.
|
|
280
289
|
def column_definition_default_sql(sql, column)
|
|
281
290
|
sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
|
|
@@ -366,7 +375,7 @@ module Sequel
|
|
|
366
375
|
|
|
367
376
|
# DDL statement for creating a table with the given name, columns, and options
|
|
368
377
|
def create_table_sql(name, generator, options)
|
|
369
|
-
"CREATE #{temporary_table_sql if options[:temp]}TABLE #{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)})"
|
|
370
379
|
end
|
|
371
380
|
|
|
372
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
|
data/lib/sequel/dataset/sql.rb
CHANGED
|
@@ -269,6 +269,7 @@ module Sequel
|
|
|
269
269
|
literal(op == :IN ? expr : ~expr)
|
|
270
270
|
else
|
|
271
271
|
old_vals = vals
|
|
272
|
+
vals = vals.naked if vals.is_a?(Sequel::Dataset)
|
|
272
273
|
vals = vals.to_a
|
|
273
274
|
val_cols = old_vals.columns
|
|
274
275
|
complex_expression_sql(op, [cols, vals.map!{|x| x.values_at(*val_cols)}])
|
|
@@ -774,59 +775,7 @@ module Sequel
|
|
|
774
775
|
|
|
775
776
|
# Qualify the given expression e to the given table.
|
|
776
777
|
def qualified_expression(e, table)
|
|
777
|
-
|
|
778
|
-
when Symbol
|
|
779
|
-
t, column, aliaz = split_symbol(e)
|
|
780
|
-
if t
|
|
781
|
-
e
|
|
782
|
-
elsif aliaz
|
|
783
|
-
SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(table, SQL::Identifier.new(column)), aliaz)
|
|
784
|
-
else
|
|
785
|
-
SQL::QualifiedIdentifier.new(table, e)
|
|
786
|
-
end
|
|
787
|
-
when Array
|
|
788
|
-
e.map{|a| qualified_expression(a, table)}
|
|
789
|
-
when Hash
|
|
790
|
-
h = {}
|
|
791
|
-
e.each{|k,v| h[qualified_expression(k, table)] = qualified_expression(v, table)}
|
|
792
|
-
h
|
|
793
|
-
when SQL::Identifier
|
|
794
|
-
SQL::QualifiedIdentifier.new(table, e)
|
|
795
|
-
when SQL::OrderedExpression
|
|
796
|
-
SQL::OrderedExpression.new(qualified_expression(e.expression, table), e.descending, :nulls=>e.nulls)
|
|
797
|
-
when SQL::AliasedExpression
|
|
798
|
-
SQL::AliasedExpression.new(qualified_expression(e.expression, table), e.aliaz)
|
|
799
|
-
when SQL::CaseExpression
|
|
800
|
-
args = [qualified_expression(e.conditions, table), qualified_expression(e.default, table)]
|
|
801
|
-
args << qualified_expression(e.expression, table) if e.expression?
|
|
802
|
-
SQL::CaseExpression.new(*args)
|
|
803
|
-
when SQL::Cast
|
|
804
|
-
SQL::Cast.new(qualified_expression(e.expr, table), e.type)
|
|
805
|
-
when SQL::Function
|
|
806
|
-
SQL::Function.new(e.f, *qualified_expression(e.args, table))
|
|
807
|
-
when SQL::ComplexExpression
|
|
808
|
-
SQL::ComplexExpression.new(e.op, *qualified_expression(e.args, table))
|
|
809
|
-
when SQL::Subscript
|
|
810
|
-
SQL::Subscript.new(qualified_expression(e.f, table), qualified_expression(e.sub, table))
|
|
811
|
-
when SQL::WindowFunction
|
|
812
|
-
SQL::WindowFunction.new(qualified_expression(e.function, table), qualified_expression(e.window, table))
|
|
813
|
-
when SQL::Window
|
|
814
|
-
o = e.opts.dup
|
|
815
|
-
o[:partition] = qualified_expression(o[:partition], table) if o[:partition]
|
|
816
|
-
o[:order] = qualified_expression(o[:order], table) if o[:order]
|
|
817
|
-
SQL::Window.new(o)
|
|
818
|
-
when SQL::PlaceholderLiteralString
|
|
819
|
-
args = if e.args.is_a?(Hash)
|
|
820
|
-
h = {}
|
|
821
|
-
e.args.each{|k,v| h[k] = qualified_expression(v, table)}
|
|
822
|
-
h
|
|
823
|
-
else
|
|
824
|
-
qualified_expression(e.args, table)
|
|
825
|
-
end
|
|
826
|
-
SQL::PlaceholderLiteralString.new(e.str, args, e.parens)
|
|
827
|
-
else
|
|
828
|
-
e
|
|
829
|
-
end
|
|
778
|
+
Qualifier.new(self, table).transform(e)
|
|
830
779
|
end
|
|
831
780
|
|
|
832
781
|
# The order of methods to call to build the SELECT SQL statement
|
data/lib/sequel/exceptions.rb
CHANGED
|
@@ -44,6 +44,10 @@ module Sequel
|
|
|
44
44
|
# and won't reraise it.
|
|
45
45
|
class Rollback < Error ; end
|
|
46
46
|
|
|
47
|
+
# Exception that occurs when unbinding a dataset that has multiple different values
|
|
48
|
+
# for a given variable.
|
|
49
|
+
class UnbindDuplicate < Error; end
|
|
50
|
+
|
|
47
51
|
class Error
|
|
48
52
|
AdapterNotFound = Sequel::AdapterNotFound
|
|
49
53
|
InvalidOperation = Sequel::InvalidOperation
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# The columns_introspection extension attempts to introspect the
|
|
2
|
+
# selected columns for a dataset before issuing a query. If it
|
|
3
|
+
# thinks it can guess correctly at the columns the query will use,
|
|
4
|
+
# it will return the columns without issuing a database query.
|
|
5
|
+
# This method is not fool-proof, it's possible that some databases
|
|
6
|
+
# will use column names that Sequel does not expect.
|
|
7
|
+
#
|
|
8
|
+
# To enable this for a single dataset, extend the dataset with
|
|
9
|
+
# Sequel::ColumnIntrospection. To enable this for all datasets, run:
|
|
10
|
+
#
|
|
11
|
+
# Sequel::Dataset.introspect_all_columns
|
|
12
|
+
|
|
13
|
+
module Sequel
|
|
14
|
+
module ColumnsIntrospection
|
|
15
|
+
# Attempt to guess the columns that will be returned
|
|
16
|
+
# if there are columns selected, in order to skip a database
|
|
17
|
+
# query to retrieve the columns. This should work with
|
|
18
|
+
# Symbols, SQL::Identifiers, SQL::QualifiedIdentifiers, and
|
|
19
|
+
# SQL::AliasedExpressions.
|
|
20
|
+
def columns
|
|
21
|
+
return @columns if @columns
|
|
22
|
+
return columns_without_introspection unless cols = opts[:select] and !cols.empty?
|
|
23
|
+
probable_columns = cols.map{|c| probable_column_name(c)}
|
|
24
|
+
if probable_columns.all?
|
|
25
|
+
@columns = probable_columns
|
|
26
|
+
else
|
|
27
|
+
columns_without_introspection
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Return the probable name of the column, or nil if one
|
|
34
|
+
# cannot be determined.
|
|
35
|
+
def probable_column_name(c)
|
|
36
|
+
case c
|
|
37
|
+
when Symbol
|
|
38
|
+
_, c, a = split_symbol(c)
|
|
39
|
+
(a || c).to_sym
|
|
40
|
+
when SQL::Identifier
|
|
41
|
+
c.value.to_sym
|
|
42
|
+
when SQL::QualifiedIdentifier
|
|
43
|
+
col = c.column
|
|
44
|
+
col.is_a?(SQL::Identifier) ? col.value.to_sym : col.to_sym
|
|
45
|
+
when SQL::AliasedExpression
|
|
46
|
+
a = c.aliaz
|
|
47
|
+
a.is_a?(SQL::Identifier) ? a.value.to_sym : a.to_sym
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class Dataset
|
|
53
|
+
alias columns_without_introspection columns
|
|
54
|
+
|
|
55
|
+
# Enable column introspection for every dataset.
|
|
56
|
+
def self.introspect_all_columns
|
|
57
|
+
include ColumnsIntrospection
|
|
58
|
+
remove_method(:columns) if instance_methods(false).map{|x| x.to_s}.include?('columns')
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -302,7 +302,7 @@ module Sequel
|
|
|
302
302
|
#
|
|
303
303
|
# Part of the +migration+ extension.
|
|
304
304
|
class Migrator
|
|
305
|
-
MIGRATION_FILE_PATTERN = /\A\d+_.+\.rb\z/i.freeze
|
|
305
|
+
MIGRATION_FILE_PATTERN = /\A(\d+)_.+\.rb\z/i.freeze
|
|
306
306
|
MIGRATION_SPLITTER = '_'.freeze
|
|
307
307
|
MINIMUM_TIMESTAMP = 20000101
|
|
308
308
|
|
|
@@ -375,7 +375,8 @@ module Sequel
|
|
|
375
375
|
@db = db
|
|
376
376
|
@directory = directory
|
|
377
377
|
@files = get_migration_files
|
|
378
|
-
|
|
378
|
+
schema, table = @db.send(:schema_and_table, opts[:table] || self.class.const_get(:DEFAULT_SCHEMA_TABLE))
|
|
379
|
+
@table = schema ? Sequel::SQL::QualifiedIdentifier.new(schema, table) : table
|
|
379
380
|
@column = opts[:column] || self.class.const_get(:DEFAULT_SCHEMA_COLUMN)
|
|
380
381
|
@ds = schema_dataset
|
|
381
382
|
end
|
|
@@ -588,7 +589,7 @@ module Sequel
|
|
|
588
589
|
next unless MIGRATION_FILE_PATTERN.match(file)
|
|
589
590
|
files << File.join(directory, file)
|
|
590
591
|
end
|
|
591
|
-
files.
|
|
592
|
+
files.sort_by{|f| MIGRATION_FILE_PATTERN.match(File.basename(f))[1].to_i}
|
|
592
593
|
end
|
|
593
594
|
|
|
594
595
|
# Returns tuples of migration, filename, and direction
|