sequel 3.21.0 → 3.29.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 (200) hide show
  1. data/CHANGELOG +413 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +23 -16
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +210 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/mass_assignment.rdoc +54 -0
  8. data/doc/migration.rdoc +15 -538
  9. data/doc/model_hooks.rdoc +72 -27
  10. data/doc/opening_databases.rdoc +86 -50
  11. data/doc/prepared_statements.rdoc +40 -13
  12. data/doc/reflection.rdoc +8 -2
  13. data/doc/release_notes/3.22.0.txt +39 -0
  14. data/doc/release_notes/3.23.0.txt +172 -0
  15. data/doc/release_notes/3.24.0.txt +420 -0
  16. data/doc/release_notes/3.25.0.txt +88 -0
  17. data/doc/release_notes/3.26.0.txt +88 -0
  18. data/doc/release_notes/3.27.0.txt +82 -0
  19. data/doc/release_notes/3.28.0.txt +304 -0
  20. data/doc/release_notes/3.29.0.txt +459 -0
  21. data/doc/schema_modification.rdoc +547 -0
  22. data/doc/sharding.rdoc +7 -1
  23. data/doc/testing.rdoc +115 -0
  24. data/doc/transactions.rdoc +137 -0
  25. data/doc/virtual_rows.rdoc +2 -2
  26. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  27. data/lib/sequel/adapters/ado.rb +40 -18
  28. data/lib/sequel/adapters/amalgalite.rb +15 -7
  29. data/lib/sequel/adapters/db2.rb +175 -86
  30. data/lib/sequel/adapters/dbi.rb +15 -15
  31. data/lib/sequel/adapters/do/mysql.rb +0 -5
  32. data/lib/sequel/adapters/do/postgres.rb +0 -5
  33. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  34. data/lib/sequel/adapters/do.rb +17 -36
  35. data/lib/sequel/adapters/firebird.rb +27 -208
  36. data/lib/sequel/adapters/ibmdb.rb +448 -0
  37. data/lib/sequel/adapters/informix.rb +6 -23
  38. data/lib/sequel/adapters/jdbc/as400.rb +5 -31
  39. data/lib/sequel/adapters/jdbc/db2.rb +44 -0
  40. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  41. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  42. data/lib/sequel/adapters/jdbc/h2.rb +26 -17
  43. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  44. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  45. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  46. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  47. data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
  48. data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
  49. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
  50. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  51. data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
  52. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  53. data/lib/sequel/adapters/jdbc.rb +107 -38
  54. data/lib/sequel/adapters/mock.rb +315 -0
  55. data/lib/sequel/adapters/mysql.rb +78 -182
  56. data/lib/sequel/adapters/mysql2.rb +24 -23
  57. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  58. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  59. data/lib/sequel/adapters/odbc.rb +28 -9
  60. data/lib/sequel/adapters/openbase.rb +2 -4
  61. data/lib/sequel/adapters/oracle.rb +349 -51
  62. data/lib/sequel/adapters/postgres.rb +169 -21
  63. data/lib/sequel/adapters/shared/access.rb +21 -6
  64. data/lib/sequel/adapters/shared/db2.rb +288 -0
  65. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  66. data/lib/sequel/adapters/shared/informix.rb +45 -0
  67. data/lib/sequel/adapters/shared/mssql.rb +108 -65
  68. data/lib/sequel/adapters/shared/mysql.rb +56 -13
  69. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  70. data/lib/sequel/adapters/shared/oracle.rb +185 -34
  71. data/lib/sequel/adapters/shared/postgres.rb +107 -58
  72. data/lib/sequel/adapters/shared/progress.rb +0 -6
  73. data/lib/sequel/adapters/shared/sqlite.rb +168 -41
  74. data/lib/sequel/adapters/sqlite.rb +27 -18
  75. data/lib/sequel/adapters/swift/mysql.rb +9 -5
  76. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  77. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  78. data/lib/sequel/adapters/swift.rb +5 -5
  79. data/lib/sequel/adapters/tinytds.rb +152 -17
  80. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
  81. data/lib/sequel/ast_transformer.rb +190 -0
  82. data/lib/sequel/connection_pool/threaded.rb +3 -2
  83. data/lib/sequel/connection_pool.rb +1 -1
  84. data/lib/sequel/core.rb +46 -5
  85. data/lib/sequel/database/connecting.rb +5 -6
  86. data/lib/sequel/database/dataset.rb +4 -4
  87. data/lib/sequel/database/dataset_defaults.rb +59 -1
  88. data/lib/sequel/database/logging.rb +1 -1
  89. data/lib/sequel/database/misc.rb +100 -19
  90. data/lib/sequel/database/query.rb +153 -58
  91. data/lib/sequel/database/schema_generator.rb +8 -5
  92. data/lib/sequel/database/schema_methods.rb +59 -29
  93. data/lib/sequel/dataset/actions.rb +194 -57
  94. data/lib/sequel/dataset/features.rb +81 -8
  95. data/lib/sequel/dataset/graph.rb +8 -7
  96. data/lib/sequel/dataset/misc.rb +50 -23
  97. data/lib/sequel/dataset/mutation.rb +5 -6
  98. data/lib/sequel/dataset/prepared_statements.rb +32 -15
  99. data/lib/sequel/dataset/query.rb +223 -42
  100. data/lib/sequel/dataset/sql.rb +58 -61
  101. data/lib/sequel/dataset.rb +8 -0
  102. data/lib/sequel/exceptions.rb +4 -0
  103. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  104. data/lib/sequel/extensions/migration.rb +6 -4
  105. data/lib/sequel/extensions/named_timezones.rb +5 -0
  106. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  107. data/lib/sequel/extensions/to_dot.rb +95 -83
  108. data/lib/sequel/model/associations.rb +997 -331
  109. data/lib/sequel/model/base.rb +386 -129
  110. data/lib/sequel/model/errors.rb +1 -1
  111. data/lib/sequel/model/exceptions.rb +5 -1
  112. data/lib/sequel/model.rb +15 -8
  113. data/lib/sequel/plugins/association_pks.rb +22 -4
  114. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  115. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  116. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  117. data/lib/sequel/plugins/force_encoding.rb +6 -6
  118. data/lib/sequel/plugins/identity_map.rb +114 -7
  119. data/lib/sequel/plugins/many_through_many.rb +65 -7
  120. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  121. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  122. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  123. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  124. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  125. data/lib/sequel/plugins/serialization.rb +6 -1
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  127. data/lib/sequel/plugins/sharding.rb +12 -25
  128. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  129. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  130. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  131. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  132. data/lib/sequel/sql.rb +107 -51
  133. data/lib/sequel/timezones.rb +44 -35
  134. data/lib/sequel/version.rb +1 -1
  135. data/spec/adapters/db2_spec.rb +146 -0
  136. data/spec/adapters/mssql_spec.rb +65 -29
  137. data/spec/adapters/mysql_spec.rb +122 -123
  138. data/spec/adapters/oracle_spec.rb +48 -76
  139. data/spec/adapters/postgres_spec.rb +213 -61
  140. data/spec/adapters/spec_helper.rb +6 -5
  141. data/spec/adapters/sqlite_spec.rb +35 -21
  142. data/spec/core/connection_pool_spec.rb +71 -92
  143. data/spec/core/core_sql_spec.rb +20 -31
  144. data/spec/core/database_spec.rb +729 -508
  145. data/spec/core/dataset_spec.rb +980 -1044
  146. data/spec/core/expression_filters_spec.rb +159 -42
  147. data/spec/core/mock_adapter_spec.rb +378 -0
  148. data/spec/core/object_graph_spec.rb +48 -114
  149. data/spec/core/schema_generator_spec.rb +3 -3
  150. data/spec/core/schema_spec.rb +298 -38
  151. data/spec/core/spec_helper.rb +6 -48
  152. data/spec/extensions/association_pks_spec.rb +38 -0
  153. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  154. data/spec/extensions/columns_introspection_spec.rb +91 -0
  155. data/spec/extensions/dataset_associations_spec.rb +199 -0
  156. data/spec/extensions/defaults_setter_spec.rb +64 -0
  157. data/spec/extensions/identity_map_spec.rb +162 -0
  158. data/spec/extensions/instance_hooks_spec.rb +71 -0
  159. data/spec/extensions/many_through_many_spec.rb +195 -20
  160. data/spec/extensions/migration_spec.rb +17 -17
  161. data/spec/extensions/named_timezones_spec.rb +22 -2
  162. data/spec/extensions/nested_attributes_spec.rb +4 -0
  163. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  164. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  165. data/spec/extensions/prepared_statements_spec.rb +72 -0
  166. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  167. data/spec/extensions/schema_dumper_spec.rb +2 -2
  168. data/spec/extensions/schema_spec.rb +13 -21
  169. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  170. data/spec/extensions/serialization_spec.rb +5 -8
  171. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  172. data/spec/extensions/spec_helper.rb +7 -1
  173. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  174. data/spec/extensions/to_dot_spec.rb +3 -5
  175. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  176. data/spec/extensions/xml_serializer_spec.rb +16 -4
  177. data/spec/integration/associations_test.rb +522 -21
  178. data/spec/integration/database_test.rb +4 -2
  179. data/spec/integration/dataset_test.rb +375 -62
  180. data/spec/integration/eager_loader_test.rb +19 -21
  181. data/spec/integration/model_test.rb +80 -1
  182. data/spec/integration/plugin_test.rb +304 -116
  183. data/spec/integration/prepared_statement_test.rb +200 -120
  184. data/spec/integration/schema_test.rb +161 -30
  185. data/spec/integration/spec_helper.rb +39 -30
  186. data/spec/integration/timezone_test.rb +38 -12
  187. data/spec/integration/transaction_test.rb +172 -5
  188. data/spec/integration/type_test.rb +17 -3
  189. data/spec/model/association_reflection_spec.rb +174 -7
  190. data/spec/model/associations_spec.rb +849 -661
  191. data/spec/model/base_spec.rb +255 -95
  192. data/spec/model/dataset_methods_spec.rb +7 -27
  193. data/spec/model/eager_loading_spec.rb +640 -676
  194. data/spec/model/hooks_spec.rb +309 -67
  195. data/spec/model/model_spec.rb +207 -167
  196. data/spec/model/plugins_spec.rb +24 -13
  197. data/spec/model/record_spec.rb +321 -218
  198. data/spec/model/spec_helper.rb +13 -71
  199. data/spec/model/validations_spec.rb +11 -0
  200. metadata +95 -38
@@ -0,0 +1,315 @@
1
+ module Sequel
2
+ module Mock
3
+ # Connection class for Sequel's mock adapter.
4
+ class Connection
5
+ # Sequel::Mock::Database object that created this connection
6
+ attr_reader :db
7
+
8
+ # Shard this connection operates on, when using Sequel's
9
+ # sharding support (always :default for databases not using
10
+ # sharding).
11
+ attr_reader :server
12
+
13
+ # The specific database options for this connection.
14
+ attr_reader :opts
15
+
16
+ # Store the db, server, and opts.
17
+ def initialize(db, server, opts)
18
+ @db = db
19
+ @server = server
20
+ @opts = opts
21
+ end
22
+
23
+ # Delegate to the db's #_execute method.
24
+ def execute(sql)
25
+ @db.send(:_execute, self, sql)
26
+ end
27
+ end
28
+
29
+ # Database class for Sequel's mock adapter.
30
+ class Database < Sequel::Database
31
+ set_adapter_scheme :mock
32
+
33
+ # Set the autogenerated primary key integer
34
+ # to be returned when running an insert query.
35
+ # Argument types supported:
36
+ #
37
+ # nil :: Return nil for all inserts
38
+ # Integer :: Starting integer for next insert, with
39
+ # futher inserts getting an incremented
40
+ # value
41
+ # Array :: First insert gets the first value in the
42
+ # array, second gets the second value, etc.
43
+ # Proc :: Called with the insert SQL query, uses
44
+ # the value returned
45
+ # Class :: Should be an Exception subclass, will create a new
46
+ # instance an raise it wrapped in a DatabaseError.
47
+ attr_writer :autoid
48
+
49
+ # Set the columns to set in the dataset when the dataset fetches
50
+ # rows. Argument types supported:
51
+ # nil :: Set no columns
52
+ # Array of Symbols: Used for all datasets
53
+ # Array (otherwise): First retrieval gets the first value in the
54
+ # array, second gets the second value, etc.
55
+ # Proc :: Called with the select SQL query, uses the value
56
+ # returned, which should be an array of symbols
57
+ attr_writer :columns
58
+
59
+ # Set the hashes to yield by execute when retrieving rows.
60
+ # Argument types supported:
61
+ #
62
+ # nil :: Yield no rows
63
+ # Hash :: Always yield a single row with this hash
64
+ # Array of Hashes :: Yield separately for each hash in this array
65
+ # Array (otherwise) :: First retrieval gets the first value
66
+ # in the array, second gets the second value, etc.
67
+ # Proc :: Called with the select SQL query, uses
68
+ # the value returned, which should be a hash or
69
+ # array of hashes.
70
+ # Class :: Should be an Exception subclass, will create a new
71
+ # instance an raise it wrapped in a DatabaseError.
72
+ attr_writer :fetch
73
+
74
+ # Set the number of rows to return from update or delete.
75
+ # Argument types supported:
76
+ #
77
+ # nil :: Return 0 for all updates and deletes
78
+ # Integer :: Used for all updates and deletes
79
+ # Array :: First update/delete gets the first value in the
80
+ # array, second gets the second value, etc.
81
+ # Proc :: Called with the update/delete SQL query, uses
82
+ # the value returned.
83
+ # Class :: Should be an Exception subclass, will create a new
84
+ # instance an raise it wrapped in a DatabaseError.
85
+ attr_writer :numrows
86
+
87
+ # Additional options supported:
88
+ #
89
+ # :autoid :: Call #autoid= with the value
90
+ # :columns :: Call #columns= with the value
91
+ # :fetch :: Call #fetch= with the value
92
+ # :numrows :: Call #numrows= with the value
93
+ # :extend :: A module the object is extended with.
94
+ # :sqls :: The array to store the SQL queries in.
95
+ def initialize(opts=nil)
96
+ super
97
+ self.autoid = opts[:autoid]
98
+ self.columns = opts[:columns]
99
+ self.fetch = opts[:fetch]
100
+ self.numrows = opts[:numrows]
101
+ extend(opts[:extend]) if opts[:extend]
102
+ @sqls = opts[:sqls] || []
103
+ end
104
+
105
+ # Return a related Connection option connecting to the given shard.
106
+ def connect(server)
107
+ Connection.new(self, server, server_opts(server))
108
+ end
109
+
110
+ # Store the sql used for later retrieval with #sqls, and return
111
+ # the appropriate value using either the #autoid, #fetch, or
112
+ # #numrows methods.
113
+ def execute(sql, opts={}, &block)
114
+ synchronize(opts[:server]){|c| _execute(c, sql, opts, &block)}
115
+ end
116
+ alias execute_ddl execute
117
+
118
+ # Store the sql used, and return the value of the #numrows method.
119
+ def execute_dui(sql, opts={})
120
+ execute(sql, opts.merge(:meth=>:numrows))
121
+ end
122
+
123
+ # Store the sql used, and return the value of the #autoid method.
124
+ def execute_insert(sql, opts={})
125
+ execute(sql, opts.merge(:meth=>:autoid))
126
+ end
127
+
128
+ # Return all stored SQL queries, and clear the cache
129
+ # of SQL queries.
130
+ def sqls
131
+ s = @sqls.dup
132
+ @sqls.clear
133
+ s
134
+ end
135
+
136
+ # Enable use of savepoints.
137
+ def supports_savepoints?
138
+ true
139
+ end
140
+
141
+ private
142
+
143
+ def _autoid(sql, v, ds=nil)
144
+ case v
145
+ when Integer
146
+ if ds
147
+ ds.autoid += 1 if ds.autoid.is_a?(Integer)
148
+ else
149
+ @autoid += 1
150
+ end
151
+ v
152
+ else
153
+ _nextres(v, sql, nil)
154
+ end
155
+ end
156
+
157
+ def _execute(c, sql, opts={}, &block)
158
+ sql += " -- args: #{opts[:arguments].inspect}" if opts[:arguments]
159
+ sql += " -- #{@opts[:append]}" if @opts[:append]
160
+ sql += " -- #{c.server}" if c.server != :default
161
+ log_info(sql)
162
+ @sqls << sql
163
+
164
+ ds = opts[:dataset]
165
+ begin
166
+ if block
167
+ columns(ds, sql) if ds
168
+ _fetch(sql, ds._fetch || @fetch, &block)
169
+ elsif meth = opts[:meth]
170
+ if meth == :numrows
171
+ _numrows(sql, ds.numrows || @numrows)
172
+ else
173
+ v = ds.autoid
174
+ _autoid(sql, v || @autoid, (ds if v))
175
+ end
176
+ end
177
+ rescue => e
178
+ raise_error(e)
179
+ end
180
+ end
181
+
182
+ def _fetch(sql, f, &block)
183
+ case f
184
+ when Hash
185
+ yield f.dup
186
+ when Array
187
+ if f.all?{|h| h.is_a?(Hash)}
188
+ f.each{|h| yield h.dup}
189
+ else
190
+ _fetch(sql, f.shift, &block)
191
+ end
192
+ when Proc
193
+ h = f.call(sql)
194
+ if h.is_a?(Hash)
195
+ yield h.dup
196
+ elsif h
197
+ h.each{|h1| yield h1.dup}
198
+ end
199
+ when Class
200
+ if f < Exception
201
+ raise f
202
+ else
203
+ raise Error, "Invalid @autoid/@numrows attribute: #{v.inspect}"
204
+ end
205
+ when nil
206
+ # nothing
207
+ else
208
+ raise Error, "Invalid @fetch attribute: #{f.inspect}"
209
+ end
210
+ end
211
+
212
+ def _nextres(v, sql, default)
213
+ case v
214
+ when Integer
215
+ v
216
+ when Array
217
+ v.empty? ? default : _nextres(v.shift, sql, default)
218
+ when Proc
219
+ v.call(sql)
220
+ when Class
221
+ if v < Exception
222
+ raise v
223
+ else
224
+ raise Error, "Invalid @autoid/@numrows attribute: #{v.inspect}"
225
+ end
226
+ when nil
227
+ default
228
+ else
229
+ raise Error, "Invalid @autoid/@numrows attribute: #{v.inspect}"
230
+ end
231
+ end
232
+
233
+ def _numrows(sql, v)
234
+ _nextres(v, sql, 0)
235
+ end
236
+
237
+ def columns(ds, sql, cs=@columns)
238
+ case cs
239
+ when Array
240
+ unless cs.empty?
241
+ if cs.all?{|c| c.is_a?(Symbol)}
242
+ ds.columns(*cs)
243
+ else
244
+ columns(ds, sql, cs.shift)
245
+ end
246
+ end
247
+ when Proc
248
+ ds.columns(*cs.call(sql))
249
+ when nil
250
+ # nothing
251
+ else
252
+ raise Error, "Invalid @columns attribute: #{cs.inspect}"
253
+ end
254
+ end
255
+
256
+ def disconnect_connection(c)
257
+ end
258
+
259
+ def quote_identifiers_default
260
+ false
261
+ end
262
+
263
+ def identifier_input_method_default
264
+ nil
265
+ end
266
+
267
+ def identifier_input_method_default
268
+ nil
269
+ end
270
+ end
271
+
272
+ class Dataset < Sequel::Dataset
273
+ Database::DatasetClass = self
274
+
275
+ # Override the databases's autoid setting for this dataset
276
+ attr_accessor :autoid
277
+
278
+ # Override the databases's fetch setting for this dataset
279
+ attr_accessor :_fetch
280
+
281
+ # Override the databases's numrows setting for this dataset
282
+ attr_accessor :numrows
283
+
284
+ # If arguments are provided, use them to set the columns
285
+ # for this dataset and return self. Otherwise, use the
286
+ # default Sequel behavior and return the columns.
287
+ def columns(*cs)
288
+ if cs.empty?
289
+ super
290
+ else
291
+ @columns = cs
292
+ self
293
+ end
294
+ end
295
+
296
+ def fetch_rows(sql, &block)
297
+ execute(sql, &block)
298
+ end
299
+
300
+ private
301
+
302
+ def execute(sql, opts={}, &block)
303
+ super(sql, opts.merge(:dataset=>self), &block)
304
+ end
305
+
306
+ def execute_dui(sql, opts={}, &block)
307
+ super(sql, opts.merge(:dataset=>self), &block)
308
+ end
309
+
310
+ def execute_insert(sql, opts={}, &block)
311
+ super(sql, opts.merge(:dataset=>self), &block)
312
+ end
313
+ end
314
+ end
315
+ end
@@ -5,97 +5,63 @@ rescue LoadError
5
5
  end
6
6
  raise(LoadError, "require 'mysql' did not define Mysql::CLIENT_MULTI_RESULTS!\n You are probably using the pure ruby mysql.rb driver,\n which Sequel does not support. You need to install\n the C based adapter, and make sure that the mysql.so\n file is loaded instead of the mysql.rb file.\n") unless defined?(Mysql::CLIENT_MULTI_RESULTS)
7
7
 
8
- Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
8
+ Sequel.require %w'shared/mysql_prepared_statements', 'adapters'
9
9
 
10
10
  module Sequel
11
11
  # Module for holding all MySQL-related classes and modules for Sequel.
12
12
  module MySQL
13
13
  TYPE_TRANSLATOR = tt = Class.new do
14
14
  def boolean(s) s.to_i != 0 end
15
- def blob(s) ::Sequel::SQL::Blob.new(s) end
16
15
  def integer(s) s.to_i end
17
16
  def float(s) s.to_f end
18
- def decimal(s) ::BigDecimal.new(s) end
19
- def date(s) ::Sequel.string_to_date(s) end
20
- def time(s) ::Sequel.string_to_time(s) end
21
- def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
22
- def date_conv(s) ::Sequel::MySQL.convert_date_time(:string_to_date, s) end
23
- def time_conv(s) ::Sequel::MySQL.convert_date_time(:string_to_time, s) end
24
- def timestamp_conv(s) ::Sequel::MySQL.convert_date_time(:database_to_application_timestamp, s) end
25
17
  end.new
26
18
 
27
19
  # Hash with integer keys and callable values for converting MySQL types.
28
20
  MYSQL_TYPES = {}
29
21
  {
30
- [0, 246] => tt.method(:decimal),
31
- [2, 3, 8, 9, 13, 247, 248] => tt.method(:integer),
32
- [4, 5] => tt.method(:float),
33
- [249, 250, 251, 252] => tt.method(:blob)
22
+ [0, 246] => ::BigDecimal.method(:new),
23
+ [2, 3, 8, 9, 13, 247, 248] => tt.method(:integer),
24
+ [4, 5] => tt.method(:float),
25
+ [249, 250, 251, 252] => ::Sequel::SQL::Blob.method(:new)
34
26
  }.each do |k,v|
35
27
  k.each{|n| MYSQL_TYPES[n] = v}
36
28
  end
37
29
 
38
- # Modify the type translator used for the tinyint type based
39
- # on the value given.
40
- def self.convert_tinyint_to_bool=(v)
41
- MYSQL_TYPES[1] = TYPE_TRANSLATOR.method(v ? :boolean : :integer)
42
- @convert_tinyint_to_bool = v
43
- end
44
- self.convert_tinyint_to_bool = convert_tinyint_to_bool
45
-
46
30
  class << self
47
- # By default, Sequel raises an exception if in invalid date or time is used.
48
- # However, if this is set to nil or :nil, the adapter treats dates
49
- # like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
50
- # it returns the strings as is.
51
- attr_reader :convert_invalid_date_time
52
- end
53
-
54
- # Modify the type translators for the date, time, and timestamp types
55
- # depending on the value given.
56
- def self.convert_invalid_date_time=(v)
57
- MYSQL_TYPES[11] = TYPE_TRANSLATOR.method(v == false ? :time : :time_conv)
58
- m = TYPE_TRANSLATOR.method(v == false ? :date : :date_conv)
59
- [10, 14].each{|i| MYSQL_TYPES[i] = m}
60
- m = TYPE_TRANSLATOR.method(v == false ? :timestamp : :timestamp_conv)
61
- [7, 12].each{|i| MYSQL_TYPES[i] = m}
62
- @convert_invalid_date_time = v
31
+ # Whether to convert invalid date time values by default.
32
+ attr_accessor :convert_invalid_date_time
63
33
  end
64
34
  self.convert_invalid_date_time = false
65
35
 
66
- # If convert_invalid_date_time is nil, :nil, or :string and
67
- # the conversion raises an InvalidValue exception, return v
68
- # if :string and nil otherwise.
69
- def self.convert_date_time(meth, v)
70
- begin
71
- Sequel.send(meth, v)
72
- rescue InvalidValue
73
- case @convert_invalid_date_time
74
- when nil, :nil
75
- nil
76
- when :string
77
- v
78
- else
79
- raise
80
- end
81
- end
82
- end
83
-
84
36
  # Database class for MySQL databases used with Sequel.
85
37
  class Database < Sequel::Database
86
38
  include Sequel::MySQL::DatabaseMethods
39
+ include Sequel::MySQL::PreparedStatements::DatabaseMethods
87
40
 
88
41
  # Mysql::Error messages that indicate the current connection should be disconnected
89
42
  MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away|Lost connection to MySQL server during query)/
90
43
 
91
44
  set_adapter_scheme :mysql
45
+
46
+ # Hash of conversion procs for the current database
47
+ attr_reader :conversion_procs
48
+ #
49
+ # Whether to convert tinyint columns to bool for the current database
50
+ attr_reader :convert_tinyint_to_bool
51
+
52
+ # By default, Sequel raises an exception if in invalid date or time is used.
53
+ # However, if this is set to nil or :nil, the adapter treats dates
54
+ # like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
55
+ # it returns the strings as is.
56
+ attr_reader :convert_invalid_date_time
92
57
 
93
- # Support stored procedures on MySQL
94
- def call_sproc(name, opts={}, &block)
95
- args = opts[:args] || []
96
- execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block)
58
+ def initialize(opts={})
59
+ super
60
+ @conversion_procs = MYSQL_TYPES.dup
61
+ self.convert_tinyint_to_bool = Sequel::MySQL.convert_tinyint_to_bool
62
+ self.convert_invalid_date_time = Sequel::MySQL.convert_invalid_date_time
97
63
  end
98
-
64
+
99
65
  # Connect to the database. In addition to the usual database options,
100
66
  # the following options have effect:
101
67
  #
@@ -139,7 +105,7 @@ module Sequel
139
105
  opts[:user],
140
106
  opts[:password],
141
107
  opts[:database],
142
- opts[:port],
108
+ (opts[:port].to_i if opts[:port]),
143
109
  opts[:socket],
144
110
  Mysql::CLIENT_MULTI_RESULTS +
145
111
  Mysql::CLIENT_MULTI_STATEMENTS +
@@ -161,30 +127,31 @@ module Sequel
161
127
 
162
128
  sqls.each{|sql| log_yield(sql){conn.query(sql)}}
163
129
 
164
- class << conn
165
- attr_accessor :prepared_statements
166
- end
167
- conn.prepared_statements = {}
130
+ add_prepared_statements_cache(conn)
168
131
  conn
169
132
  end
170
133
 
171
- # Returns instance of Sequel::MySQL::Dataset with the given options.
172
- def dataset(opts = nil)
173
- MySQL::Dataset.new(self, opts)
134
+ # Modify the type translators for the date, time, and timestamp types
135
+ # depending on the value given.
136
+ def convert_invalid_date_time=(v)
137
+ m0 = ::Sequel.method(:string_to_time)
138
+ @conversion_procs[11] = (v != false) ? lambda{|v| convert_date_time(v, &m0)} : m0
139
+ m1 = ::Sequel.method(:string_to_date)
140
+ m = (v != false) ? lambda{|v| convert_date_time(v, &m1)} : m1
141
+ [10, 14].each{|i| @conversion_procs[i] = m}
142
+ m2 = method(:to_application_timestamp)
143
+ m = (v != false) ? lambda{|v| convert_date_time(v, &m2)} : m2
144
+ [7, 12].each{|i| @conversion_procs[i] = m}
145
+ @convert_invalid_date_time = v
174
146
  end
175
-
176
- # Executes the given SQL using an available connection, yielding the
177
- # connection if the block is given.
178
- def execute(sql, opts={}, &block)
179
- if opts[:sproc]
180
- call_sproc(sql, opts, &block)
181
- elsif sql.is_a?(Symbol)
182
- execute_prepared_statement(sql, opts, &block)
183
- else
184
- synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
185
- end
147
+
148
+ # Modify the type translator used for the tinyint type based
149
+ # on the value given.
150
+ def convert_tinyint_to_bool=(v)
151
+ @conversion_procs[1] = TYPE_TRANSLATOR.method(v ? :boolean : :integer)
152
+ @convert_tinyint_to_bool = v
186
153
  end
187
-
154
+
188
155
  # Return the version of the MySQL server two which we are connecting.
189
156
  def server_version(server=nil)
190
157
  @server_version ||= (synchronize(server){|conn| conn.server_version if conn.respond_to?(:server_version)} || super)
@@ -220,7 +187,7 @@ module Sequel
220
187
  end
221
188
  end
222
189
  rescue Mysql::Error => e
223
- raise_error(e, :disconnect=>MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message))
190
+ raise_error(e)
224
191
  ensure
225
192
  r.free if r
226
193
  # Use up all results to avoid a commands out of sync message.
@@ -244,10 +211,34 @@ module Sequel
244
211
  :query
245
212
  end
246
213
 
214
+ # If convert_invalid_date_time is nil, :nil, or :string and
215
+ # the conversion raises an InvalidValue exception, return v
216
+ # if :string and nil otherwise.
217
+ def convert_date_time(v)
218
+ begin
219
+ yield v
220
+ rescue InvalidValue
221
+ case @convert_invalid_date_time
222
+ when nil, :nil
223
+ nil
224
+ when :string
225
+ v
226
+ else
227
+ raise
228
+ end
229
+ end
230
+ end
231
+
247
232
  # The MySQL adapter main error class is Mysql::Error
248
233
  def database_error_classes
249
234
  [Mysql::Error]
250
235
  end
236
+
237
+ # Raise a disconnect error if the exception message matches the list
238
+ # of recognized exceptions.
239
+ def disconnect_error?(e, opts)
240
+ super || (e.is_a?(::Mysql::Error) && MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message))
241
+ end
251
242
 
252
243
  # The database name when using the native adapter is always stored in
253
244
  # the :database option.
@@ -262,97 +253,18 @@ module Sequel
262
253
  nil
263
254
  end
264
255
 
265
- # Executes a prepared statement on an available connection. If the
266
- # prepared statement already exists for the connection and has the same
267
- # SQL, reuse it, otherwise, prepare the new statement. Because of the
268
- # usual MySQL stupidity, we are forced to name arguments via separate
269
- # SET queries. Use @sequel_arg_N (for N starting at 1) for these
270
- # arguments.
271
- def execute_prepared_statement(ps_name, opts, &block)
272
- args = opts[:arguments]
273
- ps = prepared_statements[ps_name]
274
- sql = ps.prepared_sql
275
- synchronize(opts[:server]) do |conn|
276
- unless conn.prepared_statements[ps_name] == sql
277
- conn.prepared_statements[ps_name] = sql
278
- _execute(conn, "PREPARE #{ps_name} FROM '#{::Mysql.quote(sql)}'", opts)
279
- end
280
- i = 0
281
- _execute(conn, "SET " + args.map {|arg| "@sequel_arg_#{i+=1} = #{literal(arg)}"}.join(", "), opts) unless args.empty?
282
- _execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
283
- end
284
- end
285
-
286
256
  # Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
287
257
  def schema_column_type(db_type)
288
- Sequel::MySQL.convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
258
+ convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
289
259
  end
290
260
  end
291
261
 
292
262
  # Dataset class for MySQL datasets accessed via the native driver.
293
263
  class Dataset < Sequel::Dataset
294
264
  include Sequel::MySQL::DatasetMethods
295
- include StoredProcedures
296
-
297
- # Methods to add to MySQL prepared statement calls without using a
298
- # real database prepared statement and bound variables.
299
- module CallableStatementMethods
300
- # Extend given dataset with this module so subselects inside subselects in
301
- # prepared statements work.
302
- def subselect_sql(ds)
303
- ps = ds.to_prepared_statement(:select)
304
- ps.extend(CallableStatementMethods)
305
- ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
306
- ps.prepared_args = prepared_args
307
- ps.prepared_sql
308
- end
309
- end
310
-
311
- # Methods for MySQL prepared statements using the native driver.
312
- module PreparedStatementMethods
313
- include Sequel::Dataset::UnnumberedArgumentMapper
314
-
315
- private
316
-
317
- # Execute the prepared statement with the bind arguments instead of
318
- # the given SQL.
319
- def execute(sql, opts={}, &block)
320
- super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
321
- end
322
-
323
- # Same as execute, explicit due to intricacies of alias and super.
324
- def execute_dui(sql, opts={}, &block)
325
- super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
326
- end
327
- end
328
-
329
- # Methods for MySQL stored procedures using the native driver.
330
- module StoredProcedureMethods
331
- include Sequel::Dataset::StoredProcedureMethods
332
-
333
- private
334
-
335
- # Execute the database stored procedure with the stored arguments.
336
- def execute(sql, opts={}, &block)
337
- super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
338
- end
339
-
340
- # Same as execute, explicit due to intricacies of alias and super.
341
- def execute_dui(sql, opts={}, &block)
342
- super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
343
- end
344
- end
345
-
346
- # MySQL is different in that it supports prepared statements but not bound
347
- # variables outside of prepared statements. The default implementation
348
- # breaks the use of subselects in prepared statements, so extend the
349
- # temporary prepared statement that this creates with a module that
350
- # fixes it.
351
- def call(type, bind_arguments={}, *values, &block)
352
- ps = to_prepared_statement(type, values)
353
- ps.extend(CallableStatementMethods)
354
- ps.call(bind_arguments, &block)
355
- end
265
+ include Sequel::MySQL::PreparedStatements::DatasetMethods
266
+
267
+ Database::DatasetClass = self
356
268
 
357
269
  # Delete rows matching this dataset
358
270
  def delete
@@ -365,11 +277,12 @@ module Sequel
365
277
  def fetch_rows(sql, &block)
366
278
  execute(sql) do |r|
367
279
  i = -1
280
+ cps = db.conversion_procs
368
281
  cols = r.fetch_fields.map do |f|
369
282
  # Pretend tinyint is another integer type if its length is not 1, to
370
283
  # avoid casting to boolean if Sequel::MySQL.convert_tinyint_to_bool
371
284
  # is set.
372
- type_proc = f.type == 1 && f.length != 1 ? MYSQL_TYPES[2] : MYSQL_TYPES[f.type]
285
+ type_proc = f.type == 1 && f.length != 1 ? cps[2] : cps[f.type]
373
286
  [output_identifier(f.name), type_proc, i+=1]
374
287
  end
375
288
  @columns = cols.map{|c| c.first}
@@ -395,18 +308,6 @@ module Sequel
395
308
  execute_dui(insert_sql(*values)){|c| return c.insert_id}
396
309
  end
397
310
 
398
- # Store the given type of prepared statement in the associated database
399
- # with the given name.
400
- def prepare(type, name=nil, *values)
401
- ps = to_prepared_statement(type, values)
402
- ps.extend(PreparedStatementMethods)
403
- if name
404
- ps.prepared_statement_name = name
405
- db.prepared_statements[name] = ps
406
- end
407
- ps
408
- end
409
-
410
311
  # Replace (update or insert) the matching row.
411
312
  def replace(*args)
412
313
  execute_dui(replace_sql(*args)){|c| return c.insert_id}
@@ -450,11 +351,6 @@ module Sequel
450
351
  "'#{::Mysql.quote(v)}'"
451
352
  end
452
353
 
453
- # Extend the dataset with the MySQL stored procedure methods.
454
- def prepare_extend_sproc(ds)
455
- ds.extend(StoredProcedureMethods)
456
- end
457
-
458
354
  # Yield each row of the given result set r with columns cols
459
355
  # as a hash with symbol keys
460
356
  def yield_rows(r, cols)