colincasey-sequel 2.10.0 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +7 -1
  2. data/doc/advanced_associations.rdoc +614 -0
  3. data/doc/cheat_sheet.rdoc +223 -0
  4. data/doc/dataset_filtering.rdoc +158 -0
  5. data/doc/prepared_statements.rdoc +104 -0
  6. data/doc/release_notes/1.0.txt +38 -0
  7. data/doc/release_notes/1.1.txt +143 -0
  8. data/doc/release_notes/1.3.txt +101 -0
  9. data/doc/release_notes/1.4.0.txt +53 -0
  10. data/doc/release_notes/1.5.0.txt +155 -0
  11. data/doc/release_notes/2.0.0.txt +298 -0
  12. data/doc/release_notes/2.1.0.txt +271 -0
  13. data/doc/release_notes/2.10.0.txt +328 -0
  14. data/doc/release_notes/2.2.0.txt +253 -0
  15. data/doc/release_notes/2.3.0.txt +88 -0
  16. data/doc/release_notes/2.4.0.txt +106 -0
  17. data/doc/release_notes/2.5.0.txt +137 -0
  18. data/doc/release_notes/2.6.0.txt +157 -0
  19. data/doc/release_notes/2.7.0.txt +166 -0
  20. data/doc/release_notes/2.8.0.txt +171 -0
  21. data/doc/release_notes/2.9.0.txt +97 -0
  22. data/doc/schema.rdoc +29 -0
  23. data/doc/sharding.rdoc +113 -0
  24. data/lib/sequel.rb +1 -0
  25. data/lib/sequel_core/adapters/ado.rb +89 -0
  26. data/lib/sequel_core/adapters/db2.rb +143 -0
  27. data/lib/sequel_core/adapters/dbi.rb +112 -0
  28. data/lib/sequel_core/adapters/do/mysql.rb +38 -0
  29. data/lib/sequel_core/adapters/do/postgres.rb +92 -0
  30. data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
  31. data/lib/sequel_core/adapters/do.rb +205 -0
  32. data/lib/sequel_core/adapters/firebird.rb +298 -0
  33. data/lib/sequel_core/adapters/informix.rb +85 -0
  34. data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
  35. data/lib/sequel_core/adapters/jdbc/mysql.rb +66 -0
  36. data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
  37. data/lib/sequel_core/adapters/jdbc/postgresql.rb +113 -0
  38. data/lib/sequel_core/adapters/jdbc/sqlite.rb +43 -0
  39. data/lib/sequel_core/adapters/jdbc.rb +491 -0
  40. data/lib/sequel_core/adapters/mysql.rb +369 -0
  41. data/lib/sequel_core/adapters/odbc.rb +174 -0
  42. data/lib/sequel_core/adapters/openbase.rb +68 -0
  43. data/lib/sequel_core/adapters/oracle.rb +107 -0
  44. data/lib/sequel_core/adapters/postgres.rb +456 -0
  45. data/lib/sequel_core/adapters/shared/ms_access.rb +110 -0
  46. data/lib/sequel_core/adapters/shared/mssql.rb +102 -0
  47. data/lib/sequel_core/adapters/shared/mysql.rb +325 -0
  48. data/lib/sequel_core/adapters/shared/oracle.rb +61 -0
  49. data/lib/sequel_core/adapters/shared/postgres.rb +715 -0
  50. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  51. data/lib/sequel_core/adapters/shared/sqlite.rb +265 -0
  52. data/lib/sequel_core/adapters/sqlite.rb +248 -0
  53. data/lib/sequel_core/connection_pool.rb +258 -0
  54. data/lib/sequel_core/core_ext.rb +217 -0
  55. data/lib/sequel_core/core_sql.rb +202 -0
  56. data/lib/sequel_core/database/schema.rb +164 -0
  57. data/lib/sequel_core/database.rb +691 -0
  58. data/lib/sequel_core/dataset/callback.rb +13 -0
  59. data/lib/sequel_core/dataset/convenience.rb +237 -0
  60. data/lib/sequel_core/dataset/pagination.rb +96 -0
  61. data/lib/sequel_core/dataset/prepared_statements.rb +220 -0
  62. data/lib/sequel_core/dataset/query.rb +41 -0
  63. data/lib/sequel_core/dataset/schema.rb +15 -0
  64. data/lib/sequel_core/dataset/sql.rb +1010 -0
  65. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  66. data/lib/sequel_core/dataset/unsupported.rb +43 -0
  67. data/lib/sequel_core/dataset.rb +511 -0
  68. data/lib/sequel_core/deprecated.rb +26 -0
  69. data/lib/sequel_core/exceptions.rb +44 -0
  70. data/lib/sequel_core/migration.rb +212 -0
  71. data/lib/sequel_core/object_graph.rb +230 -0
  72. data/lib/sequel_core/pretty_table.rb +71 -0
  73. data/lib/sequel_core/schema/generator.rb +320 -0
  74. data/lib/sequel_core/schema/sql.rb +325 -0
  75. data/lib/sequel_core/schema.rb +2 -0
  76. data/lib/sequel_core/sql.rb +887 -0
  77. data/lib/sequel_core/version.rb +11 -0
  78. data/lib/sequel_core.rb +172 -0
  79. data/lib/sequel_model/association_reflection.rb +267 -0
  80. data/lib/sequel_model/associations.rb +499 -0
  81. data/lib/sequel_model/base.rb +523 -0
  82. data/lib/sequel_model/caching.rb +82 -0
  83. data/lib/sequel_model/dataset_methods.rb +26 -0
  84. data/lib/sequel_model/eager_loading.rb +370 -0
  85. data/lib/sequel_model/exceptions.rb +7 -0
  86. data/lib/sequel_model/hooks.rb +101 -0
  87. data/lib/sequel_model/inflector.rb +281 -0
  88. data/lib/sequel_model/plugins.rb +62 -0
  89. data/lib/sequel_model/record.rb +568 -0
  90. data/lib/sequel_model/schema.rb +49 -0
  91. data/lib/sequel_model/validations.rb +429 -0
  92. data/lib/sequel_model.rb +91 -0
  93. data/spec/adapters/ado_spec.rb +46 -0
  94. data/spec/adapters/firebird_spec.rb +376 -0
  95. data/spec/adapters/informix_spec.rb +96 -0
  96. data/spec/adapters/mysql_spec.rb +881 -0
  97. data/spec/adapters/oracle_spec.rb +244 -0
  98. data/spec/adapters/postgres_spec.rb +687 -0
  99. data/spec/adapters/spec_helper.rb +10 -0
  100. data/spec/adapters/sqlite_spec.rb +555 -0
  101. data/spec/integration/dataset_test.rb +134 -0
  102. data/spec/integration/eager_loader_test.rb +696 -0
  103. data/spec/integration/prepared_statement_test.rb +130 -0
  104. data/spec/integration/schema_test.rb +180 -0
  105. data/spec/integration/spec_helper.rb +58 -0
  106. data/spec/integration/type_test.rb +96 -0
  107. data/spec/rcov.opts +6 -0
  108. data/spec/sequel_core/connection_pool_spec.rb +526 -0
  109. data/spec/sequel_core/core_ext_spec.rb +156 -0
  110. data/spec/sequel_core/core_sql_spec.rb +522 -0
  111. data/spec/sequel_core/database_spec.rb +1188 -0
  112. data/spec/sequel_core/dataset_spec.rb +3481 -0
  113. data/spec/sequel_core/expression_filters_spec.rb +363 -0
  114. data/spec/sequel_core/migration_spec.rb +261 -0
  115. data/spec/sequel_core/object_graph_spec.rb +272 -0
  116. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  117. data/spec/sequel_core/schema_generator_spec.rb +167 -0
  118. data/spec/sequel_core/schema_spec.rb +780 -0
  119. data/spec/sequel_core/spec_helper.rb +55 -0
  120. data/spec/sequel_core/version_spec.rb +7 -0
  121. data/spec/sequel_model/association_reflection_spec.rb +93 -0
  122. data/spec/sequel_model/associations_spec.rb +1767 -0
  123. data/spec/sequel_model/base_spec.rb +419 -0
  124. data/spec/sequel_model/caching_spec.rb +215 -0
  125. data/spec/sequel_model/dataset_methods_spec.rb +78 -0
  126. data/spec/sequel_model/eager_loading_spec.rb +1165 -0
  127. data/spec/sequel_model/hooks_spec.rb +485 -0
  128. data/spec/sequel_model/inflector_spec.rb +119 -0
  129. data/spec/sequel_model/model_spec.rb +588 -0
  130. data/spec/sequel_model/plugins_spec.rb +80 -0
  131. data/spec/sequel_model/record_spec.rb +1184 -0
  132. data/spec/sequel_model/schema_spec.rb +90 -0
  133. data/spec/sequel_model/spec_helper.rb +78 -0
  134. data/spec/sequel_model/validations_spec.rb +1067 -0
  135. data/spec/spec.opts +0 -0
  136. data/spec/spec_config.rb.example +10 -0
  137. metadata +177 -3
@@ -0,0 +1,881 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ unless defined?(MYSQL_USER)
4
+ MYSQL_USER = 'root'
5
+ end
6
+ unless defined?(MYSQL_DB)
7
+ MYSQL_URL = (ENV['SEQUEL_MY_SPEC_DB']||"mysql://#{MYSQL_USER}@localhost/sandbox") unless defined? MYSQL_URL
8
+ MYSQL_DB = Sequel.connect(MYSQL_URL)
9
+ end
10
+ unless defined?(MYSQL_SOCKET_FILE)
11
+ MYSQL_SOCKET_FILE = '/tmp/mysql.sock'
12
+ end
13
+
14
+ MYSQL_URI = URI.parse(MYSQL_DB.uri)
15
+
16
+ MYSQL_DB.create_table! :items do
17
+ text :name
18
+ integer :value, :index => true
19
+ end
20
+ MYSQL_DB.create_table! :test2 do
21
+ text :name
22
+ integer :value
23
+ end
24
+ MYSQL_DB.create_table! :booltest do
25
+ tinyint :value
26
+ end
27
+ def MYSQL_DB.sqls
28
+ (@sqls ||= [])
29
+ end
30
+ logger = Object.new
31
+ def logger.method_missing(m, msg)
32
+ MYSQL_DB.sqls << msg
33
+ end
34
+ MYSQL_DB.logger = logger
35
+
36
+ if MYSQL_DB.class.adapter_scheme == :do
37
+ SQL_BEGIN = 'Transaction.begin'
38
+ SQL_ROLLBACK = 'Transaction.rollback'
39
+ SQL_COMMIT = 'Transaction.commit'
40
+ else
41
+ SQL_BEGIN = 'BEGIN'
42
+ SQL_ROLLBACK = 'ROLLBACK'
43
+ SQL_COMMIT = 'COMMIT'
44
+ end
45
+
46
+ context "MySQL", '#create_table' do
47
+ setup do
48
+ @db = MYSQL_DB
49
+ end
50
+ after(:each) do
51
+ @db.drop_table :dolls
52
+ end
53
+ specify "should allow to specify options for MySQL" do
54
+ @db.create_table(:dolls, :engine => 'MyISAM', :charset => 'latin2') { text :name }
55
+ @db.sqls.should == ["CREATE TABLE dolls (name text) ENGINE=MyISAM DEFAULT CHARSET=latin2"]
56
+ end
57
+ end
58
+
59
+ context "A MySQL database" do
60
+ setup do
61
+ @db = MYSQL_DB
62
+ end
63
+ teardown do
64
+ Sequel.convert_tinyint_to_bool = true
65
+ end
66
+
67
+ specify "should provide the server version" do
68
+ @db.server_version.should >= 40000
69
+ end
70
+
71
+ specify "should support sequential primary keys" do
72
+ @db.create_table!(:with_pk) {primary_key :id; text :name}
73
+ @db[:with_pk] << {:name => 'abc'}
74
+ @db[:with_pk] << {:name => 'def'}
75
+ @db[:with_pk] << {:name => 'ghi'}
76
+ @db[:with_pk].order(:name).all.should == [
77
+ {:id => 1, :name => 'abc'},
78
+ {:id => 2, :name => 'def'},
79
+ {:id => 3, :name => 'ghi'}
80
+ ]
81
+ end
82
+
83
+ specify "should provide disconnect functionality" do
84
+ @db.pool.size.should == 1
85
+ @db.disconnect
86
+ @db.pool.size.should == 0
87
+ end
88
+
89
+ specify "should convert Mysql::Errors to Sequel::Errors" do
90
+ proc{@db << "SELECT 1 + blah;"}.should raise_error(Sequel::Error)
91
+ end
92
+
93
+ specify "should correctly parse the schema" do
94
+ @db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:boolean, :allow_null=>true, :primary_key=>false, :default=>nil, :db_type=>"tinyint(4)"}]]
95
+
96
+ Sequel.convert_tinyint_to_bool = false
97
+ @db.schema(:booltest, :reload=>true).should == [[:value, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :db_type=>"tinyint(4)"}]]
98
+ end
99
+
100
+ specify "should get the schema all database tables if no table name is used" do
101
+ @db.schema(:booltest, :reload=>true).should == @db.schema(nil, :reload=>true)[:booltest]
102
+ end
103
+ end
104
+
105
+ context "A MySQL dataset" do
106
+ setup do
107
+ @d = MYSQL_DB[:items]
108
+ @d.delete # remove all records
109
+ MYSQL_DB.sqls.clear
110
+ end
111
+
112
+ specify "should return the correct record count" do
113
+ @d.count.should == 0
114
+ @d << {:name => 'abc', :value => 123}
115
+ @d << {:name => 'abc', :value => 456}
116
+ @d << {:name => 'def', :value => 789}
117
+ @d.count.should == 3
118
+ end
119
+
120
+ specify "should return the correct records" do
121
+ @d.to_a.should == []
122
+ @d << {:name => 'abc', :value => 123}
123
+ @d << {:name => 'abc', :value => 456}
124
+ @d << {:name => 'def', :value => 789}
125
+
126
+ @d.order(:value).to_a.should == [
127
+ {:name => 'abc', :value => 123},
128
+ {:name => 'abc', :value => 456},
129
+ {:name => 'def', :value => 789}
130
+ ]
131
+ end
132
+
133
+ specify "should update records correctly" do
134
+ @d << {:name => 'abc', :value => 123}
135
+ @d << {:name => 'abc', :value => 456}
136
+ @d << {:name => 'def', :value => 789}
137
+ @d.filter(:name => 'abc').update(:value => 530)
138
+
139
+ # the third record should stay the same
140
+ # floating-point precision bullshit
141
+ @d[:name => 'def'][:value].should == 789
142
+ @d.filter(:value => 530).count.should == 2
143
+ end
144
+
145
+ specify "should delete records correctly" do
146
+ @d << {:name => 'abc', :value => 123}
147
+ @d << {:name => 'abc', :value => 456}
148
+ @d << {:name => 'def', :value => 789}
149
+ @d.filter(:name => 'abc').delete
150
+
151
+ @d.count.should == 1
152
+ @d.first[:name].should == 'def'
153
+ end
154
+
155
+ specify "should be able to literalize booleans" do
156
+ proc {@d.literal(true)}.should_not raise_error
157
+ proc {@d.literal(false)}.should_not raise_error
158
+ end
159
+
160
+ specify "should quote columns and tables using back-ticks if quoting identifiers" do
161
+ @d.quote_identifiers = true
162
+ @d.select(:name).sql.should == \
163
+ 'SELECT `name` FROM `items`'
164
+
165
+ @d.select('COUNT(*)'.lit).sql.should == \
166
+ 'SELECT COUNT(*) FROM `items`'
167
+
168
+ @d.select(:max.sql_function(:value)).sql.should == \
169
+ 'SELECT max(`value`) FROM `items`'
170
+
171
+ @d.select(:NOW.sql_function).sql.should == \
172
+ 'SELECT NOW() FROM `items`'
173
+
174
+ @d.select(:max.sql_function(:items__value)).sql.should == \
175
+ 'SELECT max(`items`.`value`) FROM `items`'
176
+
177
+ @d.order(:name.desc).sql.should == \
178
+ 'SELECT * FROM `items` ORDER BY `name` DESC'
179
+
180
+ @d.select('items.name AS item_name'.lit).sql.should == \
181
+ 'SELECT items.name AS item_name FROM `items`'
182
+
183
+ @d.select('`name`'.lit).sql.should == \
184
+ 'SELECT `name` FROM `items`'
185
+
186
+ @d.select('max(items.`name`) AS `max_name`'.lit).sql.should == \
187
+ 'SELECT max(items.`name`) AS `max_name` FROM `items`'
188
+
189
+ @d.select(:test.sql_function(:abc, 'hello')).sql.should == \
190
+ "SELECT test(`abc`, 'hello') FROM `items`"
191
+
192
+ @d.select(:test.sql_function(:abc__def, 'hello')).sql.should == \
193
+ "SELECT test(`abc`.`def`, 'hello') FROM `items`"
194
+
195
+ @d.select(:test.sql_function(:abc__def, 'hello').as(:x2)).sql.should == \
196
+ "SELECT test(`abc`.`def`, 'hello') AS `x2` FROM `items`"
197
+
198
+ @d.insert_sql(:value => 333).should == \
199
+ 'INSERT INTO `items` (`value`) VALUES (333)'
200
+
201
+ @d.insert_sql(:x => :y).should == \
202
+ 'INSERT INTO `items` (`x`) VALUES (`y`)'
203
+ end
204
+
205
+ specify "should quote fields correctly when reversing the order" do
206
+ @d.quote_identifiers = true
207
+ @d.reverse_order(:name).sql.should == \
208
+ 'SELECT * FROM `items` ORDER BY `name` DESC'
209
+
210
+ @d.reverse_order(:name.desc).sql.should == \
211
+ 'SELECT * FROM `items` ORDER BY `name` ASC'
212
+
213
+ @d.reverse_order(:name, :test.desc).sql.should == \
214
+ 'SELECT * FROM `items` ORDER BY `name` DESC, `test` ASC'
215
+
216
+ @d.reverse_order(:name.desc, :test).sql.should == \
217
+ 'SELECT * FROM `items` ORDER BY `name` ASC, `test` DESC'
218
+ end
219
+
220
+ specify "should support ORDER clause in UPDATE statements" do
221
+ @d.order(:name).update_sql(:value => 1).should == \
222
+ 'UPDATE items SET value = 1 ORDER BY name'
223
+ end
224
+
225
+ specify "should support LIMIT clause in UPDATE statements" do
226
+ @d.limit(10).update_sql(:value => 1).should == \
227
+ 'UPDATE items SET value = 1 LIMIT 10'
228
+ end
229
+
230
+ specify "should support transactions" do
231
+ MYSQL_DB.transaction do
232
+ @d << {:name => 'abc', :value => 1}
233
+ end
234
+
235
+ @d.count.should == 1
236
+ end
237
+
238
+ specify "should correctly rollback transactions" do
239
+ proc do
240
+ MYSQL_DB.transaction do
241
+ @d << {:name => 'abc'}
242
+ raise Interrupt, 'asdf'
243
+ end
244
+ end.should raise_error(Interrupt)
245
+
246
+ MYSQL_DB.sqls.should == [SQL_BEGIN, "INSERT INTO items (name) VALUES ('abc')", SQL_ROLLBACK]
247
+ end
248
+
249
+ specify "should handle returning inside of the block by committing" do
250
+ def MYSQL_DB.ret_commit
251
+ transaction do
252
+ self[:items] << {:name => 'abc'}
253
+ return
254
+ self[:items] << {:name => 'd'}
255
+ end
256
+ end
257
+ MYSQL_DB.ret_commit
258
+ MYSQL_DB.sqls.should == [SQL_BEGIN, "INSERT INTO items (name) VALUES ('abc')", SQL_COMMIT]
259
+ end
260
+
261
+ specify "should support regexps" do
262
+ @d << {:name => 'abc', :value => 1}
263
+ @d << {:name => 'bcd', :value => 2}
264
+ @d.filter(:name => /bc/).count.should == 2
265
+ @d.filter(:name => /^bc/).count.should == 1
266
+ end
267
+
268
+ specify "should correctly literalize strings with comment backslashes in them" do
269
+ @d.delete
270
+ proc {@d << {:name => ':\\'}}.should_not raise_error
271
+
272
+ @d.first[:name].should == ':\\'
273
+ end
274
+ end
275
+
276
+ context "MySQL datasets" do
277
+ setup do
278
+ @d = MYSQL_DB[:orders]
279
+ end
280
+ teardown do
281
+ Sequel.convert_tinyint_to_bool = true
282
+ end
283
+
284
+ specify "should correctly quote column references" do
285
+ @d.quote_identifiers = true
286
+ market = 'ICE'
287
+ ack_stamp = Time.now - 15 * 60 # 15 minutes ago
288
+ @d.query do
289
+ select :market, :minute.sql_function(:from_unixtime.sql_function(:ack)).as(:minute)
290
+ where {(:ack.sql_number > ack_stamp) & {:market => market}}
291
+ group_by :minute.sql_function(:from_unixtime.sql_function(:ack))
292
+ end.sql.should == \
293
+ "SELECT `market`, minute(from_unixtime(`ack`)) AS `minute` FROM `orders` WHERE ((`ack` > #{@d.literal(ack_stamp)}) AND (`market` = 'ICE')) GROUP BY minute(from_unixtime(`ack`))"
294
+ end
295
+
296
+ specify "should accept and return tinyints as bools or integers when configured to do so" do
297
+ MYSQL_DB[:booltest].delete
298
+ MYSQL_DB[:booltest] << {:value=>true}
299
+ MYSQL_DB[:booltest].all.should == [{:value=>true}]
300
+ MYSQL_DB[:booltest].delete
301
+ MYSQL_DB[:booltest] << {:value=>false}
302
+ MYSQL_DB[:booltest].all.should == [{:value=>false}]
303
+
304
+ Sequel.convert_tinyint_to_bool = false
305
+ MYSQL_DB[:booltest].delete
306
+ MYSQL_DB[:booltest] << {:value=>true}
307
+ MYSQL_DB[:booltest].all.should == [{:value=>1}]
308
+ MYSQL_DB[:booltest].delete
309
+ MYSQL_DB[:booltest] << {:value=>false}
310
+ MYSQL_DB[:booltest].all.should == [{:value=>0}]
311
+
312
+ MYSQL_DB[:booltest].delete
313
+ MYSQL_DB[:booltest] << {:value=>1}
314
+ MYSQL_DB[:booltest].all.should == [{:value=>1}]
315
+ MYSQL_DB[:booltest].delete
316
+ MYSQL_DB[:booltest] << {:value=>0}
317
+ MYSQL_DB[:booltest].all.should == [{:value=>0}]
318
+ end
319
+ end
320
+
321
+ context "MySQL join expressions" do
322
+ setup do
323
+ @ds = MYSQL_DB[:nodes]
324
+ @ds.db.meta_def(:server_version) {50014}
325
+ end
326
+
327
+ specify "should raise error for :full_outer join requests." do
328
+ lambda{@ds.join_table(:full_outer, :nodes)}.should raise_error(Sequel::Error)
329
+ end
330
+ specify "should support natural left joins" do
331
+ @ds.join_table(:natural_left, :nodes).sql.should == \
332
+ 'SELECT * FROM nodes NATURAL LEFT JOIN nodes'
333
+ end
334
+ specify "should support natural right joins" do
335
+ @ds.join_table(:natural_right, :nodes).sql.should == \
336
+ 'SELECT * FROM nodes NATURAL RIGHT JOIN nodes'
337
+ end
338
+ specify "should support natural left outer joins" do
339
+ @ds.join_table(:natural_left_outer, :nodes).sql.should == \
340
+ 'SELECT * FROM nodes NATURAL LEFT OUTER JOIN nodes'
341
+ end
342
+ specify "should support natural right outer joins" do
343
+ @ds.join_table(:natural_right_outer, :nodes).sql.should == \
344
+ 'SELECT * FROM nodes NATURAL RIGHT OUTER JOIN nodes'
345
+ end
346
+ specify "should support natural inner joins" do
347
+ @ds.join_table(:natural_inner, :nodes).sql.should == \
348
+ 'SELECT * FROM nodes NATURAL LEFT JOIN nodes'
349
+ end
350
+ specify "should support cross joins" do
351
+ @ds.join_table(:cross, :nodes).sql.should == \
352
+ 'SELECT * FROM nodes CROSS JOIN nodes'
353
+ end
354
+ specify "should support cross joins as inner joins if conditions are used" do
355
+ @ds.join_table(:cross, :nodes, :id=>:id).sql.should == \
356
+ 'SELECT * FROM nodes INNER JOIN nodes ON (nodes.id = nodes.id)'
357
+ end
358
+ specify "should support straight joins (force left table to be read before right)" do
359
+ @ds.join_table(:straight, :nodes).sql.should == \
360
+ 'SELECT * FROM nodes STRAIGHT_JOIN nodes'
361
+ end
362
+ specify "should support natural joins on multiple tables." do
363
+ @ds.join_table(:natural_left_outer, [:nodes, :branches]).sql.should == \
364
+ 'SELECT * FROM nodes NATURAL LEFT OUTER JOIN (nodes, branches)'
365
+ end
366
+ specify "should support straight joins on multiple tables." do
367
+ @ds.join_table(:straight, [:nodes,:branches]).sql.should == \
368
+ 'SELECT * FROM nodes STRAIGHT_JOIN (nodes, branches)'
369
+ end
370
+ end
371
+
372
+ context "Joined MySQL dataset" do
373
+ setup do
374
+ @ds = MYSQL_DB[:nodes]
375
+ end
376
+
377
+ specify "should quote fields correctly" do
378
+ @ds.quote_identifiers = true
379
+ @ds.join(:attributes, :node_id => :id).sql.should == \
380
+ "SELECT * FROM `nodes` INNER JOIN `attributes` ON (`attributes`.`node_id` = `nodes`.`id`)"
381
+ end
382
+
383
+ specify "should allow a having clause on ungrouped datasets" do
384
+ proc {@ds.having('blah')}.should_not raise_error
385
+
386
+ @ds.having('blah').sql.should == \
387
+ "SELECT * FROM nodes HAVING (blah)"
388
+ end
389
+
390
+ specify "should put a having clause before an order by clause" do
391
+ @ds.order(:aaa).having(:bbb => :ccc).sql.should == \
392
+ "SELECT * FROM nodes HAVING (bbb = ccc) ORDER BY aaa"
393
+ end
394
+ end
395
+
396
+ context "A MySQL database" do
397
+ setup do
398
+ @db = MYSQL_DB
399
+ end
400
+
401
+ specify "should support add_column operations" do
402
+ @db.add_column :test2, :xyz, :text
403
+
404
+ @db[:test2].columns.should == [:name, :value, :xyz]
405
+ @db[:test2] << {:name => 'mmm', :value => 111, :xyz => '000'}
406
+ @db[:test2].first[:xyz].should == '000'
407
+ end
408
+
409
+ specify "should support drop_column operations" do
410
+ @db[:test2].columns.should == [:name, :value, :xyz]
411
+ @db.drop_column :test2, :xyz
412
+
413
+ @db[:test2].columns.should == [:name, :value]
414
+ end
415
+
416
+ specify "should support rename_column operations" do
417
+ @db[:test2].delete
418
+ @db.add_column :test2, :xyz, :text
419
+ @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 'qqqq'}
420
+
421
+ @db[:test2].columns.should == [:name, :value, :xyz]
422
+ @db.rename_column :test2, :xyz, :zyx, :type => :text
423
+ @db[:test2].columns.should == [:name, :value, :zyx]
424
+ @db[:test2].first[:zyx].should == 'qqqq'
425
+ end
426
+
427
+ specify "should support rename_column operations with types like varchar(255)" do
428
+ @db[:test2].delete
429
+ @db.add_column :test2, :tre, :text
430
+ @db[:test2] << {:name => 'mmm', :value => 111, :tre => 'qqqq'}
431
+
432
+ @db[:test2].columns.should == [:name, :value, :zyx, :tre]
433
+ @db.rename_column :test2, :tre, :ert, :type => :varchar, :size=>255
434
+ @db[:test2].columns.should == [:name, :value, :zyx, :ert]
435
+ @db[:test2].first[:ert].should == 'qqqq'
436
+ end
437
+
438
+ specify "should support set_column_type operations" do
439
+ @db.add_column :test2, :xyz, :float
440
+ @db[:test2].delete
441
+ @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
442
+ @db.set_column_type :test2, :xyz, :integer
443
+
444
+ @db[:test2].first[:xyz].should == 57
445
+ end
446
+
447
+ specify "should support add_index" do
448
+ @db.add_index :test2, :value
449
+ end
450
+
451
+ specify "should support drop_index" do
452
+ @db.drop_index :test2, :value
453
+ end
454
+
455
+ specify "should support add_foreign_key" do
456
+ @db.alter_table :test2 do
457
+ add_foreign_key :value2, :test2, :key=>:value
458
+ end
459
+ @db[:test2].columns.should == [:name, :value, :zyx, :ert, :xyz, :value2]
460
+ end
461
+ end
462
+
463
+ context "A MySQL database", "with table options" do
464
+ before(:all) do
465
+ @options = {}
466
+ @options[:engine] = 'MyISAM'
467
+ @options[:charset] = 'latin2'
468
+ @options[:collate] = 'swedish'
469
+
470
+ Sequel::MySQL.default_engine = 'InnoDB'
471
+ Sequel::MySQL.default_charset = 'utf8'
472
+ Sequel::MySQL.default_collate = 'utf8'
473
+
474
+ @db = MYSQL_DB
475
+ @g = Sequel::Schema::Generator.new(@db) do
476
+ integer :size
477
+ text :name
478
+ end
479
+ end
480
+
481
+ after(:all) do
482
+ Sequel::MySQL.default_engine = nil
483
+ Sequel::MySQL.default_charset = nil
484
+ Sequel::MySQL.default_collate = nil
485
+ end
486
+
487
+ specify "should allow to pass custom options (engine, charset, collate) for table creation" do
488
+ statements = @db.create_table_sql_list(:items, *(@g.create_info << @options))
489
+ statements.should == [
490
+ "CREATE TABLE items (size integer, name text) ENGINE=MyISAM DEFAULT CHARSET=latin2 DEFAULT COLLATE=swedish"
491
+ ]
492
+ end
493
+
494
+ specify "should use default options if specified (engine, charset, collate) for table creation" do
495
+ statements = @db.create_table_sql_list(:items, *(@g.create_info))
496
+ statements.should == [
497
+ "CREATE TABLE items (size integer, name text) ENGINE=InnoDB DEFAULT CHARSET=utf8 DEFAULT COLLATE=utf8"
498
+ ]
499
+ end
500
+
501
+ specify "should not use default if option has a nil value" do
502
+ statements = @db.create_table_sql_list(:items, *(@g.create_info << {:engine=>nil, :charset=>nil, :collate=>nil}))
503
+ statements.should == [
504
+ "CREATE TABLE items (size integer, name text)"
505
+ ]
506
+ end
507
+ end
508
+
509
+ context "A MySQL database" do
510
+ setup do
511
+ @db = MYSQL_DB
512
+ end
513
+
514
+ specify "should support defaults for boolean columns" do
515
+ g = Sequel::Schema::Generator.new(@db) do
516
+ boolean :active1, :default => true
517
+ boolean :active2, :default => false
518
+ end
519
+ statements = @db.create_table_sql_list(:items, *g.create_info)
520
+ statements.should == [
521
+ "CREATE TABLE items (active1 boolean DEFAULT 1, active2 boolean DEFAULT 0)"
522
+ ]
523
+ end
524
+
525
+ specify "should correctly format CREATE TABLE statements with foreign keys" do
526
+ g = Sequel::Schema::Generator.new(@db) do
527
+ foreign_key :p_id, :table => :users, :key => :id,
528
+ :null => false, :on_delete => :cascade
529
+ end
530
+ @db.create_table_sql_list(:items, *g.create_info).should == [
531
+ "CREATE TABLE items (p_id integer NOT NULL, FOREIGN KEY (p_id) REFERENCES users(id) ON DELETE CASCADE)"
532
+ ]
533
+ end
534
+
535
+ specify "should correctly format ALTER TABLE statements with foreign keys" do
536
+ g = Sequel::Schema::AlterTableGenerator.new(@db) do
537
+ add_foreign_key :p_id, :users, :key => :id, :null => false, :on_delete => :cascade
538
+ end
539
+ @db.alter_table_sql_list(:items, g.operations).should == [[
540
+ "ALTER TABLE items ADD COLUMN p_id integer NOT NULL",
541
+ "ALTER TABLE items ADD FOREIGN KEY (p_id) REFERENCES users(id) ON DELETE CASCADE"
542
+ ]]
543
+ end
544
+
545
+ specify "should accept repeated raw sql statements using Database#<<" do
546
+ @db << 'DELETE FROM items'
547
+ @db[:items].count.should == 0
548
+
549
+ @db << "INSERT INTO items (name, value) VALUES ('tutu', 1234)"
550
+ @db[:items].first.should == {:name => 'tutu', :value => 1234}
551
+
552
+ @db << 'DELETE FROM items'
553
+ @db[:items].first.should == nil
554
+ end
555
+
556
+ specify "should handle multiple select statements at once" do
557
+ @db << 'DELETE FROM items; '
558
+
559
+ @db[:items].delete
560
+ @db[:items].insert(:name => 'tutu', :value => 1234)
561
+ @db["SELECT * FROM items; SELECT * FROM items"].all.should == \
562
+ [{:name => 'tutu', :value => 1234}, {:name => 'tutu', :value => 1234}]
563
+ end
564
+ end
565
+
566
+ # Socket tests should only be run if the MySQL server is on localhost
567
+ if %w'localhost 127.0.0.1 ::1'.include?(MYSQL_URI.host) and MYSQL_DB.class.adapter_scheme == :mysql
568
+ context "A MySQL database" do
569
+ specify "should accept a socket option" do
570
+ db = Sequel.mysql(MYSQL_DB.opts[:database], :host => 'localhost', :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
571
+ proc {db.test_connection}.should_not raise_error
572
+ end
573
+
574
+ specify "should accept a socket option without host option" do
575
+ db = Sequel.mysql(MYSQL_DB.opts[:database], :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
576
+ proc {db.test_connection}.should_not raise_error
577
+ end
578
+
579
+ specify "should fail to connect with invalid socket" do
580
+ db = Sequel.mysql(MYSQL_DB.opts[:database], :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket =>'blah')
581
+ proc {db.test_connection}.should raise_error
582
+ end
583
+ end
584
+ end
585
+
586
+ context "A grouped MySQL dataset" do
587
+ setup do
588
+ MYSQL_DB[:test2].delete
589
+ MYSQL_DB[:test2] << {:name => '11', :value => 10}
590
+ MYSQL_DB[:test2] << {:name => '11', :value => 20}
591
+ MYSQL_DB[:test2] << {:name => '11', :value => 30}
592
+ MYSQL_DB[:test2] << {:name => '12', :value => 10}
593
+ MYSQL_DB[:test2] << {:name => '12', :value => 20}
594
+ MYSQL_DB[:test2] << {:name => '13', :value => 10}
595
+ end
596
+
597
+ specify "should return the correct count for raw sql query" do
598
+ ds = MYSQL_DB["select name FROM test2 WHERE name = '11' GROUP BY name"]
599
+ ds.count.should == 1
600
+ end
601
+
602
+ specify "should return the correct count for a normal dataset" do
603
+ ds = MYSQL_DB[:test2].select(:name).where(:name => '11').group(:name)
604
+ ds.count.should == 1
605
+ end
606
+ end
607
+
608
+ context "A MySQL database" do
609
+ setup do
610
+ end
611
+
612
+ specify "should support fulltext indexes" do
613
+ g = Sequel::Schema::Generator.new(MYSQL_DB) do
614
+ text :title
615
+ text :body
616
+ full_text_index [:title, :body]
617
+ end
618
+ MYSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
619
+ "CREATE TABLE posts (title text, body text)",
620
+ "CREATE FULLTEXT INDEX posts_title_body_index ON posts (title, body)"
621
+ ]
622
+ end
623
+
624
+ specify "should support full_text_search" do
625
+ MYSQL_DB[:posts].full_text_search(:title, 'ruby').sql.should ==
626
+ "SELECT * FROM posts WHERE (MATCH (title) AGAINST ('ruby'))"
627
+
628
+ MYSQL_DB[:posts].full_text_search([:title, :body], ['ruby', 'sequel']).sql.should ==
629
+ "SELECT * FROM posts WHERE (MATCH (title, body) AGAINST ('ruby', 'sequel'))"
630
+
631
+ MYSQL_DB[:posts].full_text_search(:title, '+ruby -rails', :boolean => true).sql.should ==
632
+ "SELECT * FROM posts WHERE (MATCH (title) AGAINST ('+ruby -rails' IN BOOLEAN MODE))"
633
+ end
634
+
635
+ specify "should support spatial indexes" do
636
+ g = Sequel::Schema::Generator.new(MYSQL_DB) do
637
+ point :geom
638
+ spatial_index [:geom]
639
+ end
640
+ MYSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
641
+ "CREATE TABLE posts (geom point)",
642
+ "CREATE SPATIAL INDEX posts_geom_index ON posts (geom)"
643
+ ]
644
+ end
645
+
646
+ specify "should support indexes with index type" do
647
+ g = Sequel::Schema::Generator.new(MYSQL_DB) do
648
+ text :title
649
+ index :title, :type => :hash
650
+ end
651
+ MYSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
652
+ "CREATE TABLE posts (title text)",
653
+ "CREATE INDEX posts_title_index ON posts (title) USING hash"
654
+ ]
655
+ end
656
+
657
+ specify "should support unique indexes with index type" do
658
+ g = Sequel::Schema::Generator.new(MYSQL_DB) do
659
+ text :title
660
+ index :title, :type => :hash, :unique => true
661
+ end
662
+ MYSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
663
+ "CREATE TABLE posts (title text)",
664
+ "CREATE UNIQUE INDEX posts_title_index ON posts (title) USING hash"
665
+ ]
666
+ end
667
+ end
668
+
669
+ context "MySQL::Dataset#insert" do
670
+ setup do
671
+ @d = MYSQL_DB[:items]
672
+ @d.delete # remove all records
673
+ MYSQL_DB.sqls.clear
674
+ end
675
+
676
+ specify "should insert record with default values when no arguments given" do
677
+ @d.insert
678
+
679
+ MYSQL_DB.sqls.should == [
680
+ "INSERT INTO items () VALUES ()"
681
+ ]
682
+
683
+ @d.all.should == [
684
+ {:name => nil, :value => nil}
685
+ ]
686
+ end
687
+
688
+ specify "should insert record with default values when empty hash given" do
689
+ @d.insert({})
690
+
691
+ MYSQL_DB.sqls.should == [
692
+ "INSERT INTO items () VALUES ()"
693
+ ]
694
+
695
+ @d.all.should == [
696
+ {:name => nil, :value => nil}
697
+ ]
698
+ end
699
+
700
+ specify "should insert record with default values when empty array given" do
701
+ @d.insert []
702
+
703
+ MYSQL_DB.sqls.should == [
704
+ "INSERT INTO items () VALUES ()"
705
+ ]
706
+
707
+ @d.all.should == [
708
+ {:name => nil, :value => nil}
709
+ ]
710
+ end
711
+ end
712
+
713
+ context "MySQL::Dataset#multi_insert" do
714
+ setup do
715
+ @d = MYSQL_DB[:items]
716
+ @d.delete # remove all records
717
+ MYSQL_DB.sqls.clear
718
+ end
719
+
720
+ specify "should insert multiple records in a single statement" do
721
+ @d.multi_insert([{:name => 'abc'}, {:name => 'def'}])
722
+
723
+ MYSQL_DB.sqls.should == [
724
+ SQL_BEGIN,
725
+ "INSERT INTO items (name) VALUES ('abc'), ('def')",
726
+ SQL_COMMIT
727
+ ]
728
+
729
+ @d.all.should == [
730
+ {:name => 'abc', :value => nil}, {:name => 'def', :value => nil}
731
+ ]
732
+ end
733
+
734
+ specify "should split the list of records into batches if :commit_every option is given" do
735
+ @d.multi_insert([{:value => 1}, {:value => 2}, {:value => 3}, {:value => 4}],
736
+ :commit_every => 2)
737
+
738
+ MYSQL_DB.sqls.should == [
739
+ SQL_BEGIN,
740
+ "INSERT INTO items (value) VALUES (1), (2)",
741
+ SQL_COMMIT,
742
+ SQL_BEGIN,
743
+ "INSERT INTO items (value) VALUES (3), (4)",
744
+ SQL_COMMIT
745
+ ]
746
+
747
+ @d.all.should == [
748
+ {:name => nil, :value => 1},
749
+ {:name => nil, :value => 2},
750
+ {:name => nil, :value => 3},
751
+ {:name => nil, :value => 4}
752
+ ]
753
+ end
754
+
755
+ specify "should split the list of records into batches if :slice option is given" do
756
+ @d.multi_insert([{:value => 1}, {:value => 2}, {:value => 3}, {:value => 4}],
757
+ :slice => 2)
758
+
759
+ MYSQL_DB.sqls.should == [
760
+ SQL_BEGIN,
761
+ "INSERT INTO items (value) VALUES (1), (2)",
762
+ SQL_COMMIT,
763
+ SQL_BEGIN,
764
+ "INSERT INTO items (value) VALUES (3), (4)",
765
+ SQL_COMMIT
766
+ ]
767
+
768
+ @d.all.should == [
769
+ {:name => nil, :value => 1},
770
+ {:name => nil, :value => 2},
771
+ {:name => nil, :value => 3},
772
+ {:name => nil, :value => 4}
773
+ ]
774
+ end
775
+
776
+ specify "should support inserting using columns and values arrays" do
777
+ @d.multi_insert([:name, :value], [['abc', 1], ['def', 2]])
778
+
779
+ MYSQL_DB.sqls.should == [
780
+ SQL_BEGIN,
781
+ "INSERT INTO items (name, value) VALUES ('abc', 1), ('def', 2)",
782
+ SQL_COMMIT
783
+ ]
784
+
785
+ @d.all.should == [
786
+ {:name => 'abc', :value => 1},
787
+ {:name => 'def', :value => 2}
788
+ ]
789
+ end
790
+ end
791
+
792
+ context "MySQL::Dataset#replace" do
793
+ setup do
794
+ MYSQL_DB.drop_table(:items) if MYSQL_DB.table_exists?(:items)
795
+ MYSQL_DB.create_table :items do
796
+ integer :id, :unique => true
797
+ integer :value, :index => true
798
+ end
799
+ @d = MYSQL_DB[:items]
800
+ MYSQL_DB.sqls.clear
801
+ end
802
+
803
+ specify "should create a record if the condition is not met" do
804
+ @d.replace(:id => 111, :value => 333)
805
+ @d.all.should == [{:id => 111, :value => 333}]
806
+ end
807
+
808
+ specify "should update a record if the condition is met" do
809
+ @d << {:id => 111}
810
+ @d.all.should == [{:id => 111, :value => nil}]
811
+ @d.replace(:id => 111, :value => 333)
812
+ @d.all.should == [{:id => 111, :value => 333}]
813
+ end
814
+ end
815
+
816
+ context "MySQL::Dataset#complex_expression_sql" do
817
+ setup do
818
+ @d = MYSQL_DB.dataset
819
+ end
820
+
821
+ specify "should handle pattern matches correctly" do
822
+ @d.literal(:x.like('a')).should == "(x LIKE BINARY 'a')"
823
+ @d.literal(~:x.like('a')).should == "(x NOT LIKE BINARY 'a')"
824
+ @d.literal(:x.ilike('a')).should == "(x LIKE 'a')"
825
+ @d.literal(~:x.ilike('a')).should == "(x NOT LIKE 'a')"
826
+ @d.literal(:x.like(/a/)).should == "(x REGEXP BINARY 'a')"
827
+ @d.literal(~:x.like(/a/)).should == "(x NOT REGEXP BINARY 'a')"
828
+ @d.literal(:x.like(/a/i)).should == "(x REGEXP 'a')"
829
+ @d.literal(~:x.like(/a/i)).should == "(x NOT REGEXP 'a')"
830
+ end
831
+
832
+ specify "should handle string concatenation with CONCAT if more than one record" do
833
+ @d.literal([:x, :y].sql_string_join).should == "CONCAT(x, y)"
834
+ @d.literal([:x, :y].sql_string_join(' ')).should == "CONCAT(x, ' ', y)"
835
+ @d.literal([:x.sql_function(:y), 1, 'z'.lit].sql_string_join(:y|1)).should == "CONCAT(x(y), y[1], '1', y[1], z)"
836
+ end
837
+
838
+ specify "should handle string concatenation as simple string if just one record" do
839
+ @d.literal([:x].sql_string_join).should == "x"
840
+ @d.literal([:x].sql_string_join(' ')).should == "x"
841
+ end
842
+ end
843
+
844
+ unless MYSQL_DB.class.adapter_scheme == :do
845
+ context "MySQL Stored Procedures" do
846
+ teardown do
847
+ MYSQL_DB.execute('DROP PROCEDURE test_sproc')
848
+ end
849
+
850
+ specify "should be callable on the database object" do
851
+ MYSQL_DB.execute('CREATE PROCEDURE test_sproc() BEGIN DELETE FROM items; END')
852
+ MYSQL_DB[:items].delete
853
+ MYSQL_DB[:items].insert(:value=>1)
854
+ MYSQL_DB[:items].count.should == 1
855
+ MYSQL_DB.call_sproc(:test_sproc)
856
+ MYSQL_DB[:items].count.should == 0
857
+ end
858
+
859
+ specify "should be callable on the dataset object" do
860
+ MYSQL_DB.execute('CREATE PROCEDURE test_sproc(a INTEGER) BEGIN SELECT *, a AS b FROM items; END')
861
+ MYSQL_DB[:items].delete
862
+ @d = MYSQL_DB[:items]
863
+ @d.call_sproc(:select, :test_sproc, 3).should == []
864
+ @d.insert(:value=>1)
865
+ @d.call_sproc(:select, :test_sproc, 4).should == [{:id=>nil, :value=>1, :b=>4}]
866
+ @d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
867
+ @d.call_sproc(:select, :test_sproc, 3).should == [{:id=>nil, :value=>2, :b=>6}]
868
+ end
869
+
870
+ specify "should be callable on the dataset object with multiple arguments" do
871
+ MYSQL_DB.execute('CREATE PROCEDURE test_sproc(a INTEGER, c INTEGER) BEGIN SELECT *, a AS b, c AS d FROM items; END')
872
+ MYSQL_DB[:items].delete
873
+ @d = MYSQL_DB[:items]
874
+ @d.call_sproc(:select, :test_sproc, 3, 4).should == []
875
+ @d.insert(:value=>1)
876
+ @d.call_sproc(:select, :test_sproc, 4, 5).should == [{:id=>nil, :value=>1, :b=>4, :d=>5}]
877
+ @d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
878
+ @d.call_sproc(:select, :test_sproc, 3, 4).should == [{:id=>nil, :value=>2, :b=>6, :d => 8}]
879
+ end
880
+ end
881
+ end