sequel_core 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ === 1.0.4 (2008-01-24)
2
+
3
+ * Added Dataset#select_all method.
4
+
5
+ * Changed ODBC::Database to support connection using driver and database name, also added support for untitled columns in ODBC::Dataset (thanks Leonid Borisenko).
6
+
7
+ * Fixed MySQL adapter to correctly format foreign key definitions (#123).
8
+
9
+ * Changed MySQL::Dataset to allow HAVING clause on ungrouped datasets, and put HAVING clause before ORDER BY clause (#133).
10
+
11
+ * Changed Dataset#group_and_count to accept multiple columns (#134).
12
+
13
+ * Fixed database spec to open YAML file in binary mode (#131).
14
+
15
+ * Cleaned up gem spec (#132).
16
+
17
+ * Added Dataset#table_exists? convenience method.
18
+
1
19
  === 1.0.3 (2008-01-17)
2
20
 
3
21
  * Added support for UNSIGNED constraint, used in MySQL? (#127).
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ include FileUtils
9
9
  # Configuration
10
10
  ##############################################################################
11
11
  NAME = "sequel_core"
12
- VERS = "1.0.3"
12
+ VERS = "1.0.4"
13
13
  CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
14
14
  RDOC_OPTS = [
15
15
  "--quiet",
@@ -61,11 +61,6 @@ spec = Gem::Specification.new do |s|
61
61
  s.add_dependency("assistance", ">= 0.1")
62
62
 
63
63
  case RUBY_PLATFORM
64
- when /mswin/
65
- s.platform = Gem::Platform::CURRENT
66
- s.add_dependency("RubyInline", ">= 3.6.6")
67
- s.add_dependency("ParseTree", ">= 2.1.1")
68
- s.add_dependency("ruby2ruby")
69
64
  when /java/
70
65
  s.platform = "jruby"
71
66
  else
@@ -162,6 +157,11 @@ Spec::Rake::SpecTask.new("rcov") do |t|
162
157
  t.rcov = true
163
158
  end
164
159
 
160
+ desc "check documentation coverage"
161
+ task :dcov do
162
+ sh "find lib -name '*.rb' | xargs dcov"
163
+ end
164
+
165
165
  ##############################################################################
166
166
  # Statistics
167
167
  ##############################################################################
@@ -41,7 +41,7 @@ class Mysql::Result
41
41
  @column_types = []
42
42
  @columns = fetch_fields.map do |f|
43
43
  @column_types << f.type
44
- (with_table ? (f.table + "." + f.name) : f.name).to_sym
44
+ (with_table ? "#{f.table}.#{f.name}" : f.name).to_sym
45
45
  end
46
46
  end
47
47
  @columns
@@ -162,6 +162,28 @@ module Sequel
162
162
  super(table, op)
163
163
  end
164
164
  end
165
+
166
+ def column_definition_sql(column)
167
+ if column[:type] == :check
168
+ return constraint_definition_sql(column)
169
+ end
170
+ sql = "#{literal(column[:name].to_sym)} #{TYPES[column[:type]]}"
171
+ column[:size] ||= 255 if column[:type] == :varchar
172
+ elements = column[:size] || column[:elements]
173
+ sql << "(#{literal(elements)})" if elements
174
+ sql << UNIQUE if column[:unique]
175
+ sql << NOT_NULL if column[:null] == false
176
+ sql << UNSIGNED if column[:unsigned]
177
+ sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
178
+ sql << PRIMARY_KEY if column[:primary_key]
179
+ sql << " #{auto_increment_sql}" if column[:auto_increment]
180
+ if column[:table]
181
+ sql << ", FOREIGN KEY (#{literal(column[:name].to_sym)}) REFERENCES #{column[:table]}"
182
+ sql << "(#{literal(column[:key])})" if column[:key]
183
+ sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
184
+ end
185
+ sql
186
+ end
165
187
 
166
188
  def transaction
167
189
  @pool.hold do |conn|
@@ -217,6 +239,76 @@ module Sequel
217
239
  end
218
240
  end
219
241
 
242
+ # MySQL expects the having clause before the order by clause.
243
+ def select_sql(opts = nil)
244
+ opts = opts ? @opts.merge(opts) : @opts
245
+
246
+ if sql = opts[:sql]
247
+ return sql
248
+ end
249
+
250
+ columns = opts[:select]
251
+ select_columns = columns ? column_list(columns) : WILDCARD
252
+
253
+ if distinct = opts[:distinct]
254
+ distinct_clause = distinct.empty? ? "DISTINCT" : "DISTINCT ON (#{column_list(distinct)})"
255
+ sql = "SELECT #{distinct_clause} #{select_columns}"
256
+ else
257
+ sql = "SELECT #{select_columns}"
258
+ end
259
+
260
+ if opts[:from]
261
+ sql << " FROM #{source_list(opts[:from])}"
262
+ end
263
+
264
+ if join = opts[:join]
265
+ sql << join
266
+ end
267
+
268
+ if where = opts[:where]
269
+ sql << " WHERE #{where}"
270
+ end
271
+
272
+ if group = opts[:group]
273
+ sql << " GROUP BY #{column_list(group)}"
274
+ end
275
+
276
+ if having = opts[:having]
277
+ sql << " HAVING #{having}"
278
+ end
279
+
280
+ if order = opts[:order]
281
+ sql << " ORDER BY #{column_list(order)}"
282
+ end
283
+
284
+ if limit = opts[:limit]
285
+ sql << " LIMIT #{limit}"
286
+ if offset = opts[:offset]
287
+ sql << " OFFSET #{offset}"
288
+ end
289
+ end
290
+
291
+ if union = opts[:union]
292
+ sql << (opts[:union_all] ? \
293
+ " UNION ALL #{union.sql}" : " UNION #{union.sql}")
294
+ elsif intersect = opts[:intersect]
295
+ sql << (opts[:intersect_all] ? \
296
+ " INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
297
+ elsif except = opts[:except]
298
+ sql << (opts[:except_all] ? \
299
+ " EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
300
+ end
301
+
302
+ sql
303
+ end
304
+ alias_method :sql, :select_sql
305
+
306
+ # MySQL allows HAVING clause on ungrouped datasets.
307
+ def having(*cond, &block)
308
+ @opts[:having] = {}
309
+ filter(*cond, &block)
310
+ end
311
+
220
312
  # MySQL supports ORDER and LIMIT clauses in UPDATE statements.
221
313
  def update_sql(values, opts = nil)
222
314
  sql = super
@@ -5,12 +5,34 @@ module Sequel
5
5
  class Database < Sequel::Database
6
6
  set_adapter_scheme :odbc
7
7
 
8
+ # def connect
9
+ # conn = ::ODBC::connect(@opts[:database], @opts[:user], @opts[:password])
10
+ # conn.autocommit = true
11
+ # conn
12
+ # end
13
+
14
+ GUARDED_DRV_NAME = /^\{.+\}$/.freeze
15
+ DRV_NAME_GUARDS = '{%s}'.freeze
16
+
8
17
  def connect
9
- conn = ::ODBC::connect(@opts[:database], @opts[:user], @opts[:password])
18
+ if @opts.include? :driver
19
+ drv = ::ODBC::Driver.new
20
+ drv.name = 'Sequel ODBC Driver130'
21
+ @opts.each do |param, value|
22
+ if :driver == param and not (value =~ GUARDED_DRV_NAME)
23
+ value = DRV_NAME_GUARDS % value
24
+ end
25
+ drv.attrs[param.to_s.capitalize] = value
26
+ end
27
+ db = ::ODBC::Database.new
28
+ conn = db.drvconnect(drv)
29
+ else
30
+ conn = ::ODBC::connect(@opts[:database], @opts[:user], @opts[:password])
31
+ end
10
32
  conn.autocommit = true
11
33
  conn
12
- end
13
-
34
+ end
35
+
14
36
  def disconnect
15
37
  @pool.disconnect {|c| c.disconnect}
16
38
  end
@@ -49,19 +71,41 @@ module Sequel
49
71
  end
50
72
  end
51
73
 
74
+ UNTITLED_COLUMN = 'untitled_%d'.freeze
75
+
52
76
  def fetch_rows(sql, &block)
53
77
  @db.synchronize do
54
78
  s = @db.execute sql
55
79
  begin
56
- @columns = s.columns(true).map {|c| c.name.to_sym}
80
+ untitled_count = 0
81
+ @columns = s.columns(true).map do |c|
82
+ if (n = c.name).empty?
83
+ n = UNTITLED_COLUMN % (untitled_count += 1)
84
+ end
85
+ n.to_sym
86
+ end
57
87
  rows = s.fetch_all
58
- rows.each {|row| yield hash_row(row)}
88
+ rows.each {|row| yield hash_row(row)} if rows
59
89
  ensure
60
90
  s.drop unless s.nil? rescue nil
61
91
  end
62
92
  end
63
93
  self
64
94
  end
95
+
96
+ # def fetch_rows(sql, &block)
97
+ # @db.synchronize do
98
+ # s = @db.execute sql
99
+ # begin
100
+ # @columns = s.columns(true).map {|c| c.name.to_sym}
101
+ # rows = s.fetch_all
102
+ # rows.each {|row| yield hash_row(row)}
103
+ # ensure
104
+ # s.drop unless s.nil? rescue nil
105
+ # end
106
+ # end
107
+ # self
108
+ # end
65
109
 
66
110
  def hash_row(row)
67
111
  hash = {}
@@ -22,3 +22,4 @@ class Object
22
22
  nil
23
23
  end
24
24
  end
25
+
@@ -277,7 +277,8 @@ module Sequel
277
277
  if respond_to?(:tables)
278
278
  tables.include?(name.to_sym)
279
279
  else
280
- from(name).first && true
280
+ from(name).first
281
+ true
281
282
  end
282
283
  rescue
283
284
  false
@@ -176,9 +176,11 @@ module Sequel
176
176
  single_value(:select => [column.AVG.AS(:v)])
177
177
  end
178
178
 
179
+ COUNT_OF_ALL_AS_COUNT = :count['*'.lit].AS(:count)
180
+
179
181
  # Returns a dataset grouped by the given column with count by group.
180
- def group_and_count(column)
181
- group(column).select(column, :count[column].AS(:count)).order(:count)
182
+ def group_and_count(*columns)
183
+ group(*columns).select(columns + [COUNT_OF_ALL_AS_COUNT]).order(:count)
182
184
  end
183
185
 
184
186
  # Returns a Range object made from the minimum and maximum values for the
@@ -316,6 +318,23 @@ module Sequel
316
318
  def create_or_replace_view(name)
317
319
  @db.create_or_replace_view(name, self)
318
320
  end
321
+
322
+ def table_exists?
323
+ if @opts[:sql]
324
+ raise Sequel::Error, "this dataset has fixed SQL"
325
+ end
326
+
327
+ if @opts[:from].size != 1
328
+ raise Sequel::Error, "this dataset selects from multiple sources"
329
+ end
330
+
331
+ t = @opts[:from].first
332
+ if t.is_a?(Dataset)
333
+ raise Sequel::Error, "this dataset selects from a sub query"
334
+ end
335
+
336
+ @db.table_exists?(t.to_sym)
337
+ end
319
338
  end
320
339
  end
321
340
  end
@@ -156,6 +156,11 @@ module Sequel
156
156
  def select(*columns)
157
157
  clone_merge(:select => columns)
158
158
  end
159
+
160
+ # Returns a copy of the dataset selecting the wildcard.
161
+ def select_all
162
+ clone_merge(:select => nil)
163
+ end
159
164
 
160
165
  # Returns a copy of the dataset with the distinct option.
161
166
  def uniq(*args)
@@ -62,12 +62,12 @@ module Sequel
62
62
  sql << UNSIGNED if column[:unsigned]
63
63
  sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
64
64
  sql << PRIMARY_KEY if column[:primary_key]
65
+ sql << " #{auto_increment_sql}" if column[:auto_increment]
65
66
  if column[:table]
66
67
  sql << " REFERENCES #{column[:table]}"
67
68
  sql << "(#{column[:key]})" if column[:key]
69
+ sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
68
70
  end
69
- sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
70
- sql << " #{auto_increment_sql}" if column[:auto_increment]
71
71
  sql
72
72
  end
73
73
 
@@ -267,12 +267,25 @@ end
267
267
  context "Joiמed MySQL dataset" do
268
268
  setup do
269
269
  @ds = MYSQL_DB[:nodes].join(:attributes, :node_id => :id)
270
+ @ds2 = MYSQL_DB[:nodes]
270
271
  end
271
272
 
272
273
  specify "should quote fields correctly" do
273
274
  @ds.sql.should == \
274
275
  "SELECT * FROM nodes INNER JOIN attributes ON (attributes.`node_id` = nodes.`id`)"
275
276
  end
277
+
278
+ specify "should allow a having clause on ungrouped datasets" do
279
+ proc {@ds2.having('blah')}.should_not raise_error
280
+
281
+ @ds2.having('blah').sql.should == \
282
+ "SELECT * FROM nodes HAVING blah"
283
+ end
284
+
285
+ specify "should put a having clause before an order by clause" do
286
+ @ds2.order(:aaa).having(:bbb => :ccc).sql.should == \
287
+ "SELECT * FROM nodes HAVING (`bbb` = `ccc`) ORDER BY `aaa`"
288
+ end
276
289
  end
277
290
 
278
291
  context "A MySQL database" do
@@ -339,6 +352,16 @@ context "A MySQL database" do
339
352
  "CREATE TABLE items (`active1` boolean DEFAULT 1, `active2` boolean DEFAULT 0)"
340
353
  ]
341
354
  end
355
+
356
+ specify "should correctly format CREATE TABLE statements with foreign keys" do
357
+ g = Sequel::Schema::Generator.new(@db) do
358
+ foreign_key :p_id, :table => :users, :key => :id,
359
+ :null => false, :on_delete => :cascade
360
+ end
361
+ @db.create_table_sql_list(:items, *g.create_info).should == [
362
+ "CREATE TABLE items (`p_id` integer NOT NULL, FOREIGN KEY (`p_id`) REFERENCES users(`id`) ON DELETE CASCADE)"
363
+ ]
364
+ end
342
365
  end
343
366
 
344
367
  context "A MySQL database" do
@@ -400,7 +400,6 @@ context "Database#table_exists?" do
400
400
  @db = DummyDatabase.new
401
401
  @db.stub!(:tables).and_return([:a, :b])
402
402
  @db2 = DummyDatabase.new
403
- Sequel::Dataset.stub!(:first).and_return(nil)
404
403
  end
405
404
 
406
405
  specify "should use Database#tables if available" do
@@ -415,7 +414,6 @@ context "Database#table_exists?" do
415
414
  end
416
415
  end
417
416
 
418
-
419
417
  class Dummy3Database < Sequel::Database
420
418
  attr_reader :sql, :transactions
421
419
  def execute(sql); @sql ||= []; @sql << sql; end
@@ -820,7 +818,7 @@ context "Database.connect" do
820
818
  end
821
819
 
822
820
  @fn = File.join(File.dirname(__FILE__), 'eee.yaml')
823
- File.open(@fn, 'w') {|f| f << EEE_YAML}
821
+ File.open(@fn, 'wb') {|f| f << EEE_YAML}
824
822
  end
825
823
 
826
824
  teardown do
@@ -364,6 +364,11 @@ context "Dataset#where" do
364
364
  proc {@dataset.filter(:a == 1)}.should raise_error(Sequel::Error::InvalidFilter)
365
365
  proc {@dataset.filter(:a != 1)}.should raise_error(Sequel::Error::InvalidFilter)
366
366
  end
367
+
368
+ specify "should work for grouped datasets" do
369
+ @dataset.group(:a).filter(:b => 1).sql.should ==
370
+ 'SELECT * FROM test WHERE (b = 1) GROUP BY a'
371
+ end
367
372
  end
368
373
 
369
374
  context "Dataset#or" do
@@ -684,8 +689,8 @@ context "Dataset#select" do
684
689
  end
685
690
 
686
691
  specify "should overrun the previous select option" do
687
- @d.select(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
688
- @d.select(:price).select(:name).sql.should == 'SELECT name FROM test'
692
+ @d.select!(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
693
+ @d.select!(:price).select(:name).sql.should == 'SELECT name FROM test'
689
694
  end
690
695
 
691
696
  specify "should accept arbitrary objects and literalize them correctly" do
@@ -697,6 +702,21 @@ context "Dataset#select" do
697
702
  end
698
703
  end
699
704
 
705
+ context "Dataset#select_all" do
706
+ setup do
707
+ @d = Sequel::Dataset.new(nil).from(:test)
708
+ end
709
+
710
+ specify "should select the wildcard" do
711
+ @d.select_all.sql.should == 'SELECT * FROM test'
712
+ end
713
+
714
+ specify "should overrun the previous select option" do
715
+ @d.select!(:a, :b, :c).select_all.sql.should == 'SELECT * FROM test'
716
+ end
717
+ end
718
+
719
+
700
720
  context "Dataset#order" do
701
721
  setup do
702
722
  @dataset = Sequel::Dataset.new(nil).from(:test)
@@ -953,9 +973,14 @@ context "Dataset#group_and_count" do
953
973
  end
954
974
 
955
975
  specify "should format SQL properly" do
956
- @ds.group_and_count(:name).sql.should == "SELECT name, count(name) AS count FROM test GROUP BY name ORDER BY count"
976
+ @ds.group_and_count(:name).sql.should == "SELECT name, count(*) AS count FROM test GROUP BY name ORDER BY count"
977
+ end
978
+
979
+ specify "should accept multiple columns for grouping" do
980
+ @ds.group_and_count(:a, :b).sql.should == "SELECT a, b, count(*) AS count FROM test GROUP BY a, b ORDER BY count"
957
981
  end
958
982
  end
983
+
959
984
  context "Dataset#empty?" do
960
985
  specify "should return true if #count == 0" do
961
986
  @c = Class.new(Sequel::Dataset) do
@@ -2297,7 +2322,7 @@ context "Dataset magic methods" do
2297
2322
  proc {@ds.count_by_name}.should_not raise_error
2298
2323
  @ds.should respond_to(:count_by_name)
2299
2324
  @ds.count_by_name.should be_a_kind_of(@c)
2300
- @ds.count_by_name.sql.should == "SELECT name, count(name) AS count FROM items GROUP BY name ORDER BY count"
2325
+ @ds.count_by_name.sql.should == "SELECT name, count(*) AS count FROM items GROUP BY name ORDER BY count"
2301
2326
  end
2302
2327
 
2303
2328
  specify "should support filter_by_xxx" do
@@ -2399,5 +2424,58 @@ context "Dataset#update_sql" do
2399
2424
  specify "should accept array subscript references" do
2400
2425
  @ds.update_sql((:day|1) => 'd').should == "UPDATE items SET day[1] = 'd'"
2401
2426
  end
2427
+ end
2428
+
2429
+ class DummyMummyDataset < Sequel::Dataset
2430
+ def first
2431
+ raise if @opts[:from] == [:a]
2432
+ true
2433
+ end
2434
+ end
2435
+
2436
+ class DummyMummyDatabase < Sequel::Database
2437
+ attr_reader :sqls
2438
+
2439
+ def execute(sql)
2440
+ @sqls ||= []
2441
+ @sqls << sql
2442
+ end
2443
+
2444
+ def transaction; yield; end
2445
+
2446
+ def dataset
2447
+ DummyMummyDataset.new(self)
2448
+ end
2449
+ end
2450
+
2451
+ context "Dataset#table_exists?" do
2452
+ setup do
2453
+ @db = DummyMummyDatabase.new
2454
+ @db.stub!(:tables).and_return([:a, :b])
2455
+ @db2 = DummyMummyDatabase.new
2456
+ end
2457
+
2458
+ specify "should use Database#tables if available" do
2459
+ @db[:a].table_exists?.should be_true
2460
+ @db[:b].table_exists?.should be_true
2461
+ @db[:c].table_exists?.should be_false
2462
+ end
2463
+
2464
+ specify "should otherwise try to select the first record from the table's dataset" do
2465
+ @db2[:a].table_exists?.should be_false
2466
+ @db2[:b].table_exists?.should be_true
2467
+ end
2468
+
2469
+ specify "should raise Sequel::Error if dataset references more than one table" do
2470
+ proc {@db.from(:a, :b).table_exists?}.should raise_error(Sequel::Error)
2471
+ end
2472
+
2473
+ specify "should raise Sequel::Error if dataset is from a subquery" do
2474
+ proc {@db.from(@db[:a]).table_exists?}.should raise_error(Sequel::Error)
2475
+ end
2476
+
2477
+ specify "should raise Sequel::Error if dataset has fixed sql" do
2478
+ proc {@db['select * from blah'].table_exists?}.should raise_error(Sequel::Error)
2479
+ end
2480
+ end
2402
2481
 
2403
- end
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: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-01-17 00:00:00 +02:00
12
+ date: 2008-01-24 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency