viking-sequel 3.10.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.
- data/CHANGELOG +3134 -0
- data/COPYING +19 -0
- data/README.rdoc +723 -0
- data/Rakefile +193 -0
- data/bin/sequel +196 -0
- data/doc/advanced_associations.rdoc +644 -0
- data/doc/cheat_sheet.rdoc +218 -0
- data/doc/dataset_basics.rdoc +106 -0
- data/doc/dataset_filtering.rdoc +158 -0
- data/doc/opening_databases.rdoc +296 -0
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/reflection.rdoc +84 -0
- data/doc/release_notes/1.0.txt +38 -0
- data/doc/release_notes/1.1.txt +143 -0
- data/doc/release_notes/1.3.txt +101 -0
- data/doc/release_notes/1.4.0.txt +53 -0
- data/doc/release_notes/1.5.0.txt +155 -0
- data/doc/release_notes/2.0.0.txt +298 -0
- data/doc/release_notes/2.1.0.txt +271 -0
- data/doc/release_notes/2.10.0.txt +328 -0
- data/doc/release_notes/2.11.0.txt +215 -0
- data/doc/release_notes/2.12.0.txt +534 -0
- data/doc/release_notes/2.2.0.txt +253 -0
- data/doc/release_notes/2.3.0.txt +88 -0
- data/doc/release_notes/2.4.0.txt +106 -0
- data/doc/release_notes/2.5.0.txt +137 -0
- data/doc/release_notes/2.6.0.txt +157 -0
- data/doc/release_notes/2.7.0.txt +166 -0
- data/doc/release_notes/2.8.0.txt +171 -0
- data/doc/release_notes/2.9.0.txt +97 -0
- data/doc/release_notes/3.0.0.txt +221 -0
- data/doc/release_notes/3.1.0.txt +406 -0
- data/doc/release_notes/3.10.0.txt +286 -0
- data/doc/release_notes/3.2.0.txt +268 -0
- data/doc/release_notes/3.3.0.txt +192 -0
- data/doc/release_notes/3.4.0.txt +325 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/release_notes/3.7.0.txt +179 -0
- data/doc/release_notes/3.8.0.txt +151 -0
- data/doc/release_notes/3.9.0.txt +233 -0
- data/doc/schema.rdoc +36 -0
- data/doc/sharding.rdoc +113 -0
- data/doc/virtual_rows.rdoc +205 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +90 -0
- data/lib/sequel/adapters/ado/mssql.rb +30 -0
- data/lib/sequel/adapters/amalgalite.rb +176 -0
- data/lib/sequel/adapters/db2.rb +139 -0
- data/lib/sequel/adapters/dbi.rb +113 -0
- data/lib/sequel/adapters/do.rb +188 -0
- data/lib/sequel/adapters/do/mysql.rb +49 -0
- data/lib/sequel/adapters/do/postgres.rb +91 -0
- data/lib/sequel/adapters/do/sqlite.rb +40 -0
- data/lib/sequel/adapters/firebird.rb +283 -0
- data/lib/sequel/adapters/informix.rb +77 -0
- data/lib/sequel/adapters/jdbc.rb +587 -0
- data/lib/sequel/adapters/jdbc/as400.rb +58 -0
- data/lib/sequel/adapters/jdbc/h2.rb +133 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
- data/lib/sequel/adapters/mysql.rb +421 -0
- data/lib/sequel/adapters/odbc.rb +143 -0
- data/lib/sequel/adapters/odbc/mssql.rb +42 -0
- data/lib/sequel/adapters/openbase.rb +64 -0
- data/lib/sequel/adapters/oracle.rb +131 -0
- data/lib/sequel/adapters/postgres.rb +504 -0
- data/lib/sequel/adapters/shared/mssql.rb +490 -0
- data/lib/sequel/adapters/shared/mysql.rb +498 -0
- data/lib/sequel/adapters/shared/oracle.rb +195 -0
- data/lib/sequel/adapters/shared/postgres.rb +830 -0
- data/lib/sequel/adapters/shared/progress.rb +44 -0
- data/lib/sequel/adapters/shared/sqlite.rb +389 -0
- data/lib/sequel/adapters/sqlite.rb +224 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
- data/lib/sequel/connection_pool.rb +99 -0
- data/lib/sequel/connection_pool/sharded_single.rb +84 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
- data/lib/sequel/connection_pool/single.rb +29 -0
- data/lib/sequel/connection_pool/threaded.rb +150 -0
- data/lib/sequel/core.rb +293 -0
- data/lib/sequel/core_sql.rb +241 -0
- data/lib/sequel/database.rb +1079 -0
- data/lib/sequel/database/schema_generator.rb +327 -0
- data/lib/sequel/database/schema_methods.rb +203 -0
- data/lib/sequel/database/schema_sql.rb +320 -0
- data/lib/sequel/dataset.rb +32 -0
- data/lib/sequel/dataset/actions.rb +441 -0
- data/lib/sequel/dataset/features.rb +86 -0
- data/lib/sequel/dataset/graph.rb +254 -0
- data/lib/sequel/dataset/misc.rb +119 -0
- data/lib/sequel/dataset/mutation.rb +64 -0
- data/lib/sequel/dataset/prepared_statements.rb +227 -0
- data/lib/sequel/dataset/query.rb +709 -0
- data/lib/sequel/dataset/sql.rb +996 -0
- data/lib/sequel/exceptions.rb +51 -0
- data/lib/sequel/extensions/blank.rb +43 -0
- data/lib/sequel/extensions/inflector.rb +242 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/migration.rb +239 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/pagination.rb +100 -0
- data/lib/sequel/extensions/pretty_table.rb +82 -0
- data/lib/sequel/extensions/query.rb +52 -0
- data/lib/sequel/extensions/schema_dumper.rb +271 -0
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +46 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/metaprogramming.rb +9 -0
- data/lib/sequel/model.rb +120 -0
- data/lib/sequel/model/associations.rb +1514 -0
- data/lib/sequel/model/base.rb +1069 -0
- data/lib/sequel/model/default_inflections.rb +45 -0
- data/lib/sequel/model/errors.rb +39 -0
- data/lib/sequel/model/exceptions.rb +21 -0
- data/lib/sequel/model/inflections.rb +162 -0
- data/lib/sequel/model/plugins.rb +70 -0
- data/lib/sequel/plugins/active_model.rb +59 -0
- data/lib/sequel/plugins/association_dependencies.rb +103 -0
- data/lib/sequel/plugins/association_proxies.rb +41 -0
- data/lib/sequel/plugins/boolean_readers.rb +53 -0
- data/lib/sequel/plugins/caching.rb +141 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/composition.rb +138 -0
- data/lib/sequel/plugins/force_encoding.rb +72 -0
- data/lib/sequel/plugins/hook_class_methods.rb +126 -0
- data/lib/sequel/plugins/identity_map.rb +116 -0
- data/lib/sequel/plugins/instance_filters.rb +98 -0
- data/lib/sequel/plugins/instance_hooks.rb +57 -0
- data/lib/sequel/plugins/lazy_attributes.rb +77 -0
- data/lib/sequel/plugins/many_through_many.rb +208 -0
- data/lib/sequel/plugins/nested_attributes.rb +206 -0
- data/lib/sequel/plugins/optimistic_locking.rb +81 -0
- data/lib/sequel/plugins/rcte_tree.rb +281 -0
- data/lib/sequel/plugins/schema.rb +66 -0
- data/lib/sequel/plugins/serialization.rb +166 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
- data/lib/sequel/plugins/timestamps.rb +87 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +72 -0
- data/lib/sequel/plugins/validation_class_methods.rb +405 -0
- data/lib/sequel/plugins/validation_helpers.rb +223 -0
- data/lib/sequel/sql.rb +1020 -0
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +12 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/firebird_spec.rb +407 -0
- data/spec/adapters/informix_spec.rb +97 -0
- data/spec/adapters/mssql_spec.rb +403 -0
- data/spec/adapters/mysql_spec.rb +1019 -0
- data/spec/adapters/oracle_spec.rb +286 -0
- data/spec/adapters/postgres_spec.rb +969 -0
- data/spec/adapters/spec_helper.rb +51 -0
- data/spec/adapters/sqlite_spec.rb +432 -0
- data/spec/core/connection_pool_spec.rb +808 -0
- data/spec/core/core_sql_spec.rb +417 -0
- data/spec/core/database_spec.rb +1662 -0
- data/spec/core/dataset_spec.rb +3827 -0
- data/spec/core/expression_filters_spec.rb +595 -0
- data/spec/core/object_graph_spec.rb +296 -0
- data/spec/core/schema_generator_spec.rb +159 -0
- data/spec/core/schema_spec.rb +830 -0
- data/spec/core/spec_helper.rb +56 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/active_model_spec.rb +76 -0
- data/spec/extensions/association_dependencies_spec.rb +127 -0
- data/spec/extensions/association_proxies_spec.rb +50 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/boolean_readers_spec.rb +92 -0
- data/spec/extensions/caching_spec.rb +250 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/composition_spec.rb +194 -0
- data/spec/extensions/force_encoding_spec.rb +117 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/identity_map_spec.rb +202 -0
- data/spec/extensions/inflector_spec.rb +181 -0
- data/spec/extensions/instance_filters_spec.rb +55 -0
- data/spec/extensions/instance_hooks_spec.rb +133 -0
- data/spec/extensions/lazy_attributes_spec.rb +153 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +884 -0
- data/spec/extensions/migration_spec.rb +332 -0
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +396 -0
- data/spec/extensions/optimistic_locking_spec.rb +100 -0
- data/spec/extensions/pagination_spec.rb +99 -0
- data/spec/extensions/pretty_table_spec.rb +91 -0
- data/spec/extensions/query_spec.rb +85 -0
- data/spec/extensions/rcte_tree_spec.rb +205 -0
- data/spec/extensions/schema_dumper_spec.rb +357 -0
- data/spec/extensions/schema_spec.rb +127 -0
- data/spec/extensions/serialization_spec.rb +209 -0
- data/spec/extensions/single_table_inheritance_spec.rb +96 -0
- data/spec/extensions/spec_helper.rb +91 -0
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/timestamps_spec.rb +150 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +69 -0
- data/spec/extensions/validation_class_methods_spec.rb +984 -0
- data/spec/extensions/validation_helpers_spec.rb +438 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/database_test.rb +26 -0
- data/spec/integration/dataset_test.rb +963 -0
- data/spec/integration/eager_loader_test.rb +734 -0
- data/spec/integration/model_test.rb +130 -0
- data/spec/integration/plugin_test.rb +814 -0
- data/spec/integration/prepared_statement_test.rb +213 -0
- data/spec/integration/schema_test.rb +361 -0
- data/spec/integration/spec_helper.rb +73 -0
- data/spec/integration/timezone_test.rb +55 -0
- data/spec/integration/transaction_test.rb +122 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +175 -0
- data/spec/model/associations_spec.rb +2633 -0
- data/spec/model/base_spec.rb +418 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1391 -0
- data/spec/model/hooks_spec.rb +240 -0
- data/spec/model/inflector_spec.rb +26 -0
- data/spec/model/model_spec.rb +593 -0
- data/spec/model/plugins_spec.rb +236 -0
- data/spec/model/record_spec.rb +1500 -0
- data/spec/model/spec_helper.rb +97 -0
- data/spec/model/validations_spec.rb +153 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +346 -0
@@ -0,0 +1,3827 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
context "Dataset" do
|
4
|
+
before do
|
5
|
+
@dataset = Sequel::Dataset.new("db")
|
6
|
+
end
|
7
|
+
|
8
|
+
specify "should accept database and opts in initialize" do
|
9
|
+
db = "db"
|
10
|
+
opts = {:from => :test}
|
11
|
+
d = Sequel::Dataset.new(db, opts)
|
12
|
+
d.db.should be(db)
|
13
|
+
d.opts.should be(opts)
|
14
|
+
|
15
|
+
d = Sequel::Dataset.new(db)
|
16
|
+
d.db.should be(db)
|
17
|
+
d.opts.should be_a_kind_of(Hash)
|
18
|
+
d.opts.should == {}
|
19
|
+
end
|
20
|
+
|
21
|
+
specify "should provide clone for chainability" do
|
22
|
+
d1 = @dataset.clone(:from => [:test])
|
23
|
+
d1.class.should == @dataset.class
|
24
|
+
d1.should_not == @dataset
|
25
|
+
d1.db.should be(@dataset.db)
|
26
|
+
d1.opts[:from].should == [:test]
|
27
|
+
@dataset.opts[:from].should be_nil
|
28
|
+
|
29
|
+
d2 = d1.clone(:order => [:name])
|
30
|
+
d2.class.should == @dataset.class
|
31
|
+
d2.should_not == d1
|
32
|
+
d2.should_not == @dataset
|
33
|
+
d2.db.should be(@dataset.db)
|
34
|
+
d2.opts[:from].should == [:test]
|
35
|
+
d2.opts[:order].should == [:name]
|
36
|
+
d1.opts[:order].should be_nil
|
37
|
+
end
|
38
|
+
|
39
|
+
specify "should include Enumerable" do
|
40
|
+
Sequel::Dataset.included_modules.should include(Enumerable)
|
41
|
+
end
|
42
|
+
|
43
|
+
specify "should get quote_identifiers default from database" do
|
44
|
+
db = Sequel::Database.new(:quote_identifiers=>true)
|
45
|
+
db[:a].quote_identifiers?.should == true
|
46
|
+
db = Sequel::Database.new(:quote_identifiers=>false)
|
47
|
+
db[:a].quote_identifiers?.should == false
|
48
|
+
end
|
49
|
+
|
50
|
+
specify "should get identifier_input_method default from database" do
|
51
|
+
db = Sequel::Database.new(:identifier_input_method=>:upcase)
|
52
|
+
db[:a].identifier_input_method.should == :upcase
|
53
|
+
db = Sequel::Database.new(:identifier_input_method=>:downcase)
|
54
|
+
db[:a].identifier_input_method.should == :downcase
|
55
|
+
end
|
56
|
+
|
57
|
+
specify "should get identifier_output_method default from database" do
|
58
|
+
db = Sequel::Database.new(:identifier_output_method=>:upcase)
|
59
|
+
db[:a].identifier_output_method.should == :upcase
|
60
|
+
db = Sequel::Database.new(:identifier_output_method=>:downcase)
|
61
|
+
db[:a].identifier_output_method.should == :downcase
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "Dataset" do
|
66
|
+
before do
|
67
|
+
@dataset = Sequel::Dataset.new("db")
|
68
|
+
end
|
69
|
+
|
70
|
+
specify "should have quote_identifiers= method which changes literalization of identifiers" do
|
71
|
+
@dataset.quote_identifiers = true
|
72
|
+
@dataset.literal(:a).should == '"a"'
|
73
|
+
@dataset.quote_identifiers = false
|
74
|
+
@dataset.literal(:a).should == 'a'
|
75
|
+
end
|
76
|
+
|
77
|
+
specify "should have identifier_input_method= method which changes literalization of identifiers" do
|
78
|
+
@dataset.identifier_input_method = :upcase
|
79
|
+
@dataset.literal(:a).should == 'A'
|
80
|
+
@dataset.identifier_input_method = :downcase
|
81
|
+
@dataset.literal(:A).should == 'a'
|
82
|
+
@dataset.identifier_input_method = :reverse
|
83
|
+
@dataset.literal(:at_b).should == 'b_ta'
|
84
|
+
end
|
85
|
+
|
86
|
+
specify "should have identifier_output_method= method which changes identifiers returned from the database" do
|
87
|
+
@dataset.send(:output_identifier, "at_b_C").should == :at_b_C
|
88
|
+
@dataset.identifier_output_method = :upcase
|
89
|
+
@dataset.send(:output_identifier, "at_b_C").should == :AT_B_C
|
90
|
+
@dataset.identifier_output_method = :downcase
|
91
|
+
@dataset.send(:output_identifier, "at_b_C").should == :at_b_c
|
92
|
+
@dataset.identifier_output_method = :reverse
|
93
|
+
@dataset.send(:output_identifier, "at_b_C").should == :C_b_ta
|
94
|
+
end
|
95
|
+
|
96
|
+
specify "should have output_identifier handle empty identifiers" do
|
97
|
+
@dataset.send(:output_identifier, "").should == :untitled
|
98
|
+
@dataset.identifier_output_method = :upcase
|
99
|
+
@dataset.send(:output_identifier, "").should == :UNTITLED
|
100
|
+
@dataset.identifier_output_method = :downcase
|
101
|
+
@dataset.send(:output_identifier, "").should == :untitled
|
102
|
+
@dataset.identifier_output_method = :reverse
|
103
|
+
@dataset.send(:output_identifier, "").should == :deltitnu
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "Dataset#clone" do
|
108
|
+
before do
|
109
|
+
@dataset = Sequel::Dataset.new(nil).from(:items)
|
110
|
+
end
|
111
|
+
|
112
|
+
specify "should create an exact copy of the dataset" do
|
113
|
+
@dataset.row_proc = Proc.new{|r| r}
|
114
|
+
@clone = @dataset.clone
|
115
|
+
|
116
|
+
@clone.should_not === @dataset
|
117
|
+
@clone.class.should == @dataset.class
|
118
|
+
@clone.opts.should == @dataset.opts
|
119
|
+
@clone.row_proc.should == @dataset.row_proc
|
120
|
+
end
|
121
|
+
|
122
|
+
specify "should deep-copy the dataset opts" do
|
123
|
+
@clone = @dataset.clone
|
124
|
+
|
125
|
+
@clone.opts.should_not equal(@dataset.opts)
|
126
|
+
@dataset.filter!(:a => 'b')
|
127
|
+
@clone.opts[:filter].should be_nil
|
128
|
+
end
|
129
|
+
|
130
|
+
specify "should return a clone self" do
|
131
|
+
clone = @dataset.clone({})
|
132
|
+
clone.class.should == @dataset.class
|
133
|
+
clone.db.should == @dataset.db
|
134
|
+
clone.opts.should == @dataset.opts
|
135
|
+
end
|
136
|
+
|
137
|
+
specify "should merge the specified options" do
|
138
|
+
clone = @dataset.clone(1 => 2)
|
139
|
+
clone.opts.should == {1 => 2, :from => [:items]}
|
140
|
+
end
|
141
|
+
|
142
|
+
specify "should overwrite existing options" do
|
143
|
+
clone = @dataset.clone(:from => [:other])
|
144
|
+
clone.opts.should == {:from => [:other]}
|
145
|
+
end
|
146
|
+
|
147
|
+
specify "should create a clone with a deep copy of options" do
|
148
|
+
clone = @dataset.clone(:from => [:other])
|
149
|
+
@dataset.opts[:from].should == [:items]
|
150
|
+
clone.opts[:from].should == [:other]
|
151
|
+
end
|
152
|
+
|
153
|
+
specify "should return an object with the same modules included" do
|
154
|
+
m = Module.new do
|
155
|
+
def __xyz__; "xyz"; end
|
156
|
+
end
|
157
|
+
@dataset.extend(m)
|
158
|
+
@dataset.clone({}).should respond_to(:__xyz__)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "A simple dataset" do
|
163
|
+
before do
|
164
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
165
|
+
end
|
166
|
+
|
167
|
+
specify "should format a select statement" do
|
168
|
+
@dataset.select_sql.should == 'SELECT * FROM test'
|
169
|
+
end
|
170
|
+
|
171
|
+
specify "should format a delete statement" do
|
172
|
+
@dataset.delete_sql.should == 'DELETE FROM test'
|
173
|
+
end
|
174
|
+
|
175
|
+
specify "should format a truncate statement" do
|
176
|
+
@dataset.truncate_sql.should == 'TRUNCATE TABLE test'
|
177
|
+
end
|
178
|
+
|
179
|
+
specify "should format an insert statement with default values" do
|
180
|
+
@dataset.insert_sql.should == 'INSERT INTO test DEFAULT VALUES'
|
181
|
+
end
|
182
|
+
|
183
|
+
specify "should format an insert statement with hash" do
|
184
|
+
@dataset.insert_sql(:name => 'wxyz', :price => 342).
|
185
|
+
should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
|
186
|
+
|
187
|
+
@dataset.insert_sql({}).should == "INSERT INTO test DEFAULT VALUES"
|
188
|
+
end
|
189
|
+
|
190
|
+
specify "should format an insert statement with string keys" do
|
191
|
+
@dataset.insert_sql('name' => 'wxyz', 'price' => 342).
|
192
|
+
should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
|
193
|
+
end
|
194
|
+
|
195
|
+
specify "should format an insert statement with an object that respond_to? :values" do
|
196
|
+
dbb = Sequel::Database.new
|
197
|
+
|
198
|
+
v = Object.new
|
199
|
+
def v.values; {:a => 1}; end
|
200
|
+
|
201
|
+
@dataset.insert_sql(v).should == "INSERT INTO test (a) VALUES (1)"
|
202
|
+
|
203
|
+
def v.values; {}; end
|
204
|
+
@dataset.insert_sql(v).should == "INSERT INTO test DEFAULT VALUES"
|
205
|
+
end
|
206
|
+
|
207
|
+
specify "should format an insert statement with an arbitrary value" do
|
208
|
+
@dataset.insert_sql(123).should == "INSERT INTO test VALUES (123)"
|
209
|
+
end
|
210
|
+
|
211
|
+
specify "should format an insert statement with sub-query" do
|
212
|
+
@sub = Sequel::Dataset.new(nil).from(:something).filter(:x => 2)
|
213
|
+
@dataset.insert_sql(@sub).should == \
|
214
|
+
"INSERT INTO test SELECT * FROM something WHERE (x = 2)"
|
215
|
+
end
|
216
|
+
|
217
|
+
specify "should format an insert statement with array" do
|
218
|
+
@dataset.insert_sql('a', 2, 6.5).should ==
|
219
|
+
"INSERT INTO test VALUES ('a', 2, 6.5)"
|
220
|
+
end
|
221
|
+
|
222
|
+
specify "should format an update statement" do
|
223
|
+
@dataset.update_sql(:name => 'abc').should ==
|
224
|
+
"UPDATE test SET name = 'abc'"
|
225
|
+
end
|
226
|
+
|
227
|
+
specify "should be able to return rows for arbitrary SQL" do
|
228
|
+
@dataset.clone(:sql => 'xxx yyy zzz').select_sql.should ==
|
229
|
+
"xxx yyy zzz"
|
230
|
+
end
|
231
|
+
|
232
|
+
specify "should use the :sql option for all sql methods" do
|
233
|
+
sql = "X"
|
234
|
+
ds = Sequel::Dataset.new(nil, :sql=>sql)
|
235
|
+
ds.sql.should == sql
|
236
|
+
ds.select_sql.should == sql
|
237
|
+
ds.insert_sql.should == sql
|
238
|
+
ds.delete_sql.should == sql
|
239
|
+
ds.update_sql.should == sql
|
240
|
+
ds.truncate_sql.should == sql
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
context "A dataset with multiple tables in its FROM clause" do
|
245
|
+
before do
|
246
|
+
@dataset = Sequel::Dataset.new(nil).from(:t1, :t2)
|
247
|
+
end
|
248
|
+
|
249
|
+
specify "should raise on #update_sql" do
|
250
|
+
proc {@dataset.update_sql(:a=>1)}.should raise_error(Sequel::InvalidOperation)
|
251
|
+
end
|
252
|
+
|
253
|
+
specify "should raise on #delete_sql" do
|
254
|
+
proc {@dataset.delete_sql}.should raise_error(Sequel::InvalidOperation)
|
255
|
+
end
|
256
|
+
|
257
|
+
specify "should raise on #truncate_sql" do
|
258
|
+
proc {@dataset.truncate_sql}.should raise_error(Sequel::InvalidOperation)
|
259
|
+
end
|
260
|
+
|
261
|
+
specify "should raise on #insert_sql" do
|
262
|
+
proc {@dataset.insert_sql}.should raise_error(Sequel::InvalidOperation)
|
263
|
+
end
|
264
|
+
|
265
|
+
specify "should generate a select query FROM all specified tables" do
|
266
|
+
@dataset.select_sql.should == "SELECT * FROM t1, t2"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
context "Dataset#unused_table_alias" do
|
271
|
+
before do
|
272
|
+
@ds = Sequel::Dataset.new(nil).from(:test)
|
273
|
+
end
|
274
|
+
|
275
|
+
specify "should return given symbol if it hasn't already been used" do
|
276
|
+
@ds.unused_table_alias(:blah).should == :blah
|
277
|
+
end
|
278
|
+
|
279
|
+
specify "should return a symbol specifying an alias that hasn't already been used if it has already been used" do
|
280
|
+
@ds.unused_table_alias(:test).should == :test_0
|
281
|
+
@ds.from(:test, :test_0).unused_table_alias(:test).should == :test_1
|
282
|
+
@ds.from(:test, :test_0).cross_join(:test_1).unused_table_alias(:test).should == :test_2
|
283
|
+
end
|
284
|
+
|
285
|
+
specify "should return an appropriate symbol if given other forms of identifiers" do
|
286
|
+
@ds.unused_table_alias('test').should == :test_0
|
287
|
+
@ds.unused_table_alias(:b__t___test).should == :test_0
|
288
|
+
@ds.unused_table_alias(:b__test).should == :test_0
|
289
|
+
@ds.unused_table_alias(:test.qualify(:b)).should == :test_0
|
290
|
+
@ds.unused_table_alias(:b.as(:test)).should == :test_0
|
291
|
+
@ds.unused_table_alias(:b.as(:test.identifier)).should == :test_0
|
292
|
+
@ds.unused_table_alias(:b.as('test')).should == :test_0
|
293
|
+
@ds.unused_table_alias(:test.identifier).should == :test_0
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
context "Dataset#exists" do
|
298
|
+
before do
|
299
|
+
@ds1 = Sequel::Dataset.new(nil).from(:test)
|
300
|
+
@ds2 = @ds1.filter(:price.sql_number < 100)
|
301
|
+
@ds3 = @ds1.filter(:price.sql_number > 50)
|
302
|
+
end
|
303
|
+
|
304
|
+
specify "should work in filters" do
|
305
|
+
@ds1.filter(@ds2.exists).sql.should ==
|
306
|
+
'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
|
307
|
+
@ds1.filter(@ds2.exists & @ds3.exists).sql.should ==
|
308
|
+
'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)) AND EXISTS (SELECT * FROM test WHERE (price > 50)))'
|
309
|
+
end
|
310
|
+
|
311
|
+
specify "should work in select" do
|
312
|
+
@ds1.select(@ds2.exists.as(:a), @ds3.exists.as(:b)).sql.should ==
|
313
|
+
'SELECT EXISTS (SELECT * FROM test WHERE (price < 100)) AS a, EXISTS (SELECT * FROM test WHERE (price > 50)) AS b FROM test'
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
context "Dataset#where" do
|
318
|
+
before do
|
319
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
320
|
+
@d1 = @dataset.where(:region => 'Asia')
|
321
|
+
@d2 = @dataset.where('region = ?', 'Asia')
|
322
|
+
@d3 = @dataset.where("a = 1")
|
323
|
+
end
|
324
|
+
|
325
|
+
specify "should just clone if given an empty argument" do
|
326
|
+
@dataset.where({}).sql.should == @dataset.sql
|
327
|
+
@dataset.where([]).sql.should == @dataset.sql
|
328
|
+
@dataset.where('').sql.should == @dataset.sql
|
329
|
+
|
330
|
+
@dataset.filter({}).sql.should == @dataset.sql
|
331
|
+
@dataset.filter([]).sql.should == @dataset.sql
|
332
|
+
@dataset.filter('').sql.should == @dataset.sql
|
333
|
+
end
|
334
|
+
|
335
|
+
specify "should work with hashes" do
|
336
|
+
@dataset.where(:name => 'xyz', :price => 342).select_sql.
|
337
|
+
should match(/WHERE \(\(name = 'xyz'\) AND \(price = 342\)\)|WHERE \(\(price = 342\) AND \(name = 'xyz'\)\)/)
|
338
|
+
end
|
339
|
+
|
340
|
+
specify "should work with a string with placeholders and arguments for those placeholders" do
|
341
|
+
@dataset.where('price < ? AND id in ?', 100, [1, 2, 3]).select_sql.should ==
|
342
|
+
"SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
|
343
|
+
end
|
344
|
+
|
345
|
+
specify "should not modify passed array with placeholders" do
|
346
|
+
a = ['price < ? AND id in ?', 100, 1, 2, 3]
|
347
|
+
b = a.dup
|
348
|
+
@dataset.where(a)
|
349
|
+
b.should == a
|
350
|
+
end
|
351
|
+
|
352
|
+
specify "should work with strings (custom SQL expressions)" do
|
353
|
+
@dataset.where('(a = 1 AND b = 2)').select_sql.should ==
|
354
|
+
"SELECT * FROM test WHERE ((a = 1 AND b = 2))"
|
355
|
+
end
|
356
|
+
|
357
|
+
specify "should work with a string with named placeholders and a hash of placeholder value arguments" do
|
358
|
+
@dataset.where('price < :price AND id in :ids', :price=>100, :ids=>[1, 2, 3]).select_sql.should ==
|
359
|
+
"SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
|
360
|
+
end
|
361
|
+
|
362
|
+
specify "should not modify passed array with named placeholders" do
|
363
|
+
a = ['price < :price AND id in :ids', {:price=>100}]
|
364
|
+
b = a.dup
|
365
|
+
@dataset.where(a)
|
366
|
+
b.should == a
|
367
|
+
end
|
368
|
+
|
369
|
+
specify "should not replace named placeholders that don't existin in the hash" do
|
370
|
+
@dataset.where('price < :price AND id in :ids', :price=>100).select_sql.should ==
|
371
|
+
"SELECT * FROM test WHERE (price < 100 AND id in :ids)"
|
372
|
+
end
|
373
|
+
|
374
|
+
specify "should handle partial names" do
|
375
|
+
@dataset.where('price < :price AND id = :p', :p=>2, :price=>100).select_sql.should ==
|
376
|
+
"SELECT * FROM test WHERE (price < 100 AND id = 2)"
|
377
|
+
end
|
378
|
+
|
379
|
+
specify "should affect select, delete and update statements" do
|
380
|
+
@d1.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
|
381
|
+
@d1.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
|
382
|
+
@d1.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
|
383
|
+
|
384
|
+
@d2.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
|
385
|
+
@d2.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
|
386
|
+
@d2.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
|
387
|
+
|
388
|
+
@d3.select_sql.should == "SELECT * FROM test WHERE (a = 1)"
|
389
|
+
@d3.delete_sql.should == "DELETE FROM test WHERE (a = 1)"
|
390
|
+
@d3.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (a = 1)"
|
391
|
+
|
392
|
+
end
|
393
|
+
|
394
|
+
specify "should be composable using AND operator (for scoping)" do
|
395
|
+
# hashes are merged, no problem
|
396
|
+
@d1.where(:size => 'big').select_sql.should ==
|
397
|
+
"SELECT * FROM test WHERE ((region = 'Asia') AND (size = 'big'))"
|
398
|
+
|
399
|
+
# hash and string
|
400
|
+
@d1.where('population > 1000').select_sql.should ==
|
401
|
+
"SELECT * FROM test WHERE ((region = 'Asia') AND (population > 1000))"
|
402
|
+
@d1.where('(a > 1) OR (b < 2)').select_sql.should ==
|
403
|
+
"SELECT * FROM test WHERE ((region = 'Asia') AND ((a > 1) OR (b < 2)))"
|
404
|
+
|
405
|
+
# hash and array
|
406
|
+
@d1.where('GDP > ?', 1000).select_sql.should ==
|
407
|
+
"SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > 1000))"
|
408
|
+
|
409
|
+
# array and array
|
410
|
+
@d2.where('GDP > ?', 1000).select_sql.should ==
|
411
|
+
"SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > 1000))"
|
412
|
+
|
413
|
+
# array and hash
|
414
|
+
@d2.where(:name => ['Japan', 'China']).select_sql.should ==
|
415
|
+
"SELECT * FROM test WHERE ((region = 'Asia') AND (name IN ('Japan', 'China')))"
|
416
|
+
|
417
|
+
# array and string
|
418
|
+
@d2.where('GDP > ?').select_sql.should ==
|
419
|
+
"SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > ?))"
|
420
|
+
|
421
|
+
# string and string
|
422
|
+
@d3.where('b = 2').select_sql.should ==
|
423
|
+
"SELECT * FROM test WHERE ((a = 1) AND (b = 2))"
|
424
|
+
|
425
|
+
# string and hash
|
426
|
+
@d3.where(:c => 3).select_sql.should ==
|
427
|
+
"SELECT * FROM test WHERE ((a = 1) AND (c = 3))"
|
428
|
+
|
429
|
+
# string and array
|
430
|
+
@d3.where('d = ?', 4).select_sql.should ==
|
431
|
+
"SELECT * FROM test WHERE ((a = 1) AND (d = 4))"
|
432
|
+
end
|
433
|
+
|
434
|
+
specify "should be composable using AND operator (for scoping) with block" do
|
435
|
+
@d3.where{:e.sql_number < 5}.select_sql.should ==
|
436
|
+
"SELECT * FROM test WHERE ((a = 1) AND (e < 5))"
|
437
|
+
end
|
438
|
+
|
439
|
+
specify "should accept ranges" do
|
440
|
+
@dataset.filter(:id => 4..7).sql.should ==
|
441
|
+
'SELECT * FROM test WHERE ((id >= 4) AND (id <= 7))'
|
442
|
+
@dataset.filter(:id => 4...7).sql.should ==
|
443
|
+
'SELECT * FROM test WHERE ((id >= 4) AND (id < 7))'
|
444
|
+
|
445
|
+
@dataset.filter(:table__id => 4..7).sql.should ==
|
446
|
+
'SELECT * FROM test WHERE ((table.id >= 4) AND (table.id <= 7))'
|
447
|
+
@dataset.filter(:table__id => 4...7).sql.should ==
|
448
|
+
'SELECT * FROM test WHERE ((table.id >= 4) AND (table.id < 7))'
|
449
|
+
end
|
450
|
+
|
451
|
+
specify "should accept nil" do
|
452
|
+
@dataset.filter(:owner_id => nil).sql.should ==
|
453
|
+
'SELECT * FROM test WHERE (owner_id IS NULL)'
|
454
|
+
end
|
455
|
+
|
456
|
+
specify "should accept a subquery" do
|
457
|
+
@dataset.filter('gdp > ?', @d1.select(:avg.sql_function(:gdp))).sql.should ==
|
458
|
+
"SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
|
459
|
+
end
|
460
|
+
|
461
|
+
specify "should handle all types of IN/NOT IN queries" do
|
462
|
+
@dataset.filter(:id => @d1.select(:id)).sql.should == "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
|
463
|
+
@dataset.filter(:id => []).sql.should == "SELECT * FROM test WHERE (id != id)"
|
464
|
+
@dataset.filter(:id => [1, 2]).sql.should == "SELECT * FROM test WHERE (id IN (1, 2))"
|
465
|
+
@dataset.filter([:id1, :id2] => @d1.select(:id1, :id2)).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN (SELECT id1, id2 FROM test WHERE (region = 'Asia')))"
|
466
|
+
@dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
|
467
|
+
@dataset.filter([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN ((1, 2), (3, 4)))"
|
468
|
+
|
469
|
+
@dataset.exclude(:id => @d1.select(:id)).sql.should == "SELECT * FROM test WHERE (id NOT IN (SELECT id FROM test WHERE (region = 'Asia')))"
|
470
|
+
@dataset.exclude(:id => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
|
471
|
+
@dataset.exclude(:id => [1, 2]).sql.should == "SELECT * FROM test WHERE (id NOT IN (1, 2))"
|
472
|
+
@dataset.exclude([:id1, :id2] => @d1.select(:id1, :id2)).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN (SELECT id1, id2 FROM test WHERE (region = 'Asia')))"
|
473
|
+
@dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
|
474
|
+
@dataset.exclude([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN ((1, 2), (3, 4)))"
|
475
|
+
end
|
476
|
+
|
477
|
+
specify "should handle IN/NOT IN queries with multiple columns and an array where the database doesn't support it" do
|
478
|
+
@dataset.meta_def(:supports_multiple_column_in?){false}
|
479
|
+
@dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
|
480
|
+
@dataset.filter([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
|
481
|
+
@dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
|
482
|
+
@dataset.exclude([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
|
483
|
+
end
|
484
|
+
|
485
|
+
specify "should handle IN/NOT IN queries with multiple columns and a dataset where the database doesn't support it" do
|
486
|
+
@dataset.meta_def(:supports_multiple_column_in?){false}
|
487
|
+
d1 = @d1.select(:id1, :id2)
|
488
|
+
def d1.fetch_rows(sql)
|
489
|
+
@sql_used = sql
|
490
|
+
@columns = [:id1, :id2]
|
491
|
+
yield(:id1=>1, :id2=>2)
|
492
|
+
yield(:id1=>3, :id2=>4)
|
493
|
+
end
|
494
|
+
d1.instance_variable_get(:@sql_used).should == nil
|
495
|
+
@dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
|
496
|
+
d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
|
497
|
+
d1.instance_variable_set(:@sql_used, nil)
|
498
|
+
@dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
|
499
|
+
d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
|
500
|
+
end
|
501
|
+
|
502
|
+
specify "should handle IN/NOT IN queries with multiple columns and an empty dataset where the database doesn't support it" do
|
503
|
+
@dataset.meta_def(:supports_multiple_column_in?){false}
|
504
|
+
d1 = @d1.select(:id1, :id2)
|
505
|
+
def d1.fetch_rows(sql)
|
506
|
+
@sql_used = sql
|
507
|
+
@columns = [:id1, :id2]
|
508
|
+
end
|
509
|
+
d1.instance_variable_get(:@sql_used).should == nil
|
510
|
+
@dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
|
511
|
+
d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
|
512
|
+
d1.instance_variable_set(:@sql_used, nil)
|
513
|
+
@dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 1)"
|
514
|
+
d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
|
515
|
+
end
|
516
|
+
|
517
|
+
specify "should accept a subquery for an EXISTS clause" do
|
518
|
+
a = @dataset.filter(:price.sql_number < 100)
|
519
|
+
@dataset.filter(a.exists).sql.should ==
|
520
|
+
'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
|
521
|
+
end
|
522
|
+
|
523
|
+
specify "should accept proc expressions" do
|
524
|
+
d = @d1.select(:avg.sql_function(:gdp))
|
525
|
+
@dataset.filter {:gdp.sql_number > d}.sql.should ==
|
526
|
+
"SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
|
527
|
+
|
528
|
+
@dataset.filter {:a.sql_number < 1}.sql.should ==
|
529
|
+
'SELECT * FROM test WHERE (a < 1)'
|
530
|
+
|
531
|
+
@dataset.filter {(:a.sql_number >= 1) & (:b.sql_number <= 2)}.sql.should ==
|
532
|
+
'SELECT * FROM test WHERE ((a >= 1) AND (b <= 2))'
|
533
|
+
|
534
|
+
@dataset.filter {:c.like 'ABC%'}.sql.should ==
|
535
|
+
"SELECT * FROM test WHERE (c LIKE 'ABC%')"
|
536
|
+
|
537
|
+
@dataset.filter {:c.like 'ABC%'}.sql.should ==
|
538
|
+
"SELECT * FROM test WHERE (c LIKE 'ABC%')"
|
539
|
+
|
540
|
+
@dataset.filter {:c.like 'ABC%', '%XYZ'}.sql.should ==
|
541
|
+
"SELECT * FROM test WHERE ((c LIKE 'ABC%') OR (c LIKE '%XYZ'))"
|
542
|
+
end
|
543
|
+
|
544
|
+
specify "should work for grouped datasets" do
|
545
|
+
@dataset.group(:a).filter(:b => 1).sql.should ==
|
546
|
+
'SELECT * FROM test WHERE (b = 1) GROUP BY a'
|
547
|
+
end
|
548
|
+
|
549
|
+
specify "should accept true and false as arguments" do
|
550
|
+
@dataset.filter(true).sql.should ==
|
551
|
+
"SELECT * FROM test WHERE 't'"
|
552
|
+
@dataset.filter(false).sql.should ==
|
553
|
+
"SELECT * FROM test WHERE 'f'"
|
554
|
+
end
|
555
|
+
|
556
|
+
specify "should allow the use of multiple arguments" do
|
557
|
+
@dataset.filter(:a, :b).sql.should ==
|
558
|
+
'SELECT * FROM test WHERE (a AND b)'
|
559
|
+
@dataset.filter(:a, :b=>1).sql.should ==
|
560
|
+
'SELECT * FROM test WHERE (a AND (b = 1))'
|
561
|
+
@dataset.filter(:a, :c.sql_number > 3, :b=>1).sql.should ==
|
562
|
+
'SELECT * FROM test WHERE (a AND (c > 3) AND (b = 1))'
|
563
|
+
end
|
564
|
+
|
565
|
+
specify "should allow the use of blocks and arguments simultaneously" do
|
566
|
+
@dataset.filter(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should ==
|
567
|
+
'SELECT * FROM test WHERE ((zz < 3) AND (yy > 3))'
|
568
|
+
end
|
569
|
+
|
570
|
+
specify "should yield a VirtualRow to the block" do
|
571
|
+
x = nil
|
572
|
+
@dataset.filter{|r| x = r; false}
|
573
|
+
x.should be_a_kind_of(Sequel::SQL::VirtualRow)
|
574
|
+
@dataset.filter{|r| ((r.name < 'b') & {r.table__id => 1}) | r.is_active(r.blah, r.xx, r.x__y_z)}.sql.should ==
|
575
|
+
"SELECT * FROM test WHERE (((name < 'b') AND (table.id = 1)) OR is_active(blah, xx, x.y_z))"
|
576
|
+
end
|
577
|
+
|
578
|
+
specify "should instance_eval the block in the context of a VirtualRow if the block doesn't request an argument" do
|
579
|
+
x = nil
|
580
|
+
@dataset.filter{x = self; false}
|
581
|
+
x.should be_a_kind_of(Sequel::SQL::VirtualRow)
|
582
|
+
@dataset.filter{((name < 'b') & {table__id => 1}) | is_active(blah, xx, x__y_z)}.sql.should ==
|
583
|
+
"SELECT * FROM test WHERE (((name < 'b') AND (table.id = 1)) OR is_active(blah, xx, x.y_z))"
|
584
|
+
end
|
585
|
+
|
586
|
+
specify "should raise an error if an invalid argument is used" do
|
587
|
+
proc{@dataset.filter(1)}.should raise_error(Sequel::Error)
|
588
|
+
end
|
589
|
+
|
590
|
+
specify "should raise an error if a NumericExpression or StringExpression is used" do
|
591
|
+
proc{@dataset.filter(:x + 1)}.should raise_error(Sequel::Error)
|
592
|
+
proc{@dataset.filter(:x.sql_string)}.should raise_error(Sequel::Error)
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
context "Dataset#or" do
|
597
|
+
before do
|
598
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
599
|
+
@d1 = @dataset.where(:x => 1)
|
600
|
+
end
|
601
|
+
|
602
|
+
specify "should raise if no filter exists" do
|
603
|
+
proc {@dataset.or(:a => 1)}.should raise_error(Sequel::Error)
|
604
|
+
end
|
605
|
+
|
606
|
+
specify "should add an alternative expression to the where clause" do
|
607
|
+
@d1.or(:y => 2).sql.should ==
|
608
|
+
'SELECT * FROM test WHERE ((x = 1) OR (y = 2))'
|
609
|
+
end
|
610
|
+
|
611
|
+
specify "should accept all forms of filters" do
|
612
|
+
@d1.or('y > ?', 2).sql.should ==
|
613
|
+
'SELECT * FROM test WHERE ((x = 1) OR (y > 2))'
|
614
|
+
@d1.or(:yy.sql_number > 3).sql.should ==
|
615
|
+
'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
|
616
|
+
end
|
617
|
+
|
618
|
+
specify "should accept blocks passed to filter" do
|
619
|
+
@d1.or{:yy.sql_number > 3}.sql.should ==
|
620
|
+
'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
|
621
|
+
end
|
622
|
+
|
623
|
+
specify "should correctly add parens to give predictable results" do
|
624
|
+
@d1.filter(:y => 2).or(:z => 3).sql.should ==
|
625
|
+
'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
|
626
|
+
|
627
|
+
@d1.or(:y => 2).filter(:z => 3).sql.should ==
|
628
|
+
'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
|
629
|
+
end
|
630
|
+
|
631
|
+
specify "should allow the use of blocks and arguments simultaneously" do
|
632
|
+
@d1.or(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should ==
|
633
|
+
'SELECT * FROM test WHERE ((x = 1) OR ((zz < 3) AND (yy > 3)))'
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
context "Dataset#and" do
|
638
|
+
before do
|
639
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
640
|
+
@d1 = @dataset.where(:x => 1)
|
641
|
+
end
|
642
|
+
|
643
|
+
specify "should raise if no filter exists" do
|
644
|
+
proc {@dataset.and(:a => 1)}.should raise_error(Sequel::Error)
|
645
|
+
proc {@dataset.where(:a => 1).group(:t).and(:b => 2)}.should_not raise_error(Sequel::Error)
|
646
|
+
@dataset.where(:a => 1).group(:t).and(:b => 2).sql ==
|
647
|
+
"SELECT * FROM test WHERE (a = 1) AND (b = 2) GROUP BY t"
|
648
|
+
end
|
649
|
+
|
650
|
+
specify "should add an alternative expression to the where clause" do
|
651
|
+
@d1.and(:y => 2).sql.should ==
|
652
|
+
'SELECT * FROM test WHERE ((x = 1) AND (y = 2))'
|
653
|
+
end
|
654
|
+
|
655
|
+
specify "should accept all forms of filters" do
|
656
|
+
# probably not exhaustive, but good enough
|
657
|
+
@d1.and('y > ?', 2).sql.should ==
|
658
|
+
'SELECT * FROM test WHERE ((x = 1) AND (y > 2))'
|
659
|
+
@d1.and(:yy.sql_number > 3).sql.should ==
|
660
|
+
'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
|
661
|
+
end
|
662
|
+
|
663
|
+
specify "should accept blocks passed to filter" do
|
664
|
+
@d1.and {:yy.sql_number > 3}.sql.should ==
|
665
|
+
'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
|
666
|
+
end
|
667
|
+
|
668
|
+
specify "should correctly add parens to give predictable results" do
|
669
|
+
@d1.or(:y => 2).and(:z => 3).sql.should ==
|
670
|
+
'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
|
671
|
+
|
672
|
+
@d1.and(:y => 2).or(:z => 3).sql.should ==
|
673
|
+
'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
context "Dataset#exclude" do
|
678
|
+
before do
|
679
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
680
|
+
end
|
681
|
+
|
682
|
+
specify "should correctly negate the expression when one condition is given" do
|
683
|
+
@dataset.exclude(:region=>'Asia').select_sql.should ==
|
684
|
+
"SELECT * FROM test WHERE (region != 'Asia')"
|
685
|
+
end
|
686
|
+
|
687
|
+
specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
|
688
|
+
@dataset.exclude(:region => 'Asia', :name => 'Japan').select_sql.
|
689
|
+
should match(Regexp.union(/WHERE \(\(region != 'Asia'\) OR \(name != 'Japan'\)\)/,
|
690
|
+
/WHERE \(\(name != 'Japan'\) OR \(region != 'Asia'\)\)/))
|
691
|
+
end
|
692
|
+
|
693
|
+
specify "should parenthesize a single string condition correctly" do
|
694
|
+
@dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should ==
|
695
|
+
"SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
|
696
|
+
end
|
697
|
+
|
698
|
+
specify "should parenthesize an array condition correctly" do
|
699
|
+
@dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should ==
|
700
|
+
"SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
|
701
|
+
end
|
702
|
+
|
703
|
+
specify "should correctly parenthesize when it is used twice" do
|
704
|
+
@dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should ==
|
705
|
+
"SELECT * FROM test WHERE ((region != 'Asia') AND (name != 'Japan'))"
|
706
|
+
end
|
707
|
+
|
708
|
+
specify "should support proc expressions" do
|
709
|
+
@dataset.exclude{:id.sql_number < 6}.sql.should ==
|
710
|
+
'SELECT * FROM test WHERE (id >= 6)'
|
711
|
+
end
|
712
|
+
|
713
|
+
specify "should allow the use of blocks and arguments simultaneously" do
|
714
|
+
@dataset.exclude(:id => (7..11)){:id.sql_number < 6}.sql.should ==
|
715
|
+
'SELECT * FROM test WHERE ((id < 7) OR (id > 11) OR (id >= 6))'
|
716
|
+
@dataset.exclude([:id, 1], [:x, 3]){:id.sql_number < 6}.sql.should ==
|
717
|
+
'SELECT * FROM test WHERE ((id != 1) OR (x != 3) OR (id >= 6))'
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
context "Dataset#invert" do
|
722
|
+
before do
|
723
|
+
@d = Sequel::Dataset.new(nil).from(:test)
|
724
|
+
end
|
725
|
+
|
726
|
+
specify "should raise error if the dataset is not filtered" do
|
727
|
+
proc{@d.invert}.should raise_error(Sequel::Error)
|
728
|
+
end
|
729
|
+
|
730
|
+
specify "should invert current filter if dataset is filtered" do
|
731
|
+
@d.filter(:x).invert.sql.should == 'SELECT * FROM test WHERE NOT x'
|
732
|
+
end
|
733
|
+
|
734
|
+
specify "should invert both having and where if both are preset" do
|
735
|
+
@d.filter(:x).group(:x).having(:x).invert.sql.should == 'SELECT * FROM test WHERE NOT x GROUP BY x HAVING NOT x'
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
context "Dataset#having" do
|
740
|
+
before do
|
741
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
742
|
+
@grouped = @dataset.group(:region).select(:region, :sum.sql_function(:population), :avg.sql_function(:gdp))
|
743
|
+
@d1 = @grouped.having('sum(population) > 10')
|
744
|
+
@d2 = @grouped.having(:region => 'Asia')
|
745
|
+
@columns = "region, sum(population), avg(gdp)"
|
746
|
+
end
|
747
|
+
|
748
|
+
specify "should just clone if given an empty argument" do
|
749
|
+
@dataset.having({}).sql.should == @dataset.sql
|
750
|
+
@dataset.having([]).sql.should == @dataset.sql
|
751
|
+
@dataset.having('').sql.should == @dataset.sql
|
752
|
+
end
|
753
|
+
|
754
|
+
specify "should affect select statements" do
|
755
|
+
@d1.select_sql.should ==
|
756
|
+
"SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
|
757
|
+
end
|
758
|
+
|
759
|
+
specify "should support proc expressions" do
|
760
|
+
@grouped.having {:sum.sql_function(:population) > 10}.sql.should ==
|
761
|
+
"SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
|
762
|
+
end
|
763
|
+
|
764
|
+
specify "should work with and on the having clause" do
|
765
|
+
@grouped.having( :a.sql_number > 1 ).and( :b.sql_number < 2 ).sql.should ==
|
766
|
+
"SELECT #{@columns} FROM test GROUP BY region HAVING ((a > 1) AND (b < 2))"
|
767
|
+
end
|
768
|
+
end
|
769
|
+
|
770
|
+
context "a grouped dataset" do
|
771
|
+
before do
|
772
|
+
@dataset = Sequel::Dataset.new(nil).from(:test).group(:type_id)
|
773
|
+
end
|
774
|
+
|
775
|
+
specify "should raise when trying to generate an update statement" do
|
776
|
+
proc {@dataset.update_sql(:id => 0)}.should raise_error
|
777
|
+
end
|
778
|
+
|
779
|
+
specify "should raise when trying to generate a delete statement" do
|
780
|
+
proc {@dataset.delete_sql}.should raise_error
|
781
|
+
end
|
782
|
+
|
783
|
+
specify "should raise when trying to generate a truncate statement" do
|
784
|
+
proc {@dataset.truncate_sql}.should raise_error
|
785
|
+
end
|
786
|
+
|
787
|
+
specify "should raise when trying to generate an insert statement" do
|
788
|
+
proc {@dataset.insert_sql}.should raise_error
|
789
|
+
end
|
790
|
+
|
791
|
+
specify "should specify the grouping in generated select statement" do
|
792
|
+
@dataset.select_sql.should ==
|
793
|
+
"SELECT * FROM test GROUP BY type_id"
|
794
|
+
end
|
795
|
+
|
796
|
+
specify "should format the right statement for counting (as a subquery)" do
|
797
|
+
db = MockDatabase.new
|
798
|
+
db[:test].select(:name).group(:name).count
|
799
|
+
db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT name FROM test GROUP BY name) AS t1 LIMIT 1"]
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
context "Dataset#group_by" do
|
804
|
+
before do
|
805
|
+
@dataset = Sequel::Dataset.new(nil).from(:test).group_by(:type_id)
|
806
|
+
end
|
807
|
+
|
808
|
+
specify "should raise when trying to generate an update statement" do
|
809
|
+
proc {@dataset.update_sql(:id => 0)}.should raise_error
|
810
|
+
end
|
811
|
+
|
812
|
+
specify "should raise when trying to generate a delete statement" do
|
813
|
+
proc {@dataset.delete_sql}.should raise_error
|
814
|
+
end
|
815
|
+
|
816
|
+
specify "should specify the grouping in generated select statement" do
|
817
|
+
@dataset.select_sql.should ==
|
818
|
+
"SELECT * FROM test GROUP BY type_id"
|
819
|
+
@dataset.group_by(:a, :b).select_sql.should ==
|
820
|
+
"SELECT * FROM test GROUP BY a, b"
|
821
|
+
@dataset.group_by(:type_id=>nil).select_sql.should ==
|
822
|
+
"SELECT * FROM test GROUP BY (type_id IS NULL)"
|
823
|
+
end
|
824
|
+
|
825
|
+
specify "should ungroup when passed nil, empty, or no arguments" do
|
826
|
+
@dataset.group_by.select_sql.should ==
|
827
|
+
"SELECT * FROM test"
|
828
|
+
@dataset.group_by(nil).select_sql.should ==
|
829
|
+
"SELECT * FROM test"
|
830
|
+
end
|
831
|
+
|
832
|
+
specify "should undo previous grouping" do
|
833
|
+
@dataset.group_by(:a).group_by(:b).select_sql.should ==
|
834
|
+
"SELECT * FROM test GROUP BY b"
|
835
|
+
@dataset.group_by(:a, :b).group_by.select_sql.should ==
|
836
|
+
"SELECT * FROM test"
|
837
|
+
end
|
838
|
+
|
839
|
+
specify "should be aliased as #group" do
|
840
|
+
@dataset.group(:type_id=>nil).select_sql.should ==
|
841
|
+
"SELECT * FROM test GROUP BY (type_id IS NULL)"
|
842
|
+
end
|
843
|
+
end
|
844
|
+
|
845
|
+
context "Dataset#as" do
|
846
|
+
specify "should set up an alias" do
|
847
|
+
dataset = Sequel::Dataset.new(nil).from(:test)
|
848
|
+
dataset.select(dataset.limit(1).select(:name).as(:n)).sql.should == \
|
849
|
+
'SELECT (SELECT name FROM test LIMIT 1) AS n FROM test'
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
context "Dataset#literal" do
|
854
|
+
before do
|
855
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
856
|
+
end
|
857
|
+
|
858
|
+
specify "should escape strings properly" do
|
859
|
+
@dataset.literal('abc').should == "'abc'"
|
860
|
+
@dataset.literal('a"x"bc').should == "'a\"x\"bc'"
|
861
|
+
@dataset.literal("a'bc").should == "'a''bc'"
|
862
|
+
@dataset.literal("a''bc").should == "'a''''bc'"
|
863
|
+
@dataset.literal("a\\bc").should == "'a\\\\bc'"
|
864
|
+
@dataset.literal("a\\\\bc").should == "'a\\\\\\\\bc'"
|
865
|
+
@dataset.literal("a\\'bc").should == "'a\\\\''bc'"
|
866
|
+
end
|
867
|
+
|
868
|
+
specify "should escape blobs as strings by default" do
|
869
|
+
@dataset.literal('abc'.to_sequel_blob).should == "'abc'"
|
870
|
+
end
|
871
|
+
|
872
|
+
specify "should literalize numbers properly" do
|
873
|
+
@dataset.literal(1).should == "1"
|
874
|
+
@dataset.literal(1.5).should == "1.5"
|
875
|
+
end
|
876
|
+
|
877
|
+
specify "should literalize nil as NULL" do
|
878
|
+
@dataset.literal(nil).should == "NULL"
|
879
|
+
end
|
880
|
+
|
881
|
+
specify "should literalize an array properly" do
|
882
|
+
@dataset.literal([]).should == "(NULL)"
|
883
|
+
@dataset.literal([1, 'abc', 3]).should == "(1, 'abc', 3)"
|
884
|
+
@dataset.literal([1, "a'b''c", 3]).should == "(1, 'a''b''''c', 3)"
|
885
|
+
end
|
886
|
+
|
887
|
+
specify "should literalize symbols as column references" do
|
888
|
+
@dataset.literal(:name).should == "name"
|
889
|
+
@dataset.literal(:items__name).should == "items.name"
|
890
|
+
end
|
891
|
+
|
892
|
+
specify "should call sql_literal with dataset on type if not natively supported and the object responds to it" do
|
893
|
+
@a = Class.new do
|
894
|
+
def sql_literal(ds)
|
895
|
+
"called #{ds.blah}"
|
896
|
+
end
|
897
|
+
end
|
898
|
+
def @dataset.blah
|
899
|
+
"ds"
|
900
|
+
end
|
901
|
+
@dataset.literal(@a.new).should == "called ds"
|
902
|
+
end
|
903
|
+
|
904
|
+
specify "should raise an error for unsupported types with no sql_literal method" do
|
905
|
+
proc {@dataset.literal(Object.new)}.should raise_error
|
906
|
+
end
|
907
|
+
|
908
|
+
specify "should literalize datasets as subqueries" do
|
909
|
+
d = @dataset.from(:test)
|
910
|
+
d.literal(d).should == "(#{d.sql})"
|
911
|
+
end
|
912
|
+
|
913
|
+
specify "should literalize Time properly" do
|
914
|
+
t = Time.now
|
915
|
+
s = t.strftime("'%Y-%m-%d %H:%M:%S")
|
916
|
+
@dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
|
917
|
+
end
|
918
|
+
|
919
|
+
specify "should literalize DateTime properly" do
|
920
|
+
t = DateTime.now
|
921
|
+
s = t.strftime("'%Y-%m-%d %H:%M:%S")
|
922
|
+
@dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.sec_fraction* 86400000000)}'"
|
923
|
+
end
|
924
|
+
|
925
|
+
specify "should literalize Date properly" do
|
926
|
+
d = Date.today
|
927
|
+
s = d.strftime("'%Y-%m-%d'")
|
928
|
+
@dataset.literal(d).should == s
|
929
|
+
end
|
930
|
+
|
931
|
+
specify "should literalize Date properly, even if to_s is overridden" do
|
932
|
+
d = Date.today
|
933
|
+
def d.to_s; "adsf" end
|
934
|
+
s = d.strftime("'%Y-%m-%d'")
|
935
|
+
@dataset.literal(d).should == s
|
936
|
+
end
|
937
|
+
|
938
|
+
specify "should literalize Time, DateTime, Date properly if SQL standard format is required" do
|
939
|
+
@dataset.meta_def(:requires_sql_standard_datetimes?){true}
|
940
|
+
|
941
|
+
t = Time.now
|
942
|
+
s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S")
|
943
|
+
@dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
|
944
|
+
|
945
|
+
t = DateTime.now
|
946
|
+
s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S")
|
947
|
+
@dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.sec_fraction* 86400000000)}'"
|
948
|
+
|
949
|
+
d = Date.today
|
950
|
+
s = d.strftime("DATE '%Y-%m-%d'")
|
951
|
+
@dataset.literal(d).should == s
|
952
|
+
end
|
953
|
+
|
954
|
+
specify "should literalize Time and DateTime properly if the database support timezones in timestamps" do
|
955
|
+
@dataset.meta_def(:supports_timestamp_timezones?){true}
|
956
|
+
|
957
|
+
t = Time.now.utc
|
958
|
+
s = t.strftime("'%Y-%m-%d %H:%M:%S")
|
959
|
+
@dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}+0000'"
|
960
|
+
|
961
|
+
t = DateTime.now.new_offset(0)
|
962
|
+
s = t.strftime("'%Y-%m-%d %H:%M:%S")
|
963
|
+
@dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.sec_fraction* 86400000000)}+0000'"
|
964
|
+
end
|
965
|
+
|
966
|
+
specify "should literalize Time and DateTime properly if the database doesn't support usecs in timestamps" do
|
967
|
+
@dataset.meta_def(:supports_timestamp_usecs?){false}
|
968
|
+
|
969
|
+
t = Time.now.utc
|
970
|
+
s = t.strftime("'%Y-%m-%d %H:%M:%S")
|
971
|
+
@dataset.literal(t).should == "#{s}'"
|
972
|
+
|
973
|
+
t = DateTime.now.new_offset(0)
|
974
|
+
s = t.strftime("'%Y-%m-%d %H:%M:%S")
|
975
|
+
@dataset.literal(t).should == "#{s}'"
|
976
|
+
|
977
|
+
@dataset.meta_def(:supports_timestamp_timezones?){true}
|
978
|
+
|
979
|
+
t = Time.now.utc
|
980
|
+
s = t.strftime("'%Y-%m-%d %H:%M:%S")
|
981
|
+
@dataset.literal(t).should == "#{s}+0000'"
|
982
|
+
|
983
|
+
t = DateTime.now.new_offset(0)
|
984
|
+
s = t.strftime("'%Y-%m-%d %H:%M:%S")
|
985
|
+
@dataset.literal(t).should == "#{s}+0000'"
|
986
|
+
end
|
987
|
+
|
988
|
+
specify "should not modify literal strings" do
|
989
|
+
@dataset.literal('col1 + 2'.lit).should == 'col1 + 2'
|
990
|
+
|
991
|
+
@dataset.update_sql(:a => 'a + 2'.lit).should ==
|
992
|
+
'UPDATE test SET a = a + 2'
|
993
|
+
end
|
994
|
+
|
995
|
+
specify "should literalize BigDecimal instances correctly" do
|
996
|
+
@dataset.literal(BigDecimal.new("80")).should == "80.0"
|
997
|
+
@dataset.literal(BigDecimal.new("NaN")).should == "'NaN'"
|
998
|
+
@dataset.literal(BigDecimal.new("Infinity")).should == "'Infinity'"
|
999
|
+
@dataset.literal(BigDecimal.new("-Infinity")).should == "'-Infinity'"
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
specify "should raise an Error if the object can't be literalized" do
|
1003
|
+
proc{@dataset.literal(Object.new)}.should raise_error(Sequel::Error)
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
context "Dataset#from" do
|
1008
|
+
before do
|
1009
|
+
@dataset = Sequel::Dataset.new(nil)
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
specify "should accept a Dataset" do
|
1013
|
+
proc {@dataset.from(@dataset)}.should_not raise_error
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
specify "should format a Dataset as a subquery if it has had options set" do
|
1017
|
+
@dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
|
1018
|
+
"SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
specify "should automatically alias sub-queries" do
|
1022
|
+
@dataset.from(@dataset.from(:a).group(:b)).select_sql.should ==
|
1023
|
+
"SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
|
1024
|
+
|
1025
|
+
d1 = @dataset.from(:a).group(:b)
|
1026
|
+
d2 = @dataset.from(:c).group(:d)
|
1027
|
+
|
1028
|
+
@dataset.from(d1, d2).sql.should ==
|
1029
|
+
"SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1, (SELECT * FROM c GROUP BY d) AS t2"
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
specify "should accept a hash for aliasing" do
|
1033
|
+
@dataset.from(:a => :b).sql.should ==
|
1034
|
+
"SELECT * FROM a AS b"
|
1035
|
+
|
1036
|
+
@dataset.from(:a => 'b').sql.should ==
|
1037
|
+
"SELECT * FROM a AS b"
|
1038
|
+
|
1039
|
+
@dataset.from(@dataset.from(:a).group(:b) => :c).sql.should ==
|
1040
|
+
"SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
specify "should always use a subquery if given a dataset" do
|
1044
|
+
@dataset.from(@dataset.from(:a)).select_sql.should ==
|
1045
|
+
"SELECT * FROM (SELECT * FROM a) AS t1"
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
specify "should remove all FROM tables if called with no arguments" do
|
1049
|
+
@dataset.from.sql.should == 'SELECT *'
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
specify "should accept sql functions" do
|
1053
|
+
@dataset.from(:abc.sql_function(:def)).select_sql.should ==
|
1054
|
+
"SELECT * FROM abc(def)"
|
1055
|
+
|
1056
|
+
@dataset.from(:a.sql_function(:i)).select_sql.should ==
|
1057
|
+
"SELECT * FROM a(i)"
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
specify "should accept :schema__table___alias symbol format" do
|
1061
|
+
@dataset.from(:abc__def).select_sql.should ==
|
1062
|
+
"SELECT * FROM abc.def"
|
1063
|
+
@dataset.from(:abc__def___d).select_sql.should ==
|
1064
|
+
"SELECT * FROM abc.def AS d"
|
1065
|
+
@dataset.from(:abc___def).select_sql.should ==
|
1066
|
+
"SELECT * FROM abc AS def"
|
1067
|
+
end
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
context "Dataset#select" do
|
1071
|
+
before do
|
1072
|
+
@d = Sequel::Dataset.new(nil).from(:test)
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
specify "should accept variable arity" do
|
1076
|
+
@d.select(:name).sql.should == 'SELECT name FROM test'
|
1077
|
+
@d.select(:a, :b, :test__c).sql.should == 'SELECT a, b, test.c FROM test'
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
specify "should accept symbols and literal strings" do
|
1081
|
+
@d.select('aaa'.lit).sql.should == 'SELECT aaa FROM test'
|
1082
|
+
@d.select(:a, 'b'.lit).sql.should == 'SELECT a, b FROM test'
|
1083
|
+
@d.select(:test__cc, 'test.d AS e'.lit).sql.should ==
|
1084
|
+
'SELECT test.cc, test.d AS e FROM test'
|
1085
|
+
@d.select('test.d AS e'.lit, :test__cc).sql.should ==
|
1086
|
+
'SELECT test.d AS e, test.cc FROM test'
|
1087
|
+
|
1088
|
+
# symbol helpers
|
1089
|
+
@d.select(:test.*).sql.should ==
|
1090
|
+
'SELECT test.* FROM test'
|
1091
|
+
@d.select(:test__name.as(:n)).sql.should ==
|
1092
|
+
'SELECT test.name AS n FROM test'
|
1093
|
+
@d.select(:test__name___n).sql.should ==
|
1094
|
+
'SELECT test.name AS n FROM test'
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
specify "should use the wildcard if no arguments are given" do
|
1098
|
+
@d.select.sql.should == 'SELECT * FROM test'
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
specify "should accept a hash for AS values" do
|
1102
|
+
@d.select(:name => 'n', :__ggh => 'age').sql.should =~
|
1103
|
+
/SELECT ((name AS n, __ggh AS age)|(__ggh AS age, name AS n)) FROM test/
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
specify "should overrun the previous select option" do
|
1107
|
+
@d.select!(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
|
1108
|
+
@d.select!(:price).select(:name).sql.should == 'SELECT name FROM test'
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
specify "should accept arbitrary objects and literalize them correctly" do
|
1112
|
+
@d.select(1, :a, 't').sql.should == "SELECT 1, a, 't' FROM test"
|
1113
|
+
|
1114
|
+
@d.select(nil, :sum.sql_function(:t), :x___y).sql.should == "SELECT NULL, sum(t), x AS y FROM test"
|
1115
|
+
|
1116
|
+
@d.select(nil, 1, :x => :y).sql.should == "SELECT NULL, 1, x AS y FROM test"
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
specify "should accept a block that yields a virtual row" do
|
1120
|
+
@d.select{|o| o.a}.sql.should == 'SELECT a FROM test'
|
1121
|
+
@d.select{a(1)}.sql.should == 'SELECT a(1) FROM test'
|
1122
|
+
@d.select{|o| o.a(1, 2)}.sql.should == 'SELECT a(1, 2) FROM test'
|
1123
|
+
@d.select{[a, a(1, 2)]}.sql.should == 'SELECT a, a(1, 2) FROM test'
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
specify "should merge regular arguments with argument returned from block" do
|
1127
|
+
@d.select(:b){a}.sql.should == 'SELECT b, a FROM test'
|
1128
|
+
@d.select(:b, :c){|o| o.a(1)}.sql.should == 'SELECT b, c, a(1) FROM test'
|
1129
|
+
@d.select(:b){[a, a(1, 2)]}.sql.should == 'SELECT b, a, a(1, 2) FROM test'
|
1130
|
+
@d.select(:b, :c){|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT b, c, a, a(1, 2) FROM test'
|
1131
|
+
end
|
1132
|
+
end
|
1133
|
+
|
1134
|
+
context "Dataset#select_all" do
|
1135
|
+
before do
|
1136
|
+
@d = Sequel::Dataset.new(nil).from(:test)
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
specify "should select the wildcard" do
|
1140
|
+
@d.select_all.sql.should == 'SELECT * FROM test'
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
specify "should overrun the previous select option" do
|
1144
|
+
@d.select!(:a, :b, :c).select_all.sql.should == 'SELECT * FROM test'
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
context "Dataset#select_more" do
|
1149
|
+
before do
|
1150
|
+
@d = Sequel::Dataset.new(nil).from(:test)
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
specify "should act like #select for datasets with no selection" do
|
1154
|
+
@d.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
|
1155
|
+
@d.select_all.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
|
1156
|
+
@d.select(:blah).select_all.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
specify "should add to the currently selected columns" do
|
1160
|
+
@d.select(:a).select_more(:b).sql.should == 'SELECT a, b FROM test'
|
1161
|
+
@d.select(:a.*).select_more(:b.*).sql.should == 'SELECT a.*, b.* FROM test'
|
1162
|
+
end
|
1163
|
+
|
1164
|
+
specify "should accept a block that yields a virtual row" do
|
1165
|
+
@d.select(:a).select_more{|o| o.b}.sql.should == 'SELECT a, b FROM test'
|
1166
|
+
@d.select(:a.*).select_more(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
|
1167
|
+
end
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
context "Dataset#select_append" do
|
1171
|
+
before do
|
1172
|
+
@d = Sequel::Dataset.new(nil).from(:test)
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
specify "should select * in addition to columns if no columns selected" do
|
1176
|
+
@d.select_append(:a, :b).sql.should == 'SELECT *, a, b FROM test'
|
1177
|
+
@d.select_all.select_append(:a, :b).sql.should == 'SELECT *, a, b FROM test'
|
1178
|
+
@d.select(:blah).select_all.select_append(:a, :b).sql.should == 'SELECT *, a, b FROM test'
|
1179
|
+
end
|
1180
|
+
|
1181
|
+
specify "should add to the currently selected columns" do
|
1182
|
+
@d.select(:a).select_append(:b).sql.should == 'SELECT a, b FROM test'
|
1183
|
+
@d.select(:a.*).select_append(:b.*).sql.should == 'SELECT a.*, b.* FROM test'
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
specify "should accept a block that yields a virtual row" do
|
1187
|
+
@d.select(:a).select_append{|o| o.b}.sql.should == 'SELECT a, b FROM test'
|
1188
|
+
@d.select(:a.*).select_append(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
|
1189
|
+
end
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
context "Dataset#order" do
|
1193
|
+
before do
|
1194
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
specify "should include an ORDER BY clause in the select statement" do
|
1198
|
+
@dataset.order(:name).sql.should ==
|
1199
|
+
'SELECT * FROM test ORDER BY name'
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
specify "should accept multiple arguments" do
|
1203
|
+
@dataset.order(:name, :price.desc).sql.should ==
|
1204
|
+
'SELECT * FROM test ORDER BY name, price DESC'
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
specify "should overrun a previous ordering" do
|
1208
|
+
@dataset.order(:name).order(:stamp).sql.should ==
|
1209
|
+
'SELECT * FROM test ORDER BY stamp'
|
1210
|
+
end
|
1211
|
+
|
1212
|
+
specify "should accept a literal string" do
|
1213
|
+
@dataset.order('dada ASC'.lit).sql.should ==
|
1214
|
+
'SELECT * FROM test ORDER BY dada ASC'
|
1215
|
+
end
|
1216
|
+
|
1217
|
+
specify "should accept a hash as an expression" do
|
1218
|
+
@dataset.order(:name=>nil).sql.should ==
|
1219
|
+
'SELECT * FROM test ORDER BY (name IS NULL)'
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
specify "should accept a nil to remove ordering" do
|
1223
|
+
@dataset.order(:bah).order(nil).sql.should ==
|
1224
|
+
'SELECT * FROM test'
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
specify "should accept a block that yields a virtual row" do
|
1228
|
+
@dataset.order{|o| o.a}.sql.should == 'SELECT * FROM test ORDER BY a'
|
1229
|
+
@dataset.order{a(1)}.sql.should == 'SELECT * FROM test ORDER BY a(1)'
|
1230
|
+
@dataset.order{|o| o.a(1, 2)}.sql.should == 'SELECT * FROM test ORDER BY a(1, 2)'
|
1231
|
+
@dataset.order{[a, a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY a, a(1, 2)'
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
specify "should merge regular arguments with argument returned from block" do
|
1235
|
+
@dataset.order(:b){a}.sql.should == 'SELECT * FROM test ORDER BY b, a'
|
1236
|
+
@dataset.order(:b, :c){|o| o.a(1)}.sql.should == 'SELECT * FROM test ORDER BY b, c, a(1)'
|
1237
|
+
@dataset.order(:b){[a, a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY b, a, a(1, 2)'
|
1238
|
+
@dataset.order(:b, :c){|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY b, c, a, a(1, 2)'
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
context "Dataset#unfiltered" do
|
1243
|
+
specify "should remove filtering from the dataset" do
|
1244
|
+
Sequel::Dataset.new(nil).from(:test).filter(:score=>1).unfiltered.sql.should == 'SELECT * FROM test'
|
1245
|
+
end
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
context "Dataset#unlimited" do
|
1249
|
+
specify "should remove limit and offset from the dataset" do
|
1250
|
+
Sequel::Dataset.new(nil).from(:test).limit(1, 2).unlimited.sql.should == 'SELECT * FROM test'
|
1251
|
+
end
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
context "Dataset#ungrouped" do
|
1255
|
+
specify "should remove group and having clauses from the dataset" do
|
1256
|
+
Sequel::Dataset.new(nil).from(:test).group(:a).having(:b).ungrouped.sql.should == 'SELECT * FROM test'
|
1257
|
+
end
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
context "Dataset#unordered" do
|
1261
|
+
specify "should remove ordering from the dataset" do
|
1262
|
+
Sequel::Dataset.new(nil).from(:test).order(:name).unordered.sql.should == 'SELECT * FROM test'
|
1263
|
+
end
|
1264
|
+
end
|
1265
|
+
|
1266
|
+
context "Dataset#with_sql" do
|
1267
|
+
before do
|
1268
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
specify "should use static sql" do
|
1272
|
+
@dataset.with_sql('SELECT 1 FROM test').sql.should == 'SELECT 1 FROM test'
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
specify "should work with placeholders" do
|
1276
|
+
@dataset.with_sql('SELECT ? FROM test', 1).sql.should == 'SELECT 1 FROM test'
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
specify "should work with named placeholders" do
|
1280
|
+
@dataset.with_sql('SELECT :x FROM test', :x=>1).sql.should == 'SELECT 1 FROM test'
|
1281
|
+
end
|
1282
|
+
|
1283
|
+
specify "should keep row_proc" do
|
1284
|
+
@dataset.with_sql('SELECT 1 FROM test').row_proc.should == @dataset.row_proc
|
1285
|
+
end
|
1286
|
+
end
|
1287
|
+
|
1288
|
+
context "Dataset#order_by" do
|
1289
|
+
before do
|
1290
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
specify "should include an ORDER BY clause in the select statement" do
|
1294
|
+
@dataset.order_by(:name).sql.should ==
|
1295
|
+
'SELECT * FROM test ORDER BY name'
|
1296
|
+
end
|
1297
|
+
|
1298
|
+
specify "should accept multiple arguments" do
|
1299
|
+
@dataset.order_by(:name, :price.desc).sql.should ==
|
1300
|
+
'SELECT * FROM test ORDER BY name, price DESC'
|
1301
|
+
end
|
1302
|
+
|
1303
|
+
specify "should overrun a previous ordering" do
|
1304
|
+
@dataset.order_by(:name).order(:stamp).sql.should ==
|
1305
|
+
'SELECT * FROM test ORDER BY stamp'
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
specify "should accept a string" do
|
1309
|
+
@dataset.order_by('dada ASC'.lit).sql.should ==
|
1310
|
+
'SELECT * FROM test ORDER BY dada ASC'
|
1311
|
+
end
|
1312
|
+
|
1313
|
+
specify "should accept a nil to remove ordering" do
|
1314
|
+
@dataset.order_by(:bah).order_by(nil).sql.should ==
|
1315
|
+
'SELECT * FROM test'
|
1316
|
+
end
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
context "Dataset#order_more" do
|
1320
|
+
before do
|
1321
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
specify "should include an ORDER BY clause in the select statement" do
|
1325
|
+
@dataset.order_more(:name).sql.should ==
|
1326
|
+
'SELECT * FROM test ORDER BY name'
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
specify "should add to a previous ordering" do
|
1330
|
+
@dataset.order(:name).order_more(:stamp.desc).sql.should ==
|
1331
|
+
'SELECT * FROM test ORDER BY name, stamp DESC'
|
1332
|
+
end
|
1333
|
+
|
1334
|
+
specify "should accept a block that yields a virtual row" do
|
1335
|
+
@dataset.order(:a).order_more{|o| o.b}.sql.should == 'SELECT * FROM test ORDER BY a, b'
|
1336
|
+
@dataset.order(:a, :b).order_more(:c, :d){[e, f(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY a, b, c, d, e, f(1, 2)'
|
1337
|
+
end
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
context "Dataset#reverse_order" do
|
1341
|
+
before do
|
1342
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
1343
|
+
end
|
1344
|
+
|
1345
|
+
specify "should use DESC as default order" do
|
1346
|
+
@dataset.reverse_order(:name).sql.should ==
|
1347
|
+
'SELECT * FROM test ORDER BY name DESC'
|
1348
|
+
end
|
1349
|
+
|
1350
|
+
specify "should invert the order given" do
|
1351
|
+
@dataset.reverse_order(:name.desc).sql.should ==
|
1352
|
+
'SELECT * FROM test ORDER BY name ASC'
|
1353
|
+
end
|
1354
|
+
|
1355
|
+
specify "should invert the order for ASC expressions" do
|
1356
|
+
@dataset.reverse_order(:name.asc).sql.should ==
|
1357
|
+
'SELECT * FROM test ORDER BY name DESC'
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
specify "should accept multiple arguments" do
|
1361
|
+
@dataset.reverse_order(:name, :price.desc).sql.should ==
|
1362
|
+
'SELECT * FROM test ORDER BY name DESC, price ASC'
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
specify "should reverse a previous ordering if no arguments are given" do
|
1366
|
+
@dataset.order(:name).reverse_order.sql.should ==
|
1367
|
+
'SELECT * FROM test ORDER BY name DESC'
|
1368
|
+
@dataset.order(:clumsy.desc, :fool).reverse_order.sql.should ==
|
1369
|
+
'SELECT * FROM test ORDER BY clumsy ASC, fool DESC'
|
1370
|
+
end
|
1371
|
+
|
1372
|
+
specify "should return an unordered dataset for a dataset with no order" do
|
1373
|
+
@dataset.unordered.reverse_order.sql.should ==
|
1374
|
+
'SELECT * FROM test'
|
1375
|
+
end
|
1376
|
+
|
1377
|
+
specify "should have #reverse alias" do
|
1378
|
+
@dataset.order(:name).reverse.sql.should ==
|
1379
|
+
'SELECT * FROM test ORDER BY name DESC'
|
1380
|
+
end
|
1381
|
+
end
|
1382
|
+
|
1383
|
+
context "Dataset#limit" do
|
1384
|
+
before do
|
1385
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
1386
|
+
end
|
1387
|
+
|
1388
|
+
specify "should include a LIMIT clause in the select statement" do
|
1389
|
+
@dataset.limit(10).sql.should ==
|
1390
|
+
'SELECT * FROM test LIMIT 10'
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
specify "should accept ranges" do
|
1394
|
+
@dataset.limit(3..7).sql.should ==
|
1395
|
+
'SELECT * FROM test LIMIT 5 OFFSET 3'
|
1396
|
+
|
1397
|
+
@dataset.limit(3...7).sql.should ==
|
1398
|
+
'SELECT * FROM test LIMIT 4 OFFSET 3'
|
1399
|
+
end
|
1400
|
+
|
1401
|
+
specify "should include an offset if a second argument is given" do
|
1402
|
+
@dataset.limit(6, 10).sql.should ==
|
1403
|
+
'SELECT * FROM test LIMIT 6 OFFSET 10'
|
1404
|
+
end
|
1405
|
+
|
1406
|
+
specify "should convert regular strings to integers" do
|
1407
|
+
@dataset.limit('6', 'a() - 1').sql.should ==
|
1408
|
+
'SELECT * FROM test LIMIT 6 OFFSET 0'
|
1409
|
+
end
|
1410
|
+
|
1411
|
+
specify "should not convert literal strings to integers" do
|
1412
|
+
@dataset.limit('6'.lit, 'a() - 1'.lit).sql.should ==
|
1413
|
+
'SELECT * FROM test LIMIT 6 OFFSET a() - 1'
|
1414
|
+
end
|
1415
|
+
|
1416
|
+
specify "should not convert other objects" do
|
1417
|
+
@dataset.limit(6, :a.sql_function - 1).sql.should ==
|
1418
|
+
'SELECT * FROM test LIMIT 6 OFFSET (a() - 1)'
|
1419
|
+
end
|
1420
|
+
|
1421
|
+
specify "should work with fixed sql datasets" do
|
1422
|
+
@dataset.opts[:sql] = 'select * from cccc'
|
1423
|
+
@dataset.limit(6, 10).sql.should ==
|
1424
|
+
'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
|
1425
|
+
end
|
1426
|
+
|
1427
|
+
specify "should raise an error if an invalid limit or offset is used" do
|
1428
|
+
proc{@dataset.limit(-1)}.should raise_error(Sequel::Error)
|
1429
|
+
proc{@dataset.limit(0)}.should raise_error(Sequel::Error)
|
1430
|
+
proc{@dataset.limit(1)}.should_not raise_error(Sequel::Error)
|
1431
|
+
proc{@dataset.limit(1, -1)}.should raise_error(Sequel::Error)
|
1432
|
+
proc{@dataset.limit(1, 0)}.should_not raise_error(Sequel::Error)
|
1433
|
+
proc{@dataset.limit(1, 1)}.should_not raise_error(Sequel::Error)
|
1434
|
+
end
|
1435
|
+
end
|
1436
|
+
|
1437
|
+
context "Dataset#naked" do
|
1438
|
+
before do
|
1439
|
+
@d1 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4})
|
1440
|
+
@d2 = @d1.clone
|
1441
|
+
@d2.row_proc = Proc.new{|r| r}
|
1442
|
+
end
|
1443
|
+
|
1444
|
+
specify "should remove any existing row_proc" do
|
1445
|
+
naked = @d2.naked
|
1446
|
+
naked.row_proc.should be_nil
|
1447
|
+
end
|
1448
|
+
end
|
1449
|
+
|
1450
|
+
context "Dataset#qualified_column_name" do
|
1451
|
+
before do
|
1452
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
1453
|
+
end
|
1454
|
+
|
1455
|
+
specify "should return the literal value if not given a symbol" do
|
1456
|
+
@dataset.literal(@dataset.send(:qualified_column_name, 'ccc__b', :items)).should == "'ccc__b'"
|
1457
|
+
@dataset.literal(@dataset.send(:qualified_column_name, 3, :items)).should == '3'
|
1458
|
+
@dataset.literal(@dataset.send(:qualified_column_name, 'a'.lit, :items)).should == 'a'
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
specify "should qualify the column with the supplied table name if given an unqualified symbol" do
|
1462
|
+
@dataset.literal(@dataset.send(:qualified_column_name, :b1, :items)).should == 'items.b1'
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
specify "should not changed the qualifed column's table if given a qualified symbol" do
|
1466
|
+
@dataset.literal(@dataset.send(:qualified_column_name, :ccc__b, :items)).should == 'ccc.b'
|
1467
|
+
end
|
1468
|
+
end
|
1469
|
+
|
1470
|
+
class DummyDataset < Sequel::Dataset
|
1471
|
+
VALUES = [
|
1472
|
+
{:a => 1, :b => 2},
|
1473
|
+
{:a => 3, :b => 4},
|
1474
|
+
{:a => 5, :b => 6}
|
1475
|
+
]
|
1476
|
+
def fetch_rows(sql, &block)
|
1477
|
+
VALUES.each(&block)
|
1478
|
+
end
|
1479
|
+
end
|
1480
|
+
|
1481
|
+
context "Dataset#map" do
|
1482
|
+
before do
|
1483
|
+
@d = DummyDataset.new(nil).from(:items)
|
1484
|
+
end
|
1485
|
+
|
1486
|
+
specify "should provide the usual functionality if no argument is given" do
|
1487
|
+
@d.map {|n| n[:a] + n[:b]}.should == [3, 7, 11]
|
1488
|
+
end
|
1489
|
+
|
1490
|
+
specify "should map using #[column name] if column name is given" do
|
1491
|
+
@d.map(:a).should == [1, 3, 5]
|
1492
|
+
end
|
1493
|
+
|
1494
|
+
specify "should return the complete dataset values if nothing is given" do
|
1495
|
+
@d.map.to_a.should == DummyDataset::VALUES
|
1496
|
+
end
|
1497
|
+
end
|
1498
|
+
|
1499
|
+
context "Dataset#to_hash" do
|
1500
|
+
before do
|
1501
|
+
@d = DummyDataset.new(nil).from(:items)
|
1502
|
+
end
|
1503
|
+
|
1504
|
+
specify "should provide a hash with the first column as key and the second as value" do
|
1505
|
+
@d.to_hash(:a, :b).should == {1 => 2, 3 => 4, 5 => 6}
|
1506
|
+
@d.to_hash(:b, :a).should == {2 => 1, 4 => 3, 6 => 5}
|
1507
|
+
end
|
1508
|
+
|
1509
|
+
specify "should provide a hash with the first column as key and the entire hash as value if the value column is blank or nil" do
|
1510
|
+
@d.to_hash(:a).should == {1 => {:a => 1, :b => 2}, 3 => {:a => 3, :b => 4}, 5 => {:a => 5, :b => 6}}
|
1511
|
+
@d.to_hash(:b).should == {2 => {:a => 1, :b => 2}, 4 => {:a => 3, :b => 4}, 6 => {:a => 5, :b => 6}}
|
1512
|
+
end
|
1513
|
+
end
|
1514
|
+
|
1515
|
+
context "Dataset#distinct" do
|
1516
|
+
before do
|
1517
|
+
@db = MockDatabase.new
|
1518
|
+
@dataset = @db[:test].select(:name)
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
specify "should include DISTINCT clause in statement" do
|
1522
|
+
@dataset.distinct.sql.should == 'SELECT DISTINCT name FROM test'
|
1523
|
+
end
|
1524
|
+
|
1525
|
+
specify "should raise an error if columns given and DISTINCT ON is not supported" do
|
1526
|
+
proc{@dataset.distinct}.should_not raise_error
|
1527
|
+
proc{@dataset.distinct(:a)}.should raise_error(Sequel::InvalidOperation)
|
1528
|
+
end
|
1529
|
+
|
1530
|
+
specify "should use DISTINCT ON if columns are given and DISTINCT ON is supported" do
|
1531
|
+
@dataset.meta_def(:supports_distinct_on?){true}
|
1532
|
+
@dataset.distinct(:a, :b).sql.should == 'SELECT DISTINCT ON (a, b) name FROM test'
|
1533
|
+
@dataset.distinct(:stamp.cast(:integer), :node_id=>nil).sql.should == 'SELECT DISTINCT ON (CAST(stamp AS integer), (node_id IS NULL)) name FROM test'
|
1534
|
+
end
|
1535
|
+
|
1536
|
+
specify "should do a subselect for count" do
|
1537
|
+
@dataset.distinct.count
|
1538
|
+
@db.sqls.should == ['SELECT COUNT(*) AS count FROM (SELECT DISTINCT name FROM test) AS t1 LIMIT 1']
|
1539
|
+
end
|
1540
|
+
end
|
1541
|
+
|
1542
|
+
context "Dataset#count" do
|
1543
|
+
before do
|
1544
|
+
@c = Class.new(Sequel::Dataset) do
|
1545
|
+
def self.sql
|
1546
|
+
@@sql
|
1547
|
+
end
|
1548
|
+
|
1549
|
+
def fetch_rows(sql)
|
1550
|
+
@columns = [sql =~ /SELECT COUNT/i ? :count : :a]
|
1551
|
+
@@sql = sql
|
1552
|
+
yield({@columns.first=>1})
|
1553
|
+
end
|
1554
|
+
end
|
1555
|
+
@dataset = @c.new(nil).from(:test)
|
1556
|
+
end
|
1557
|
+
|
1558
|
+
specify "should format SQL properly" do
|
1559
|
+
@dataset.count.should == 1
|
1560
|
+
@c.sql.should == 'SELECT COUNT(*) AS count FROM test LIMIT 1'
|
1561
|
+
end
|
1562
|
+
|
1563
|
+
specify "should include the where clause if it's there" do
|
1564
|
+
@dataset.filter(:abc.sql_number < 30).count.should == 1
|
1565
|
+
@c.sql.should == 'SELECT COUNT(*) AS count FROM test WHERE (abc < 30) LIMIT 1'
|
1566
|
+
end
|
1567
|
+
|
1568
|
+
specify "should count properly for datasets with fixed sql" do
|
1569
|
+
@dataset.opts[:sql] = "select abc from xyz"
|
1570
|
+
@dataset.count.should == 1
|
1571
|
+
@c.sql.should == "SELECT COUNT(*) AS count FROM (select abc from xyz) AS t1 LIMIT 1"
|
1572
|
+
end
|
1573
|
+
|
1574
|
+
specify "should count properly when using UNION, INTERSECT, or EXCEPT" do
|
1575
|
+
@dataset.union(@dataset).count.should == 1
|
1576
|
+
@c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 1"
|
1577
|
+
@dataset.intersect(@dataset).count.should == 1
|
1578
|
+
@c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test INTERSECT SELECT * FROM test) AS t1 LIMIT 1"
|
1579
|
+
@dataset.except(@dataset).count.should == 1
|
1580
|
+
@c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test EXCEPT SELECT * FROM test) AS t1 LIMIT 1"
|
1581
|
+
end
|
1582
|
+
|
1583
|
+
specify "should return limit if count is greater than it" do
|
1584
|
+
@dataset.limit(5).count.should == 1
|
1585
|
+
@c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"
|
1586
|
+
end
|
1587
|
+
|
1588
|
+
it "should work on a graphed_dataset" do
|
1589
|
+
@dataset.should_receive(:columns).twice.and_return([:a])
|
1590
|
+
@dataset.graph(@dataset, [:a], :table_alias=>:test2).count.should == 1
|
1591
|
+
@c.sql.should == 'SELECT COUNT(*) AS count FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1'
|
1592
|
+
end
|
1593
|
+
|
1594
|
+
specify "should not cache the columns value" do
|
1595
|
+
ds = @dataset.from(:blah)
|
1596
|
+
ds.columns.should == [:a]
|
1597
|
+
ds.count.should == 1
|
1598
|
+
@c.sql.should == 'SELECT COUNT(*) AS count FROM blah LIMIT 1'
|
1599
|
+
ds.columns.should == [:a]
|
1600
|
+
end
|
1601
|
+
end
|
1602
|
+
|
1603
|
+
|
1604
|
+
context "Dataset#group_and_count" do
|
1605
|
+
before do
|
1606
|
+
@c = Class.new(Sequel::Dataset) do
|
1607
|
+
def self.sql
|
1608
|
+
@@sql
|
1609
|
+
end
|
1610
|
+
|
1611
|
+
def fetch_rows(sql)
|
1612
|
+
@@sql = sql
|
1613
|
+
yield({1 => 1})
|
1614
|
+
end
|
1615
|
+
end
|
1616
|
+
@ds = @c.new(nil).from(:test)
|
1617
|
+
end
|
1618
|
+
|
1619
|
+
specify "should format SQL properly" do
|
1620
|
+
@ds.group_and_count(:name).sql.should ==
|
1621
|
+
"SELECT name, count(*) AS count FROM test GROUP BY name"
|
1622
|
+
end
|
1623
|
+
|
1624
|
+
specify "should accept multiple columns for grouping" do
|
1625
|
+
@ds.group_and_count(:a, :b).sql.should ==
|
1626
|
+
"SELECT a, b, count(*) AS count FROM test GROUP BY a, b"
|
1627
|
+
end
|
1628
|
+
|
1629
|
+
specify "should format column aliases in the select clause but not in the group clause" do
|
1630
|
+
@ds.group_and_count(:name___n).sql.should ==
|
1631
|
+
"SELECT name AS n, count(*) AS count FROM test GROUP BY name"
|
1632
|
+
@ds.group_and_count(:name__n).sql.should ==
|
1633
|
+
"SELECT name.n, count(*) AS count FROM test GROUP BY name.n"
|
1634
|
+
end
|
1635
|
+
|
1636
|
+
specify "should handle identifiers" do
|
1637
|
+
@ds.group_and_count(:name___n.identifier).sql.should ==
|
1638
|
+
"SELECT name___n, count(*) AS count FROM test GROUP BY name___n"
|
1639
|
+
end
|
1640
|
+
|
1641
|
+
specify "should handle literal strings" do
|
1642
|
+
@ds.group_and_count("name".lit).sql.should ==
|
1643
|
+
"SELECT name, count(*) AS count FROM test GROUP BY name"
|
1644
|
+
end
|
1645
|
+
|
1646
|
+
specify "should handle aliased expressions" do
|
1647
|
+
@ds.group_and_count(:name.as(:n)).sql.should ==
|
1648
|
+
"SELECT name AS n, count(*) AS count FROM test GROUP BY name"
|
1649
|
+
@ds.group_and_count(:name.identifier.as(:n)).sql.should ==
|
1650
|
+
"SELECT name AS n, count(*) AS count FROM test GROUP BY name"
|
1651
|
+
end
|
1652
|
+
end
|
1653
|
+
|
1654
|
+
context "Dataset#empty?" do
|
1655
|
+
specify "should return true if records exist in the dataset" do
|
1656
|
+
@c = Class.new(Sequel::Dataset) do
|
1657
|
+
def self.sql
|
1658
|
+
@@sql
|
1659
|
+
end
|
1660
|
+
|
1661
|
+
def fetch_rows(sql)
|
1662
|
+
@@sql = sql
|
1663
|
+
yield({1 => 1}) unless sql =~ /WHERE 'f'/
|
1664
|
+
end
|
1665
|
+
end
|
1666
|
+
@c.new(nil).from(:test).should_not be_empty
|
1667
|
+
@c.sql.should == 'SELECT 1 FROM test LIMIT 1'
|
1668
|
+
@c.new(nil).from(:test).filter(false).should be_empty
|
1669
|
+
@c.sql.should == "SELECT 1 FROM test WHERE 'f' LIMIT 1"
|
1670
|
+
end
|
1671
|
+
end
|
1672
|
+
|
1673
|
+
context "Dataset#first_source_alias" do
|
1674
|
+
before do
|
1675
|
+
@ds = Sequel::Dataset.new(nil)
|
1676
|
+
end
|
1677
|
+
|
1678
|
+
specify "should be the entire first source if not aliased" do
|
1679
|
+
@ds.from(:t).first_source_alias.should == :t
|
1680
|
+
@ds.from(:t__a.identifier).first_source_alias.should == :t__a.identifier
|
1681
|
+
@ds.from(:s__t).first_source_alias.should == :s__t
|
1682
|
+
@ds.from(:t.qualify(:s)).first_source_alias.should == :t.qualify(:s)
|
1683
|
+
end
|
1684
|
+
|
1685
|
+
specify "should be the alias if aliased" do
|
1686
|
+
@ds.from(:t___a).first_source_alias.should == :a
|
1687
|
+
@ds.from(:s__t___a).first_source_alias.should == :a
|
1688
|
+
@ds.from(:t.as(:a)).first_source_alias.should == :a
|
1689
|
+
end
|
1690
|
+
|
1691
|
+
specify "should be aliased as first_source" do
|
1692
|
+
@ds.from(:t).first_source.should == :t
|
1693
|
+
@ds.from(:t__a.identifier).first_source.should == :t__a.identifier
|
1694
|
+
@ds.from(:s__t___a).first_source.should == :a
|
1695
|
+
@ds.from(:t.as(:a)).first_source.should == :a
|
1696
|
+
end
|
1697
|
+
|
1698
|
+
specify "should raise exception if table doesn't have a source" do
|
1699
|
+
proc{@ds.first_source_alias.should == :t}.should raise_error(Sequel::Error)
|
1700
|
+
end
|
1701
|
+
end
|
1702
|
+
|
1703
|
+
context "Dataset#first_source_table" do
|
1704
|
+
before do
|
1705
|
+
@ds = Sequel::Dataset.new(nil)
|
1706
|
+
end
|
1707
|
+
|
1708
|
+
specify "should be the entire first source if not aliased" do
|
1709
|
+
@ds.from(:t).first_source_table.should == :t
|
1710
|
+
@ds.from(:t__a.identifier).first_source_table.should == :t__a.identifier
|
1711
|
+
@ds.from(:s__t).first_source_table.should == :s__t
|
1712
|
+
@ds.from(:t.qualify(:s)).first_source_table.should == :t.qualify(:s)
|
1713
|
+
end
|
1714
|
+
|
1715
|
+
specify "should be the unaliased part if aliased" do
|
1716
|
+
@ds.from(:t___a).first_source_table.should == :t.identifier
|
1717
|
+
@ds.from(:s__t___a).first_source_table.should == :t.qualify(:s)
|
1718
|
+
@ds.from(:t.as(:a)).first_source_table.should == :t
|
1719
|
+
end
|
1720
|
+
|
1721
|
+
specify "should raise exception if table doesn't have a source" do
|
1722
|
+
proc{@ds.first_source_table.should == :t}.should raise_error(Sequel::Error)
|
1723
|
+
end
|
1724
|
+
end
|
1725
|
+
|
1726
|
+
context "Dataset#from_self" do
|
1727
|
+
before do
|
1728
|
+
@ds = Sequel::Dataset.new(nil).from(:test).select(:name).limit(1)
|
1729
|
+
end
|
1730
|
+
|
1731
|
+
specify "should set up a default alias" do
|
1732
|
+
@ds.from_self.sql.should == 'SELECT * FROM (SELECT name FROM test LIMIT 1) AS t1'
|
1733
|
+
end
|
1734
|
+
|
1735
|
+
specify "should modify only the new dataset" do
|
1736
|
+
@ds.from_self.select(:bogus).sql.should == 'SELECT bogus FROM (SELECT name FROM test LIMIT 1) AS t1'
|
1737
|
+
end
|
1738
|
+
|
1739
|
+
specify "should use the user-specified alias" do
|
1740
|
+
@ds.from_self(:alias=>:some_name).sql.should == 'SELECT * FROM (SELECT name FROM test LIMIT 1) AS some_name'
|
1741
|
+
end
|
1742
|
+
|
1743
|
+
specify "should use the user-specified alias for joins" do
|
1744
|
+
@ds.from_self(:alias=>:some_name).inner_join(:posts, :alias=>:name).sql.should == \
|
1745
|
+
'SELECT * FROM (SELECT name FROM test LIMIT 1) AS some_name INNER JOIN posts ON (posts.alias = some_name.name)'
|
1746
|
+
end
|
1747
|
+
|
1748
|
+
specify "should not options such as server" do
|
1749
|
+
@ds.server(:blah).from_self(:alias=>:some_name).opts[:server].should == :blah
|
1750
|
+
end
|
1751
|
+
|
1752
|
+
end
|
1753
|
+
|
1754
|
+
context "Dataset#join_table" do
|
1755
|
+
before do
|
1756
|
+
@d = MockDataset.new(nil).from(:items)
|
1757
|
+
@d.quote_identifiers = true
|
1758
|
+
end
|
1759
|
+
|
1760
|
+
specify "should format the JOIN clause properly" do
|
1761
|
+
@d.join_table(:left_outer, :categories, :category_id => :id).sql.should ==
|
1762
|
+
'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1763
|
+
end
|
1764
|
+
|
1765
|
+
specify "should handle multiple conditions on the same join table column" do
|
1766
|
+
@d.join_table(:left_outer, :categories, [[:category_id, :id], [:category_id, 0..100]]).sql.should ==
|
1767
|
+
'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON (("categories"."category_id" = "items"."id") AND ("categories"."category_id" >= 0) AND ("categories"."category_id" <= 100))'
|
1768
|
+
end
|
1769
|
+
|
1770
|
+
specify "should include WHERE clause if applicable" do
|
1771
|
+
@d.filter(:price.sql_number < 100).join_table(:right_outer, :categories, :category_id => :id).sql.should ==
|
1772
|
+
'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") WHERE ("price" < 100)'
|
1773
|
+
end
|
1774
|
+
|
1775
|
+
specify "should include ORDER BY clause if applicable" do
|
1776
|
+
@d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should ==
|
1777
|
+
'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") ORDER BY "stamp"'
|
1778
|
+
end
|
1779
|
+
|
1780
|
+
specify "should support multiple joins" do
|
1781
|
+
@d.join_table(:inner, :b, :items_id=>:id).join_table(:left_outer, :c, :b_id => :b__id).sql.should ==
|
1782
|
+
'SELECT * FROM "items" INNER JOIN "b" ON ("b"."items_id" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")'
|
1783
|
+
end
|
1784
|
+
|
1785
|
+
specify "should support arbitrary join types" do
|
1786
|
+
@d.join_table(:magic, :categories, :category_id=>:id).sql.should ==
|
1787
|
+
'SELECT * FROM "items" MAGIC JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1788
|
+
end
|
1789
|
+
|
1790
|
+
specify "should support many join methods" do
|
1791
|
+
@d.left_outer_join(:categories, :category_id=>:id).sql.should ==
|
1792
|
+
'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1793
|
+
@d.right_outer_join(:categories, :category_id=>:id).sql.should ==
|
1794
|
+
'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1795
|
+
@d.full_outer_join(:categories, :category_id=>:id).sql.should ==
|
1796
|
+
'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1797
|
+
@d.inner_join(:categories, :category_id=>:id).sql.should ==
|
1798
|
+
'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1799
|
+
@d.left_join(:categories, :category_id=>:id).sql.should ==
|
1800
|
+
'SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1801
|
+
@d.right_join(:categories, :category_id=>:id).sql.should ==
|
1802
|
+
'SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1803
|
+
@d.full_join(:categories, :category_id=>:id).sql.should ==
|
1804
|
+
'SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1805
|
+
@d.natural_join(:categories).sql.should ==
|
1806
|
+
'SELECT * FROM "items" NATURAL JOIN "categories"'
|
1807
|
+
@d.natural_left_join(:categories).sql.should ==
|
1808
|
+
'SELECT * FROM "items" NATURAL LEFT JOIN "categories"'
|
1809
|
+
@d.natural_right_join(:categories).sql.should ==
|
1810
|
+
'SELECT * FROM "items" NATURAL RIGHT JOIN "categories"'
|
1811
|
+
@d.natural_full_join(:categories).sql.should ==
|
1812
|
+
'SELECT * FROM "items" NATURAL FULL JOIN "categories"'
|
1813
|
+
@d.cross_join(:categories).sql.should ==
|
1814
|
+
'SELECT * FROM "items" CROSS JOIN "categories"'
|
1815
|
+
end
|
1816
|
+
|
1817
|
+
specify "should raise an error if additional arguments are provided to join methods that don't take conditions" do
|
1818
|
+
proc{@d.natural_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
|
1819
|
+
proc{@d.natural_left_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
|
1820
|
+
proc{@d.natural_right_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
|
1821
|
+
proc{@d.natural_full_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
|
1822
|
+
proc{@d.cross_join(:categories, :id=>:id)}.should raise_error(ArgumentError)
|
1823
|
+
end
|
1824
|
+
|
1825
|
+
specify "should raise an error if blocks are provided to join methods that don't pass them" do
|
1826
|
+
proc{@d.natural_join(:categories){}}.should raise_error(Sequel::Error)
|
1827
|
+
proc{@d.natural_left_join(:categories){}}.should raise_error(Sequel::Error)
|
1828
|
+
proc{@d.natural_right_join(:categories){}}.should raise_error(Sequel::Error)
|
1829
|
+
proc{@d.natural_full_join(:categories){}}.should raise_error(Sequel::Error)
|
1830
|
+
proc{@d.cross_join(:categories){}}.should raise_error(Sequel::Error)
|
1831
|
+
end
|
1832
|
+
|
1833
|
+
specify "should default to a plain join if nil is used for the type" do
|
1834
|
+
@d.join_table(nil, :categories, :category_id=>:id).sql.should ==
|
1835
|
+
'SELECT * FROM "items" JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1836
|
+
end
|
1837
|
+
|
1838
|
+
specify "should use an inner join for Dataset#join" do
|
1839
|
+
@d.join(:categories, :category_id=>:id).sql.should ==
|
1840
|
+
'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
|
1841
|
+
end
|
1842
|
+
|
1843
|
+
specify "should support aliased tables using the deprecated argument" do
|
1844
|
+
@d.from('stats').join('players', {:id => :player_id}, 'p').sql.should ==
|
1845
|
+
'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
|
1846
|
+
end
|
1847
|
+
|
1848
|
+
specify "should support aliased tables using the :table_alias option" do
|
1849
|
+
@d.from('stats').join('players', {:id => :player_id}, :table_alias=>:p).sql.should ==
|
1850
|
+
'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
|
1851
|
+
end
|
1852
|
+
|
1853
|
+
specify "should support using an alias for the FROM when doing the first join with unqualified condition columns" do
|
1854
|
+
ds = MockDataset.new(nil).from(:foo => :f)
|
1855
|
+
ds.quote_identifiers = true
|
1856
|
+
ds.join_table(:inner, :bar, :id => :bar_id).sql.should ==
|
1857
|
+
'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
|
1858
|
+
end
|
1859
|
+
|
1860
|
+
specify "should support implicit schemas in from table symbols" do
|
1861
|
+
@d.from(:s__t).join(:u__v, {:id => :player_id}).sql.should ==
|
1862
|
+
'SELECT * FROM "s"."t" INNER JOIN "u"."v" ON ("u"."v"."id" = "s"."t"."player_id")'
|
1863
|
+
end
|
1864
|
+
|
1865
|
+
specify "should support implicit aliases in from table symbols" do
|
1866
|
+
@d.from(:t___z).join(:v___y, {:id => :player_id}).sql.should ==
|
1867
|
+
'SELECT * FROM "t" AS "z" INNER JOIN "v" AS "y" ON ("y"."id" = "z"."player_id")'
|
1868
|
+
@d.from(:s__t___z).join(:u__v___y, {:id => :player_id}).sql.should ==
|
1869
|
+
'SELECT * FROM "s"."t" AS "z" INNER JOIN "u"."v" AS "y" ON ("y"."id" = "z"."player_id")'
|
1870
|
+
end
|
1871
|
+
|
1872
|
+
specify "should support AliasedExpressions" do
|
1873
|
+
@d.from(:s.as(:t)).join(:u.as(:v), {:id => :player_id}).sql.should ==
|
1874
|
+
'SELECT * FROM "s" AS "t" INNER JOIN "u" AS "v" ON ("v"."id" = "t"."player_id")'
|
1875
|
+
end
|
1876
|
+
|
1877
|
+
specify "should support the :implicit_qualifier option" do
|
1878
|
+
@d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should ==
|
1879
|
+
'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
|
1880
|
+
end
|
1881
|
+
|
1882
|
+
specify "should allow for arbitrary conditions in the JOIN clause" do
|
1883
|
+
@d.join_table(:left_outer, :categories, :status => 0).sql.should ==
|
1884
|
+
'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" = 0)'
|
1885
|
+
@d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should ==
|
1886
|
+
'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categorizable_type" = \'Post\')'
|
1887
|
+
@d.join_table(:left_outer, :categories, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should ==
|
1888
|
+
'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."timestamp" = CURRENT_TIMESTAMP)'
|
1889
|
+
@d.join_table(:left_outer, :categories, :status => [1, 2, 3]).sql.should ==
|
1890
|
+
'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" IN (1, 2, 3))'
|
1891
|
+
end
|
1892
|
+
|
1893
|
+
specify "should raise error for a table without a source" do
|
1894
|
+
proc {Sequel::Dataset.new(nil).join('players', :id => :player_id)}. \
|
1895
|
+
should raise_error(Sequel::Error)
|
1896
|
+
end
|
1897
|
+
|
1898
|
+
specify "should support joining datasets" do
|
1899
|
+
ds = Sequel::Dataset.new(nil).from(:categories)
|
1900
|
+
|
1901
|
+
@d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
|
1902
|
+
'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id")'
|
1903
|
+
|
1904
|
+
ds.filter!(:active => true)
|
1905
|
+
|
1906
|
+
@d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
|
1907
|
+
'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t1" ON ("t1"."item_id" = "items"."id")'
|
1908
|
+
|
1909
|
+
@d.from_self.join_table(:left_outer, ds, :item_id => :id).sql.should ==
|
1910
|
+
'SELECT * FROM (SELECT * FROM "items") AS "t1" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t2" ON ("t2"."item_id" = "t1"."id")'
|
1911
|
+
end
|
1912
|
+
|
1913
|
+
specify "should support joining datasets and aliasing the join" do
|
1914
|
+
ds = Sequel::Dataset.new(nil).from(:categories)
|
1915
|
+
|
1916
|
+
@d.join_table(:left_outer, ds, {:ds__item_id => :id}, :ds).sql.should ==
|
1917
|
+
'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "ds" ON ("ds"."item_id" = "items"."id")'
|
1918
|
+
end
|
1919
|
+
|
1920
|
+
specify "should support joining multiple datasets" do
|
1921
|
+
ds = Sequel::Dataset.new(nil).from(:categories)
|
1922
|
+
ds2 = Sequel::Dataset.new(nil).from(:nodes).select(:name)
|
1923
|
+
ds3 = Sequel::Dataset.new(nil).from(:attributes).filter("name = 'blah'")
|
1924
|
+
|
1925
|
+
@d.join_table(:left_outer, ds, :item_id => :id).join_table(:inner, ds2, :node_id=>:id).join_table(:right_outer, ds3, :attribute_id=>:id).sql.should ==
|
1926
|
+
'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id") ' \
|
1927
|
+
'INNER JOIN (SELECT name FROM nodes) AS "t2" ON ("t2"."node_id" = "t1"."id") ' \
|
1928
|
+
'RIGHT OUTER JOIN (SELECT * FROM attributes WHERE (name = \'blah\')) AS "t3" ON ("t3"."attribute_id" = "t2"."id")'
|
1929
|
+
end
|
1930
|
+
|
1931
|
+
specify "should support joining objects that respond to :table_name" do
|
1932
|
+
ds = Object.new
|
1933
|
+
def ds.table_name; :categories end
|
1934
|
+
|
1935
|
+
@d.join(ds, :item_id => :id).sql.should ==
|
1936
|
+
'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."item_id" = "items"."id")'
|
1937
|
+
end
|
1938
|
+
|
1939
|
+
specify "should support using a SQL String as the join condition" do
|
1940
|
+
@d.join(:categories, %{c.item_id = items.id}, :c).sql.should ==
|
1941
|
+
'SELECT * FROM "items" INNER JOIN "categories" AS "c" ON (c.item_id = items.id)'
|
1942
|
+
end
|
1943
|
+
|
1944
|
+
specify "should support using a boolean column as the join condition" do
|
1945
|
+
@d.join(:categories, :active).sql.should ==
|
1946
|
+
'SELECT * FROM "items" INNER JOIN "categories" ON "active"'
|
1947
|
+
end
|
1948
|
+
|
1949
|
+
specify "should support using an expression as the join condition" do
|
1950
|
+
@d.join(:categories, :number.sql_number > 10).sql.should ==
|
1951
|
+
'SELECT * FROM "items" INNER JOIN "categories" ON ("number" > 10)'
|
1952
|
+
end
|
1953
|
+
|
1954
|
+
specify "should support natural and cross joins using nil" do
|
1955
|
+
@d.join_table(:natural, :categories).sql.should ==
|
1956
|
+
'SELECT * FROM "items" NATURAL JOIN "categories"'
|
1957
|
+
@d.join_table(:cross, :categories, nil).sql.should ==
|
1958
|
+
'SELECT * FROM "items" CROSS JOIN "categories"'
|
1959
|
+
@d.join_table(:natural, :categories, nil, :c).sql.should ==
|
1960
|
+
'SELECT * FROM "items" NATURAL JOIN "categories" AS "c"'
|
1961
|
+
end
|
1962
|
+
|
1963
|
+
specify "should support joins with a USING clause if an array of symbols is used" do
|
1964
|
+
@d.join(:categories, [:id]).sql.should ==
|
1965
|
+
'SELECT * FROM "items" INNER JOIN "categories" USING ("id")'
|
1966
|
+
@d.join(:categories, [:id1, :id2]).sql.should ==
|
1967
|
+
'SELECT * FROM "items" INNER JOIN "categories" USING ("id1", "id2")'
|
1968
|
+
end
|
1969
|
+
|
1970
|
+
specify "should emulate JOIN USING (poorly) if the dataset doesn't support it" do
|
1971
|
+
@d.meta_def(:supports_join_using?){false}
|
1972
|
+
@d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."id" = "items"."id")'
|
1973
|
+
end
|
1974
|
+
|
1975
|
+
specify "should raise an error if using an array of symbols with a block" do
|
1976
|
+
proc{@d.join(:categories, [:id]){|j,lj,js|}}.should raise_error(Sequel::Error)
|
1977
|
+
end
|
1978
|
+
|
1979
|
+
specify "should support using a block that receieves the join table/alias, last join table/alias, and array of previous joins" do
|
1980
|
+
@d.join(:categories) do |join_alias, last_join_alias, joins|
|
1981
|
+
join_alias.should == :categories
|
1982
|
+
last_join_alias.should == :items
|
1983
|
+
joins.should == []
|
1984
|
+
end
|
1985
|
+
|
1986
|
+
@d.from(:items=>:i).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
|
1987
|
+
join_alias.should == :c
|
1988
|
+
last_join_alias.should == :i
|
1989
|
+
joins.should == []
|
1990
|
+
end
|
1991
|
+
|
1992
|
+
@d.from(:items___i).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
|
1993
|
+
join_alias.should == :c
|
1994
|
+
last_join_alias.should == :i
|
1995
|
+
joins.should == []
|
1996
|
+
end
|
1997
|
+
|
1998
|
+
@d.join(:blah).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
|
1999
|
+
join_alias.should == :c
|
2000
|
+
last_join_alias.should == :blah
|
2001
|
+
joins.should be_a_kind_of(Array)
|
2002
|
+
joins.length.should == 1
|
2003
|
+
joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
|
2004
|
+
joins.first.join_type.should == :inner
|
2005
|
+
end
|
2006
|
+
|
2007
|
+
@d.join_table(:natural, :blah, nil, :b).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
|
2008
|
+
join_alias.should == :c
|
2009
|
+
last_join_alias.should == :b
|
2010
|
+
joins.should be_a_kind_of(Array)
|
2011
|
+
joins.length.should == 1
|
2012
|
+
joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
|
2013
|
+
joins.first.join_type.should == :natural
|
2014
|
+
end
|
2015
|
+
|
2016
|
+
@d.join(:blah).join(:categories).join(:blah2) do |join_alias, last_join_alias, joins|
|
2017
|
+
join_alias.should == :blah2
|
2018
|
+
last_join_alias.should == :categories
|
2019
|
+
joins.should be_a_kind_of(Array)
|
2020
|
+
joins.length.should == 2
|
2021
|
+
joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
|
2022
|
+
joins.first.table.should == :blah
|
2023
|
+
joins.last.should be_a_kind_of(Sequel::SQL::JoinClause)
|
2024
|
+
joins.last.table.should == :categories
|
2025
|
+
end
|
2026
|
+
end
|
2027
|
+
|
2028
|
+
specify "should use the block result as the only condition if no condition is given" do
|
2029
|
+
@d.join(:categories){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
|
2030
|
+
'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" = "items"."c")'
|
2031
|
+
@d.join(:categories){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
|
2032
|
+
'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" > "items"."c")'
|
2033
|
+
end
|
2034
|
+
|
2035
|
+
specify "should combine the block conditions and argument conditions if both given" do
|
2036
|
+
@d.join(:categories, :a=>:d){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
|
2037
|
+
'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" = "items"."c"))'
|
2038
|
+
@d.join(:categories, :a=>:d){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
|
2039
|
+
'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" > "items"."c"))'
|
2040
|
+
end
|
2041
|
+
|
2042
|
+
specify "should not allow insert, update, delete, or truncate" do
|
2043
|
+
proc{@d.join(:categories, :a=>:d).insert_sql}.should raise_error(Sequel::InvalidOperation)
|
2044
|
+
proc{@d.join(:categories, :a=>:d).update_sql(:a=>1)}.should raise_error(Sequel::InvalidOperation)
|
2045
|
+
proc{@d.join(:categories, :a=>:d).delete_sql}.should raise_error(Sequel::InvalidOperation)
|
2046
|
+
proc{@d.join(:categories, :a=>:d).truncate_sql}.should raise_error(Sequel::InvalidOperation)
|
2047
|
+
end
|
2048
|
+
|
2049
|
+
specify "should raise an error if an invalid option is passed" do
|
2050
|
+
proc{@d.join(:c, [:id], nil)}.should raise_error(Sequel::Error)
|
2051
|
+
proc{@d.join(:c, [:id], :c.qualify(:d))}.should raise_error(Sequel::Error)
|
2052
|
+
end
|
2053
|
+
end
|
2054
|
+
|
2055
|
+
context "Dataset#[]=" do
|
2056
|
+
before do
|
2057
|
+
c = Class.new(Sequel::Dataset) do
|
2058
|
+
def last_sql
|
2059
|
+
@@last_sql
|
2060
|
+
end
|
2061
|
+
|
2062
|
+
def update(*args)
|
2063
|
+
@@last_sql = update_sql(*args)
|
2064
|
+
end
|
2065
|
+
end
|
2066
|
+
|
2067
|
+
@d = c.new(nil).from(:items)
|
2068
|
+
end
|
2069
|
+
|
2070
|
+
specify "should perform an update on the specified filter" do
|
2071
|
+
@d[:a => 1] = {:x => 3}
|
2072
|
+
@d.last_sql.should == 'UPDATE items SET x = 3 WHERE (a = 1)'
|
2073
|
+
end
|
2074
|
+
end
|
2075
|
+
|
2076
|
+
context "Dataset#set" do
|
2077
|
+
before do
|
2078
|
+
c = Class.new(Sequel::Dataset) do
|
2079
|
+
def last_sql
|
2080
|
+
@@last_sql
|
2081
|
+
end
|
2082
|
+
|
2083
|
+
def update(*args, &block)
|
2084
|
+
@@last_sql = update_sql(*args, &block)
|
2085
|
+
end
|
2086
|
+
end
|
2087
|
+
|
2088
|
+
@d = c.new(nil).from(:items)
|
2089
|
+
end
|
2090
|
+
|
2091
|
+
specify "should act as alias to #update" do
|
2092
|
+
@d.set({:x => 3})
|
2093
|
+
@d.last_sql.should == 'UPDATE items SET x = 3'
|
2094
|
+
end
|
2095
|
+
end
|
2096
|
+
|
2097
|
+
|
2098
|
+
context "Dataset#insert_multiple" do
|
2099
|
+
before do
|
2100
|
+
c = Class.new(Sequel::Dataset) do
|
2101
|
+
attr_reader :inserts
|
2102
|
+
def insert(arg)
|
2103
|
+
@inserts ||= []
|
2104
|
+
@inserts << arg
|
2105
|
+
end
|
2106
|
+
end
|
2107
|
+
|
2108
|
+
@d = c.new(nil)
|
2109
|
+
end
|
2110
|
+
|
2111
|
+
specify "should insert all items in the supplied array" do
|
2112
|
+
@d.insert_multiple [:aa, 5, 3, {1 => 2}]
|
2113
|
+
@d.inserts.should == [:aa, 5, 3, {1 => 2}]
|
2114
|
+
end
|
2115
|
+
|
2116
|
+
specify "should pass array items through the supplied block if given" do
|
2117
|
+
a = ["inevitable", "hello", "the ticking clock"]
|
2118
|
+
@d.insert_multiple(a) {|i| i.gsub('l', 'r')}
|
2119
|
+
@d.inserts.should == ["inevitabre", "herro", "the ticking crock"]
|
2120
|
+
end
|
2121
|
+
end
|
2122
|
+
|
2123
|
+
context "Dataset aggregate methods" do
|
2124
|
+
before do
|
2125
|
+
c = Class.new(Sequel::Dataset) do
|
2126
|
+
def fetch_rows(sql)
|
2127
|
+
yield({1 => sql})
|
2128
|
+
end
|
2129
|
+
end
|
2130
|
+
@d = c.new(nil).from(:test)
|
2131
|
+
end
|
2132
|
+
|
2133
|
+
specify "should include min" do
|
2134
|
+
@d.min(:a).should == 'SELECT min(a) FROM test LIMIT 1'
|
2135
|
+
end
|
2136
|
+
|
2137
|
+
specify "should include max" do
|
2138
|
+
@d.max(:b).should == 'SELECT max(b) FROM test LIMIT 1'
|
2139
|
+
end
|
2140
|
+
|
2141
|
+
specify "should include sum" do
|
2142
|
+
@d.sum(:c).should == 'SELECT sum(c) FROM test LIMIT 1'
|
2143
|
+
end
|
2144
|
+
|
2145
|
+
specify "should include avg" do
|
2146
|
+
@d.avg(:d).should == 'SELECT avg(d) FROM test LIMIT 1'
|
2147
|
+
end
|
2148
|
+
|
2149
|
+
specify "should accept qualified columns" do
|
2150
|
+
@d.avg(:test__bc).should == 'SELECT avg(test.bc) FROM test LIMIT 1'
|
2151
|
+
end
|
2152
|
+
|
2153
|
+
specify "should use a subselect for the same conditions as count" do
|
2154
|
+
d = @d.order(:a).limit(5)
|
2155
|
+
d.avg(:a).should == 'SELECT avg(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
|
2156
|
+
d.sum(:a).should == 'SELECT sum(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
|
2157
|
+
d.min(:a).should == 'SELECT min(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
|
2158
|
+
d.max(:a).should == 'SELECT max(a) FROM (SELECT * FROM test ORDER BY a LIMIT 5) AS t1 LIMIT 1'
|
2159
|
+
end
|
2160
|
+
end
|
2161
|
+
|
2162
|
+
context "Dataset#range" do
|
2163
|
+
before do
|
2164
|
+
c = Class.new(Sequel::Dataset) do
|
2165
|
+
@@sql = nil
|
2166
|
+
|
2167
|
+
def last_sql; @@sql; end
|
2168
|
+
|
2169
|
+
def fetch_rows(sql)
|
2170
|
+
@@sql = sql
|
2171
|
+
yield(:v1 => 1, :v2 => 10)
|
2172
|
+
end
|
2173
|
+
end
|
2174
|
+
@d = c.new(nil).from(:test)
|
2175
|
+
end
|
2176
|
+
|
2177
|
+
specify "should generate a correct SQL statement" do
|
2178
|
+
@d.range(:stamp)
|
2179
|
+
@d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test LIMIT 1"
|
2180
|
+
|
2181
|
+
@d.filter(:price.sql_number > 100).range(:stamp)
|
2182
|
+
@d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test WHERE (price > 100) LIMIT 1"
|
2183
|
+
end
|
2184
|
+
|
2185
|
+
specify "should return a range object" do
|
2186
|
+
@d.range(:tryme).should == (1..10)
|
2187
|
+
end
|
2188
|
+
|
2189
|
+
specify "should use a subselect for the same conditions as count" do
|
2190
|
+
@d.order(:stamp).limit(5).range(:stamp).should == (1..10)
|
2191
|
+
@d.last_sql.should == 'SELECT min(stamp) AS v1, max(stamp) AS v2 FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1'
|
2192
|
+
end
|
2193
|
+
end
|
2194
|
+
|
2195
|
+
context "Dataset#interval" do
|
2196
|
+
before do
|
2197
|
+
c = Class.new(Sequel::Dataset) do
|
2198
|
+
@@sql = nil
|
2199
|
+
|
2200
|
+
def last_sql; @@sql; end
|
2201
|
+
|
2202
|
+
def fetch_rows(sql)
|
2203
|
+
@@sql = sql
|
2204
|
+
yield(:v => 1234)
|
2205
|
+
end
|
2206
|
+
end
|
2207
|
+
@d = c.new(nil).from(:test)
|
2208
|
+
end
|
2209
|
+
|
2210
|
+
specify "should generate a correct SQL statement" do
|
2211
|
+
@d.interval(:stamp)
|
2212
|
+
@d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test LIMIT 1"
|
2213
|
+
|
2214
|
+
@d.filter(:price.sql_number > 100).interval(:stamp)
|
2215
|
+
@d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test WHERE (price > 100) LIMIT 1"
|
2216
|
+
end
|
2217
|
+
|
2218
|
+
specify "should return an integer" do
|
2219
|
+
@d.interval(:tryme).should == 1234
|
2220
|
+
end
|
2221
|
+
|
2222
|
+
specify "should use a subselect for the same conditions as count" do
|
2223
|
+
@d.order(:stamp).limit(5).interval(:stamp).should == 1234
|
2224
|
+
@d.last_sql.should == 'SELECT (max(stamp) - min(stamp)) FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1'
|
2225
|
+
end
|
2226
|
+
end
|
2227
|
+
|
2228
|
+
context "Dataset #first and #last" do
|
2229
|
+
before do
|
2230
|
+
@c = Class.new(Sequel::Dataset) do
|
2231
|
+
def each(&block)
|
2232
|
+
s = select_sql
|
2233
|
+
x = [:a,1,:b,2,s]
|
2234
|
+
i = /LIMIT (\d+)/.match(s)[1].to_i.times{yield x}
|
2235
|
+
end
|
2236
|
+
end
|
2237
|
+
@d = @c.new(nil).from(:test)
|
2238
|
+
end
|
2239
|
+
|
2240
|
+
specify "should return a single record if no argument is given" do
|
2241
|
+
@d.order(:a).first.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a LIMIT 1']
|
2242
|
+
@d.order(:a).last.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a DESC LIMIT 1']
|
2243
|
+
end
|
2244
|
+
|
2245
|
+
specify "should return the first/last matching record if argument is not an Integer" do
|
2246
|
+
@d.order(:a).first(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1']
|
2247
|
+
@d.order(:a).first('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1']
|
2248
|
+
@d.order(:a).last(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a DESC LIMIT 1']
|
2249
|
+
@d.order(:a).last('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a DESC LIMIT 1']
|
2250
|
+
end
|
2251
|
+
|
2252
|
+
specify "should set the limit and return an array of records if the given number is > 1" do
|
2253
|
+
i = rand(10) + 10
|
2254
|
+
r = @d.order(:a).first(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a LIMIT #{i}"]] * i
|
2255
|
+
i = rand(10) + 10
|
2256
|
+
r = @d.order(:a).last(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a DESC LIMIT #{i}"]] * i
|
2257
|
+
end
|
2258
|
+
|
2259
|
+
specify "should return the first matching record if a block is given without an argument" do
|
2260
|
+
@d.first{:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z > 26) LIMIT 1']
|
2261
|
+
@d.order(:name).last{:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z > 26) ORDER BY name DESC LIMIT 1']
|
2262
|
+
end
|
2263
|
+
|
2264
|
+
specify "should combine block and standard argument filters if argument is not an Integer" do
|
2265
|
+
@d.first(:y=>25){:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE ((z > 26) AND (y = 25)) LIMIT 1']
|
2266
|
+
@d.order(:name).last('y = ?', 16){:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE ((z > 26) AND (y = 16)) ORDER BY name DESC LIMIT 1']
|
2267
|
+
end
|
2268
|
+
|
2269
|
+
specify "should filter and return an array of records if an Integer argument is provided and a block is given" do
|
2270
|
+
i = rand(10) + 10
|
2271
|
+
r = @d.order(:a).first(i){:z.sql_number > 26}.should == [[:a,1,:b,2, "SELECT * FROM test WHERE (z > 26) ORDER BY a LIMIT #{i}"]] * i
|
2272
|
+
i = rand(10) + 10
|
2273
|
+
r = @d.order(:a).last(i){:z.sql_number > 26}.should == [[:a,1,:b,2, "SELECT * FROM test WHERE (z > 26) ORDER BY a DESC LIMIT #{i}"]] * i
|
2274
|
+
end
|
2275
|
+
|
2276
|
+
specify "#last should raise if no order is given" do
|
2277
|
+
proc {@d.last}.should raise_error(Sequel::Error)
|
2278
|
+
proc {@d.last(2)}.should raise_error(Sequel::Error)
|
2279
|
+
proc {@d.order(:a).last}.should_not raise_error
|
2280
|
+
proc {@d.order(:a).last(2)}.should_not raise_error
|
2281
|
+
end
|
2282
|
+
|
2283
|
+
specify "#last should invert the order" do
|
2284
|
+
@d.order(:a).last.pop.should == 'SELECT * FROM test ORDER BY a DESC LIMIT 1'
|
2285
|
+
@d.order(:b.desc).last.pop.should == 'SELECT * FROM test ORDER BY b ASC LIMIT 1'
|
2286
|
+
@d.order(:c, :d).last.pop.should == 'SELECT * FROM test ORDER BY c DESC, d DESC LIMIT 1'
|
2287
|
+
@d.order(:e.desc, :f).last.pop.should == 'SELECT * FROM test ORDER BY e ASC, f DESC LIMIT 1'
|
2288
|
+
end
|
2289
|
+
end
|
2290
|
+
|
2291
|
+
context "Dataset compound operations" do
|
2292
|
+
before do
|
2293
|
+
@a = Sequel::Dataset.new(nil).from(:a).filter(:z => 1)
|
2294
|
+
@b = Sequel::Dataset.new(nil).from(:b).filter(:z => 2)
|
2295
|
+
end
|
2296
|
+
|
2297
|
+
specify "should support UNION and UNION ALL" do
|
2298
|
+
@a.union(@b).sql.should == \
|
2299
|
+
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1"
|
2300
|
+
@b.union(@a, true).sql.should == \
|
2301
|
+
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
2302
|
+
@b.union(@a, :all=>true).sql.should == \
|
2303
|
+
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
2304
|
+
end
|
2305
|
+
|
2306
|
+
specify "should support INTERSECT and INTERSECT ALL" do
|
2307
|
+
@a.intersect(@b).sql.should == \
|
2308
|
+
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS t1"
|
2309
|
+
@b.intersect(@a, true).sql.should == \
|
2310
|
+
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
2311
|
+
@b.intersect(@a, :all=>true).sql.should == \
|
2312
|
+
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
2313
|
+
end
|
2314
|
+
|
2315
|
+
specify "should support EXCEPT and EXCEPT ALL" do
|
2316
|
+
@a.except(@b).sql.should == \
|
2317
|
+
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1"
|
2318
|
+
@b.except(@a, true).sql.should == \
|
2319
|
+
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
2320
|
+
@b.except(@a, :all=>true).sql.should == \
|
2321
|
+
"SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
|
2322
|
+
end
|
2323
|
+
|
2324
|
+
specify "should support :alias option for specifying identifier" do
|
2325
|
+
@a.union(@b, :alias=>:xx).sql.should == \
|
2326
|
+
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS xx"
|
2327
|
+
@a.intersect(@b, :alias=>:xx).sql.should == \
|
2328
|
+
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS xx"
|
2329
|
+
@a.except(@b, :alias=>:xx).sql.should == \
|
2330
|
+
"SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS xx"
|
2331
|
+
end
|
2332
|
+
|
2333
|
+
specify "should support :from_self=>false option to not wrap the compound in a SELECT * FROM (...)" do
|
2334
|
+
@b.union(@a, :from_self=>false).sql.should == \
|
2335
|
+
"SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1)"
|
2336
|
+
@b.intersect(@a, :from_self=>false).sql.should == \
|
2337
|
+
"SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
|
2338
|
+
@b.except(@a, :from_self=>false).sql.should == \
|
2339
|
+
"SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
|
2340
|
+
|
2341
|
+
@b.union(@a, :from_self=>false, :all=>true).sql.should == \
|
2342
|
+
"SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
|
2343
|
+
@b.intersect(@a, :from_self=>false, :all=>true).sql.should == \
|
2344
|
+
"SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
|
2345
|
+
@b.except(@a, :from_self=>false, :all=>true).sql.should == \
|
2346
|
+
"SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
|
2347
|
+
end
|
2348
|
+
|
2349
|
+
specify "should raise an InvalidOperation if INTERSECT or EXCEPT is used and they are not supported" do
|
2350
|
+
@a.meta_def(:supports_intersect_except?){false}
|
2351
|
+
proc{@a.intersect(@b)}.should raise_error(Sequel::InvalidOperation)
|
2352
|
+
proc{@a.intersect(@b, true)}.should raise_error(Sequel::InvalidOperation)
|
2353
|
+
proc{@a.except(@b)}.should raise_error(Sequel::InvalidOperation)
|
2354
|
+
proc{@a.except(@b, true)}.should raise_error(Sequel::InvalidOperation)
|
2355
|
+
end
|
2356
|
+
|
2357
|
+
specify "should raise an InvalidOperation if INTERSECT ALL or EXCEPT ALL is used and they are not supported" do
|
2358
|
+
@a.meta_def(:supports_intersect_except_all?){false}
|
2359
|
+
proc{@a.intersect(@b)}.should_not raise_error
|
2360
|
+
proc{@a.intersect(@b, true)}.should raise_error(Sequel::InvalidOperation)
|
2361
|
+
proc{@a.except(@b)}.should_not raise_error
|
2362
|
+
proc{@a.except(@b, true)}.should raise_error(Sequel::InvalidOperation)
|
2363
|
+
end
|
2364
|
+
|
2365
|
+
specify "should handle chained compound operations" do
|
2366
|
+
@a.union(@b).union(@a, true).sql.should == \
|
2367
|
+
"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"
|
2368
|
+
@a.intersect(@b, true).intersect(@a).sql.should == \
|
2369
|
+
"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"
|
2370
|
+
@a.except(@b).except(@a, true).sql.should == \
|
2371
|
+
"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"
|
2372
|
+
end
|
2373
|
+
|
2374
|
+
specify "should use a subselect when using a compound operation with a dataset that already has a compound operation" do
|
2375
|
+
@a.union(@b.union(@a, true)).sql.should == \
|
2376
|
+
"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"
|
2377
|
+
@a.intersect(@b.intersect(@a), true).sql.should == \
|
2378
|
+
"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"
|
2379
|
+
@a.except(@b.except(@a, true)).sql.should == \
|
2380
|
+
"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"
|
2381
|
+
end
|
2382
|
+
|
2383
|
+
specify "should order and limit properly when using UNION, INTERSECT, or EXCEPT" do
|
2384
|
+
@dataset = Sequel::Dataset.new(nil).from(:test)
|
2385
|
+
@dataset.union(@dataset).limit(2).sql.should ==
|
2386
|
+
"SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 2"
|
2387
|
+
@dataset.limit(2).intersect(@dataset).sql.should ==
|
2388
|
+
"SELECT * FROM (SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1 INTERSECT SELECT * FROM test) AS t1"
|
2389
|
+
@dataset.except(@dataset.limit(2)).sql.should ==
|
2390
|
+
"SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1) AS t1"
|
2391
|
+
|
2392
|
+
@dataset.union(@dataset).order(:num).sql.should ==
|
2393
|
+
"SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 ORDER BY num"
|
2394
|
+
@dataset.order(:num).intersect(@dataset).sql.should ==
|
2395
|
+
"SELECT * FROM (SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1 INTERSECT SELECT * FROM test) AS t1"
|
2396
|
+
@dataset.except(@dataset.order(:num)).sql.should ==
|
2397
|
+
"SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1) AS t1"
|
2398
|
+
|
2399
|
+
@dataset.limit(2).order(:a).union(@dataset.limit(3).order(:b)).order(:c).limit(4).sql.should ==
|
2400
|
+
"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"
|
2401
|
+
end
|
2402
|
+
|
2403
|
+
end
|
2404
|
+
|
2405
|
+
context "Dataset#[]" do
|
2406
|
+
before do
|
2407
|
+
@c = Class.new(Sequel::Dataset) do
|
2408
|
+
@@last_dataset = nil
|
2409
|
+
|
2410
|
+
def self.last_dataset
|
2411
|
+
@@last_dataset
|
2412
|
+
end
|
2413
|
+
|
2414
|
+
def single_record
|
2415
|
+
@@last_dataset = opts ? clone(opts) : self
|
2416
|
+
{1 => 2, 3 => 4}
|
2417
|
+
end
|
2418
|
+
end
|
2419
|
+
@d = @c.new(nil).from(:test)
|
2420
|
+
end
|
2421
|
+
|
2422
|
+
specify "should return a single record filtered according to the given conditions" do
|
2423
|
+
@d[:name => 'didi'].should == {1 => 2, 3 => 4}
|
2424
|
+
@c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "(name = 'didi')"
|
2425
|
+
|
2426
|
+
@d[:id => 5..45].should == {1 => 2, 3 => 4}
|
2427
|
+
@c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "((id >= 5) AND (id <= 45))"
|
2428
|
+
end
|
2429
|
+
end
|
2430
|
+
|
2431
|
+
context "Dataset#single_record" do
|
2432
|
+
before do
|
2433
|
+
@c = Class.new(Sequel::Dataset) do
|
2434
|
+
def fetch_rows(sql)
|
2435
|
+
yield sql
|
2436
|
+
end
|
2437
|
+
end
|
2438
|
+
@cc = Class.new(@c) do
|
2439
|
+
def fetch_rows(sql); end
|
2440
|
+
end
|
2441
|
+
|
2442
|
+
@d = @c.new(nil).from(:test)
|
2443
|
+
@e = @cc.new(nil).from(:test)
|
2444
|
+
end
|
2445
|
+
|
2446
|
+
specify "should call each with a limit of 1 and return the record" do
|
2447
|
+
@d.single_record.should == 'SELECT * FROM test LIMIT 1'
|
2448
|
+
end
|
2449
|
+
|
2450
|
+
specify "should return nil if no record is present" do
|
2451
|
+
@e.single_record.should be_nil
|
2452
|
+
end
|
2453
|
+
end
|
2454
|
+
|
2455
|
+
context "Dataset#single_value" do
|
2456
|
+
before do
|
2457
|
+
@c = Class.new(Sequel::Dataset) do
|
2458
|
+
def fetch_rows(sql)
|
2459
|
+
yield({1 => sql})
|
2460
|
+
end
|
2461
|
+
end
|
2462
|
+
@cc = Class.new(@c) do
|
2463
|
+
def fetch_rows(sql); end
|
2464
|
+
end
|
2465
|
+
|
2466
|
+
@d = @c.new(nil).from(:test)
|
2467
|
+
@e = @cc.new(nil).from(:test)
|
2468
|
+
end
|
2469
|
+
|
2470
|
+
specify "should call each and return the first value of the first record" do
|
2471
|
+
@d.single_value.should == 'SELECT * FROM test LIMIT 1'
|
2472
|
+
end
|
2473
|
+
|
2474
|
+
specify "should return nil if no records" do
|
2475
|
+
@e.single_value.should be_nil
|
2476
|
+
end
|
2477
|
+
|
2478
|
+
it "should work on a graphed_dataset" do
|
2479
|
+
@d.should_receive(:columns).twice.and_return([:a])
|
2480
|
+
@d.graph(@d, [:a], :table_alias=>:test2).single_value.should == 'SELECT test.a, test2.a AS test2_a FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1'
|
2481
|
+
end
|
2482
|
+
end
|
2483
|
+
|
2484
|
+
context "Dataset#get" do
|
2485
|
+
before do
|
2486
|
+
@c = Class.new(Sequel::Dataset) do
|
2487
|
+
attr_reader :last_sql
|
2488
|
+
|
2489
|
+
def fetch_rows(sql)
|
2490
|
+
@last_sql = sql
|
2491
|
+
yield(:name => sql)
|
2492
|
+
end
|
2493
|
+
end
|
2494
|
+
|
2495
|
+
@d = @c.new(nil).from(:test)
|
2496
|
+
end
|
2497
|
+
|
2498
|
+
specify "should select the specified column and fetch its value" do
|
2499
|
+
@d.get(:name).should == "SELECT name FROM test LIMIT 1"
|
2500
|
+
@d.get(:abc).should == "SELECT abc FROM test LIMIT 1" # the first available value is returned always
|
2501
|
+
end
|
2502
|
+
|
2503
|
+
specify "should work with filters" do
|
2504
|
+
@d.filter(:id => 1).get(:name).should == "SELECT name FROM test WHERE (id = 1) LIMIT 1"
|
2505
|
+
end
|
2506
|
+
|
2507
|
+
specify "should work with aliased fields" do
|
2508
|
+
@d.get(:x__b.as(:name)).should == "SELECT x.b AS name FROM test LIMIT 1"
|
2509
|
+
end
|
2510
|
+
|
2511
|
+
specify "should accept a block that yields a virtual row" do
|
2512
|
+
@d.get{|o| o.x__b.as(:name)}.should == "SELECT x.b AS name FROM test LIMIT 1"
|
2513
|
+
@d.get{x(1).as(:name)}.should == "SELECT x(1) AS name FROM test LIMIT 1"
|
2514
|
+
end
|
2515
|
+
|
2516
|
+
specify "should raise an error if both a regular argument and block argument are used" do
|
2517
|
+
proc{@d.get(:name){|o| o.x__b.as(:name)}}.should raise_error(Sequel::Error)
|
2518
|
+
end
|
2519
|
+
end
|
2520
|
+
|
2521
|
+
context "Dataset#set_row_proc" do
|
2522
|
+
before do
|
2523
|
+
@c = Class.new(Sequel::Dataset) do
|
2524
|
+
def fetch_rows(sql, &block)
|
2525
|
+
# yield a hash with kind as the 1 bit of a number
|
2526
|
+
(1..10).each {|i| block.call({:kind => i[0]})}
|
2527
|
+
end
|
2528
|
+
end
|
2529
|
+
@dataset = @c.new(nil).from(:items)
|
2530
|
+
end
|
2531
|
+
|
2532
|
+
specify "should cause dataset to pass all rows through the filter" do
|
2533
|
+
@dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
|
2534
|
+
|
2535
|
+
rows = @dataset.all
|
2536
|
+
rows.size.should == 10
|
2537
|
+
|
2538
|
+
rows.each {|r| r[:der].should == (r[:kind] + 2)}
|
2539
|
+
end
|
2540
|
+
|
2541
|
+
specify "should be copied over when dataset is cloned" do
|
2542
|
+
@dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
|
2543
|
+
|
2544
|
+
@dataset.filter(:a => 1).first.should == {:kind => 1, :der => 3}
|
2545
|
+
end
|
2546
|
+
end
|
2547
|
+
|
2548
|
+
context "Dataset#<<" do
|
2549
|
+
before do
|
2550
|
+
@d = Sequel::Dataset.new(nil)
|
2551
|
+
@d.meta_def(:insert) do |*args|
|
2552
|
+
1234567890
|
2553
|
+
end
|
2554
|
+
end
|
2555
|
+
|
2556
|
+
specify "should call #insert" do
|
2557
|
+
(@d << {:name => 1}).should == 1234567890
|
2558
|
+
end
|
2559
|
+
end
|
2560
|
+
|
2561
|
+
context "Dataset#columns" do
|
2562
|
+
before do
|
2563
|
+
@dataset = DummyDataset.new(nil).from(:items)
|
2564
|
+
@dataset.meta_def(:columns=) {|c| @columns = c}
|
2565
|
+
i = 'a'
|
2566
|
+
@dataset.meta_def(:each){@columns = select_sql + i; i = i.next}
|
2567
|
+
end
|
2568
|
+
|
2569
|
+
specify "should return the value of @columns if @columns is not nil" do
|
2570
|
+
@dataset.columns = [:a, :b, :c]
|
2571
|
+
@dataset.columns.should == [:a, :b, :c]
|
2572
|
+
end
|
2573
|
+
|
2574
|
+
specify "should attempt to get a single record and return @columns if @columns is nil" do
|
2575
|
+
@dataset.columns = nil
|
2576
|
+
@dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
|
2577
|
+
@dataset.opts[:from] = [:nana]
|
2578
|
+
@dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
|
2579
|
+
end
|
2580
|
+
|
2581
|
+
specify "should ignore any filters, orders, or DISTINCT clauses" do
|
2582
|
+
@dataset.filter!(:b=>100).order!(:b).distinct!
|
2583
|
+
@dataset.columns = nil
|
2584
|
+
@dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
|
2585
|
+
end
|
2586
|
+
end
|
2587
|
+
|
2588
|
+
context "Dataset#columns!" do
|
2589
|
+
before do
|
2590
|
+
@dataset = DummyDataset.new(nil).from(:items)
|
2591
|
+
i = 'a'
|
2592
|
+
@dataset.meta_def(:each){@columns = select_sql + i; i = i.next}
|
2593
|
+
end
|
2594
|
+
|
2595
|
+
specify "should always attempt to get a record and return @columns" do
|
2596
|
+
@dataset.columns!.should == 'SELECT * FROM items LIMIT 1a'
|
2597
|
+
@dataset.columns!.should == 'SELECT * FROM items LIMIT 1b'
|
2598
|
+
@dataset.opts[:from] = [:nana]
|
2599
|
+
@dataset.columns!.should == 'SELECT * FROM nana LIMIT 1c'
|
2600
|
+
end
|
2601
|
+
end
|
2602
|
+
|
2603
|
+
context "Dataset#import" do
|
2604
|
+
before do
|
2605
|
+
@dbc = Class.new(Sequel::Database) do
|
2606
|
+
attr_reader :sqls
|
2607
|
+
|
2608
|
+
def execute(sql, opts={})
|
2609
|
+
@sqls ||= []
|
2610
|
+
@sqls << sql
|
2611
|
+
end
|
2612
|
+
alias execute_dui execute
|
2613
|
+
|
2614
|
+
def transaction(opts={})
|
2615
|
+
@sqls ||= []
|
2616
|
+
@sqls << 'BEGIN'
|
2617
|
+
yield
|
2618
|
+
@sqls << 'COMMIT'
|
2619
|
+
end
|
2620
|
+
end
|
2621
|
+
@db = @dbc.new
|
2622
|
+
|
2623
|
+
@ds = Sequel::Dataset.new(@db).from(:items)
|
2624
|
+
|
2625
|
+
@list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
|
2626
|
+
end
|
2627
|
+
|
2628
|
+
specify "should accept string keys as column names" do
|
2629
|
+
@ds.import(['x', 'y'], [[1, 2], [3, 4]])
|
2630
|
+
@db.sqls.should == [
|
2631
|
+
'BEGIN',
|
2632
|
+
"INSERT INTO items (x, y) VALUES (1, 2)",
|
2633
|
+
"INSERT INTO items (x, y) VALUES (3, 4)",
|
2634
|
+
'COMMIT'
|
2635
|
+
]
|
2636
|
+
end
|
2637
|
+
|
2638
|
+
specify "should accept a columns array and a values array" do
|
2639
|
+
@ds.import([:x, :y], [[1, 2], [3, 4]])
|
2640
|
+
@db.sqls.should == [
|
2641
|
+
'BEGIN',
|
2642
|
+
"INSERT INTO items (x, y) VALUES (1, 2)",
|
2643
|
+
"INSERT INTO items (x, y) VALUES (3, 4)",
|
2644
|
+
'COMMIT'
|
2645
|
+
]
|
2646
|
+
end
|
2647
|
+
|
2648
|
+
specify "should accept a columns array and a dataset" do
|
2649
|
+
@ds2 = Sequel::Dataset.new(@db).from(:cats).filter(:purr => true).select(:a, :b)
|
2650
|
+
|
2651
|
+
@ds.import([:x, :y], @ds2)
|
2652
|
+
@db.sqls.should == [
|
2653
|
+
'BEGIN',
|
2654
|
+
"INSERT INTO items (x, y) SELECT a, b FROM cats WHERE (purr IS TRUE)",
|
2655
|
+
'COMMIT'
|
2656
|
+
]
|
2657
|
+
end
|
2658
|
+
|
2659
|
+
specify "should accept a columns array and a values array with :commit_every option" do
|
2660
|
+
@ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :commit_every => 3)
|
2661
|
+
@db.sqls.should == [
|
2662
|
+
'BEGIN',
|
2663
|
+
"INSERT INTO items (x, y) VALUES (1, 2)",
|
2664
|
+
"INSERT INTO items (x, y) VALUES (3, 4)",
|
2665
|
+
"INSERT INTO items (x, y) VALUES (5, 6)",
|
2666
|
+
'COMMIT',
|
2667
|
+
]
|
2668
|
+
end
|
2669
|
+
specify "should accept a columns array and a values array with slice option" do
|
2670
|
+
@ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
|
2671
|
+
@db.sqls.should == [
|
2672
|
+
'BEGIN',
|
2673
|
+
"INSERT INTO items (x, y) VALUES (1, 2)",
|
2674
|
+
"INSERT INTO items (x, y) VALUES (3, 4)",
|
2675
|
+
'COMMIT',
|
2676
|
+
'BEGIN',
|
2677
|
+
"INSERT INTO items (x, y) VALUES (5, 6)",
|
2678
|
+
'COMMIT'
|
2679
|
+
]
|
2680
|
+
end
|
2681
|
+
end
|
2682
|
+
|
2683
|
+
context "Dataset#multi_insert" do
|
2684
|
+
before do
|
2685
|
+
@dbc = Class.new do
|
2686
|
+
attr_reader :sqls
|
2687
|
+
|
2688
|
+
def execute(sql, opts={})
|
2689
|
+
@sqls ||= []
|
2690
|
+
@sqls << sql
|
2691
|
+
end
|
2692
|
+
alias execute_dui execute
|
2693
|
+
|
2694
|
+
def transaction(opts={})
|
2695
|
+
@sqls ||= []
|
2696
|
+
@sqls << 'BEGIN'
|
2697
|
+
yield
|
2698
|
+
@sqls << 'COMMIT'
|
2699
|
+
end
|
2700
|
+
end
|
2701
|
+
@db = @dbc.new
|
2702
|
+
|
2703
|
+
@ds = Sequel::Dataset.new(@db).from(:items)
|
2704
|
+
|
2705
|
+
@list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
|
2706
|
+
end
|
2707
|
+
|
2708
|
+
specify "should issue multiple insert statements inside a transaction" do
|
2709
|
+
@ds.multi_insert(@list)
|
2710
|
+
@db.sqls.should == [
|
2711
|
+
'BEGIN',
|
2712
|
+
"INSERT INTO items (name) VALUES ('abc')",
|
2713
|
+
"INSERT INTO items (name) VALUES ('def')",
|
2714
|
+
"INSERT INTO items (name) VALUES ('ghi')",
|
2715
|
+
'COMMIT'
|
2716
|
+
]
|
2717
|
+
end
|
2718
|
+
|
2719
|
+
specify "should handle different formats for tables" do
|
2720
|
+
@ds = @ds.from(:sch__tab)
|
2721
|
+
@ds.multi_insert(@list)
|
2722
|
+
@db.sqls.should == [
|
2723
|
+
'BEGIN',
|
2724
|
+
"INSERT INTO sch.tab (name) VALUES ('abc')",
|
2725
|
+
"INSERT INTO sch.tab (name) VALUES ('def')",
|
2726
|
+
"INSERT INTO sch.tab (name) VALUES ('ghi')",
|
2727
|
+
'COMMIT'
|
2728
|
+
]
|
2729
|
+
@db.sqls.clear
|
2730
|
+
|
2731
|
+
@ds = @ds.from(:tab.qualify(:sch))
|
2732
|
+
@ds.multi_insert(@list)
|
2733
|
+
@db.sqls.should == [
|
2734
|
+
'BEGIN',
|
2735
|
+
"INSERT INTO sch.tab (name) VALUES ('abc')",
|
2736
|
+
"INSERT INTO sch.tab (name) VALUES ('def')",
|
2737
|
+
"INSERT INTO sch.tab (name) VALUES ('ghi')",
|
2738
|
+
'COMMIT'
|
2739
|
+
]
|
2740
|
+
@db.sqls.clear
|
2741
|
+
@ds = @ds.from(:sch__tab.identifier)
|
2742
|
+
@ds.multi_insert(@list)
|
2743
|
+
@db.sqls.should == [
|
2744
|
+
'BEGIN',
|
2745
|
+
"INSERT INTO sch__tab (name) VALUES ('abc')",
|
2746
|
+
"INSERT INTO sch__tab (name) VALUES ('def')",
|
2747
|
+
"INSERT INTO sch__tab (name) VALUES ('ghi')",
|
2748
|
+
'COMMIT'
|
2749
|
+
]
|
2750
|
+
end
|
2751
|
+
|
2752
|
+
specify "should accept the :commit_every option for committing every x records" do
|
2753
|
+
@ds.multi_insert(@list, :commit_every => 1)
|
2754
|
+
@db.sqls.should == [
|
2755
|
+
'BEGIN',
|
2756
|
+
"INSERT INTO items (name) VALUES ('abc')",
|
2757
|
+
'COMMIT',
|
2758
|
+
'BEGIN',
|
2759
|
+
"INSERT INTO items (name) VALUES ('def')",
|
2760
|
+
'COMMIT',
|
2761
|
+
'BEGIN',
|
2762
|
+
"INSERT INTO items (name) VALUES ('ghi')",
|
2763
|
+
'COMMIT'
|
2764
|
+
]
|
2765
|
+
end
|
2766
|
+
|
2767
|
+
specify "should accept the :slice option for committing every x records" do
|
2768
|
+
@ds.multi_insert(@list, :slice => 2)
|
2769
|
+
@db.sqls.should == [
|
2770
|
+
'BEGIN',
|
2771
|
+
"INSERT INTO items (name) VALUES ('abc')",
|
2772
|
+
"INSERT INTO items (name) VALUES ('def')",
|
2773
|
+
'COMMIT',
|
2774
|
+
'BEGIN',
|
2775
|
+
"INSERT INTO items (name) VALUES ('ghi')",
|
2776
|
+
'COMMIT'
|
2777
|
+
]
|
2778
|
+
end
|
2779
|
+
|
2780
|
+
specify "should accept string keys as column names" do
|
2781
|
+
@ds.multi_insert([{'x'=>1, 'y'=>2}, {'x'=>3, 'y'=>4}])
|
2782
|
+
@db.sqls.should == [
|
2783
|
+
'BEGIN',
|
2784
|
+
"INSERT INTO items (x, y) VALUES (1, 2)",
|
2785
|
+
"INSERT INTO items (x, y) VALUES (3, 4)",
|
2786
|
+
'COMMIT'
|
2787
|
+
]
|
2788
|
+
end
|
2789
|
+
|
2790
|
+
specify "should not do anything if no hashes are provided" do
|
2791
|
+
@ds.multi_insert([])
|
2792
|
+
@db.sqls.should be_nil
|
2793
|
+
end
|
2794
|
+
end
|
2795
|
+
|
2796
|
+
context "Dataset" do
|
2797
|
+
before do
|
2798
|
+
@d = Sequel::Dataset.new(nil).from(:x)
|
2799
|
+
end
|
2800
|
+
|
2801
|
+
specify "should support self-changing select!" do
|
2802
|
+
@d.select!(:y)
|
2803
|
+
@d.sql.should == "SELECT y FROM x"
|
2804
|
+
end
|
2805
|
+
|
2806
|
+
specify "should support self-changing from!" do
|
2807
|
+
@d.from!(:y)
|
2808
|
+
@d.sql.should == "SELECT * FROM y"
|
2809
|
+
end
|
2810
|
+
|
2811
|
+
specify "should support self-changing order!" do
|
2812
|
+
@d.order!(:y)
|
2813
|
+
@d.sql.should == "SELECT * FROM x ORDER BY y"
|
2814
|
+
end
|
2815
|
+
|
2816
|
+
specify "should support self-changing filter!" do
|
2817
|
+
@d.filter!(:y => 1)
|
2818
|
+
@d.sql.should == "SELECT * FROM x WHERE (y = 1)"
|
2819
|
+
end
|
2820
|
+
|
2821
|
+
specify "should support self-changing filter! with block" do
|
2822
|
+
@d.filter!{:y.sql_number < 2}
|
2823
|
+
@d.sql.should == "SELECT * FROM x WHERE (y < 2)"
|
2824
|
+
end
|
2825
|
+
|
2826
|
+
specify "should raise for ! methods that don't return a dataset" do
|
2827
|
+
proc {@d.opts!}.should raise_error(NameError)
|
2828
|
+
end
|
2829
|
+
|
2830
|
+
specify "should raise for missing methods" do
|
2831
|
+
proc {@d.xuyz}.should raise_error(NameError)
|
2832
|
+
proc {@d.xyz!}.should raise_error(NameError)
|
2833
|
+
proc {@d.xyz?}.should raise_error(NameError)
|
2834
|
+
end
|
2835
|
+
|
2836
|
+
specify "should support chaining of bang methods" do
|
2837
|
+
@d.order!(:y)
|
2838
|
+
@d.filter!(:y => 1)
|
2839
|
+
@d.sql.should == "SELECT * FROM x WHERE (y = 1) ORDER BY y"
|
2840
|
+
end
|
2841
|
+
end
|
2842
|
+
|
2843
|
+
context "Dataset#to_csv" do
|
2844
|
+
before do
|
2845
|
+
@c = Class.new(Sequel::Dataset) do
|
2846
|
+
attr_accessor :data
|
2847
|
+
attr_accessor :columns
|
2848
|
+
|
2849
|
+
def fetch_rows(sql, &block)
|
2850
|
+
@data.each(&block)
|
2851
|
+
end
|
2852
|
+
|
2853
|
+
# naked should return self here because to_csv wants a naked result set.
|
2854
|
+
def naked
|
2855
|
+
self
|
2856
|
+
end
|
2857
|
+
end
|
2858
|
+
|
2859
|
+
@ds = @c.new(nil).from(:items)
|
2860
|
+
@ds.columns = [:a, :b, :c]
|
2861
|
+
@ds.data = [ {:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}, {:a=>7, :b=>8, :c=>9} ]
|
2862
|
+
end
|
2863
|
+
|
2864
|
+
specify "should format a CSV representation of the records" do
|
2865
|
+
@ds.to_csv.should ==
|
2866
|
+
"a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
|
2867
|
+
end
|
2868
|
+
|
2869
|
+
specify "should exclude column titles if so specified" do
|
2870
|
+
@ds.to_csv(false).should ==
|
2871
|
+
"1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
|
2872
|
+
end
|
2873
|
+
end
|
2874
|
+
|
2875
|
+
context "Dataset#update_sql" do
|
2876
|
+
before do
|
2877
|
+
@ds = Sequel::Dataset.new(nil).from(:items)
|
2878
|
+
end
|
2879
|
+
|
2880
|
+
specify "should accept strings" do
|
2881
|
+
@ds.update_sql("a = b").should == "UPDATE items SET a = b"
|
2882
|
+
end
|
2883
|
+
|
2884
|
+
specify "should handle implicitly qualified symbols" do
|
2885
|
+
@ds.update_sql(:items__a=>:b).should == "UPDATE items SET items.a = b"
|
2886
|
+
end
|
2887
|
+
|
2888
|
+
specify "should accept hash with string keys" do
|
2889
|
+
@ds.update_sql('c' => 'd').should == "UPDATE items SET c = 'd'"
|
2890
|
+
end
|
2891
|
+
|
2892
|
+
specify "should accept array subscript references" do
|
2893
|
+
@ds.update_sql((:day.sql_subscript(1)) => 'd').should == "UPDATE items SET day[1] = 'd'"
|
2894
|
+
end
|
2895
|
+
end
|
2896
|
+
|
2897
|
+
context "Dataset#insert_sql" do
|
2898
|
+
before do
|
2899
|
+
@ds = Sequel::Dataset.new(nil).from(:items)
|
2900
|
+
end
|
2901
|
+
|
2902
|
+
specify "should accept hash with symbol keys" do
|
2903
|
+
@ds.insert_sql(:c => 'd').should == "INSERT INTO items (c) VALUES ('d')"
|
2904
|
+
end
|
2905
|
+
|
2906
|
+
specify "should accept hash with string keys" do
|
2907
|
+
@ds.insert_sql('c' => 'd').should == "INSERT INTO items (c) VALUES ('d')"
|
2908
|
+
end
|
2909
|
+
|
2910
|
+
specify "should accept array subscript references" do
|
2911
|
+
@ds.insert_sql((:day.sql_subscript(1)) => 'd').should == "INSERT INTO items (day[1]) VALUES ('d')"
|
2912
|
+
end
|
2913
|
+
|
2914
|
+
specify "should raise an Error if the dataset has no sources" do
|
2915
|
+
proc{Sequel::Database.new.dataset.insert_sql}.should raise_error(Sequel::Error)
|
2916
|
+
end
|
2917
|
+
|
2918
|
+
specify "should accept datasets" do
|
2919
|
+
@ds.insert_sql(@ds).should == "INSERT INTO items SELECT * FROM items"
|
2920
|
+
end
|
2921
|
+
|
2922
|
+
specify "should accept datasets with columns" do
|
2923
|
+
@ds.insert_sql([:a, :b], @ds).should == "INSERT INTO items (a, b) SELECT * FROM items"
|
2924
|
+
end
|
2925
|
+
|
2926
|
+
specify "should raise if given bad values" do
|
2927
|
+
proc{@ds.clone(:values=>'a').send(:_insert_sql)}.should raise_error(Sequel::Error)
|
2928
|
+
end
|
2929
|
+
|
2930
|
+
specify "should accept separate values" do
|
2931
|
+
@ds.insert_sql(1).should == "INSERT INTO items VALUES (1)"
|
2932
|
+
@ds.insert_sql(1, 2).should == "INSERT INTO items VALUES (1, 2)"
|
2933
|
+
@ds.insert_sql(1, 2, 3).should == "INSERT INTO items VALUES (1, 2, 3)"
|
2934
|
+
end
|
2935
|
+
|
2936
|
+
specify "should accept a single array of values" do
|
2937
|
+
@ds.insert_sql([1, 2, 3]).should == "INSERT INTO items VALUES (1, 2, 3)"
|
2938
|
+
end
|
2939
|
+
|
2940
|
+
specify "should accept an array of columns and an array of values" do
|
2941
|
+
@ds.insert_sql([:a, :b, :c], [1, 2, 3]).should == "INSERT INTO items (a, b, c) VALUES (1, 2, 3)"
|
2942
|
+
end
|
2943
|
+
|
2944
|
+
specify "should raise an array if the columns and values differ in size" do
|
2945
|
+
proc{@ds.insert_sql([:a, :b], [1, 2, 3])}.should raise_error(Sequel::Error)
|
2946
|
+
end
|
2947
|
+
|
2948
|
+
specify "should accept a single LiteralString" do
|
2949
|
+
@ds.insert_sql('VALUES (1, 2, 3)'.lit).should == "INSERT INTO items VALUES (1, 2, 3)"
|
2950
|
+
end
|
2951
|
+
|
2952
|
+
specify "should accept an array of columns and an LiteralString" do
|
2953
|
+
@ds.insert_sql([:a, :b, :c], 'VALUES (1, 2, 3)'.lit).should == "INSERT INTO items (a, b, c) VALUES (1, 2, 3)"
|
2954
|
+
end
|
2955
|
+
|
2956
|
+
specify "should accept an object that responds to values and returns a hash by using that hash as the columns and values" do
|
2957
|
+
o = Object.new
|
2958
|
+
def o.values; {:c=>'d'}; end
|
2959
|
+
@ds.insert_sql(o).should == "INSERT INTO items (c) VALUES ('d')"
|
2960
|
+
end
|
2961
|
+
|
2962
|
+
specify "should accept an object that responds to values and returns something other than a hash by using the object itself as a single value" do
|
2963
|
+
o = Date.civil(2000, 1, 1)
|
2964
|
+
def o.values; self; end
|
2965
|
+
@ds.insert_sql(o).should == "INSERT INTO items VALUES ('2000-01-01')"
|
2966
|
+
end
|
2967
|
+
end
|
2968
|
+
|
2969
|
+
class DummyMummyDataset < Sequel::Dataset
|
2970
|
+
def first
|
2971
|
+
raise if @opts[:from] == [:a]
|
2972
|
+
true
|
2973
|
+
end
|
2974
|
+
end
|
2975
|
+
|
2976
|
+
class DummyMummyDatabase < Sequel::Database
|
2977
|
+
attr_reader :sqls
|
2978
|
+
|
2979
|
+
def execute(sql)
|
2980
|
+
@sqls ||= []
|
2981
|
+
@sqls << sql
|
2982
|
+
end
|
2983
|
+
|
2984
|
+
def transaction; yield; end
|
2985
|
+
|
2986
|
+
def dataset
|
2987
|
+
DummyMummyDataset.new(self)
|
2988
|
+
end
|
2989
|
+
end
|
2990
|
+
|
2991
|
+
context "Dataset#inspect" do
|
2992
|
+
before do
|
2993
|
+
@ds = Sequel::Dataset.new(nil).from(:blah)
|
2994
|
+
end
|
2995
|
+
|
2996
|
+
specify "should include the class name and the corresponding SQL statement" do
|
2997
|
+
@ds.inspect.should == '#<%s: %s>' % [@ds.class.to_s, @ds.sql.inspect]
|
2998
|
+
end
|
2999
|
+
end
|
3000
|
+
|
3001
|
+
context "Dataset#all" do
|
3002
|
+
before do
|
3003
|
+
@c = Class.new(Sequel::Dataset) do
|
3004
|
+
def fetch_rows(sql, &block)
|
3005
|
+
block.call({:x => 1, :y => 2})
|
3006
|
+
block.call({:x => 3, :y => 4})
|
3007
|
+
block.call(sql)
|
3008
|
+
end
|
3009
|
+
end
|
3010
|
+
@dataset = @c.new(nil).from(:items)
|
3011
|
+
end
|
3012
|
+
|
3013
|
+
specify "should return an array with all records" do
|
3014
|
+
@dataset.all.should == [
|
3015
|
+
{:x => 1, :y => 2},
|
3016
|
+
{:x => 3, :y => 4},
|
3017
|
+
"SELECT * FROM items"
|
3018
|
+
]
|
3019
|
+
end
|
3020
|
+
|
3021
|
+
specify "should iterate over the array if a block is given" do
|
3022
|
+
a = []
|
3023
|
+
|
3024
|
+
@dataset.all do |r|
|
3025
|
+
a << (r.is_a?(Hash) ? r[:x] : r)
|
3026
|
+
end
|
3027
|
+
|
3028
|
+
a.should == [1, 3, "SELECT * FROM items"]
|
3029
|
+
end
|
3030
|
+
end
|
3031
|
+
|
3032
|
+
context "Dataset#grep" do
|
3033
|
+
before do
|
3034
|
+
@ds = Sequel::Dataset.new(nil).from(:posts)
|
3035
|
+
end
|
3036
|
+
|
3037
|
+
specify "should format a SQL filter correctly" do
|
3038
|
+
@ds.grep(:title, 'ruby').sql.should ==
|
3039
|
+
"SELECT * FROM posts WHERE ((title LIKE 'ruby'))"
|
3040
|
+
end
|
3041
|
+
|
3042
|
+
specify "should support multiple columns" do
|
3043
|
+
@ds.grep([:title, :body], 'ruby').sql.should ==
|
3044
|
+
"SELECT * FROM posts WHERE ((title LIKE 'ruby') OR (body LIKE 'ruby'))"
|
3045
|
+
end
|
3046
|
+
|
3047
|
+
specify "should support multiple search terms" do
|
3048
|
+
@ds.grep(:title, ['abc', 'def']).sql.should ==
|
3049
|
+
"SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def'))"
|
3050
|
+
end
|
3051
|
+
|
3052
|
+
specify "should support multiple columns and search terms" do
|
3053
|
+
@ds.grep([:title, :body], ['abc', 'def']).sql.should ==
|
3054
|
+
"SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def') OR (body LIKE 'abc') OR (body LIKE 'def'))"
|
3055
|
+
end
|
3056
|
+
|
3057
|
+
specify "should support regexps though the database may not support it" do
|
3058
|
+
@ds.grep(:title, /ruby/).sql.should ==
|
3059
|
+
"SELECT * FROM posts WHERE ((title ~ 'ruby'))"
|
3060
|
+
|
3061
|
+
@ds.grep(:title, [/^ruby/, 'ruby']).sql.should ==
|
3062
|
+
"SELECT * FROM posts WHERE ((title ~ '^ruby') OR (title LIKE 'ruby'))"
|
3063
|
+
end
|
3064
|
+
|
3065
|
+
specify "should support searching against other columns" do
|
3066
|
+
@ds.grep(:title, :body).sql.should ==
|
3067
|
+
"SELECT * FROM posts WHERE ((title LIKE body))"
|
3068
|
+
end
|
3069
|
+
end
|
3070
|
+
|
3071
|
+
context "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #execute" do
|
3072
|
+
before do
|
3073
|
+
@db = Sequel::Database.new
|
3074
|
+
@ds = @db[:items]
|
3075
|
+
end
|
3076
|
+
|
3077
|
+
specify "#fetch_rows should raise a NotImplementedError" do
|
3078
|
+
proc{@ds.fetch_rows(''){}}.should raise_error(NotImplementedError)
|
3079
|
+
end
|
3080
|
+
|
3081
|
+
specify "#delete should execute delete SQL" do
|
3082
|
+
@db.should_receive(:execute).once.with('DELETE FROM items', :server=>:default)
|
3083
|
+
@ds.delete
|
3084
|
+
@db.should_receive(:execute_dui).once.with('DELETE FROM items', :server=>:default)
|
3085
|
+
@ds.delete
|
3086
|
+
end
|
3087
|
+
|
3088
|
+
specify "#insert should execute insert SQL" do
|
3089
|
+
@db.should_receive(:execute).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
|
3090
|
+
@ds.insert([])
|
3091
|
+
@db.should_receive(:execute_insert).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
|
3092
|
+
@ds.insert([])
|
3093
|
+
end
|
3094
|
+
|
3095
|
+
specify "#update should execute update SQL" do
|
3096
|
+
@db.should_receive(:execute).once.with('UPDATE items SET number = 1', :server=>:default)
|
3097
|
+
@ds.update(:number=>1)
|
3098
|
+
@db.should_receive(:execute_dui).once.with('UPDATE items SET number = 1', :server=>:default)
|
3099
|
+
@ds.update(:number=>1)
|
3100
|
+
end
|
3101
|
+
|
3102
|
+
specify "#truncate should execute truncate SQL" do
|
3103
|
+
@db.should_receive(:execute).once.with('TRUNCATE TABLE items', :server=>:default)
|
3104
|
+
@ds.truncate.should == nil
|
3105
|
+
@db.should_receive(:execute_ddl).once.with('TRUNCATE TABLE items', :server=>:default)
|
3106
|
+
@ds.truncate.should == nil
|
3107
|
+
end
|
3108
|
+
|
3109
|
+
specify "#truncate should raise an InvalidOperation exception if the dataset is filtered" do
|
3110
|
+
proc{@ds.filter(:a=>1).truncate}.should raise_error(Sequel::InvalidOperation)
|
3111
|
+
end
|
3112
|
+
|
3113
|
+
specify "#execute should execute the SQL on the database" do
|
3114
|
+
@db.should_receive(:execute).once.with('SELECT 1', :server=>:read_only)
|
3115
|
+
@ds.send(:execute, 'SELECT 1')
|
3116
|
+
end
|
3117
|
+
end
|
3118
|
+
|
3119
|
+
context "Dataset prepared statements and bound variables " do
|
3120
|
+
before do
|
3121
|
+
@db = Sequel::Database.new
|
3122
|
+
@db.meta_def(:sqls){@sqls||=[]}
|
3123
|
+
def @db.execute(sql, opts={})
|
3124
|
+
sqls << sql
|
3125
|
+
end
|
3126
|
+
def @db.dataset
|
3127
|
+
ds = super()
|
3128
|
+
def ds.fetch_rows(sql, &block)
|
3129
|
+
execute(sql)
|
3130
|
+
end
|
3131
|
+
ds
|
3132
|
+
end
|
3133
|
+
@ds = @db[:items]
|
3134
|
+
end
|
3135
|
+
|
3136
|
+
specify "#call should take a type and bind hash and interpolate it" do
|
3137
|
+
@ds.filter(:num=>:$n).call(:select, :n=>1)
|
3138
|
+
@ds.filter(:num=>:$n).call(:first, :n=>1)
|
3139
|
+
@ds.filter(:num=>:$n).call(:delete, :n=>1)
|
3140
|
+
@ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
|
3141
|
+
@ds.call(:insert, {:n=>1}, :num=>:$n)
|
3142
|
+
@db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
|
3143
|
+
'SELECT * FROM items WHERE (num = 1) LIMIT 1',
|
3144
|
+
'DELETE FROM items WHERE (num = 1)',
|
3145
|
+
'UPDATE items SET num = 2 WHERE (num = 1)',
|
3146
|
+
'INSERT INTO items (num) VALUES (1)']
|
3147
|
+
end
|
3148
|
+
|
3149
|
+
specify "#prepare should take a type and name and store it in the database for later use with call" do
|
3150
|
+
pss = []
|
3151
|
+
pss << @ds.filter(:num=>:$n).prepare(:select, :sn)
|
3152
|
+
pss << @ds.filter(:num=>:$n).prepare(:first, :fn)
|
3153
|
+
pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
|
3154
|
+
pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
|
3155
|
+
pss << @ds.prepare(:insert, :in, :num=>:$n)
|
3156
|
+
@db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :sn, :un]
|
3157
|
+
[:sn, :fn, :dn, :un, :in].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
|
3158
|
+
@db.call(:sn, :n=>1)
|
3159
|
+
@db.call(:fn, :n=>1)
|
3160
|
+
@db.call(:dn, :n=>1)
|
3161
|
+
@db.call(:un, :n=>1, :n2=>2)
|
3162
|
+
@db.call(:in, :n=>1)
|
3163
|
+
@db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
|
3164
|
+
'SELECT * FROM items WHERE (num = 1) LIMIT 1',
|
3165
|
+
'DELETE FROM items WHERE (num = 1)',
|
3166
|
+
'UPDATE items SET num = 2 WHERE (num = 1)',
|
3167
|
+
'INSERT INTO items (num) VALUES (1)']
|
3168
|
+
end
|
3169
|
+
|
3170
|
+
specify "#inspect should indicate it is a prepared statement with the prepared SQL" do
|
3171
|
+
@ds.filter(:num=>:$n).prepare(:select, :sn).inspect.should == \
|
3172
|
+
'<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
|
3173
|
+
end
|
3174
|
+
|
3175
|
+
specify "should handle literal strings" do
|
3176
|
+
@ds.filter("num = ?", :$n).call(:select, :n=>1)
|
3177
|
+
@db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
|
3178
|
+
end
|
3179
|
+
|
3180
|
+
specify "should handle datasets using static sql and placeholders" do
|
3181
|
+
@db["SELECT * FROM items WHERE (num = ?)", :$n].call(:select, :n=>1)
|
3182
|
+
@db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
|
3183
|
+
end
|
3184
|
+
|
3185
|
+
specify "should handle subselects" do
|
3186
|
+
@ds.filter(:$b).filter(:num=>@ds.select(:num).filter(:num=>:$n)).filter(:$c).call(:select, :n=>1, :b=>0, :c=>2)
|
3187
|
+
@db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))) AND 2)']
|
3188
|
+
end
|
3189
|
+
|
3190
|
+
specify "should handle subselects in subselects" do
|
3191
|
+
@ds.filter(:$b).filter(:num=>@ds.select(:num).filter(:num=>@ds.select(:num).filter(:num=>:$n))).call(:select, :n=>1, :b=>0)
|
3192
|
+
@db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num IN (SELECT num FROM items WHERE (num = 1))))))']
|
3193
|
+
end
|
3194
|
+
|
3195
|
+
specify "should handle subselects with literal strings" do
|
3196
|
+
@ds.filter(:$b).filter(:num=>@ds.select(:num).filter("num = ?", :$n)).call(:select, :n=>1, :b=>0)
|
3197
|
+
@db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
|
3198
|
+
end
|
3199
|
+
|
3200
|
+
specify "should handle subselects with static sql and placeholders" do
|
3201
|
+
@ds.filter(:$b).filter(:num=>@db["SELECT num FROM items WHERE (num = ?)", :$n]).call(:select, :n=>1, :b=>0)
|
3202
|
+
@db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
|
3203
|
+
end
|
3204
|
+
end
|
3205
|
+
|
3206
|
+
context Sequel::Dataset::UnnumberedArgumentMapper do
|
3207
|
+
before do
|
3208
|
+
@db = Sequel::Database.new
|
3209
|
+
@db.meta_def(:sqls){@sqls||=[]}
|
3210
|
+
def @db.execute(sql, opts={})
|
3211
|
+
sqls << [sql, *opts[:arguments]]
|
3212
|
+
end
|
3213
|
+
@ds = @db[:items].filter(:num=>:$n)
|
3214
|
+
def @ds.fetch_rows(sql, &block)
|
3215
|
+
execute(sql)
|
3216
|
+
end
|
3217
|
+
def @ds.execute(sql, opts={}, &block)
|
3218
|
+
@db.execute(sql, {:arguments=>bind_arguments}.merge(opts))
|
3219
|
+
end
|
3220
|
+
def @ds.execute_dui(*args, &block)
|
3221
|
+
execute(*args, &block)
|
3222
|
+
end
|
3223
|
+
def @ds.execute_insert(*args, &block)
|
3224
|
+
execute(*args, &block)
|
3225
|
+
end
|
3226
|
+
@ps = []
|
3227
|
+
@ps << @ds.prepare(:select, :s)
|
3228
|
+
@ps << @ds.prepare(:all, :a)
|
3229
|
+
@ps << @ds.prepare(:first, :f)
|
3230
|
+
@ps << @ds.prepare(:delete, :d)
|
3231
|
+
@ps << @ds.prepare(:insert, :i, :num=>:$n)
|
3232
|
+
@ps << @ds.prepare(:update, :u, :num=>:$n)
|
3233
|
+
@ps.each{|p| p.extend(Sequel::Dataset::UnnumberedArgumentMapper)}
|
3234
|
+
end
|
3235
|
+
|
3236
|
+
specify "#inspect should show the actual SQL submitted to the database" do
|
3237
|
+
@ps.first.inspect.should == '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
|
3238
|
+
end
|
3239
|
+
|
3240
|
+
specify "should submitted the SQL to the database with placeholders and bind variables" do
|
3241
|
+
@ps.each{|p| p.prepared_sql; p.call(:n=>1)}
|
3242
|
+
@db.sqls.should == [["SELECT * FROM items WHERE (num = ?)", 1],
|
3243
|
+
["SELECT * FROM items WHERE (num = ?)", 1],
|
3244
|
+
["SELECT * FROM items WHERE (num = ?) LIMIT 1", 1],
|
3245
|
+
["DELETE FROM items WHERE (num = ?)", 1],
|
3246
|
+
["INSERT INTO items (num) VALUES (?)", 1],
|
3247
|
+
["UPDATE items SET num = ? WHERE (num = ?)", 1, 1],
|
3248
|
+
]
|
3249
|
+
end
|
3250
|
+
end
|
3251
|
+
|
3252
|
+
context "Sequel::Dataset#server" do
|
3253
|
+
specify "should set the server to use for the dataset" do
|
3254
|
+
@db = Sequel::Database.new
|
3255
|
+
@ds = @db[:items].server(:s)
|
3256
|
+
sqls = []
|
3257
|
+
@db.meta_def(:execute) do |sql, opts|
|
3258
|
+
sqls << [sql, opts[:server]]
|
3259
|
+
end
|
3260
|
+
def @ds.fetch_rows(sql, &block)
|
3261
|
+
execute(sql)
|
3262
|
+
end
|
3263
|
+
@ds.all
|
3264
|
+
@ds.server(:i).insert(:a=>1)
|
3265
|
+
@ds.server(:d).delete
|
3266
|
+
@ds.server(:u).update(:a=>:a+1)
|
3267
|
+
sqls.should == [['SELECT * FROM items', :s],
|
3268
|
+
['INSERT INTO items (a) VALUES (1)', :i],
|
3269
|
+
['DELETE FROM items', :d],
|
3270
|
+
['UPDATE items SET a = (a + 1)', :u]]
|
3271
|
+
end
|
3272
|
+
end
|
3273
|
+
|
3274
|
+
context "Sequel::Dataset#each_server" do
|
3275
|
+
before do
|
3276
|
+
@db = Sequel::Database.new(:servers=>{:s=>{}, :i=>{}})
|
3277
|
+
@ds = @db[:items]
|
3278
|
+
sqls = @sqls = []
|
3279
|
+
@db.meta_def(:execute) do |sql, opts|
|
3280
|
+
sqls << [sql, opts[:server]]
|
3281
|
+
end
|
3282
|
+
def @ds.fetch_rows(sql, &block)
|
3283
|
+
execute(sql)
|
3284
|
+
end
|
3285
|
+
end
|
3286
|
+
|
3287
|
+
specify "should yield a dataset for each server" do
|
3288
|
+
@ds.each_server do |ds|
|
3289
|
+
ds.should be_a_kind_of(Sequel::Dataset)
|
3290
|
+
ds.should_not == @ds
|
3291
|
+
ds.sql.should == @ds.sql
|
3292
|
+
ds.all
|
3293
|
+
end
|
3294
|
+
@sqls.sort_by{|sql, s| s.to_s}.should == [['SELECT * FROM items', :default], ['SELECT * FROM items', :i], ['SELECT * FROM items', :s]]
|
3295
|
+
end
|
3296
|
+
end
|
3297
|
+
|
3298
|
+
context "Sequel::Dataset #set_defaults" do
|
3299
|
+
before do
|
3300
|
+
@ds = Sequel::Dataset.new(nil).from(:items).set_defaults(:x=>1)
|
3301
|
+
end
|
3302
|
+
|
3303
|
+
specify "should set the default values for inserts" do
|
3304
|
+
@ds.insert_sql.should == "INSERT INTO items (x) VALUES (1)"
|
3305
|
+
@ds.insert_sql(:x=>2).should == "INSERT INTO items (x) VALUES (2)"
|
3306
|
+
@ds.insert_sql(:y=>2).should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
|
3307
|
+
@ds.set_defaults(:y=>2).insert_sql.should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
|
3308
|
+
@ds.set_defaults(:x=>2).insert_sql.should == "INSERT INTO items (x) VALUES (2)"
|
3309
|
+
end
|
3310
|
+
|
3311
|
+
specify "should set the default values for updates" do
|
3312
|
+
@ds.update_sql.should == "UPDATE items SET x = 1"
|
3313
|
+
@ds.update_sql(:x=>2).should == "UPDATE items SET x = 2"
|
3314
|
+
@ds.update_sql(:y=>2).should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
|
3315
|
+
@ds.set_defaults(:y=>2).update_sql.should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
|
3316
|
+
@ds.set_defaults(:x=>2).update_sql.should == "UPDATE items SET x = 2"
|
3317
|
+
end
|
3318
|
+
end
|
3319
|
+
|
3320
|
+
context "Sequel::Dataset #set_overrides" do
|
3321
|
+
before do
|
3322
|
+
@ds = Sequel::Dataset.new(nil).from(:items).set_overrides(:x=>1)
|
3323
|
+
end
|
3324
|
+
|
3325
|
+
specify "should override the given values for inserts" do
|
3326
|
+
@ds.insert_sql.should == "INSERT INTO items (x) VALUES (1)"
|
3327
|
+
@ds.insert_sql(:x=>2).should == "INSERT INTO items (x) VALUES (1)"
|
3328
|
+
@ds.insert_sql(:y=>2).should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
|
3329
|
+
@ds.set_overrides(:y=>2).insert_sql.should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
|
3330
|
+
@ds.set_overrides(:x=>2).insert_sql.should == "INSERT INTO items (x) VALUES (1)"
|
3331
|
+
end
|
3332
|
+
|
3333
|
+
specify "should override the given values for updates" do
|
3334
|
+
@ds.update_sql.should == "UPDATE items SET x = 1"
|
3335
|
+
@ds.update_sql(:x=>2).should == "UPDATE items SET x = 1"
|
3336
|
+
@ds.update_sql(:y=>2).should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
|
3337
|
+
@ds.set_overrides(:y=>2).update_sql.should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
|
3338
|
+
@ds.set_overrides(:x=>2).update_sql.should == "UPDATE items SET x = 1"
|
3339
|
+
end
|
3340
|
+
end
|
3341
|
+
|
3342
|
+
context "Sequel::Dataset#qualify" do
|
3343
|
+
specify "should qualify to the given table" do
|
3344
|
+
MockDatabase.new[:t].filter{a<b}.qualify(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
|
3345
|
+
end
|
3346
|
+
|
3347
|
+
specify "should qualify to the first source if no table if given" do
|
3348
|
+
MockDatabase.new[:t].filter{a<b}.qualify.sql.should == 'SELECT t.* FROM t WHERE (t.a < t.b)'
|
3349
|
+
end
|
3350
|
+
end
|
3351
|
+
|
3352
|
+
context "Sequel::Dataset#qualify_to" do
|
3353
|
+
specify "should qualify to the given table" do
|
3354
|
+
MockDatabase.new[:t].filter{a<b}.qualify_to(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
|
3355
|
+
end
|
3356
|
+
end
|
3357
|
+
|
3358
|
+
context "Sequel::Dataset#qualify_to_first_source" do
|
3359
|
+
before do
|
3360
|
+
@ds = MockDatabase.new[:t]
|
3361
|
+
end
|
3362
|
+
|
3363
|
+
specify "should qualify_to the first source" do
|
3364
|
+
@ds.qualify_to_first_source.sql.should == 'SELECT t.* FROM t'
|
3365
|
+
@ds.should_receive(:qualify_to).with(:t).once
|
3366
|
+
@ds.qualify_to_first_source
|
3367
|
+
end
|
3368
|
+
|
3369
|
+
specify "should handle the select, order, where, having, and group options/clauses" do
|
3370
|
+
@ds.select(:a).filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == \
|
3371
|
+
'SELECT t.a FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
|
3372
|
+
end
|
3373
|
+
|
3374
|
+
specify "should handle the select using a table.* if all columns are currently selected" do
|
3375
|
+
@ds.filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == \
|
3376
|
+
'SELECT t.* FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
|
3377
|
+
end
|
3378
|
+
|
3379
|
+
specify "should handle hashes in select option" do
|
3380
|
+
@ds.select(:a=>:b).qualify_to_first_source.sql.should == 'SELECT t.a AS b FROM t'
|
3381
|
+
end
|
3382
|
+
|
3383
|
+
specify "should handle symbols" do
|
3384
|
+
@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'
|
3385
|
+
end
|
3386
|
+
|
3387
|
+
specify "should handle arrays" do
|
3388
|
+
@ds.filter(:a=>[:b, :c]).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a IN (t.b, t.c))'
|
3389
|
+
end
|
3390
|
+
|
3391
|
+
specify "should handle hashes" do
|
3392
|
+
@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"
|
3393
|
+
end
|
3394
|
+
|
3395
|
+
specify "should handle SQL::Identifiers" do
|
3396
|
+
@ds.select{a}.qualify_to_first_source.sql.should == 'SELECT t.a FROM t'
|
3397
|
+
end
|
3398
|
+
|
3399
|
+
specify "should handle SQL::OrderedExpressions" do
|
3400
|
+
@ds.order(:a.desc, :b.asc).qualify_to_first_source.sql.should == 'SELECT t.* FROM t ORDER BY t.a DESC, t.b ASC'
|
3401
|
+
end
|
3402
|
+
|
3403
|
+
specify "should handle SQL::AliasedExpressions" do
|
3404
|
+
@ds.select(:a.as(:b)).qualify_to_first_source.sql.should == 'SELECT t.a AS b FROM t'
|
3405
|
+
end
|
3406
|
+
|
3407
|
+
specify "should handle SQL::CaseExpressions" do
|
3408
|
+
@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)'
|
3409
|
+
end
|
3410
|
+
|
3411
|
+
specify "should handle SQL:Casts" do
|
3412
|
+
@ds.filter{a.cast(:boolean)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE CAST(t.a AS boolean)'
|
3413
|
+
end
|
3414
|
+
|
3415
|
+
specify "should handle SQL::Functions" do
|
3416
|
+
@ds.filter{a(b, 1)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE a(t.b, 1)'
|
3417
|
+
end
|
3418
|
+
|
3419
|
+
specify "should handle SQL::ComplexExpressions" do
|
3420
|
+
@ds.filter{(a+b)<(c-3)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE ((t.a + t.b) < (t.c - 3))'
|
3421
|
+
end
|
3422
|
+
|
3423
|
+
specify "should handle SQL::SQLArrays" do
|
3424
|
+
@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))'
|
3425
|
+
end
|
3426
|
+
|
3427
|
+
specify "should handle SQL::Subscripts" do
|
3428
|
+
@ds.filter{a.sql_subscript(b,3)}.qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE t.a[t.b, 3]'
|
3429
|
+
end
|
3430
|
+
|
3431
|
+
specify "should handle SQL::PlaceholderLiteralStrings" do
|
3432
|
+
@ds.filter('? > ?', :a, 1).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a > 1)'
|
3433
|
+
end
|
3434
|
+
|
3435
|
+
specify "should handle SQL::PlaceholderLiteralStrings with named placeholders" do
|
3436
|
+
@ds.filter(':a > :b', :a=>:c, :b=>1).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.c > 1)'
|
3437
|
+
end
|
3438
|
+
|
3439
|
+
specify "should handle SQL::WindowFunctions" do
|
3440
|
+
@ds.meta_def(:supports_window_functions?){true}
|
3441
|
+
@ds.select{sum(:over, :args=>:a, :partition=>:b, :order=>:c){}}.qualify_to_first_source.sql.should == 'SELECT sum(t.a) OVER (PARTITION BY t.b ORDER BY t.c) FROM t'
|
3442
|
+
end
|
3443
|
+
|
3444
|
+
specify "should handle all other objects by returning them unchanged" do
|
3445
|
+
@ds.select("a").filter{a(3)}.filter('blah').order('true'.lit).group('a > ?'.lit(1)).having(false).qualify_to_first_source.sql.should == \
|
3446
|
+
"SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true"
|
3447
|
+
end
|
3448
|
+
end
|
3449
|
+
|
3450
|
+
context "Sequel::Dataset #with and #with_recursive" do
|
3451
|
+
before do
|
3452
|
+
@db = MockDatabase.new
|
3453
|
+
@ds = @db[:t]
|
3454
|
+
end
|
3455
|
+
|
3456
|
+
specify "#with should take a name and dataset and use a WITH clause" do
|
3457
|
+
@ds.with(:t, @db[:x]).sql.should == 'WITH t AS (SELECT * FROM x) SELECT * FROM t'
|
3458
|
+
end
|
3459
|
+
|
3460
|
+
specify "#with_recursive should take a name, nonrecursive dataset, and recursive dataset, and use a WITH clause" do
|
3461
|
+
@ds.with_recursive(:t, @db[:x], @db[:t]).sql.should == 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
|
3462
|
+
end
|
3463
|
+
|
3464
|
+
specify "#with and #with_recursive should add to existing WITH clause if called multiple times" do
|
3465
|
+
@ds.with(:t, @db[:x]).with(:j, @db[:y]).sql.should == 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y) SELECT * FROM t'
|
3466
|
+
@ds.with_recursive(:t, @db[:x], @db[:t]).with_recursive(:j, @db[:y], @db[:j]).sql.should == 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
|
3467
|
+
@ds.with(:t, @db[:x]).with_recursive(:j, @db[:y], @db[:j]).sql.should == 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
|
3468
|
+
end
|
3469
|
+
|
3470
|
+
specify "#with and #with_recursive should take an :args option" do
|
3471
|
+
@ds.with(:t, @db[:x], :args=>[:b]).sql.should == 'WITH t(b) AS (SELECT * FROM x) SELECT * FROM t'
|
3472
|
+
@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c]).sql.should == 'WITH t(b, c) AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
|
3473
|
+
end
|
3474
|
+
|
3475
|
+
specify "#with_recursive should take an :union_all=>false option" do
|
3476
|
+
@ds.with_recursive(:t, @db[:x], @db[:t], :union_all=>false).sql.should == 'WITH t AS (SELECT * FROM x UNION SELECT * FROM t) SELECT * FROM t'
|
3477
|
+
end
|
3478
|
+
|
3479
|
+
specify "#with and #with_recursive should raise an error unless the dataset supports CTEs" do
|
3480
|
+
@ds.meta_def(:supports_cte?){false}
|
3481
|
+
proc{@ds.with(:t, @db[:x], :args=>[:b])}.should raise_error(Sequel::Error)
|
3482
|
+
proc{@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c])}.should raise_error(Sequel::Error)
|
3483
|
+
end
|
3484
|
+
end
|
3485
|
+
|
3486
|
+
describe Sequel::SQL::Constants do
|
3487
|
+
before do
|
3488
|
+
@db = MockDatabase.new
|
3489
|
+
end
|
3490
|
+
|
3491
|
+
it "should have CURRENT_DATE" do
|
3492
|
+
@db.literal(Sequel::SQL::Constants::CURRENT_DATE) == 'CURRENT_DATE'
|
3493
|
+
@db.literal(Sequel::CURRENT_DATE) == 'CURRENT_DATE'
|
3494
|
+
end
|
3495
|
+
|
3496
|
+
it "should have CURRENT_TIME" do
|
3497
|
+
@db.literal(Sequel::SQL::Constants::CURRENT_TIME) == 'CURRENT_TIME'
|
3498
|
+
@db.literal(Sequel::CURRENT_TIME) == 'CURRENT_TIME'
|
3499
|
+
end
|
3500
|
+
|
3501
|
+
it "should have CURRENT_TIMESTAMP" do
|
3502
|
+
@db.literal(Sequel::SQL::Constants::CURRENT_TIMESTAMP) == 'CURRENT_TIMESTAMP'
|
3503
|
+
@db.literal(Sequel::CURRENT_TIMESTAMP) == 'CURRENT_TIMESTAMP'
|
3504
|
+
end
|
3505
|
+
|
3506
|
+
it "should have NULL" do
|
3507
|
+
@db.literal(Sequel::SQL::Constants::NULL) == 'NULL'
|
3508
|
+
@db.literal(Sequel::NULL) == 'NULL'
|
3509
|
+
end
|
3510
|
+
|
3511
|
+
it "should have NOTNULL" do
|
3512
|
+
@db.literal(Sequel::SQL::Constants::NOTNULL) == 'NOT NULL'
|
3513
|
+
@db.literal(Sequel::NOTNULL) == 'NOT NULL'
|
3514
|
+
end
|
3515
|
+
|
3516
|
+
it "should have TRUE and SQLTRUE" do
|
3517
|
+
@db.literal(Sequel::SQL::Constants::TRUE) == '1'
|
3518
|
+
@db.literal(Sequel::TRUE) == '1'
|
3519
|
+
@db.literal(Sequel::SQL::Constants::SQLTRUE) == '1'
|
3520
|
+
@db.literal(Sequel::SQLTRUE) == '1'
|
3521
|
+
end
|
3522
|
+
|
3523
|
+
it "should have FALSE and SQLFALSE" do
|
3524
|
+
@db.literal(Sequel::SQL::Constants::FALSE) == '0'
|
3525
|
+
@db.literal(Sequel::FALSE) == '0'
|
3526
|
+
@db.literal(Sequel::SQL::Constants::SQLFALSE) == '0'
|
3527
|
+
@db.literal(Sequel::SQLFALSE) == '0'
|
3528
|
+
end
|
3529
|
+
end
|
3530
|
+
|
3531
|
+
describe "Sequel timezone support" do
|
3532
|
+
before do
|
3533
|
+
@db = MockDatabase.new
|
3534
|
+
@dataset = @db.dataset
|
3535
|
+
@dataset.meta_def(:supports_timestamp_timezones?){true}
|
3536
|
+
@dataset.meta_def(:supports_timestamp_usecs?){false}
|
3537
|
+
@offset = sprintf("%+03i%02i", *(Time.now.utc_offset/60).divmod(60))
|
3538
|
+
end
|
3539
|
+
after do
|
3540
|
+
Sequel.default_timezone = nil
|
3541
|
+
Sequel.datetime_class = Time
|
3542
|
+
end
|
3543
|
+
|
3544
|
+
specify "should handle an database timezone of :utc when literalizing values" do
|
3545
|
+
Sequel.database_timezone = :utc
|
3546
|
+
|
3547
|
+
t = Time.now
|
3548
|
+
s = t.getutc.strftime("'%Y-%m-%d %H:%M:%S")
|
3549
|
+
@dataset.literal(t).should == "#{s}+0000'"
|
3550
|
+
|
3551
|
+
t = DateTime.now
|
3552
|
+
s = t.new_offset(0).strftime("'%Y-%m-%d %H:%M:%S")
|
3553
|
+
@dataset.literal(t).should == "#{s}+0000'"
|
3554
|
+
end
|
3555
|
+
|
3556
|
+
specify "should handle an database timezone of :local when literalizing values" do
|
3557
|
+
Sequel.database_timezone = :local
|
3558
|
+
|
3559
|
+
t = Time.now.utc
|
3560
|
+
s = t.getlocal.strftime("'%Y-%m-%d %H:%M:%S")
|
3561
|
+
@dataset.literal(t).should == "#{s}#{@offset}'"
|
3562
|
+
|
3563
|
+
t = DateTime.now.new_offset(0)
|
3564
|
+
s = t.new_offset(Sequel::LOCAL_DATETIME_OFFSET).strftime("'%Y-%m-%d %H:%M:%S")
|
3565
|
+
@dataset.literal(t).should == "#{s}#{@offset}'"
|
3566
|
+
end
|
3567
|
+
|
3568
|
+
specify "should handle converting database timestamps into application timestamps" do
|
3569
|
+
Sequel.database_timezone = :utc
|
3570
|
+
Sequel.application_timezone = :local
|
3571
|
+
t = Time.now.utc
|
3572
|
+
Sequel.database_to_application_timestamp(t).to_s.should == t.getlocal.to_s
|
3573
|
+
Sequel.database_to_application_timestamp(t.to_s).to_s.should == t.getlocal.to_s
|
3574
|
+
Sequel.database_to_application_timestamp(t.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == t.getlocal.to_s
|
3575
|
+
|
3576
|
+
Sequel.datetime_class = DateTime
|
3577
|
+
dt = DateTime.now
|
3578
|
+
dt2 = dt.new_offset(0)
|
3579
|
+
Sequel.database_to_application_timestamp(dt2).to_s.should == dt.to_s
|
3580
|
+
Sequel.database_to_application_timestamp(dt2.to_s).to_s.should == dt.to_s
|
3581
|
+
Sequel.database_to_application_timestamp(dt2.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == dt.to_s
|
3582
|
+
|
3583
|
+
Sequel.datetime_class = Time
|
3584
|
+
Sequel.database_timezone = :local
|
3585
|
+
Sequel.application_timezone = :utc
|
3586
|
+
Sequel.database_to_application_timestamp(t.getlocal).to_s.should == t.to_s
|
3587
|
+
Sequel.database_to_application_timestamp(t.getlocal.to_s).to_s.should == t.to_s
|
3588
|
+
Sequel.database_to_application_timestamp(t.getlocal.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == t.to_s
|
3589
|
+
|
3590
|
+
Sequel.datetime_class = DateTime
|
3591
|
+
Sequel.database_to_application_timestamp(dt).to_s.should == dt2.to_s
|
3592
|
+
Sequel.database_to_application_timestamp(dt.to_s).to_s.should == dt2.to_s
|
3593
|
+
Sequel.database_to_application_timestamp(dt.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == dt2.to_s
|
3594
|
+
end
|
3595
|
+
|
3596
|
+
specify "should handle typecasting timestamp columns" do
|
3597
|
+
Sequel.typecast_timezone = :utc
|
3598
|
+
Sequel.application_timezone = :local
|
3599
|
+
t = Time.now.utc
|
3600
|
+
@db.typecast_value(:datetime, t).to_s.should == t.getlocal.to_s
|
3601
|
+
@db.typecast_value(:datetime, t.to_s).to_s.should == t.getlocal.to_s
|
3602
|
+
@db.typecast_value(:datetime, t.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == t.getlocal.to_s
|
3603
|
+
|
3604
|
+
Sequel.datetime_class = DateTime
|
3605
|
+
dt = DateTime.now
|
3606
|
+
dt2 = dt.new_offset(0)
|
3607
|
+
@db.typecast_value(:datetime, dt2).to_s.should == dt.to_s
|
3608
|
+
@db.typecast_value(:datetime, dt2.to_s).to_s.should == dt.to_s
|
3609
|
+
@db.typecast_value(:datetime, dt2.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == dt.to_s
|
3610
|
+
|
3611
|
+
Sequel.datetime_class = Time
|
3612
|
+
Sequel.typecast_timezone = :local
|
3613
|
+
Sequel.application_timezone = :utc
|
3614
|
+
@db.typecast_value(:datetime, t.getlocal).to_s.should == t.to_s
|
3615
|
+
@db.typecast_value(:datetime, t.getlocal.to_s).to_s.should == t.to_s
|
3616
|
+
@db.typecast_value(:datetime, t.getlocal.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == t.to_s
|
3617
|
+
|
3618
|
+
Sequel.datetime_class = DateTime
|
3619
|
+
@db.typecast_value(:datetime, dt).to_s.should == dt2.to_s
|
3620
|
+
@db.typecast_value(:datetime, dt.to_s).to_s.should == dt2.to_s
|
3621
|
+
@db.typecast_value(:datetime, dt.strftime('%Y-%m-%d %H:%M:%S')).to_s.should == dt2.to_s
|
3622
|
+
end
|
3623
|
+
|
3624
|
+
specify "should handle converting database timestamp columns from an array of values" do
|
3625
|
+
Sequel.database_timezone = :utc
|
3626
|
+
Sequel.application_timezone = :local
|
3627
|
+
t = Time.now.utc
|
3628
|
+
Sequel.database_to_application_timestamp([t.year, t.mon, t.day, t.hour, t.min, t.sec]).to_s.should == t.getlocal.to_s
|
3629
|
+
|
3630
|
+
Sequel.datetime_class = DateTime
|
3631
|
+
dt = DateTime.now
|
3632
|
+
dt2 = dt.new_offset(0)
|
3633
|
+
Sequel.database_to_application_timestamp([dt2.year, dt2.mon, dt2.day, dt2.hour, dt2.min, dt2.sec]).to_s.should == dt.to_s
|
3634
|
+
|
3635
|
+
Sequel.datetime_class = Time
|
3636
|
+
Sequel.database_timezone = :local
|
3637
|
+
Sequel.application_timezone = :utc
|
3638
|
+
t = t.getlocal
|
3639
|
+
Sequel.database_to_application_timestamp([t.year, t.mon, t.day, t.hour, t.min, t.sec]).to_s.should == t.getutc.to_s
|
3640
|
+
|
3641
|
+
Sequel.datetime_class = DateTime
|
3642
|
+
Sequel.database_to_application_timestamp([dt.year, dt.mon, dt.day, dt.hour, dt.min, dt.sec]).to_s.should == dt2.to_s
|
3643
|
+
end
|
3644
|
+
|
3645
|
+
specify "should raise an InvalidValue error when an error occurs while converting a timestamp" do
|
3646
|
+
proc{Sequel.database_to_application_timestamp([0, 0, 0, 0, 0, 0])}.should raise_error(Sequel::InvalidValue)
|
3647
|
+
end
|
3648
|
+
|
3649
|
+
specify "should raise an error when attempting to typecast to a timestamp from an unsupported type" do
|
3650
|
+
proc{Sequel.database_to_application_timestamp(Object.new)}.should raise_error(Sequel::InvalidValue)
|
3651
|
+
end
|
3652
|
+
|
3653
|
+
specify "should raise an InvalidValue error when the DateTime class is used and when a bad application timezone is used when attempting to convert timestamps" do
|
3654
|
+
Sequel.application_timezone = :blah
|
3655
|
+
Sequel.datetime_class = DateTime
|
3656
|
+
proc{Sequel.database_to_application_timestamp('2009-06-01 10:20:30')}.should raise_error(Sequel::InvalidValue)
|
3657
|
+
end
|
3658
|
+
|
3659
|
+
specify "should raise an InvalidValue error when the DateTime class is used and when a bad database timezone is used when attempting to convert timestamps" do
|
3660
|
+
Sequel.database_timezone = :blah
|
3661
|
+
Sequel.datetime_class = DateTime
|
3662
|
+
proc{Sequel.database_to_application_timestamp('2009-06-01 10:20:30')}.should raise_error(Sequel::InvalidValue)
|
3663
|
+
end
|
3664
|
+
|
3665
|
+
specify "should have Sequel.default_timezone= should set all other timezones" do
|
3666
|
+
Sequel.database_timezone.should == nil
|
3667
|
+
Sequel.application_timezone.should == nil
|
3668
|
+
Sequel.typecast_timezone.should == nil
|
3669
|
+
Sequel.default_timezone = :utc
|
3670
|
+
Sequel.database_timezone.should == :utc
|
3671
|
+
Sequel.application_timezone.should == :utc
|
3672
|
+
Sequel.typecast_timezone.should == :utc
|
3673
|
+
end
|
3674
|
+
end
|
3675
|
+
|
3676
|
+
context "Sequel::Dataset#select_map" do
|
3677
|
+
before do
|
3678
|
+
@ds = MockDatabase.new[:t]
|
3679
|
+
def @ds.fetch_rows(sql)
|
3680
|
+
db << sql
|
3681
|
+
yield({:c=>1})
|
3682
|
+
yield({:c=>2})
|
3683
|
+
end
|
3684
|
+
@ds.db.reset
|
3685
|
+
end
|
3686
|
+
|
3687
|
+
specify "should do select and map in one step" do
|
3688
|
+
@ds.select_map(:a).should == [1, 2]
|
3689
|
+
@ds.db.sqls.should == ['SELECT a FROM t']
|
3690
|
+
end
|
3691
|
+
|
3692
|
+
specify "should handle implicit qualifiers in arguments" do
|
3693
|
+
@ds.select_map(:a__b).should == [1, 2]
|
3694
|
+
@ds.db.sqls.should == ['SELECT a.b FROM t']
|
3695
|
+
end
|
3696
|
+
|
3697
|
+
specify "should handle implicit aliases in arguments" do
|
3698
|
+
@ds.select_map(:a___b).should == [1, 2]
|
3699
|
+
@ds.db.sqls.should == ['SELECT a AS b FROM t']
|
3700
|
+
end
|
3701
|
+
|
3702
|
+
specify "should handle other objects" do
|
3703
|
+
@ds.select_map("a".lit.as(:b)).should == [1, 2]
|
3704
|
+
@ds.db.sqls.should == ['SELECT a AS b FROM t']
|
3705
|
+
end
|
3706
|
+
|
3707
|
+
specify "should accept a block" do
|
3708
|
+
@ds.select_map{a(t__c)}.should == [1, 2]
|
3709
|
+
@ds.db.sqls.should == ['SELECT a(t.c) FROM t']
|
3710
|
+
end
|
3711
|
+
end
|
3712
|
+
|
3713
|
+
context "Sequel::Dataset#select_order_map" do
|
3714
|
+
before do
|
3715
|
+
@ds = MockDatabase.new[:t]
|
3716
|
+
def @ds.fetch_rows(sql)
|
3717
|
+
db << sql
|
3718
|
+
yield({:c=>1})
|
3719
|
+
yield({:c=>2})
|
3720
|
+
end
|
3721
|
+
@ds.db.reset
|
3722
|
+
end
|
3723
|
+
|
3724
|
+
specify "should do select and map in one step" do
|
3725
|
+
@ds.select_order_map(:a).should == [1, 2]
|
3726
|
+
@ds.db.sqls.should == ['SELECT a FROM t ORDER BY a']
|
3727
|
+
end
|
3728
|
+
|
3729
|
+
specify "should handle implicit qualifiers in arguments" do
|
3730
|
+
@ds.select_order_map(:a__b).should == [1, 2]
|
3731
|
+
@ds.db.sqls.should == ['SELECT a.b FROM t ORDER BY a.b']
|
3732
|
+
end
|
3733
|
+
|
3734
|
+
specify "should handle implicit aliases in arguments" do
|
3735
|
+
@ds.select_order_map(:a___b).should == [1, 2]
|
3736
|
+
@ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
|
3737
|
+
end
|
3738
|
+
|
3739
|
+
specify "should handle implicit qualifiers and aliases in arguments" do
|
3740
|
+
@ds.select_order_map(:t__a___b).should == [1, 2]
|
3741
|
+
@ds.db.sqls.should == ['SELECT t.a AS b FROM t ORDER BY t.a']
|
3742
|
+
end
|
3743
|
+
|
3744
|
+
specify "should handle AliasedExpressions" do
|
3745
|
+
@ds.select_order_map("a".lit.as(:b)).should == [1, 2]
|
3746
|
+
@ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
|
3747
|
+
end
|
3748
|
+
|
3749
|
+
specify "should accept a block" do
|
3750
|
+
@ds.select_order_map{a(t__c)}.should == [1, 2]
|
3751
|
+
@ds.db.sqls.should == ['SELECT a(t.c) FROM t ORDER BY a(t.c)']
|
3752
|
+
end
|
3753
|
+
end
|
3754
|
+
|
3755
|
+
context "Sequel::Dataset#select_hash" do
|
3756
|
+
before do
|
3757
|
+
@ds = MockDatabase.new[:t]
|
3758
|
+
def @ds.set_fr_yield(hs)
|
3759
|
+
@hs = hs
|
3760
|
+
end
|
3761
|
+
def @ds.fetch_rows(sql)
|
3762
|
+
db << sql
|
3763
|
+
@hs.each{|h| yield h}
|
3764
|
+
end
|
3765
|
+
@ds.db.reset
|
3766
|
+
end
|
3767
|
+
|
3768
|
+
specify "should do select and map in one step" do
|
3769
|
+
@ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
|
3770
|
+
@ds.select_hash(:a, :b).should == {1=>2, 3=>4}
|
3771
|
+
@ds.db.sqls.should == ['SELECT a, b FROM t']
|
3772
|
+
end
|
3773
|
+
|
3774
|
+
specify "should handle implicit qualifiers in arguments" do
|
3775
|
+
@ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
|
3776
|
+
@ds.select_hash(:t__a, :t__b).should == {1=>2, 3=>4}
|
3777
|
+
@ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
|
3778
|
+
end
|
3779
|
+
|
3780
|
+
specify "should handle implicit aliases in arguments" do
|
3781
|
+
@ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
|
3782
|
+
@ds.select_hash(:c___a, :d___b).should == {1=>2, 3=>4}
|
3783
|
+
@ds.db.sqls.should == ['SELECT c AS a, d AS b FROM t']
|
3784
|
+
end
|
3785
|
+
|
3786
|
+
specify "should handle implicit qualifiers and aliases in arguments" do
|
3787
|
+
@ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
|
3788
|
+
@ds.select_hash(:t__c___a, :t__d___b).should == {1=>2, 3=>4}
|
3789
|
+
@ds.db.sqls.should == ['SELECT t.c AS a, t.d AS b FROM t']
|
3790
|
+
end
|
3791
|
+
end
|
3792
|
+
|
3793
|
+
context "Modifying joined datasets" do
|
3794
|
+
before do
|
3795
|
+
@ds = MockDatabase.new.from(:b, :c).join(:d, [:id]).where(:id => 2)
|
3796
|
+
@ds.meta_def(:supports_modifying_joins?){true}
|
3797
|
+
@ds.db.reset
|
3798
|
+
end
|
3799
|
+
|
3800
|
+
specify "should allow deleting from joined datasets" do
|
3801
|
+
@ds.delete
|
3802
|
+
@ds.db.sqls.should == ['DELETE FROM b, c WHERE (id = 2)']
|
3803
|
+
end
|
3804
|
+
|
3805
|
+
specify "should allow updating joined datasets" do
|
3806
|
+
@ds.update(:a=>1)
|
3807
|
+
@ds.db.sqls.should == ['UPDATE b, c INNER JOIN d USING (id) SET a = 1 WHERE (id = 2)']
|
3808
|
+
end
|
3809
|
+
end
|
3810
|
+
|
3811
|
+
context "Dataset#lock_style and for_update" do
|
3812
|
+
before do
|
3813
|
+
@ds = MockDatabase.new[:t]
|
3814
|
+
end
|
3815
|
+
|
3816
|
+
specify "#for_update should use FOR UPDATE" do
|
3817
|
+
@ds.for_update.sql.should == "SELECT * FROM t FOR UPDATE"
|
3818
|
+
end
|
3819
|
+
|
3820
|
+
specify "#lock_style should accept symbols" do
|
3821
|
+
@ds.lock_style(:update).sql.should == "SELECT * FROM t FOR UPDATE"
|
3822
|
+
end
|
3823
|
+
|
3824
|
+
specify "#lock_style should accept strings for arbitrary SQL" do
|
3825
|
+
@ds.lock_style("FOR SHARE").sql.should == "SELECT * FROM t FOR SHARE"
|
3826
|
+
end
|
3827
|
+
end
|