sequel 3.28.0 → 3.29.0

Sign up to get free protection for your applications and to get access to all the features.
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