viking-sequel 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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