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
@@ -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