sequel 0.1.9.12 → 0.2.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.
@@ -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