sequel_core 1.0.0.1 → 1.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 CHANGED
@@ -1,3 +1,17 @@
1
+ === 1.0.1 (2008-01-12)
2
+
3
+ * Changed postgres adapter to quote column references using double quotes.
4
+
5
+ * Applied patch for oracle adapter: fix behavior of limit and offset, transactions, #table_exists?, #tables and additional specs (thanks Liming Lian #122).
6
+
7
+ * Allow for additional filters on a grouped dataset (#119 and #120)
8
+
9
+ * Changed mysql adapter to default to localhost if :host option is not specified (#114).
10
+
11
+ * Refactored Sequelizer to use Proc#to_sexp (method provided by r2r).
12
+
13
+ * Enhanced Database.connect to accept options with string keys, so it can now accept options loaded from YAML files. Database.connect also automatically converts :username option into :user for compatibility with existing YAML configuration files for AR and DataMapper.
14
+
1
15
  === 1.0.0.1 (2008-01-03)
2
16
 
3
17
  * Changed MySQL adapter to support specifying socket option.
data/COPYING CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2007 Sharon Rosner
1
+ Copyright (c) 2006-2008 Sharon Rosner
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ include FileUtils
9
9
  # Configuration
10
10
  ##############################################################################
11
11
  NAME = "sequel_core"
12
- VERS = "1.0.0.1"
12
+ VERS = "1.0.1"
13
13
  CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
14
14
  RDOC_OPTS = [
15
15
  "--quiet",
@@ -105,6 +105,9 @@ end
105
105
 
106
106
  task :tag do
107
107
  cwd = FileUtils.pwd
108
+ sh %{rm -rf doc/*}
109
+ sh %{rm -rf pkg/*}
110
+ sh %{rm -rf coverage/*}
108
111
  sh %{cd ../.. && svn copy #{cwd} tags/#{NAME}-#{VERS} && svn commit -m "#{NAME}-#{VERS} tag." tags}
109
112
  end
110
113
 
@@ -86,8 +86,15 @@ module Sequel
86
86
  end
87
87
 
88
88
  def connect
89
- conn = Mysql.real_connect(@opts[:host], @opts[:user], @opts[:password],
90
- @opts[:database], @opts[:port], @opts[:socket], Mysql::CLIENT_MULTI_RESULTS)
89
+ conn = Mysql.real_connect(
90
+ @opts[:host] || 'localhost',
91
+ @opts[:user],
92
+ @opts[:password],
93
+ @opts[:database],
94
+ @opts[:port],
95
+ @opts[:socket],
96
+ Mysql::CLIENT_MULTI_RESULTS
97
+ )
91
98
  conn.query_with_result = false
92
99
  if encoding = @opts[:encoding] || @opts[:charset]
93
100
  conn.query("set character_set_connection = '#{encoding}'")
@@ -38,6 +38,39 @@ module Sequel
38
38
  end
39
39
 
40
40
  alias_method :do, :execute
41
+
42
+ def tables
43
+ from(:tab).select(:tname).filter(:tabtype => 'TABLE').map do |r|
44
+ r[:tname].downcase.to_sym
45
+ end
46
+ end
47
+
48
+ def table_exists?(name)
49
+ from(:tab).filter(:tname => name.to_s.upcase, :tabtype => 'TABLE').count > 0
50
+ end
51
+
52
+ def transaction
53
+ @pool.hold do |conn|
54
+ @transactions ||= []
55
+ if @transactions.include? Thread.current
56
+ return yield(conn)
57
+ end
58
+
59
+ conn.autocommit = false
60
+ begin
61
+ @transactions << Thread.current
62
+ result = yield(conn)
63
+ conn.commit
64
+ result
65
+ rescue => e
66
+ conn.rollback
67
+ raise e unless SequelRollbackError === e
68
+ ensure
69
+ conn.autocommit = true
70
+ @transactions.delete(Thread.current)
71
+ end
72
+ end
73
+ end
41
74
  end
42
75
 
43
76
  class Dataset < Sequel::Dataset
@@ -54,10 +87,10 @@ module Sequel
54
87
  @db.synchronize do
55
88
  cursor = @db.execute sql
56
89
  begin
57
- @columns = cursor.get_col_names.map {|c| c.to_sym}
90
+ @columns = cursor.get_col_names.map {|c| c.downcase.to_sym}
58
91
  while r = cursor.fetch
59
92
  row = {}
60
- r.each_with_index {|v, i| row[@columns[i]] = v}
93
+ r.each_with_index {|v, i| row[columns[i]] = v unless columns[i] == :raw_rnum_}
61
94
  yield row
62
95
  end
63
96
  ensure
@@ -71,9 +104,11 @@ module Sequel
71
104
  @db.synchronize do
72
105
  cursor = @db.execute sql
73
106
  begin
74
- @columns = cursor.get_col_names.map {|c| c.to_sym}
107
+ @columns = cursor.get_col_names.map {|c| c.downcase.to_sym}
108
+ raw_rnum_index = columns.index(:raw_rnum_)
75
109
  while r = cursor.fetch
76
- r.keys = columns
110
+ r.delete_at(raw_rnum_index) if raw_rnum_index
111
+ r.keys = columns.delete_if{|x| x == :raw_rnum_}
77
112
  yield r
78
113
  end
79
114
  ensure
@@ -94,6 +129,70 @@ module Sequel
94
129
  def delete(opts = nil)
95
130
  @db.do delete_sql(opts)
96
131
  end
132
+
133
+
134
+ # Formats a SELECT statement using the given options and the dataset
135
+ # options.
136
+ def select_sql(opts = nil)
137
+ opts = opts ? @opts.merge(opts) : @opts
138
+
139
+ if sql = opts[:sql]
140
+ return sql
141
+ end
142
+
143
+ columns = opts[:select]
144
+ select_columns = columns ? column_list(columns) : WILDCARD
145
+ sql = opts[:distinct] ? \
146
+ "SELECT DISTINCT #{select_columns}" : \
147
+ "SELECT #{select_columns}"
148
+
149
+ if opts[:from]
150
+ sql << " FROM #{source_list(opts[:from])}"
151
+ end
152
+
153
+ if join = opts[:join]
154
+ sql << join
155
+ end
156
+
157
+ if where = opts[:where]
158
+ sql << " WHERE #{where}"
159
+ end
160
+
161
+ if group = opts[:group]
162
+ sql << " GROUP BY #{column_list(group)}"
163
+ end
164
+
165
+ if having = opts[:having]
166
+ sql << " HAVING #{having}"
167
+ end
168
+
169
+ if union = opts[:union]
170
+ sql << (opts[:union_all] ? \
171
+ " UNION ALL #{union.sql}" : " UNION #{union.sql}")
172
+ elsif intersect = opts[:intersect]
173
+ sql << (opts[:intersect_all] ? \
174
+ " INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
175
+ elsif except = opts[:except]
176
+ sql << (opts[:except_all] ? \
177
+ " EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
178
+ end
179
+
180
+ if order = opts[:order]
181
+ sql << " ORDER BY #{column_list(order)}"
182
+ end
183
+
184
+ if limit = opts[:limit]
185
+ if (offset = opts[:offset]) && (offset > 0)
186
+ sql = "SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM(#{sql}) raw_sql_ WHERE ROWNUM <= #{limit + offset}) WHERE raw_rnum_ > #{offset}"
187
+ else
188
+ sql = "SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}"
189
+ end
190
+ end
191
+
192
+ sql
193
+ end
194
+
195
+ alias sql select_sql
97
196
  end
98
197
  end
99
198
  end
@@ -313,6 +313,8 @@ module Sequel
313
313
  end
314
314
 
315
315
  class Dataset < Sequel::Dataset
316
+ def quote_column_ref(c); "\"#{c}\""; end
317
+
316
318
  def literal(v)
317
319
  case v
318
320
  when LiteralString
@@ -414,12 +414,18 @@ module Sequel
414
414
  scheme = uri.scheme
415
415
  scheme = :dbi if scheme =~ /^dbi-(.+)/
416
416
  c = adapter_class(scheme)
417
- c.new(c.uri_to_options(uri).merge(opts || {}))
417
+ opts = c.uri_to_options(uri).merge(opts || {})
418
418
  else
419
419
  opts = conn_string.merge(opts || {})
420
- c = adapter_class(opts[:adapter])
421
- c.new(opts)
420
+ c = adapter_class(opts[:adapter] || opts['adapter'])
422
421
  end
422
+ # process opts a bit
423
+ opts = opts.inject({}) do |m, kv| k, v = *kv
424
+ k = :user if k == 'username'
425
+ m[k.to_sym] = v
426
+ m
427
+ end
428
+ c.new(opts)
423
429
  end
424
430
 
425
431
  @@single_threaded = false
@@ -251,8 +251,8 @@ class Sequel::Dataset
251
251
  eval("$#{e[1]}", b)
252
252
  when :lvar # local context
253
253
  if e[1] == :block
254
- pr = eval(e[1].to_s, b)
255
- "#{proc_to_sql(pr)}"
254
+ sub_proc = eval(e[1].to_s, b)
255
+ sub_proc.to_sql(self)
256
256
  else
257
257
  eval(e[1].to_s, b)
258
258
  end
@@ -324,20 +324,20 @@ class Sequel::Dataset
324
324
  end
325
325
  end
326
326
  end
327
+ end
328
+ end
327
329
 
328
- # Translates a Ruby block into an SQL expression.
329
- def proc_to_sql(proc, opts = {})
330
- c = Class.new {define_method(:m, &proc)}
331
- pt_expr(ParseTree.translate(c, :m)[2][2], proc.binding, opts)
332
- end
330
+ class Proc
331
+ def to_sql(dataset, opts = {})
332
+ dataset.pt_expr(to_sexp[2], self.binding, opts)
333
333
  end
334
334
  end
335
335
 
336
336
  begin
337
337
  require 'parse_tree'
338
338
  rescue Exception
339
- module Sequel::Dataset::Sequelizer
340
- def proc_to_sql(*args)
339
+ class Proc
340
+ def to_sql(*args)
341
341
  raise Sequel::Error, "You must have the ParseTree gem installed in order to use block filters."
342
342
  end
343
343
  end
@@ -352,3 +352,14 @@ rescue Exception
352
352
  end
353
353
  end
354
354
  end
355
+
356
+ class Proc
357
+ # replacement for Proc#to_sexp, if it's not available
358
+ unless instance_methods.include?('to_sexp')
359
+ def to_sexp
360
+ block = self
361
+ c = Class.new {define_method(:m, &block)}
362
+ ParseTree.translate(c, :m)[2]
363
+ end
364
+ end
365
+ end
@@ -128,7 +128,7 @@ module Sequel
128
128
  when Array
129
129
  fmt = expr.shift.gsub(QUESTION_MARK) {literal(expr.shift)}
130
130
  when Proc
131
- fmt = proc_to_sql(expr)
131
+ fmt = expr.to_sql(self)
132
132
  else
133
133
  # if the expression is compound, it should be parenthesized in order for
134
134
  # things to be predictable (when using #or and #and.)
@@ -223,14 +223,14 @@ module Sequel
223
223
  # software.filter {price < 100}.sql #=>
224
224
  # "SELECT * FROM items WHERE (category = 'software') AND (price < 100)"
225
225
  def filter(*cond, &block)
226
- clause = (@opts[:group] ? :having : :where)
226
+ clause = (@opts[:having] ? :having : :where)
227
227
  cond = cond.first if cond.size == 1
228
228
  if cond === true || cond === false
229
229
  raise Error::InvalidFilter, "Invalid filter specified. Did you mean to supply a block?"
230
230
  end
231
231
  parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
232
232
  filter = cond.is_a?(Hash) && cond
233
- if @opts[clause]
233
+ if !@opts[clause].nil? and @opts[clause].any?
234
234
  l = expression_list(@opts[clause])
235
235
  r = expression_list(block || cond, parenthesize)
236
236
  clone_merge(clause => "#{l} AND #{r}")
@@ -242,7 +242,7 @@ module Sequel
242
242
  # Adds an alternate filter to an existing filter using OR. If no filter
243
243
  # exists an error is raised.
244
244
  def or(*cond, &block)
245
- clause = (@opts[:group] ? :having : :where)
245
+ clause = (@opts[:having] ? :having : :where)
246
246
  cond = cond.first if cond.size == 1
247
247
  parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
248
248
  if @opts[clause]
@@ -258,7 +258,7 @@ module Sequel
258
258
  # exists an error is raised. This method is identical to #filter except
259
259
  # it expects an existing filter.
260
260
  def and(*cond, &block)
261
- clause = (@opts[:group] ? :having : :where)
261
+ clause = (@opts[:having] ? :having : :where)
262
262
  unless @opts[clause]
263
263
  raise Error::NoExistingFilter, "No existing filter found."
264
264
  end
@@ -270,7 +270,7 @@ module Sequel
270
270
  # dataset.exclude(:category => 'software').sql #=>
271
271
  # "SELECT * FROM items WHERE NOT (category = 'software')"
272
272
  def exclude(*cond, &block)
273
- clause = (@opts[:group] ? :having : :where)
273
+ clause = (@opts[:having] ? :having : :where)
274
274
  cond = cond.first if cond.size == 1
275
275
  parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
276
276
  if @opts[clause]
@@ -286,11 +286,7 @@ module Sequel
286
286
  # Returns a copy of the dataset with the where conditions changed. Raises
287
287
  # if the dataset has been grouped. See also #filter.
288
288
  def where(*cond, &block)
289
- if @opts[:group]
290
- raise Error, "Can't specify a WHERE clause once the dataset has been grouped"
291
- else
292
- filter(*cond, &block)
293
- end
289
+ filter(*cond, &block)
294
290
  end
295
291
 
296
292
  # Returns a copy of the dataset with the having conditions changed. Raises
@@ -299,6 +295,7 @@ module Sequel
299
295
  unless @opts[:group]
300
296
  raise Error, "Can only specify a HAVING clause on a grouped dataset"
301
297
  else
298
+ @opts[:having] = {}
302
299
  filter(*cond, &block)
303
300
  end
304
301
  end
@@ -501,7 +498,7 @@ module Sequel
501
498
 
502
499
  sql = "UPDATE #{@opts[:from]} SET "
503
500
  if block
504
- sql << proc_to_sql(block, :comma_separated => true)
501
+ sql << block.to_sql(self, :comma_separated => true)
505
502
  else
506
503
  # check if array with keys
507
504
  values = values.to_hash if values.is_a?(Array) && values.keys
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Model
3
3
  def self.database_opened(db)
4
- @db = db if (self == Model) && !@db
4
+ @db ||= db if (self == Model)
5
5
  end
6
6
  end
7
7
  end
@@ -24,6 +24,18 @@ context "A MySQL database" do
24
24
  @db.disconnect
25
25
  @db.pool.size.should == 0
26
26
  end
27
+
28
+ specify "should support sequential primary keys" do
29
+ @db.create_table!(:with_pk) {primary_key :id; text :name}
30
+ @db[:with_pk] << {:name => 'abc'}
31
+ @db[:with_pk] << {:name => 'def'}
32
+ @db[:with_pk] << {:name => 'ghi'}
33
+ @db[:with_pk].order(:name).all.should == [
34
+ {:id => 1, :name => 'abc'},
35
+ {:id => 2, :name => 'def'},
36
+ {:id => 3, :name => 'ghi'}
37
+ ]
38
+ end
27
39
  end
28
40
 
29
41
  context "A MySQL dataset" do
@@ -335,6 +347,11 @@ context "A MySQL database" do
335
347
  proc {db.test_connection}.should_not raise_error
336
348
  end
337
349
 
350
+ specify "should accept a socket option without host option" do
351
+ db = Sequel.mysql('sandbox', :user => 'root', :socket => '/tmp/mysql.sock')
352
+ proc {db.test_connection}.should_not raise_error
353
+ end
354
+
338
355
  specify "should fail to connect with invalid socket" do
339
356
  db = Sequel.mysql('sandbox', :host => 'localhost', :user => 'root', :socket => 'blah')
340
357
  proc {db.test_connection}.should raise_error
@@ -1,17 +1,35 @@
1
1
  require File.join(File.dirname(__FILE__), '../../lib/sequel_core')
2
2
 
3
- ORACLE_DB = Sequel('oracle://hr:hr@loalhost/XE')
4
- if ORACLE_DB.table_exists?(:test)
5
- ORACLE_DB.drop_table :test
3
+ ORACLE_DB = Sequel('oracle://hr:hr@localhost/XE')
4
+
5
+ if ORACLE_DB.table_exists?(:items)
6
+ ORACLE_DB.drop_table :items
6
7
  end
7
- ORACLE_DB.create_table :test do
8
- text :name
9
- integer :value
8
+ ORACLE_DB.create_table :items do
9
+ varchar2 :name, :size => 50
10
+ number :value, :size => 38
10
11
 
11
12
  index :value
12
13
  end
13
14
 
14
- context "A Oracle database" do
15
+ if ORACLE_DB.table_exists?(:books)
16
+ ORACLE_DB.drop_table :books
17
+ end
18
+ ORACLE_DB.create_table :books do
19
+ number :id, :size => 38
20
+ varchar2 :title, :size => 50
21
+ number :category_id, :size => 38
22
+ end
23
+
24
+ if ORACLE_DB.table_exists?(:categories)
25
+ ORACLE_DB.drop_table :categories
26
+ end
27
+ ORACLE_DB.create_table :categories do
28
+ number :id, :size => 38
29
+ varchar2 :cat_name, :size => 50
30
+ end
31
+
32
+ context "An Oracle database" do
15
33
  specify "should provide disconnect functionality" do
16
34
  ORACLE_DB.execute("select user from dual")
17
35
  ORACLE_DB.pool.size.should == 1
@@ -20,9 +38,9 @@ context "A Oracle database" do
20
38
  end
21
39
  end
22
40
 
23
- context "A Oracle dataset" do
41
+ context "An Oracle dataset" do
24
42
  setup do
25
- @d = ORACLE_DB[:test]
43
+ @d = ORACLE_DB[:items]
26
44
  @d.delete # remove all records
27
45
  end
28
46
 
@@ -45,6 +63,83 @@ context "A Oracle dataset" do
45
63
  {:name => 'abc', :value => 456},
46
64
  {:name => 'def', :value => 789}
47
65
  ]
66
+
67
+ @d.select(:name).uniq.order_by(:name).to_a.should == [
68
+ {:name => 'abc'},
69
+ {:name => 'def'}
70
+ ]
71
+
72
+ @d.order(:value.DESC).limit(1).to_a.should == [
73
+ {:name => 'def', :value => 789}
74
+ ]
75
+
76
+ @d.filter(:name => 'abc').to_a.should == [
77
+ {:name => 'abc', :value => 123},
78
+ {:name => 'abc', :value => 456}
79
+ ]
80
+
81
+ @d.order(:value.DESC).filter(:name => 'abc').to_a.should == [
82
+ {:name => 'abc', :value => 456},
83
+ {:name => 'abc', :value => 123}
84
+ ]
85
+
86
+ @d.filter(:name => 'abc').limit(1).to_a.should == [
87
+ {:name => 'abc', :value => 123}
88
+ ]
89
+
90
+ @d.filter(:name => 'abc').order(:value.DESC).limit(1).to_a.should == [
91
+ {:name => 'abc', :value => 456}
92
+ ]
93
+
94
+ @d.filter(:name => 'abc').order(:value).limit(1).to_a.should == [
95
+ {:name => 'abc', :value => 123}
96
+ ]
97
+
98
+ @d.order(:value).limit(1).to_a.should == [
99
+ {:name => 'abc', :value => 123}
100
+ ]
101
+
102
+ @d.order(:value).limit(1, 1).to_a.should == [
103
+ {:name => 'abc', :value => 456}
104
+ ]
105
+
106
+ @d.order(:value).limit(1, 2).to_a.should == [
107
+ {:name => 'def', :value => 789}
108
+ ]
109
+
110
+ @d.avg(:value).to_i.should == (789+123+456)/3
111
+
112
+ @d.max(:value).to_i.should == 789
113
+
114
+ @d.select(:name, :AVG[:value]).filter(:name => 'abc').group(:name).to_a.should == [
115
+ {:name => 'abc', :"avg(value)" => (456+123)/2.0}
116
+ ]
117
+
118
+ @d.select(:AVG[:value]).group(:name).order(:name).limit(1).to_a.should == [
119
+ {:"avg(value)" => (456+123)/2.0}
120
+ ]
121
+
122
+ @d.select(:name, :AVG[:value]).group(:name).order(:name).to_a.should == [
123
+ {:name => 'abc', :"avg(value)" => (456+123)/2.0},
124
+ {:name => 'def', :"avg(value)" => 789*1.0}
125
+ ]
126
+
127
+ @d.select(:name, :AVG[:value]).group(:name).order(:name).to_a.should == [
128
+ {:name => 'abc', :"avg(value)" => (456+123)/2.0},
129
+ {:name => 'def', :"avg(value)" => 789*1.0}
130
+ ]
131
+
132
+ @d.select(:name, :AVG[:value]).group(:name).having(:name => ['abc', 'def']).order(:name).to_a.should == [
133
+ {:name => 'abc', :"avg(value)" => (456+123)/2.0},
134
+ {:name => 'def', :"avg(value)" => 789*1.0}
135
+ ]
136
+
137
+ @d.select(:name, :value).filter(:name => 'abc').union(@d.select(:name, :value).filter(:name => 'def')).order(:value).to_a.should == [
138
+ {:name => 'abc', :value => 123},
139
+ {:name => 'abc', :value => 456},
140
+ {:name => 'def', :value => 789}
141
+ ]
142
+
48
143
  end
49
144
 
50
145
  specify "should update records correctly" do
@@ -83,9 +178,51 @@ context "A Oracle dataset" do
83
178
  end
84
179
  end
85
180
 
86
- context "A Oracle dataset in array tuples mode" do
181
+ context "Joined Oracle dataset" do
182
+ setup do
183
+ @d1 = ORACLE_DB[:books]
184
+ @d1.delete # remove all records
185
+ @d1 << {:id => 1, :title => 'aaa', :category_id => 100}
186
+ @d1 << {:id => 2, :title => 'bbb', :category_id => 100}
187
+ @d1 << {:id => 3, :title => 'ccc', :category_id => 101}
188
+ @d1 << {:id => 4, :title => 'ddd', :category_id => 102}
189
+
190
+ @d2 = ORACLE_DB[:categories]
191
+ @d2.delete # remove all records
192
+ @d2 << {:id => 100, :cat_name => 'ruby'}
193
+ @d2 << {:id => 101, :cat_name => 'rails'}
194
+ end
195
+
196
+ specify "should return correct result" do
197
+ @d1.join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id).to_a.should == [
198
+ {:id => 1, :title => 'aaa', :cat_name => 'ruby'},
199
+ {:id => 2, :title => 'bbb', :cat_name => 'ruby'},
200
+ {:id => 3, :title => 'ccc', :cat_name => 'rails'}
201
+ ]
202
+
203
+ @d1.join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id).limit(2, 1).to_a.should == [
204
+ {:id => 2, :title => 'bbb', :cat_name => 'ruby'},
205
+ {:id => 3, :title => 'ccc', :cat_name => 'rails'},
206
+ ]
207
+
208
+ @d1.left_outer_join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id).to_a.should == [
209
+ {:id => 1, :title => 'aaa', :cat_name => 'ruby'},
210
+ {:id => 2, :title => 'bbb', :cat_name => 'ruby'},
211
+ {:id => 3, :title => 'ccc', :cat_name => 'rails'},
212
+ {:id => 4, :title => 'ddd', :cat_name => nil}
213
+ ]
214
+
215
+ @d1.left_outer_join(:categories, :id => :category_id).select(:books__id, :title, :cat_name).order(:books__id.DESC).limit(2, 0).to_a.should == [
216
+ {:id => 4, :title => 'ddd', :cat_name => nil},
217
+ {:id => 3, :title => 'ccc', :cat_name => 'rails'}
218
+ ]
219
+ end
220
+ end
221
+
222
+
223
+ context "An Oracle dataset in array tuples mode" do
87
224
  setup do
88
- @d = ORACLE_DB[:test]
225
+ @d = ORACLE_DB[:items]
89
226
  @d.delete # remove all records
90
227
  Sequel.use_array_tuples
91
228
  end
@@ -105,6 +242,15 @@ context "A Oracle dataset in array tuples mode" do
105
242
  ['abc', 456],
106
243
  ['def', 789]
107
244
  ]
245
+
246
+ @d.order(:value).select(:name, :value).limit(1).to_a.should == [
247
+ ['abc',123]
248
+ ]
249
+
250
+ @d.order(:value).select(:name, :value).limit(2,1).to_a.should == [
251
+ ['abc',456],
252
+ ['def',789]
253
+ ]
108
254
  end
109
255
 
110
256
  specify "should work correctly with transforms" do