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