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
@@ -110,7 +110,6 @@ module Sequel
|
|
110
110
|
[700, 701] => tt.method(:float),
|
111
111
|
[790, 1700] => ::BigDecimal.method(:new),
|
112
112
|
[1083, 1266] => ::Sequel.method(:string_to_time),
|
113
|
-
[1114, 1184] => ::Sequel.method(:database_to_application_timestamp)
|
114
113
|
}.each do |k,v|
|
115
114
|
k.each{|n| PG_TYPES[n] = v}
|
116
115
|
end
|
@@ -240,9 +239,29 @@ module Sequel
|
|
240
239
|
conn
|
241
240
|
end
|
242
241
|
|
242
|
+
# Execute the given SQL with the given args on an available connection.
|
243
|
+
def execute(sql, opts={}, &block)
|
244
|
+
check_database_errors do
|
245
|
+
return execute_prepared_statement(sql, opts, &block) if Symbol === sql
|
246
|
+
synchronize(opts[:server]){|conn| conn.execute(sql, opts[:arguments], &block)}
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Insert the values into the table and return the primary key (if
|
251
|
+
# automatically generated).
|
252
|
+
def execute_insert(sql, opts={})
|
253
|
+
return execute(sql, opts) if Symbol === sql
|
254
|
+
check_database_errors do
|
255
|
+
synchronize(opts[:server]) do |conn|
|
256
|
+
conn.execute(sql, opts[:arguments])
|
257
|
+
insert_result(conn, opts[:table], opts[:values])
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
243
262
|
if SEQUEL_POSTGRES_USES_PG
|
244
263
|
# +copy_table+ uses PostgreSQL's +COPY+ SQL statement to return formatted
|
245
|
-
# results directly to the caller. This method is only
|
264
|
+
# results directly to the caller. This method is only supported if pg is the
|
246
265
|
# underlying ruby driver. This method should only be called if you want
|
247
266
|
# results returned to the client. If you are using +COPY FROM+ or +COPY TO+
|
248
267
|
# with a filename, you should just use +run+ instead of this method. This
|
@@ -302,33 +321,57 @@ module Sequel
|
|
302
321
|
end
|
303
322
|
end
|
304
323
|
end
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
324
|
+
|
325
|
+
# Listens on the given channel (or multiple channels if channel is an array), waiting for notifications.
|
326
|
+
# After a notification is received, or the timeout has passed, stops listening to the channel. Options:
|
327
|
+
#
|
328
|
+
# :after_listen :: An object that responds to +call+ that is called with the underlying connection after the LISTEN
|
329
|
+
# statement is sent, but before the connection starts waiting for notifications.
|
330
|
+
# :loop :: Whether to continually wait for notifications, instead of just waiting for a single
|
331
|
+
# notification. If this option is given, a block must be provided. If this object responds to call, it is
|
332
|
+
# called with the underlying connection after each notification is received (after the block is called).
|
333
|
+
# If a :timeout option is used, and a callable object is given, the object will also be called if the
|
334
|
+
# timeout expires. If :loop is used and you want to stop listening, you can either break from inside the
|
335
|
+
# block given to #listen, or you can throw :stop from inside the :loop object's call method or the block.
|
336
|
+
# :server :: The server on which to listen, if the sharding support is being used.
|
337
|
+
# :timeout :: How long to wait for a notification, in seconds (can provide a float value for
|
338
|
+
# fractional seconds). If not given or nil, waits indefinitely.
|
339
|
+
#
|
340
|
+
# This method is only supported if pg is used as the underlying ruby driver. It returns the
|
341
|
+
# channel the notification was sent to (as a string), unless :loop was used, in which case it returns nil.
|
342
|
+
# If a block is given, it is yielded 3 arguments:
|
343
|
+
# * the channel the notification was sent to (as a string)
|
344
|
+
# * the backend pid of the notifier (as an integer),
|
345
|
+
# * and the payload of the notification (as a string or nil).
|
346
|
+
def listen(channels, opts={}, &block)
|
347
|
+
check_database_errors do
|
348
|
+
synchronize(opts[:server]) do |conn|
|
349
|
+
begin
|
350
|
+
channels = Array(channels)
|
351
|
+
channels.each{|channel| conn.execute("LISTEN #{channel}")}
|
352
|
+
opts[:after_listen].call(conn) if opts[:after_listen]
|
353
|
+
timeout = opts[:timeout] ? [opts[:timeout]] : []
|
354
|
+
if l = opts[:loop]
|
355
|
+
raise Error, 'calling #listen with :loop requires a block' unless block
|
356
|
+
loop_call = l.respond_to?(:call)
|
357
|
+
catch(:stop) do
|
358
|
+
loop do
|
359
|
+
conn.wait_for_notify(*timeout, &block)
|
360
|
+
l.call(conn) if loop_call
|
361
|
+
end
|
362
|
+
end
|
363
|
+
nil
|
364
|
+
else
|
365
|
+
conn.wait_for_notify(*timeout, &block)
|
366
|
+
end
|
367
|
+
ensure
|
368
|
+
conn.execute("UNLISTEN *")
|
369
|
+
end
|
370
|
+
end
|
328
371
|
end
|
329
372
|
end
|
330
373
|
end
|
331
|
-
|
374
|
+
|
332
375
|
private
|
333
376
|
|
334
377
|
# Convert exceptions raised from the block into DatabaseErrors.
|
@@ -385,9 +428,11 @@ module Sequel
|
|
385
428
|
# Return the conversion procs hash to use for this database
|
386
429
|
def get_conversion_procs(conn)
|
387
430
|
procs = PG_TYPES.dup
|
431
|
+
procs[1114] = method(:to_application_timestamp)
|
432
|
+
procs[1184] = method(:to_application_timestamp)
|
388
433
|
conn.execute("SELECT oid, typname FROM pg_type where typtype = 'b'") do |res|
|
389
434
|
res.ntuples.times do |recnum|
|
390
|
-
if pr = PG_NAMED_TYPES[res.getvalue(recnum, 1).to_sym]
|
435
|
+
if pr = PG_NAMED_TYPES[res.getvalue(recnum, 1).untaint.to_sym]
|
391
436
|
procs[res.getvalue(recnum, 0).to_i] ||= pr
|
392
437
|
end
|
393
438
|
end
|
@@ -400,6 +445,8 @@ module Sequel
|
|
400
445
|
# postgres-pr driver.
|
401
446
|
class Dataset < Sequel::Dataset
|
402
447
|
include Sequel::Postgres::DatasetMethods
|
448
|
+
|
449
|
+
Database::DatasetClass = self
|
403
450
|
|
404
451
|
# Yield all rows returned by executing the given SQL and converting
|
405
452
|
# the types.
|
@@ -495,6 +542,12 @@ module Sequel
|
|
495
542
|
# pg driver.
|
496
543
|
module PreparedStatementMethods
|
497
544
|
include BindArgumentMethods
|
545
|
+
|
546
|
+
# Raise a more obvious error if you attempt to call a unnamed prepared statement.
|
547
|
+
def call(*)
|
548
|
+
raise Error, "Cannot call prepared statement without a name" if prepared_statement_name.nil?
|
549
|
+
super
|
550
|
+
end
|
498
551
|
|
499
552
|
private
|
500
553
|
|
@@ -6,12 +6,6 @@ module Sequel
|
|
6
6
|
:access
|
7
7
|
end
|
8
8
|
|
9
|
-
def dataset(opts = nil)
|
10
|
-
ds = super
|
11
|
-
ds.extend(DatasetMethods)
|
12
|
-
ds
|
13
|
-
end
|
14
|
-
|
15
9
|
# Doesn't work, due to security restrictions on MSysObjects
|
16
10
|
def tables
|
17
11
|
from(:MSysObjects).filter(:Type=>1, :Flags=>0).select_map(:Name).map{|x| x.to_sym}
|
@@ -29,8 +29,8 @@ module Sequel
|
|
29
29
|
|
30
30
|
# Use SYSIBM.SYSCOLUMNS to get the information on the tables.
|
31
31
|
def schema_parse_table(table, opts = {})
|
32
|
-
m = output_identifier_meth
|
33
|
-
im = input_identifier_meth
|
32
|
+
m = output_identifier_meth(opts[:dataset])
|
33
|
+
im = input_identifier_meth(opts[:dataset])
|
34
34
|
metadata_dataset.with_sql("SELECT * FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = #{literal(im.call(table))} ORDER BY COLNO").
|
35
35
|
collect do |column|
|
36
36
|
column[:db_type] = column.delete(:typename)
|
@@ -192,13 +192,16 @@ module Sequel
|
|
192
192
|
super(:LIKE, [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1)) ])
|
193
193
|
when :"NOT ILIKE"
|
194
194
|
super(:"NOT LIKE", [SQL::Function.new(:upper, args.at(0)), SQL::Function.new(:upper, args.at(1)) ])
|
195
|
-
when :&, :|,
|
195
|
+
when :&, :|, :^
|
196
196
|
# works with db2 v9.5 and after
|
197
|
-
|
197
|
+
op = BITWISE_METHOD_MAP[op]
|
198
|
+
complex_expression_arg_pairs(args){|a, b| literal(SQL::Function.new(op, a, b))}
|
198
199
|
when :<<
|
199
|
-
"(#{literal(
|
200
|
+
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
|
200
201
|
when :>>
|
201
|
-
"(#{literal(
|
202
|
+
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
|
203
|
+
when :'B~'
|
204
|
+
literal(SQL::Function.new(:BITNOT, *args))
|
202
205
|
when :extract
|
203
206
|
"#{args.at(0)}(#{literal(args.at(1))})"
|
204
207
|
else
|
@@ -206,12 +209,6 @@ module Sequel
|
|
206
209
|
end
|
207
210
|
end
|
208
211
|
|
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
212
|
# DB2 does not support IS TRUE.
|
216
213
|
def supports_is_true?
|
217
214
|
false
|
@@ -244,9 +241,10 @@ module Sequel
|
|
244
241
|
|
245
242
|
private
|
246
243
|
|
247
|
-
# DB2
|
248
|
-
|
249
|
-
|
244
|
+
# DB2 needs the standard workaround to insert all default values into
|
245
|
+
# a table with more than one column.
|
246
|
+
def insert_supports_empty_values?
|
247
|
+
false
|
250
248
|
end
|
251
249
|
|
252
250
|
# Use 0 for false on DB2
|
@@ -20,11 +20,6 @@ module Sequel
|
|
20
20
|
self << drop_sequence_sql(name)
|
21
21
|
end
|
22
22
|
|
23
|
-
def drop_table(*names)
|
24
|
-
clear_primary_key(*names)
|
25
|
-
super
|
26
|
-
end
|
27
|
-
|
28
23
|
# Return primary key for the given table.
|
29
24
|
def primary_key(table)
|
30
25
|
t = dataset.send(:input_identifier, table)
|
@@ -131,6 +126,11 @@ module Sequel
|
|
131
126
|
"DROP SEQUENCE #{quote_identifier(name)}"
|
132
127
|
end
|
133
128
|
|
129
|
+
def remove_cached_schema(table)
|
130
|
+
clear_primary_key(table)
|
131
|
+
super
|
132
|
+
end
|
133
|
+
|
134
134
|
def restart_sequence_sql(name, opts={})
|
135
135
|
seq_name = quote_identifier(name)
|
136
136
|
"ALTER SEQUENCE #{seq_name} RESTART WITH #{opts[:restart_position]}"
|
@@ -158,7 +158,7 @@ module Sequel
|
|
158
158
|
def insert(*values)
|
159
159
|
if @opts[:sql] || @opts[:returning]
|
160
160
|
super
|
161
|
-
|
161
|
+
else
|
162
162
|
returning(insert_pk).insert(*values){|r| return r.values.first}
|
163
163
|
end
|
164
164
|
end
|
@@ -107,7 +107,7 @@ module Sequel
|
|
107
107
|
# Commit the active transaction on the connection, does not commit/release
|
108
108
|
# savepoints.
|
109
109
|
def commit_transaction(conn, opts={})
|
110
|
-
log_connection_execute(conn, commit_transaction_sql) unless
|
110
|
+
log_connection_execute(conn, commit_transaction_sql) unless @transactions[conn][:savepoint_level] > 1
|
111
111
|
end
|
112
112
|
|
113
113
|
# SQL to COMMIT a transaction.
|
@@ -168,20 +168,31 @@ module Sequel
|
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
171
|
-
# MSSQL uses the INFORMATION_SCHEMA to hold column information
|
172
|
-
#
|
171
|
+
# MSSQL uses the INFORMATION_SCHEMA to hold column information, and
|
172
|
+
# parses primary key information from the sysindexes, sysindexkeys,
|
173
|
+
# and syscolumns system tables.
|
173
174
|
def schema_parse_table(table_name, opts)
|
174
|
-
m = output_identifier_meth
|
175
|
-
m2 = input_identifier_meth
|
175
|
+
m = output_identifier_meth(opts[:dataset])
|
176
|
+
m2 = input_identifier_meth(opts[:dataset])
|
177
|
+
tn = m2.call(table_name.to_s)
|
178
|
+
table_id = get{object_id(tn)}
|
179
|
+
pk_index_id = metadata_dataset.from(:sysindexes).
|
180
|
+
where(:id=>table_id, :indid=>1..254){{(status & 2048)=>2048}}.
|
181
|
+
get(:indid)
|
182
|
+
pk_cols = metadata_dataset.from(:sysindexkeys___sik).
|
183
|
+
join(:syscolumns___sc, :id=>:id, :colid=>:colid).
|
184
|
+
where(:sik__id=>table_id, :sik__indid=>pk_index_id).
|
185
|
+
select_order_map(:sc__name)
|
176
186
|
ds = metadata_dataset.from(:information_schema__tables___t).
|
177
187
|
join(:information_schema__columns___c, :table_catalog=>:table_catalog,
|
178
188
|
:table_schema => :table_schema, :table_name => :table_name).
|
179
189
|
select(:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, :column_default___default, :is_nullable___allow_null, :numeric_precision___column_size, :numeric_scale___scale).
|
180
|
-
filter(:c__table_name=>
|
190
|
+
filter(:c__table_name=>tn)
|
181
191
|
if schema = opts[:schema] || default_schema
|
182
192
|
ds.filter!(:c__table_schema=>schema)
|
183
193
|
end
|
184
194
|
ds.map do |row|
|
195
|
+
row[:primary_key] = pk_cols.include?(row[:column])
|
185
196
|
row[:allow_null] = row[:allow_null] == 'YES' ? true : false
|
186
197
|
row[:default] = nil if blank_object?(row[:default])
|
187
198
|
row[:type] = if row[:db_type] =~ DECIMAL_TYPE_RE && row[:scale] == 0
|
@@ -262,14 +273,14 @@ module Sequel
|
|
262
273
|
case op
|
263
274
|
when :'||'
|
264
275
|
super(:+, args)
|
265
|
-
when :
|
266
|
-
super(
|
267
|
-
when :"NOT ILIKE"
|
268
|
-
super(:"NOT LIKE", args)
|
276
|
+
when :LIKE, :"NOT LIKE"
|
277
|
+
super(op, args.map{|a| LiteralString.new("(#{literal(a)} COLLATE Latin1_General_CS_AS)")})
|
278
|
+
when :ILIKE, :"NOT ILIKE"
|
279
|
+
super((op == :ILIKE ? :LIKE : :"NOT LIKE"), args)
|
269
280
|
when :<<
|
270
|
-
"(#{literal(
|
281
|
+
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
|
271
282
|
when :>>
|
272
|
-
"(#{literal(
|
283
|
+
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
|
273
284
|
when :extract
|
274
285
|
part = args.at(0)
|
275
286
|
raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
|
@@ -297,12 +308,6 @@ module Sequel
|
|
297
308
|
mutation_method(:disable_insert_output)
|
298
309
|
end
|
299
310
|
|
300
|
-
# When returning all rows, if an offset is used, delete the row_number column
|
301
|
-
# before yielding the row.
|
302
|
-
def fetch_rows(sql, &block)
|
303
|
-
@opts[:offset] ? super(sql){|r| r.delete(row_number_column); yield r} : super(sql, &block)
|
304
|
-
end
|
305
|
-
|
306
311
|
# MSSQL uses the CONTAINS keyword for full text search
|
307
312
|
def full_text_search(cols, terms, opts = {})
|
308
313
|
filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
|
@@ -185,9 +185,9 @@ module Sequel
|
|
185
185
|
# Use XA START to start a new prepared transaction if the :prepare
|
186
186
|
# option is given.
|
187
187
|
def begin_transaction(conn, opts={})
|
188
|
-
if s = opts[:prepare]
|
188
|
+
if (s = opts[:prepare]) && (th = @transactions[conn])[:savepoint_level] == 0
|
189
189
|
log_connection_execute(conn, "XA START #{literal(s)}")
|
190
|
-
|
190
|
+
th[:savepoint_level] += 1
|
191
191
|
else
|
192
192
|
super
|
193
193
|
end
|
@@ -208,7 +208,7 @@ module Sequel
|
|
208
208
|
# Prepare the XA transaction for a two-phase commit if the
|
209
209
|
# :prepare option is given.
|
210
210
|
def commit_transaction(conn, opts={})
|
211
|
-
if s = opts[:prepare]
|
211
|
+
if (s = opts[:prepare]) && @transactions[conn][:savepoint_level] <= 1
|
212
212
|
log_connection_execute(conn, "XA END #{literal(s)}")
|
213
213
|
log_connection_execute(conn, "XA PREPARE #{literal(s)}")
|
214
214
|
else
|
@@ -262,7 +262,7 @@ module Sequel
|
|
262
262
|
|
263
263
|
# Rollback the currently open XA transaction
|
264
264
|
def rollback_transaction(conn, opts={})
|
265
|
-
if s = opts[:prepare]
|
265
|
+
if (s = opts[:prepare]) && @transactions[conn][:savepoint_level] <= 1
|
266
266
|
log_connection_execute(conn, "XA END #{literal(s)}")
|
267
267
|
log_connection_execute(conn, "XA PREPARE #{literal(s)}")
|
268
268
|
log_connection_execute(conn, "XA ROLLBACK #{literal(s)}")
|
@@ -278,8 +278,8 @@ module Sequel
|
|
278
278
|
|
279
279
|
# Use the MySQL specific DESCRIBE syntax to get a table description.
|
280
280
|
def schema_parse_table(table_name, opts)
|
281
|
-
m = output_identifier_meth
|
282
|
-
im = input_identifier_meth
|
281
|
+
m = output_identifier_meth(opts[:dataset])
|
282
|
+
im = input_identifier_meth(opts[:dataset])
|
283
283
|
metadata_dataset.with_sql("DESCRIBE ?", SQL::Identifier.new(im.call(table_name))).map do |row|
|
284
284
|
row[:auto_increment] = true if row.delete(:Extra).to_s =~ /auto_increment/io
|
285
285
|
row[:allow_null] = row.delete(:Null) == 'YES'
|
@@ -76,6 +76,12 @@ module Sequel
|
|
76
76
|
module PreparedStatementMethods
|
77
77
|
include Sequel::Dataset::UnnumberedArgumentMapper
|
78
78
|
|
79
|
+
# Raise a more obvious error if you attempt to call a unnamed prepared statement.
|
80
|
+
def call(*)
|
81
|
+
raise Error, "Cannot call prepared statement without a name" if prepared_statement_name.nil?
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
79
85
|
private
|
80
86
|
|
81
87
|
# Execute the prepared statement with the bind arguments instead of
|
@@ -1,9 +1,13 @@
|
|
1
|
+
Sequel.require 'adapters/utils/emulate_offset_with_row_number'
|
2
|
+
|
1
3
|
module Sequel
|
2
4
|
module Oracle
|
3
5
|
module DatabaseMethods
|
4
6
|
TEMPORARY = 'GLOBAL TEMPORARY '.freeze
|
5
7
|
AUTOINCREMENT = ''.freeze
|
6
8
|
|
9
|
+
attr_accessor :autosequence
|
10
|
+
|
7
11
|
def create_sequence(name, opts={})
|
8
12
|
self << create_sequence_sql(name, opts)
|
9
13
|
end
|
@@ -12,6 +16,10 @@ module Sequel
|
|
12
16
|
self << create_trigger_sql(*args)
|
13
17
|
end
|
14
18
|
|
19
|
+
def current_user
|
20
|
+
@current_user ||= get{sys_context('USERENV', 'CURRENT_USER')}
|
21
|
+
end
|
22
|
+
|
15
23
|
def drop_sequence(name)
|
16
24
|
self << drop_sequence_sql(name)
|
17
25
|
end
|
@@ -37,13 +45,37 @@ module Sequel
|
|
37
45
|
|
38
46
|
private
|
39
47
|
|
40
|
-
|
41
|
-
|
48
|
+
# Handle Oracle specific ALTER TABLE SQL
|
49
|
+
def alter_table_sql(table, op)
|
50
|
+
case op[:op]
|
51
|
+
when :add_column
|
52
|
+
if op[:primary_key]
|
53
|
+
sqls = []
|
54
|
+
sqls << alter_table_sql(table, op.merge(:primary_key=>nil))
|
55
|
+
if op[:auto_increment]
|
56
|
+
seq_name = default_sequence_name(table, op[:name])
|
57
|
+
sqls << drop_sequence_sql(seq_name)
|
58
|
+
sqls << create_sequence_sql(seq_name, op)
|
59
|
+
sqls << "UPDATE #{quote_schema_table(table)} SET #{quote_identifier(op[:name])} = #{seq_name}.nextval"
|
60
|
+
end
|
61
|
+
sqls << "ALTER TABLE #{quote_schema_table(table)} ADD PRIMARY KEY (#{quote_identifier(op[:name])})"
|
62
|
+
sqls
|
63
|
+
else
|
64
|
+
"ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
|
65
|
+
end
|
66
|
+
when :set_column_null
|
67
|
+
"ALTER TABLE #{quote_schema_table(table)} MODIFY #{quote_identifier(op[:name])} #{op[:null] ? 'NULL' : 'NOT NULL'}"
|
68
|
+
when :set_column_type
|
69
|
+
"ALTER TABLE #{quote_schema_table(table)} MODIFY #{quote_identifier(op[:name])} #{type_literal(op)}"
|
70
|
+
when :set_column_default
|
71
|
+
"ALTER TABLE #{quote_schema_table(table)} MODIFY #{quote_identifier(op[:name])} DEFAULT #{literal(op[:default])}"
|
72
|
+
else
|
73
|
+
super(table, op)
|
74
|
+
end
|
42
75
|
end
|
43
76
|
|
44
|
-
|
45
|
-
|
46
|
-
TEMPORARY
|
77
|
+
def auto_increment_sql
|
78
|
+
AUTOINCREMENT
|
47
79
|
end
|
48
80
|
|
49
81
|
def create_sequence_sql(name, opts={})
|
@@ -61,7 +93,7 @@ module Sequel
|
|
61
93
|
drop_seq_statement = nil
|
62
94
|
generator.columns.each do |c|
|
63
95
|
if c[:auto_increment]
|
64
|
-
c[:sequence_name] ||=
|
96
|
+
c[:sequence_name] ||= default_sequence_name(name, c[:name])
|
65
97
|
unless c[:create_sequence] == false
|
66
98
|
drop_seq_statement = drop_sequence_sql(c[:sequence_name])
|
67
99
|
statements << create_sequence_sql(c[:sequence_name], c)
|
@@ -93,13 +125,94 @@ module Sequel
|
|
93
125
|
sql
|
94
126
|
end
|
95
127
|
|
128
|
+
def default_sequence_name(table, column)
|
129
|
+
"seq_#{table}_#{column}"
|
130
|
+
end
|
131
|
+
|
96
132
|
def drop_sequence_sql(name)
|
97
133
|
"DROP SEQUENCE #{quote_identifier(name)}"
|
98
134
|
end
|
135
|
+
|
136
|
+
def remove_cached_schema(table)
|
137
|
+
@primary_key_sequences.delete(table)
|
138
|
+
super
|
139
|
+
end
|
140
|
+
|
141
|
+
def sequence_for_table(table)
|
142
|
+
return nil unless autosequence
|
143
|
+
@primary_key_sequences.fetch(table) do |key|
|
144
|
+
pk = schema(table).select{|k, v| v[:primary_key]}
|
145
|
+
@primary_key_sequences[table] = if pk.length == 1
|
146
|
+
seq = "seq_#{table}_#{pk.first.first}"
|
147
|
+
seq.to_sym unless from(:user_sequences).filter(:sequence_name=>input_identifier_meth.call(seq)).empty?
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Oracle's integer/:number type handles larger values than
|
153
|
+
# most other databases's bigint types, so it should be
|
154
|
+
# safe to use for Bignum.
|
155
|
+
def type_literal_generic_bignum(column)
|
156
|
+
:integer
|
157
|
+
end
|
158
|
+
|
159
|
+
# Oracle doesn't have a time type, so use timestamp for all
|
160
|
+
# time columns.
|
161
|
+
def type_literal_generic_time(column)
|
162
|
+
:timestamp
|
163
|
+
end
|
164
|
+
|
165
|
+
# Oracle doesn't have a boolean type or even a reasonable
|
166
|
+
# facsimile. Using a char(1) seems to be the recommended way.
|
167
|
+
def type_literal_generic_trueclass(column)
|
168
|
+
:'char(1)'
|
169
|
+
end
|
170
|
+
|
171
|
+
# SQL fragment for showing a table is temporary
|
172
|
+
def temporary_table_sql
|
173
|
+
TEMPORARY
|
174
|
+
end
|
99
175
|
end
|
100
176
|
|
101
177
|
module DatasetMethods
|
102
|
-
|
178
|
+
include EmulateOffsetWithRowNumber
|
179
|
+
|
180
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct columns from join where group having compounds order lock')
|
181
|
+
ROW_NUMBER_EXPRESSION = 'ROWNUM'.lit.freeze
|
182
|
+
|
183
|
+
# Oracle needs to emulate bitwise operators and ILIKE/NOT ILIKE operators.
|
184
|
+
def complex_expression_sql(op, args)
|
185
|
+
case op
|
186
|
+
when :&
|
187
|
+
complex_expression_arg_pairs(args){|a, b| "CAST(BITAND(#{literal(a)}, #{literal(b)}) AS INTEGER)"}
|
188
|
+
when :|
|
189
|
+
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} - #{complex_expression_sql(:&, [a, b])} + #{literal(b)})"}
|
190
|
+
when :^
|
191
|
+
complex_expression_arg_pairs(args){|*x| "(#{complex_expression_sql(:|, x)} - #{complex_expression_sql(:&, x)})"}
|
192
|
+
when :'B~'
|
193
|
+
"((0 - #{literal(args.at(0))}) - 1)"
|
194
|
+
when :<<
|
195
|
+
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * power(2, #{literal b}))"}
|
196
|
+
when :>>
|
197
|
+
complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / power(2, #{literal b}))"}
|
198
|
+
when :ILIKE, :'NOT ILIKE'
|
199
|
+
a, b = args
|
200
|
+
"(UPPER(#{literal(a)}) #{op == :ILIKE ? :LIKE : :'NOT LIKE'} UPPER(#{literal(b)}))"
|
201
|
+
else
|
202
|
+
super
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Oracle doesn't support CURRENT_TIME, as it doesn't have
|
207
|
+
# a type for storing just time values without a date, so
|
208
|
+
# use CURRENT_TIMESTAMP in its place.
|
209
|
+
def constant_sql(c)
|
210
|
+
if c == :CURRENT_TIME
|
211
|
+
super(:CURRENT_TIMESTAMP)
|
212
|
+
else
|
213
|
+
super
|
214
|
+
end
|
215
|
+
end
|
103
216
|
|
104
217
|
# Oracle uses MINUS instead of EXCEPT, and doesn't support EXCEPT ALL
|
105
218
|
def except(dataset, opts={})
|
@@ -109,18 +222,7 @@ module Sequel
|
|
109
222
|
end
|
110
223
|
|
111
224
|
def empty?
|
112
|
-
db[:dual].where(exists).get(1) == nil
|
113
|
-
end
|
114
|
-
|
115
|
-
# If this dataset is associated with a sequence, return the most recently
|
116
|
-
# inserted sequence value.
|
117
|
-
def insert(*args)
|
118
|
-
r = super
|
119
|
-
if s = opts[:sequence]
|
120
|
-
with_sql("SELECT #{literal(s)}.currval FROM dual").single_value.to_i
|
121
|
-
else
|
122
|
-
r
|
123
|
-
end
|
225
|
+
db[:dual].where(unordered.exists).get(1) == nil
|
124
226
|
end
|
125
227
|
|
126
228
|
# Oracle requires SQL standard datetimes
|
@@ -135,16 +237,49 @@ module Sequel
|
|
135
237
|
clone(:sequence=>s)
|
136
238
|
end
|
137
239
|
|
240
|
+
# Handle LIMIT by using a unlimited subselect filtered with ROWNUM.
|
241
|
+
def select_sql
|
242
|
+
if (limit = @opts[:limit]) && !@opts[:sql]
|
243
|
+
ds = clone(:limit=>nil)
|
244
|
+
# Lock doesn't work in subselects, so don't use a subselect when locking.
|
245
|
+
# Don't use a subselect if custom SQL is used, as it breaks somethings.
|
246
|
+
ds = ds.from_self unless @opts[:lock]
|
247
|
+
subselect_sql(ds.where(SQL::ComplexExpression.new(:<=, ROW_NUMBER_EXPRESSION, limit)))
|
248
|
+
else
|
249
|
+
super
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Oracle requires recursive CTEs to have column aliases.
|
254
|
+
def recursive_cte_requires_column_aliases?
|
255
|
+
true
|
256
|
+
end
|
257
|
+
|
138
258
|
# Oracle does not support INTERSECT ALL or EXCEPT ALL
|
139
259
|
def supports_intersect_except_all?
|
140
260
|
false
|
141
261
|
end
|
262
|
+
|
263
|
+
# Oracle does not support IS TRUE.
|
264
|
+
def supports_is_true?
|
265
|
+
false
|
266
|
+
end
|
267
|
+
|
268
|
+
# Oracle does not support SELECT *, column
|
269
|
+
def supports_select_all_and_column?
|
270
|
+
false
|
271
|
+
end
|
142
272
|
|
143
273
|
# Oracle supports timezones in literal timestamps.
|
144
274
|
def supports_timestamp_timezones?
|
145
275
|
true
|
146
276
|
end
|
147
277
|
|
278
|
+
# Oracle does not support WHERE 'Y' for WHERE TRUE.
|
279
|
+
def supports_where_true?
|
280
|
+
false
|
281
|
+
end
|
282
|
+
|
148
283
|
# Oracle supports window functions
|
149
284
|
def supports_window_functions?
|
150
285
|
true
|
@@ -163,16 +298,47 @@ module Sequel
|
|
163
298
|
"TIMESTAMP '%Y-%m-%d %H:%M:%S%N %z'".freeze
|
164
299
|
end
|
165
300
|
|
301
|
+
# If this dataset is associated with a sequence, return the most recently
|
302
|
+
# inserted sequence value.
|
303
|
+
def execute_insert(sql, opts={})
|
304
|
+
f = @opts[:from]
|
305
|
+
super(sql, {:table=>(f.first if f), :sequence=>@opts[:sequence]}.merge(opts))
|
306
|
+
end
|
307
|
+
|
166
308
|
# Use a colon for the timestamp offset, since Oracle appears to require it.
|
167
309
|
def format_timestamp_offset(hour, minute)
|
168
310
|
sprintf("%+03i:%02i", hour, minute)
|
169
311
|
end
|
170
312
|
|
313
|
+
# Oracle doesn't support empty values when inserting.
|
314
|
+
def insert_supports_empty_values?
|
315
|
+
false
|
316
|
+
end
|
317
|
+
|
318
|
+
# Use string in hex format for blob data.
|
319
|
+
def literal_blob(v)
|
320
|
+
blob = "'"
|
321
|
+
v.each_byte{|x| blob << sprintf('%02x', x)}
|
322
|
+
blob << "'"
|
323
|
+
blob
|
324
|
+
end
|
325
|
+
|
326
|
+
# Oracle uses 'N' for false values.
|
327
|
+
def literal_false
|
328
|
+
"'N'"
|
329
|
+
end
|
330
|
+
|
171
331
|
# Oracle uses the SQL standard of only doubling ' inside strings.
|
172
332
|
def literal_string(v)
|
173
333
|
"'#{v.gsub("'", "''")}'"
|
174
334
|
end
|
175
335
|
|
336
|
+
# Oracle uses 'Y' for true values.
|
337
|
+
def literal_true
|
338
|
+
"'Y'"
|
339
|
+
end
|
340
|
+
|
341
|
+
# Use the Oracle-specific SQL clauses (no limit, since it is emulated).
|
176
342
|
def select_clause_methods
|
177
343
|
SELECT_CLAUSE_METHODS
|
178
344
|
end
|
@@ -184,17 +350,6 @@ module Sequel
|
|
184
350
|
def select_from_sql(sql)
|
185
351
|
sql << " FROM #{source_list(@opts[:from] || ['DUAL'])}"
|
186
352
|
end
|
187
|
-
|
188
|
-
# Oracle requires a subselect to do limit and offset
|
189
|
-
def select_limit_sql(sql)
|
190
|
-
if limit = @opts[:limit]
|
191
|
-
if (offset = @opts[:offset]) && (offset > 0)
|
192
|
-
sql.replace("SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM(#{sql}) raw_sql_ WHERE ROWNUM <= #{limit + offset}) WHERE raw_rnum_ > #{offset}")
|
193
|
-
else
|
194
|
-
sql.replace("SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}")
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
353
|
end
|
199
354
|
end
|
200
355
|
end
|