viking-sequel 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,1019 @@
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
+ INTEGRATION_DB = MYSQL_DB unless defined?(INTEGRATION_DB)
14
+
15
+ MYSQL_URI = URI.parse(MYSQL_DB.uri)
16
+
17
+ MYSQL_DB.create_table! :test2 do
18
+ text :name
19
+ integer :value
20
+ end
21
+ def MYSQL_DB.sqls
22
+ (@sqls ||= [])
23
+ end
24
+ logger = Object.new
25
+ def logger.method_missing(m, msg)
26
+ MYSQL_DB.sqls << msg
27
+ end
28
+ MYSQL_DB.logger = logger
29
+ MYSQL_DB.drop_table(:items) rescue nil
30
+ MYSQL_DB.drop_table(:dolls) rescue nil
31
+ MYSQL_DB.drop_table(:booltest) rescue nil
32
+
33
+ SQL_BEGIN = 'BEGIN'
34
+ SQL_ROLLBACK = 'ROLLBACK'
35
+ SQL_COMMIT = 'COMMIT'
36
+
37
+ context "MySQL", '#create_table' do
38
+ before do
39
+ @db = MYSQL_DB
40
+ MYSQL_DB.sqls.clear
41
+ end
42
+ after do
43
+ @db.drop_table(:dolls) rescue nil
44
+ end
45
+
46
+ specify "should allow to specify options for MySQL" do
47
+ @db.create_table(:dolls, :engine => 'MyISAM', :charset => 'latin2'){text :name}
48
+ @db.sqls.should == ["CREATE TABLE dolls (name text) ENGINE=MyISAM DEFAULT CHARSET=latin2"]
49
+ end
50
+
51
+ specify "should create a temporary table" do
52
+ @db.create_table(:tmp_dolls, :temp => true, :engine => 'MyISAM', :charset => 'latin2'){text :name}
53
+ @db.sqls.should == ["CREATE TEMPORARY TABLE tmp_dolls (name text) ENGINE=MyISAM DEFAULT CHARSET=latin2"]
54
+ end
55
+
56
+ specify "should not use a default for a String :text=>true type" do
57
+ @db.create_table(:dolls){String :name, :text=>true, :default=>'blah'}
58
+ @db.sqls.should == ["CREATE TABLE dolls (name text)"]
59
+ end
60
+
61
+ specify "should not use a default for a File type" do
62
+ @db.create_table(:dolls){File :name, :default=>'blah'}
63
+ @db.sqls.should == ["CREATE TABLE dolls (name blob)"]
64
+ end
65
+
66
+ specify "should respect the size option for File type" do
67
+ @db.create_table(:dolls) do
68
+ File :n1
69
+ File :n2, :size=>:tiny
70
+ File :n3, :size=>:medium
71
+ File :n4, :size=>:long
72
+ File :n5, :size=>255
73
+ end
74
+ @db.schema(:dolls).map{|k, v| v[:db_type]}.should == %w"blob tinyblob mediumblob longblob blob"
75
+ end
76
+ end
77
+
78
+ context "A MySQL database" do
79
+ specify "should provide the server version" do
80
+ MYSQL_DB.server_version.should >= 40000
81
+ end
82
+
83
+ specify "should handle the creation and dropping of an InnoDB table with foreign keys" do
84
+ proc{MYSQL_DB.create_table!(:test_innodb, :engine=>:InnoDB){primary_key :id; foreign_key :fk, :test_innodb, :key=>:id}}.should_not raise_error
85
+ end
86
+
87
+ specify "should support for_share" do
88
+ MYSQL_DB.transaction{MYSQL_DB[:test2].for_share.all.should == []}
89
+ end
90
+ end
91
+
92
+ if MYSQL_DB.class.adapter_scheme == :mysql
93
+ context "Sequel::MySQL.convert_tinyint_to_bool" do
94
+ before do
95
+ @db = MYSQL_DB
96
+ @db.create_table(:booltest){column :b, 'tinyint(1)'; column :i, 'tinyint(4)'}
97
+ @ds = @db[:booltest]
98
+ end
99
+ after do
100
+ Sequel::MySQL.convert_tinyint_to_bool = true
101
+ @db.drop_table(:booltest)
102
+ end
103
+
104
+ specify "should consider tinyint(1) datatypes as boolean if set, but not larger tinyints" do
105
+ @db.schema(:booltest, :reload=>true).should == [[:b, {:type=>:boolean, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(1)"}, ], [:i, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(4)"}, ]]
106
+ Sequel::MySQL.convert_tinyint_to_bool = false
107
+ @db.schema(:booltest, :reload=>true).should == [[:b, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(1)"}, ], [:i, {:type=>:integer, :allow_null=>true, :primary_key=>false, :default=>nil, :ruby_default=>nil, :db_type=>"tinyint(4)"}, ]]
108
+ end
109
+
110
+ specify "should return tinyints as bools when set" do
111
+ @ds.delete
112
+ @ds << {:b=>true, :i=>10}
113
+ @ds.all.should == [{:b=>true, :i=>true}]
114
+ @ds.delete
115
+ @ds << {:b=>false, :i=>0}
116
+ @ds.all.should == [{:b=>false, :i=>false}]
117
+
118
+ Sequel::MySQL.convert_tinyint_to_bool = false
119
+ @ds.delete
120
+ @ds << {:b=>true, :i=>10}
121
+ @ds.all.should == [{:b=>1, :i=>10}]
122
+ @ds.delete
123
+ @ds << {:b=>false, :i=>0}
124
+ @ds.all.should == [{:b=>0, :i=>0}]
125
+
126
+ @ds.delete
127
+ @ds << {:b=>1, :i=>10}
128
+ @ds.all.should == [{:b=>1, :i=>10}]
129
+ @ds.delete
130
+ @ds << {:b=>0, :i=>0}
131
+ @ds.all.should == [{:b=>0, :i=>0}]
132
+ end
133
+ end
134
+ end
135
+
136
+ context "A MySQL dataset" do
137
+ before do
138
+ MYSQL_DB.create_table(:items){String :name; Integer :value}
139
+ @d = MYSQL_DB[:items]
140
+ MYSQL_DB.sqls.clear
141
+ end
142
+ after do
143
+ MYSQL_DB.drop_table(:items)
144
+ end
145
+
146
+ specify "should quote columns and tables using back-ticks if quoting identifiers" do
147
+ @d.quote_identifiers = true
148
+ @d.select(:name).sql.should == \
149
+ 'SELECT `name` FROM `items`'
150
+
151
+ @d.select('COUNT(*)'.lit).sql.should == \
152
+ 'SELECT COUNT(*) FROM `items`'
153
+
154
+ @d.select(:max.sql_function(:value)).sql.should == \
155
+ 'SELECT max(`value`) FROM `items`'
156
+
157
+ @d.select(:NOW.sql_function).sql.should == \
158
+ 'SELECT NOW() FROM `items`'
159
+
160
+ @d.select(:max.sql_function(:items__value)).sql.should == \
161
+ 'SELECT max(`items`.`value`) FROM `items`'
162
+
163
+ @d.order(:name.desc).sql.should == \
164
+ 'SELECT * FROM `items` ORDER BY `name` DESC'
165
+
166
+ @d.select('items.name AS item_name'.lit).sql.should == \
167
+ 'SELECT items.name AS item_name FROM `items`'
168
+
169
+ @d.select('`name`'.lit).sql.should == \
170
+ 'SELECT `name` FROM `items`'
171
+
172
+ @d.select('max(items.`name`) AS `max_name`'.lit).sql.should == \
173
+ 'SELECT max(items.`name`) AS `max_name` FROM `items`'
174
+
175
+ @d.select(:test.sql_function(:abc, 'hello')).sql.should == \
176
+ "SELECT test(`abc`, 'hello') FROM `items`"
177
+
178
+ @d.select(:test.sql_function(:abc__def, 'hello')).sql.should == \
179
+ "SELECT test(`abc`.`def`, 'hello') FROM `items`"
180
+
181
+ @d.select(:test.sql_function(:abc__def, 'hello').as(:x2)).sql.should == \
182
+ "SELECT test(`abc`.`def`, 'hello') AS `x2` FROM `items`"
183
+
184
+ @d.insert_sql(:value => 333).should == \
185
+ 'INSERT INTO `items` (`value`) VALUES (333)'
186
+
187
+ @d.insert_sql(:x => :y).should == \
188
+ 'INSERT INTO `items` (`x`) VALUES (`y`)'
189
+ end
190
+
191
+ specify "should quote fields correctly when reversing the order" do
192
+ @d.quote_identifiers = true
193
+ @d.reverse_order(:name).sql.should == \
194
+ 'SELECT * FROM `items` ORDER BY `name` DESC'
195
+
196
+ @d.reverse_order(:name.desc).sql.should == \
197
+ 'SELECT * FROM `items` ORDER BY `name` ASC'
198
+
199
+ @d.reverse_order(:name, :test.desc).sql.should == \
200
+ 'SELECT * FROM `items` ORDER BY `name` DESC, `test` ASC'
201
+
202
+ @d.reverse_order(:name.desc, :test).sql.should == \
203
+ 'SELECT * FROM `items` ORDER BY `name` ASC, `test` DESC'
204
+ end
205
+
206
+ specify "should support ORDER clause in UPDATE statements" do
207
+ @d.order(:name).update_sql(:value => 1).should == \
208
+ 'UPDATE items SET value = 1 ORDER BY name'
209
+ end
210
+
211
+ specify "should support LIMIT clause in UPDATE statements" do
212
+ @d.limit(10).update_sql(:value => 1).should == \
213
+ 'UPDATE items SET value = 1 LIMIT 10'
214
+ end
215
+
216
+ specify "should support regexps" do
217
+ @d << {:name => 'abc', :value => 1}
218
+ @d << {:name => 'bcd', :value => 2}
219
+ @d.filter(:name => /bc/).count.should == 2
220
+ @d.filter(:name => /^bc/).count.should == 1
221
+ end
222
+
223
+ specify "should correctly literalize strings with comment backslashes in them" do
224
+ @d.delete
225
+ proc {@d << {:name => ':\\'}}.should_not raise_error
226
+
227
+ @d.first[:name].should == ':\\'
228
+ end
229
+ end
230
+
231
+ context "MySQL datasets" do
232
+ before do
233
+ @d = MYSQL_DB[:orders]
234
+ end
235
+
236
+ specify "should correctly quote column references" do
237
+ @d.quote_identifiers = true
238
+ market = 'ICE'
239
+ ack_stamp = Time.now - 15 * 60 # 15 minutes ago
240
+ @d.select(:market, :minute.sql_function(:from_unixtime.sql_function(:ack)).as(:minute)).
241
+ where{|o|(:ack.sql_number > ack_stamp) & {:market => market}}.
242
+ group_by(:minute.sql_function(:from_unixtime.sql_function(:ack))).sql.should == \
243
+ "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`))"
244
+ end
245
+ end
246
+
247
+ describe "Dataset#distinct" do
248
+ before do
249
+ @db = MYSQL_DB
250
+ @db.create_table!(:a) do
251
+ Integer :a
252
+ Integer :b
253
+ end
254
+ @ds = @db[:a]
255
+ end
256
+ after do
257
+ @db.drop_table(:a)
258
+ end
259
+
260
+ it "#distinct with arguments should return results distinct on those arguments" do
261
+ @ds.insert(20, 10)
262
+ @ds.insert(30, 10)
263
+ @ds.order(:b, :a).distinct.map(:a).should == [20, 30]
264
+ @ds.order(:b, :a.desc).distinct.map(:a).should == [30, 20]
265
+ # MySQL doesn't respect orders when using the nonstandard GROUP BY
266
+ [[20], [30]].should include(@ds.order(:b, :a).distinct(:b).map(:a))
267
+ end
268
+ end
269
+
270
+ context "MySQL join expressions" do
271
+ before do
272
+ @ds = MYSQL_DB[:nodes]
273
+ @ds.db.meta_def(:server_version) {50014}
274
+ end
275
+
276
+ specify "should raise error for :full_outer join requests." do
277
+ lambda{@ds.join_table(:full_outer, :nodes)}.should raise_error(Sequel::Error)
278
+ end
279
+ specify "should support natural left joins" do
280
+ @ds.join_table(:natural_left, :nodes).sql.should == \
281
+ 'SELECT * FROM nodes NATURAL LEFT JOIN nodes'
282
+ end
283
+ specify "should support natural right joins" do
284
+ @ds.join_table(:natural_right, :nodes).sql.should == \
285
+ 'SELECT * FROM nodes NATURAL RIGHT JOIN nodes'
286
+ end
287
+ specify "should support natural left outer joins" do
288
+ @ds.join_table(:natural_left_outer, :nodes).sql.should == \
289
+ 'SELECT * FROM nodes NATURAL LEFT OUTER JOIN nodes'
290
+ end
291
+ specify "should support natural right outer joins" do
292
+ @ds.join_table(:natural_right_outer, :nodes).sql.should == \
293
+ 'SELECT * FROM nodes NATURAL RIGHT OUTER JOIN nodes'
294
+ end
295
+ specify "should support natural inner joins" do
296
+ @ds.join_table(:natural_inner, :nodes).sql.should == \
297
+ 'SELECT * FROM nodes NATURAL LEFT JOIN nodes'
298
+ end
299
+ specify "should support cross joins" do
300
+ @ds.join_table(:cross, :nodes).sql.should == \
301
+ 'SELECT * FROM nodes CROSS JOIN nodes'
302
+ end
303
+ specify "should support cross joins as inner joins if conditions are used" do
304
+ @ds.join_table(:cross, :nodes, :id=>:id).sql.should == \
305
+ 'SELECT * FROM nodes INNER JOIN nodes ON (nodes.id = nodes.id)'
306
+ end
307
+ specify "should support straight joins (force left table to be read before right)" do
308
+ @ds.join_table(:straight, :nodes).sql.should == \
309
+ 'SELECT * FROM nodes STRAIGHT_JOIN nodes'
310
+ end
311
+ specify "should support natural joins on multiple tables." do
312
+ @ds.join_table(:natural_left_outer, [:nodes, :branches]).sql.should == \
313
+ 'SELECT * FROM nodes NATURAL LEFT OUTER JOIN (nodes, branches)'
314
+ end
315
+ specify "should support straight joins on multiple tables." do
316
+ @ds.join_table(:straight, [:nodes,:branches]).sql.should == \
317
+ 'SELECT * FROM nodes STRAIGHT_JOIN (nodes, branches)'
318
+ end
319
+ end
320
+
321
+ context "Joined MySQL dataset" do
322
+ before do
323
+ @ds = MYSQL_DB[:nodes]
324
+ end
325
+
326
+ specify "should quote fields correctly" do
327
+ @ds.quote_identifiers = true
328
+ @ds.join(:attributes, :node_id => :id).sql.should == \
329
+ "SELECT * FROM `nodes` INNER JOIN `attributes` ON (`attributes`.`node_id` = `nodes`.`id`)"
330
+ end
331
+
332
+ specify "should allow a having clause on ungrouped datasets" do
333
+ proc {@ds.having('blah')}.should_not raise_error
334
+
335
+ @ds.having('blah').sql.should == \
336
+ "SELECT * FROM nodes HAVING (blah)"
337
+ end
338
+
339
+ specify "should put a having clause before an order by clause" do
340
+ @ds.order(:aaa).having(:bbb => :ccc).sql.should == \
341
+ "SELECT * FROM nodes HAVING (bbb = ccc) ORDER BY aaa"
342
+ end
343
+ end
344
+
345
+ context "A MySQL database" do
346
+ before do
347
+ @db = MYSQL_DB
348
+ end
349
+
350
+ specify "should support add_column operations" do
351
+ @db.add_column :test2, :xyz, :text
352
+
353
+ @db[:test2].columns.should == [:name, :value, :xyz]
354
+ @db[:test2] << {:name => 'mmm', :value => 111, :xyz => '000'}
355
+ @db[:test2].first[:xyz].should == '000'
356
+ end
357
+
358
+ specify "should support drop_column operations" do
359
+ @db[:test2].columns.should == [:name, :value, :xyz]
360
+ @db.drop_column :test2, :xyz
361
+
362
+ @db[:test2].columns.should == [:name, :value]
363
+ end
364
+
365
+ specify "should support rename_column operations" do
366
+ @db[:test2].delete
367
+ @db.add_column :test2, :xyz, :text
368
+ @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 'qqqq'}
369
+
370
+ @db[:test2].columns.should == [:name, :value, :xyz]
371
+ @db.rename_column :test2, :xyz, :zyx, :type => :text
372
+ @db[:test2].columns.should == [:name, :value, :zyx]
373
+ @db[:test2].first[:zyx].should == 'qqqq'
374
+ end
375
+
376
+ specify "should support rename_column operations with types like varchar(255)" do
377
+ @db[:test2].delete
378
+ @db.add_column :test2, :tre, :text
379
+ @db[:test2] << {:name => 'mmm', :value => 111, :tre => 'qqqq'}
380
+
381
+ @db[:test2].columns.should == [:name, :value, :zyx, :tre]
382
+ @db.rename_column :test2, :tre, :ert, :type => :varchar, :size=>255
383
+ @db[:test2].columns.should == [:name, :value, :zyx, :ert]
384
+ @db[:test2].first[:ert].should == 'qqqq'
385
+ end
386
+
387
+ specify "should support set_column_type operations" do
388
+ @db.add_column :test2, :xyz, :float
389
+ @db[:test2].delete
390
+ @db[:test2] << {:name => 'mmm', :value => 111, :xyz => 56.78}
391
+ @db.set_column_type :test2, :xyz, :integer
392
+
393
+ @db[:test2].first[:xyz].should == 57
394
+ end
395
+
396
+ specify "should support add_index" do
397
+ @db.add_index :test2, :value
398
+ end
399
+
400
+ specify "should support drop_index" do
401
+ @db.drop_index :test2, :value
402
+ end
403
+
404
+ specify "should support add_foreign_key" do
405
+ @db.alter_table :test2 do
406
+ add_foreign_key :value2, :test2, :key=>:value
407
+ end
408
+ @db[:test2].columns.should == [:name, :value, :zyx, :ert, :xyz, :value2]
409
+ end
410
+ end
411
+
412
+ context "A MySQL database with table options" do
413
+ before do
414
+ @options = {:engine=>'MyISAM', :charset=>'latin1', :collate => 'latin1_swedish_ci'}
415
+
416
+ Sequel::MySQL.default_engine = 'InnoDB'
417
+ Sequel::MySQL.default_charset = 'utf8'
418
+ Sequel::MySQL.default_collate = 'utf8_general_ci'
419
+
420
+ @db = MYSQL_DB
421
+ @db.drop_table(:items) rescue nil
422
+
423
+ MYSQL_DB.sqls.clear
424
+ end
425
+ after do
426
+ @db.drop_table(:items) rescue nil
427
+ Sequel::MySQL.default_engine = nil
428
+ Sequel::MySQL.default_charset = nil
429
+ Sequel::MySQL.default_collate = nil
430
+ end
431
+
432
+ specify "should allow to pass custom options (engine, charset, collate) for table creation" do
433
+ @db.create_table(:items, @options){Integer :size; text :name}
434
+ @db.sqls.should == ["CREATE TABLE items (size integer, name text) ENGINE=MyISAM DEFAULT CHARSET=latin1 DEFAULT COLLATE=latin1_swedish_ci"]
435
+ end
436
+
437
+ specify "should use default options if specified (engine, charset, collate) for table creation" do
438
+ @db.create_table(:items){Integer :size; text :name}
439
+ @db.sqls.should == ["CREATE TABLE items (size integer, name text) ENGINE=InnoDB DEFAULT CHARSET=utf8 DEFAULT COLLATE=utf8_general_ci"]
440
+ end
441
+
442
+ specify "should not use default if option has a nil value" do
443
+ @db.create_table(:items, :engine=>nil, :charset=>nil, :collate=>nil){Integer :size; text :name}
444
+ @db.sqls.should == ["CREATE TABLE items (size integer, name text)"]
445
+ end
446
+ end
447
+
448
+ context "A MySQL database" do
449
+ before do
450
+ @db = MYSQL_DB
451
+ @db.drop_table(:items) rescue nil
452
+ MYSQL_DB.sqls.clear
453
+ end
454
+ after do
455
+ @db.drop_table(:items) rescue nil
456
+ end
457
+
458
+ specify "should support defaults for boolean columns" do
459
+ @db.create_table(:items){TrueClass :active1, :default=>true; FalseClass :active2, :default => false}
460
+ @db.sqls.should == ["CREATE TABLE items (active1 tinyint(1) DEFAULT 1, active2 tinyint(1) DEFAULT 0)"]
461
+ end
462
+
463
+ specify "should correctly format CREATE TABLE statements with foreign keys" do
464
+ @db.create_table(:items){Integer :id; foreign_key :p_id, :items, :key => :id, :null => false, :on_delete => :cascade}
465
+ @db.sqls.should == ["CREATE TABLE items (id integer, p_id integer NOT NULL, FOREIGN KEY (p_id) REFERENCES items(id) ON DELETE CASCADE)"]
466
+ end
467
+
468
+ specify "should correctly format ALTER TABLE statements with foreign keys" do
469
+ @db.create_table(:items){Integer :id}
470
+ @db.alter_table(:items){add_foreign_key :p_id, :users, :key => :id, :null => false, :on_delete => :cascade}
471
+ @db.sqls.should == ["CREATE TABLE items (id integer)", "ALTER TABLE items ADD COLUMN p_id integer NOT NULL", "ALTER TABLE items ADD FOREIGN KEY (p_id) REFERENCES users(id) ON DELETE CASCADE"]
472
+ end
473
+
474
+ specify "should have rename_column support keep existing options" do
475
+ @db.create_table(:items){String :id, :null=>false, :default=>'blah'}
476
+ @db.alter_table(:items){rename_column :id, :nid}
477
+ @db.sqls.should == ["CREATE TABLE items (id varchar(255) NOT NULL DEFAULT 'blah')", "DESCRIBE items", "ALTER TABLE items CHANGE COLUMN id nid varchar(255) NOT NULL DEFAULT 'blah'"]
478
+ @db[:items].insert
479
+ @db[:items].all.should == [{:nid=>'blah'}]
480
+ proc{@db[:items].insert(:nid=>nil)}.should raise_error(Sequel::DatabaseError)
481
+ end
482
+
483
+ specify "should have set_column_type support keep existing options" do
484
+ @db.create_table(:items){Integer :id, :null=>false, :default=>5}
485
+ @db.alter_table(:items){set_column_type :id, Bignum}
486
+ @db.sqls.should == ["CREATE TABLE items (id integer NOT NULL DEFAULT 5)", "DESCRIBE items", "ALTER TABLE items CHANGE COLUMN id id bigint NOT NULL DEFAULT 5"]
487
+ @db[:items].insert
488
+ @db[:items].all.should == [{:id=>5}]
489
+ proc{@db[:items].insert(:id=>nil)}.should raise_error(Sequel::DatabaseError)
490
+ @db[:items].delete
491
+ @db[:items].insert(2**40)
492
+ @db[:items].all.should == [{:id=>2**40}]
493
+ end
494
+
495
+ specify "should have set_column_type pass through options" do
496
+ @db.create_table(:items){integer :id; enum :list, :elements=>%w[one]}
497
+ @db.alter_table(:items){set_column_type :id, :int, :unsigned=>true, :size=>8; set_column_type :list, :enum, :elements=>%w[two]}
498
+ @db.sqls.should == ["CREATE TABLE items (id integer, list enum('one'))", "DESCRIBE items", "ALTER TABLE items CHANGE COLUMN id id int(8) UNSIGNED NULL", "ALTER TABLE items CHANGE COLUMN list list enum('two') NULL"]
499
+ end
500
+
501
+ specify "should have set_column_default support keep existing options" do
502
+ @db.create_table(:items){Integer :id, :null=>false, :default=>5}
503
+ @db.alter_table(:items){set_column_default :id, 6}
504
+ @db.sqls.should == ["CREATE TABLE items (id integer NOT NULL DEFAULT 5)", "DESCRIBE items", "ALTER TABLE items CHANGE COLUMN id id int(11) NOT NULL DEFAULT 6"]
505
+ @db[:items].insert
506
+ @db[:items].all.should == [{:id=>6}]
507
+ proc{@db[:items].insert(:id=>nil)}.should raise_error(Sequel::DatabaseError)
508
+ end
509
+
510
+ specify "should have set_column_allow_null support keep existing options" do
511
+ @db.create_table(:items){Integer :id, :null=>false, :default=>5}
512
+ @db.alter_table(:items){set_column_allow_null :id, true}
513
+ @db.sqls.should == ["CREATE TABLE items (id integer NOT NULL DEFAULT 5)", "DESCRIBE items", "ALTER TABLE items CHANGE COLUMN id id int(11) NULL DEFAULT 5"]
514
+ @db[:items].insert
515
+ @db[:items].all.should == [{:id=>5}]
516
+ proc{@db[:items].insert(:id=>nil)}.should_not
517
+ end
518
+
519
+ specify "should accept repeated raw sql statements using Database#<<" do
520
+ @db.create_table(:items){String :name; Integer :value}
521
+ @db << 'DELETE FROM items'
522
+ @db[:items].count.should == 0
523
+
524
+ @db << "INSERT INTO items (name, value) VALUES ('tutu', 1234)"
525
+ @db[:items].first.should == {:name => 'tutu', :value => 1234}
526
+
527
+ @db << 'DELETE FROM items'
528
+ @db[:items].first.should == nil
529
+ end
530
+ end
531
+
532
+ # Socket tests should only be run if the MySQL server is on localhost
533
+ if %w'localhost 127.0.0.1 ::1'.include?(MYSQL_URI.host) and MYSQL_DB.class.adapter_scheme == :mysql
534
+ context "A MySQL database" do
535
+ specify "should accept a socket option" do
536
+ db = Sequel.mysql(MYSQL_DB.opts[:database], :host => 'localhost', :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
537
+ proc {db.test_connection}.should_not raise_error
538
+ end
539
+
540
+ specify "should accept a socket option without host option" do
541
+ db = Sequel.mysql(MYSQL_DB.opts[:database], :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket => MYSQL_SOCKET_FILE)
542
+ proc {db.test_connection}.should_not raise_error
543
+ end
544
+
545
+ specify "should fail to connect with invalid socket" do
546
+ db = Sequel.mysql(MYSQL_DB.opts[:database], :user => MYSQL_DB.opts[:user], :password => MYSQL_DB.opts[:password], :socket =>'blah')
547
+ proc {db.test_connection}.should raise_error
548
+ end
549
+ end
550
+ end
551
+
552
+ context "A grouped MySQL dataset" do
553
+ before do
554
+ MYSQL_DB[:test2].delete
555
+ MYSQL_DB[:test2] << {:name => '11', :value => 10}
556
+ MYSQL_DB[:test2] << {:name => '11', :value => 20}
557
+ MYSQL_DB[:test2] << {:name => '11', :value => 30}
558
+ MYSQL_DB[:test2] << {:name => '12', :value => 10}
559
+ MYSQL_DB[:test2] << {:name => '12', :value => 20}
560
+ MYSQL_DB[:test2] << {:name => '13', :value => 10}
561
+ end
562
+
563
+ specify "should return the correct count for raw sql query" do
564
+ ds = MYSQL_DB["select name FROM test2 WHERE name = '11' GROUP BY name"]
565
+ ds.count.should == 1
566
+ end
567
+
568
+ specify "should return the correct count for a normal dataset" do
569
+ ds = MYSQL_DB[:test2].select(:name).where(:name => '11').group(:name)
570
+ ds.count.should == 1
571
+ end
572
+ end
573
+
574
+ context "A MySQL database" do
575
+ before do
576
+ @db = MYSQL_DB
577
+ @db.drop_table(:posts) rescue nil
578
+ @db.sqls.clear
579
+ end
580
+ after do
581
+ @db.drop_table(:posts) rescue nil
582
+ end
583
+
584
+ specify "should support fulltext indexes and full_text_search" do
585
+ @db.create_table(:posts){text :title; text :body; full_text_index :title; full_text_index [:title, :body]}
586
+ @db.sqls.should == [
587
+ "CREATE TABLE posts (title text, body text)",
588
+ "CREATE FULLTEXT INDEX posts_title_index ON posts (title)",
589
+ "CREATE FULLTEXT INDEX posts_title_body_index ON posts (title, body)"
590
+ ]
591
+
592
+ @db[:posts].insert(:title=>'ruby rails', :body=>'y')
593
+ @db[:posts].insert(:title=>'sequel', :body=>'ruby')
594
+ @db[:posts].insert(:title=>'ruby scooby', :body=>'x')
595
+ @db.sqls.clear
596
+
597
+ @db[:posts].full_text_search(:title, 'rails').all.should == [{:title=>'ruby rails', :body=>'y'}]
598
+ @db[:posts].full_text_search([:title, :body], ['sequel', 'ruby']).all.should == [{:title=>'sequel', :body=>'ruby'}]
599
+ @db[:posts].full_text_search(:title, '+ruby -rails', :boolean => true).all.should == [{:title=>'ruby scooby', :body=>'x'}]
600
+ @db.sqls.should == [
601
+ "SELECT * FROM posts WHERE (MATCH (title) AGAINST ('rails'))",
602
+ "SELECT * FROM posts WHERE (MATCH (title, body) AGAINST ('sequel ruby'))",
603
+ "SELECT * FROM posts WHERE (MATCH (title) AGAINST ('+ruby -rails' IN BOOLEAN MODE))"]
604
+ end
605
+
606
+ specify "should support spatial indexes" do
607
+ @db.create_table(:posts){point :geom, :null=>false; spatial_index [:geom]}
608
+ @db.sqls.should == [
609
+ "CREATE TABLE posts (geom point NOT NULL)",
610
+ "CREATE SPATIAL INDEX posts_geom_index ON posts (geom)"
611
+ ]
612
+ end
613
+
614
+ specify "should support indexes with index type" do
615
+ @db.create_table(:posts){Integer :id; index :id, :type => :btree}
616
+ @db.sqls.should == [
617
+ "CREATE TABLE posts (id integer)",
618
+ "CREATE INDEX posts_id_index USING btree ON posts (id)"
619
+ ]
620
+ end
621
+
622
+ specify "should support unique indexes with index type" do
623
+ @db.create_table(:posts){Integer :id; index :id, :type => :btree, :unique => true}
624
+ @db.sqls.should == [
625
+ "CREATE TABLE posts (id integer)",
626
+ "CREATE UNIQUE INDEX posts_id_index USING btree ON posts (id)"
627
+ ]
628
+ end
629
+
630
+ specify "should not dump partial indexes" do
631
+ @db.create_table(:posts){text :id}
632
+ @db << "CREATE INDEX posts_id_index ON posts (id(10))"
633
+ @db.indexes(:posts).should == {}
634
+ end
635
+ end
636
+
637
+ context "MySQL::Dataset#insert and related methods" do
638
+ before do
639
+ MYSQL_DB.create_table(:items){String :name; Integer :value}
640
+ @d = MYSQL_DB[:items]
641
+ MYSQL_DB.sqls.clear
642
+ end
643
+ after do
644
+ MYSQL_DB.drop_table(:items)
645
+ end
646
+
647
+ specify "#insert should insert record with default values when no arguments given" do
648
+ @d.insert
649
+
650
+ MYSQL_DB.sqls.should == [
651
+ "INSERT INTO items () VALUES ()"
652
+ ]
653
+
654
+ @d.all.should == [
655
+ {:name => nil, :value => nil}
656
+ ]
657
+ end
658
+
659
+ specify "#insert should insert record with default values when empty hash given" do
660
+ @d.insert({})
661
+
662
+ MYSQL_DB.sqls.should == [
663
+ "INSERT INTO items () VALUES ()"
664
+ ]
665
+
666
+ @d.all.should == [
667
+ {:name => nil, :value => nil}
668
+ ]
669
+ end
670
+
671
+ specify "#insert should insert record with default values when empty array given" do
672
+ @d.insert []
673
+
674
+ MYSQL_DB.sqls.should == [
675
+ "INSERT INTO items () VALUES ()"
676
+ ]
677
+
678
+ @d.all.should == [
679
+ {:name => nil, :value => nil}
680
+ ]
681
+ end
682
+
683
+ specify "#on_duplicate_key_update should work with regular inserts" do
684
+ MYSQL_DB.add_index :items, :name, :unique=>true
685
+ MYSQL_DB.sqls.clear
686
+ @d.insert(:name => 'abc', :value => 1)
687
+ @d.on_duplicate_key_update(:name, :value => 6).insert(:name => 'abc', :value => 1)
688
+ @d.on_duplicate_key_update(:name, :value => 6).insert(:name => 'def', :value => 2)
689
+
690
+ MYSQL_DB.sqls.length.should == 3
691
+ MYSQL_DB.sqls[0].should =~ /\AINSERT INTO items \((name|value), (name|value)\) VALUES \(('abc'|1), (1|'abc')\)\z/
692
+ MYSQL_DB.sqls[1].should =~ /\AINSERT INTO items \((name|value), (name|value)\) VALUES \(('abc'|1), (1|'abc')\) ON DUPLICATE KEY UPDATE name=VALUES\(name\), value=6\z/
693
+ MYSQL_DB.sqls[2].should =~ /\AINSERT INTO items \((name|value), (name|value)\) VALUES \(('def'|2), (2|'def')\) ON DUPLICATE KEY UPDATE name=VALUES\(name\), value=6\z/
694
+
695
+ @d.all.should == [{:name => 'abc', :value => 6}, {:name => 'def', :value => 2}]
696
+ end
697
+
698
+ specify "#multi_insert should insert multiple records in a single statement" do
699
+ @d.multi_insert([{:name => 'abc'}, {:name => 'def'}])
700
+
701
+ MYSQL_DB.sqls.should == [
702
+ SQL_BEGIN,
703
+ "INSERT INTO items (name) VALUES ('abc'), ('def')",
704
+ SQL_COMMIT
705
+ ]
706
+
707
+ @d.all.should == [
708
+ {:name => 'abc', :value => nil}, {:name => 'def', :value => nil}
709
+ ]
710
+ end
711
+
712
+ specify "#multi_insert should split the list of records into batches if :commit_every option is given" do
713
+ @d.multi_insert([{:value => 1}, {:value => 2}, {:value => 3}, {:value => 4}],
714
+ :commit_every => 2)
715
+
716
+ MYSQL_DB.sqls.should == [
717
+ SQL_BEGIN,
718
+ "INSERT INTO items (value) VALUES (1), (2)",
719
+ SQL_COMMIT,
720
+ SQL_BEGIN,
721
+ "INSERT INTO items (value) VALUES (3), (4)",
722
+ SQL_COMMIT
723
+ ]
724
+
725
+ @d.all.should == [
726
+ {:name => nil, :value => 1},
727
+ {:name => nil, :value => 2},
728
+ {:name => nil, :value => 3},
729
+ {:name => nil, :value => 4}
730
+ ]
731
+ end
732
+
733
+ specify "#multi_insert should split the list of records into batches if :slice option is given" do
734
+ @d.multi_insert([{:value => 1}, {:value => 2}, {:value => 3}, {:value => 4}],
735
+ :slice => 2)
736
+
737
+ MYSQL_DB.sqls.should == [
738
+ SQL_BEGIN,
739
+ "INSERT INTO items (value) VALUES (1), (2)",
740
+ SQL_COMMIT,
741
+ SQL_BEGIN,
742
+ "INSERT INTO items (value) VALUES (3), (4)",
743
+ SQL_COMMIT
744
+ ]
745
+
746
+ @d.all.should == [
747
+ {:name => nil, :value => 1},
748
+ {:name => nil, :value => 2},
749
+ {:name => nil, :value => 3},
750
+ {:name => nil, :value => 4}
751
+ ]
752
+ end
753
+
754
+ specify "#import should support inserting using columns and values arrays" do
755
+ @d.import([:name, :value], [['abc', 1], ['def', 2]])
756
+
757
+ MYSQL_DB.sqls.should == [
758
+ SQL_BEGIN,
759
+ "INSERT INTO items (name, value) VALUES ('abc', 1), ('def', 2)",
760
+ SQL_COMMIT
761
+ ]
762
+
763
+ @d.all.should == [
764
+ {:name => 'abc', :value => 1},
765
+ {:name => 'def', :value => 2}
766
+ ]
767
+ end
768
+
769
+ specify "#insert_ignore should add the IGNORE keyword when inserting" do
770
+ @d.insert_ignore.multi_insert([{:name => 'abc'}, {:name => 'def'}])
771
+
772
+ MYSQL_DB.sqls.should == [
773
+ SQL_BEGIN,
774
+ "INSERT IGNORE INTO items (name) VALUES ('abc'), ('def')",
775
+ SQL_COMMIT
776
+ ]
777
+
778
+ @d.all.should == [
779
+ {:name => 'abc', :value => nil}, {:name => 'def', :value => nil}
780
+ ]
781
+ end
782
+
783
+ specify "#insert_ignore should add the IGNORE keyword for single inserts" do
784
+ @d.insert_ignore.insert(:name => 'ghi')
785
+ MYSQL_DB.sqls.should == ["INSERT IGNORE INTO items (name) VALUES ('ghi')"]
786
+ @d.all.should == [{:name => 'ghi', :value => nil}]
787
+ end
788
+
789
+ specify "#on_duplicate_key_update should add the ON DUPLICATE KEY UPDATE and ALL columns when no args given" do
790
+ @d.on_duplicate_key_update.import([:name,:value], [['abc', 1], ['def',2]])
791
+
792
+ MYSQL_DB.sqls.should == [
793
+ "SELECT * FROM items LIMIT 1",
794
+ SQL_BEGIN,
795
+ "INSERT INTO items (name, value) VALUES ('abc', 1), ('def', 2) ON DUPLICATE KEY UPDATE name=VALUES(name), value=VALUES(value)",
796
+ SQL_COMMIT
797
+ ]
798
+
799
+ @d.all.should == [
800
+ {:name => 'abc', :value => 1}, {:name => 'def', :value => 2}
801
+ ]
802
+ end
803
+
804
+ specify "#on_duplicate_key_update should add the ON DUPLICATE KEY UPDATE and columns specified when args are given" do
805
+ @d.on_duplicate_key_update(:value).import([:name,:value],
806
+ [['abc', 1], ['def',2]]
807
+ )
808
+
809
+ MYSQL_DB.sqls.should == [
810
+ SQL_BEGIN,
811
+ "INSERT INTO items (name, value) VALUES ('abc', 1), ('def', 2) ON DUPLICATE KEY UPDATE value=VALUES(value)",
812
+ SQL_COMMIT
813
+ ]
814
+
815
+ @d.all.should == [
816
+ {:name => 'abc', :value => 1}, {:name => 'def', :value => 2}
817
+ ]
818
+ end
819
+
820
+ end
821
+
822
+ context "MySQL::Dataset#replace" do
823
+ before do
824
+ MYSQL_DB.create_table(:items){Integer :id, :unique=>true; Integer :value}
825
+ @d = MYSQL_DB[:items]
826
+ MYSQL_DB.sqls.clear
827
+ end
828
+ after do
829
+ MYSQL_DB.drop_table(:items)
830
+ end
831
+
832
+ specify "should use default values if they exist" do
833
+ MYSQL_DB.alter_table(:items){set_column_default :id, 1; set_column_default :value, 2}
834
+ @d.replace
835
+ @d.all.should == [{:id=>1, :value=>2}]
836
+ @d.replace([])
837
+ @d.all.should == [{:id=>1, :value=>2}]
838
+ @d.replace({})
839
+ @d.all.should == [{:id=>1, :value=>2}]
840
+ end
841
+
842
+ specify "should use support arrays, datasets, and multiple values" do
843
+ @d.replace([1, 2])
844
+ @d.all.should == [{:id=>1, :value=>2}]
845
+ @d.replace(1, 2)
846
+ @d.all.should == [{:id=>1, :value=>2}]
847
+ @d.replace(@d)
848
+ @d.all.should == [{:id=>1, :value=>2}]
849
+ end
850
+
851
+ specify "should create a record if the condition is not met" do
852
+ @d.replace(:id => 111, :value => 333)
853
+ @d.all.should == [{:id => 111, :value => 333}]
854
+ end
855
+
856
+ specify "should update a record if the condition is met" do
857
+ @d << {:id => 111}
858
+ @d.all.should == [{:id => 111, :value => nil}]
859
+ @d.replace(:id => 111, :value => 333)
860
+ @d.all.should == [{:id => 111, :value => 333}]
861
+ end
862
+ end
863
+
864
+ context "MySQL::Dataset#complex_expression_sql" do
865
+ before do
866
+ @d = MYSQL_DB.dataset
867
+ end
868
+
869
+ specify "should handle pattern matches correctly" do
870
+ @d.literal(:x.like('a')).should == "(x LIKE BINARY 'a')"
871
+ @d.literal(~:x.like('a')).should == "(x NOT LIKE BINARY 'a')"
872
+ @d.literal(:x.ilike('a')).should == "(x LIKE 'a')"
873
+ @d.literal(~:x.ilike('a')).should == "(x NOT LIKE 'a')"
874
+ @d.literal(:x.like(/a/)).should == "(x REGEXP BINARY 'a')"
875
+ @d.literal(~:x.like(/a/)).should == "(x NOT REGEXP BINARY 'a')"
876
+ @d.literal(:x.like(/a/i)).should == "(x REGEXP 'a')"
877
+ @d.literal(~:x.like(/a/i)).should == "(x NOT REGEXP 'a')"
878
+ end
879
+
880
+ specify "should handle string concatenation with CONCAT if more than one record" do
881
+ @d.literal([:x, :y].sql_string_join).should == "CONCAT(x, y)"
882
+ @d.literal([:x, :y].sql_string_join(' ')).should == "CONCAT(x, ' ', y)"
883
+ @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)"
884
+ end
885
+
886
+ specify "should handle string concatenation as simple string if just one record" do
887
+ @d.literal([:x].sql_string_join).should == "x"
888
+ @d.literal([:x].sql_string_join(' ')).should == "x"
889
+ end
890
+ end
891
+
892
+ unless MYSQL_DB.class.adapter_scheme == :do
893
+ context "MySQL Stored Procedures" do
894
+ before do
895
+ MYSQL_DB.create_table(:items){Integer :id; Integer :value}
896
+ @d = MYSQL_DB[:items]
897
+ MYSQL_DB.sqls.clear
898
+ end
899
+ after do
900
+ MYSQL_DB.drop_table(:items)
901
+ MYSQL_DB.execute('DROP PROCEDURE test_sproc')
902
+ end
903
+
904
+ specify "should be callable on the database object" do
905
+ MYSQL_DB.execute('CREATE PROCEDURE test_sproc() BEGIN DELETE FROM items; END')
906
+ MYSQL_DB[:items].delete
907
+ MYSQL_DB[:items].insert(:value=>1)
908
+ MYSQL_DB[:items].count.should == 1
909
+ MYSQL_DB.call_sproc(:test_sproc)
910
+ MYSQL_DB[:items].count.should == 0
911
+ end
912
+
913
+ specify "should be callable on the dataset object" do
914
+ MYSQL_DB.execute('CREATE PROCEDURE test_sproc(a INTEGER) BEGIN SELECT *, a AS b FROM items; END')
915
+ MYSQL_DB[:items].delete
916
+ @d = MYSQL_DB[:items]
917
+ @d.call_sproc(:select, :test_sproc, 3).should == []
918
+ @d.insert(:value=>1)
919
+ @d.call_sproc(:select, :test_sproc, 4).should == [{:id=>nil, :value=>1, :b=>4}]
920
+ @d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
921
+ @d.call_sproc(:select, :test_sproc, 3).should == [{:id=>nil, :value=>2, :b=>6}]
922
+ end
923
+
924
+ specify "should be callable on the dataset object with multiple arguments" do
925
+ MYSQL_DB.execute('CREATE PROCEDURE test_sproc(a INTEGER, c INTEGER) BEGIN SELECT *, a AS b, c AS d FROM items; END')
926
+ MYSQL_DB[:items].delete
927
+ @d = MYSQL_DB[:items]
928
+ @d.call_sproc(:select, :test_sproc, 3, 4).should == []
929
+ @d.insert(:value=>1)
930
+ @d.call_sproc(:select, :test_sproc, 4, 5).should == [{:id=>nil, :value=>1, :b=>4, :d=>5}]
931
+ @d.row_proc = proc{|r| r.keys.each{|k| r[k] *= 2 if r[k].is_a?(Integer)}; r}
932
+ @d.call_sproc(:select, :test_sproc, 3, 4).should == [{:id=>nil, :value=>2, :b=>6, :d => 8}]
933
+ end
934
+ end
935
+ end
936
+
937
+ if MYSQL_DB.class.adapter_scheme == :mysql
938
+ context "MySQL bad date/time conversions" do
939
+ after do
940
+ Sequel::MySQL.convert_invalid_date_time = false
941
+ end
942
+
943
+ specify "should raise an exception when a bad date/time is used and convert_invalid_date_time is false" do
944
+ Sequel::MySQL.convert_invalid_date_time = false
945
+ proc{MYSQL_DB["SELECT CAST('0000-00-00' AS date)"].single_value}.should raise_error(Sequel::InvalidValue)
946
+ proc{MYSQL_DB["SELECT CAST('0000-00-00 00:00:00' AS datetime)"].single_value}.should raise_error(Sequel::InvalidValue)
947
+ proc{MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value}.should raise_error(Sequel::InvalidValue)
948
+ end
949
+
950
+ specify "should not use a nil value bad date/time is used and convert_invalid_date_time is nil or :nil" do
951
+ Sequel::MySQL.convert_invalid_date_time = nil
952
+ MYSQL_DB["SELECT CAST('0000-00-00' AS date)"].single_value.should == nil
953
+ MYSQL_DB["SELECT CAST('0000-00-00 00:00:00' AS datetime)"].single_value.should == nil
954
+ MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value.should == nil
955
+ Sequel::MySQL.convert_invalid_date_time = :nil
956
+ MYSQL_DB["SELECT CAST('0000-00-00' AS date)"].single_value.should == nil
957
+ MYSQL_DB["SELECT CAST('0000-00-00 00:00:00' AS datetime)"].single_value.should == nil
958
+ MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value.should == nil
959
+ end
960
+
961
+ specify "should not use a nil value bad date/time is used and convert_invalid_date_time is :string" do
962
+ Sequel::MySQL.convert_invalid_date_time = :string
963
+ MYSQL_DB["SELECT CAST('0000-00-00' AS date)"].single_value.should == '0000-00-00'
964
+ MYSQL_DB["SELECT CAST('0000-00-00 00:00:00' AS datetime)"].single_value.should == '0000-00-00 00:00:00'
965
+ MYSQL_DB["SELECT CAST('25:00:00' AS time)"].single_value.should == '25:00:00'
966
+ end
967
+ end
968
+
969
+ context "MySQL multiple result sets" do
970
+ before do
971
+ MYSQL_DB.create_table!(:a){Integer :a}
972
+ MYSQL_DB.create_table!(:b){Integer :b}
973
+ @ds = MYSQL_DB['SELECT * FROM a; SELECT * FROM b']
974
+ MYSQL_DB[:a].insert(10)
975
+ MYSQL_DB[:a].insert(15)
976
+ MYSQL_DB[:b].insert(20)
977
+ MYSQL_DB[:b].insert(25)
978
+ end
979
+ after do
980
+ MYSQL_DB.drop_table(:a, :b)
981
+ end
982
+
983
+ specify "should combine all results by default" do
984
+ @ds.all.should == [{:a=>10}, {:a=>15}, {:b=>20}, {:b=>25}]
985
+ end
986
+
987
+ specify "should work with Database#run" do
988
+ proc{MYSQL_DB.run('SELECT * FROM a; SELECT * FROM b')}.should_not raise_error
989
+ proc{MYSQL_DB.run('SELECT * FROM a; SELECT * FROM b')}.should_not raise_error
990
+ end
991
+
992
+ specify "should work with Database#run and other statements" do
993
+ proc{MYSQL_DB.run('UPDATE a SET a = 1; SELECT * FROM a; DELETE FROM b')}.should_not raise_error
994
+ MYSQL_DB[:a].select_order_map(:a).should == [1, 1]
995
+ MYSQL_DB[:b].all.should == []
996
+ end
997
+
998
+ specify "should split results returned into arrays if split_multiple_result_sets is used" do
999
+ @ds.split_multiple_result_sets.all.should == [[{:a=>10}, {:a=>15}], [{:b=>20}, {:b=>25}]]
1000
+ end
1001
+
1002
+ specify "should have regular row_procs work when splitting multiple result sets" do
1003
+ @ds.row_proc = proc{|x| x[x.keys.first] *= 2; x}
1004
+ @ds.split_multiple_result_sets.all.should == [[{:a=>20}, {:a=>30}], [{:b=>40}, {:b=>50}]]
1005
+ end
1006
+
1007
+ specify "should use the columns from the first result set when splitting result sets" do
1008
+ @ds.split_multiple_result_sets.columns.should == [:a]
1009
+ end
1010
+
1011
+ specify "should not allow graphing a dataset that splits multiple statements" do
1012
+ proc{@ds.split_multiple_result_sets.graph(:b, :b=>:a)}.should raise_error(Sequel::Error)
1013
+ end
1014
+
1015
+ specify "should not allow splitting a graphed dataset" do
1016
+ proc{MYSQL_DB[:a].graph(:b, :b=>:a).split_multiple_result_sets}.should raise_error(Sequel::Error)
1017
+ end
1018
+ end
1019
+ end