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
@@ -88,13 +88,6 @@ module Sequel
88
88
  def supports_window_functions?
89
89
  true
90
90
  end
91
-
92
- private
93
-
94
- # The alias to use for the row_number column when emulating LIMIT and OFFSET
95
- def row_number_column
96
- :x_sequel_row_number_x
97
- end
98
91
  end
99
92
  end
100
93
  end
@@ -0,0 +1,49 @@
1
+ Sequel.require 'adapters/shared/db2'
2
+ Sequel.require 'adapters/jdbc/transactions'
3
+
4
+ module Sequel
5
+ module JDBC
6
+ class Database
7
+ # Alias the generic JDBC versions so they can be called directly later
8
+ alias jdbc_schema_parse_table schema_parse_table
9
+ alias jdbc_tables tables
10
+ alias jdbc_views views
11
+ alias jdbc_indexes indexes
12
+ end
13
+
14
+ # Database and Dataset instance methods for DB2 specific
15
+ # support via JDBC.
16
+ module DB2
17
+ # Database instance methods for DB2 databases accessed via JDBC.
18
+ module DatabaseMethods
19
+ include Sequel::DB2::DatabaseMethods
20
+ include Sequel::JDBC::Transactions
21
+
22
+ # Return instance of Sequel::JDBC::DB2::Dataset with the given opts.
23
+ def dataset(opts=nil)
24
+ Sequel::JDBC::DB2::Dataset.new(self, opts)
25
+ end
26
+
27
+ %w'schema_parse_table tables views indexes'.each do |s|
28
+ class_eval("def #{s}(*a) jdbc_#{s}(*a) end", __FILE__, __LINE__)
29
+ end
30
+
31
+ private
32
+
33
+ def last_insert_id(conn, opts={})
34
+ statement(conn) do |stmt|
35
+ sql = "SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1"
36
+ rs = log_yield(sql){stmt.executeQuery(sql)}
37
+ rs.next
38
+ rs.getInt(1)
39
+ end
40
+ end
41
+ end
42
+
43
+ # Dataset class for DB2 datasets accessed via JDBC.
44
+ class Dataset < JDBC::Dataset
45
+ include Sequel::DB2::DatasetMethods
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+ Sequel.require 'adapters/shared/firebird'
2
+ Sequel.require 'adapters/jdbc/transactions'
3
+
4
+ module Sequel
5
+ module JDBC
6
+ # Database and Dataset instance methods for Firebird specific
7
+ # support via JDBC.
8
+ module Firebird
9
+ # Database instance methods for Firebird databases accessed via JDBC.
10
+ module DatabaseMethods
11
+ include Sequel::Firebird::DatabaseMethods
12
+ include Sequel::JDBC::Transactions
13
+
14
+ # Add the primary_keys and primary_key_sequences instance variables,
15
+ # so we can get the correct return values for inserted rows.
16
+ def self.extended(db)
17
+ db.instance_eval do
18
+ @primary_keys = {}
19
+ end
20
+ end
21
+
22
+ # Return instance of Sequel::JDBC::Firebird::Dataset with the given opts.
23
+ def dataset(opts=nil)
24
+ Sequel::JDBC::Firebird::Dataset.new(self, opts)
25
+ end
26
+ end
27
+
28
+ # Dataset class for Firebird datasets accessed via JDBC.
29
+ class Dataset < JDBC::Dataset
30
+ include Sequel::Firebird::DatasetMethods
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,4 +1,5 @@
1
1
  Sequel.require 'adapters/shared/oracle'
2
+ Sequel.require 'adapters/jdbc/transactions'
2
3
 
3
4
  module Sequel
4
5
  module JDBC
@@ -7,38 +8,12 @@ module Sequel
7
8
  # Instance methods for Oracle Database objects accessed via JDBC.
8
9
  module DatabaseMethods
9
10
  include Sequel::Oracle::DatabaseMethods
10
- TRANSACTION_BEGIN = 'Transaction.begin'.freeze
11
- TRANSACTION_COMMIT = 'Transaction.commit'.freeze
12
- TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
11
+ include Sequel::JDBC::Transactions
13
12
 
14
13
  # Return Sequel::JDBC::Oracle::Dataset object with the given opts.
15
14
  def dataset(opts=nil)
16
15
  Sequel::JDBC::Oracle::Dataset.new(self, opts)
17
16
  end
18
-
19
- private
20
-
21
- # Use JDBC connection's setAutoCommit to false to start transactions
22
- def begin_transaction(conn, opts={})
23
- log_yield(TRANSACTION_BEGIN){conn.setAutoCommit(false)}
24
- conn
25
- end
26
-
27
- # Use JDBC connection's commit method to commit transactions
28
- def commit_transaction(conn, opts={})
29
- log_yield(TRANSACTION_COMMIT){conn.commit}
30
- end
31
-
32
- # Use JDBC connection's setAutoCommit to true to enable non-transactional behavior
33
- def remove_transaction(conn)
34
- conn.setAutoCommit(true) if conn
35
- @transactions.delete(Thread.current)
36
- end
37
-
38
- # Use JDBC connection's rollback method to rollback transactions
39
- def rollback_transaction(conn, opts={})
40
- log_yield(TRANSACTION_ROLLBACK){conn.rollback}
41
- end
42
17
  end
43
18
 
44
19
  # Dataset class for Oracle datasets accessed via JDBC.
@@ -0,0 +1,34 @@
1
+ module Sequel
2
+ module JDBC
3
+ module Transactions
4
+ TRANSACTION_BEGIN = 'Transaction.begin'.freeze
5
+ TRANSACTION_COMMIT = 'Transaction.commit'.freeze
6
+ TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
7
+
8
+ private
9
+
10
+ # Use JDBC connection's setAutoCommit to false to start transactions
11
+ def begin_transaction(conn, opts={})
12
+ log_yield(TRANSACTION_BEGIN){conn.setAutoCommit(false)}
13
+ conn
14
+ end
15
+
16
+ # Use JDBC connection's commit method to commit transactions
17
+ def commit_transaction(conn, opts={})
18
+ log_yield(TRANSACTION_COMMIT){conn.commit}
19
+ end
20
+
21
+ # Use JDBC connection's setAutoCommit to true to enable non-transactional behavior
22
+ def remove_transaction(conn)
23
+ conn.setAutoCommit(true) if conn
24
+ @transactions.delete(Thread.current)
25
+ end
26
+
27
+ # Use JDBC connection's rollback method to rollback transactions
28
+ def rollback_transaction(conn, opts={})
29
+ log_yield(TRANSACTION_ROLLBACK){conn.rollback}
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -12,25 +12,20 @@ module 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
17
+ def date(s) ::Sequel::MySQL.convert_date_time(:string_to_date, s) end
18
+ def time(s) ::Sequel::MySQL.convert_date_time(:string_to_time, s) end
19
+ def timestamp(s) ::Sequel::MySQL.convert_date_time(:database_to_application_timestamp, s) end
25
20
  end.new
26
21
 
27
22
  # Hash with integer keys and callable values for converting MySQL types.
28
23
  MYSQL_TYPES = {}
29
24
  {
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)
25
+ [0, 246] => ::BigDecimal.method(:new),
26
+ [2, 3, 8, 9, 13, 247, 248] => tt.method(:integer),
27
+ [4, 5] => tt.method(:float),
28
+ [249, 250, 251, 252] => ::Sequel::SQL::Blob.method(:new)
34
29
  }.each do |k,v|
35
30
  k.each{|n| MYSQL_TYPES[n] = v}
36
31
  end
@@ -54,10 +49,10 @@ module Sequel
54
49
  # Modify the type translators for the date, time, and timestamp types
55
50
  # depending on the value given.
56
51
  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)
52
+ MYSQL_TYPES[11] = (v != false) ? TYPE_TRANSLATOR.method(:time) : ::Sequel.method(:string_to_time)
53
+ m = (v != false) ? TYPE_TRANSLATOR.method(:date) : ::Sequel.method(:string_to_date)
59
54
  [10, 14].each{|i| MYSQL_TYPES[i] = m}
60
- m = TYPE_TRANSLATOR.method(v == false ? :timestamp : :timestamp_conv)
55
+ m = (v != false) ? TYPE_TRANSLATOR.method(:timestamp) : ::Sequel.method(:database_to_application_timestamp)
61
56
  [7, 12].each{|i| MYSQL_TYPES[i] = m}
62
57
  @convert_invalid_date_time = v
63
58
  end
@@ -128,8 +128,7 @@ module Sequel
128
128
  when ::ODBC::TimeStamp
129
129
  Sequel.database_to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second])
130
130
  when ::ODBC::Time
131
- now = ::Time.now
132
- Sequel::SQLTime.local(now.year, now.month, now.day, v.hour, v.minute, v.second)
131
+ Sequel::SQLTime.create(v.hour, v.minute, v.second)
133
132
  when ::ODBC::Date
134
133
  Date.new(v.year, v.month, v.day)
135
134
  else
@@ -1,20 +1,20 @@
1
+ Sequel.require 'adapters/shared/db2'
2
+
1
3
  module Sequel
2
4
  module ODBC
3
5
  # Database and Dataset instance methods for DB2 specific
4
6
  # support via ODBC.
5
7
  module DB2
6
8
  module DatabaseMethods
9
+ include ::Sequel::DB2::DatabaseMethods
10
+
7
11
  def dataset(opts=nil)
8
12
  Sequel::ODBC::DB2::Dataset.new(self, opts)
9
13
  end
10
14
  end
11
15
 
12
16
  class Dataset < ODBC::Dataset
13
- def select_limit_sql(sql)
14
- if l = @opts[:limit]
15
- sql << " FETCH FIRST #{l == 1 ? 'ROW' : "#{literal(l)} ROWS"} ONLY"
16
- end
17
- end
17
+ include ::Sequel::DB2::DatasetMethods
18
18
  end
19
19
  end
20
20
  end
@@ -93,11 +93,7 @@ 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
 
103
99
  # Hash with type name symbols and callable values for converting PostgreSQL types.
@@ -112,9 +108,9 @@ module Sequel
112
108
  [17] => tt.method(:bytea),
113
109
  [20, 21, 22, 23, 26] => tt.method(:integer),
114
110
  [700, 701] => tt.method(:float),
115
- [790, 1700] => tt.method(:numeric),
116
- [1083, 1266] => tt.method(:time),
117
- [1114, 1184] => tt.method(:timestamp)
111
+ [790, 1700] => ::BigDecimal.method(:new),
112
+ [1083, 1266] => ::Sequel.method(:string_to_time),
113
+ [1114, 1184] => ::Sequel.method(:database_to_application_timestamp)
118
114
  }.each do |k,v|
119
115
  k.each{|n| PG_TYPES[n] = v}
120
116
  end
@@ -128,7 +124,7 @@ module Sequel
128
124
 
129
125
  # Modify the type translator for the date type depending on the value given.
130
126
  def self.use_iso_date_format=(v)
131
- PG_TYPES[1082] = TYPE_TRANSLATOR.method(v ? :date_iso : :date)
127
+ PG_TYPES[1082] = v ? TYPE_TRANSLATOR.method(:date) : Sequel.method(:string_to_date)
132
128
  @use_iso_date_format = v
133
129
  end
134
130
  self.use_iso_date_format = true
@@ -244,6 +240,70 @@ module Sequel
244
240
  conn
245
241
  end
246
242
 
243
+ if SEQUEL_POSTGRES_USES_PG
244
+ # +copy_table+ uses PostgreSQL's +COPY+ SQL statement to return formatted
245
+ # results directly to the caller. This method is only support if pg is the
246
+ # underlying ruby driver. This method should only be called if you want
247
+ # results returned to the client. If you are using +COPY FROM+ or +COPY TO+
248
+ # with a filename, you should just use +run+ instead of this method. This
249
+ # method does not currently support +COPY FROM STDIN+, but that may be supported
250
+ # in the future.
251
+ #
252
+ # The table argument supports the following types:
253
+ #
254
+ # String :: Uses the first argument directly as literal SQL. If you are using
255
+ # a version of PostgreSQL before 9.0, you will probably want to
256
+ # use a string if you are using any options at all, as the syntax
257
+ # Sequel uses for options is only compatible with PostgreSQL 9.0+.
258
+ # Dataset :: Uses a query instead of a table name when copying.
259
+ # other :: Uses a table name (usually a symbol) when copying.
260
+ #
261
+ # The following options are respected:
262
+ #
263
+ # :format :: The format to use. text is the default, so this should be :csv or :binary.
264
+ # :options :: An options SQL string to use, which should contain comma separated options.
265
+ # :server :: The server on which to run the query.
266
+ #
267
+ # If a block is provided, the method continually yields to the block, one yield
268
+ # per row. If a block is not provided, a single string is returned with all
269
+ # of the data.
270
+ def copy_table(table, opts={})
271
+ sql = if table.is_a?(String)
272
+ sql = table
273
+ else
274
+ if opts[:options] || opts[:format]
275
+ options = " ("
276
+ options << "FORMAT #{opts[:format]}" if opts[:format]
277
+ options << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
278
+ options << ')'
279
+ end
280
+ table = if table.is_a?(::Sequel::Dataset)
281
+ "(#{table.sql})"
282
+ else
283
+ literal(table)
284
+ end
285
+ sql = "COPY #{table} TO STDOUT#{options}"
286
+ end
287
+ synchronize(opts[:server]) do |conn|
288
+ conn.execute(sql)
289
+ begin
290
+ if block_given?
291
+ while buf = conn.get_copy_data
292
+ yield buf
293
+ end
294
+ nil
295
+ else
296
+ b = ''
297
+ b << buf while buf = conn.get_copy_data
298
+ b
299
+ end
300
+ ensure
301
+ raise DatabaseDisconnectError, "disconnecting as a partial COPY may leave the connection in an unusable state" if buf
302
+ end
303
+ end
304
+ end
305
+ end
306
+
247
307
  # Return instance of Sequel::Postgres::Dataset with the given options.
248
308
  def dataset(opts = nil)
249
309
  Postgres::Dataset.new(self, opts)
@@ -411,6 +471,7 @@ module Sequel
411
471
  # Allow use of bind arguments for PostgreSQL using the pg driver.
412
472
  module BindArgumentMethods
413
473
  include ArgumentMapper
474
+ include ::Sequel::Postgres::DatasetMethods::PreparedStatementMethods
414
475
 
415
476
  private
416
477
 
@@ -434,7 +495,6 @@ module Sequel
434
495
  # pg driver.
435
496
  module PreparedStatementMethods
436
497
  include BindArgumentMethods
437
- include ::Sequel::Postgres::DatasetMethods::PreparedStatementMethods
438
498
 
439
499
  private
440
500
 
@@ -461,7 +521,7 @@ module Sequel
461
521
  ps.extend(BindArgumentMethods)
462
522
  ps.call(bind_vars, &block)
463
523
  end
464
-
524
+
465
525
  # Prepare the given type of statement with the given name, and store
466
526
  # it in the database to be called later.
467
527
  def prepare(type, name=nil, *values)
@@ -0,0 +1,290 @@
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
33
+ im = input_identifier_meth
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 :&, :|, :^, :'B~'
196
+ # works with db2 v9.5 and after
197
+ literal(SQL::Function.new(BITWISE_METHOD_MAP[op], *args))
198
+ when :<<
199
+ "(#{literal(args[0])} * POWER(2, #{literal(args[1])}))"
200
+ when :>>
201
+ "(#{literal(args[0])} / POWER(2, #{literal(args[1])}))"
202
+ when :extract
203
+ "#{args.at(0)}(#{literal(args.at(1))})"
204
+ else
205
+ super
206
+ end
207
+ end
208
+
209
+ # Delete the row_number_column if offsets are being emulated with
210
+ # ROW_NUMBER.
211
+ def fetch_rows(sql, &block)
212
+ @opts[:offset] ? super(sql){|r| r.delete(row_number_column); yield r} : super(sql, &block)
213
+ end
214
+
215
+ # DB2 does not support IS TRUE.
216
+ def supports_is_true?
217
+ false
218
+ end
219
+
220
+ # DB2 does not support multiple columns in IN.
221
+ def supports_multiple_column_in?
222
+ false
223
+ end
224
+
225
+ # DB2 only allows * in SELECT if it is the only thing being selected.
226
+ def supports_select_all_and_column?
227
+ false
228
+ end
229
+
230
+ # DB2 does not support fractional seconds in timestamps.
231
+ def supports_timestamp_usecs?
232
+ false
233
+ end
234
+
235
+ # DB2 supports window functions
236
+ def supports_window_functions?
237
+ true
238
+ end
239
+
240
+ # DB2 does not support WHERE 1.
241
+ def supports_where_true?
242
+ false
243
+ end
244
+
245
+ private
246
+
247
+ # DB2 uses "INSERT INTO "ITEMS" VALUES DEFAULT" for a record with default values to be inserted
248
+ def insert_values_sql(sql)
249
+ opts[:values].empty? ? sql << " VALUES DEFAULT" : super
250
+ end
251
+
252
+ # Use 0 for false on DB2
253
+ def literal_false
254
+ BOOL_FALSE
255
+ end
256
+
257
+ # Use 1 for true on DB2
258
+ def literal_true
259
+ BOOL_TRUE
260
+ end
261
+
262
+ # Add a fallback table for empty from situation
263
+ def select_from_sql(sql)
264
+ @opts[:from] ? super : (sql << ' FROM "SYSIBM"."SYSDUMMY1"')
265
+ end
266
+
267
+ # Modify the sql to limit the number of rows returned
268
+ # Note:
269
+ #
270
+ # After db2 v9.7, MySQL flavored "LIMIT X OFFSET Y" can be enabled using
271
+ #
272
+ # db2set DB2_COMPATIBILITY_VECTOR=MYSQL
273
+ # db2stop
274
+ # db2start
275
+ #
276
+ # Support for this feature is not used in this adapter however.
277
+ def select_limit_sql(sql)
278
+ if l = @opts[:limit]
279
+ sql << " FETCH FIRST #{l == 1 ? 'ROW' : "#{literal(l)} ROWS"} ONLY"
280
+ end
281
+ end
282
+
283
+ def _truncate_sql(table)
284
+ # "TRUNCATE #{table} IMMEDIATE" is only for newer version of db2, so we
285
+ # use the following one
286
+ "ALTER TABLE #{quote_schema_table(table)} ACTIVATE NOT LOGGED INITIALLY WITH EMPTY TABLE"
287
+ end
288
+ end
289
+ end
290
+ end