sequel_core 1.0.3 → 1.0.4

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,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