sequel 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data/CHANGELOG +100 -0
  2. data/README.rdoc +3 -3
  3. data/bin/sequel +102 -19
  4. data/doc/reflection.rdoc +83 -0
  5. data/doc/release_notes/3.1.0.txt +406 -0
  6. data/lib/sequel/adapters/ado.rb +11 -0
  7. data/lib/sequel/adapters/amalgalite.rb +5 -20
  8. data/lib/sequel/adapters/do.rb +44 -36
  9. data/lib/sequel/adapters/firebird.rb +29 -43
  10. data/lib/sequel/adapters/jdbc.rb +17 -27
  11. data/lib/sequel/adapters/mysql.rb +35 -40
  12. data/lib/sequel/adapters/odbc.rb +4 -23
  13. data/lib/sequel/adapters/oracle.rb +22 -19
  14. data/lib/sequel/adapters/postgres.rb +6 -15
  15. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  16. data/lib/sequel/adapters/shared/mysql.rb +29 -10
  17. data/lib/sequel/adapters/shared/oracle.rb +6 -8
  18. data/lib/sequel/adapters/shared/postgres.rb +28 -72
  19. data/lib/sequel/adapters/shared/sqlite.rb +5 -3
  20. data/lib/sequel/adapters/sqlite.rb +5 -20
  21. data/lib/sequel/adapters/utils/savepoint_transactions.rb +80 -0
  22. data/lib/sequel/adapters/utils/unsupported.rb +0 -12
  23. data/lib/sequel/core.rb +12 -3
  24. data/lib/sequel/core_sql.rb +1 -8
  25. data/lib/sequel/database.rb +107 -43
  26. data/lib/sequel/database/schema_generator.rb +1 -0
  27. data/lib/sequel/database/schema_methods.rb +38 -4
  28. data/lib/sequel/dataset.rb +6 -0
  29. data/lib/sequel/dataset/convenience.rb +2 -2
  30. data/lib/sequel/dataset/graph.rb +2 -2
  31. data/lib/sequel/dataset/prepared_statements.rb +3 -8
  32. data/lib/sequel/dataset/sql.rb +93 -19
  33. data/lib/sequel/extensions/blank.rb +2 -1
  34. data/lib/sequel/extensions/inflector.rb +4 -3
  35. data/lib/sequel/extensions/migration.rb +13 -2
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pretty_table.rb +4 -0
  38. data/lib/sequel/extensions/query.rb +4 -0
  39. data/lib/sequel/extensions/schema_dumper.rb +100 -24
  40. data/lib/sequel/extensions/string_date_time.rb +3 -4
  41. data/lib/sequel/model.rb +2 -1
  42. data/lib/sequel/model/associations.rb +96 -38
  43. data/lib/sequel/model/base.rb +14 -14
  44. data/lib/sequel/model/plugins.rb +32 -21
  45. data/lib/sequel/plugins/caching.rb +13 -15
  46. data/lib/sequel/plugins/identity_map.rb +107 -0
  47. data/lib/sequel/plugins/lazy_attributes.rb +65 -0
  48. data/lib/sequel/plugins/many_through_many.rb +188 -0
  49. data/lib/sequel/plugins/schema.rb +13 -0
  50. data/lib/sequel/plugins/serialization.rb +53 -37
  51. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  52. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  53. data/lib/sequel/plugins/validation_class_methods.rb +28 -7
  54. data/lib/sequel/plugins/validation_helpers.rb +31 -24
  55. data/lib/sequel/sql.rb +16 -0
  56. data/lib/sequel/version.rb +1 -1
  57. data/spec/adapters/ado_spec.rb +47 -1
  58. data/spec/adapters/firebird_spec.rb +39 -36
  59. data/spec/adapters/mysql_spec.rb +25 -9
  60. data/spec/adapters/postgres_spec.rb +11 -24
  61. data/spec/core/database_spec.rb +54 -13
  62. data/spec/core/dataset_spec.rb +147 -29
  63. data/spec/core/object_graph_spec.rb +6 -1
  64. data/spec/core/schema_spec.rb +34 -0
  65. data/spec/core/spec_helper.rb +0 -2
  66. data/spec/extensions/caching_spec.rb +7 -0
  67. data/spec/extensions/identity_map_spec.rb +158 -0
  68. data/spec/extensions/lazy_attributes_spec.rb +113 -0
  69. data/spec/extensions/many_through_many_spec.rb +813 -0
  70. data/spec/extensions/migration_spec.rb +4 -4
  71. data/spec/extensions/schema_dumper_spec.rb +114 -13
  72. data/spec/extensions/schema_spec.rb +19 -3
  73. data/spec/extensions/serialization_spec.rb +28 -0
  74. data/spec/extensions/single_table_inheritance_spec.rb +25 -1
  75. data/spec/extensions/spec_helper.rb +2 -7
  76. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  77. data/spec/extensions/validation_class_methods_spec.rb +10 -5
  78. data/spec/integration/dataset_test.rb +39 -6
  79. data/spec/integration/eager_loader_test.rb +7 -7
  80. data/spec/integration/spec_helper.rb +0 -1
  81. data/spec/integration/transaction_test.rb +28 -1
  82. data/spec/model/association_reflection_spec.rb +29 -3
  83. data/spec/model/associations_spec.rb +1 -0
  84. data/spec/model/eager_loading_spec.rb +70 -1
  85. data/spec/model/plugins_spec.rb +236 -50
  86. data/spec/model/spec_helper.rb +0 -2
  87. metadata +18 -5
@@ -5,6 +5,15 @@ unless defined?(FIREBIRD_DB)
5
5
  FIREBIRD_DB = Sequel.connect(ENV['SEQUEL_FB_SPEC_DB']||FIREBIRD_URL)
6
6
  end
7
7
 
8
+ def FIREBIRD_DB.sqls
9
+ (@sqls ||= [])
10
+ end
11
+ logger = Object.new
12
+ def logger.method_missing(m, msg)
13
+ FIREBIRD_DB.sqls.push(msg)
14
+ end
15
+ FIREBIRD_DB.logger = logger
16
+
8
17
  FIREBIRD_DB.create_table! :test do
9
18
  varchar :name, :size => 50
10
19
  integer :val, :index => true
@@ -30,7 +39,7 @@ FIREBIRD_DB.create_table! :test6 do
30
39
  blob :val
31
40
  String :val2
32
41
  varchar :val3, :size=>200
33
- text :val4
42
+ String :val4, :text=>true
34
43
  end
35
44
 
36
45
  context "A Firebird database" do
@@ -54,6 +63,7 @@ context "A Firebird dataset" do
54
63
  before do
55
64
  @d = FIREBIRD_DB[:test]
56
65
  @d.delete # remove all records
66
+ @d.quote_identifiers = true
57
67
  end
58
68
 
59
69
  specify "should return the correct record count" do
@@ -236,81 +246,74 @@ end
236
246
  context "A Firebird database" do
237
247
  before do
238
248
  @db = FIREBIRD_DB
249
+ @db.drop_table(:posts) rescue nil
250
+ @db.sqls.clear
239
251
  end
240
252
 
241
253
  specify "should allow us to name the sequences" do
242
- g = Sequel::Schema::Generator.new(FIREBIRD_DB) do
243
- primary_key :id, :sequence_name => "seq_test"
244
- end
245
- FIREBIRD_DB.send(:create_table_sql_list, :posts, *g.create_info).should == [[
254
+ @db.create_table(:posts){primary_key :id, :sequence_name => "seq_test"}
255
+ @db.sqls.should == [
256
+ "DROP SEQUENCE SEQ_TEST",
246
257
  "CREATE TABLE POSTS (ID integer PRIMARY KEY )",
247
258
  "CREATE SEQUENCE SEQ_TEST",
248
259
  " CREATE TRIGGER BI_POSTS_ID for POSTS\n ACTIVE BEFORE INSERT position 0\n as begin\n if ((new.ID is null) or (new.ID = 0)) then\n begin\n new.ID = next value for seq_test;\n end\n end\n\n"
249
- ], "DROP SEQUENCE SEQ_TEST" ]
260
+ ]
250
261
  end
251
262
 
252
263
  specify "should allow us to set the starting position for the sequences" do
253
- g = Sequel::Schema::Generator.new(FIREBIRD_DB) do
254
- primary_key :id, :sequence_start_position => 999
255
- end
256
- FIREBIRD_DB.send(:create_table_sql_list, :posts, *g.create_info).should == [[
264
+ @db.create_table(:posts){primary_key :id, :sequence_start_position => 999}
265
+ @db.sqls.should == [
266
+ "DROP SEQUENCE SEQ_POSTS_ID",
257
267
  "CREATE TABLE POSTS (ID integer PRIMARY KEY )",
258
268
  "CREATE SEQUENCE SEQ_POSTS_ID",
259
269
  "ALTER SEQUENCE SEQ_POSTS_ID RESTART WITH 999",
260
270
  " CREATE TRIGGER BI_POSTS_ID for POSTS\n ACTIVE BEFORE INSERT position 0\n as begin\n if ((new.ID is null) or (new.ID = 0)) then\n begin\n new.ID = next value for seq_posts_id;\n end\n end\n\n"
261
- ], "DROP SEQUENCE SEQ_POSTS_ID" ]
271
+ ]
262
272
  end
263
273
 
264
274
  specify "should allow us to name and set the starting position for the sequences" do
265
- g = Sequel::Schema::Generator.new(FIREBIRD_DB) do
266
- primary_key :id, :sequence_name => "seq_test", :sequence_start_position => 999
267
- end
268
- FIREBIRD_DB.send(:create_table_sql_list, :posts, *g.create_info).should == [[
275
+ @db.create_table(:posts){primary_key :id, :sequence_name => "seq_test", :sequence_start_position => 999}
276
+ @db.sqls.should == [
277
+ "DROP SEQUENCE SEQ_TEST",
269
278
  "CREATE TABLE POSTS (ID integer PRIMARY KEY )",
270
279
  "CREATE SEQUENCE SEQ_TEST",
271
280
  "ALTER SEQUENCE SEQ_TEST RESTART WITH 999",
272
281
  " CREATE TRIGGER BI_POSTS_ID for POSTS\n ACTIVE BEFORE INSERT position 0\n as begin\n if ((new.ID is null) or (new.ID = 0)) then\n begin\n new.ID = next value for seq_test;\n end\n end\n\n"
273
- ], "DROP SEQUENCE SEQ_TEST" ]
282
+ ]
274
283
  end
275
284
 
276
285
  specify "should allow us to name the triggers" do
277
- g = Sequel::Schema::Generator.new(FIREBIRD_DB) do
278
- primary_key :id, :trigger_name => "trig_test"
279
- end
280
- FIREBIRD_DB.send(:create_table_sql_list, :posts, *g.create_info).should == [[
286
+ @db.create_table(:posts){primary_key :id, :trigger_name => "trig_test"}
287
+ @db.sqls.should == [
288
+ "DROP SEQUENCE SEQ_POSTS_ID",
281
289
  "CREATE TABLE POSTS (ID integer PRIMARY KEY )",
282
290
  "CREATE SEQUENCE SEQ_POSTS_ID",
283
291
  " CREATE TRIGGER TRIG_TEST for POSTS\n ACTIVE BEFORE INSERT position 0\n as begin\n if ((new.ID is null) or (new.ID = 0)) then\n begin\n new.ID = next value for seq_posts_id;\n end\n end\n\n"
284
- ], "DROP SEQUENCE SEQ_POSTS_ID" ]
292
+ ]
285
293
  end
286
294
 
287
295
  specify "should allow us to not create the sequence" do
288
- g = Sequel::Schema::Generator.new(FIREBIRD_DB) do
289
- primary_key :id, :create_sequence => false
290
- end
291
- FIREBIRD_DB.send(:create_table_sql_list, :posts, *g.create_info).should == [[
296
+ @db.create_table(:posts){primary_key :id, :create_sequence => false}
297
+ @db.sqls.should == [
292
298
  "CREATE TABLE POSTS (ID integer PRIMARY KEY )",
293
299
  " CREATE TRIGGER BI_POSTS_ID for POSTS\n ACTIVE BEFORE INSERT position 0\n as begin\n if ((new.ID is null) or (new.ID = 0)) then\n begin\n new.ID = next value for seq_posts_id;\n end\n end\n\n"
294
- ], nil]
300
+ ]
295
301
  end
296
302
 
297
303
  specify "should allow us to not create the trigger" do
298
- g = Sequel::Schema::Generator.new(FIREBIRD_DB) do
299
- primary_key :id, :create_trigger => false
300
- end
301
- FIREBIRD_DB.send(:create_table_sql_list, :posts, *g.create_info).should == [[
304
+ @db.create_table(:posts){primary_key :id, :create_trigger => false}
305
+ @db.sqls.should == [
306
+ "DROP SEQUENCE SEQ_POSTS_ID",
302
307
  "CREATE TABLE POSTS (ID integer PRIMARY KEY )",
303
308
  "CREATE SEQUENCE SEQ_POSTS_ID",
304
- ], "DROP SEQUENCE SEQ_POSTS_ID"]
309
+ ]
305
310
  end
306
311
 
307
312
  specify "should allow us to not create either the sequence nor the trigger" do
308
- g = Sequel::Schema::Generator.new(FIREBIRD_DB) do
309
- primary_key :id, :create_sequence => false, :create_trigger => false
310
- end
311
- FIREBIRD_DB.send(:create_table_sql_list, :posts, *g.create_info).should == [[
313
+ @db.create_table(:posts){primary_key :id, :create_sequence => false, :create_trigger => false}
314
+ @db.sqls.should == [
312
315
  "CREATE TABLE POSTS (ID integer PRIMARY KEY )"
313
- ], nil]
316
+ ]
314
317
  end
315
318
 
316
319
  specify "should support column operations" do
@@ -29,15 +29,9 @@ MYSQL_DB.drop_table(:items) rescue nil
29
29
  MYSQL_DB.drop_table(:dolls) rescue nil
30
30
  MYSQL_DB.drop_table(:booltest) rescue nil
31
31
 
32
- if MYSQL_DB.class.adapter_scheme == :do
33
- SQL_BEGIN = 'Transaction.begin'
34
- SQL_ROLLBACK = 'Transaction.rollback'
35
- SQL_COMMIT = 'Transaction.commit'
36
- else
37
- SQL_BEGIN = 'BEGIN'
38
- SQL_ROLLBACK = 'ROLLBACK'
39
- SQL_COMMIT = 'COMMIT'
40
- end
32
+ SQL_BEGIN = 'BEGIN'
33
+ SQL_ROLLBACK = 'ROLLBACK'
34
+ SQL_COMMIT = 'COMMIT'
41
35
 
42
36
  context "MySQL", '#create_table' do
43
37
  before do
@@ -57,6 +51,16 @@ context "MySQL", '#create_table' do
57
51
  @db.create_table(:tmp_dolls, :temp => true, :engine => 'MyISAM', :charset => 'latin2'){text :name}
58
52
  @db.sqls.should == ["CREATE TEMPORARY TABLE tmp_dolls (name text) ENGINE=MyISAM DEFAULT CHARSET=latin2"]
59
53
  end
54
+
55
+ specify "should not use a default for a String :text=>true type" do
56
+ @db.create_table(:dolls){String :name, :text=>true, :default=>'blah'}
57
+ @db.sqls.should == ["CREATE TABLE dolls (name text)"]
58
+ end
59
+
60
+ specify "should not use a default for a File type" do
61
+ @db.create_table(:dolls){File :name, :default=>'blah'}
62
+ @db.sqls.should == ["CREATE TABLE dolls (name blob)"]
63
+ end
60
64
  end
61
65
 
62
66
  context "A MySQL database" do
@@ -577,6 +581,12 @@ context "A MySQL database" do
577
581
  "CREATE UNIQUE INDEX posts_id_index USING btree ON posts (id)"
578
582
  ]
579
583
  end
584
+
585
+ specify "should not dump partial indexes" do
586
+ @db.create_table(:posts){text :id}
587
+ @db << "CREATE INDEX posts_id_index ON posts (id(10))"
588
+ @db.indexes(:posts).should == {}
589
+ end
580
590
  end
581
591
 
582
592
  context "MySQL::Dataset#insert and related methods" do
@@ -710,6 +720,12 @@ context "MySQL::Dataset#insert and related methods" do
710
720
  ]
711
721
  end
712
722
 
723
+ specify "#insert_ignore should add the IGNORE keyword for single inserts" do
724
+ @d.insert_ignore.insert(:name => 'ghi')
725
+ MYSQL_DB.sqls.should == ["INSERT IGNORE INTO items (name) VALUES ('ghi')"]
726
+ @d.all.should == [{:name => 'ghi', :value => nil}]
727
+ end
728
+
713
729
  specify "#on_duplicate_key_update should add the ON DUPLICATE KEY UPDATE and ALL columns when no args given" do
714
730
  @d.on_duplicate_key_update.import([:name,:value],
715
731
  [['abc', 1], ['def',2]]
@@ -121,30 +121,6 @@ context "A PostgreSQL dataset" do
121
121
  @d.reverse_order(:name.desc, :test).sql.should == \
122
122
  'SELECT * FROM "test" ORDER BY "name" ASC, "test" DESC'
123
123
  end
124
-
125
- specify "should support nested transactions through savepoints using the savepoint option" do
126
- POSTGRES_DB.transaction do
127
- @d << {:name => '1'}
128
- POSTGRES_DB.transaction(:savepoint=>true) do
129
- @d << {:name => '2'}
130
- POSTGRES_DB.transaction do
131
- @d << {:name => '3'}
132
- raise Sequel::Rollback
133
- end
134
- end
135
- @d << {:name => '4'}
136
- POSTGRES_DB.transaction do
137
- @d << {:name => '6'}
138
- POSTGRES_DB.transaction(:savepoint=>true) do
139
- @d << {:name => '7'}
140
- raise Sequel::Rollback
141
- end
142
- end
143
- @d << {:name => '5'}
144
- end
145
-
146
- @d.order(:name).map(:name).should == %w{1 4 5 6}
147
- end
148
124
 
149
125
  specify "should support regexps" do
150
126
  @d << {:name => 'abc', :value => 1}
@@ -227,6 +203,11 @@ context "A PostgreSQL database" do
227
203
  @db[:posts].order(:a).map(:a).should == [1, 2, 10, 20, 21]
228
204
  end
229
205
 
206
+ specify "should not raise an error if attempting to resetting the primary key sequence for a table without a primary key" do
207
+ @db.create_table(:posts){Integer :a}
208
+ @db.reset_primary_key_sequence(:posts).should == nil
209
+ end
210
+
230
211
  specify "should support fulltext indexes and searching" do
231
212
  @db.create_table(:posts){text :title; text :body; full_text_index [:title, :body]; full_text_index :title, :language => 'french'}
232
213
  @db.sqls.should == [
@@ -340,6 +321,12 @@ context "Postgres::Dataset#insert" do
340
321
  @db.drop_table(:test5) rescue nil
341
322
  end
342
323
 
324
+ specify "should work with static SQL" do
325
+ @ds.with_sql('INSERT INTO test5 (value) VALUES (10)').insert.should == nil
326
+ @db['INSERT INTO test5 (value) VALUES (20)'].insert.should == nil
327
+ @ds.all.should == [{:xid=>1, :value=>10}, {:xid=>2, :value=>20}]
328
+ end
329
+
343
330
  specify "should work regardless of how it is used" do
344
331
  @ds.insert(:value=>10).should == 1
345
332
  @ds.disable_insert_returning.insert(:value=>20).should == 2
@@ -188,6 +188,12 @@ context "Database#disconnect" do
188
188
  end
189
189
  end
190
190
 
191
+ context "Sequel.extension" do
192
+ specify "should attempt to load the given extension" do
193
+ proc{Sequel.extension :blah}.should raise_error(LoadError)
194
+ end
195
+ end
196
+
191
197
  context "Database#connect" do
192
198
  specify "should raise NotImplementedError" do
193
199
  proc {Sequel::Database.new.connect}.should raise_error(NotImplementedError)
@@ -551,19 +557,10 @@ context "Database#rename_table" do
551
557
  end
552
558
 
553
559
  context "Database#table_exists?" do
554
- before do
555
- @db = DummyDatabase.new
556
- @db.instance_variable_set(:@schemas, {:a=>[]})
557
- @db2 = DummyDatabase.new
558
- end
559
-
560
- specify "should use schema information if available" do
561
- @db.table_exists?(:a).should be_true
562
- end
563
-
564
- specify "should otherwise try to select the first record from the table's dataset" do
565
- @db2.table_exists?(:a).should be_false
566
- @db2.table_exists?(:b).should be_true
560
+ specify "should try to select the first record from the table's dataset" do
561
+ db2 = DummyDatabase.new
562
+ db2.table_exists?(:a).should be_false
563
+ db2.table_exists?(:b).should be_true
567
564
  end
568
565
  end
569
566
 
@@ -617,6 +614,14 @@ context "Database#transaction" do
617
614
  @db.sql.should == ['BEGIN', 'DROP TABLE a', 'ROLLBACK']
618
615
  end
619
616
 
617
+ specify "should raise database errors when commiting a transaction as Sequel::DatabaseError" do
618
+ @db.meta_def(:commit_transaction){raise ArgumentError}
619
+ lambda{@db.transaction{}}.should raise_error(ArgumentError)
620
+
621
+ @db.meta_def(:database_error_classes){[ArgumentError]}
622
+ lambda{@db.transaction{}}.should raise_error(Sequel::DatabaseError)
623
+ end
624
+
620
625
  specify "should be re-entrant" do
621
626
  stop = false
622
627
  cc = nil
@@ -1091,6 +1096,10 @@ context "Database#raise_error" do
1091
1096
  specify "should convert the exception to a DatabaseError if opts[:classes] if not present" do
1092
1097
  proc{MockDatabase.new.send(:raise_error, Interrupt.new(''))}.should raise_error(Sequel::DatabaseError)
1093
1098
  end
1099
+
1100
+ specify "should convert the exception to a DatabaseDisconnectError if opts[:disconnect] is true" do
1101
+ proc{MockDatabase.new.send(:raise_error, Interrupt.new(''), :disconnect=>true)}.should raise_error(Sequel::DatabaseDisconnectError)
1102
+ end
1094
1103
  end
1095
1104
 
1096
1105
  context "Database#typecast_value" do
@@ -1140,3 +1149,35 @@ context "Database#schema_autoincrementing_primary_key?" do
1140
1149
  m.call(:primary_key=>false).should == false
1141
1150
  end
1142
1151
  end
1152
+
1153
+ context "Database#supports_savepoints?" do
1154
+ specify "should be false by default" do
1155
+ Sequel::Database.new.supports_savepoints?.should == false
1156
+ end
1157
+ end
1158
+
1159
+ context "Database#input_identifier_meth" do
1160
+ specify "should be the input_identifer method of a default dataset for this database" do
1161
+ db = Sequel::Database.new
1162
+ db.send(:input_identifier_meth).call(:a).should == 'a'
1163
+ db.identifier_input_method = :upcase
1164
+ db.send(:input_identifier_meth).call(:a).should == 'A'
1165
+ end
1166
+ end
1167
+
1168
+ context "Database#output_identifier_meth" do
1169
+ specify "should be the output_identifer method of a default dataset for this database" do
1170
+ db = Sequel::Database.new
1171
+ db.send(:output_identifier_meth).call('A').should == :A
1172
+ db.identifier_output_method = :downcase
1173
+ db.send(:output_identifier_meth).call('A').should == :a
1174
+ end
1175
+ end
1176
+
1177
+ context "Database#metadata_dataset" do
1178
+ specify "should be a dataset with the default settings for identifier_input_method and identifier_output_method" do
1179
+ ds = Sequel::Database.new.send(:metadata_dataset)
1180
+ ds.literal(:a).should == 'A'
1181
+ ds.send(:output_identifier, 'A').should == :a
1182
+ end
1183
+ end
@@ -1858,46 +1858,63 @@ context "Dataset compound operations" do
1858
1858
 
1859
1859
  specify "should support UNION and UNION ALL" do
1860
1860
  @a.union(@b).sql.should == \
1861
- "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)"
1861
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1"
1862
1862
  @b.union(@a, true).sql.should == \
1863
- "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
1863
+ "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
1864
1864
  end
1865
1865
 
1866
1866
  specify "should support INTERSECT and INTERSECT ALL" do
1867
1867
  @a.intersect(@b).sql.should == \
1868
- "SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)"
1868
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS t1"
1869
1869
  @b.intersect(@a, true).sql.should == \
1870
- "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
1870
+ "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
1871
1871
  end
1872
1872
 
1873
1873
  specify "should support EXCEPT and EXCEPT ALL" do
1874
1874
  @a.except(@b).sql.should == \
1875
- "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)"
1875
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1"
1876
1876
  @b.except(@a, true).sql.should == \
1877
- "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
1877
+ "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
1878
1878
  end
1879
1879
 
1880
1880
  specify "should handle chained compound operations" do
1881
1881
  @a.union(@b).union(@a, true).sql.should == \
1882
- "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
1882
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1 UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
1883
1883
  @a.intersect(@b, true).intersect(@a).sql.should == \
1884
- "SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
1884
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM b WHERE (z = 2)) AS t1 INTERSECT SELECT * FROM a WHERE (z = 1)) AS t1"
1885
1885
  @a.except(@b).except(@a, true).sql.should == \
1886
- "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
1887
- @a.union(@b, true).intersect(@a).except(@b, true).union(@a).intersect(@b, true).except(@a).sql.should == \
1888
- "SELECT * FROM a WHERE (z = 1) UNION ALL SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1) EXCEPT ALL SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
1886
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1 EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
1889
1887
  end
1890
1888
 
1891
1889
  specify "should use a subselect when using a compound operation with a dataset that already has a compound operation" do
1892
1890
  @a.union(@b.union(@a, true)).sql.should == \
1893
- "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1))"
1891
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
1894
1892
  @a.intersect(@b.intersect(@a), true).sql.should == \
1895
- "SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1))"
1893
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
1896
1894
  @a.except(@b.except(@a, true)).sql.should == \
1897
- "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1))"
1898
- @a.union(@b.intersect(@a.except(@b, true)), true).union(@a.intersect(@b.except(@a), true)).sql.should == \
1899
- "SELECT * FROM a WHERE (z = 1) UNION ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT ALL SELECT * FROM b WHERE (z = 2))) UNION SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)))"
1895
+ "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
1900
1896
  end
1897
+
1898
+ specify "should order and limit properly when using UNION, INTERSECT, or EXCEPT" do
1899
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1900
+ @dataset.union(@dataset).limit(2).sql.should ==
1901
+ "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 2"
1902
+ @dataset.limit(2).intersect(@dataset).sql.should ==
1903
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1 INTERSECT SELECT * FROM test) AS t1"
1904
+ @dataset.except(@dataset.limit(2)).sql.should ==
1905
+ "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1) AS t1"
1906
+
1907
+ @dataset.union(@dataset).order(:num).sql.should ==
1908
+ "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 ORDER BY num"
1909
+ @dataset.order(:num).intersect(@dataset).sql.should ==
1910
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1 INTERSECT SELECT * FROM test) AS t1"
1911
+ @dataset.except(@dataset.order(:num)).sql.should ==
1912
+ "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1) AS t1"
1913
+
1914
+ @dataset.limit(2).order(:a).union(@dataset.limit(3).order(:b)).order(:c).limit(4).sql.should ==
1915
+ "SELECT * FROM (SELECT * FROM (SELECT * FROM test ORDER BY a LIMIT 2) AS t1 UNION SELECT * FROM (SELECT * FROM test ORDER BY b LIMIT 3) AS t1) AS t1 ORDER BY c LIMIT 4"
1916
+ end
1917
+
1901
1918
  end
1902
1919
 
1903
1920
  context "Dataset#[]" do
@@ -2404,6 +2421,10 @@ context "Dataset#insert_sql" do
2404
2421
  specify "should accept array subscript references" do
2405
2422
  @ds.insert_sql((:day.sql_subscript(1)) => 'd').should == "INSERT INTO items (day[1]) VALUES ('d')"
2406
2423
  end
2424
+
2425
+ specify "should raise an Error if the dataset has no sources" do
2426
+ proc{Sequel::Database.new.dataset.insert_sql}.should raise_error(Sequel::Error)
2427
+ end
2407
2428
  end
2408
2429
 
2409
2430
  class DummyMummyDataset < Sequel::Dataset
@@ -2431,17 +2452,11 @@ end
2431
2452
  context "Dataset#table_exists?" do
2432
2453
  before do
2433
2454
  @db = DummyMummyDatabase.new
2434
- @db.instance_variable_set(:@schemas, {:a=>[]})
2435
- @db2 = DummyMummyDatabase.new
2436
- end
2437
-
2438
- specify "should use the database schema if available" do
2439
- @db[:a].table_exists?.should be_true
2440
2455
  end
2441
2456
 
2442
2457
  specify "should otherwise try to select the first record from the table's dataset" do
2443
- @db2[:a].table_exists?.should be_false
2444
- @db2[:b].table_exists?.should be_true
2458
+ @db[:a].table_exists?.should be_false
2459
+ @db[:b].table_exists?.should be_true
2445
2460
  end
2446
2461
 
2447
2462
  specify "should raise Sequel::Error if dataset references more than one table" do
@@ -2669,17 +2684,35 @@ context Sequel::Dataset::UnnumberedArgumentMapper do
2669
2684
  def @ds.execute(sql, opts={}, &block)
2670
2685
  @db.execute(sql, {:arguments=>bind_arguments}.merge(opts))
2671
2686
  end
2672
- @ps = @ds.prepare(:select, :sn)
2673
- @ps.extend(Sequel::Dataset::UnnumberedArgumentMapper)
2687
+ def @ds.execute_dui(*args, &block)
2688
+ execute(*args, &block)
2689
+ end
2690
+ def @ds.execute_insert(*args, &block)
2691
+ execute(*args, &block)
2692
+ end
2693
+ @ps = []
2694
+ @ps << @ds.prepare(:select, :s)
2695
+ @ps << @ds.prepare(:all, :a)
2696
+ @ps << @ds.prepare(:first, :f)
2697
+ @ps << @ds.prepare(:delete, :d)
2698
+ @ps << @ds.prepare(:insert, :i, :num=>:$n)
2699
+ @ps << @ds.prepare(:update, :u, :num=>:$n)
2700
+ @ps.each{|p| p.extend(Sequel::Dataset::UnnumberedArgumentMapper)}
2674
2701
  end
2675
2702
 
2676
2703
  specify "#inspect should show the actual SQL submitted to the database" do
2677
- @ps.inspect.should == '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
2704
+ @ps.first.inspect.should == '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
2678
2705
  end
2679
2706
 
2680
2707
  specify "should submitted the SQL to the database with placeholders and bind variables" do
2681
- @ps.call(:n=>1)
2682
- @db.sqls.should == [["SELECT * FROM items WHERE (num = ?)", 1]]
2708
+ @ps.each{|p| p.call(:n=>1)}
2709
+ @db.sqls.should == [["SELECT * FROM items WHERE (num = ?)", 1],
2710
+ ["SELECT * FROM items WHERE (num = ?)", 1],
2711
+ ["SELECT * FROM items WHERE (num = ?) LIMIT 1", 1],
2712
+ ["DELETE FROM items WHERE (num = ?)", 1],
2713
+ ["INSERT INTO items (num) VALUES (?)", 1],
2714
+ ["UPDATE items SET num = ? WHERE (num = ?)", 1, 1],
2715
+ ]
2683
2716
  end
2684
2717
  end
2685
2718
 
@@ -2748,3 +2781,88 @@ context "Sequel::Dataset #set_overrides" do
2748
2781
  @ds.set_overrides(:x=>2).update_sql.should == "UPDATE items SET x = 1"
2749
2782
  end
2750
2783
  end
2784
+
2785
+ context "Sequel::Dataset#qualify_to" do
2786
+ specify "should qualify_to the first source" do
2787
+ MockDatabase.new[:t].filter{a<b}.qualify_to(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
2788
+ end
2789
+ end
2790
+
2791
+ context "Sequel::Dataset#qualify_to_first_source" do
2792
+ before do
2793
+ @ds = MockDatabase.new[:t]
2794
+ end
2795
+
2796
+ specify "should qualify_to the first source" do
2797
+ @ds.qualify_to_first_source.sql.should == 'SELECT t.* FROM t'
2798
+ @ds.should_receive(:qualify_to).with(:t).once
2799
+ @ds.qualify_to_first_source
2800
+ end
2801
+
2802
+ specify "should handle the select, order, where, having, and group options/clauses" do
2803
+ @ds.select(:a).filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == \
2804
+ 'SELECT t.a FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
2805
+ end
2806
+
2807
+ specify "should handle the select using a table.* if all columns are currently selected" do
2808
+ @ds.filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == \
2809
+ 'SELECT t.* FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
2810
+ end
2811
+
2812
+ specify "should handle hashes in select option" do
2813
+ @ds.select(:a=>:b).qualify_to_first_source.sql.should == 'SELECT t.a AS b FROM t'
2814
+ end
2815
+
2816
+ specify "should handle symbols" do
2817
+ @ds.select(:a, :b__c, :d___e, :f__g___h).qualify_to_first_source.sql.should == 'SELECT t.a, b.c, t.d AS e, f.g AS h FROM t'
2818
+ end
2819
+
2820
+ specify "should handle arrays" do
2821
+ @ds.filter(:a=>[:b, :c]).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a IN (t.b, t.c))'
2822
+ end
2823
+
2824
+ specify "should handle hashes" do
2825
+ @ds.select({:b=>{:c=>1}}.case(false)).qualify_to_first_source.sql.should == "SELECT (CASE WHEN t.b THEN (t.c = 1) ELSE 'f' END) FROM t"
2826
+ end
2827
+
2828
+ specify "should handle SQL::Identifiers" do
2829
+ @ds.select{a}.qualify_to_first_source.sql.should == 'SELECT t.a FROM t'
2830
+ end
2831
+
2832
+ specify "should handle SQL::OrderedExpressions" do
2833
+ @ds.order(:a.desc, :b.asc).qualify_to_first_source.sql.should == 'SELECT t.* FROM t ORDER BY t.a DESC, t.b ASC'
2834
+ end
2835
+
2836
+ specify "should handle SQL::AliasedExpressions" do
2837
+ @ds.select(:a.as(:b)).qualify_to_first_source.sql.should == 'SELECT t.a AS b FROM t'
2838
+ end
2839
+
2840
+ specify "should handle SQL::CaseExpressions" do
2841
+ @ds.filter{{a=>b}.case(c, d)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (CASE t.d WHEN t.a THEN t.b ELSE t.c END)'
2842
+ end
2843
+
2844
+ specify "should handle SQL:Casts" do
2845
+ @ds.filter{a.cast(:boolean)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE CAST(t.a AS boolean)'
2846
+ end
2847
+
2848
+ specify "should handle SQL::Functions" do
2849
+ @ds.filter{a(b, 1)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE a(t.b, 1)'
2850
+ end
2851
+
2852
+ specify "should handle SQL::ComplexExpressions" do
2853
+ @ds.filter{(a+b)<(c-3)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE ((t.a + t.b) < (t.c - 3))'
2854
+ end
2855
+
2856
+ specify "should handle SQL::SQLArrays" do
2857
+ @ds.filter(:a=>[:b, :c].sql_array).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a IN (t.b, t.c))'
2858
+ end
2859
+
2860
+ specify "should handle SQL::Subscripts" do
2861
+ @ds.filter{a.sql_subscript(b,3)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE t.a[t.b, 3]'
2862
+ end
2863
+
2864
+ specify "should handle all other objects by returning them unchanged" do
2865
+ @ds.select("a").filter{a(3)}.filter('blah').order('true'.lit).group('?'.lit(:a)).having(false).qualify_to_first_source.sql.should == \
2866
+ "SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a HAVING 'f' ORDER BY true"
2867
+ end
2868
+ end