sequel 3.27.0 → 3.28.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 +96 -0
- data/README.rdoc +2 -2
- data/Rakefile +1 -1
- data/doc/association_basics.rdoc +48 -0
- data/doc/opening_databases.rdoc +29 -5
- data/doc/prepared_statements.rdoc +1 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/testing.rdoc +42 -0
- data/doc/transactions.rdoc +97 -0
- data/lib/sequel/adapters/db2.rb +95 -65
- data/lib/sequel/adapters/firebird.rb +25 -219
- data/lib/sequel/adapters/ibmdb.rb +440 -0
- data/lib/sequel/adapters/jdbc.rb +12 -0
- data/lib/sequel/adapters/jdbc/as400.rb +0 -7
- data/lib/sequel/adapters/jdbc/db2.rb +49 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
- data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
- data/lib/sequel/adapters/mysql.rb +10 -15
- data/lib/sequel/adapters/odbc.rb +1 -2
- data/lib/sequel/adapters/odbc/db2.rb +5 -5
- data/lib/sequel/adapters/postgres.rb +71 -11
- data/lib/sequel/adapters/shared/db2.rb +290 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/mssql.rb +18 -75
- data/lib/sequel/adapters/shared/mysql.rb +13 -0
- data/lib/sequel/adapters/shared/postgres.rb +52 -36
- data/lib/sequel/adapters/shared/sqlite.rb +32 -36
- data/lib/sequel/adapters/sqlite.rb +4 -8
- data/lib/sequel/adapters/tinytds.rb +7 -3
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -5
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -1
- data/lib/sequel/dataset/actions.rb +149 -33
- data/lib/sequel/dataset/features.rb +44 -7
- data/lib/sequel/dataset/misc.rb +9 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -2
- data/lib/sequel/dataset/query.rb +63 -10
- data/lib/sequel/dataset/sql.rb +22 -5
- data/lib/sequel/model.rb +3 -3
- data/lib/sequel/model/associations.rb +250 -27
- data/lib/sequel/model/base.rb +10 -16
- data/lib/sequel/plugins/many_through_many.rb +34 -2
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
- data/lib/sequel/sql.rb +94 -51
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/postgres_spec.rb +74 -6
- data/spec/adapters/spec_helper.rb +1 -0
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/database_spec.rb +7 -0
- data/spec/core/dataset_spec.rb +180 -17
- data/spec/core/expression_filters_spec.rb +107 -41
- data/spec/core/spec_helper.rb +11 -0
- data/spec/extensions/many_through_many_spec.rb +115 -1
- data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
- data/spec/integration/associations_test.rb +193 -15
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +215 -19
- data/spec/integration/plugin_test.rb +8 -5
- data/spec/integration/prepared_statement_test.rb +91 -98
- data/spec/integration/schema_test.rb +27 -11
- data/spec/integration/spec_helper.rb +10 -0
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/association_reflection_spec.rb +91 -0
- data/spec/model/associations_spec.rb +13 -0
- data/spec/model/base_spec.rb +8 -21
- data/spec/model/eager_loading_spec.rb +243 -9
- data/spec/model/model_spec.rb +15 -2
- metadata +16 -4
data/lib/sequel/model/base.rb
CHANGED
@@ -23,7 +23,7 @@ module Sequel
|
|
23
23
|
# stored so when the dataset changes, methods defined with def_dataset_method
|
24
24
|
# will be applied to the new dataset.
|
25
25
|
attr_reader :dataset_methods
|
26
|
-
|
26
|
+
|
27
27
|
# Array of plugin modules loaded by this class
|
28
28
|
#
|
29
29
|
# Sequel::Model.plugins
|
@@ -423,10 +423,12 @@ module Sequel
|
|
423
423
|
@allowed_columns = cols
|
424
424
|
end
|
425
425
|
|
426
|
-
# Sets the dataset associated with the Model class. +ds+ can be a +Symbol
|
427
|
-
#
|
426
|
+
# Sets the dataset associated with the Model class. +ds+ can be a +Symbol+,
|
427
|
+
# +LiteralString+, <tt>SQL::Identifier</tt>, <tt>SQL::QualifiedIdentifier</tt>,
|
428
|
+
# <tt>SQL::AliasedExpression</tt>
|
429
|
+
# (all specifying a table name in the current database), or a +Dataset+.
|
428
430
|
# If a dataset is used, the model's database is changed to the database of the given
|
429
|
-
# dataset. If a
|
431
|
+
# dataset. If a dataset is not used, a dataset is created from the current
|
430
432
|
# database with the table name given. Other arguments raise an +Error+.
|
431
433
|
# Returns self.
|
432
434
|
#
|
@@ -441,15 +443,15 @@ module Sequel
|
|
441
443
|
def set_dataset(ds, opts={})
|
442
444
|
inherited = opts[:inherited]
|
443
445
|
@dataset = case ds
|
444
|
-
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression
|
446
|
+
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
|
445
447
|
@simple_table = db.literal(ds)
|
446
|
-
db
|
448
|
+
db.from(ds)
|
447
449
|
when Dataset
|
448
450
|
@simple_table = nil
|
449
451
|
@db = ds.db
|
450
452
|
ds
|
451
453
|
else
|
452
|
-
raise(Error, "Model.set_dataset takes one of the following classes as an argument: Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, Dataset")
|
454
|
+
raise(Error, "Model.set_dataset takes one of the following classes as an argument: Symbol, LiteralString, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, Dataset")
|
453
455
|
end
|
454
456
|
@dataset.row_proc = Proc.new{|r| load(r)}
|
455
457
|
@require_modification = Sequel::Model.require_modification.nil? ? @dataset.provides_accurate_rows_matched? : Sequel::Model.require_modification
|
@@ -1684,15 +1686,7 @@ module Sequel
|
|
1684
1686
|
# Artist.dataset.with_pk([1, 2]) # SELECT * FROM artists
|
1685
1687
|
# # WHERE ((id1 = 1) AND (id2 = 2)) LIMIT 1
|
1686
1688
|
def with_pk(pk)
|
1687
|
-
|
1688
|
-
when Array
|
1689
|
-
raise(Error, "single primary key given (#{pk.inspect}) when a composite primary key is expected (#{primary_key.inspect})") unless pk.is_a?(Array)
|
1690
|
-
raise(Error, "composite primary key given (#{pk.inspect}) does not match composite primary key length (#{primary_key.inspect})") if pk.length != primary_key.length
|
1691
|
-
first(primary_key.zip(pk))
|
1692
|
-
else
|
1693
|
-
raise(Error, "composite primary key given (#{pk.inspect}) when a single primary key is expected (#{primary_key.inspect})") if pk.is_a?(Array)
|
1694
|
-
first(primary_key=>pk)
|
1695
|
-
end
|
1689
|
+
first(model.qualified_primary_key_hash(pk))
|
1696
1690
|
end
|
1697
1691
|
end
|
1698
1692
|
|
@@ -58,6 +58,19 @@ module Sequel
|
|
58
58
|
self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
|
59
59
|
end
|
60
60
|
|
61
|
+
# The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
|
62
|
+
def eager_loading_predicate_key
|
63
|
+
self[:eager_loading_predicate_key] ||= begin
|
64
|
+
calculate_edges
|
65
|
+
e = self[:edges].first
|
66
|
+
if self[:uses_left_composite_keys]
|
67
|
+
e[:right].map{|k| SQL::QualifiedIdentifier.new(e[:table], k)}
|
68
|
+
else
|
69
|
+
SQL::QualifiedIdentifier.new(e[:table], e[:right])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
61
74
|
# The list of joins to use when eager graphing
|
62
75
|
def edges
|
63
76
|
self[:edges] || calculate_edges || self[:edges]
|
@@ -182,13 +195,28 @@ module Sequel
|
|
182
195
|
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
|
183
196
|
opts[:eager_loader] ||= lambda do |eo|
|
184
197
|
h = eo[:key_hash][left_pk]
|
185
|
-
|
198
|
+
rows = eo[:rows]
|
199
|
+
rows.each{|object| object.associations[name] = []}
|
186
200
|
ds = opts.associated_class
|
187
201
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
188
202
|
ft = opts[:final_reverse_edge]
|
189
203
|
conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
|
190
204
|
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
|
191
|
-
model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo)
|
205
|
+
ds = model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo)
|
206
|
+
case opts.eager_limit_strategy
|
207
|
+
when :window_function
|
208
|
+
delete_rn = true
|
209
|
+
rn = ds.row_number_column
|
210
|
+
ds = apply_window_function_eager_limit_strategy(ds, opts)
|
211
|
+
when :correlated_subquery
|
212
|
+
ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
|
213
|
+
dsa = ds.send(:dataset_alias, 2)
|
214
|
+
opts.reverse_edges.each{|t| xds = xds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
215
|
+
xds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.map{|k| [k, SQL::QualifiedIdentifier.new(ft[:table], k)]}, :table_alias=>dsa)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
ds.all do |assoc_record|
|
219
|
+
assoc_record.values.delete(rn) if delete_rn
|
192
220
|
hash_key = if uses_lcks
|
193
221
|
left_key_alias.map{|k| assoc_record.values.delete(k)}
|
194
222
|
else
|
@@ -197,6 +225,10 @@ module Sequel
|
|
197
225
|
next unless objects = h[hash_key]
|
198
226
|
objects.each{|object| object.associations[name].push(assoc_record)}
|
199
227
|
end
|
228
|
+
if opts.eager_limit_strategy == :ruby
|
229
|
+
limit, offset = opts.limit_and_offset
|
230
|
+
rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
|
231
|
+
end
|
200
232
|
end
|
201
233
|
|
202
234
|
join_type = opts[:graph_join_type]
|
@@ -31,7 +31,7 @@ module Sequel
|
|
31
31
|
# Return a prepared statement that can be used to lookup a row given a dataset for the row matching
|
32
32
|
# the primary key.
|
33
33
|
def prepared_lookup_dataset(ds)
|
34
|
-
@prepared_statements[:lookup_sql][ds.sql] ||= prepare_statement(ds.filter(prepared_statement_key_array(primary_key)), :first)
|
34
|
+
@prepared_statements[:lookup_sql][ds.sql] ||= prepare_statement(ds.filter(prepared_statement_key_array(primary_key).map{|k, v| [SQL::QualifiedIdentifier.new(ds.model.table_name, k), v]}), :first)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
data/lib/sequel/sql.rb
CHANGED
@@ -41,6 +41,11 @@ module Sequel
|
|
41
41
|
# Time subclass that gets literalized with only the time value, so it operates
|
42
42
|
# like a standard SQL time type.
|
43
43
|
class SQLTime < ::Time
|
44
|
+
# Create a new SQLTime instance given an hour, minute, and second.
|
45
|
+
def self.create(hour, minute, second, usec = 0)
|
46
|
+
t = now
|
47
|
+
local(t.year, t.month, t.day, hour, minute, second, usec)
|
48
|
+
end
|
44
49
|
end
|
45
50
|
|
46
51
|
# The SQL module holds classes whose instances represent SQL fragments.
|
@@ -149,14 +154,17 @@ module Sequel
|
|
149
154
|
# Operator symbols that take exactly two arguments
|
150
155
|
TWO_ARITY_OPERATORS = [:'=', :'!=', :LIKE, :'NOT LIKE', \
|
151
156
|
:~, :'!~', :'~*', :'!~*', :ILIKE, :'NOT ILIKE'] + \
|
152
|
-
INEQUALITY_OPERATORS +
|
157
|
+
INEQUALITY_OPERATORS + IS_OPERATORS + IN_OPERATORS
|
153
158
|
|
154
159
|
# Operator symbols that take one or more arguments
|
155
|
-
N_ARITY_OPERATORS = [:AND, :OR, :'||'] + MATHEMATICAL_OPERATORS
|
160
|
+
N_ARITY_OPERATORS = [:AND, :OR, :'||'] + MATHEMATICAL_OPERATORS + BITWISE_OPERATORS
|
156
161
|
|
157
162
|
# Operator symbols that take only a single argument
|
158
163
|
ONE_ARITY_OPERATORS = [:NOT, :NOOP, :'B~']
|
159
164
|
|
165
|
+
# Custom expressions that may have different syntax on different databases
|
166
|
+
CUSTOM_EXPRESSIONS = [:extract]
|
167
|
+
|
160
168
|
# An array of args for this object
|
161
169
|
attr_reader :args
|
162
170
|
|
@@ -185,6 +193,8 @@ module Sequel
|
|
185
193
|
args[1] = orig_args[1] if IN_OPERATORS.include?(op)
|
186
194
|
when *ONE_ARITY_OPERATORS
|
187
195
|
raise(Error, "The #{op} operator requires a single argument") unless args.length == 1
|
196
|
+
when *CUSTOM_EXPRESSIONS
|
197
|
+
# nothing
|
188
198
|
else
|
189
199
|
raise(Error, "Invalid operator #{op}")
|
190
200
|
end
|
@@ -224,14 +234,7 @@ module Sequel
|
|
224
234
|
# ~:a.sql_number # ~"a"
|
225
235
|
module BitwiseMethods
|
226
236
|
ComplexExpression::BITWISE_OPERATORS.each do |o|
|
227
|
-
|
228
|
-
case ce
|
229
|
-
when BooleanExpression, StringExpression
|
230
|
-
raise(Sequel::Error, "cannot apply #{o} to a non-numeric expression")
|
231
|
-
else
|
232
|
-
NumericExpression.new(o, self, ce)
|
233
|
-
end
|
234
|
-
end
|
237
|
+
module_eval("def #{o}(o) NumericExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__)
|
235
238
|
end
|
236
239
|
|
237
240
|
# Do the bitwise compliment of the self
|
@@ -249,16 +252,24 @@ module Sequel
|
|
249
252
|
# :a & :b # "a" AND "b"
|
250
253
|
# :a | :b # "a" OR "b"
|
251
254
|
# ~:a # NOT "a"
|
255
|
+
#
|
256
|
+
# One exception to this is when a NumericExpression or Integer is the argument
|
257
|
+
# to & or |, in which case a bitwise method will be used:
|
258
|
+
#
|
259
|
+
# :a & 1 # "a" & 1
|
260
|
+
# :a | (:b + 1) # "a" | ("b" + 1)
|
252
261
|
module BooleanMethods
|
253
262
|
ComplexExpression::BOOLEAN_OPERATOR_METHODS.each do |m, o|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
263
|
+
module_eval(<<-END, __FILE__, __LINE__+1)
|
264
|
+
def #{m}(o)
|
265
|
+
case o
|
266
|
+
when NumericExpression, Integer
|
267
|
+
NumericExpression.new(#{m.inspect}, self, o)
|
268
|
+
else
|
269
|
+
BooleanExpression.new(#{o.inspect}, self, o)
|
270
|
+
end
|
260
271
|
end
|
261
|
-
|
272
|
+
END
|
262
273
|
end
|
263
274
|
|
264
275
|
# Create a new BooleanExpression with NOT, representing the inversion of whatever self represents.
|
@@ -326,7 +337,7 @@ module Sequel
|
|
326
337
|
# doesn't use the standard function calling convention, and it
|
327
338
|
# doesn't work on all databases.
|
328
339
|
def extract(datetime_part)
|
329
|
-
|
340
|
+
NumericExpression.new(:extract, datetime_part, self)
|
330
341
|
end
|
331
342
|
|
332
343
|
# Return a BooleanExpression representation of +self+.
|
@@ -372,34 +383,12 @@ module Sequel
|
|
372
383
|
# 'a'.lit <= :b # a <= "b"
|
373
384
|
module InequalityMethods
|
374
385
|
ComplexExpression::INEQUALITY_OPERATORS.each do |o|
|
375
|
-
|
376
|
-
case ce
|
377
|
-
when BooleanExpression, TrueClass, FalseClass, NilClass, Hash, ::Array
|
378
|
-
raise(Error, "cannot apply #{o} to a boolean expression")
|
379
|
-
else
|
380
|
-
BooleanExpression.new(o, self, ce)
|
381
|
-
end
|
382
|
-
end
|
386
|
+
module_eval("def #{o}(o) BooleanExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__)
|
383
387
|
end
|
384
388
|
end
|
385
389
|
|
386
|
-
#
|
387
|
-
# +ComplexExpression+ subclass it is included in, so that
|
388
|
-
# attempting to use boolean input when initializing a +NumericExpression+
|
389
|
-
# or +StringExpression+ results in an error. It is not expected to be
|
390
|
-
# used directly.
|
390
|
+
# Only exists for backwards compatibility, ignore it.
|
391
391
|
module NoBooleanInputMethods
|
392
|
-
# Raise an +Error+ if one of the args would be boolean in an SQL
|
393
|
-
# context, otherwise call super.
|
394
|
-
def initialize(op, *args)
|
395
|
-
args.each do |a|
|
396
|
-
case a
|
397
|
-
when BooleanExpression, TrueClass, FalseClass, NilClass, Hash, ::Array
|
398
|
-
raise(Error, "cannot apply #{op} to a boolean expression")
|
399
|
-
end
|
400
|
-
end
|
401
|
-
super
|
402
|
-
end
|
403
392
|
end
|
404
393
|
|
405
394
|
# This module includes the standard mathematical methods (+, -, *, and /)
|
@@ -410,15 +399,26 @@ module Sequel
|
|
410
399
|
# :a - :b # "a" - "b"
|
411
400
|
# :a * :b # "a" * "b"
|
412
401
|
# :a / :b # "a" / "b"
|
402
|
+
#
|
403
|
+
# One exception to this is if + is called with a +String+ or +StringExpression+,
|
404
|
+
# in which case the || operator is used instead of the + operator:
|
405
|
+
#
|
406
|
+
# :a + 'b' # "a" || 'b'
|
413
407
|
module NumericMethods
|
414
408
|
ComplexExpression::MATHEMATICAL_OPERATORS.each do |o|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
409
|
+
module_eval("def #{o}(o) NumericExpression.new(#{o.inspect}, self, o) end", __FILE__, __LINE__) unless o == :+
|
410
|
+
end
|
411
|
+
|
412
|
+
# Use || as the operator when called with StringExpression and String instances,
|
413
|
+
# and the + operator for LiteralStrings and all other types.
|
414
|
+
def +(ce)
|
415
|
+
case ce
|
416
|
+
when LiteralString
|
417
|
+
NumericExpression.new(:+, self, ce)
|
418
|
+
when StringExpression, String
|
419
|
+
StringExpression.new(:'||', self, ce)
|
420
|
+
else
|
421
|
+
NumericExpression.new(:+, self, ce)
|
422
422
|
end
|
423
423
|
end
|
424
424
|
end
|
@@ -606,6 +606,21 @@ module Sequel
|
|
606
606
|
BooleanExpression.new(:NOT, ce)
|
607
607
|
end
|
608
608
|
end
|
609
|
+
|
610
|
+
# Always use an AND operator for & on BooleanExpressions
|
611
|
+
def &(ce)
|
612
|
+
BooleanExpression.new(:AND, self, ce)
|
613
|
+
end
|
614
|
+
|
615
|
+
# Always use an OR operator for | on BooleanExpressions
|
616
|
+
def |(ce)
|
617
|
+
BooleanExpression.new(:OR, self, ce)
|
618
|
+
end
|
619
|
+
|
620
|
+
# Return self instead of creating a new object to save on memory.
|
621
|
+
def sql_boolean
|
622
|
+
self
|
623
|
+
end
|
609
624
|
end
|
610
625
|
|
611
626
|
# Represents an SQL CASE expression, used for conditional branching in SQL.
|
@@ -673,6 +688,21 @@ module Sequel
|
|
673
688
|
include CastMethods
|
674
689
|
include OrderMethods
|
675
690
|
include SubscriptMethods
|
691
|
+
|
692
|
+
# Return a BooleanExpression with the same op and args.
|
693
|
+
def sql_boolean
|
694
|
+
BooleanExpression.new(self.op, *self.args)
|
695
|
+
end
|
696
|
+
|
697
|
+
# Return a NumericExpression with the same op and args.
|
698
|
+
def sql_number
|
699
|
+
NumericExpression.new(self.op, *self.args)
|
700
|
+
end
|
701
|
+
|
702
|
+
# Return a StringExpression with the same op and args.
|
703
|
+
def sql_string
|
704
|
+
StringExpression.new(self.op, *self.args)
|
705
|
+
end
|
676
706
|
end
|
677
707
|
|
678
708
|
# Represents constants or psuedo-constants (e.g. +CURRENT_DATE+) in SQL.
|
@@ -847,7 +877,16 @@ module Sequel
|
|
847
877
|
include BitwiseMethods
|
848
878
|
include NumericMethods
|
849
879
|
include InequalityMethods
|
850
|
-
|
880
|
+
|
881
|
+
# Always use + for + operator for NumericExpressions.
|
882
|
+
def +(ce)
|
883
|
+
NumericExpression.new(:+, self, ce)
|
884
|
+
end
|
885
|
+
|
886
|
+
# Return self instead of creating a new object to save on memory.
|
887
|
+
def sql_number
|
888
|
+
self
|
889
|
+
end
|
851
890
|
end
|
852
891
|
|
853
892
|
# Represents a column/expression to order the result set by.
|
@@ -913,7 +952,6 @@ module Sequel
|
|
913
952
|
include StringMethods
|
914
953
|
include StringConcatenationMethods
|
915
954
|
include InequalityMethods
|
916
|
-
include NoBooleanInputMethods
|
917
955
|
|
918
956
|
# Map of [regexp, case_insenstive] to +ComplexExpression+ operator symbol
|
919
957
|
LIKE_MAP = {[true, true]=>:'~*', [true, false]=>:~, [false, true]=>:ILIKE, [false, false]=>:LIKE}
|
@@ -962,6 +1000,11 @@ module Sequel
|
|
962
1000
|
end
|
963
1001
|
end
|
964
1002
|
private_class_method :like_element
|
1003
|
+
|
1004
|
+
# Return self instead of creating a new object to save on memory.
|
1005
|
+
def sql_string
|
1006
|
+
self
|
1007
|
+
end
|
965
1008
|
end
|
966
1009
|
|
967
1010
|
# Represents an SQL array access, with multiple possible arguments.
|
data/lib/sequel/version.rb
CHANGED
@@ -3,7 +3,7 @@ module Sequel
|
|
3
3
|
MAJOR = 3
|
4
4
|
# The minor version of Sequel. Bumped for every non-patch level
|
5
5
|
# release, generally around once a month.
|
6
|
-
MINOR =
|
6
|
+
MINOR = 28
|
7
7
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
8
8
|
# releases that fix regressions from previous versions.
|
9
9
|
TINY = 0
|
@@ -0,0 +1,146 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
#Author: Roy L Zuo (roylzuo at gmail dot com)
|
4
|
+
#Description:
|
5
|
+
|
6
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
|
7
|
+
|
8
|
+
require ENV['SEQUEL_DB2_SPEC_REQUIRE'] if ENV['SEQUEL_DB2_SPEC_REQUIRE']
|
9
|
+
|
10
|
+
unless defined?(DB2_DB)
|
11
|
+
DB2_DB = Sequel.connect(ENV['SEQUEL_DB2_SPEC_DB']||DB2_URL)
|
12
|
+
end
|
13
|
+
|
14
|
+
if DB2_DB.table_exists?(:test)
|
15
|
+
DB2_DB.drop_table :test
|
16
|
+
end
|
17
|
+
INTEGRATION_DB = DB2_DB unless defined?(INTEGRATION_DB)
|
18
|
+
|
19
|
+
describe Sequel::Database do
|
20
|
+
before do
|
21
|
+
@db = DB2_DB
|
22
|
+
@db.create_table(:test){String :a}
|
23
|
+
@ds = @db[:test]
|
24
|
+
end
|
25
|
+
|
26
|
+
after do
|
27
|
+
@db.drop_table(:test)
|
28
|
+
end
|
29
|
+
|
30
|
+
specify "should provide disconnect functionality after preparing a connection" do
|
31
|
+
@ds.prepare(:first, :a).call
|
32
|
+
@db.disconnect
|
33
|
+
@db.pool.size.should == 0
|
34
|
+
end
|
35
|
+
|
36
|
+
specify "should return version correctly" do
|
37
|
+
@db.db2_version.should match(/DB2 v/i)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "Simple Dataset operations" do
|
42
|
+
before do
|
43
|
+
DB2_DB.create_table!(:items) do
|
44
|
+
Integer :id, :primary_key => true
|
45
|
+
Integer :number
|
46
|
+
end
|
47
|
+
@ds = DB2_DB[:items]
|
48
|
+
@ds.insert(:number=>10, :id => 1 )
|
49
|
+
end
|
50
|
+
after do
|
51
|
+
DB2_DB.drop_table(:items)
|
52
|
+
end
|
53
|
+
cspecify "should insert with a primary key specified", :mssql do
|
54
|
+
@ds.insert(:id=>100, :number=>20)
|
55
|
+
@ds.count.should == 2
|
56
|
+
@ds.order(:id).all.should == [{:id=>1, :number=>10}, {:id=>100, :number=>20}]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe Sequel::Database do
|
61
|
+
before do
|
62
|
+
@db = DB2_DB
|
63
|
+
end
|
64
|
+
after do
|
65
|
+
@db.drop_table(:items)
|
66
|
+
end
|
67
|
+
specify "should parse primary keys from the schema properly" do
|
68
|
+
@db.create_table!(:items){Integer :number}
|
69
|
+
@db.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == []
|
70
|
+
@db.create_table!(:items){primary_key :number}
|
71
|
+
@db.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == [:number]
|
72
|
+
@db.create_table!(:items){Integer :number1, :null => false; Integer :number2, :null => false; primary_key [:number1, :number2]}
|
73
|
+
@db.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == [:number1, :number2]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "Sequel::IBMDB.convert_smallint_to_bool" do
|
78
|
+
before do
|
79
|
+
@db = DB2_DB
|
80
|
+
@db.create_table!(:booltest){column :b, 'smallint'; column :i, 'integer'}
|
81
|
+
@ds = @db[:booltest]
|
82
|
+
end
|
83
|
+
after do
|
84
|
+
Sequel::IBMDB.convert_smallint_to_bool = true
|
85
|
+
@db.drop_table(:booltest)
|
86
|
+
end
|
87
|
+
|
88
|
+
specify "should consider smallint datatypes as boolean if set, but not larger smallints" do
|
89
|
+
@db.schema(:booltest, :reload=>true).first.last[:type].should == :boolean
|
90
|
+
@db.schema(:booltest, :reload=>true).first.last[:db_type].should match /smallint/i
|
91
|
+
Sequel::IBMDB.convert_smallint_to_bool = false
|
92
|
+
@db.schema(:booltest, :reload=>true).first.last[:type].should == :integer
|
93
|
+
@db.schema(:booltest, :reload=>true).first.last[:db_type].should match /smallint/i
|
94
|
+
end
|
95
|
+
|
96
|
+
specify "should return smallints as bools and integers as integers when set" do
|
97
|
+
Sequel::IBMDB.convert_smallint_to_bool = true
|
98
|
+
@ds.delete
|
99
|
+
@ds << {:b=>true, :i=>10}
|
100
|
+
@ds.all.should == [{:b=>true, :i=>10}]
|
101
|
+
@ds.delete
|
102
|
+
@ds << {:b=>false, :i=>0}
|
103
|
+
@ds.all.should == [{:b=>false, :i=>0}]
|
104
|
+
@ds.delete
|
105
|
+
@ds << {:b=>true, :i=>1}
|
106
|
+
@ds.all.should == [{:b=>true, :i=>1}]
|
107
|
+
end
|
108
|
+
|
109
|
+
specify "should return all smallints as integers when unset" do
|
110
|
+
Sequel::IBMDB.convert_smallint_to_bool = false
|
111
|
+
@ds.delete
|
112
|
+
@ds << {:b=>true, :i=>10}
|
113
|
+
@ds.all.should == [{:b=>1, :i=>10}]
|
114
|
+
@ds.delete
|
115
|
+
@ds << {:b=>false, :i=>0}
|
116
|
+
@ds.all.should == [{:b=>0, :i=>0}]
|
117
|
+
|
118
|
+
@ds.delete
|
119
|
+
@ds << {:b=>1, :i=>10}
|
120
|
+
@ds.all.should == [{:b=>1, :i=>10}]
|
121
|
+
@ds.delete
|
122
|
+
@ds << {:b=>0, :i=>0}
|
123
|
+
@ds.all.should == [{:b=>0, :i=>0}]
|
124
|
+
end
|
125
|
+
end if DB2_DB.adapter_scheme == :ibmdb
|
126
|
+
|
127
|
+
describe "Simple Dataset operations in transactions" do
|
128
|
+
before do
|
129
|
+
DB2_DB.create_table!(:items_insert_in_transaction) do
|
130
|
+
Integer :id, :primary_key => true
|
131
|
+
integer :number
|
132
|
+
end
|
133
|
+
@ds = DB2_DB[:items_insert_in_transaction]
|
134
|
+
end
|
135
|
+
after do
|
136
|
+
DB2_DB.drop_table(:items_insert_in_transaction)
|
137
|
+
end
|
138
|
+
|
139
|
+
specify "should insert correctly with a primary key specified inside a transaction" do
|
140
|
+
DB2_DB.transaction do
|
141
|
+
@ds.insert(:id=>100, :number=>20)
|
142
|
+
@ds.count.should == 1
|
143
|
+
@ds.order(:id).all.should == [{:id=>100, :number=>20}]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|