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.
- data/CHANGELOG +96 -0
- data/README.rdoc +2 -2
- data/Rakefile +1 -1
- data/doc/association_basics.rdoc +48 -0
- data/doc/opening_databases.rdoc +29 -5
- data/doc/prepared_statements.rdoc +1 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/testing.rdoc +42 -0
- data/doc/transactions.rdoc +97 -0
- data/lib/sequel/adapters/db2.rb +95 -65
- data/lib/sequel/adapters/firebird.rb +25 -219
- data/lib/sequel/adapters/ibmdb.rb +440 -0
- data/lib/sequel/adapters/jdbc.rb +12 -0
- data/lib/sequel/adapters/jdbc/as400.rb +0 -7
- data/lib/sequel/adapters/jdbc/db2.rb +49 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
- data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
- data/lib/sequel/adapters/mysql.rb +10 -15
- data/lib/sequel/adapters/odbc.rb +1 -2
- data/lib/sequel/adapters/odbc/db2.rb +5 -5
- data/lib/sequel/adapters/postgres.rb +71 -11
- data/lib/sequel/adapters/shared/db2.rb +290 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/mssql.rb +18 -75
- data/lib/sequel/adapters/shared/mysql.rb +13 -0
- data/lib/sequel/adapters/shared/postgres.rb +52 -36
- data/lib/sequel/adapters/shared/sqlite.rb +32 -36
- data/lib/sequel/adapters/sqlite.rb +4 -8
- data/lib/sequel/adapters/tinytds.rb +7 -3
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -5
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -1
- data/lib/sequel/dataset/actions.rb +149 -33
- data/lib/sequel/dataset/features.rb +44 -7
- data/lib/sequel/dataset/misc.rb +9 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -2
- data/lib/sequel/dataset/query.rb +63 -10
- data/lib/sequel/dataset/sql.rb +22 -5
- data/lib/sequel/model.rb +3 -3
- data/lib/sequel/model/associations.rb +250 -27
- data/lib/sequel/model/base.rb +10 -16
- data/lib/sequel/plugins/many_through_many.rb +34 -2
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
- data/lib/sequel/sql.rb +94 -51
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/postgres_spec.rb +74 -6
- data/spec/adapters/spec_helper.rb +1 -0
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/database_spec.rb +7 -0
- data/spec/core/dataset_spec.rb +180 -17
- data/spec/core/expression_filters_spec.rb +107 -41
- data/spec/core/spec_helper.rb +11 -0
- data/spec/extensions/many_through_many_spec.rb +115 -1
- data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
- data/spec/integration/associations_test.rb +193 -15
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +215 -19
- data/spec/integration/plugin_test.rb +8 -5
- data/spec/integration/prepared_statement_test.rb +91 -98
- data/spec/integration/schema_test.rb +27 -11
- data/spec/integration/spec_helper.rb +10 -0
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/association_reflection_spec.rb +91 -0
- data/spec/model/associations_spec.rb +13 -0
- data/spec/model/base_spec.rb +8 -21
- data/spec/model/eager_loading_spec.rb +243 -9
- data/spec/model/model_spec.rb +15 -2
- metadata +16 -4
@@ -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
|
-
|
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
|
19
|
-
def
|
20
|
-
def
|
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]
|
31
|
-
[2, 3, 8, 9, 13, 247, 248]
|
32
|
-
[4, 5]
|
33
|
-
[249, 250, 251, 252]
|
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] =
|
58
|
-
m =
|
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 =
|
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
|
data/lib/sequel/adapters/odbc.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
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] =>
|
116
|
-
[1083, 1266] =>
|
117
|
-
[1114, 1184] =>
|
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(
|
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
|