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.
Files changed (148) hide show
  1. data/CHANGELOG +119 -3
  2. data/Rakefile +5 -3
  3. data/bin/sequel +1 -5
  4. data/doc/model_hooks.rdoc +9 -1
  5. data/doc/opening_databases.rdoc +49 -40
  6. data/doc/prepared_statements.rdoc +27 -6
  7. data/doc/release_notes/3.28.0.txt +2 -2
  8. data/doc/release_notes/3.29.0.txt +459 -0
  9. data/doc/sharding.rdoc +7 -1
  10. data/doc/testing.rdoc +18 -9
  11. data/doc/transactions.rdoc +41 -1
  12. data/lib/sequel/adapters/ado.rb +28 -17
  13. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  14. data/lib/sequel/adapters/amalgalite.rb +11 -7
  15. data/lib/sequel/adapters/db2.rb +122 -70
  16. data/lib/sequel/adapters/dbi.rb +15 -15
  17. data/lib/sequel/adapters/do.rb +5 -36
  18. data/lib/sequel/adapters/do/mysql.rb +0 -5
  19. data/lib/sequel/adapters/do/postgres.rb +0 -5
  20. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  21. data/lib/sequel/adapters/firebird.rb +3 -6
  22. data/lib/sequel/adapters/ibmdb.rb +24 -16
  23. data/lib/sequel/adapters/informix.rb +2 -4
  24. data/lib/sequel/adapters/jdbc.rb +47 -11
  25. data/lib/sequel/adapters/jdbc/as400.rb +5 -24
  26. data/lib/sequel/adapters/jdbc/db2.rb +0 -5
  27. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  28. data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
  29. data/lib/sequel/adapters/jdbc/h2.rb +10 -12
  30. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  31. data/lib/sequel/adapters/jdbc/informix.rb +0 -5
  32. data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
  33. data/lib/sequel/adapters/jdbc/mysql.rb +0 -10
  34. data/lib/sequel/adapters/jdbc/oracle.rb +70 -3
  35. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -11
  36. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
  38. data/lib/sequel/adapters/jdbc/transactions.rb +56 -7
  39. data/lib/sequel/adapters/mock.rb +315 -0
  40. data/lib/sequel/adapters/mysql.rb +64 -51
  41. data/lib/sequel/adapters/mysql2.rb +15 -9
  42. data/lib/sequel/adapters/odbc.rb +13 -6
  43. data/lib/sequel/adapters/odbc/db2.rb +0 -4
  44. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  45. data/lib/sequel/adapters/openbase.rb +2 -4
  46. data/lib/sequel/adapters/oracle.rb +333 -51
  47. data/lib/sequel/adapters/postgres.rb +80 -27
  48. data/lib/sequel/adapters/shared/access.rb +0 -6
  49. data/lib/sequel/adapters/shared/db2.rb +13 -15
  50. data/lib/sequel/adapters/shared/firebird.rb +6 -6
  51. data/lib/sequel/adapters/shared/mssql.rb +23 -18
  52. data/lib/sequel/adapters/shared/mysql.rb +6 -6
  53. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  54. data/lib/sequel/adapters/shared/oracle.rb +185 -30
  55. data/lib/sequel/adapters/shared/postgres.rb +35 -18
  56. data/lib/sequel/adapters/shared/progress.rb +0 -6
  57. data/lib/sequel/adapters/shared/sqlite.rb +116 -37
  58. data/lib/sequel/adapters/sqlite.rb +16 -8
  59. data/lib/sequel/adapters/swift.rb +5 -5
  60. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  61. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  62. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  63. data/lib/sequel/adapters/tinytds.rb +13 -10
  64. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -0
  65. data/lib/sequel/core.rb +40 -0
  66. data/lib/sequel/database/connecting.rb +1 -2
  67. data/lib/sequel/database/dataset.rb +3 -3
  68. data/lib/sequel/database/dataset_defaults.rb +58 -0
  69. data/lib/sequel/database/misc.rb +62 -2
  70. data/lib/sequel/database/query.rb +113 -49
  71. data/lib/sequel/database/schema_methods.rb +7 -2
  72. data/lib/sequel/dataset/actions.rb +37 -19
  73. data/lib/sequel/dataset/features.rb +24 -0
  74. data/lib/sequel/dataset/graph.rb +7 -6
  75. data/lib/sequel/dataset/misc.rb +11 -3
  76. data/lib/sequel/dataset/mutation.rb +2 -3
  77. data/lib/sequel/dataset/prepared_statements.rb +6 -4
  78. data/lib/sequel/dataset/query.rb +46 -15
  79. data/lib/sequel/dataset/sql.rb +28 -4
  80. data/lib/sequel/extensions/named_timezones.rb +5 -0
  81. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  82. data/lib/sequel/model.rb +2 -1
  83. data/lib/sequel/model/associations.rb +115 -33
  84. data/lib/sequel/model/base.rb +91 -31
  85. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  86. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  87. data/lib/sequel/plugins/force_encoding.rb +6 -6
  88. data/lib/sequel/plugins/identity_map.rb +1 -1
  89. data/lib/sequel/plugins/many_through_many.rb +6 -10
  90. data/lib/sequel/plugins/prepared_statements.rb +12 -1
  91. data/lib/sequel/plugins/prepared_statements_associations.rb +1 -1
  92. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  93. data/lib/sequel/plugins/serialization.rb +6 -1
  94. data/lib/sequel/plugins/sharding.rb +0 -5
  95. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  96. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  97. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  98. data/lib/sequel/timezones.rb +42 -42
  99. data/lib/sequel/version.rb +1 -1
  100. data/spec/adapters/mssql_spec.rb +29 -29
  101. data/spec/adapters/mysql_spec.rb +86 -104
  102. data/spec/adapters/oracle_spec.rb +48 -76
  103. data/spec/adapters/postgres_spec.rb +98 -33
  104. data/spec/adapters/spec_helper.rb +0 -5
  105. data/spec/adapters/sqlite_spec.rb +24 -21
  106. data/spec/core/connection_pool_spec.rb +9 -15
  107. data/spec/core/core_sql_spec.rb +20 -31
  108. data/spec/core/database_spec.rb +491 -227
  109. data/spec/core/dataset_spec.rb +638 -1051
  110. data/spec/core/expression_filters_spec.rb +0 -1
  111. data/spec/core/mock_adapter_spec.rb +378 -0
  112. data/spec/core/object_graph_spec.rb +48 -114
  113. data/spec/core/schema_generator_spec.rb +3 -3
  114. data/spec/core/schema_spec.rb +51 -114
  115. data/spec/core/spec_helper.rb +3 -90
  116. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  117. data/spec/extensions/dataset_associations_spec.rb +199 -0
  118. data/spec/extensions/instance_hooks_spec.rb +71 -0
  119. data/spec/extensions/named_timezones_spec.rb +22 -2
  120. data/spec/extensions/nested_attributes_spec.rb +3 -0
  121. data/spec/extensions/schema_spec.rb +1 -1
  122. data/spec/extensions/serialization_modification_detection_spec.rb +1 -0
  123. data/spec/extensions/serialization_spec.rb +5 -8
  124. data/spec/extensions/spec_helper.rb +4 -0
  125. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  126. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  127. data/spec/integration/associations_test.rb +123 -12
  128. data/spec/integration/dataset_test.rb +140 -47
  129. data/spec/integration/eager_loader_test.rb +19 -21
  130. data/spec/integration/model_test.rb +80 -1
  131. data/spec/integration/plugin_test.rb +179 -128
  132. data/spec/integration/prepared_statement_test.rb +92 -91
  133. data/spec/integration/schema_test.rb +42 -23
  134. data/spec/integration/spec_helper.rb +25 -31
  135. data/spec/integration/timezone_test.rb +38 -12
  136. data/spec/integration/transaction_test.rb +161 -34
  137. data/spec/integration/type_test.rb +3 -3
  138. data/spec/model/association_reflection_spec.rb +83 -7
  139. data/spec/model/associations_spec.rb +393 -676
  140. data/spec/model/base_spec.rb +186 -116
  141. data/spec/model/dataset_methods_spec.rb +7 -27
  142. data/spec/model/eager_loading_spec.rb +343 -867
  143. data/spec/model/hooks_spec.rb +160 -79
  144. data/spec/model/model_spec.rb +118 -165
  145. data/spec/model/plugins_spec.rb +7 -13
  146. data/spec/model/record_spec.rb +138 -207
  147. data/spec/model/spec_helper.rb +10 -73
  148. 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 support if pg is the
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
- end
306
-
307
- # Return instance of Sequel::Postgres::Dataset with the given options.
308
- def dataset(opts = nil)
309
- Postgres::Dataset.new(self, opts)
310
- end
311
-
312
- # Execute the given SQL with the given args on an available connection.
313
- def execute(sql, opts={}, &block)
314
- check_database_errors do
315
- return execute_prepared_statement(sql, opts, &block) if Symbol === sql
316
- synchronize(opts[:server]){|conn| conn.execute(sql, opts[:arguments], &block)}
317
- end
318
- end
319
-
320
- # Insert the values into the table and return the primary key (if
321
- # automatically generated).
322
- def execute_insert(sql, opts={})
323
- return execute(sql, opts) if Symbol === sql
324
- check_database_errors do
325
- synchronize(opts[:server]) do |conn|
326
- conn.execute(sql, opts[:arguments])
327
- insert_result(conn, opts[:table], opts[:values])
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 :&, :|, :^, :'B~'
195
+ when :&, :|, :^
196
196
  # works with db2 v9.5 and after
197
- literal(SQL::Function.new(BITWISE_METHOD_MAP[op], *args))
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(args[0])} * POWER(2, #{literal(args[1])}))"
200
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
200
201
  when :>>
201
- "(#{literal(args[0])} / POWER(2, #{literal(args[1])}))"
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 uses "INSERT INTO "ITEMS" VALUES DEFAULT" for a record with default values to be inserted
248
- def insert_values_sql(sql)
249
- opts[:values].empty? ? sql << " VALUES DEFAULT" : super
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
- elsif supports_insert_select?
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 Thread.current[:sequel_transaction_depth] > 1
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. This method does
172
- # not support the parsing of primary key information.
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=>m2.call(table_name.to_s))
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 :ILIKE
266
- super(:LIKE, args)
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(args[0])} * POWER(2, #{literal(args[1])}))"
281
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
271
282
  when :>>
272
- "(#{literal(args[0])} / POWER(2, #{literal(args[1])}))"
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
- conn
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
- def auto_increment_sql
41
- AUTOINCREMENT
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
- # SQL fragment for showing a table is temporary
45
- def temporary_table_sql
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] ||= "seq_#{name}_#{c[: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
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct columns from join where group having compounds order limit lock')
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