sequel 3.27.0 → 3.28.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 (73) hide show
  1. data/CHANGELOG +96 -0
  2. data/README.rdoc +2 -2
  3. data/Rakefile +1 -1
  4. data/doc/association_basics.rdoc +48 -0
  5. data/doc/opening_databases.rdoc +29 -5
  6. data/doc/prepared_statements.rdoc +1 -0
  7. data/doc/release_notes/3.28.0.txt +304 -0
  8. data/doc/testing.rdoc +42 -0
  9. data/doc/transactions.rdoc +97 -0
  10. data/lib/sequel/adapters/db2.rb +95 -65
  11. data/lib/sequel/adapters/firebird.rb +25 -219
  12. data/lib/sequel/adapters/ibmdb.rb +440 -0
  13. data/lib/sequel/adapters/jdbc.rb +12 -0
  14. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  15. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  16. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  18. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  19. data/lib/sequel/adapters/mysql.rb +10 -15
  20. data/lib/sequel/adapters/odbc.rb +1 -2
  21. data/lib/sequel/adapters/odbc/db2.rb +5 -5
  22. data/lib/sequel/adapters/postgres.rb +71 -11
  23. data/lib/sequel/adapters/shared/db2.rb +290 -0
  24. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +18 -75
  26. data/lib/sequel/adapters/shared/mysql.rb +13 -0
  27. data/lib/sequel/adapters/shared/postgres.rb +52 -36
  28. data/lib/sequel/adapters/shared/sqlite.rb +32 -36
  29. data/lib/sequel/adapters/sqlite.rb +4 -8
  30. data/lib/sequel/adapters/tinytds.rb +7 -3
  31. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  32. data/lib/sequel/core.rb +1 -1
  33. data/lib/sequel/database/connecting.rb +1 -1
  34. data/lib/sequel/database/misc.rb +6 -5
  35. data/lib/sequel/database/query.rb +1 -1
  36. data/lib/sequel/database/schema_generator.rb +2 -1
  37. data/lib/sequel/dataset/actions.rb +149 -33
  38. data/lib/sequel/dataset/features.rb +44 -7
  39. data/lib/sequel/dataset/misc.rb +9 -1
  40. data/lib/sequel/dataset/prepared_statements.rb +2 -2
  41. data/lib/sequel/dataset/query.rb +63 -10
  42. data/lib/sequel/dataset/sql.rb +22 -5
  43. data/lib/sequel/model.rb +3 -3
  44. data/lib/sequel/model/associations.rb +250 -27
  45. data/lib/sequel/model/base.rb +10 -16
  46. data/lib/sequel/plugins/many_through_many.rb +34 -2
  47. data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
  48. data/lib/sequel/sql.rb +94 -51
  49. data/lib/sequel/version.rb +1 -1
  50. data/spec/adapters/db2_spec.rb +146 -0
  51. data/spec/adapters/postgres_spec.rb +74 -6
  52. data/spec/adapters/spec_helper.rb +1 -0
  53. data/spec/adapters/sqlite_spec.rb +11 -0
  54. data/spec/core/database_spec.rb +7 -0
  55. data/spec/core/dataset_spec.rb +180 -17
  56. data/spec/core/expression_filters_spec.rb +107 -41
  57. data/spec/core/spec_helper.rb +11 -0
  58. data/spec/extensions/many_through_many_spec.rb +115 -1
  59. data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
  60. data/spec/integration/associations_test.rb +193 -15
  61. data/spec/integration/database_test.rb +4 -2
  62. data/spec/integration/dataset_test.rb +215 -19
  63. data/spec/integration/plugin_test.rb +8 -5
  64. data/spec/integration/prepared_statement_test.rb +91 -98
  65. data/spec/integration/schema_test.rb +27 -11
  66. data/spec/integration/spec_helper.rb +10 -0
  67. data/spec/integration/type_test.rb +2 -2
  68. data/spec/model/association_reflection_spec.rb +91 -0
  69. data/spec/model/associations_spec.rb +13 -0
  70. data/spec/model/base_spec.rb +8 -21
  71. data/spec/model/eager_loading_spec.rb +243 -9
  72. data/spec/model/model_spec.rb +15 -2
  73. metadata +16 -4
@@ -0,0 +1,440 @@
1
+ require 'ibm_db'
2
+ Sequel.require 'adapters/shared/db2'
3
+
4
+ module Sequel
5
+
6
+ module IBMDB
7
+ @convert_smallint_to_bool = true
8
+
9
+ class << self
10
+ # Whether to convert smallint values to bool, true by default.
11
+ # Can also be overridden per dataset.
12
+ attr_accessor :convert_smallint_to_bool
13
+ end
14
+
15
+ tt = Class.new do
16
+ def boolean(s) !s.to_i.zero? end
17
+ def int(s) s.to_i end
18
+ end.new
19
+
20
+ # Hash holding type translation methods, used by Dataset#fetch_rows.
21
+ DB2_TYPES = {
22
+ :boolean => tt.method(:boolean),
23
+ :int => tt.method(:int),
24
+ :blob => ::Sequel::SQL::Blob.method(:new),
25
+ :time => ::Sequel.method(:string_to_time),
26
+ :date => ::Sequel.method(:string_to_date),
27
+ :timestamp => ::Sequel.method(:database_to_application_timestamp)
28
+ }
29
+ DB2_TYPES[:clob] = DB2_TYPES[:blob]
30
+
31
+ # Wraps an underlying connection to DB2 using IBM_DB.
32
+ class Connection
33
+ # A hash with prepared statement name symbol keys, where each value is
34
+ # a two element array with an sql string and cached Statement value.
35
+ attr_accessor :prepared_statements
36
+
37
+ # Error class for exceptions raised by the connection.
38
+ class Error < StandardError
39
+ end
40
+
41
+ # Create the underlying IBM_DB connection.
42
+ def initialize(connection_string)
43
+ @conn = IBM_DB.connect(connection_string, '', '')
44
+ self.autocommit = true
45
+ @prepared_statements = {}
46
+ end
47
+
48
+ # Check whether the connection is in autocommit state or not.
49
+ def autocommit
50
+ IBM_DB.autocommit(@conn) == 1
51
+ end
52
+
53
+ # Turn autocommit on or off for the connection.
54
+ def autocommit=(value)
55
+ IBM_DB.autocommit(@conn, value ? IBM_DB::SQL_AUTOCOMMIT_ON : IBM_DB::SQL_AUTOCOMMIT_OFF)
56
+ end
57
+
58
+ # Close the connection, disconnecting from DB2.
59
+ def close
60
+ IBM_DB.close(@conn)
61
+ end
62
+
63
+ # Commit the currently outstanding transaction on this connection.
64
+ def commit
65
+ IBM_DB.commit(@conn)
66
+ end
67
+
68
+ # Return the related error message for the connection.
69
+ def error_msg
70
+ IBM_DB.getErrormsg(@conn, IBM_DB::DB_CONN)
71
+ end
72
+
73
+ # Execute the given SQL on the database, and return a Statement instance
74
+ # holding the results.
75
+ def execute(sql)
76
+ stmt = IBM_DB.exec(@conn, sql)
77
+ raise Error, error_msg unless stmt
78
+ Statement.new(stmt)
79
+ end
80
+
81
+ # Execute the related prepared statement on the database with the given
82
+ # arguments.
83
+ def execute_prepared(ps_name, *values)
84
+ stmt = @prepared_statements[ps_name].last
85
+ res = stmt.execute(*values)
86
+ unless res
87
+ raise Error, "Error executing statement #{ps_name}: #{error_msg}"
88
+ end
89
+ stmt
90
+ end
91
+
92
+ # Prepare a statement with the given +sql+ on the database, and
93
+ # cache the prepared statement value by name.
94
+ def prepare(sql, ps_name)
95
+ if stmt = IBM_DB.prepare(@conn, sql)
96
+ ps_name = ps_name.to_sym
97
+ stmt = Statement.new(stmt)
98
+ @prepared_statements[ps_name] = [sql, stmt]
99
+ else
100
+ err = error_msg
101
+ err = "Error preparing #{ps_name} with SQL: #{sql}" if error_msg.nil? || error_msg.empty?
102
+ raise Error, err
103
+ end
104
+ end
105
+
106
+ # Rollback the currently outstanding transaction on this connection.
107
+ def rollback
108
+ IBM_DB.rollback(@conn)
109
+ end
110
+ end
111
+
112
+ # Wraps results returned by queries on IBM_DB.
113
+ class Statement
114
+ # Hold the given statement.
115
+ def initialize(stmt)
116
+ @stmt = stmt
117
+ end
118
+
119
+ # Return the number of rows affected.
120
+ def affected
121
+ IBM_DB.num_rows(@stmt)
122
+ end
123
+
124
+ # If this statement is a prepared statement, execute it on the database
125
+ # with the given values.
126
+ def execute(*values)
127
+ IBM_DB.execute(@stmt, values)
128
+ end
129
+
130
+ # Return the results of a query as an array of values.
131
+ def fetch_array
132
+ IBM_DB.fetch_array(@stmt) if @stmt
133
+ end
134
+
135
+ # Return the field name at the given column in the result set.
136
+ def field_name(ind)
137
+ IBM_DB.field_name(@stmt, ind)
138
+ end
139
+
140
+ # Return the field type for the given field name in the result set.
141
+ def field_type(key)
142
+ IBM_DB.field_type(@stmt, key)
143
+ end
144
+
145
+ # Return the field precision for the given field name in the result set.
146
+ def field_precision(key)
147
+ IBM_DB.field_precision(@stmt, key)
148
+ end
149
+
150
+ # Free the memory related to this result set.
151
+ def free
152
+ IBM_DB.free_result(@stmt)
153
+ end
154
+
155
+ # Return the number of fields in the result set.
156
+ def num_fields
157
+ IBM_DB.num_fields(@stmt)
158
+ end
159
+ end
160
+
161
+ class Database < Sequel::Database
162
+ include Sequel::DB2::DatabaseMethods
163
+
164
+ set_adapter_scheme :ibmdb
165
+
166
+ # REORG the related table whenever it is altered. This is not always
167
+ # required, but it is necessary for compatibilty with other Sequel
168
+ # code in many cases.
169
+ def alter_table(name, generator=nil, &block)
170
+ res = super
171
+ reorg(name)
172
+ res
173
+ end
174
+
175
+ # Create a new connection object for the given server.
176
+ def connect(server)
177
+ opts = server_opts(server)
178
+
179
+ # use uncataloged connection so that host and port can be supported
180
+ connection_string = ( \
181
+ 'Driver={IBM DB2 ODBC DRIVER};' \
182
+ "Database=#{opts[:database]};" \
183
+ "Hostname=#{opts[:host]};" \
184
+ "Port=#{opts[:port] || 50000};" \
185
+ 'Protocol=TCPIP;' \
186
+ "Uid=#{opts[:user]};" \
187
+ "Pwd=#{opts[:password]};" \
188
+ )
189
+
190
+ Connection.new(connection_string)
191
+ end
192
+
193
+ # Return a related IBMDB::Dataset instance with the given options.
194
+ def dataset(opts = nil)
195
+ IBMDB::Dataset.new(self, opts)
196
+ end
197
+
198
+ # Execute the given SQL on the database.
199
+ def execute(sql, opts={}, &block)
200
+ if sql.is_a?(Symbol)
201
+ execute_prepared_statement(sql, opts, &block)
202
+ else
203
+ synchronize(opts[:server]){|c| _execute(c, sql, opts, &block)}
204
+ end
205
+ rescue Connection::Error => e
206
+ raise_error(e)
207
+ end
208
+
209
+ # Execute the given SQL on the database, returning the last inserted
210
+ # identity value.
211
+ def execute_insert(sql, opts={})
212
+ synchronize(opts[:server]) do |c|
213
+ if sql.is_a?(Symbol)
214
+ execute_prepared_statement(sql, opts)
215
+ else
216
+ _execute(c, sql, opts)
217
+ end
218
+ _execute(c, "SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1", opts){|stmt| i = stmt.fetch_array.first.to_i; stmt.free; i}
219
+ end
220
+ rescue Connection::Error => e
221
+ raise_error(e)
222
+ end
223
+
224
+ # Execute a prepared statement named by name on the database.
225
+ def execute_prepared_statement(ps_name, opts)
226
+ args = opts[:arguments]
227
+ ps = prepared_statements[ps_name]
228
+ sql = ps.prepared_sql
229
+ synchronize(opts[:server]) do |conn|
230
+ unless conn.prepared_statements.fetch(ps_name, []).first == sql
231
+ log_yield("Preparing #{ps_name}: #{sql}"){conn.prepare(sql, ps_name)}
232
+ end
233
+ args = args.map{|v| v.nil? ? nil : prepared_statement_arg(v)}
234
+ stmt = log_yield("Executing #{ps_name}: #{args.inspect}"){conn.execute_prepared(ps_name, *args)}
235
+
236
+ if block_given?
237
+ begin
238
+ yield(stmt)
239
+ ensure
240
+ stmt.free
241
+ end
242
+ else
243
+ stmt.affected
244
+ end
245
+ end
246
+ end
247
+
248
+ # Convert smallint type to boolean if convert_smallint_to_bool is true
249
+ def schema_column_type(db_type)
250
+ if Sequel::IBMDB.convert_smallint_to_bool && db_type =~ /smallint/i
251
+ :boolean
252
+ else
253
+ db_type =~ /[bc]lob/i ? :blob : super
254
+ end
255
+ end
256
+
257
+ # On DB2, a table might need to be REORGed if you are testing existence
258
+ # of it. This REORGs automatically if the database raises a specific
259
+ # error that indicates it should be REORGed.
260
+ def table_exists?(name)
261
+ v ||= false # only retry once
262
+ sch, table_name = schema_and_table(name)
263
+ name = SQL::QualifiedIdentifier.new(sch, table_name) if sch
264
+ from(name).first
265
+ true
266
+ rescue DatabaseError => e
267
+ if e.to_s =~ /Operation not allowed for reason code "7" on table/ && v == false
268
+ # table probably needs reorg
269
+ reorg(name)
270
+ v = true
271
+ retry
272
+ end
273
+ false
274
+ end
275
+
276
+ private
277
+
278
+ # Execute the given SQL on the database.
279
+ def _execute(conn, sql, opts)
280
+ stmt = log_yield(sql){conn.execute(sql)}
281
+ if block_given?
282
+ begin
283
+ yield(stmt)
284
+ ensure
285
+ stmt.free
286
+ end
287
+ else
288
+ stmt.affected
289
+ end
290
+ end
291
+
292
+ # IBM_DB uses an autocommit setting instead of sending SQL queries.
293
+ # So starting a transaction just turns autocommit off.
294
+ def begin_transaction(conn, opts={})
295
+ log_yield(TRANSACTION_BEGIN){conn.autocommit = false}
296
+ conn
297
+ end
298
+
299
+ # This commits transaction in progress on the
300
+ # connection and sets autocommit back on.
301
+ def commit_transaction(conn, opts={})
302
+ log_yield(TRANSACTION_COMMIT){conn.commit} if conn
303
+ ensure
304
+ conn.autocommit = true if conn
305
+ end
306
+
307
+ # Close the given connection.
308
+ def disconnect_connection(conn)
309
+ conn.close
310
+ end
311
+
312
+ # Don't convert smallint to boolean for the metadata
313
+ # dataset, since the DB2 metadata does not use
314
+ # boolean columns, and some smallint columns are
315
+ # accidently treated as booleans.
316
+ def metadata_dataset
317
+ ds = super
318
+ ds.convert_smallint_to_bool = false
319
+ ds
320
+ end
321
+
322
+ # Format Numeric, Date, and Time types specially for use
323
+ # as IBM_DB prepared statements argument vlaues.
324
+ def prepared_statement_arg(v)
325
+ case v
326
+ when Numeric
327
+ v.to_s
328
+ when Date, Time
329
+ literal(v).gsub("'", '')
330
+ else
331
+ v
332
+ end
333
+ end
334
+
335
+ # This rolls back the transaction in progress on the
336
+ # connection and sets autocommit back on.
337
+ def rollback_transaction(conn, opts={})
338
+ log_yield(TRANSACTION_ROLLBACK){conn.rollback} if conn
339
+ ensure
340
+ conn.autocommit = true if conn
341
+ end
342
+ end
343
+
344
+ class Dataset < Sequel::Dataset
345
+ include Sequel::DB2::DatasetMethods
346
+
347
+ module CallableStatementMethods
348
+ # Extend given dataset with this module so subselects inside subselects in
349
+ # prepared statements work.
350
+ def subselect_sql(ds)
351
+ ps = ds.to_prepared_statement(:select)
352
+ ps.extend(CallableStatementMethods)
353
+ ps = ps.bind(@opts[:bind_vars]) if @opts[:bind_vars]
354
+ ps.prepared_args = prepared_args
355
+ ps.prepared_sql
356
+ end
357
+ end
358
+
359
+ # Methods for DB2 prepared statements using the native driver.
360
+ module PreparedStatementMethods
361
+ include Sequel::Dataset::UnnumberedArgumentMapper
362
+
363
+ private
364
+ # Execute the prepared statement with arguments instead of the given SQL.
365
+ def execute(sql, opts={}, &block)
366
+ super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
367
+ end
368
+
369
+ # Execute the prepared statment with arguments instead of the given SQL.
370
+ def execute_dui(sql, opts={}, &block)
371
+ super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
372
+ end
373
+
374
+ # Execute the prepared statement with arguments instead of the given SQL.
375
+ def execute_insert(sql, opts={}, &block)
376
+ super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
377
+ end
378
+
379
+ end
380
+
381
+ # Emulate support of bind arguments in called statements.
382
+ def call(type, bind_arguments={}, *values, &block)
383
+ ps = to_prepared_statement(type, values)
384
+ ps.extend(CallableStatementMethods)
385
+ ps.call(bind_arguments, &block)
386
+ end
387
+
388
+ # Whether to convert smallint to boolean arguments for this dataset.
389
+ # Defaults to the IBMDB module setting.
390
+ def convert_smallint_to_bool
391
+ defined?(@convert_smallint_to_bool) ? @convert_smallint_to_bool : (@convert_smallint_to_bool = IBMDB.convert_smallint_to_bool)
392
+ end
393
+
394
+ # Override the default IBMDB.convert_smallint_to_bool setting for this dataset.
395
+ attr_writer :convert_smallint_to_bool
396
+
397
+ # Fetch the rows from the database and yield plain hashes.
398
+ def fetch_rows(sql)
399
+ execute(sql) do |stmt|
400
+ offset = @opts[:offset]
401
+ columns = []
402
+ convert = convert_smallint_to_bool
403
+ stmt.num_fields.times do |i|
404
+ k = stmt.field_name i
405
+ key = output_identifier(k)
406
+ type = stmt.field_type(k).downcase.to_sym
407
+ # decide if it is a smallint from precision
408
+ type = :boolean if type ==:int && convert && stmt.field_precision(k) < 8
409
+ columns << [key, DB2_TYPES[type]]
410
+ end
411
+ cols = columns.map{|c| c.at(0)}
412
+ cols.delete(row_number_column) if offset
413
+ @columns = cols
414
+
415
+ while res = stmt.fetch_array
416
+ row = {}
417
+ res.zip(columns).each do |v, (k, pr)|
418
+ row[k] = ((pr ? pr.call(v) : v) if v)
419
+ end
420
+ row.delete(row_number_column) if offset
421
+ yield row
422
+ end
423
+ end
424
+ self
425
+ end
426
+
427
+ # Store the given type of prepared statement in the associated database
428
+ # with the given name.
429
+ def prepare(type, name=nil, *values)
430
+ ps = to_prepared_statement(type, values)
431
+ ps.extend(PreparedStatementMethods)
432
+ if name
433
+ ps.prepared_statement_name = name
434
+ db.prepared_statements[name] = ps
435
+ end
436
+ ps
437
+ end
438
+ end
439
+ end
440
+ end
@@ -80,6 +80,16 @@ module Sequel
80
80
  Sequel.ts_require 'adapters/jdbc/informix'
81
81
  db.extend(Sequel::JDBC::Informix::DatabaseMethods)
82
82
  com.informix.jdbc.IfxDriver
83
+ end,
84
+ :db2=>proc do |db|
85
+ Sequel.ts_require 'adapters/jdbc/db2'
86
+ db.extend(Sequel::JDBC::DB2::DatabaseMethods)
87
+ com.ibm.db2.jcc.DB2Driver
88
+ end,
89
+ :firebirdsql=>proc do |db|
90
+ Sequel.ts_require 'adapters/jdbc/firebird'
91
+ db.extend(Sequel::JDBC::Firebird::DatabaseMethods)
92
+ org.firebirdsql.jdbc.FBDriver
83
93
  end
84
94
  }
85
95
 
@@ -595,6 +605,8 @@ module Sequel
595
605
  Sequel::SQL::Blob.new(String.from_java_bytes(v))
596
606
  when Java::JavaSQL::Blob
597
607
  convert_type(v.getBytes(1, v.length))
608
+ when Java::JavaSQL::Clob
609
+ Sequel::SQL::Blob.new(v.getSubString(1, v.length))
598
610
  else
599
611
  v
600
612
  end