epugh-sequel 0.0.0

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