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
@@ -93,13 +93,14 @@ module Sequel
93
93
  def bytea(s) ::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s)) end
94
94
  def integer(s) s.to_i end
95
95
  def float(s) s.to_f end
96
- def numeric(s) ::BigDecimal.new(s) end
97
- def date(s) ::Sequel.string_to_date(s) end
98
- def date_iso(s) ::Date.new(*s.split("-").map{|x| x.to_i}) end
99
- def time(s) ::Sequel.string_to_time(s) end
100
- def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
96
+ def date(s) ::Date.new(*s.split("-").map{|x| x.to_i}) end
101
97
  end.new
102
98
 
99
+ # Hash with type name symbols and callable values for converting PostgreSQL types.
100
+ # Non-builtin types that don't have fixed numbers should use this to register
101
+ # conversion procs.
102
+ PG_NAMED_TYPES = {}
103
+
103
104
  # Hash with integer keys and callable values for converting PostgreSQL types.
104
105
  PG_TYPES = {}
105
106
  {
@@ -107,9 +108,8 @@ module Sequel
107
108
  [17] => tt.method(:bytea),
108
109
  [20, 21, 22, 23, 26] => tt.method(:integer),
109
110
  [700, 701] => tt.method(:float),
110
- [790, 1700] => tt.method(:numeric),
111
- [1083, 1266] => tt.method(:time),
112
- [1114, 1184] => tt.method(:timestamp)
111
+ [790, 1700] => ::BigDecimal.method(:new),
112
+ [1083, 1266] => ::Sequel.method(:string_to_time),
113
113
  }.each do |k,v|
114
114
  k.each{|n| PG_TYPES[n] = v}
115
115
  end
@@ -123,7 +123,7 @@ module Sequel
123
123
 
124
124
  # Modify the type translator for the date type depending on the value given.
125
125
  def self.use_iso_date_format=(v)
126
- PG_TYPES[1082] = TYPE_TRANSLATOR.method(v ? :date_iso : :date)
126
+ PG_TYPES[1082] = v ? TYPE_TRANSLATOR.method(:date) : Sequel.method(:string_to_date)
127
127
  @use_iso_date_format = v
128
128
  end
129
129
  self.use_iso_date_format = true
@@ -131,7 +131,10 @@ module Sequel
131
131
  # PGconn subclass for connection specific methods used with the
132
132
  # pg, postgres, or postgres-pr driver.
133
133
  class Adapter < ::PGconn
134
+ DISCONNECT_ERROR_RE = /\Acould not receive data from server: Software caused connection abort/
135
+
134
136
  include Sequel::Postgres::AdapterMethods
137
+
135
138
  self.translate_results = false if respond_to?(:translate_results=)
136
139
 
137
140
  # Hash of prepared statements for this connection. Keys are
@@ -150,20 +153,23 @@ module Sequel
150
153
  end
151
154
  @prepared_statements = {} if SEQUEL_POSTGRES_USES_PG
152
155
  end
153
-
156
+
154
157
  # Raise a Sequel::DatabaseDisconnectError if a PGError is raised and
155
158
  # the connection status cannot be determined or it is not OK.
156
159
  def check_disconnect_errors
157
160
  begin
158
161
  yield
159
162
  rescue PGError =>e
163
+ disconnect = false
160
164
  begin
161
165
  s = status
162
166
  rescue PGError
163
- raise Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError)
167
+ disconnect = true
164
168
  end
165
169
  status_ok = (s == Adapter::CONNECTION_OK)
166
- status_ok ? raise : raise(Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError))
170
+ disconnect ||= !status_ok
171
+ disconnect ||= e.message =~ DISCONNECT_ERROR_RE
172
+ disconnect ? raise(Sequel.convert_exception_class(e, Sequel::DatabaseDisconnectError)) : raise
167
173
  ensure
168
174
  block if status_ok
169
175
  end
@@ -176,7 +182,7 @@ module Sequel
176
182
  begin
177
183
  block_given? ? yield(q) : q.cmd_tuples
178
184
  ensure
179
- q.clear
185
+ q.clear if q
180
186
  end
181
187
  end
182
188
 
@@ -194,6 +200,10 @@ module Sequel
194
200
  include Sequel::Postgres::DatabaseMethods
195
201
 
196
202
  set_adapter_scheme :postgres
203
+
204
+ # A hash of conversion procs, keyed by type integer (oid) and
205
+ # having callable values for the conversion proc for that type.
206
+ attr_reader :conversion_procs
197
207
 
198
208
  # Add the primary_keys and primary_key_sequences instance variables,
199
209
  # so we can get the correct return values for inserted rows.
@@ -225,14 +235,10 @@ module Sequel
225
235
  end
226
236
  conn.db = self
227
237
  conn.apply_connection_settings
238
+ @conversion_procs ||= get_conversion_procs(conn)
228
239
  conn
229
240
  end
230
241
 
231
- # Return instance of Sequel::Postgres::Dataset with the given options.
232
- def dataset(opts = nil)
233
- Postgres::Dataset.new(self, opts)
234
- end
235
-
236
242
  # Execute the given SQL with the given args on an available connection.
237
243
  def execute(sql, opts={}, &block)
238
244
  check_database_errors do
@@ -253,6 +259,119 @@ module Sequel
253
259
  end
254
260
  end
255
261
 
262
+ if SEQUEL_POSTGRES_USES_PG
263
+ # +copy_table+ uses PostgreSQL's +COPY+ SQL statement to return formatted
264
+ # results directly to the caller. This method is only supported if pg is the
265
+ # underlying ruby driver. This method should only be called if you want
266
+ # results returned to the client. If you are using +COPY FROM+ or +COPY TO+
267
+ # with a filename, you should just use +run+ instead of this method. This
268
+ # method does not currently support +COPY FROM STDIN+, but that may be supported
269
+ # in the future.
270
+ #
271
+ # The table argument supports the following types:
272
+ #
273
+ # String :: Uses the first argument directly as literal SQL. If you are using
274
+ # a version of PostgreSQL before 9.0, you will probably want to
275
+ # use a string if you are using any options at all, as the syntax
276
+ # Sequel uses for options is only compatible with PostgreSQL 9.0+.
277
+ # Dataset :: Uses a query instead of a table name when copying.
278
+ # other :: Uses a table name (usually a symbol) when copying.
279
+ #
280
+ # The following options are respected:
281
+ #
282
+ # :format :: The format to use. text is the default, so this should be :csv or :binary.
283
+ # :options :: An options SQL string to use, which should contain comma separated options.
284
+ # :server :: The server on which to run the query.
285
+ #
286
+ # If a block is provided, the method continually yields to the block, one yield
287
+ # per row. If a block is not provided, a single string is returned with all
288
+ # of the data.
289
+ def copy_table(table, opts={})
290
+ sql = if table.is_a?(String)
291
+ sql = table
292
+ else
293
+ if opts[:options] || opts[:format]
294
+ options = " ("
295
+ options << "FORMAT #{opts[:format]}" if opts[:format]
296
+ options << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
297
+ options << ')'
298
+ end
299
+ table = if table.is_a?(::Sequel::Dataset)
300
+ "(#{table.sql})"
301
+ else
302
+ literal(table)
303
+ end
304
+ sql = "COPY #{table} TO STDOUT#{options}"
305
+ end
306
+ synchronize(opts[:server]) do |conn|
307
+ conn.execute(sql)
308
+ begin
309
+ if block_given?
310
+ while buf = conn.get_copy_data
311
+ yield buf
312
+ end
313
+ nil
314
+ else
315
+ b = ''
316
+ b << buf while buf = conn.get_copy_data
317
+ b
318
+ end
319
+ ensure
320
+ raise DatabaseDisconnectError, "disconnecting as a partial COPY may leave the connection in an unusable state" if buf
321
+ end
322
+ end
323
+ end
324
+
325
+ # Listens on the given channel (or multiple channels if channel is an array), waiting for notifications.
326
+ # After a notification is received, or the timeout has passed, stops listening to the channel. Options:
327
+ #
328
+ # :after_listen :: An object that responds to +call+ that is called with the underlying connection after the LISTEN
329
+ # statement is sent, but before the connection starts waiting for notifications.
330
+ # :loop :: Whether to continually wait for notifications, instead of just waiting for a single
331
+ # notification. If this option is given, a block must be provided. If this object responds to call, it is
332
+ # called with the underlying connection after each notification is received (after the block is called).
333
+ # If a :timeout option is used, and a callable object is given, the object will also be called if the
334
+ # timeout expires. If :loop is used and you want to stop listening, you can either break from inside the
335
+ # block given to #listen, or you can throw :stop from inside the :loop object's call method or the block.
336
+ # :server :: The server on which to listen, if the sharding support is being used.
337
+ # :timeout :: How long to wait for a notification, in seconds (can provide a float value for
338
+ # fractional seconds). If not given or nil, waits indefinitely.
339
+ #
340
+ # This method is only supported if pg is used as the underlying ruby driver. It returns the
341
+ # channel the notification was sent to (as a string), unless :loop was used, in which case it returns nil.
342
+ # If a block is given, it is yielded 3 arguments:
343
+ # * the channel the notification was sent to (as a string)
344
+ # * the backend pid of the notifier (as an integer),
345
+ # * and the payload of the notification (as a string or nil).
346
+ def listen(channels, opts={}, &block)
347
+ check_database_errors do
348
+ synchronize(opts[:server]) do |conn|
349
+ begin
350
+ channels = Array(channels)
351
+ channels.each{|channel| conn.execute("LISTEN #{channel}")}
352
+ opts[:after_listen].call(conn) if opts[:after_listen]
353
+ timeout = opts[:timeout] ? [opts[:timeout]] : []
354
+ if l = opts[:loop]
355
+ raise Error, 'calling #listen with :loop requires a block' unless block
356
+ loop_call = l.respond_to?(:call)
357
+ catch(:stop) do
358
+ loop do
359
+ conn.wait_for_notify(*timeout, &block)
360
+ l.call(conn) if loop_call
361
+ end
362
+ end
363
+ nil
364
+ else
365
+ conn.wait_for_notify(*timeout, &block)
366
+ end
367
+ ensure
368
+ conn.execute("UNLISTEN *")
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end
374
+
256
375
  private
257
376
 
258
377
  # Convert exceptions raised from the block into DatabaseErrors.
@@ -305,12 +424,29 @@ module Sequel
305
424
  end
306
425
  end
307
426
  end
427
+
428
+ # Return the conversion procs hash to use for this database
429
+ def get_conversion_procs(conn)
430
+ procs = PG_TYPES.dup
431
+ procs[1114] = method(:to_application_timestamp)
432
+ procs[1184] = method(:to_application_timestamp)
433
+ conn.execute("SELECT oid, typname FROM pg_type where typtype = 'b'") do |res|
434
+ res.ntuples.times do |recnum|
435
+ if pr = PG_NAMED_TYPES[res.getvalue(recnum, 1).untaint.to_sym]
436
+ procs[res.getvalue(recnum, 0).to_i] ||= pr
437
+ end
438
+ end
439
+ end
440
+ procs
441
+ end
308
442
  end
309
443
 
310
444
  # Dataset class for PostgreSQL datasets that use the pg, postgres, or
311
445
  # postgres-pr driver.
312
446
  class Dataset < Sequel::Dataset
313
447
  include Sequel::Postgres::DatasetMethods
448
+
449
+ Database::DatasetClass = self
314
450
 
315
451
  # Yield all rows returned by executing the given SQL and converting
316
452
  # the types.
@@ -372,11 +508,17 @@ module Sequel
372
508
  end
373
509
  LiteralString.new("#{prepared_arg_placeholder}#{i}#{"::#{type}" if type}")
374
510
  end
511
+
512
+ # Always assume a prepared argument.
513
+ def prepared_arg?(k)
514
+ true
515
+ end
375
516
  end
376
517
 
377
518
  # Allow use of bind arguments for PostgreSQL using the pg driver.
378
519
  module BindArgumentMethods
379
520
  include ArgumentMapper
521
+ include ::Sequel::Postgres::DatasetMethods::PreparedStatementMethods
380
522
 
381
523
  private
382
524
 
@@ -400,7 +542,12 @@ module Sequel
400
542
  # pg driver.
401
543
  module PreparedStatementMethods
402
544
  include BindArgumentMethods
403
- include ::Sequel::Postgres::DatasetMethods::PreparedStatementMethods
545
+
546
+ # Raise a more obvious error if you attempt to call a unnamed prepared statement.
547
+ def call(*)
548
+ raise Error, "Cannot call prepared statement without a name" if prepared_statement_name.nil?
549
+ super
550
+ end
404
551
 
405
552
  private
406
553
 
@@ -427,7 +574,7 @@ module Sequel
427
574
  ps.extend(BindArgumentMethods)
428
575
  ps.call(bind_vars, &block)
429
576
  end
430
-
577
+
431
578
  # Prepare the given type of statement with the given name, and store
432
579
  # it in the database to be called later.
433
580
  def prepare(type, name=nil, *values)
@@ -483,8 +630,9 @@ module Sequel
483
630
  # field numers, type conversion procs, and name symbol arrays.
484
631
  def fetch_rows_set_cols(res)
485
632
  cols = []
633
+ procs = db.conversion_procs
486
634
  res.nfields.times do |fieldnum|
487
- cols << [fieldnum, PG_TYPES[res.ftype(fieldnum)], output_identifier(res.fname(fieldnum))]
635
+ cols << [fieldnum, procs[res.ftype(fieldnum)], output_identifier(res.fname(fieldnum))]
488
636
  end
489
637
  @columns = cols.map{|c| c.at(2)}
490
638
  cols
@@ -1,21 +1,17 @@
1
1
  module Sequel
2
2
  module Access
3
3
  module DatabaseMethods
4
+ # Access uses type :access as the database_type
4
5
  def database_type
5
6
  :access
6
7
  end
7
8
 
8
- def dataset(opts = nil)
9
- ds = super
10
- ds.extend(DatasetMethods)
11
- ds
12
- end
13
-
14
9
  # Doesn't work, due to security restrictions on MSysObjects
15
10
  def tables
16
11
  from(:MSysObjects).filter(:Type=>1, :Flags=>0).select_map(:Name).map{|x| x.to_sym}
17
12
  end
18
13
 
14
+ # Access uses type Counter for an autoincrementing keys
19
15
  def serial_primary_key_options
20
16
  {:primary_key => true, :type=>:Counter}
21
17
  end
@@ -34,16 +30,35 @@ module Sequel
34
30
  module DatasetMethods
35
31
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'limit distinct columns from join where group order having compounds')
36
32
 
33
+ # Access doesn't support INTERSECT or EXCEPT
37
34
  def supports_intersect_except?
38
35
  false
39
36
  end
40
37
 
41
38
  private
42
39
 
40
+ # Access uses # to quote dates
41
+ def literal_date(d)
42
+ d.strftime('#%Y-%m-%d#')
43
+ end
44
+
45
+ # Access uses # to quote datetimes
46
+ def literal_datetime(t)
47
+ t.strftime('#%Y-%m-%d %H:%M:%S#')
48
+ end
49
+ alias literal_time literal_datetime
50
+
51
+ # Access uses TOP for limits
52
+ def select_limit_sql(sql)
53
+ sql << " TOP #{@opts[:limit]}" if @opts[:limit]
54
+ end
55
+
56
+ # Access uses [] for quoting identifiers
43
57
  def quoted_identifier(v)
44
58
  "[#{v}]"
45
59
  end
46
60
 
61
+ # Access requires the limit clause come before other clauses
47
62
  def select_clause_methods
48
63
  SELECT_CLAUSE_METHODS
49
64
  end
@@ -0,0 +1,288 @@
1
+ Sequel.require 'adapters/utils/emulate_offset_with_row_number'
2
+
3
+ module Sequel
4
+ module DB2
5
+ @use_clob_as_blob = true
6
+
7
+ class << self
8
+ # Whether to use clob as the generic File type, true by default.
9
+ attr_accessor :use_clob_as_blob
10
+ end
11
+
12
+ module DatabaseMethods
13
+ AUTOINCREMENT = 'GENERATED ALWAYS AS IDENTITY'.freeze
14
+ NOT_NULL = ' NOT NULL'.freeze
15
+ NULL = ''.freeze
16
+
17
+ # DB2 always uses :db2 as it's database type
18
+ def database_type
19
+ :db2
20
+ end
21
+
22
+ # Return the database version as a string. Don't rely on this,
23
+ # it may return an integer in the future.
24
+ def db2_version
25
+ return @db2_version if @db2_version
26
+ @db2_version = metadata_dataset.with_sql("select service_level from sysibmadm.env_inst_info").first[:service_level]
27
+ end
28
+ alias_method :server_version, :db2_version
29
+
30
+ # Use SYSIBM.SYSCOLUMNS to get the information on the tables.
31
+ def schema_parse_table(table, opts = {})
32
+ m = output_identifier_meth(opts[:dataset])
33
+ im = input_identifier_meth(opts[:dataset])
34
+ metadata_dataset.with_sql("SELECT * FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = #{literal(im.call(table))} ORDER BY COLNO").
35
+ collect do |column|
36
+ column[:db_type] = column.delete(:typename)
37
+ if column[:db_type] == "DECIMAL"
38
+ column[:db_type] << "(#{column[:longlength]},#{column[:scale]})"
39
+ end
40
+ column[:allow_null] = column.delete(:nulls) == 'Y'
41
+ column[:primary_key] = column.delete(:identity) == 'Y' || !column[:keyseq].nil?
42
+ column[:type] = schema_column_type(column[:db_type])
43
+ [ m.call(column.delete(:name)), column]
44
+ end
45
+ end
46
+
47
+ # Use SYSCAT.TABLES to get the tables for the database
48
+ def tables
49
+ metadata_dataset.
50
+ with_sql("SELECT TABNAME FROM SYSCAT.TABLES WHERE TYPE='T' AND OWNER = #{literal(input_identifier_meth.call(opts[:user]))}").
51
+ all.map{|h| output_identifier_meth.call(h[:tabname]) }
52
+ end
53
+
54
+ # Use SYSCAT.TABLES to get the views for the database
55
+ def views
56
+ metadata_dataset.
57
+ with_sql("SELECT TABNAME FROM SYSCAT.TABLES WHERE TYPE='V' AND OWNER = #{literal(input_identifier_meth.call(opts[:user]))}").
58
+ all.map{|h| output_identifier_meth.call(h[:tabname]) }
59
+ end
60
+
61
+ # Use SYSCAT.INDEXES to get the indexes for the table
62
+ def indexes(table, opts = {})
63
+ metadata_dataset.
64
+ with_sql("SELECT INDNAME,UNIQUERULE,MADE_UNIQUE,SYSTEM_REQUIRED FROM SYSCAT.INDEXES WHERE TABNAME = #{literal(input_identifier_meth.call(table))}").
65
+ all.map{|h| Hash[ h.map{|k,v| [k.to_sym, v]} ] }
66
+ end
67
+
68
+ private
69
+
70
+ # Handle DB2 specific alter table operations.
71
+ def alter_table_sql(table, op)
72
+ case op[:op]
73
+ when :add_column
74
+ if op[:primary_key] && op[:auto_increment] && op[:type] == Integer
75
+ [
76
+ "ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op.merge(:auto_increment=>false, :primary_key=>false, :default=>0, :null=>false))}",
77
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{literal(op[:name])} DROP DEFAULT",
78
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{literal(op[:name])} SET #{AUTOINCREMENT}"
79
+ ]
80
+ else
81
+ "ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
82
+ end
83
+ when :drop_column
84
+ "ALTER TABLE #{quote_schema_table(table)} DROP #{column_definition_sql(op)}"
85
+ when :rename_column # renaming is only possible after db2 v9.7
86
+ "ALTER TABLE #{quote_schema_table(table)} RENAME COLUMN #{quote_identifier(op[:name])} TO #{quote_identifier(op[:new_name])}"
87
+ when :set_column_type
88
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} SET DATA TYPE #{type_literal(op)}"
89
+ when :set_column_default
90
+ "ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(op[:name])} SET DEFAULT #{literal(op[:default])}"
91
+ when :add_constraint
92
+ if op[:type] == :unique
93
+ sqls = op[:columns].map{|c| ["ALTER TABLE #{quote_schema_table(table)} ALTER COLUMN #{quote_identifier(c)} SET NOT NULL", reorg_sql(table)]}
94
+ sqls << super
95
+ sqls.flatten
96
+ else
97
+ super
98
+ end
99
+ else
100
+ super
101
+ end
102
+ end
103
+
104
+ # DB2 uses an identity column for autoincrement.
105
+ def auto_increment_sql
106
+ AUTOINCREMENT
107
+ end
108
+
109
+ # Add null/not null SQL fragment to column creation SQL.
110
+ def column_definition_null_sql(sql, column)
111
+ null = column.fetch(:null, column[:allow_null])
112
+ null = false if column[:primary_key]
113
+
114
+ sql << NOT_NULL if null == false
115
+ sql << NULL if null == true
116
+ end
117
+
118
+ # Supply columns with NOT NULL if they are part of a composite
119
+ # primary/foreign key
120
+ def column_list_sql(g)
121
+ ks = []
122
+ g.constraints.each{|c| ks = c[:columns] if [:primary_key, :foreign_key].include? c[:type]}
123
+ g.columns.each{|c| c[:null] = false if ks.include?(c[:name]) }
124
+ super
125
+ end
126
+
127
+ # Here we use DGTT which has most backward compatibility, which uses
128
+ # DECLARE instead of CREATE. CGTT can only be used after version 9.7.
129
+ # http://www.ibm.com/developerworks/data/library/techarticle/dm-0912globaltemptable/
130
+ def create_table_sql(name, generator, options)
131
+ if options[:temp]
132
+ "DECLARE GLOBAL TEMPORARY TABLE #{quote_identifier(name)} (#{column_list_sql(generator)})"
133
+ else
134
+ super
135
+ end
136
+ end
137
+
138
+ # DB2 has issues with quoted identifiers, so
139
+ # turn off database quoting by default.
140
+ def quote_identifiers_default
141
+ false
142
+ end
143
+
144
+ # DB2 uses RENAME TABLE to rename tables.
145
+ def rename_table_sql(name, new_name)
146
+ "RENAME TABLE #{quote_schema_table(name)} TO #{quote_schema_table(new_name)}"
147
+ end
148
+
149
+ # Run the REORG TABLE command for the table, necessary when
150
+ # the table has been altered.
151
+ def reorg(table)
152
+ synchronize(opts[:server]){|c| c.execute(reorg_sql(table))}
153
+ end
154
+
155
+ # The SQL to use for REORGing a table.
156
+ def reorg_sql(table)
157
+ "CALL ADMIN_CMD(#{literal("REORG TABLE #{table}")})"
158
+ end
159
+
160
+ # We uses the clob type by default for Files.
161
+ # Note: if user select to use blob, then insert statement should use
162
+ # use this for blob value:
163
+ # cast(X'fffefdfcfbfa' as blob(2G))
164
+ def type_literal_generic_file(column)
165
+ ::Sequel::DB2::use_clob_as_blob ? :clob : :blob
166
+ end
167
+
168
+ # DB2 uses smallint to store booleans.
169
+ def type_literal_generic_trueclass(column)
170
+ :smallint
171
+ end
172
+ alias type_literal_generic_falseclass type_literal_generic_trueclass
173
+ end
174
+
175
+ module DatasetMethods
176
+ include EmulateOffsetWithRowNumber
177
+
178
+ BITWISE_METHOD_MAP = {:& =>:BITAND, :| => :BITOR, :^ => :BITXOR, :'B~'=>:BITNOT}
179
+ BOOL_TRUE = '1'.freeze
180
+ BOOL_FALSE = '0'.freeze
181
+
182
+ # DB2 casts strings using RTRIM and CHAR instead of VARCHAR.
183
+ def cast_sql(expr, type)
184
+ type == String ? "RTRIM(CHAR(#{literal(expr)}))" : super
185
+ end
186
+
187
+ # Handle DB2 specific LIKE and bitwise operator support, and
188
+ # emulate the extract method, which DB2 doesn't natively support.
189
+ def complex_expression_sql(op, args)
190
+ case op
191
+ when :ILIKE
192
+ super(:LIKE, [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1)) ])
193
+ when :"NOT ILIKE"
194
+ super(:"NOT LIKE", [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1)) ])
195
+ when :&, :|, :^
196
+ # works with db2 v9.5 and after
197
+ op = BITWISE_METHOD_MAP[op]
198
+ complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(op, a, b))}
199
+ when :<<
200
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
201
+ when :>>
202
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
203
+ when :'B~'
204
+ literal(SQL::Function.new(:BITNOT, *args))
205
+ when :extract
206
+ "#{args.at(0)}(#{literal(args.at(1))})"
207
+ else
208
+ super
209
+ end
210
+ end
211
+
212
+ # DB2 does not support IS TRUE.
213
+ def supports_is_true?
214
+ false
215
+ end
216
+
217
+ # DB2 does not support multiple columns in IN.
218
+ def supports_multiple_column_in?
219
+ false
220
+ end
221
+
222
+ # DB2 only allows * in SELECT if it is the only thing being selected.
223
+ def supports_select_all_and_column?
224
+ false
225
+ end
226
+
227
+ # DB2 does not support fractional seconds in timestamps.
228
+ def supports_timestamp_usecs?
229
+ false
230
+ end
231
+
232
+ # DB2 supports window functions
233
+ def supports_window_functions?
234
+ true
235
+ end
236
+
237
+ # DB2 does not support WHERE 1.
238
+ def supports_where_true?
239
+ false
240
+ end
241
+
242
+ private
243
+
244
+ # DB2 needs the standard workaround to insert all default values into
245
+ # a table with more than one column.
246
+ def insert_supports_empty_values?
247
+ false
248
+ end
249
+
250
+ # Use 0 for false on DB2
251
+ def literal_false
252
+ BOOL_FALSE
253
+ end
254
+
255
+ # Use 1 for true on DB2
256
+ def literal_true
257
+ BOOL_TRUE
258
+ end
259
+
260
+ # Add a fallback table for empty from situation
261
+ def select_from_sql(sql)
262
+ @opts[:from] ? super : (sql << ' FROM "SYSIBM"."SYSDUMMY1"')
263
+ end
264
+
265
+ # Modify the sql to limit the number of rows returned
266
+ # Note:
267
+ #
268
+ # After db2 v9.7, MySQL flavored "LIMIT X OFFSET Y" can be enabled using
269
+ #
270
+ # db2set DB2_COMPATIBILITY_VECTOR=MYSQL
271
+ # db2stop
272
+ # db2start
273
+ #
274
+ # Support for this feature is not used in this adapter however.
275
+ def select_limit_sql(sql)
276
+ if l = @opts[:limit]
277
+ sql << " FETCH FIRST #{l == 1 ? 'ROW' : "#{literal(l)} ROWS"} ONLY"
278
+ end
279
+ end
280
+
281
+ def _truncate_sql(table)
282
+ # "TRUNCATE #{table} IMMEDIATE" is only for newer version of db2, so we
283
+ # use the following one
284
+ "ALTER TABLE #{quote_schema_table(table)} ACTIVATE NOT LOGGED INITIALLY WITH EMPTY TABLE"
285
+ end
286
+ end
287
+ end
288
+ end