sequel 0.1.9.12 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -86,7 +86,7 @@ module Sequel
86
86
  # If an unsupported object is given, an exception is raised.
87
87
  def literal(v)
88
88
  case v
89
- when ExpressionString: v
89
+ when LiteralString: v
90
90
  when String: "'#{v.gsub(/'/, "''")}'"
91
91
  when Integer, Float: v.to_s
92
92
  when NilClass: NULL
@@ -103,92 +103,24 @@ module Sequel
103
103
  end
104
104
 
105
105
  AND_SEPARATOR = " AND ".freeze
106
-
107
- # Formats an equality expression involving a left value and a right value.
108
- # Equality expressions differ according to the class of the right value.
109
- # The stock implementation supports Range (inclusive and exclusive), Array
110
- # (as a list of values to compare against), Dataset (as a subquery to
111
- # compare against), or a regular value.
112
- #
113
- # dataset.format_eq_expression('id', 1..20) #=>
114
- # "(id >= 1 AND id <= 20)"
115
- # dataset.format_eq_expression('id', [3,6,10]) #=>
116
- # "(id IN (3, 6, 10))"
117
- # dataset.format_eq_expression('id', DB[:items].select(:id)) #=>
118
- # "(id IN (SELECT id FROM items))"
119
- # dataset.format_eq_expression('id', nil) #=>
120
- # "(id IS NULL)"
121
- # dataset.format_eq_expression('id', 3) #=>
122
- # "(id = 3)"
123
- def format_eq_expression(left, right)
124
- case right
125
- when Range:
126
- right.exclude_end? ? \
127
- "(#{left} >= #{literal(right.begin)} AND #{left} < #{literal(right.end)})" : \
128
- "(#{left} >= #{literal(right.begin)} AND #{left} <= #{literal(right.end)})"
129
- when Array:
130
- "(#{left} IN (#{literal(right)}))"
131
- when Dataset:
132
- "(#{left} IN (#{right.sql}))"
133
- when NilClass:
134
- "(#{left} IS NULL)"
135
- else
136
- "(#{left} = #{literal(right)})"
137
- end
138
- end
139
-
140
- # Formats an expression comprising a left value, a binary operator and a
141
- # right value. The supported operators are :eql (=), :not (!=), :lt (<),
142
- # :lte (<=), :gt (>), :gte (>=) and :like (LIKE operator). Examples:
143
- #
144
- # dataset.format_expression('price', :gte, 100) #=> "(price >= 100)"
145
- # dataset.format_expression('id', :not, 30) #=> "NOT (id = 30)"
146
- # dataset.format_expression('name', :like, 'abc%') #=>
147
- # "(name LIKE 'abc%')"
148
- #
149
- # If an unsupported operator is given, an exception is raised.
150
- def format_expression(left, op, right)
151
- left = field_name(left)
152
- case op
153
- when :eql:
154
- format_eq_expression(left, right)
155
- when :not:
156
- "NOT #{format_eq_expression(left, right)}"
157
- when :lt:
158
- "(#{left} < #{literal(right)})"
159
- when :lte:
160
- "(#{left} <= #{literal(right)})"
161
- when :gt:
162
- "(#{left} > #{literal(right)})"
163
- when :gte:
164
- "(#{left} >= #{literal(right)})"
165
- when :like:
166
- "(#{left} LIKE #{literal(right)})"
167
- else
168
- raise SequelError, "Invalid operator specified: #{op}"
169
- end
170
- end
171
-
172
106
  QUESTION_MARK = '?'.freeze
173
107
 
174
108
  # Formats a where clause. If parenthesize is true, then the whole
175
109
  # generated clause will be enclosed in a set of parentheses.
176
- def expression_list(where, parenthesize = false)
177
- case where
110
+ def expression_list(expr, parenthesize = false)
111
+ case expr
178
112
  when Hash:
179
- parenthesize = false if where.size == 1
180
- fmt = where.map {|i| format_expression(i[0], :eql, i[1])}.
181
- join(AND_SEPARATOR)
113
+ parenthesize = false if expr.size == 1
114
+ fmt = expr.map {|i| compare_expr(i[0], i[1])}.join(AND_SEPARATOR)
182
115
  when Array:
183
- fmt = where.shift.gsub(QUESTION_MARK) {literal(where.shift)}
116
+ fmt = expr.shift.gsub(QUESTION_MARK) {literal(expr.shift)}
184
117
  when Proc:
185
- fmt = where.to_expressions.map {|e| format_expression(e.left, e.op, e.right)}.
186
- join(AND_SEPARATOR)
118
+ fmt = proc_to_sql(expr)
187
119
  else
188
120
  # if the expression is compound, it should be parenthesized in order for
189
121
  # things to be predictable (when using #or and #and.)
190
- parenthesize |= where =~ /\).+\(/
191
- fmt = where
122
+ parenthesize |= expr =~ /\).+\(/
123
+ fmt = expr
192
124
  end
193
125
  parenthesize ? "(#{fmt})" : fmt
194
126
  end
@@ -272,10 +204,6 @@ module Sequel
272
204
  parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
273
205
  filter = cond.is_a?(Hash) && cond
274
206
  if @opts[clause]
275
- if filter && cond.is_a?(Hash)
276
- filter
277
- end
278
- filter =
279
207
  l = expression_list(@opts[clause])
280
208
  r = expression_list(block || cond, parenthesize)
281
209
  clone_merge(clause => "#{l} AND #{r}")
@@ -427,6 +355,10 @@ module Sequel
427
355
  # options.
428
356
  def select_sql(opts = nil)
429
357
  opts = opts ? @opts.merge(opts) : @opts
358
+
359
+ if sql = opts[:sql]
360
+ return sql
361
+ end
430
362
 
431
363
  fields = opts[:select]
432
364
  select_fields = fields ? field_list(fields) : WILDCARD
data/lib/sequel/model.rb CHANGED
@@ -82,7 +82,7 @@ module Sequel
82
82
  end
83
83
 
84
84
  def self.drop_table
85
- db.execute schema.drop_sql
85
+ db.execute db.drop_table_sql(table_name)
86
86
  end
87
87
 
88
88
  def self.recreate_table
@@ -224,13 +224,13 @@ module Sequel
224
224
  Thread.exclusive do
225
225
  method_name = m.to_s
226
226
  if method_name =~ FIND_BY_REGEXP
227
- c = $1
227
+ c = $1.to_sym
228
228
  meta_def(method_name) {|arg| find(c => arg)}
229
229
  elsif method_name =~ FILTER_BY_REGEXP
230
- c = $1
230
+ c = $1.to_sym
231
231
  meta_def(method_name) {|arg| filter(c => arg)}
232
232
  elsif method_name =~ ALL_BY_REGEXP
233
- c = $1
233
+ c = $1.to_sym
234
234
  meta_def(method_name) {|arg| filter(c => arg).all}
235
235
  elsif dataset.respond_to?(m)
236
236
  instance_eval("def #{m}(*args, &block); dataset.#{m}(*args, &block); end")
data/lib/sequel/mysql.rb CHANGED
@@ -156,10 +156,22 @@ module Sequel
156
156
  end
157
157
  end
158
158
 
159
+ TRUE = '1'
160
+ FALSE = '0'
161
+
159
162
  def literal(v)
160
163
  case v
161
- when true: '1'
162
- when false: '0'
164
+ when true: TRUE
165
+ when false: FALSE
166
+ else
167
+ super
168
+ end
169
+ end
170
+
171
+ def match_expr(l, r)
172
+ case r
173
+ when Regexp:
174
+ "(#{literal(l)} REGEXP #{literal(r.source)})"
163
175
  else
164
176
  super
165
177
  end
@@ -285,14 +285,12 @@ module Sequel
285
285
  LIKE = '(%s ~ %s)'.freeze
286
286
  LIKE_CI = '%s ~* %s'.freeze
287
287
 
288
- def format_eq_expression(left, right)
289
- case right
288
+ def match_expr(l, r)
289
+ case r
290
290
  when Regexp:
291
- l = field_name(left)
292
- r = PGconn.quote(right.source)
293
- right.casefold? ? \
294
- "(#{l} ~* #{r})" : \
295
- "(#{l} ~ #{r})"
291
+ r.casefold? ? \
292
+ "(#{literal(l)} ~* #{literal(r.source)})" :
293
+ "(#{literal(l)} ~ #{literal(r.source)})"
296
294
  else
297
295
  super
298
296
  end
@@ -10,8 +10,7 @@ module Sequel
10
10
  end
11
11
 
12
12
  def method_missing(type, name = nil, opts = nil)
13
- return super unless name
14
- column(name, type, opts)
13
+ name ? column(name, type, opts) : super
15
14
  end
16
15
 
17
16
  def primary_key_name
@@ -116,4 +116,11 @@ context "A MySQL dataset" do
116
116
 
117
117
  @d.count.should == 1
118
118
  end
119
+
120
+ specify "should support regexps" do
121
+ @d << {:name => 'abc', :value => 1}
122
+ @d << {:name => 'bcd', :value => 2}
123
+ @d.filter(:name => /bc/).count.should == 2
124
+ @d.filter(:name => /^bc/).count.should == 1
125
+ end
119
126
  end
@@ -0,0 +1,87 @@
1
+ require File.join(File.dirname(__FILE__), '../../lib/sequel/postgres')
2
+
3
+ PGSQL_DB = Sequel('postgres://postgres:postgres@localhost:5432/reality_spec')
4
+ if PGSQL_DB.table_exists?(:test)
5
+ PGSQL_DB.drop_table :test
6
+ end
7
+ PGSQL_DB.create_table :test do
8
+ text :name
9
+ integer :value
10
+
11
+ index :value
12
+ end
13
+
14
+ context "A MySQL dataset" do
15
+ setup do
16
+ @d = PGSQL_DB[:test]
17
+ @d.delete # remove all records
18
+ end
19
+
20
+ specify "should return the correct record count" do
21
+ @d.count.should == 0
22
+ @d << {:name => 'abc', :value => 123}
23
+ @d << {:name => 'abc', :value => 456}
24
+ @d << {:name => 'def', :value => 789}
25
+ @d.count.should == 3
26
+ end
27
+
28
+ # specify "should return the last inserted id when inserting records" do
29
+ # id = @d << {:name => 'abc', :value => 1.23}
30
+ # id.should == @d.first[:id]
31
+ # end
32
+ #
33
+
34
+ specify "should return all records" do
35
+ @d << {:name => 'abc', :value => 123}
36
+ @d << {:name => 'abc', :value => 456}
37
+ @d << {:name => 'def', :value => 789}
38
+
39
+ @d.order(:value).all.should == [
40
+ {:name => 'abc', :value => 123},
41
+ {:name => 'abc', :value => 456},
42
+ {:name => 'def', :value => 789}
43
+ ]
44
+ end
45
+
46
+ specify "should update records correctly" do
47
+ @d << {:name => 'abc', :value => 123}
48
+ @d << {:name => 'abc', :value => 456}
49
+ @d << {:name => 'def', :value => 789}
50
+ @d.filter(:name => 'abc').update(:value => 530)
51
+
52
+ # the third record should stay the same
53
+ # floating-point precision bullshit
54
+ @d[:name => 'def'][:value].should == 789
55
+ @d.filter(:value => 530).count.should == 2
56
+ end
57
+
58
+ specify "should delete records correctly" do
59
+ @d << {:name => 'abc', :value => 123}
60
+ @d << {:name => 'abc', :value => 456}
61
+ @d << {:name => 'def', :value => 789}
62
+ @d.filter(:name => 'abc').delete
63
+
64
+ @d.count.should == 1
65
+ @d.first[:name].should == 'def'
66
+ end
67
+
68
+ specify "should be able to literalize booleans" do
69
+ proc {@d.literal(true)}.should_not raise_error
70
+ proc {@d.literal(false)}.should_not raise_error
71
+ end
72
+
73
+ specify "should support transactions" do
74
+ PGSQL_DB.transaction do
75
+ @d << {:name => 'abc', :value => 1}
76
+ end
77
+
78
+ @d.count.should == 1
79
+ end
80
+
81
+ specify "should support regexps" do
82
+ @d << {:name => 'abc', :value => 1}
83
+ @d << {:name => 'bcd', :value => 2}
84
+ @d.filter(:name => /bc/).count.should == 2
85
+ @d.filter(:name => /^bc/).count.should == 1
86
+ end
87
+ end
@@ -152,10 +152,10 @@ context "SQLite::Dataset#delete" do
152
152
 
153
153
  specify "should return the number of records affected when filtered" do
154
154
  @d.count.should == 3
155
- @d.filter {value < 3}.delete.should == 1
155
+ @d.filter {:value < 3}.delete.should == 1
156
156
  @d.count.should == 2
157
157
 
158
- @d.filter {value < 3}.delete.should == 0
158
+ @d.filter {:value < 3}.delete.should == 0
159
159
  @d.count.should == 2
160
160
  end
161
161
 
@@ -152,7 +152,7 @@ context "A connection pool with a max size of 1" do
152
152
  @pool.allocated.should == {t1 => cc}
153
153
 
154
154
  cc.gsub!('rr', 'll')
155
- sleep 0.2
155
+ sleep 0.5
156
156
 
157
157
  # connection held by t2
158
158
  t1.should_not be_alive
@@ -164,7 +164,7 @@ context "A connection pool with a max size of 1" do
164
164
  @pool.allocated.should == {t2 => cc}
165
165
 
166
166
  cc.gsub!('ll', 'rr')
167
- sleep 0.2
167
+ sleep 0.5
168
168
 
169
169
  #connection released
170
170
  t2.should_not be_alive
@@ -52,11 +52,34 @@ context "String#to_sql" do
52
52
  end
53
53
  end
54
54
 
55
+ context "String#lit" do
56
+ specify "should return an LiteralString object" do
57
+ 'xyz'.lit.should be_a_kind_of(Sequel::LiteralString)
58
+ 'xyz'.lit.to_s.should == 'xyz'
59
+ end
60
+
61
+ specify "should inhibit string literalization" do
62
+ db = Sequel::Database.new
63
+ ds = db[:t]
64
+
65
+ ds.update_sql(:stamp => "NOW()".lit).should == \
66
+ "UPDATE t SET stamp = NOW()"
67
+ end
68
+ end
69
+
55
70
  context "String#expr" do
56
- specify "should return an ExpressionString object" do
57
- 'xyz'.expr.should be_a_kind_of(Sequel::ExpressionString)
71
+ specify "should return an LiteralString object" do
72
+ 'xyz'.expr.should be_a_kind_of(Sequel::LiteralString)
58
73
  'xyz'.expr.to_s.should == 'xyz'
59
74
  end
75
+
76
+ specify "should inhibit string literalization" do
77
+ db = Sequel::Database.new
78
+ ds = db[:t]
79
+
80
+ ds.update_sql(:stamp => "NOW()".expr).should == \
81
+ "UPDATE t SET stamp = NOW()"
82
+ end
60
83
  end
61
84
 
62
85
  context "String#split_sql" do
@@ -150,4 +173,22 @@ context "Symbol" do
150
173
  specify "should support AVG for specifying avg function" do
151
174
  :abc__def.AVG.should == 'avg(abc.def)'
152
175
  end
176
+
177
+ specify "should support COUNT for specifying count function" do
178
+ :abc__def.COUNT.should == 'count(abc.def)'
179
+ end
180
+
181
+ specify "should support any other function using upper case letters" do
182
+ :abc__def.DADA.should == 'dada(abc.def)'
183
+ end
184
+
185
+ specify "should support upper case outer functions" do
186
+ :COUNT['1'].should == 'COUNT(1)'
187
+ end
188
+
189
+ specify "should inhibit string literalization" do
190
+ db = Sequel::Database.new
191
+ ds = db[:t]
192
+ ds.select(:COUNT['1']).sql.should == "SELECT COUNT(1) FROM t"
193
+ end
153
194
  end
@@ -82,6 +82,12 @@ context "Database#dataset" do
82
82
  e.sql.should == 'SELECT * FROM miu'
83
83
  end
84
84
 
85
+ specify "should provide a filtered #from dataset if a block is given" do
86
+ d = @db.from(:mau) {:x > 100}
87
+ d.should be_a_kind_of(Sequel::Dataset)
88
+ d.sql.should == 'SELECT * FROM mau WHERE (x > 100)'
89
+ end
90
+
85
91
  specify "should provide a #select dataset" do
86
92
  d = @db.select(:a, :b, :c).from(:mau)
87
93
  d.should be_a_kind_of(Sequel::Dataset)
data/spec/dataset_spec.rb CHANGED
@@ -110,6 +110,11 @@ context "A simple dataset" do
110
110
  @dataset.update_sql(:name => 'abc').should ==
111
111
  "UPDATE test SET name = 'abc'"
112
112
  end
113
+
114
+ specify "should be able to return rows for arbitrary SQL" do
115
+ @dataset.select_sql(:sql => 'xxx yyy zzz').should ==
116
+ "xxx yyy zzz"
117
+ end
113
118
  end
114
119
 
115
120
  context "A dataset with multiple tables in its FROM clause" do
@@ -208,7 +213,7 @@ context "Dataset#where" do
208
213
  "SELECT * FROM test WHERE (a = 1) AND (d = 4)"
209
214
 
210
215
  # string and proc expr
211
- @d3.where {e < 5}.select_sql.should ==
216
+ @d3.where {:e < 5}.select_sql.should ==
212
217
  "SELECT * FROM test WHERE (a = 1) AND (e < 5)"
213
218
  end
214
219
 
@@ -222,10 +227,10 @@ context "Dataset#where" do
222
227
  @dataset.filter(:id => 4...7).sql.should ==
223
228
  'SELECT * FROM test WHERE (id >= 4 AND id < 7)'
224
229
 
225
- @dataset.filter {id == (4..7)}.sql.should ==
230
+ @dataset.filter {:id == (4..7)}.sql.should ==
226
231
  'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
227
232
 
228
- @dataset.filter {id.in 4..7}.sql.should ==
233
+ @dataset.filter {:id.in?(4..7)}.sql.should ==
229
234
  'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
230
235
  end
231
236
 
@@ -233,7 +238,7 @@ context "Dataset#where" do
233
238
  @dataset.filter(:owner_id => nil).sql.should ==
234
239
  'SELECT * FROM test WHERE (owner_id IS NULL)'
235
240
 
236
- @dataset.filter{owner_id.nil?}.sql.should ==
241
+ @dataset.filter{:owner_id.nil?}.sql.should ==
237
242
  'SELECT * FROM test WHERE (owner_id IS NULL)'
238
243
  end
239
244
 
@@ -247,45 +252,39 @@ context "Dataset#where" do
247
252
  end
248
253
 
249
254
  specify "should accept a subquery for an EXISTS clause" do
250
- a = @dataset.filter {price < 100}
255
+ a = @dataset.filter {:price < 100}
251
256
  @dataset.filter(a.exists).sql.should ==
252
257
  'SELECT * FROM test WHERE EXISTS (SELECT 1 FROM test WHERE (price < 100))'
253
258
  end
254
259
 
255
- specify "should accept proc expressions (nice!)" do
260
+ specify "should accept proc expressions" do
256
261
  d = @d1.select(:gdp.AVG)
257
- @dataset.filter {gdp > d}.sql.should ==
262
+ @dataset.filter {:gdp > d}.sql.should ==
258
263
  "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
259
264
 
260
- @dataset.filter {id.in 4..7}.sql.should ==
265
+ @dataset.filter {:id.in(4..7)}.sql.should ==
261
266
  'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
262
267
 
263
- @dataset.filter {c == 3}.sql.should ==
268
+ @dataset.filter {:c == 3}.sql.should ==
264
269
  'SELECT * FROM test WHERE (c = 3)'
265
270
 
266
- @dataset.filter {id == :items__id}.sql.should ==
271
+ @dataset.filter {:id == :items__id}.sql.should ==
267
272
  'SELECT * FROM test WHERE (id = items.id)'
268
273
 
269
- @dataset.filter {a < 1}.sql.should ==
274
+ @dataset.filter {:a < 1}.sql.should ==
270
275
  'SELECT * FROM test WHERE (a < 1)'
271
276
 
272
- @dataset.filter {a <=> 1}.sql.should ==
273
- 'SELECT * FROM test WHERE NOT (a = 1)'
277
+ @dataset.filter {:a != 1}.sql.should ==
278
+ 'SELECT * FROM test WHERE (NOT (a = 1))'
274
279
 
275
- @dataset.filter {a >= 1 && b <= 2}.sql.should ==
276
- 'SELECT * FROM test WHERE (a >= 1) AND (b <= 2)'
280
+ @dataset.filter {:a >= 1 && :b <= 2}.sql.should ==
281
+ 'SELECT * FROM test WHERE ((a >= 1) AND (b <= 2))'
277
282
 
278
- @dataset.filter {c =~ 'ABC%'}.sql.should ==
283
+ @dataset.filter {:c.like 'ABC%'}.sql.should ==
279
284
  "SELECT * FROM test WHERE (c LIKE 'ABC%')"
280
285
 
281
- @dataset.filter {test.ccc =~ 'ABC%'}.sql.should ==
282
- "SELECT * FROM test WHERE (test.ccc LIKE 'ABC%')"
283
- end
284
-
285
- specify "should raise SequelError for invalid proc expressions" do
286
- proc {@dataset.filter {Object.czxczxcz}}.should raise_error(SequelError)
287
- proc {@dataset.filter {a.bcvxv}}.should raise_error(SequelError)
288
- proc {@dataset.filter {x}}.should raise_error(SequelError)
286
+ @dataset.filter {:c.like? 'ABC%'}.sql.should ==
287
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
289
288
  end
290
289
  end
291
290
 
@@ -309,7 +308,7 @@ context "Dataset#or" do
309
308
  @d1.or('(y > ?)', 2).sql.should ==
310
309
  'SELECT * FROM test WHERE (x = 1) OR (y > 2)'
311
310
 
312
- (@d1.or {yy > 3}).sql.should ==
311
+ (@d1.or {:yy > 3}).sql.should ==
313
312
  'SELECT * FROM test WHERE (x = 1) OR (yy > 3)'
314
313
  end
315
314
 
@@ -342,7 +341,7 @@ context "Dataset#and" do
342
341
  @d1.and('(y > ?)', 2).sql.should ==
343
342
  'SELECT * FROM test WHERE (x = 1) AND (y > 2)'
344
343
 
345
- (@d1.and {yy > 3}).sql.should ==
344
+ (@d1.and {:yy > 3}).sql.should ==
346
345
  'SELECT * FROM test WHERE (x = 1) AND (yy > 3)'
347
346
  end
348
347
 
@@ -387,7 +386,7 @@ context "Dataset#exclude" do
387
386
  end
388
387
 
389
388
  specify "should support proc expressions" do
390
- @dataset.exclude {id == (6...12)}.sql.should ==
389
+ @dataset.exclude {:id == (6...12)}.sql.should ==
391
390
  'SELECT * FROM test WHERE NOT ((id >= 6 AND id < 12))'
392
391
  end
393
392
  end
@@ -411,7 +410,7 @@ context "Dataset#having" do
411
410
  end
412
411
 
413
412
  specify "should support proc expressions" do
414
- @grouped.having {SUM(:population) > 10}.sql.should ==
413
+ @grouped.having {:sum[:population] > 10}.sql.should ==
415
414
  "SELECT #{@fields} FROM test GROUP BY region HAVING (sum(population) > 10)"
416
415
  end
417
416
  end
@@ -753,7 +752,7 @@ context "Dataset#count" do
753
752
  end
754
753
 
755
754
  specify "should include the where clause if it's there" do
756
- @dataset.filter {abc < 30}.count.should == 1
755
+ @dataset.filter {:abc < 30}.count.should == 1
757
756
  @c.sql.should == 'SELECT COUNT(*) FROM test WHERE (abc < 30)'
758
757
  end
759
758
  end
@@ -769,7 +768,7 @@ context "Dataset#join_table" do
769
768
  end
770
769
 
771
770
  specify "should include WHERE clause if applicable" do
772
- @d.filter {price < 100}.join_table(:right_outer, :categories, :category_id => :id).sql.should ==
771
+ @d.filter {:price < 100}.join_table(:right_outer, :categories, :category_id => :id).sql.should ==
773
772
  'SELECT * FROM items RIGHT OUTER JOIN categories ON (categories.category_id = items.id) WHERE (price < 100)'
774
773
  end
775
774