sequel 3.0.0 → 3.1.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.
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