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
@@ -14,9 +14,6 @@ module Sequel
|
|
14
14
|
def boolean(s) s.to_i != 0 end
|
15
15
|
def integer(s) s.to_i end
|
16
16
|
def float(s) s.to_f 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
|
20
17
|
end.new
|
21
18
|
|
22
19
|
# Hash with integer keys and callable values for converting MySQL types.
|
@@ -30,52 +27,12 @@ module Sequel
|
|
30
27
|
k.each{|n| MYSQL_TYPES[n] = v}
|
31
28
|
end
|
32
29
|
|
33
|
-
# Modify the type translator used for the tinyint type based
|
34
|
-
# on the value given.
|
35
|
-
def self.convert_tinyint_to_bool=(v)
|
36
|
-
MYSQL_TYPES[1] = TYPE_TRANSLATOR.method(v ? :boolean : :integer)
|
37
|
-
@convert_tinyint_to_bool = v
|
38
|
-
end
|
39
|
-
self.convert_tinyint_to_bool = convert_tinyint_to_bool
|
40
|
-
|
41
30
|
class << self
|
42
|
-
#
|
43
|
-
|
44
|
-
# like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
|
45
|
-
# it returns the strings as is.
|
46
|
-
attr_reader :convert_invalid_date_time
|
47
|
-
end
|
48
|
-
|
49
|
-
# Modify the type translators for the date, time, and timestamp types
|
50
|
-
# depending on the value given.
|
51
|
-
def self.convert_invalid_date_time=(v)
|
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)
|
54
|
-
[10, 14].each{|i| MYSQL_TYPES[i] = m}
|
55
|
-
m = (v != false) ? TYPE_TRANSLATOR.method(:timestamp) : ::Sequel.method(:database_to_application_timestamp)
|
56
|
-
[7, 12].each{|i| MYSQL_TYPES[i] = m}
|
57
|
-
@convert_invalid_date_time = v
|
31
|
+
# Whether to convert invalid date time values by default.
|
32
|
+
attr_accessor :convert_invalid_date_time
|
58
33
|
end
|
59
34
|
self.convert_invalid_date_time = false
|
60
35
|
|
61
|
-
# If convert_invalid_date_time is nil, :nil, or :string and
|
62
|
-
# the conversion raises an InvalidValue exception, return v
|
63
|
-
# if :string and nil otherwise.
|
64
|
-
def self.convert_date_time(meth, v)
|
65
|
-
begin
|
66
|
-
Sequel.send(meth, v)
|
67
|
-
rescue InvalidValue
|
68
|
-
case @convert_invalid_date_time
|
69
|
-
when nil, :nil
|
70
|
-
nil
|
71
|
-
when :string
|
72
|
-
v
|
73
|
-
else
|
74
|
-
raise
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
36
|
# Database class for MySQL databases used with Sequel.
|
80
37
|
class Database < Sequel::Database
|
81
38
|
include Sequel::MySQL::DatabaseMethods
|
@@ -85,7 +42,26 @@ module Sequel
|
|
85
42
|
MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away|Lost connection to MySQL server during query)/
|
86
43
|
|
87
44
|
set_adapter_scheme :mysql
|
45
|
+
|
46
|
+
# Hash of conversion procs for the current database
|
47
|
+
attr_reader :conversion_procs
|
48
|
+
#
|
49
|
+
# Whether to convert tinyint columns to bool for the current database
|
50
|
+
attr_reader :convert_tinyint_to_bool
|
51
|
+
|
52
|
+
# By default, Sequel raises an exception if in invalid date or time is used.
|
53
|
+
# However, if this is set to nil or :nil, the adapter treats dates
|
54
|
+
# like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
|
55
|
+
# it returns the strings as is.
|
56
|
+
attr_reader :convert_invalid_date_time
|
88
57
|
|
58
|
+
def initialize(opts={})
|
59
|
+
super
|
60
|
+
@conversion_procs = MYSQL_TYPES.dup
|
61
|
+
self.convert_tinyint_to_bool = Sequel::MySQL.convert_tinyint_to_bool
|
62
|
+
self.convert_invalid_date_time = Sequel::MySQL.convert_invalid_date_time
|
63
|
+
end
|
64
|
+
|
89
65
|
# Connect to the database. In addition to the usual database options,
|
90
66
|
# the following options have effect:
|
91
67
|
#
|
@@ -155,11 +131,27 @@ module Sequel
|
|
155
131
|
conn
|
156
132
|
end
|
157
133
|
|
158
|
-
#
|
159
|
-
|
160
|
-
|
134
|
+
# Modify the type translators for the date, time, and timestamp types
|
135
|
+
# depending on the value given.
|
136
|
+
def convert_invalid_date_time=(v)
|
137
|
+
m0 = ::Sequel.method(:string_to_time)
|
138
|
+
@conversion_procs[11] = (v != false) ? lambda{|v| convert_date_time(v, &m0)} : m0
|
139
|
+
m1 = ::Sequel.method(:string_to_date)
|
140
|
+
m = (v != false) ? lambda{|v| convert_date_time(v, &m1)} : m1
|
141
|
+
[10, 14].each{|i| @conversion_procs[i] = m}
|
142
|
+
m2 = method(:to_application_timestamp)
|
143
|
+
m = (v != false) ? lambda{|v| convert_date_time(v, &m2)} : m2
|
144
|
+
[7, 12].each{|i| @conversion_procs[i] = m}
|
145
|
+
@convert_invalid_date_time = v
|
161
146
|
end
|
162
|
-
|
147
|
+
|
148
|
+
# Modify the type translator used for the tinyint type based
|
149
|
+
# on the value given.
|
150
|
+
def convert_tinyint_to_bool=(v)
|
151
|
+
@conversion_procs[1] = TYPE_TRANSLATOR.method(v ? :boolean : :integer)
|
152
|
+
@convert_tinyint_to_bool = v
|
153
|
+
end
|
154
|
+
|
163
155
|
# Return the version of the MySQL server two which we are connecting.
|
164
156
|
def server_version(server=nil)
|
165
157
|
@server_version ||= (synchronize(server){|conn| conn.server_version if conn.respond_to?(:server_version)} || super)
|
@@ -219,6 +211,24 @@ module Sequel
|
|
219
211
|
:query
|
220
212
|
end
|
221
213
|
|
214
|
+
# If convert_invalid_date_time is nil, :nil, or :string and
|
215
|
+
# the conversion raises an InvalidValue exception, return v
|
216
|
+
# if :string and nil otherwise.
|
217
|
+
def convert_date_time(v)
|
218
|
+
begin
|
219
|
+
yield v
|
220
|
+
rescue InvalidValue
|
221
|
+
case @convert_invalid_date_time
|
222
|
+
when nil, :nil
|
223
|
+
nil
|
224
|
+
when :string
|
225
|
+
v
|
226
|
+
else
|
227
|
+
raise
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
222
232
|
# The MySQL adapter main error class is Mysql::Error
|
223
233
|
def database_error_classes
|
224
234
|
[Mysql::Error]
|
@@ -245,7 +255,7 @@ module Sequel
|
|
245
255
|
|
246
256
|
# Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
|
247
257
|
def schema_column_type(db_type)
|
248
|
-
|
258
|
+
convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
|
249
259
|
end
|
250
260
|
end
|
251
261
|
|
@@ -253,6 +263,8 @@ module Sequel
|
|
253
263
|
class Dataset < Sequel::Dataset
|
254
264
|
include Sequel::MySQL::DatasetMethods
|
255
265
|
include Sequel::MySQL::PreparedStatements::DatasetMethods
|
266
|
+
|
267
|
+
Database::DatasetClass = self
|
256
268
|
|
257
269
|
# Delete rows matching this dataset
|
258
270
|
def delete
|
@@ -265,11 +277,12 @@ module Sequel
|
|
265
277
|
def fetch_rows(sql, &block)
|
266
278
|
execute(sql) do |r|
|
267
279
|
i = -1
|
280
|
+
cps = db.conversion_procs
|
268
281
|
cols = r.fetch_fields.map do |f|
|
269
282
|
# Pretend tinyint is another integer type if its length is not 1, to
|
270
283
|
# avoid casting to boolean if Sequel::MySQL.convert_tinyint_to_bool
|
271
284
|
# is set.
|
272
|
-
type_proc = f.type == 1 && f.length != 1 ?
|
285
|
+
type_proc = f.type == 1 && f.length != 1 ? cps[2] : cps[f.type]
|
273
286
|
[output_identifier(f.name), type_proc, i+=1]
|
274
287
|
end
|
275
288
|
@columns = cols.map{|c| c.first}
|
@@ -10,9 +10,18 @@ module Sequel
|
|
10
10
|
include Sequel::MySQL::PreparedStatements::DatabaseMethods
|
11
11
|
|
12
12
|
# Mysql::Error messages that indicate the current connection should be disconnected
|
13
|
-
MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away)/
|
13
|
+
MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away|This connection is still waiting for a result, try again once you have the result)/
|
14
14
|
|
15
15
|
set_adapter_scheme :mysql2
|
16
|
+
|
17
|
+
# Whether to convert tinyint columns to bool for this database
|
18
|
+
attr_accessor :convert_tinyint_to_bool
|
19
|
+
|
20
|
+
# Set the convert_tinyint_to_bool setting based on the default value.
|
21
|
+
def initialize(opts={})
|
22
|
+
super
|
23
|
+
self.convert_tinyint_to_bool = Sequel::MySQL.convert_tinyint_to_bool
|
24
|
+
end
|
16
25
|
|
17
26
|
# Connect to the database. In addition to the usual database options,
|
18
27
|
# the following options have effect:
|
@@ -60,11 +69,6 @@ module Sequel
|
|
60
69
|
conn
|
61
70
|
end
|
62
71
|
|
63
|
-
# Returns instance of Sequel::MySQL::Dataset with the given options.
|
64
|
-
def dataset(opts = nil)
|
65
|
-
Mysql2::Dataset.new(self, opts)
|
66
|
-
end
|
67
|
-
|
68
72
|
# Return the version of the MySQL server two which we are connecting.
|
69
73
|
def server_version(server=nil)
|
70
74
|
@server_version ||= (synchronize(server){|conn| conn.server_info[:id]} || super)
|
@@ -77,7 +81,7 @@ module Sequel
|
|
77
81
|
# yield the connection if a block is given.
|
78
82
|
def _execute(conn, sql, opts)
|
79
83
|
begin
|
80
|
-
r = log_yield(sql){conn.query(sql, :symbolize_keys => true, :database_timezone =>
|
84
|
+
r = log_yield(sql){conn.query(sql, :symbolize_keys => true, :database_timezone => timezone, :application_timezone => Sequel.application_timezone)}
|
81
85
|
if opts[:type] == :select
|
82
86
|
yield r if r
|
83
87
|
elsif block_given?
|
@@ -115,7 +119,7 @@ module Sequel
|
|
115
119
|
|
116
120
|
# Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
|
117
121
|
def schema_column_type(db_type)
|
118
|
-
|
122
|
+
convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
|
119
123
|
end
|
120
124
|
end
|
121
125
|
|
@@ -124,6 +128,8 @@ module Sequel
|
|
124
128
|
include Sequel::MySQL::DatasetMethods
|
125
129
|
include Sequel::MySQL::PreparedStatements::DatasetMethods
|
126
130
|
|
131
|
+
Database::DatasetClass = self
|
132
|
+
|
127
133
|
# Delete rows matching this dataset
|
128
134
|
def delete
|
129
135
|
execute_dui(delete_sql){|c| return c.affected_rows}
|
@@ -133,7 +139,7 @@ module Sequel
|
|
133
139
|
def fetch_rows(sql, &block)
|
134
140
|
execute(sql) do |r|
|
135
141
|
@columns = r.fields
|
136
|
-
r.each(:cast_booleans =>
|
142
|
+
r.each(:cast_booleans => db.convert_tinyint_to_bool, &block)
|
137
143
|
end
|
138
144
|
self
|
139
145
|
end
|
data/lib/sequel/adapters/odbc.rb
CHANGED
@@ -15,13 +15,16 @@ module Sequel
|
|
15
15
|
when 'mssql'
|
16
16
|
Sequel.ts_require 'adapters/odbc/mssql'
|
17
17
|
extend Sequel::ODBC::MSSQL::DatabaseMethods
|
18
|
+
@dataset_class = Sequel::ODBC::MSSQL::Dataset
|
18
19
|
set_mssql_unicode_strings
|
19
20
|
when 'progress'
|
20
21
|
Sequel.ts_require 'adapters/shared/progress'
|
21
22
|
extend Sequel::Progress::DatabaseMethods
|
23
|
+
extend_datasets(Sequel::Progress::DatasetMethods)
|
22
24
|
when 'db2'
|
23
25
|
Sequel.ts_require 'adapters/odbc/db2'
|
24
26
|
extend Sequel::ODBC::DB2::DatabaseMethods
|
27
|
+
@dataset_class = Sequel::ODBC::DB2::Dataset
|
25
28
|
end
|
26
29
|
end
|
27
30
|
|
@@ -45,10 +48,6 @@ module Sequel
|
|
45
48
|
conn
|
46
49
|
end
|
47
50
|
|
48
|
-
def dataset(opts = nil)
|
49
|
-
ODBC::Dataset.new(self, opts)
|
50
|
-
end
|
51
|
-
|
52
51
|
def execute(sql, opts={})
|
53
52
|
synchronize(opts[:server]) do |conn|
|
54
53
|
begin
|
@@ -99,15 +98,23 @@ module Sequel
|
|
99
98
|
ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
|
100
99
|
TIMESTAMP_FORMAT="{ts '%Y-%m-%d %H:%M:%S'}".freeze
|
101
100
|
|
101
|
+
Database::DatasetClass = self
|
102
|
+
|
102
103
|
def fetch_rows(sql)
|
103
104
|
execute(sql) do |s|
|
104
105
|
i = -1
|
105
106
|
cols = s.columns(true).map{|c| [output_identifier(c.name), i+=1]}
|
106
|
-
|
107
|
+
columns = cols.map{|c| c.at(0)}
|
108
|
+
if opts[:offset] && offset_returns_row_number_column?
|
109
|
+
rn = row_number_column
|
110
|
+
columns.delete(rn)
|
111
|
+
end
|
112
|
+
@columns = columns
|
107
113
|
if rows = s.fetch_all
|
108
114
|
rows.each do |row|
|
109
115
|
hash = {}
|
110
116
|
cols.each{|n,i| hash[n] = convert_odbc_value(row[i])}
|
117
|
+
hash.delete(rn) if rn
|
111
118
|
yield hash
|
112
119
|
end
|
113
120
|
end
|
@@ -126,7 +133,7 @@ module Sequel
|
|
126
133
|
# ODBCColumn#mapSqlTypeToGenericType and Column#klass.
|
127
134
|
case v
|
128
135
|
when ::ODBC::TimeStamp
|
129
|
-
|
136
|
+
db.to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second])
|
130
137
|
when ::ODBC::Time
|
131
138
|
Sequel::SQLTime.create(v.hour, v.minute, v.second)
|
132
139
|
when ::ODBC::Date
|
@@ -9,11 +9,6 @@ module Sequel
|
|
9
9
|
include Sequel::MSSQL::DatabaseMethods
|
10
10
|
LAST_INSERT_ID_SQL='SELECT SCOPE_IDENTITY()'
|
11
11
|
|
12
|
-
# Return an instance of Sequel::ODBC::MSSQL::Dataset with the given opts.
|
13
|
-
def dataset(opts=nil)
|
14
|
-
Sequel::ODBC::MSSQL::Dataset.new(self, opts)
|
15
|
-
end
|
16
|
-
|
17
12
|
# Return the last inserted identity value.
|
18
13
|
def execute_insert(sql, opts={})
|
19
14
|
synchronize(opts[:server]) do |conn|
|
@@ -15,10 +15,6 @@ module Sequel
|
|
15
15
|
)
|
16
16
|
end
|
17
17
|
|
18
|
-
def dataset(opts = nil)
|
19
|
-
OpenBase::Dataset.new(self, opts)
|
20
|
-
end
|
21
|
-
|
22
18
|
def execute(sql, opts={})
|
23
19
|
synchronize(opts[:server]) do |conn|
|
24
20
|
r = log_yield(sql){conn.execute(sql)}
|
@@ -37,6 +33,8 @@ module Sequel
|
|
37
33
|
|
38
34
|
class Dataset < Sequel::Dataset
|
39
35
|
SELECT_CLAUSE_METHODS = clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
|
36
|
+
|
37
|
+
Database::DatasetClass = self
|
40
38
|
|
41
39
|
def fetch_rows(sql)
|
42
40
|
execute(sql) do |result|
|
@@ -13,6 +13,18 @@ module Sequel
|
|
13
13
|
# ORA-03114: not connected to ORACLE
|
14
14
|
CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
|
15
15
|
|
16
|
+
ORACLE_TYPES = {:blob=>lambda{|b| Sequel::SQL::Blob.new(b.read)}}
|
17
|
+
|
18
|
+
# Hash of conversion procs for this database.
|
19
|
+
attr_reader :conversion_procs
|
20
|
+
|
21
|
+
def initialize(opts={})
|
22
|
+
super
|
23
|
+
@autosequence = opts[:autosequence]
|
24
|
+
@primary_key_sequences = {}
|
25
|
+
@conversion_procs = ORACLE_TYPES.dup
|
26
|
+
end
|
27
|
+
|
16
28
|
def connect(server)
|
17
29
|
opts = server_opts(server)
|
18
30
|
if opts[:database]
|
@@ -22,6 +34,7 @@ module Sequel
|
|
22
34
|
dbname = opts[:host]
|
23
35
|
end
|
24
36
|
conn = OCI8.new(opts[:user], opts[:password], dbname, opts[:privilege])
|
37
|
+
conn.prefetch_rows = typecast_value_integer(opts[:prefetch_rows]) if opts[:prefetch_rows]
|
25
38
|
conn.autocommit = true
|
26
39
|
conn.non_blocking = true
|
27
40
|
|
@@ -36,59 +49,129 @@ module Sequel
|
|
36
49
|
conn.exec("ALTER SESSION SET TIME_ZONE='-00:00'")
|
37
50
|
end
|
38
51
|
|
52
|
+
class << conn
|
53
|
+
attr_reader :prepared_statements
|
54
|
+
end
|
55
|
+
conn.instance_variable_set(:@prepared_statements, {})
|
56
|
+
|
39
57
|
conn
|
40
58
|
end
|
41
|
-
|
42
|
-
def
|
43
|
-
|
59
|
+
|
60
|
+
def execute(sql, opts={}, &block)
|
61
|
+
_execute(nil, sql, opts, &block)
|
44
62
|
end
|
63
|
+
alias do execute
|
45
64
|
|
46
|
-
def
|
47
|
-
|
48
|
-
ds.identifier_output_method = :downcase
|
49
|
-
schema_and_table = "#{"#{quote_identifier(opts[:schema])}." if opts[:schema]}#{quote_identifier(table)}"
|
50
|
-
table_schema = []
|
51
|
-
metadata = transaction(opts){|conn| conn.describe_table(schema_and_table)}
|
52
|
-
metadata.columns.each do |column|
|
53
|
-
table_schema << [
|
54
|
-
column.name.downcase.to_sym,
|
55
|
-
{
|
56
|
-
:type => column.data_type,
|
57
|
-
:db_type => column.type_string.split(' ')[0],
|
58
|
-
:type_string => column.type_string,
|
59
|
-
:charset_form => column.charset_form,
|
60
|
-
:char_used => column.char_used?,
|
61
|
-
:char_size => column.char_size,
|
62
|
-
:data_size => column.data_size,
|
63
|
-
:precision => column.precision,
|
64
|
-
:scale => column.scale,
|
65
|
-
:fsprecision => column.fsprecision,
|
66
|
-
:lfprecision => column.lfprecision,
|
67
|
-
:allow_null => column.nullable?
|
68
|
-
}
|
69
|
-
]
|
70
|
-
end
|
71
|
-
table_schema
|
65
|
+
def execute_insert(sql, opts={})
|
66
|
+
_execute(:insert, sql, opts)
|
72
67
|
end
|
73
68
|
|
74
|
-
|
69
|
+
private
|
70
|
+
|
71
|
+
def _execute(type, sql, opts={}, &block)
|
75
72
|
synchronize(opts[:server]) do |conn|
|
76
73
|
begin
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
74
|
+
return execute_prepared_statement(conn, type, sql, opts, &block) if sql.is_a?(Symbol)
|
75
|
+
if args = opts[:arguments]
|
76
|
+
r = conn.parse(sql)
|
77
|
+
args = cursor_bind_params(conn, r, args)
|
78
|
+
nr = log_yield(sql, args){r.exec}
|
79
|
+
r = nr unless block_given?
|
80
|
+
else
|
81
|
+
r = log_yield(sql){conn.exec(sql)}
|
82
|
+
end
|
83
|
+
if block_given?
|
84
|
+
begin
|
85
|
+
yield(r)
|
86
|
+
ensure
|
87
|
+
r.close
|
88
|
+
end
|
89
|
+
elsif type == :insert
|
90
|
+
last_insert_id(conn, opts)
|
91
|
+
else
|
92
|
+
r
|
93
|
+
end
|
94
|
+
rescue OCIException, RuntimeError => e
|
95
|
+
# ruby-oci8 is naughty and raises strings in some places
|
81
96
|
raise_error(e)
|
82
97
|
end
|
83
98
|
end
|
84
99
|
end
|
85
|
-
alias_method :do, :execute
|
86
100
|
|
87
|
-
|
88
|
-
|
101
|
+
PS_TYPES = {'string'.freeze=>String, 'integer'.freeze=>Integer, 'float'.freeze=>Float,
|
102
|
+
'decimal'.freeze=>Float, 'date'.freeze=>Time, 'datetime'.freeze=>Time,
|
103
|
+
'time'.freeze=>Time, 'boolean'.freeze=>String, 'blob'.freeze=>OCI8::BLOB}
|
104
|
+
def cursor_bind_params(conn, cursor, args)
|
105
|
+
cursor
|
106
|
+
i = 0
|
107
|
+
args.map do |arg, type|
|
108
|
+
i += 1
|
109
|
+
case arg
|
110
|
+
when true
|
111
|
+
arg = 'Y'
|
112
|
+
when false
|
113
|
+
arg = 'N'
|
114
|
+
when BigDecimal
|
115
|
+
arg = arg.to_f
|
116
|
+
when ::Sequel::SQL::Blob
|
117
|
+
raise Error, "Sequel's oracle adapter does not currently support using a blob in a bound variable"
|
118
|
+
end
|
119
|
+
if t = PS_TYPES[type]
|
120
|
+
cursor.bind_param(i, arg, t)
|
121
|
+
else
|
122
|
+
cursor.bind_param(i, arg, arg.class)
|
123
|
+
end
|
124
|
+
arg
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def execute_prepared_statement(conn, type, name, opts)
|
129
|
+
ps = prepared_statements[name]
|
130
|
+
sql = ps.prepared_sql
|
131
|
+
if cursora = conn.prepared_statements[name]
|
132
|
+
cursor, cursor_sql = cursora
|
133
|
+
if cursor_sql != sql
|
134
|
+
cursor.close
|
135
|
+
cursor = nil
|
136
|
+
end
|
137
|
+
end
|
138
|
+
unless cursor
|
139
|
+
cursor = log_yield("Preparing #{name}: #{sql}"){conn.parse(sql)}
|
140
|
+
conn.prepared_statements[name] = [cursor, sql]
|
141
|
+
end
|
142
|
+
args = cursor_bind_params(conn, cursor, opts[:arguments])
|
143
|
+
r = log_yield("Executing #{name}", args){cursor.exec}
|
144
|
+
if block_given?
|
145
|
+
yield(cursor)
|
146
|
+
elsif type == :insert
|
147
|
+
last_insert_id(conn, opts)
|
148
|
+
else
|
149
|
+
r
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def last_insert_id(conn, opts)
|
154
|
+
unless sequence = opts[:sequence]
|
155
|
+
if t = opts[:table]
|
156
|
+
sequence = sequence_for_table(t)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
if sequence
|
160
|
+
sql = "SELECT #{literal(sequence)}.currval FROM dual"
|
161
|
+
begin
|
162
|
+
cursor = log_yield(sql){conn.exec(sql)}
|
163
|
+
row = cursor.fetch
|
164
|
+
row.each{|v| return (v.to_i if v)}
|
165
|
+
rescue OCIError
|
166
|
+
nil
|
167
|
+
ensure
|
168
|
+
cursor.close if cursor
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
89
173
|
def begin_transaction(conn, opts={})
|
90
174
|
log_yield(TRANSACTION_BEGIN){conn.autocommit = false}
|
91
|
-
conn
|
92
175
|
end
|
93
176
|
|
94
177
|
def commit_transaction(conn, opts={})
|
@@ -102,44 +185,239 @@ module Sequel
|
|
102
185
|
end
|
103
186
|
|
104
187
|
def disconnect_error?(e, opts)
|
105
|
-
super || (e.is_a?(::
|
188
|
+
super || (e.is_a?(::OCIError) && CONNECTION_ERROR_CODES.include?(e.code))
|
106
189
|
end
|
107
190
|
|
108
|
-
def
|
109
|
-
|
191
|
+
def oracle_column_type(h)
|
192
|
+
case h[:oci8_type]
|
193
|
+
when :number
|
194
|
+
case h[:scale]
|
195
|
+
when 0
|
196
|
+
:integer
|
197
|
+
when -127
|
198
|
+
:float
|
199
|
+
else
|
200
|
+
:decimal
|
201
|
+
end
|
202
|
+
when :date
|
203
|
+
:datetime
|
204
|
+
else
|
205
|
+
schema_column_type(h[:db_type])
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def remove_transaction(conn, committed)
|
210
|
+
conn.autocommit = true
|
211
|
+
ensure
|
110
212
|
super
|
111
213
|
end
|
112
214
|
|
113
215
|
def rollback_transaction(conn, opts={})
|
114
216
|
log_yield(TRANSACTION_ROLLBACK){conn.rollback}
|
115
217
|
end
|
218
|
+
|
219
|
+
def schema_parse_table(table, opts={})
|
220
|
+
schema, table = schema_and_table(table)
|
221
|
+
schema ||= opts[:schema]
|
222
|
+
schema_and_table = if ds = opts[:dataset]
|
223
|
+
ds.literal(schema ? SQL::QualifiedIdentifier.new(schema, table) : SQL::Identifier.new(table))
|
224
|
+
else
|
225
|
+
"#{"#{quote_identifier(schema)}." if schema}#{quote_identifier(table)}"
|
226
|
+
end
|
227
|
+
table_schema = []
|
228
|
+
m = output_identifier_meth(ds)
|
229
|
+
im = input_identifier_meth(ds)
|
230
|
+
|
231
|
+
# Primary Keys
|
232
|
+
ds = metadata_dataset.from(:all_constraints___cons, :all_cons_columns___cols).
|
233
|
+
where(:cols__table_name=>im.call(table), :cons__constraint_type=>'P',
|
234
|
+
:cons__constraint_name=>:cols__constraint_name, :cons__owner=>:cols__owner)
|
235
|
+
ds = ds.where(:cons__owner=>im.call(schema)) if schema
|
236
|
+
pks = ds.select_map(:cols__column_name)
|
237
|
+
|
238
|
+
# Default values
|
239
|
+
defaults = metadata_dataset.from(:dba_tab_cols).
|
240
|
+
where(:table_name=>im.call(table)).
|
241
|
+
to_hash(:column_name, :data_default)
|
242
|
+
|
243
|
+
metadata = synchronize(opts[:server]) do |conn|
|
244
|
+
begin
|
245
|
+
log_yield("Connection.describe_table"){conn.describe_table(schema_and_table)}
|
246
|
+
rescue OCIError => e
|
247
|
+
raise_error(e)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
metadata.columns.each do |column|
|
251
|
+
h = {
|
252
|
+
:primary_key => pks.include?(column.name),
|
253
|
+
:default => defaults[column.name],
|
254
|
+
:oci8_type => column.data_type,
|
255
|
+
:db_type => column.type_string.split(' ')[0],
|
256
|
+
:type_string => column.type_string,
|
257
|
+
:charset_form => column.charset_form,
|
258
|
+
:char_used => column.char_used?,
|
259
|
+
:char_size => column.char_size,
|
260
|
+
:data_size => column.data_size,
|
261
|
+
:precision => column.precision,
|
262
|
+
:scale => column.scale,
|
263
|
+
:fsprecision => column.fsprecision,
|
264
|
+
:lfprecision => column.lfprecision,
|
265
|
+
:allow_null => column.nullable?
|
266
|
+
}
|
267
|
+
h[:type] = oracle_column_type(h)
|
268
|
+
table_schema << [m.call(column.name), h]
|
269
|
+
end
|
270
|
+
table_schema
|
271
|
+
end
|
116
272
|
end
|
117
273
|
|
118
274
|
class Dataset < Sequel::Dataset
|
119
275
|
include DatasetMethods
|
120
276
|
|
277
|
+
Database::DatasetClass = self
|
278
|
+
|
279
|
+
PREPARED_ARG_PLACEHOLDER = ':'.freeze
|
280
|
+
|
281
|
+
# Oracle already supports named bind arguments, so use directly.
|
282
|
+
module ArgumentMapper
|
283
|
+
include Sequel::Dataset::ArgumentMapper
|
284
|
+
|
285
|
+
protected
|
286
|
+
|
287
|
+
# Return a hash with the same values as the given hash,
|
288
|
+
# but with the keys converted to strings.
|
289
|
+
def map_to_prepared_args(bind_vars)
|
290
|
+
prepared_args.map{|v, t| [bind_vars[v], t]}
|
291
|
+
end
|
292
|
+
|
293
|
+
private
|
294
|
+
|
295
|
+
# Oracle uses a : before the name of the argument for named
|
296
|
+
# arguments.
|
297
|
+
def prepared_arg(k)
|
298
|
+
y, type = k.to_s.split("__", 2)
|
299
|
+
prepared_args << [y.to_sym, type]
|
300
|
+
i = prepared_args.length
|
301
|
+
LiteralString.new(":#{i}")
|
302
|
+
end
|
303
|
+
|
304
|
+
# Always assume a prepared argument.
|
305
|
+
def prepared_arg?(k)
|
306
|
+
true
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# Oracle prepared statement uses a new prepared statement each time
|
311
|
+
# it is called, but it does use the bind arguments.
|
312
|
+
module BindArgumentMethods
|
313
|
+
include ArgumentMapper
|
314
|
+
|
315
|
+
private
|
316
|
+
|
317
|
+
# Run execute_select on the database with the given SQL and the stored
|
318
|
+
# bind arguments.
|
319
|
+
def execute(sql, opts={}, &block)
|
320
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
324
|
+
def execute_dui(sql, opts={}, &block)
|
325
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
326
|
+
end
|
327
|
+
|
328
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
329
|
+
def execute_insert(sql, opts={}, &block)
|
330
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
module PreparedStatementMethods
|
335
|
+
include BindArgumentMethods
|
336
|
+
|
337
|
+
private
|
338
|
+
|
339
|
+
# Execute the stored prepared statement name and the stored bind
|
340
|
+
# arguments instead of the SQL given.
|
341
|
+
def execute(sql, opts={}, &block)
|
342
|
+
super(prepared_statement_name, opts, &block)
|
343
|
+
end
|
344
|
+
|
345
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
346
|
+
def execute_dui(sql, opts={}, &block)
|
347
|
+
super(prepared_statement_name, opts, &block)
|
348
|
+
end
|
349
|
+
|
350
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
351
|
+
def execute_insert(sql, opts={}, &block)
|
352
|
+
super(prepared_statement_name, opts, &block)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Execute the given type of statement with the hash of values.
|
357
|
+
def call(type, bind_vars={}, *values, &block)
|
358
|
+
ps = to_prepared_statement(type, values)
|
359
|
+
ps.extend(BindArgumentMethods)
|
360
|
+
ps.call(bind_vars, &block)
|
361
|
+
end
|
362
|
+
|
363
|
+
# Prepare the given type of query with the given name and store
|
364
|
+
# it in the database. Note that a new native prepared statement is
|
365
|
+
# created on each call to this prepared statement.
|
366
|
+
def prepare(type, name=nil, *values)
|
367
|
+
ps = to_prepared_statement(type, values)
|
368
|
+
ps.extend(PreparedStatementMethods)
|
369
|
+
if name
|
370
|
+
ps.prepared_statement_name = name
|
371
|
+
db.prepared_statements[name] = ps
|
372
|
+
end
|
373
|
+
ps
|
374
|
+
end
|
375
|
+
|
121
376
|
def fetch_rows(sql)
|
122
377
|
execute(sql) do |cursor|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
378
|
+
offset = @opts[:offset]
|
379
|
+
rn = row_number_column
|
380
|
+
cps = db.conversion_procs
|
381
|
+
cols = columns = cursor.get_col_names.map{|c| output_identifier(c)}
|
382
|
+
metadata = cursor.column_metadata
|
383
|
+
cm = cols.zip(metadata).map{|c, m| [c, cps[m.data_type]]}
|
384
|
+
columns = cols.reject{|x| x == rn} if offset
|
385
|
+
@columns = columns
|
386
|
+
while r = cursor.fetch
|
387
|
+
row = {}
|
388
|
+
r.zip(cm).each{|v, (c, cp)| row[c] = ((v && cp) ? cp.call(v) : v)}
|
389
|
+
row.delete(rn) if offset
|
390
|
+
yield row
|
132
391
|
end
|
133
392
|
end
|
134
393
|
self
|
135
394
|
end
|
136
395
|
|
396
|
+
# Create a named prepared statement that is stored in the
|
397
|
+
# database (and connection) for reuse.
|
398
|
+
def prepare(type, name=nil, *values)
|
399
|
+
ps = to_prepared_statement(type, values)
|
400
|
+
ps.extend(PreparedStatementMethods)
|
401
|
+
if name
|
402
|
+
ps.prepared_statement_name = name
|
403
|
+
db.prepared_statements[name] = ps
|
404
|
+
end
|
405
|
+
ps
|
406
|
+
end
|
407
|
+
|
408
|
+
# Oracle requires type specifiers for placeholders, at least
|
409
|
+
# if you ever want to use a nil/NULL value as the value for
|
410
|
+
# the placeholder.
|
411
|
+
def requires_placeholder_type_specifiers?
|
412
|
+
true
|
413
|
+
end
|
414
|
+
|
137
415
|
private
|
138
416
|
|
139
417
|
def literal_other(v)
|
140
418
|
case v
|
141
419
|
when OraDate
|
142
|
-
literal(
|
420
|
+
literal(db.to_application_timestamp(v))
|
143
421
|
when OCI8::CLOB
|
144
422
|
v.rewind
|
145
423
|
literal(v.read)
|
@@ -147,6 +425,10 @@ module Sequel
|
|
147
425
|
super
|
148
426
|
end
|
149
427
|
end
|
428
|
+
|
429
|
+
def prepared_arg_placeholder
|
430
|
+
PREPARED_ARG_PLACEHOLDER
|
431
|
+
end
|
150
432
|
end
|
151
433
|
end
|
152
434
|
end
|