sequel 3.28.0 → 3.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +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
|