sequel 3.27.0 → 3.28.0

Sign up to get free protection for your applications and to get access to all the features.
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