sequel_core 2.0.0 → 2.0.1
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 +14 -0
- data/Rakefile +1 -1
- data/lib/sequel_core/adapters/mysql.rb +5 -7
- data/lib/sequel_core/adapters/postgres.rb +20 -16
- data/lib/sequel_core/adapters/sqlite.rb +12 -0
- data/lib/sequel_core/core_ext.rb +10 -0
- data/lib/sequel_core/core_sql.rb +22 -22
- data/lib/sequel_core/database.rb +14 -8
- data/lib/sequel_core/dataset/sql.rb +25 -19
- data/lib/sequel_core/schema/sql.rb +4 -2
- data/lib/sequel_core/sql.rb +262 -155
- data/lib/sequel_core.rb +8 -0
- data/spec/adapters/mysql_spec.rb +8 -7
- data/spec/adapters/sqlite_spec.rb +23 -15
- data/spec/blockless_filters_spec.rb +44 -16
- data/spec/core_sql_spec.rb +24 -1
- data/spec/dataset_spec.rb +2 -2
- data/spec/spec_helper.rb +4 -0
- metadata +2 -2
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
=== 2.0.1 (2008-06-04)
|
|
2
|
+
|
|
3
|
+
* Have PostgreSQL money type use BigDecimal instead of Float (jeremyevans)
|
|
4
|
+
|
|
5
|
+
* Have the PostgreSQL and MySQL adapters use the Sequel.time_class setting for datetime/timestamp types (jeremyevans)
|
|
6
|
+
|
|
7
|
+
* Add Sequel.time_class and String#to_sequel_time, used for converting time values from the database to either Time (default) or DateTime (jeremyevans)
|
|
8
|
+
|
|
9
|
+
* Make identifier quoting uppercase by default, to work better with the SQL standard, override in PostgreSQL (jeremyevans) (#232)
|
|
10
|
+
|
|
11
|
+
* Add StringExpression#+, for simple SQL string concatenation (:x.sql_string + :y) (jeremyevans)
|
|
12
|
+
|
|
13
|
+
* Refactor ComplexExpression into three subclasses and a few modules, so operators that don't make sense are not defined for the class (jeremyevans)
|
|
14
|
+
|
|
1
15
|
=== 2.0.0 (2008-06-01)
|
|
2
16
|
|
|
3
17
|
* Refactor String inflection support, you must use String.inflections instead of Inflector.inflections now (jeremyevans)
|
data/Rakefile
CHANGED
|
@@ -9,7 +9,7 @@ include FileUtils
|
|
|
9
9
|
# Configuration
|
|
10
10
|
##############################################################################
|
|
11
11
|
NAME = "sequel_core"
|
|
12
|
-
VERS = "2.0.
|
|
12
|
+
VERS = "2.0.1"
|
|
13
13
|
CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage"]
|
|
14
14
|
RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
|
|
15
15
|
'Sequel: The Database Toolkit for Ruby: Core Library and Adapters', \
|
|
@@ -10,12 +10,12 @@ class Mysql::Result
|
|
|
10
10
|
4 => :to_f, # MYSQL_TYPE_FLOAT
|
|
11
11
|
5 => :to_f, # MYSQL_TYPE_DOUBLE
|
|
12
12
|
# 6 => ??, # MYSQL_TYPE_NULL
|
|
13
|
-
7 => :
|
|
13
|
+
7 => :to_sequel_time, # MYSQL_TYPE_TIMESTAMP
|
|
14
14
|
8 => :to_i, # MYSQL_TYPE_LONGLONG
|
|
15
15
|
9 => :to_i, # MYSQL_TYPE_INT24
|
|
16
16
|
10 => :to_date, # MYSQL_TYPE_DATE
|
|
17
17
|
11 => :to_time, # MYSQL_TYPE_TIME
|
|
18
|
-
12 => :
|
|
18
|
+
12 => :to_sequel_time, # MYSQL_TYPE_DATETIME
|
|
19
19
|
13 => :to_i, # MYSQL_TYPE_YEAR
|
|
20
20
|
14 => :to_date, # MYSQL_TYPE_NEWDATE
|
|
21
21
|
# 15 => :to_s # MYSQL_TYPE_VARCHAR
|
|
@@ -268,7 +268,7 @@ module Sequel
|
|
|
268
268
|
def schema_ds_filter(table_name, opts)
|
|
269
269
|
filt = super
|
|
270
270
|
# Restrict it to the given or current database, unless specifically requesting :database = nil
|
|
271
|
-
filt = SQL::
|
|
271
|
+
filt = SQL::BooleanExpression.new(:AND, filt, {:c__table_schema=>opts[:database] || self.opts[:database]}) if opts[:database] || !opts.include?(:database)
|
|
272
272
|
filt
|
|
273
273
|
end
|
|
274
274
|
|
|
@@ -360,10 +360,8 @@ module Sequel
|
|
|
360
360
|
|
|
361
361
|
def complex_expression_sql(op, args)
|
|
362
362
|
case op
|
|
363
|
-
when :~, :'!~'
|
|
364
|
-
"#{'NOT ' if
|
|
365
|
-
when :'~*', :'!~*'
|
|
366
|
-
"#{'NOT ' if op == :'!~*'}(#{literal(args.at(0))} REGEXP #{literal(args.at(1))})"
|
|
363
|
+
when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
|
364
|
+
"(#{literal(args.at(0))} #{'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)}#{[:~, :'!~', :'~*', :'!~*'].include?(op) ? 'REGEXP' : 'LIKE'} #{'BINARY ' if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)}#{literal(args.at(1))})"
|
|
367
365
|
when :'||'
|
|
368
366
|
if args.length > 1
|
|
369
367
|
"CONCAT(#{args.collect{|a| literal(a)}.join(', ')})"
|
|
@@ -159,21 +159,22 @@ module Sequel
|
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
PG_TYPES = {
|
|
162
|
-
16 => lambda{ |s| Adapter.string_to_bool(s) },
|
|
163
|
-
17 => lambda{ |s| Adapter.unescape_bytea(s) },
|
|
164
|
-
20 => lambda{ |s| s.to_i },
|
|
165
|
-
21 => lambda{ |s| s.to_i },
|
|
166
|
-
22 => lambda{ |s| s.to_i },
|
|
167
|
-
23 => lambda{ |s| s.to_i },
|
|
168
|
-
26 => lambda{ |s| s.to_i },
|
|
169
|
-
700 => lambda{ |s| s.to_f },
|
|
170
|
-
701 => lambda{ |s| s.to_f },
|
|
171
|
-
790 => lambda{ |s| s.
|
|
172
|
-
1082 => lambda{ |s| s.to_date },
|
|
173
|
-
1083 => lambda{ |s| s.to_time },
|
|
174
|
-
1114 => lambda{ |s| s.
|
|
175
|
-
1184 => lambda{ |s| s.
|
|
176
|
-
1186 => lambda{ |s| s.to_i }
|
|
162
|
+
16 => lambda{ |s| Adapter.string_to_bool(s) }, # boolean
|
|
163
|
+
17 => lambda{ |s| Adapter.unescape_bytea(s) }, # bytea
|
|
164
|
+
20 => lambda{ |s| s.to_i }, # int8
|
|
165
|
+
21 => lambda{ |s| s.to_i }, # int2
|
|
166
|
+
22 => lambda{ |s| s.to_i }, # int2vector
|
|
167
|
+
23 => lambda{ |s| s.to_i }, # int4
|
|
168
|
+
26 => lambda{ |s| s.to_i }, # oid
|
|
169
|
+
700 => lambda{ |s| s.to_f }, # float4
|
|
170
|
+
701 => lambda{ |s| s.to_f }, # float8
|
|
171
|
+
790 => lambda{ |s| s.to_d }, # money
|
|
172
|
+
1082 => lambda{ |s| s.to_date }, # date
|
|
173
|
+
1083 => lambda{ |s| s.to_time }, # time without time zone
|
|
174
|
+
1114 => lambda{ |s| s.to_sequel_time }, # timestamp without time zone
|
|
175
|
+
1184 => lambda{ |s| s.to_sequel_time }, # timestamp with time zone
|
|
176
|
+
1186 => lambda{ |s| s.to_i }, # interval
|
|
177
|
+
1266 => lambda{ |s| s.to_time } # time with time zone
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
if Adapter.respond_to?(:translate_results=)
|
|
@@ -365,12 +366,15 @@ module Sequel
|
|
|
365
366
|
def schema_ds_filter(table_name, opts)
|
|
366
367
|
filt = super
|
|
367
368
|
# Restrict it to the given or public schema, unless specifically requesting :schema = nil
|
|
368
|
-
filt = SQL::
|
|
369
|
+
filt = SQL::BooleanExpression.new(:AND, filt, {:c__table_schema=>opts[:schema] || 'public'}) if opts[:schema] || !opts.include?(:schema)
|
|
369
370
|
filt
|
|
370
371
|
end
|
|
371
372
|
end
|
|
372
373
|
|
|
373
374
|
class Dataset < Sequel::Dataset
|
|
375
|
+
def quoted_identifier(c)
|
|
376
|
+
"\"#{c}\""
|
|
377
|
+
end
|
|
374
378
|
|
|
375
379
|
PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
|
|
376
380
|
|
|
@@ -193,6 +193,18 @@ module Sequel
|
|
|
193
193
|
end
|
|
194
194
|
end
|
|
195
195
|
|
|
196
|
+
def complex_expression_sql(op, args)
|
|
197
|
+
case op
|
|
198
|
+
when :~, :'!~', :'~*', :'!~*'
|
|
199
|
+
raise Error, "SQLite does not support pattern matching via regular expressions"
|
|
200
|
+
when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
|
|
201
|
+
# SQLite is case insensitive for ASCII, and non case sensitive for other character sets
|
|
202
|
+
"#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
|
|
203
|
+
else
|
|
204
|
+
super(op, args)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
196
208
|
def insert_sql(*values)
|
|
197
209
|
if (values.size == 1) && values.first.is_a?(Sequel::Dataset)
|
|
198
210
|
"INSERT INTO #{source_list(@opts[:from])} #{values.first.sql};"
|
data/lib/sequel_core/core_ext.rb
CHANGED
|
@@ -183,6 +183,16 @@ class String
|
|
|
183
183
|
end
|
|
184
184
|
end
|
|
185
185
|
|
|
186
|
+
# Converts a string into a Time or DateTime object, depending on the
|
|
187
|
+
# value of Sequel.time_class
|
|
188
|
+
def to_sequel_time
|
|
189
|
+
begin
|
|
190
|
+
Sequel.time_class.parse(self)
|
|
191
|
+
rescue => e
|
|
192
|
+
raise Sequel::Error::InvalidValue, "Invalid time value '#{self}' (#{e.message})"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
186
196
|
# Converts a string into a Time object.
|
|
187
197
|
def to_time
|
|
188
198
|
begin
|
data/lib/sequel_core/core_sql.rb
CHANGED
|
@@ -2,31 +2,31 @@
|
|
|
2
2
|
# code.
|
|
3
3
|
|
|
4
4
|
class Array
|
|
5
|
-
# Return a Sequel::SQL::
|
|
5
|
+
# Return a Sequel::SQL::BooleanExpression created from this array, not matching any of the
|
|
6
6
|
# conditions.
|
|
7
7
|
def ~
|
|
8
8
|
sql_expr_if_all_two_pairs(:OR, true)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
# Return a Sequel::SQL::
|
|
11
|
+
# Return a Sequel::SQL::BooleanExpression created from this array, matching all of the
|
|
12
12
|
# conditions.
|
|
13
13
|
def sql_expr
|
|
14
14
|
sql_expr_if_all_two_pairs
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
# Return a Sequel::SQL::
|
|
17
|
+
# Return a Sequel::SQL::BooleanExpression created from this array, matching none
|
|
18
18
|
# of the conditions.
|
|
19
19
|
def sql_negate
|
|
20
20
|
sql_expr_if_all_two_pairs(:AND, true)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
# Return a Sequel::SQL::
|
|
23
|
+
# Return a Sequel::SQL::BooleanExpression created from this array, matching any of the
|
|
24
24
|
# conditions.
|
|
25
25
|
def sql_or
|
|
26
26
|
sql_expr_if_all_two_pairs(:OR)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# Return a Sequel::SQL::
|
|
29
|
+
# Return a Sequel::SQL::BooleanExpression representing an SQL string made up of the
|
|
30
30
|
# concatenation of this array's elements. If an argument is passed
|
|
31
31
|
# it is used in between each element of the array in the SQL
|
|
32
32
|
# concatenation.
|
|
@@ -41,7 +41,7 @@ class Array
|
|
|
41
41
|
args = self
|
|
42
42
|
end
|
|
43
43
|
args = args.collect{|a| a.is_one_of?(Symbol, ::Sequel::SQL::Expression, ::Sequel::LiteralString, TrueClass, FalseClass, NilClass) ? a : a.to_s}
|
|
44
|
-
::Sequel::SQL::
|
|
44
|
+
::Sequel::SQL::StringExpression.new(:'||', *args)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# Concatenates an array of strings into an SQL string. ANSI SQL and C-style
|
|
@@ -53,50 +53,50 @@ class Array
|
|
|
53
53
|
|
|
54
54
|
private
|
|
55
55
|
|
|
56
|
-
# Raise an error if this array is not made up of all two pairs, otherwise create a Sequel::SQL::
|
|
56
|
+
# Raise an error if this array is not made up of all two pairs, otherwise create a Sequel::SQL::BooleanExpression from this array.
|
|
57
57
|
def sql_expr_if_all_two_pairs(*args)
|
|
58
58
|
raise(Sequel::Error, 'Not all elements of the array are arrays of size 2, so it cannot be converted to an SQL expression') unless all_two_pairs?
|
|
59
|
-
::Sequel::SQL::
|
|
59
|
+
::Sequel::SQL::BooleanExpression.from_value_pairs(self, *args)
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
class Hash
|
|
64
|
-
# Return a Sequel::SQL::
|
|
64
|
+
# Return a Sequel::SQL::BooleanExpression created from this hash, matching
|
|
65
65
|
# all of the conditions in this hash and the condition specified by
|
|
66
66
|
# the given argument.
|
|
67
67
|
def &(ce)
|
|
68
|
-
::Sequel::SQL::
|
|
68
|
+
::Sequel::SQL::BooleanExpression.new(:AND, self, ce)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
# Return a Sequel::SQL::
|
|
71
|
+
# Return a Sequel::SQL::BooleanExpression created from this hash, matching
|
|
72
72
|
# all of the conditions in this hash or the condition specified by
|
|
73
73
|
# the given argument.
|
|
74
74
|
def |(ce)
|
|
75
|
-
::Sequel::SQL::
|
|
75
|
+
::Sequel::SQL::BooleanExpression.new(:OR, self, ce)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
# Return a Sequel::SQL::
|
|
78
|
+
# Return a Sequel::SQL::BooleanExpression created from this hash, not matching any of the
|
|
79
79
|
# conditions.
|
|
80
80
|
def ~
|
|
81
|
-
::Sequel::SQL::
|
|
81
|
+
::Sequel::SQL::BooleanExpression.from_value_pairs(self, :OR, true)
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
# Return a Sequel::SQL::
|
|
84
|
+
# Return a Sequel::SQL::BooleanExpression created from this hash, matching all of the
|
|
85
85
|
# conditions.
|
|
86
86
|
def sql_expr
|
|
87
|
-
::Sequel::SQL::
|
|
87
|
+
::Sequel::SQL::BooleanExpression.from_value_pairs(self)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
# Return a Sequel::SQL::
|
|
90
|
+
# Return a Sequel::SQL::BooleanExpression created from this hash, matching none
|
|
91
91
|
# of the conditions.
|
|
92
92
|
def sql_negate
|
|
93
|
-
::Sequel::SQL::
|
|
93
|
+
::Sequel::SQL::BooleanExpression.from_value_pairs(self, :AND, true)
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
# Return a Sequel::SQL::
|
|
96
|
+
# Return a Sequel::SQL::BooleanExpression created from this hash, matching any of the
|
|
97
97
|
# conditions.
|
|
98
98
|
def sql_or
|
|
99
|
-
::Sequel::SQL::
|
|
99
|
+
::Sequel::SQL::BooleanExpression.from_value_pairs(self, :OR)
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
|
|
@@ -136,7 +136,7 @@ class Symbol
|
|
|
136
136
|
|
|
137
137
|
# If no argument is given, returns a Sequel::SQL::ColumnAll object specifying all
|
|
138
138
|
# columns for this table.
|
|
139
|
-
# If an argument is given, returns a Sequel::SQL::
|
|
139
|
+
# If an argument is given, returns a Sequel::SQL::NumericExpression using the *
|
|
140
140
|
# (multiplication) operator with this and the given argument.
|
|
141
141
|
def *(ce=(arg=false;nil))
|
|
142
142
|
return super(ce) unless arg == false
|
|
@@ -151,7 +151,7 @@ class Symbol
|
|
|
151
151
|
|
|
152
152
|
# If the given argument is an Integer or an array containing an Integer, returns
|
|
153
153
|
# a Sequel::SQL::Subscript with this column and the given arg.
|
|
154
|
-
# Otherwise returns a Sequel::SQL::
|
|
154
|
+
# Otherwise returns a Sequel::SQL::BooleanExpression where this column (which should be boolean)
|
|
155
155
|
# or the given argument is true.
|
|
156
156
|
def |(sub)
|
|
157
157
|
return super unless (Integer === sub) || ((Array === sub) && sub.any?{|x| Integer === x})
|
data/lib/sequel_core/database.rb
CHANGED
|
@@ -396,18 +396,24 @@ module Sequel
|
|
|
396
396
|
else
|
|
397
397
|
raise ArgumentError, "invalid value for Date: #{value.inspect}"
|
|
398
398
|
end
|
|
399
|
-
when :
|
|
399
|
+
when :time
|
|
400
400
|
case value
|
|
401
|
-
when DateTime
|
|
402
|
-
value
|
|
403
|
-
when Date
|
|
404
|
-
DateTime.new(value.year, value.month, value.day)
|
|
405
401
|
when Time
|
|
406
|
-
|
|
402
|
+
value
|
|
407
403
|
when String
|
|
408
|
-
value.
|
|
404
|
+
value.to_time
|
|
405
|
+
else
|
|
406
|
+
raise ArgumentError, "invalid value for Time: #{value.inspect}"
|
|
407
|
+
end
|
|
408
|
+
when :datetime
|
|
409
|
+
raise(ArgumentError, "invalid value for #{tc}: #{value.inspect}") unless value.is_one_of?(DateTime, Date, Time, String)
|
|
410
|
+
if Sequel.time_class === value
|
|
411
|
+
# Already the correct class, no need to convert
|
|
412
|
+
value
|
|
409
413
|
else
|
|
410
|
-
|
|
414
|
+
# First convert it to standard ISO 8601 time, then
|
|
415
|
+
# parse that string using the time class.
|
|
416
|
+
(Time === value ? value.iso8601 : value.to_s).to_sequel_time
|
|
411
417
|
end
|
|
412
418
|
else
|
|
413
419
|
value
|
|
@@ -52,6 +52,8 @@ module Sequel
|
|
|
52
52
|
"(#{args.collect{|a| literal(a)}.join(" #{op} ")})"
|
|
53
53
|
when :NOT
|
|
54
54
|
"NOT #{literal(args.at(0))}"
|
|
55
|
+
when :NOOP
|
|
56
|
+
literal(args.at(0))
|
|
55
57
|
else
|
|
56
58
|
raise(Sequel::Error, "invalid operator #{op}")
|
|
57
59
|
end
|
|
@@ -107,8 +109,8 @@ module Sequel
|
|
|
107
109
|
cond = cond.first if cond.size == 1
|
|
108
110
|
cond = cond.sql_or if (Hash === cond) || ((Array === cond) && (cond.all_two_pairs?))
|
|
109
111
|
cond = filter_expr(block || cond)
|
|
110
|
-
cond = SQL::
|
|
111
|
-
cond = SQL::
|
|
112
|
+
cond = SQL::BooleanExpression.invert(cond)
|
|
113
|
+
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause]
|
|
112
114
|
clone(clause => cond)
|
|
113
115
|
end
|
|
114
116
|
|
|
@@ -135,7 +137,7 @@ module Sequel
|
|
|
135
137
|
# specified.
|
|
136
138
|
# * String - taken literally
|
|
137
139
|
# * Symbol - taken as a boolean column argument (e.g. WHERE active)
|
|
138
|
-
# * Sequel::SQL::
|
|
140
|
+
# * Sequel::SQL::BooleanExpression - an existing condition expression,
|
|
139
141
|
# probably created using the Sequel blockless filter DSL.
|
|
140
142
|
#
|
|
141
143
|
# filter also takes a block, but use of this is discouraged as it requires
|
|
@@ -169,7 +171,7 @@ module Sequel
|
|
|
169
171
|
raise(Error::InvalidFilter, "Invalid filter specified. Did you mean to supply a block?") if cond === true || cond === false
|
|
170
172
|
cond = transform_save(cond) if @transform if cond.is_a?(Hash)
|
|
171
173
|
cond = filter_expr(block || cond)
|
|
172
|
-
cond = SQL::
|
|
174
|
+
cond = SQL::BooleanExpression.new(:AND, @opts[clause], cond) if @opts[clause] && !@opts[clause].blank?
|
|
173
175
|
clone(clause => cond)
|
|
174
176
|
end
|
|
175
177
|
alias_method :where, :filter
|
|
@@ -215,11 +217,11 @@ module Sequel
|
|
|
215
217
|
|
|
216
218
|
# Pattern match any of the columns to any of the terms. The terms can be
|
|
217
219
|
# strings (which use LIKE) or regular expressions (which are only supported
|
|
218
|
-
# in some databases). See Sequel::SQL::
|
|
220
|
+
# in some databases). See Sequel::SQL::StringExpression.like. Note that the
|
|
219
221
|
# total number of pattern matches will be cols.length * terms.length,
|
|
220
222
|
# which could cause performance issues.
|
|
221
223
|
def grep(cols, terms)
|
|
222
|
-
filter(SQL::
|
|
224
|
+
filter(SQL::BooleanExpression.new(:OR, *Array(cols).collect{|c| SQL::StringExpression.like(c, *terms)}))
|
|
223
225
|
end
|
|
224
226
|
|
|
225
227
|
# Returns a copy of the dataset with the results grouped by the value of
|
|
@@ -312,12 +314,8 @@ module Sequel
|
|
|
312
314
|
having, where = @opts[:having], @opts[:where]
|
|
313
315
|
raise(Error, "No current filter") unless having || where
|
|
314
316
|
o = {}
|
|
315
|
-
if having
|
|
316
|
-
|
|
317
|
-
end
|
|
318
|
-
if where
|
|
319
|
-
o[:where] = SQL::ComplexExpression === where ? ~where : SQL::ComplexExpression.new(:NOT, where)
|
|
320
|
-
end
|
|
317
|
+
o[:having] = SQL::BooleanExpression.invert(having) if having
|
|
318
|
+
o[:where] = SQL::BooleanExpression.invert(where) if where
|
|
321
319
|
clone(o)
|
|
322
320
|
end
|
|
323
321
|
|
|
@@ -451,7 +449,7 @@ module Sequel
|
|
|
451
449
|
clause = (@opts[:having] ? :having : :where)
|
|
452
450
|
cond = cond.first if cond.size == 1
|
|
453
451
|
if @opts[clause]
|
|
454
|
-
clone(clause => SQL::
|
|
452
|
+
clone(clause => SQL::BooleanExpression.new(:OR, @opts[clause], filter_expr(block || cond)))
|
|
455
453
|
else
|
|
456
454
|
raise Error::NoExistingFilter, "No existing filter found."
|
|
457
455
|
end
|
|
@@ -494,11 +492,17 @@ module Sequel
|
|
|
494
492
|
end
|
|
495
493
|
alias_method :quote_column_ref, :quote_identifier
|
|
496
494
|
|
|
497
|
-
# This method quotes the given name with the SQL standard double quote.
|
|
495
|
+
# This method quotes the given name with the SQL standard double quote.
|
|
496
|
+
# It uppercases the name given to conform with the SQL standard. This
|
|
498
497
|
# should be overridden by subclasses to provide quoting not matching the
|
|
499
|
-
# SQL standard, such as backtick (used by MySQL and SQLite)
|
|
498
|
+
# SQL standard, such as backtick (used by MySQL and SQLite), or where
|
|
499
|
+
# lowercase is the default for unquoted identifiers (PostgreSQL).
|
|
500
|
+
#
|
|
501
|
+
# If you are using a database such as Oracle that defaults to uppercase
|
|
502
|
+
# but you are using lower case identifiers, you should override this
|
|
503
|
+
# method to not upcase the name for those identifiers.
|
|
500
504
|
def quoted_identifier(name)
|
|
501
|
-
"\"#{name}\""
|
|
505
|
+
"\"#{name.to_s.upcase}\""
|
|
502
506
|
end
|
|
503
507
|
|
|
504
508
|
# Returns a copy of the dataset with the order reversed. If no order is
|
|
@@ -710,21 +714,23 @@ module Sequel
|
|
|
710
714
|
def filter_expr(expr)
|
|
711
715
|
case expr
|
|
712
716
|
when Hash
|
|
713
|
-
SQL::
|
|
717
|
+
SQL::BooleanExpression.from_value_pairs(expr)
|
|
714
718
|
when Array
|
|
715
719
|
if String === expr[0]
|
|
716
720
|
filter_expr(expr.shift.gsub(QUESTION_MARK){literal(expr.shift)}.lit)
|
|
717
721
|
else
|
|
718
|
-
SQL::
|
|
722
|
+
SQL::BooleanExpression.from_value_pairs(expr)
|
|
719
723
|
end
|
|
720
724
|
when Proc
|
|
721
725
|
expr.to_sql(self).lit
|
|
726
|
+
when SQL::NumericExpression, SQL::StringExpression
|
|
727
|
+
raise(Error, "Invalid SQL Expression type: #{expr.inspect}")
|
|
722
728
|
when Symbol, SQL::Expression
|
|
723
729
|
expr
|
|
724
730
|
when String
|
|
725
731
|
"(#{expr})".lit
|
|
726
732
|
else
|
|
727
|
-
raise(
|
|
733
|
+
raise(Error, 'Invalid filter argument')
|
|
728
734
|
end
|
|
729
735
|
end
|
|
730
736
|
|
|
@@ -231,10 +231,12 @@ module Sequel
|
|
|
231
231
|
:integer
|
|
232
232
|
when /\A(character( varying)?|varchar|text)\z/
|
|
233
233
|
:string
|
|
234
|
-
when /\
|
|
234
|
+
when /\Adate\z/
|
|
235
235
|
:date
|
|
236
|
-
when /\A(datetime|
|
|
236
|
+
when /\A(datetime|timestamp( with(out)? time zone)?)\z/
|
|
237
237
|
:datetime
|
|
238
|
+
when /\Atime( with(out)? time zone)?\z/
|
|
239
|
+
:time
|
|
238
240
|
when /\A(boolean|tinyint)\z/
|
|
239
241
|
:boolean
|
|
240
242
|
when /\A(real|float|double( precision)?)\z/
|
data/lib/sequel_core/sql.rb
CHANGED
|
@@ -6,11 +6,38 @@ module Sequel
|
|
|
6
6
|
# It also holds modules that are included in core ruby classes that
|
|
7
7
|
# make Sequel a friendly DSL.
|
|
8
8
|
module SQL
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
# Holds methods that should be called on columns only.
|
|
10
|
+
module ColumnMethods
|
|
11
|
+
AS = 'AS'.freeze
|
|
12
|
+
DESC = 'DESC'.freeze
|
|
13
|
+
ASC = 'ASC'.freeze
|
|
14
|
+
|
|
15
|
+
# Create an SQL column alias of the receiving column to the given alias.
|
|
16
|
+
def as(a)
|
|
17
|
+
ColumnExpr.new(self, AS, a)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Mark the receiving SQL column as sorting in a descending fashion.
|
|
21
|
+
def desc
|
|
22
|
+
ColumnExpr.new(self, DESC)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Mark the receiving SQL column as sorting in an ascending fashion (generally a no-op).
|
|
26
|
+
def asc
|
|
27
|
+
ColumnExpr.new(self, ASC)
|
|
28
|
+
end
|
|
11
29
|
|
|
30
|
+
# Cast the reciever to the given SQL type
|
|
31
|
+
def cast_as(t)
|
|
32
|
+
t = t.to_s.lit if t.is_a?(Symbol)
|
|
33
|
+
Sequel::SQL::Function.new(:cast, self.as(t))
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
12
37
|
# Base class for all SQL fragments
|
|
13
38
|
class Expression
|
|
39
|
+
include ColumnMethods
|
|
40
|
+
|
|
14
41
|
# Returns self, because SQL::Expression already acts like
|
|
15
42
|
# LiteralString.
|
|
16
43
|
def lit
|
|
@@ -67,52 +94,44 @@ module Sequel
|
|
|
67
94
|
# a tree). This class is the backbone of the blockless filter support in
|
|
68
95
|
# Sequel.
|
|
69
96
|
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
97
|
+
# This is an abstract class that is not that useful by itself. The
|
|
98
|
+
# subclasses BooleanExpression, NumericExpression, and StringExpression
|
|
99
|
+
# define the behavior of the DSL via operators.
|
|
73
100
|
class ComplexExpression < Expression
|
|
74
|
-
# A hash of the opposite for each operator symbol, used for inverting
|
|
101
|
+
# A hash of the opposite for each operator symbol, used for inverting
|
|
75
102
|
# objects.
|
|
76
103
|
OPERTATOR_INVERSIONS = {:AND => :OR, :OR => :AND, :< => :>=, :> => :<=,
|
|
77
104
|
:<= => :>, :>= => :<, :'=' => :'!=' , :'!=' => :'=', :LIKE => :'NOT LIKE',
|
|
78
105
|
:'NOT LIKE' => :LIKE, :~ => :'!~', :'!~' => :~, :IN => :'NOT IN',
|
|
79
106
|
:'NOT IN' => :IN, :IS => :'IS NOT', :'IS NOT' => :IS, :'~*' => :'!~*',
|
|
80
|
-
:'!~*' => :'~*'
|
|
107
|
+
:'!~*' => :'~*', :NOT => :NOOP, :NOOP => :NOT, :ILIKE => :'NOT ILIKE',
|
|
108
|
+
:'NOT ILIKE'=>:ILIKE}
|
|
81
109
|
|
|
110
|
+
# Mathematical Operators used in NumericMethods
|
|
82
111
|
MATHEMATICAL_OPERATORS = [:+, :-, :/, :*]
|
|
83
|
-
INEQUALITY_OPERATORS = [:<, :>, :<=, :>=]
|
|
84
|
-
STRING_OPERATORS = [:'||']
|
|
85
|
-
SEARCH_OPERATORS = [:LIKE, :'NOT LIKE', :~, :'!~', :'~*', :'!~*']
|
|
86
|
-
INCLUSION_OPERATORS = [:IN, :'NOT IN']
|
|
87
|
-
BOOLEAN_OPERATORS = [:AND, :OR]
|
|
88
|
-
|
|
89
|
-
# Collection of all equality/inequality operator symbols
|
|
90
|
-
EQUALITY_OPERATORS = [:'=', :'!=', :IS, :'IS NOT', *INEQUALITY_OPERATORS]
|
|
91
|
-
|
|
92
|
-
# Operator symbols that do not work on boolean SQL input
|
|
93
|
-
NO_BOOLEAN_INPUT_OPERATORS = MATHEMATICAL_OPERATORS + INEQUALITY_OPERATORS + STRING_OPERATORS
|
|
94
112
|
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
# Literal SQL booleans that are not allowed
|
|
99
|
-
BOOLEAN_LITERALS = [true, false, nil]
|
|
113
|
+
# Inequality Operators used in InequalityMethods
|
|
114
|
+
INEQUALITY_OPERATORS = [:<, :>, :<=, :>=]
|
|
100
115
|
|
|
101
|
-
# Hash of ruby operator symbols to SQL operators, used
|
|
116
|
+
# Hash of ruby operator symbols to SQL operators, used in BooleanMethods
|
|
102
117
|
BOOLEAN_OPERATOR_METHODS = {:& => :AND, :| =>:OR}
|
|
103
118
|
|
|
104
119
|
# Operator symbols that take exactly two arguments
|
|
105
|
-
TWO_ARITY_OPERATORS =
|
|
120
|
+
TWO_ARITY_OPERATORS = [:'=', :'!=', :IS, :'IS NOT', :LIKE, :'NOT LIKE', \
|
|
121
|
+
:~, :'!~', :'~*', :'!~*', :IN, :'NOT IN', :ILIKE, :'NOT ILIKE'] + INEQUALITY_OPERATORS
|
|
106
122
|
|
|
107
123
|
# Operator symbols that take one or more arguments
|
|
108
|
-
N_ARITY_OPERATORS =
|
|
124
|
+
N_ARITY_OPERATORS = [:AND, :OR, :'||'] + MATHEMATICAL_OPERATORS
|
|
125
|
+
|
|
126
|
+
# Operator symbols that take one argument
|
|
127
|
+
ONE_ARITY_OPERATORS = [:NOT, :NOOP]
|
|
109
128
|
|
|
110
129
|
# An array of args for this object
|
|
111
130
|
attr_reader :args
|
|
112
131
|
|
|
113
132
|
# The operator symbol for this object
|
|
114
133
|
attr_reader :op
|
|
115
|
-
|
|
134
|
+
|
|
116
135
|
# Set the operator symbol and arguments for this object to the ones given.
|
|
117
136
|
# Convert all args that are hashes or arrays with all two pairs to ComplexExpressions.
|
|
118
137
|
# Raise an error if the operator doesn't allow boolean input and a boolean argument is given.
|
|
@@ -128,31 +147,184 @@ module Sequel
|
|
|
128
147
|
a
|
|
129
148
|
end
|
|
130
149
|
end
|
|
131
|
-
if NO_BOOLEAN_INPUT_OPERATORS.include?(op)
|
|
132
|
-
args.each do |a|
|
|
133
|
-
if BOOLEAN_LITERALS.include?(a) || ((ComplexExpression === a) && BOOLEAN_RESULT_OPERATORS.include?(a.op))
|
|
134
|
-
raise(Sequel::Error, "cannot apply #{op} to a boolean expression")
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
150
|
case op
|
|
139
151
|
when *N_ARITY_OPERATORS
|
|
140
|
-
raise(
|
|
152
|
+
raise(Error, "The #{op} operator requires at least 1 argument") unless args.length >= 1
|
|
141
153
|
when *TWO_ARITY_OPERATORS
|
|
142
|
-
raise(
|
|
143
|
-
when
|
|
144
|
-
raise(
|
|
154
|
+
raise(Error, "The #{op} operator requires precisely 2 arguments") unless args.length == 2
|
|
155
|
+
when *ONE_ARITY_OPERATORS
|
|
156
|
+
raise(Error, "The #{op} operator requires a single argument") unless args.length == 1
|
|
145
157
|
else
|
|
146
|
-
raise(
|
|
158
|
+
raise(Error, "Invalid operator #{op}")
|
|
147
159
|
end
|
|
148
160
|
@op = op
|
|
149
161
|
@args = args
|
|
150
162
|
end
|
|
163
|
+
|
|
164
|
+
# Delegate the creation of the resulting SQL to the given dataset,
|
|
165
|
+
# since it may be database dependent.
|
|
166
|
+
def to_s(ds)
|
|
167
|
+
ds.complex_expression_sql(@op, @args)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# This module includes the methods that are defined on objects that can be
|
|
172
|
+
# used in a boolean context in SQL (Symbol, LiteralString, SQL::Function,
|
|
173
|
+
# and SQL::BooleanExpression).
|
|
174
|
+
#
|
|
175
|
+
# This defines the ~ (NOT), & (AND), and | (OR) methods.
|
|
176
|
+
module BooleanMethods
|
|
177
|
+
# Create a new BooleanExpression with NOT, representing the inversion of whatever self represents.
|
|
178
|
+
def ~
|
|
179
|
+
BooleanExpression.invert(self)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
ComplexExpression::BOOLEAN_OPERATOR_METHODS.each do |m, o|
|
|
183
|
+
define_method(m) do |ce|
|
|
184
|
+
case ce
|
|
185
|
+
when BooleanExpression
|
|
186
|
+
BooleanExpression.new(o, self, ce)
|
|
187
|
+
when ComplexExpression
|
|
188
|
+
raise(Sequel::Error, "cannot apply #{o} to a non-boolean expression")
|
|
189
|
+
else
|
|
190
|
+
BooleanExpression.new(o, self, ce)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# This module includes the methods that are defined on objects that can be
|
|
197
|
+
# used in a numeric context in SQL (Symbol, LiteralString, SQL::Function,
|
|
198
|
+
# and SQL::NumericExpression).
|
|
199
|
+
#
|
|
200
|
+
# This defines the +, -, *, and / methods.
|
|
201
|
+
module NumericMethods
|
|
202
|
+
ComplexExpression::MATHEMATICAL_OPERATORS.each do |o|
|
|
203
|
+
define_method(o) do |ce|
|
|
204
|
+
case ce
|
|
205
|
+
when NumericExpression
|
|
206
|
+
NumericExpression.new(o, self, ce)
|
|
207
|
+
when ComplexExpression
|
|
208
|
+
raise(Sequel::Error, "cannot apply #{o} to a non-numeric expression")
|
|
209
|
+
else
|
|
210
|
+
NumericExpression.new(o, self, ce)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# This module includes the methods that are defined on objects that can be
|
|
217
|
+
# used in a numeric context in SQL (Symbol, LiteralString, SQL::Function,
|
|
218
|
+
# and SQL::StringExpression).
|
|
219
|
+
#
|
|
220
|
+
# This defines the like (LIKE) method, used for pattern matching.
|
|
221
|
+
module StringMethods
|
|
222
|
+
# Create a BooleanExpression case insensitive pattern match of self
|
|
223
|
+
# with the given patterns. See StringExpression.like.
|
|
224
|
+
def ilike(*ces)
|
|
225
|
+
StringExpression.like(self, *(ces << {:case_insensitive=>true}))
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Create a BooleanExpression case sensitive pattern match of self with
|
|
229
|
+
# the given patterns. See StringExpression.like.
|
|
230
|
+
def like(*ces)
|
|
231
|
+
StringExpression.like(self, *ces)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# This module is included in StringExpression and can be included elsewhere
|
|
236
|
+
# to allow the use of the + operator to represent concatenation of SQL
|
|
237
|
+
# Strings:
|
|
238
|
+
#
|
|
239
|
+
# :x.sql_string + :y => # SQL: x || y
|
|
240
|
+
module StringConcatenationMethods
|
|
241
|
+
def +(ce)
|
|
242
|
+
StringExpression.new(:'||', self, ce)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# This module includes the methods that are defined on objects that can be
|
|
247
|
+
# used in a numeric or string context in SQL (Symbol, LiteralString,
|
|
248
|
+
# SQL::Function, and SQL::StringExpression).
|
|
249
|
+
#
|
|
250
|
+
# This defines the >, <, >=, and <= methods.
|
|
251
|
+
module InequalityMethods
|
|
252
|
+
ComplexExpression::INEQUALITY_OPERATORS.each do |o|
|
|
253
|
+
define_method(o) do |ce|
|
|
254
|
+
case ce
|
|
255
|
+
when BooleanExpression, TrueClass, FalseClass, NilClass, Hash, Array
|
|
256
|
+
raise(Error, "cannot apply #{o} to a boolean expression")
|
|
257
|
+
else
|
|
258
|
+
BooleanExpression.new(o, self, ce)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# This module augments the default initalize method for the
|
|
265
|
+
# ComplexExpression subclass it is included in, so that
|
|
266
|
+
# attempting to use boolean input when initializing a NumericExpression
|
|
267
|
+
# or StringExpression results in an error.
|
|
268
|
+
module NoBooleanInputMethods
|
|
269
|
+
# Raise an Error if one of the args would be boolean in an SQL
|
|
270
|
+
# context, otherwise call super.
|
|
271
|
+
def initialize(op, *args)
|
|
272
|
+
args.each do |a|
|
|
273
|
+
case a
|
|
274
|
+
when BooleanExpression, TrueClass, FalseClass, NilClass, Hash, Array
|
|
275
|
+
raise(Error, "cannot apply #{op} to a boolean expression")
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
super
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# This module includes other Sequel::SQL::*Methods modules and is
|
|
283
|
+
# included in other classes that are could be either booleans,
|
|
284
|
+
# strings, or numbers. It also adds three methods so that
|
|
285
|
+
# can specify behavior in case one of the operator methods has
|
|
286
|
+
# been overridden (such as Symbol#/).
|
|
287
|
+
#
|
|
288
|
+
# For example, if Symbol#/ is overridden to produce a string (for
|
|
289
|
+
# example, to make file system path creation easier), the
|
|
290
|
+
# following code will not do what you want:
|
|
291
|
+
#
|
|
292
|
+
# :price/10 > 100
|
|
293
|
+
#
|
|
294
|
+
# In that case, you need to do the following:
|
|
295
|
+
#
|
|
296
|
+
# :price.sql_number/10 > 100
|
|
297
|
+
module ComplexExpressionMethods
|
|
298
|
+
include BooleanMethods
|
|
299
|
+
include NumericMethods
|
|
300
|
+
include StringMethods
|
|
301
|
+
include InequalityMethods
|
|
302
|
+
|
|
303
|
+
# Return a BooleanExpression representation of self.
|
|
304
|
+
def sql_boolean
|
|
305
|
+
BooleanExpression.new(:NOOP, self)
|
|
306
|
+
end
|
|
151
307
|
|
|
308
|
+
# Return a NumericExpression representation of self.
|
|
309
|
+
def sql_number
|
|
310
|
+
NumericExpression.new(:NOOP, self)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Return a StringExpression representation of self.
|
|
314
|
+
def sql_string
|
|
315
|
+
StringExpression.new(:NOOP, self)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Subclass of ComplexExpression where the expression results
|
|
320
|
+
# in a boolean value in SQL.
|
|
321
|
+
class BooleanExpression < ComplexExpression
|
|
322
|
+
include BooleanMethods
|
|
323
|
+
|
|
152
324
|
# Take pairs of values (e.g. a hash or array of arrays of two pairs)
|
|
153
325
|
# and converts it to a ComplexExpression. The operator and args
|
|
154
326
|
# used depends on the case of the right (2nd) argument:
|
|
155
|
-
#
|
|
327
|
+
#
|
|
156
328
|
# * 0..10 - left >= 0 AND left <= 10
|
|
157
329
|
# * [1,2] - left IN (1,2)
|
|
158
330
|
# * nil - left IS NULL
|
|
@@ -177,15 +349,51 @@ module Sequel
|
|
|
177
349
|
when NilClass
|
|
178
350
|
new(:IS, l, r)
|
|
179
351
|
when Regexp
|
|
180
|
-
like(l, r)
|
|
352
|
+
StringExpression.like(l, r)
|
|
181
353
|
else
|
|
182
354
|
new(:'=', l, r)
|
|
183
355
|
end
|
|
184
|
-
negate ?
|
|
356
|
+
negate ? invert(ce) : ce
|
|
185
357
|
end
|
|
186
358
|
pairs.length == 1 ? pairs.at(0) : new(op, *pairs)
|
|
187
359
|
end
|
|
360
|
+
|
|
361
|
+
# Invert the expression, if possible. If the expression cannot
|
|
362
|
+
# be inverted, raise an error. An inverted expression should match everything that the
|
|
363
|
+
# uninverted expression did not match, and vice-versa.
|
|
364
|
+
def self.invert(ce)
|
|
365
|
+
case ce
|
|
366
|
+
when BooleanExpression
|
|
367
|
+
case op = ce.op
|
|
368
|
+
when :AND, :OR
|
|
369
|
+
BooleanExpression.new(OPERTATOR_INVERSIONS[op], *ce.args.collect{|a| BooleanExpression.invert(a)})
|
|
370
|
+
else
|
|
371
|
+
BooleanExpression.new(OPERTATOR_INVERSIONS[op], *ce.args.dup)
|
|
372
|
+
end
|
|
373
|
+
when ComplexExpression
|
|
374
|
+
raise(Sequel::Error, "operator #{ce.op} cannot be inverted")
|
|
375
|
+
else
|
|
376
|
+
BooleanExpression.new(:NOT, ce)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Subclass of ComplexExpression where the expression results
|
|
382
|
+
# in a numeric value in SQL.
|
|
383
|
+
class NumericExpression < ComplexExpression
|
|
384
|
+
include NumericMethods
|
|
385
|
+
include InequalityMethods
|
|
386
|
+
include NoBooleanInputMethods
|
|
387
|
+
end
|
|
188
388
|
|
|
389
|
+
# Subclass of ComplexExpression where the expression results
|
|
390
|
+
# in a text/string/varchar value in SQL.
|
|
391
|
+
class StringExpression < ComplexExpression
|
|
392
|
+
include StringMethods
|
|
393
|
+
include StringConcatenationMethods
|
|
394
|
+
include InequalityMethods
|
|
395
|
+
include NoBooleanInputMethods
|
|
396
|
+
|
|
189
397
|
# Creates a SQL pattern match exprssion. left (l) is the SQL string we
|
|
190
398
|
# are matching against, and ces are the patterns we are matching.
|
|
191
399
|
# The match succeeds if any of the patterns match (SQL OR). Patterns
|
|
@@ -193,55 +401,26 @@ module Sequel
|
|
|
193
401
|
# the SQL LIKE operator to be used, and should be supported by most
|
|
194
402
|
# databases. Regular expressions will probably only work on MySQL
|
|
195
403
|
# and PostgreSQL, and SQL regular expression syntax is not fully compatible
|
|
196
|
-
# with ruby regular expression syntax, so be careful if using regular
|
|
404
|
+
# with ruby regular expression syntax, so be careful if using regular
|
|
197
405
|
# expressions.
|
|
406
|
+
#
|
|
407
|
+
# The pattern match will be case insensitive if the last argument is a hash
|
|
408
|
+
# with a key of :case_insensitive that is not false or nil. Also,
|
|
409
|
+
# if a case insensitive regular expression is used (//i), that particular
|
|
410
|
+
# pattern which will always be case insensitive.
|
|
198
411
|
def self.like(l, *ces)
|
|
199
|
-
ces.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
ces.length == 1 ? ces.at(0) : new(:OR, *ces)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Invert the regular expression, if possible. If the expression cannot
|
|
207
|
-
# be inverted, raise an error. An inverted expression should match everything that the
|
|
208
|
-
# uninverted expression did not match, and vice-versa.
|
|
209
|
-
def ~
|
|
210
|
-
case @op
|
|
211
|
-
when *BOOLEAN_OPERATORS
|
|
212
|
-
self.class.new(OPERTATOR_INVERSIONS[@op], *@args.collect{|a| ComplexExpression === a ? ~a : ComplexExpression.new(:NOT, a)})
|
|
213
|
-
when *TWO_ARITY_OPERATORS
|
|
214
|
-
self.class.new(OPERTATOR_INVERSIONS[@op], *@args.dup)
|
|
215
|
-
when :NOT
|
|
216
|
-
@args.first
|
|
217
|
-
else
|
|
218
|
-
raise(Sequel::Error, "operator #{@op} cannot be inverted")
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# Delegate the creation of the resulting SQL to the given dataset,
|
|
223
|
-
# since it may be database dependent.
|
|
224
|
-
def to_s(ds)
|
|
225
|
-
ds.complex_expression_sql(@op, @args)
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
BOOLEAN_OPERATOR_METHODS.each do |m, o|
|
|
229
|
-
define_method(m) do |ce|
|
|
230
|
-
raise(Sequel::Error, "cannot apply #{o} to a non-boolean expression") unless BOOLEAN_RESULT_OPERATORS.include?(@op)
|
|
231
|
-
super
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
(MATHEMATICAL_OPERATORS + INEQUALITY_OPERATORS).each do |o|
|
|
236
|
-
define_method(o) do |ce|
|
|
237
|
-
raise(Sequel::Error, "cannot apply #{o} to a boolean expression") unless NO_BOOLEAN_INPUT_OPERATORS.include?(@op)
|
|
238
|
-
super
|
|
412
|
+
case_insensitive = ces.extract_options![:case_insensitive]
|
|
413
|
+
ces.collect! do |ce|
|
|
414
|
+
op, expr = Regexp === ce ? [ce.casefold? || case_insensitive ? :'~*' : :~, ce.source] : [case_insensitive ? :ILIKE : :LIKE, ce.to_s]
|
|
415
|
+
BooleanExpression.new(op, l, expr)
|
|
239
416
|
end
|
|
417
|
+
ces.length == 1 ? ces.at(0) : BooleanExpression.new(:OR, *ces)
|
|
240
418
|
end
|
|
241
419
|
end
|
|
242
420
|
|
|
243
421
|
# Represents an SQL function call.
|
|
244
422
|
class Function < Expression
|
|
423
|
+
include ComplexExpressionMethods
|
|
245
424
|
# The array of arguments to pass to the function (may be blank)
|
|
246
425
|
attr_reader :args
|
|
247
426
|
|
|
@@ -309,78 +488,6 @@ module Sequel
|
|
|
309
488
|
ds.subscript_sql(self)
|
|
310
489
|
end
|
|
311
490
|
end
|
|
312
|
-
|
|
313
|
-
### Modules ###
|
|
314
|
-
|
|
315
|
-
# Module included in core classes giving them a simple and easy DSL
|
|
316
|
-
# for creation of ComplexExpressions.
|
|
317
|
-
#
|
|
318
|
-
# Most ruby operators methods are defined via metaprogramming: +, -, /, *, <, >, <=,
|
|
319
|
-
# >=, & (AND), | (OR).
|
|
320
|
-
module ComplexExpressionMethods
|
|
321
|
-
NO_BOOLEAN_INPUT_OPERATORS = ComplexExpression::NO_BOOLEAN_INPUT_OPERATORS
|
|
322
|
-
BOOLEAN_RESULT_OPERATORS = ComplexExpression::BOOLEAN_RESULT_OPERATORS
|
|
323
|
-
BOOLEAN_OPERATOR_METHODS = ComplexExpression::BOOLEAN_OPERATOR_METHODS
|
|
324
|
-
|
|
325
|
-
BOOLEAN_OPERATOR_METHODS.each do |m, o|
|
|
326
|
-
define_method(m) do |ce|
|
|
327
|
-
raise(Sequel::Error, "cannot apply #{o} to a non-boolean expression") if (ComplexExpression === ce) && !BOOLEAN_RESULT_OPERATORS.include?(ce.op)
|
|
328
|
-
ComplexExpression.new(o, self, ce)
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
(ComplexExpression::MATHEMATICAL_OPERATORS + ComplexExpression::INEQUALITY_OPERATORS).each do |o|
|
|
333
|
-
define_method(o) do |ce|
|
|
334
|
-
raise(Sequel::Error, "cannot apply #{o} to a boolean expression") if (ComplexExpression === ce) && !NO_BOOLEAN_INPUT_OPERATORS.include?(ce.op)
|
|
335
|
-
ComplexExpression.new(o, self, ce)
|
|
336
|
-
end
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
# Create a new ComplexExpression with NOT, representing the inversion of whatever self represents.
|
|
340
|
-
def ~
|
|
341
|
-
ComplexExpression.new(:NOT, self)
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
# Create a ComplexExpression pattern match of self with the given patterns.
|
|
345
|
-
def like(*ces)
|
|
346
|
-
ComplexExpression.like(self, *ces)
|
|
347
|
-
end
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
# Holds methods that should be called on columns only.
|
|
351
|
-
module ColumnMethods
|
|
352
|
-
AS = 'AS'.freeze
|
|
353
|
-
DESC = 'DESC'.freeze
|
|
354
|
-
ASC = 'ASC'.freeze
|
|
355
|
-
|
|
356
|
-
# Create an SQL column alias of the receiving column to the given alias.
|
|
357
|
-
def as(a)
|
|
358
|
-
ColumnExpr.new(self, AS, a)
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
# Mark the receiving SQL column as sorting in a descending fashion.
|
|
362
|
-
def desc
|
|
363
|
-
ColumnExpr.new(self, DESC)
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
# Mark the receiving SQL column as sorting in an ascending fashion (generally a no-op).
|
|
367
|
-
def asc
|
|
368
|
-
ColumnExpr.new(self, ASC)
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
# Cast the reciever to the given SQL type
|
|
372
|
-
def cast_as(t)
|
|
373
|
-
t = t.to_s.lit if t.is_a?(Symbol)
|
|
374
|
-
Sequel::SQL::Function.new(:cast, self.as(t))
|
|
375
|
-
end
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
class Expression
|
|
379
|
-
# Include the modules in Expression, couldn't be done
|
|
380
|
-
# earlier due to cyclic dependencies.
|
|
381
|
-
include ColumnMethods
|
|
382
|
-
include ComplexExpressionMethods
|
|
383
|
-
end
|
|
384
491
|
end
|
|
385
492
|
|
|
386
493
|
# LiteralString is used to represent literal SQL expressions. An
|
data/lib/sequel_core.rb
CHANGED
|
@@ -21,7 +21,15 @@ end
|
|
|
21
21
|
# object, which is closed when the block exits. For example:
|
|
22
22
|
#
|
|
23
23
|
# Sequel.sqlite('blog.db'){|db| puts db.users.count}
|
|
24
|
+
#
|
|
25
|
+
# Sequel can use either Time or DateTime for times returned from the
|
|
26
|
+
# database. It defaults to Time. To change it to DateTime, use:
|
|
27
|
+
#
|
|
28
|
+
# Sequel.time_class = DateTime
|
|
24
29
|
module Sequel
|
|
30
|
+
@time_class = Time
|
|
31
|
+
metaattr_accessor :time_class
|
|
32
|
+
|
|
25
33
|
# Creates a new database object based on the supplied connection string
|
|
26
34
|
# and optional arguments. The specified scheme determines the database
|
|
27
35
|
# class used, and the rest of the string specifies the connection options.
|
data/spec/adapters/mysql_spec.rb
CHANGED
|
@@ -713,14 +713,15 @@ context "MySQL::Dataset#complex_expression_sql" do
|
|
|
713
713
|
@d = MYSQL_DB.dataset
|
|
714
714
|
end
|
|
715
715
|
|
|
716
|
-
specify "should handle
|
|
717
|
-
@d.literal(:x.like(
|
|
718
|
-
@d.literal(~:x.like(
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
specify "should handle case sensitive regular expressions with REGEXP BINARY" do
|
|
716
|
+
specify "should handle pattern matches correctly" do
|
|
717
|
+
@d.literal(:x.like('a')).should == "(x LIKE BINARY 'a')"
|
|
718
|
+
@d.literal(~:x.like('a')).should == "(x NOT LIKE BINARY 'a')"
|
|
719
|
+
@d.literal(:x.ilike('a')).should == "(x LIKE 'a')"
|
|
720
|
+
@d.literal(~:x.ilike('a')).should == "(x NOT LIKE 'a')"
|
|
722
721
|
@d.literal(:x.like(/a/)).should == "(x REGEXP BINARY 'a')"
|
|
723
|
-
@d.literal(~:x.like(/a/)).should == "
|
|
722
|
+
@d.literal(~:x.like(/a/)).should == "(x NOT REGEXP BINARY 'a')"
|
|
723
|
+
@d.literal(:x.like(/a/i)).should == "(x REGEXP 'a')"
|
|
724
|
+
@d.literal(~:x.like(/a/i)).should == "(x NOT REGEXP 'a')"
|
|
724
725
|
end
|
|
725
726
|
|
|
726
727
|
specify "should handle string concatenation with CONCAT if more than one record" do
|
|
@@ -256,6 +256,20 @@ context "An SQLite dataset" do
|
|
|
256
256
|
@d.count.should == 1
|
|
257
257
|
@d.first[:name].should == 'def'
|
|
258
258
|
end
|
|
259
|
+
|
|
260
|
+
specify "should handle string pattern matches correctly" do
|
|
261
|
+
@d.literal(:x.like('a')).should == "(x LIKE 'a')"
|
|
262
|
+
@d.literal(~:x.like('a')).should == "NOT (x LIKE 'a')"
|
|
263
|
+
@d.literal(:x.ilike('a')).should == "(x LIKE 'a')"
|
|
264
|
+
@d.literal(~:x.ilike('a')).should == "NOT (x LIKE 'a')"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
specify "should raise errors if given a regexp pattern match" do
|
|
268
|
+
proc{@d.literal(:x.like(/a/))}.should raise_error(Sequel::Error)
|
|
269
|
+
proc{@d.literal(~:x.like(/a/))}.should raise_error(Sequel::Error)
|
|
270
|
+
proc{@d.literal(:x.like(/a/i))}.should raise_error(Sequel::Error)
|
|
271
|
+
proc{@d.literal(~:x.like(/a/i))}.should raise_error(Sequel::Error)
|
|
272
|
+
end
|
|
259
273
|
end
|
|
260
274
|
|
|
261
275
|
context "An SQLite dataset" do
|
|
@@ -356,38 +370,32 @@ context "SQLite dataset" do
|
|
|
356
370
|
end
|
|
357
371
|
end
|
|
358
372
|
|
|
359
|
-
__END__
|
|
360
|
-
|
|
361
373
|
context "A SQLite database" do
|
|
362
374
|
setup do
|
|
363
375
|
@db = SQLITE_DB
|
|
376
|
+
@db.create_table! :test2 do
|
|
377
|
+
text :name
|
|
378
|
+
integer :value
|
|
379
|
+
end
|
|
364
380
|
end
|
|
365
381
|
|
|
366
382
|
specify "should support add_column operations" do
|
|
367
383
|
@db.add_column :test2, :xyz, :text
|
|
368
384
|
|
|
369
385
|
@db[:test2].columns.should == [:name, :value, :xyz]
|
|
370
|
-
@db[:test2] << {:name => 'mmm', :value => 111}
|
|
371
|
-
@db[:test2].first
|
|
386
|
+
@db[:test2] << {:name => 'mmm', :value => 111, :xyz=>'000'}
|
|
387
|
+
@db[:test2].first.should == {:name => 'mmm', :value => 111, :xyz=>'000'}
|
|
372
388
|
end
|
|
373
389
|
|
|
374
390
|
specify "should not support drop_column operations" do
|
|
375
|
-
proc {@db.drop_column :test2, :
|
|
391
|
+
proc {@db.drop_column :test2, :value}.should raise_error(Sequel::Error)
|
|
376
392
|
end
|
|
377
393
|
|
|
378
394
|
specify "should not support rename_column operations" do
|
|
379
|
-
@db
|
|
380
|
-
@db.add_column :test2, :xyz, :text, :default => '000'
|
|
381
|
-
@db[:test2] << {:name => 'mmm', :value => 111, :xyz => 'qqqq'}
|
|
382
|
-
|
|
383
|
-
@db[:test2].columns.should == [:name, :value, :xyz]
|
|
384
|
-
proc {@db.rename_column :test2, :xyz, :zyx}.should raise_error(Sequel::Error)
|
|
395
|
+
proc {@db.rename_column :test2, :value, :zyx}.should raise_error(Sequel::Error)
|
|
385
396
|
end
|
|
386
397
|
|
|
387
398
|
specify "should not support set_column_type operations" do
|
|
388
|
-
@db.
|
|
389
|
-
@db[:test2].delete
|
|
390
|
-
@db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
|
|
391
|
-
proc {@db.set_column_type :test2, :xyz, :integer}.should raise_error(Sequel::Error)
|
|
399
|
+
proc {@db.set_column_type :test2, :value, :integer}.should raise_error(Sequel::Error)
|
|
392
400
|
end
|
|
393
401
|
end
|
|
@@ -8,6 +8,9 @@ context "Blockless Ruby Filters" do
|
|
|
8
8
|
def @d.l(*args)
|
|
9
9
|
literal(filter_expr(*args))
|
|
10
10
|
end
|
|
11
|
+
def @d.lit(*args)
|
|
12
|
+
literal(*args)
|
|
13
|
+
end
|
|
11
14
|
end
|
|
12
15
|
|
|
13
16
|
it "should support boolean columns directly" do
|
|
@@ -81,7 +84,7 @@ context "Blockless Ruby Filters" do
|
|
|
81
84
|
@d.l(:x.like('a')).should == '(x LIKE \'a\')'
|
|
82
85
|
@d.l(:x.like(/a/)).should == '(x ~ \'a\')'
|
|
83
86
|
@d.l(:x.like('a', 'b')).should == '((x LIKE \'a\') OR (x LIKE \'b\'))'
|
|
84
|
-
@d.l(:x.like(/a/, /b/)).should == '((x ~ \'a\') OR (x
|
|
87
|
+
@d.l(:x.like(/a/, /b/i)).should == '((x ~ \'a\') OR (x ~* \'b\'))'
|
|
85
88
|
@d.l(:x.like('a', /b/)).should == '((x LIKE \'a\') OR (x ~ \'b\'))'
|
|
86
89
|
end
|
|
87
90
|
|
|
@@ -89,10 +92,26 @@ context "Blockless Ruby Filters" do
|
|
|
89
92
|
@d.l(~:x.like('a')).should == '(x NOT LIKE \'a\')'
|
|
90
93
|
@d.l(~:x.like(/a/)).should == '(x !~ \'a\')'
|
|
91
94
|
@d.l(~:x.like('a', 'b')).should == '((x NOT LIKE \'a\') AND (x NOT LIKE \'b\'))'
|
|
92
|
-
@d.l(~:x.like(/a/, /b/)).should == '((x !~ \'a\') AND (x
|
|
95
|
+
@d.l(~:x.like(/a/, /b/i)).should == '((x !~ \'a\') AND (x !~* \'b\'))'
|
|
93
96
|
@d.l(~:x.like('a', /b/)).should == '((x NOT LIKE \'a\') AND (x !~ \'b\'))'
|
|
94
97
|
end
|
|
95
98
|
|
|
99
|
+
it "should support ILIKE via Symbol#ilike" do
|
|
100
|
+
@d.l(:x.ilike('a')).should == '(x ILIKE \'a\')'
|
|
101
|
+
@d.l(:x.ilike(/a/)).should == '(x ~* \'a\')'
|
|
102
|
+
@d.l(:x.ilike('a', 'b')).should == '((x ILIKE \'a\') OR (x ILIKE \'b\'))'
|
|
103
|
+
@d.l(:x.ilike(/a/, /b/i)).should == '((x ~* \'a\') OR (x ~* \'b\'))'
|
|
104
|
+
@d.l(:x.ilike('a', /b/)).should == '((x ILIKE \'a\') OR (x ~* \'b\'))'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "should support NOT ILIKE via Symbol#ilike and Symbol#~" do
|
|
108
|
+
@d.l(~:x.ilike('a')).should == '(x NOT ILIKE \'a\')'
|
|
109
|
+
@d.l(~:x.ilike(/a/)).should == '(x !~* \'a\')'
|
|
110
|
+
@d.l(~:x.ilike('a', 'b')).should == '((x NOT ILIKE \'a\') AND (x NOT ILIKE \'b\'))'
|
|
111
|
+
@d.l(~:x.ilike(/a/, /b/i)).should == '((x !~* \'a\') AND (x !~* \'b\'))'
|
|
112
|
+
@d.l(~:x.ilike('a', /b/)).should == '((x NOT ILIKE \'a\') AND (x !~* \'b\'))'
|
|
113
|
+
end
|
|
114
|
+
|
|
96
115
|
it "should support negating ranges via Hash#~ and Range" do
|
|
97
116
|
@d.l(~{:x => 1..5}).should == '((x < 1) OR (x > 5))'
|
|
98
117
|
@d.l(~{:x => 1...5}).should == '((x < 1) OR (x >= 5))'
|
|
@@ -113,7 +132,10 @@ context "Blockless Ruby Filters" do
|
|
|
113
132
|
|
|
114
133
|
it "should not allow negation of non-boolean expressions" do
|
|
115
134
|
proc{~(:x + 1 > 100)}.should_not raise_error
|
|
116
|
-
proc{~(:x + 1)}.should raise_error
|
|
135
|
+
proc{~(:x + 1)}.should raise_error
|
|
136
|
+
proc{~:x.sql_string}.should raise_error
|
|
137
|
+
proc{~:x.sql_number}.should raise_error
|
|
138
|
+
proc{~([:x, :y].sql_string_join)}.should raise_error
|
|
117
139
|
end
|
|
118
140
|
|
|
119
141
|
it "should not allow mathematical, inequality, or string operations on true, false, or nil" do
|
|
@@ -189,7 +211,7 @@ context "Blockless Ruby Filters" do
|
|
|
189
211
|
it "should support LiteralString" do
|
|
190
212
|
@d.l('x'.lit).should == '(x)'
|
|
191
213
|
@d.l(~'x'.lit).should == 'NOT x'
|
|
192
|
-
@d.l(~~'x'.lit).should == '
|
|
214
|
+
@d.l(~~'x'.lit).should == 'x'
|
|
193
215
|
@d.l(~(('x'.lit | :y) & :z)).should == '((NOT x AND NOT y) OR NOT z)'
|
|
194
216
|
@d.l(~(:x | 'y'.lit)).should == '(NOT x AND NOT y)'
|
|
195
217
|
@d.l(~('x'.lit & 'y'.lit)).should == '(NOT x OR NOT y)'
|
|
@@ -253,17 +275,23 @@ context "Blockless Ruby Filters" do
|
|
|
253
275
|
end
|
|
254
276
|
|
|
255
277
|
it "should support Array#sql_string_join for concatenation of SQL strings" do
|
|
256
|
-
@d.
|
|
257
|
-
@d.
|
|
258
|
-
@d.
|
|
259
|
-
@d.
|
|
260
|
-
@d.
|
|
261
|
-
@d.
|
|
262
|
-
@d.
|
|
263
|
-
@d.
|
|
264
|
-
@d.
|
|
265
|
-
@d.
|
|
266
|
-
@d.
|
|
267
|
-
@d.
|
|
278
|
+
@d.lit([:x].sql_string_join).should == '(x)'
|
|
279
|
+
@d.lit([:x].sql_string_join(', ')).should == '(x)'
|
|
280
|
+
@d.lit([:x, :y].sql_string_join).should == '(x || y)'
|
|
281
|
+
@d.lit([:x, :y].sql_string_join(', ')).should == "(x || ', ' || y)"
|
|
282
|
+
@d.lit([:x[1], :y|1].sql_string_join).should == '(x(1) || y[1])'
|
|
283
|
+
@d.lit([:x[1], 'y.z'.lit].sql_string_join(', ')).should == "(x(1) || ', ' || y.z)"
|
|
284
|
+
@d.lit([:x, 1, :y].sql_string_join).should == "(x || '1' || y)"
|
|
285
|
+
@d.lit([:x, 1, :y].sql_string_join(', ')).should == "(x || ', ' || '1' || ', ' || y)"
|
|
286
|
+
@d.lit([:x, 1, :y].sql_string_join(:y__z)).should == "(x || y.z || '1' || y.z || y)"
|
|
287
|
+
@d.lit([:x, 1, :y].sql_string_join(1)).should == "(x || '1' || '1' || '1' || y)"
|
|
288
|
+
@d.lit([:x, :y].sql_string_join('y.x || x.y'.lit)).should == "(x || y.x || x.y || y)"
|
|
289
|
+
@d.lit([[:x, :y].sql_string_join, [:a, :b].sql_string_join].sql_string_join).should == "((x || y) || (a || b))"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
it "should support StringExpression#+ for concatenation of SQL strings" do
|
|
293
|
+
@d.lit(:x.sql_string + :y).should == '(x || y)'
|
|
294
|
+
@d.lit([:x].sql_string_join + :y).should == '((x) || y)'
|
|
295
|
+
@d.lit([:x, :z].sql_string_join(' ') + :y).should == "((x || ' ' || z) || y)"
|
|
268
296
|
end
|
|
269
297
|
end
|
data/spec/core_sql_spec.rb
CHANGED
|
@@ -213,7 +213,7 @@ end
|
|
|
213
213
|
|
|
214
214
|
context "Symbol#to_column_ref" do
|
|
215
215
|
setup do
|
|
216
|
-
@ds =
|
|
216
|
+
@ds = MockDataset.new(nil)
|
|
217
217
|
end
|
|
218
218
|
|
|
219
219
|
specify "should convert qualified symbol notation into dot notation" do
|
|
@@ -305,6 +305,29 @@ context "String#to_datetime" do
|
|
|
305
305
|
end
|
|
306
306
|
end
|
|
307
307
|
|
|
308
|
+
context "String#to_sequel_time" do
|
|
309
|
+
after do
|
|
310
|
+
Sequel.time_class = Time
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
specify "should convert the string into a Time object by default" do
|
|
314
|
+
"2007-07-11 10:11:12a".to_sequel_time.class.should == Time
|
|
315
|
+
"2007-07-11 10:11:12a".to_sequel_time.should == Time.parse("2007-07-11 10:11:12a")
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
specify "should convert the string into a DateTime object if that is set" do
|
|
319
|
+
Sequel.time_class = DateTime
|
|
320
|
+
"2007-07-11 10:11:12a".to_sequel_time.class.should == DateTime
|
|
321
|
+
"2007-07-11 10:11:12a".to_sequel_time.should == DateTime.parse("2007-07-11 10:11:12a")
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
specify "should raise Error::InvalidValue for an invalid time" do
|
|
325
|
+
proc {'0000-00-00'.to_sequel_time}.should raise_error(Sequel::Error::InvalidValue)
|
|
326
|
+
Sequel.time_class = DateTime
|
|
327
|
+
proc {'0000-00-00'.to_sequel_time}.should raise_error(Sequel::Error::InvalidValue)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
308
331
|
context "Sequel::SQL::Function#==" do
|
|
309
332
|
specify "should be true for functions with the same name and arguments, false otherwise" do
|
|
310
333
|
a = :date[:t]
|
data/spec/dataset_spec.rb
CHANGED
|
@@ -1183,7 +1183,7 @@ end
|
|
|
1183
1183
|
|
|
1184
1184
|
context "Dataset#join_table" do
|
|
1185
1185
|
setup do
|
|
1186
|
-
@d =
|
|
1186
|
+
@d = MockDataset.new(nil).from(:items)
|
|
1187
1187
|
@d.quote_identifiers = true
|
|
1188
1188
|
end
|
|
1189
1189
|
|
|
@@ -1269,7 +1269,7 @@ context "Dataset#join_table" do
|
|
|
1269
1269
|
@d.from('stats').join('players', {:id => :player_id}, 'p').sql.should ==
|
|
1270
1270
|
'SELECT * FROM "stats" INNER JOIN "players" "p" ON ("p"."id" = "stats"."player_id")'
|
|
1271
1271
|
|
|
1272
|
-
ds =
|
|
1272
|
+
ds = MockDataset.new(nil).from(:foo => :f)
|
|
1273
1273
|
ds.quote_identifiers = true
|
|
1274
1274
|
ds.join_table(:inner, :bar, :id => :bar_id).sql.should ==
|
|
1275
1275
|
'SELECT * FROM "foo" "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sequel_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeremy Evans
|
|
@@ -9,7 +9,7 @@ autorequire:
|
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
|
|
12
|
-
date: 2008-06-
|
|
12
|
+
date: 2008-06-04 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies: []
|
|
15
15
|
|