sequel 3.28.0 → 3.29.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +119 -3
- data/Rakefile +5 -3
- data/bin/sequel +1 -5
- data/doc/model_hooks.rdoc +9 -1
- data/doc/opening_databases.rdoc +49 -40
- data/doc/prepared_statements.rdoc +27 -6
- data/doc/release_notes/3.28.0.txt +2 -2
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/sharding.rdoc +7 -1
- data/doc/testing.rdoc +18 -9
- data/doc/transactions.rdoc +41 -1
- data/lib/sequel/adapters/ado.rb +28 -17
- data/lib/sequel/adapters/ado/mssql.rb +18 -6
- data/lib/sequel/adapters/amalgalite.rb +11 -7
- data/lib/sequel/adapters/db2.rb +122 -70
- data/lib/sequel/adapters/dbi.rb +15 -15
- data/lib/sequel/adapters/do.rb +5 -36
- data/lib/sequel/adapters/do/mysql.rb +0 -5
- data/lib/sequel/adapters/do/postgres.rb +0 -5
- data/lib/sequel/adapters/do/sqlite.rb +0 -5
- data/lib/sequel/adapters/firebird.rb +3 -6
- data/lib/sequel/adapters/ibmdb.rb +24 -16
- data/lib/sequel/adapters/informix.rb +2 -4
- data/lib/sequel/adapters/jdbc.rb +47 -11
- data/lib/sequel/adapters/jdbc/as400.rb +5 -24
- data/lib/sequel/adapters/jdbc/db2.rb +0 -5
- data/lib/sequel/adapters/jdbc/derby.rb +217 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
- data/lib/sequel/adapters/jdbc/h2.rb +10 -12
- data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
- data/lib/sequel/adapters/jdbc/informix.rb +0 -5
- data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
- data/lib/sequel/adapters/jdbc/mysql.rb +0 -10
- data/lib/sequel/adapters/jdbc/oracle.rb +70 -3
- data/lib/sequel/adapters/jdbc/postgresql.rb +0 -11
- data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
- data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
- data/lib/sequel/adapters/jdbc/transactions.rb +56 -7
- data/lib/sequel/adapters/mock.rb +315 -0
- data/lib/sequel/adapters/mysql.rb +64 -51
- data/lib/sequel/adapters/mysql2.rb +15 -9
- data/lib/sequel/adapters/odbc.rb +13 -6
- data/lib/sequel/adapters/odbc/db2.rb +0 -4
- data/lib/sequel/adapters/odbc/mssql.rb +0 -5
- data/lib/sequel/adapters/openbase.rb +2 -4
- data/lib/sequel/adapters/oracle.rb +333 -51
- data/lib/sequel/adapters/postgres.rb +80 -27
- data/lib/sequel/adapters/shared/access.rb +0 -6
- data/lib/sequel/adapters/shared/db2.rb +13 -15
- data/lib/sequel/adapters/shared/firebird.rb +6 -6
- data/lib/sequel/adapters/shared/mssql.rb +23 -18
- data/lib/sequel/adapters/shared/mysql.rb +6 -6
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
- data/lib/sequel/adapters/shared/oracle.rb +185 -30
- data/lib/sequel/adapters/shared/postgres.rb +35 -18
- data/lib/sequel/adapters/shared/progress.rb +0 -6
- data/lib/sequel/adapters/shared/sqlite.rb +116 -37
- data/lib/sequel/adapters/sqlite.rb +16 -8
- data/lib/sequel/adapters/swift.rb +5 -5
- data/lib/sequel/adapters/swift/mysql.rb +0 -5
- data/lib/sequel/adapters/swift/postgres.rb +0 -5
- data/lib/sequel/adapters/swift/sqlite.rb +6 -4
- data/lib/sequel/adapters/tinytds.rb +13 -10
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -0
- data/lib/sequel/core.rb +40 -0
- data/lib/sequel/database/connecting.rb +1 -2
- data/lib/sequel/database/dataset.rb +3 -3
- data/lib/sequel/database/dataset_defaults.rb +58 -0
- data/lib/sequel/database/misc.rb +62 -2
- data/lib/sequel/database/query.rb +113 -49
- data/lib/sequel/database/schema_methods.rb +7 -2
- data/lib/sequel/dataset/actions.rb +37 -19
- data/lib/sequel/dataset/features.rb +24 -0
- data/lib/sequel/dataset/graph.rb +7 -6
- data/lib/sequel/dataset/misc.rb +11 -3
- data/lib/sequel/dataset/mutation.rb +2 -3
- data/lib/sequel/dataset/prepared_statements.rb +6 -4
- data/lib/sequel/dataset/query.rb +46 -15
- data/lib/sequel/dataset/sql.rb +28 -4
- data/lib/sequel/extensions/named_timezones.rb +5 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
- data/lib/sequel/model.rb +2 -1
- data/lib/sequel/model/associations.rb +115 -33
- data/lib/sequel/model/base.rb +91 -31
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/identity_map.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +6 -10
- data/lib/sequel/plugins/prepared_statements.rb +12 -1
- data/lib/sequel/plugins/prepared_statements_associations.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +29 -15
- data/lib/sequel/plugins/serialization.rb +6 -1
- data/lib/sequel/plugins/sharding.rb +0 -5
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +1 -1
- data/lib/sequel/timezones.rb +42 -42
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +29 -29
- data/spec/adapters/mysql_spec.rb +86 -104
- data/spec/adapters/oracle_spec.rb +48 -76
- data/spec/adapters/postgres_spec.rb +98 -33
- data/spec/adapters/spec_helper.rb +0 -5
- data/spec/adapters/sqlite_spec.rb +24 -21
- data/spec/core/connection_pool_spec.rb +9 -15
- data/spec/core/core_sql_spec.rb +20 -31
- data/spec/core/database_spec.rb +491 -227
- data/spec/core/dataset_spec.rb +638 -1051
- data/spec/core/expression_filters_spec.rb +0 -1
- data/spec/core/mock_adapter_spec.rb +378 -0
- data/spec/core/object_graph_spec.rb +48 -114
- data/spec/core/schema_generator_spec.rb +3 -3
- data/spec/core/schema_spec.rb +51 -114
- data/spec/core/spec_helper.rb +3 -90
- data/spec/extensions/class_table_inheritance_spec.rb +1 -1
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/instance_hooks_spec.rb +71 -0
- data/spec/extensions/named_timezones_spec.rb +22 -2
- data/spec/extensions/nested_attributes_spec.rb +3 -0
- data/spec/extensions/schema_spec.rb +1 -1
- data/spec/extensions/serialization_modification_detection_spec.rb +1 -0
- data/spec/extensions/serialization_spec.rb +5 -8
- data/spec/extensions/spec_helper.rb +4 -0
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/typecast_on_load_spec.rb +1 -6
- data/spec/integration/associations_test.rb +123 -12
- data/spec/integration/dataset_test.rb +140 -47
- data/spec/integration/eager_loader_test.rb +19 -21
- data/spec/integration/model_test.rb +80 -1
- data/spec/integration/plugin_test.rb +179 -128
- data/spec/integration/prepared_statement_test.rb +92 -91
- data/spec/integration/schema_test.rb +42 -23
- data/spec/integration/spec_helper.rb +25 -31
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +161 -34
- data/spec/integration/type_test.rb +3 -3
- data/spec/model/association_reflection_spec.rb +83 -7
- data/spec/model/associations_spec.rb +393 -676
- data/spec/model/base_spec.rb +186 -116
- data/spec/model/dataset_methods_spec.rb +7 -27
- data/spec/model/eager_loading_spec.rb +343 -867
- data/spec/model/hooks_spec.rb +160 -79
- data/spec/model/model_spec.rb +118 -165
- data/spec/model/plugins_spec.rb +7 -13
- data/spec/model/record_spec.rb +138 -207
- data/spec/model/spec_helper.rb +10 -73
- metadata +14 -8
data/doc/sharding.rdoc
CHANGED
@@ -10,7 +10,13 @@ that ship with Sequel.
|
|
10
10
|
|
11
11
|
Both features use the :servers Database option. The :servers option should
|
12
12
|
be a hash with symbol keys and values that are either hashes or procs that
|
13
|
-
return hashes.
|
13
|
+
return hashes. These hashes are merged into the Database object's default
|
14
|
+
options hash to get the connection options for the shard, so you don't need
|
15
|
+
to override all options, just the ones that need to be modified. For example,
|
16
|
+
if you are using the same user, password, and database name and just the
|
17
|
+
host is changing, you only need a :host entry in each shard's hash.
|
18
|
+
|
19
|
+
Note that all servers should have the same schema for all
|
14
20
|
tables you are accessing, unless you really know what you are doing.
|
15
21
|
|
16
22
|
== Master and Slave Database Configurations
|
data/doc/testing.rdoc
CHANGED
@@ -6,12 +6,14 @@ Whether or not you use Sequel in your application, you are usually going to want
|
|
6
6
|
|
7
7
|
These run each test in its own transaction, the recommended way to test.
|
8
8
|
|
9
|
+
Make sure you are using Sequel 3.29.0 or above when using these examples, as older versions don't support the <tt>:rollback=>:always</tt> option.
|
10
|
+
|
9
11
|
=== RSpec 1
|
10
12
|
|
11
13
|
class Spec::Example::ExampleGroup
|
12
14
|
def execute(*args, &block)
|
13
15
|
x = nil
|
14
|
-
Sequel::Model.db.transaction{x = super(*args, &block)
|
16
|
+
Sequel::Model.db.transaction(:rollback=>:always){x = super(*args, &block)}
|
15
17
|
x
|
16
18
|
end
|
17
19
|
end
|
@@ -20,7 +22,7 @@ These run each test in its own transaction, the recommended way to test.
|
|
20
22
|
|
21
23
|
class Spec::Example::ExampleGroup
|
22
24
|
around do |example|
|
23
|
-
Sequel::Model.db.transaction{example.call
|
25
|
+
Sequel::Model.db.transaction(:rollback=>:always){example.call}
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
@@ -29,13 +31,20 @@ These run each test in its own transaction, the recommended way to test.
|
|
29
31
|
# Must use this class as the base class for your tests
|
30
32
|
class SequelTestCase < Test::Unit::TestCase
|
31
33
|
def run(*args, &block)
|
32
|
-
Sequel::Model.db.transaction
|
33
|
-
super
|
34
|
-
raise Sequel::Rollback
|
35
|
-
end
|
34
|
+
Sequel::Model.db.transaction(:rollback=>:always){super}
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
38
|
+
== Transactional testing with multiple databases
|
39
|
+
|
40
|
+
You can use the Sequel.transaction method to run a transaction on multiple databases, rolling all of them back. Instead of:
|
41
|
+
|
42
|
+
Sequel::Model.db.transaction(:rollback=>:always)
|
43
|
+
|
44
|
+
Use Sequel.transaction with an array of databases:
|
45
|
+
|
46
|
+
Sequel.transaction([DB1, DB2, DB3], :rollback=>:always)
|
47
|
+
|
39
48
|
== Nontransactional tests
|
40
49
|
|
41
50
|
In some cases, it is not possible to use transactions. For example, if you are testing a web application that is running in a separate process, you don't have access to that process's database connections, so you can't run your examples in transactions. In that case, the best way to handle things is to cleanup after each test by deleting or truncating the database tables used in the test.
|
@@ -77,13 +86,13 @@ The +spec_plugin+ rake task runs the specs for the plugins and extensions that s
|
|
77
86
|
|
78
87
|
== rake spec_<i>adapter</i> (e.g. rake spec_postgres)
|
79
88
|
|
80
|
-
The <tt>spec_<i>adapter</i></tt> specs run against a real database connection with nothing mocked, and test for correct results. They are
|
89
|
+
The <tt>spec_<i>adapter</i></tt> specs run against a real database connection with nothing mocked, and test for correct results. They are slower than the standard specs, but they will catch errors that are mocked out by the default specs, as well as show issues that only occur on a certain database, adapter, or a combination of the two.
|
81
90
|
|
82
91
|
These specs are broken down into two parts. For each database, there are specific specs that only apply to that database, and these are called the adapter specs. There are also shared specs that apply to all (or almost all) databases, these are called the integration specs.
|
83
92
|
|
84
93
|
== Environment variables
|
85
94
|
|
86
|
-
Sequel often uses environment variables when testing to specify either the database to be tested or specify how testing should be done.
|
95
|
+
Sequel often uses environment variables when testing to specify either the database to be tested or specify how testing should be done. You can also specify the databases to test by copying spec/spec_config.rb.example to spec/spec_config.rb and modifying it. See that file for details. It may be necessary to use spec_config.rb as opposed to an environment variable if you database connection cannot be specified by a connection string.
|
87
96
|
|
88
97
|
=== Connection Strings
|
89
98
|
|
@@ -101,6 +110,6 @@ The following environment variables specify Database connection URL strings:
|
|
101
110
|
|
102
111
|
* SEQUEL_MSSQL_SPEC_REQUIRE: Separate file to require when running mssql adapter specs
|
103
112
|
* SEQUEL_DB2_SPEC_REQUIRE: Separate file to require when running db2 adapter specs
|
104
|
-
* SEQUEL_COLUMNS_INTROSPECTION:
|
113
|
+
* SEQUEL_COLUMNS_INTROSPECTION: Whether to run the specs with the columns_introspection extension loaded by default
|
105
114
|
* SEQUEL_NO_PENDING: Don't mark any specs as pending, try running all specs
|
106
115
|
* SKIPPED_TEST_WARN: Warn when skipping any tests because libraries aren't available
|
data/doc/transactions.rdoc
CHANGED
@@ -33,6 +33,46 @@ If any other exception is raised, the transaction is rolled back, and the except
|
|
33
33
|
end # ROLLBACK
|
34
34
|
# ArgumentError raised
|
35
35
|
|
36
|
+
If you want Sequel::Rollback exceptions to be reraised, use the <tt>:rollback => :reraise</tt> option:
|
37
|
+
|
38
|
+
DB.transaction(:rollback => :reraise) do # BEGIN
|
39
|
+
raise Sequel::Rollback
|
40
|
+
end # ROLLBACK
|
41
|
+
# Sequel::Rollback raised
|
42
|
+
|
43
|
+
If you always want to rollback (useful for testing), use the <tt>:rollback => :always</tt> option:
|
44
|
+
|
45
|
+
DB.transaction(:rollback => :always) do # BEGIN
|
46
|
+
DB[:foo].insert(1) # INSERT
|
47
|
+
end # ROLLBACK
|
48
|
+
|
49
|
+
If you want to check whether you are currently in a transaction, use the Database#in_transaction? method:
|
50
|
+
|
51
|
+
DB.in_transaction? # false
|
52
|
+
DB.transaction do
|
53
|
+
DB.in_transaction? # true
|
54
|
+
end
|
55
|
+
|
56
|
+
== Transaction Hooks
|
57
|
+
|
58
|
+
You can add hooks to an in progress transaction that are called after the transaction commits or rolls back:
|
59
|
+
|
60
|
+
x = nil
|
61
|
+
DB.transaction do
|
62
|
+
DB.after_commit{x = 1}
|
63
|
+
DB.after_rollback{x = 2}
|
64
|
+
x # nil
|
65
|
+
end
|
66
|
+
x # 1
|
67
|
+
|
68
|
+
x = nil
|
69
|
+
DB.transaction do
|
70
|
+
DB.after_commit{x = 1}
|
71
|
+
DB.after_rollback{x = 2}
|
72
|
+
raise Sequel::Rollback
|
73
|
+
end
|
74
|
+
x # 2
|
75
|
+
|
36
76
|
== Nested Transaction Calls / Savepoints
|
37
77
|
|
38
78
|
You can nest calls to transaction, which by default just reuses the existing transaction:
|
@@ -71,7 +111,7 @@ Other exceptions, unless rescued inside the outer transaction block, will rollba
|
|
71
111
|
|
72
112
|
== Prepared Transactions / Two-Phase Commit
|
73
113
|
|
74
|
-
Sequel supports database prepared transactions on
|
114
|
+
Sequel supports database prepared transactions on PostgreSQL, MySQL, and H2. With prepared transactions, at the end of the transaction, the transaction is not immediately committed (it acts like a rollback). Later, you can call +commit_prepared_transaction+ to commit the transaction or +rollback_prepared_transaction+ to roll the transaction back. Prepared transactions are usually used with distributed databases to make sure all databases commit the same transaction or none of them do.
|
75
115
|
|
76
116
|
To use prepared transactions in Sequel, you provide a string as the value of the :prepare option:
|
77
117
|
|
data/lib/sequel/adapters/ado.rb
CHANGED
@@ -14,12 +14,14 @@ module Sequel
|
|
14
14
|
when /Microsoft\.(Jet|ACE)\.OLEDB/io
|
15
15
|
Sequel.ts_require 'adapters/shared/access'
|
16
16
|
extend Sequel::Access::DatabaseMethods
|
17
|
+
extend_datasets(Sequel::Access::DatasetMethods)
|
17
18
|
else
|
18
19
|
@opts[:driver] ||= 'SQL Server'
|
19
20
|
case @opts[:driver]
|
20
21
|
when 'SQL Server'
|
21
22
|
Sequel.ts_require 'adapters/ado/mssql'
|
22
23
|
extend Sequel::ADO::MSSQL::DatabaseMethods
|
24
|
+
@dataset_class = ADO::MSSQL::Dataset
|
23
25
|
set_mssql_unicode_strings
|
24
26
|
end
|
25
27
|
end
|
@@ -55,10 +57,6 @@ module Sequel
|
|
55
57
|
handle
|
56
58
|
end
|
57
59
|
|
58
|
-
def dataset(opts = nil)
|
59
|
-
ADO::Dataset.new(self, opts)
|
60
|
-
end
|
61
|
-
|
62
60
|
def execute(sql, opts={})
|
63
61
|
synchronize(opts[:server]) do |conn|
|
64
62
|
begin
|
@@ -77,18 +75,14 @@ module Sequel
|
|
77
75
|
# The ADO adapter's default provider doesn't support transactions, since it
|
78
76
|
# creates a new native connection for each query. So Sequel only attempts
|
79
77
|
# to use transactions if an explicit :provider is given.
|
80
|
-
def
|
81
|
-
|
82
|
-
th = Thread.current
|
83
|
-
begin
|
84
|
-
@transactions << th
|
85
|
-
yield conn
|
86
|
-
rescue Sequel::Rollback
|
87
|
-
ensure
|
88
|
-
@transactions.delete(th)
|
89
|
-
end
|
78
|
+
def begin_transaction(conn, opts={})
|
79
|
+
super if @opts[:provider]
|
90
80
|
end
|
91
|
-
|
81
|
+
|
82
|
+
def commit_transaction(conn, opts={})
|
83
|
+
super if @opts[:provider]
|
84
|
+
end
|
85
|
+
|
92
86
|
def database_error_classes
|
93
87
|
[::WIN32OLERuntimeError]
|
94
88
|
end
|
@@ -100,13 +94,30 @@ module Sequel
|
|
100
94
|
def disconnect_error?(e, opts)
|
101
95
|
super || (e.is_a?(::WIN32OLERuntimeError) && e.message =~ DISCONNECT_ERROR_RE)
|
102
96
|
end
|
97
|
+
|
98
|
+
def rollback_transaction(conn, opts={})
|
99
|
+
super if @opts[:provider]
|
100
|
+
end
|
103
101
|
end
|
104
102
|
|
105
103
|
class Dataset < Sequel::Dataset
|
104
|
+
Database::DatasetClass = self
|
105
|
+
|
106
106
|
def fetch_rows(sql)
|
107
107
|
execute(sql) do |s|
|
108
|
-
|
109
|
-
|
108
|
+
columns = cols = s.Fields.extend(Enumerable).map{|column| output_identifier(column.Name)}
|
109
|
+
if opts[:offset] && offset_returns_row_number_column?
|
110
|
+
rn = row_number_column
|
111
|
+
columns = columns.dup
|
112
|
+
columns.delete(rn)
|
113
|
+
end
|
114
|
+
@columns = columns
|
115
|
+
s.getRows.transpose.each do |r|
|
116
|
+
row = {}
|
117
|
+
cols.each{|c| row[c] = r.shift}
|
118
|
+
row.delete(rn) if rn
|
119
|
+
yield row
|
120
|
+
end unless s.eof
|
110
121
|
end
|
111
122
|
end
|
112
123
|
|
@@ -11,11 +11,6 @@ module Sequel
|
|
11
11
|
# delete query.
|
12
12
|
ROWS_AFFECTED = "SELECT @@ROWCOUNT AS AffectedRows"
|
13
13
|
|
14
|
-
# Return instance of Sequel::ADO::MSSQL::Dataset with the given opts.
|
15
|
-
def dataset(opts=nil)
|
16
|
-
Sequel::ADO::MSSQL::Dataset.new(self, opts)
|
17
|
-
end
|
18
|
-
|
19
14
|
# Just execute so it doesn't attempt to return the number of rows modified.
|
20
15
|
def execute_ddl(sql, opts={})
|
21
16
|
execute(sql, opts)
|
@@ -37,11 +32,28 @@ module Sequel
|
|
37
32
|
end
|
38
33
|
end
|
39
34
|
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# The ADO adapter's default provider doesn't support transactions, since it
|
39
|
+
# creates a new native connection for each query. So Sequel only attempts
|
40
|
+
# to use transactions if an explicit :provider is given.
|
41
|
+
def begin_transaction(conn, opts={})
|
42
|
+
super if @opts[:provider]
|
43
|
+
end
|
44
|
+
|
45
|
+
def commit_transaction(conn, opts={})
|
46
|
+
super if @opts[:provider]
|
47
|
+
end
|
48
|
+
|
49
|
+
def rollback_transaction(conn, opts={})
|
50
|
+
super if @opts[:provider]
|
51
|
+
end
|
40
52
|
end
|
41
53
|
|
42
54
|
class Dataset < ADO::Dataset
|
43
55
|
include Sequel::MSSQL::DatasetMethods
|
44
|
-
|
56
|
+
|
45
57
|
# Use a nasty hack of multiple SQL statements in the same call and
|
46
58
|
# having the last one return the most recently inserted id. This
|
47
59
|
# is necessary as ADO's default :provider uses a separate native
|
@@ -14,6 +14,12 @@ module Sequel
|
|
14
14
|
'float' => ['float', 'double', 'real', 'double precision'],
|
15
15
|
'decimal' => %w'numeric decimal money'
|
16
16
|
)
|
17
|
+
|
18
|
+
# Store the related database object, in order to be able to correctly
|
19
|
+
# handle the database timezone.
|
20
|
+
def initialize(db)
|
21
|
+
@db = db
|
22
|
+
end
|
17
23
|
|
18
24
|
# Return blobs as instances of Sequel::SQL::Blob instead of
|
19
25
|
# Amalgamite::Blob
|
@@ -29,7 +35,7 @@ module Sequel
|
|
29
35
|
|
30
36
|
# Return datetime types as instances of Sequel.datetime_class
|
31
37
|
def datetime(s)
|
32
|
-
|
38
|
+
@db.to_application_timestamp(s)
|
33
39
|
end
|
34
40
|
|
35
41
|
def time(s)
|
@@ -72,7 +78,8 @@ module Sequel
|
|
72
78
|
opts[:database] = ':memory:' if blank_object?(opts[:database])
|
73
79
|
db = ::Amalgalite::Database.new(opts[:database])
|
74
80
|
db.busy_handler(::Amalgalite::BusyTimeout.new(opts.fetch(:timeout, 5000)/50, 50))
|
75
|
-
db.type_map = SequelTypeMap.new
|
81
|
+
db.type_map = SequelTypeMap.new(self)
|
82
|
+
connection_pragmas.each{|s| log_yield(s){db.execute_batch(s)}}
|
76
83
|
db
|
77
84
|
end
|
78
85
|
|
@@ -81,11 +88,6 @@ module Sequel
|
|
81
88
|
:sqlite
|
82
89
|
end
|
83
90
|
|
84
|
-
# Return instance of Sequel::Amalgalite::Dataset with the given options.
|
85
|
-
def dataset(opts = nil)
|
86
|
-
Amalgalite::Dataset.new(self, opts)
|
87
|
-
end
|
88
|
-
|
89
91
|
# Run the given SQL with the given arguments. Returns nil.
|
90
92
|
def execute_ddl(sql, opts={})
|
91
93
|
_execute(sql, opts){|conn| log_yield(sql){conn.execute_batch(sql)}}
|
@@ -155,6 +157,8 @@ module Sequel
|
|
155
157
|
# Dataset class for SQLite datasets that use the amalgalite driver.
|
156
158
|
class Dataset < Sequel::Dataset
|
157
159
|
include ::Sequel::SQLite::DatasetMethods
|
160
|
+
|
161
|
+
Database::DatasetClass = self
|
158
162
|
|
159
163
|
# Yield a hash for each row in the dataset.
|
160
164
|
def fetch_rows(sql)
|
data/lib/sequel/adapters/db2.rb
CHANGED
@@ -3,36 +3,60 @@ Sequel.require %w'shared/db2', 'adapters'
|
|
3
3
|
|
4
4
|
module Sequel
|
5
5
|
module DB2
|
6
|
+
|
7
|
+
@convert_smallint_to_bool = true
|
8
|
+
|
9
|
+
# Underlying error raised by Sequel, since ruby-db2 doesn't
|
10
|
+
# use exceptions.
|
11
|
+
class DB2Error < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Whether to convert smallint values to bool, true by default.
|
16
|
+
# Can also be overridden per dataset.
|
17
|
+
attr_accessor :convert_smallint_to_bool
|
18
|
+
end
|
19
|
+
|
20
|
+
tt = Class.new do
|
21
|
+
def boolean(s) !s.to_i.zero? end
|
22
|
+
def date(s) Date.new(s.year, s.month, s.day) end
|
23
|
+
def time(s) Sequel::SQLTime.create(s.hour, s.minute, s.second) end
|
24
|
+
end.new
|
25
|
+
|
26
|
+
# Hash holding type translation methods, used by Dataset#fetch_rows.
|
27
|
+
DB2_TYPES = {
|
28
|
+
:boolean => tt.method(:boolean),
|
29
|
+
DB2CLI::SQL_BLOB => ::Sequel::SQL::Blob.method(:new),
|
30
|
+
DB2CLI::SQL_TYPE_DATE => tt.method(:date),
|
31
|
+
DB2CLI::SQL_TYPE_TIME => tt.method(:time),
|
32
|
+
DB2CLI::SQL_DECIMAL => ::BigDecimal.method(:new)
|
33
|
+
}
|
34
|
+
DB2_TYPES[DB2CLI::SQL_CLOB] = DB2_TYPES[DB2CLI::SQL_BLOB]
|
35
|
+
|
6
36
|
class Database < Sequel::Database
|
7
37
|
include DatabaseMethods
|
8
38
|
|
9
39
|
set_adapter_scheme :db2
|
10
40
|
|
11
41
|
TEMPORARY = 'GLOBAL TEMPORARY '.freeze
|
42
|
+
rc, NullHandle = DB2CLI.SQLAllocHandle(DB2CLI::SQL_HANDLE_ENV, DB2CLI::SQL_NULL_HANDLE)
|
12
43
|
|
13
|
-
|
14
|
-
|
44
|
+
# Hash of connection procs for converting
|
45
|
+
attr_reader :conversion_procs
|
46
|
+
|
47
|
+
def initialize(opts={})
|
48
|
+
super
|
49
|
+
@conversion_procs = DB2_TYPES.dup
|
50
|
+
@conversion_procs[DB2CLI::SQL_TYPE_TIMESTAMP] = method(:to_application_timestamp_db2)
|
51
|
+
end
|
15
52
|
|
16
53
|
def connect(server)
|
17
54
|
opts = server_opts(server)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
rc = DB2CLI.SQLConnect(dbc, opts[:database], opts[:user], opts[:password])
|
22
|
-
check_error(rc, "Could not connect to database")
|
23
|
-
|
55
|
+
dbc = checked_error("Could not allocate database connection"){DB2CLI.SQLAllocHandle(DB2CLI::SQL_HANDLE_DBC, NullHandle)}
|
56
|
+
checked_error("Could not connect to database"){DB2CLI.SQLConnect(dbc, opts[:database], opts[:user], opts[:password])}
|
24
57
|
dbc
|
25
58
|
end
|
26
59
|
|
27
|
-
def test_connection(server=nil)
|
28
|
-
synchronize(server){|conn|}
|
29
|
-
true
|
30
|
-
end
|
31
|
-
|
32
|
-
def dataset(opts = nil)
|
33
|
-
DB2::Dataset.new(self, opts)
|
34
|
-
end
|
35
|
-
|
36
60
|
def execute(sql, opts={}, &block)
|
37
61
|
synchronize(opts[:server]){|conn| log_connection_execute(conn, sql, &block)}
|
38
62
|
end
|
@@ -43,11 +67,9 @@ module Sequel
|
|
43
67
|
log_connection_execute(conn, sql)
|
44
68
|
sql = "SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1"
|
45
69
|
log_connection_execute(conn, sql) do |sth|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
rc, v = DB2CLI.SQLGetData(sth, 1, datatype, size)
|
50
|
-
check_error(rc, "Could not get data")
|
70
|
+
name, buflen, datatype, size, digits, nullable = checked_error("Could not describe column"){DB2CLI.SQLDescribeCol(sth, 1, 256)}
|
71
|
+
if DB2CLI.SQLFetch(sth) != DB2CLI::SQL_NO_DATA_FOUND
|
72
|
+
v, _ = checked_error("Could not get data"){DB2CLI.SQLGetData(sth, 1, datatype, size)}
|
51
73
|
if v.is_a?(String)
|
52
74
|
return v.to_i
|
53
75
|
else
|
@@ -58,67 +80,101 @@ module Sequel
|
|
58
80
|
end
|
59
81
|
end
|
60
82
|
|
83
|
+
ERROR_MAP = {}
|
84
|
+
%w'SQL_INVALID_HANDLE SQL_STILL_EXECUTING SQL_ERROR'.each do |s|
|
85
|
+
ERROR_MAP[DB2CLI.const_get(s)] = s
|
86
|
+
end
|
61
87
|
def check_error(rc, msg)
|
62
88
|
case rc
|
63
|
-
when DB2CLI::SQL_SUCCESS, DB2CLI::SQL_SUCCESS_WITH_INFO
|
89
|
+
when DB2CLI::SQL_SUCCESS, DB2CLI::SQL_SUCCESS_WITH_INFO, DB2CLI::SQL_NO_DATA_FOUND
|
64
90
|
nil
|
91
|
+
when DB2CLI::SQL_INVALID_HANDLE, DB2CLI::SQL_STILL_EXECUTING
|
92
|
+
e = DB2Error.new("#{ERROR_MAP[rc]}: #{msg}")
|
93
|
+
e.set_backtrace(caller)
|
94
|
+
raise_error(e, :disconnect=>true)
|
65
95
|
else
|
66
|
-
|
96
|
+
e = DB2Error.new("#{ERROR_MAP[rc] || "Error code #{rc}"}: #{msg}")
|
97
|
+
e.set_backtrace(caller)
|
98
|
+
raise_error(e, :disconnect=>true)
|
67
99
|
end
|
68
100
|
end
|
69
101
|
|
102
|
+
def checked_error(msg)
|
103
|
+
rc, *ary= yield
|
104
|
+
check_error(rc, msg)
|
105
|
+
ary.length <= 1 ? ary.first : ary
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_application_timestamp_db2(v)
|
109
|
+
to_application_timestamp(v.to_s)
|
110
|
+
end
|
111
|
+
|
70
112
|
private
|
71
113
|
|
72
114
|
def begin_transaction(conn, opts={})
|
73
115
|
log_yield(TRANSACTION_BEGIN){DB2CLI.SQLSetConnectAttr(conn, DB2CLI::SQL_ATTR_AUTOCOMMIT, DB2CLI::SQL_AUTOCOMMIT_OFF)}
|
74
|
-
|
116
|
+
end
|
117
|
+
|
118
|
+
def remove_transaction(conn, committed)
|
119
|
+
DB2CLI.SQLSetConnectAttr(conn, DB2CLI::SQL_ATTR_AUTOCOMMIT, DB2CLI::SQL_AUTOCOMMIT_ON)
|
120
|
+
ensure
|
121
|
+
super
|
75
122
|
end
|
76
123
|
|
77
124
|
def rollback_transaction(conn, opts={})
|
78
125
|
log_yield(TRANSACTION_ROLLBACK){DB2CLI.SQLEndTran(DB2CLI::SQL_HANDLE_DBC, conn, DB2CLI::SQL_ROLLBACK)}
|
79
|
-
ensure
|
80
|
-
DB2CLI.SQLSetConnectAttr(conn, DB2CLI::SQL_ATTR_AUTOCOMMIT, DB2CLI::SQL_AUTOCOMMIT_ON)
|
81
126
|
end
|
82
127
|
|
83
128
|
def commit_transaction(conn, opts={})
|
84
129
|
log_yield(TRANSACTION_COMMIT){DB2CLI.SQLEndTran(DB2CLI::SQL_HANDLE_DBC, conn, DB2CLI::SQL_COMMIT)}
|
85
|
-
ensure
|
86
|
-
DB2CLI.SQLSetConnectAttr(conn, DB2CLI::SQL_ATTR_AUTOCOMMIT, DB2CLI::SQL_AUTOCOMMIT_ON)
|
87
130
|
end
|
88
131
|
|
89
132
|
def log_connection_execute(conn, sql)
|
90
|
-
|
91
|
-
check_error(rc, "Could not allocate statement")
|
133
|
+
sth = checked_error("Could not allocate statement"){DB2CLI.SQLAllocHandle(DB2CLI::SQL_HANDLE_STMT, conn)}
|
92
134
|
|
93
135
|
begin
|
94
|
-
|
95
|
-
check_error(rc, "Could not execute statement: #{sql}")
|
136
|
+
checked_error("Could not execute statement: #{sql}"){log_yield(sql){DB2CLI.SQLExecDirect(sth, sql)}}
|
96
137
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
138
|
+
if block_given?
|
139
|
+
yield(sth)
|
140
|
+
else
|
141
|
+
checked_error("Could not get RPC"){DB2CLI.SQLRowCount(sth)}
|
142
|
+
end
|
102
143
|
ensure
|
103
|
-
|
104
|
-
check_error(rc, "Could not free statement")
|
144
|
+
checked_error("Could not free statement"){DB2CLI.SQLFreeHandle(DB2CLI::SQL_HANDLE_STMT, sth)}
|
105
145
|
end
|
106
146
|
end
|
107
147
|
|
108
|
-
|
109
|
-
|
110
|
-
|
148
|
+
# Convert smallint type to boolean if convert_smallint_to_bool is true
|
149
|
+
def schema_column_type(db_type)
|
150
|
+
if DB2.convert_smallint_to_bool && db_type =~ /smallint/i
|
151
|
+
:boolean
|
152
|
+
else
|
153
|
+
super
|
154
|
+
end
|
155
|
+
end
|
111
156
|
|
112
|
-
|
113
|
-
|
157
|
+
def disconnect_connection(conn)
|
158
|
+
checked_error("Could not disconnect from database"){DB2CLI.SQLDisconnect(conn)}
|
159
|
+
checked_error("Could not free Database handle"){DB2CLI.SQLFreeHandle(DB2CLI::SQL_HANDLE_DBC, conn)}
|
114
160
|
end
|
115
161
|
end
|
116
162
|
|
117
163
|
class Dataset < Sequel::Dataset
|
118
164
|
include DatasetMethods
|
119
165
|
|
166
|
+
Database::DatasetClass = self
|
120
167
|
MAX_COL_SIZE = 256
|
121
168
|
|
169
|
+
# Whether to convert smallint to boolean arguments for this dataset.
|
170
|
+
# Defaults to the DB2 module setting.
|
171
|
+
def convert_smallint_to_bool
|
172
|
+
defined?(@convert_smallint_to_bool) ? @convert_smallint_to_bool : (@convert_smallint_to_bool = DB2.convert_smallint_to_bool)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Override the default DB2.convert_smallint_to_bool setting for this dataset.
|
176
|
+
attr_writer :convert_smallint_to_bool
|
177
|
+
|
122
178
|
def fetch_rows(sql)
|
123
179
|
execute(sql) do |sth|
|
124
180
|
offset = @opts[:offset]
|
@@ -128,13 +184,19 @@ module Sequel
|
|
128
184
|
cols = column_info.map{|c| c.at(1)}
|
129
185
|
cols.delete(row_number_column) if offset
|
130
186
|
@columns = cols
|
131
|
-
|
187
|
+
errors = [DB2CLI::SQL_NO_DATA_FOUND, DB2CLI::SQL_ERROR]
|
188
|
+
until errors.include?(rc = DB2CLI.SQLFetch(sth))
|
132
189
|
db.check_error(rc, "Could not fetch row")
|
133
190
|
row = {}
|
134
|
-
column_info.each do |i, c, t, s|
|
135
|
-
|
136
|
-
|
137
|
-
|
191
|
+
column_info.each do |i, c, t, s, pr|
|
192
|
+
v, _ = db.checked_error("Could not get data"){DB2CLI.SQLGetData(sth, i, t, s)}
|
193
|
+
row[c] = if v == DB2CLI::Null
|
194
|
+
nil
|
195
|
+
elsif pr
|
196
|
+
pr.call(v)
|
197
|
+
else
|
198
|
+
v
|
199
|
+
end
|
138
200
|
end
|
139
201
|
row.delete(row_number_column) if offset
|
140
202
|
yield row
|
@@ -147,30 +209,20 @@ module Sequel
|
|
147
209
|
|
148
210
|
def get_column_info(sth)
|
149
211
|
db = @db
|
150
|
-
|
151
|
-
|
212
|
+
column_count = db.checked_error("Could not get number of result columns"){DB2CLI.SQLNumResultCols(sth)}
|
213
|
+
convert = convert_smallint_to_bool
|
214
|
+
cps = db.conversion_procs
|
152
215
|
|
153
216
|
(1..column_count).map do |i|
|
154
|
-
|
155
|
-
|
156
|
-
|
217
|
+
name, buflen, datatype, size, digits, nullable = db.checked_error("Could not describe column"){DB2CLI.SQLDescribeCol(sth, i, MAX_COL_SIZE)}
|
218
|
+
pr = if datatype == DB2CLI::SQL_SMALLINT && convert && size <= 5 && digits <= 1
|
219
|
+
cps[:boolean]
|
220
|
+
else
|
221
|
+
cps[datatype]
|
222
|
+
end
|
223
|
+
[i, output_identifier(name), datatype, size, pr]
|
157
224
|
end
|
158
225
|
end
|
159
|
-
|
160
|
-
def convert_type(v)
|
161
|
-
case v
|
162
|
-
when DB2CLI::Date
|
163
|
-
Date.new(v.year, v.month, v.day)
|
164
|
-
when DB2CLI::Time
|
165
|
-
Sequel::SQLTime.create(v.hour, v.minute, v.second)
|
166
|
-
when DB2CLI::Timestamp
|
167
|
-
Sequel.database_to_application_timestamp(v.to_s)
|
168
|
-
when DB2CLI::Null
|
169
|
-
nil
|
170
|
-
else
|
171
|
-
v
|
172
|
-
end
|
173
|
-
end
|
174
226
|
end
|
175
227
|
end
|
176
228
|
end
|