sequel 3.27.0 → 3.28.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 (73) hide show
  1. data/CHANGELOG +96 -0
  2. data/README.rdoc +2 -2
  3. data/Rakefile +1 -1
  4. data/doc/association_basics.rdoc +48 -0
  5. data/doc/opening_databases.rdoc +29 -5
  6. data/doc/prepared_statements.rdoc +1 -0
  7. data/doc/release_notes/3.28.0.txt +304 -0
  8. data/doc/testing.rdoc +42 -0
  9. data/doc/transactions.rdoc +97 -0
  10. data/lib/sequel/adapters/db2.rb +95 -65
  11. data/lib/sequel/adapters/firebird.rb +25 -219
  12. data/lib/sequel/adapters/ibmdb.rb +440 -0
  13. data/lib/sequel/adapters/jdbc.rb +12 -0
  14. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  15. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  16. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  18. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  19. data/lib/sequel/adapters/mysql.rb +10 -15
  20. data/lib/sequel/adapters/odbc.rb +1 -2
  21. data/lib/sequel/adapters/odbc/db2.rb +5 -5
  22. data/lib/sequel/adapters/postgres.rb +71 -11
  23. data/lib/sequel/adapters/shared/db2.rb +290 -0
  24. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +18 -75
  26. data/lib/sequel/adapters/shared/mysql.rb +13 -0
  27. data/lib/sequel/adapters/shared/postgres.rb +52 -36
  28. data/lib/sequel/adapters/shared/sqlite.rb +32 -36
  29. data/lib/sequel/adapters/sqlite.rb +4 -8
  30. data/lib/sequel/adapters/tinytds.rb +7 -3
  31. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  32. data/lib/sequel/core.rb +1 -1
  33. data/lib/sequel/database/connecting.rb +1 -1
  34. data/lib/sequel/database/misc.rb +6 -5
  35. data/lib/sequel/database/query.rb +1 -1
  36. data/lib/sequel/database/schema_generator.rb +2 -1
  37. data/lib/sequel/dataset/actions.rb +149 -33
  38. data/lib/sequel/dataset/features.rb +44 -7
  39. data/lib/sequel/dataset/misc.rb +9 -1
  40. data/lib/sequel/dataset/prepared_statements.rb +2 -2
  41. data/lib/sequel/dataset/query.rb +63 -10
  42. data/lib/sequel/dataset/sql.rb +22 -5
  43. data/lib/sequel/model.rb +3 -3
  44. data/lib/sequel/model/associations.rb +250 -27
  45. data/lib/sequel/model/base.rb +10 -16
  46. data/lib/sequel/plugins/many_through_many.rb +34 -2
  47. data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
  48. data/lib/sequel/sql.rb +94 -51
  49. data/lib/sequel/version.rb +1 -1
  50. data/spec/adapters/db2_spec.rb +146 -0
  51. data/spec/adapters/postgres_spec.rb +74 -6
  52. data/spec/adapters/spec_helper.rb +1 -0
  53. data/spec/adapters/sqlite_spec.rb +11 -0
  54. data/spec/core/database_spec.rb +7 -0
  55. data/spec/core/dataset_spec.rb +180 -17
  56. data/spec/core/expression_filters_spec.rb +107 -41
  57. data/spec/core/spec_helper.rb +11 -0
  58. data/spec/extensions/many_through_many_spec.rb +115 -1
  59. data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
  60. data/spec/integration/associations_test.rb +193 -15
  61. data/spec/integration/database_test.rb +4 -2
  62. data/spec/integration/dataset_test.rb +215 -19
  63. data/spec/integration/plugin_test.rb +8 -5
  64. data/spec/integration/prepared_statement_test.rb +91 -98
  65. data/spec/integration/schema_test.rb +27 -11
  66. data/spec/integration/spec_helper.rb +10 -0
  67. data/spec/integration/type_test.rb +2 -2
  68. data/spec/model/association_reflection_spec.rb +91 -0
  69. data/spec/model/associations_spec.rb +13 -0
  70. data/spec/model/base_spec.rb +8 -21
  71. data/spec/model/eager_loading_spec.rb +243 -9
  72. data/spec/model/model_spec.rb +15 -2
  73. metadata +16 -4
@@ -0,0 +1,214 @@
1
+ module Sequel
2
+ module Firebird
3
+ module DatabaseMethods
4
+ AUTO_INCREMENT = ''.freeze
5
+ TEMPORARY = 'GLOBAL TEMPORARY '.freeze
6
+
7
+ def clear_primary_key(*tables)
8
+ tables.each{|t| @primary_keys.delete(dataset.send(:input_identifier, t))}
9
+ end
10
+
11
+ def create_trigger(*args)
12
+ self << create_trigger_sql(*args)
13
+ end
14
+
15
+ def database_type
16
+ :firebird
17
+ end
18
+
19
+ def drop_sequence(name)
20
+ self << drop_sequence_sql(name)
21
+ end
22
+
23
+ def drop_table(*names)
24
+ clear_primary_key(*names)
25
+ super
26
+ end
27
+
28
+ # Return primary key for the given table.
29
+ def primary_key(table)
30
+ t = dataset.send(:input_identifier, table)
31
+ @primary_keys.fetch(t) do
32
+ pk = fetch("SELECT RDB$FIELD_NAME FROM RDB$INDEX_SEGMENTS NATURAL JOIN RDB$RELATION_CONSTRAINTS WHERE RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' AND RDB$RELATION_NAME = ?", t).single_value
33
+ @primary_keys[t] = dataset.send(:output_identifier, pk.rstrip) if pk
34
+ end
35
+ end
36
+
37
+ def restart_sequence(*args)
38
+ self << restart_sequence_sql(*args)
39
+ end
40
+
41
+ def sequences(opts={})
42
+ ds = self[:"rdb$generators"].server(opts[:server]).filter(:"rdb$system_flag" => 0).select(:"rdb$generator_name")
43
+ block_given? ? yield(ds) : ds.map{|r| ds.send(:output_identifier, r[:"rdb$generator_name"])}
44
+ end
45
+
46
+ def tables(opts={})
47
+ tables_or_views(0, opts)
48
+ end
49
+
50
+ def views(opts={})
51
+ tables_or_views(1, opts)
52
+ end
53
+
54
+ private
55
+
56
+ # Use Firebird specific syntax for add column
57
+ def alter_table_sql(table, op)
58
+ case op[:op]
59
+ when :add_column
60
+ "ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
61
+ when :drop_column
62
+ "ALTER TABLE #{quote_schema_table(table)} DROP #{column_definition_sql(op)}"
63
+ when :rename_column
64
+ "ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TO #{quote_identifier(op[:new_name])}"
65
+ when :set_column_type
66
+ "ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TYPE #{type_literal(op)}"
67
+ else
68
+ super(table, op)
69
+ end
70
+ end
71
+
72
+ def auto_increment_sql()
73
+ AUTO_INCREMENT
74
+ end
75
+
76
+ def create_sequence_sql(name, opts={})
77
+ "CREATE SEQUENCE #{quote_identifier(name)}"
78
+ end
79
+
80
+ # Firebird gets an override because of the mess of creating a
81
+ # sequence and trigger for auto-incrementing primary keys.
82
+ def create_table_from_generator(name, generator, options)
83
+ drop_statement, create_statements = create_table_sql_list(name, generator, options)
84
+ (execute_ddl(drop_statement) rescue nil) if drop_statement
85
+ create_statements.each{|sql| execute_ddl(sql)}
86
+ end
87
+
88
+ def create_table_sql_list(name, generator, options={})
89
+ statements = [create_table_sql(name, generator, options)]
90
+ drop_seq_statement = nil
91
+ generator.columns.each do |c|
92
+ if c[:auto_increment]
93
+ c[:sequence_name] ||= "seq_#{name}_#{c[:name]}"
94
+ unless c[:create_sequence] == false
95
+ drop_seq_statement = drop_sequence_sql(c[:sequence_name])
96
+ statements << create_sequence_sql(c[:sequence_name])
97
+ statements << restart_sequence_sql(c[:sequence_name], {:restart_position => c[:sequence_start_position]}) if c[:sequence_start_position]
98
+ end
99
+ unless c[:create_trigger] == false
100
+ c[:trigger_name] ||= "BI_#{name}_#{c[:name]}"
101
+ c[:quoted_name] = quote_identifier(c[:name])
102
+ trigger_definition = <<-END
103
+ begin
104
+ if ((new.#{c[:quoted_name]} is null) or (new.#{c[:quoted_name]} = 0)) then
105
+ begin
106
+ new.#{c[:quoted_name]} = next value for #{c[:sequence_name]};
107
+ end
108
+ end
109
+ END
110
+ statements << create_trigger_sql(name, c[:trigger_name], trigger_definition, {:events => [:insert]})
111
+ end
112
+ end
113
+ end
114
+ [drop_seq_statement, statements]
115
+ end
116
+
117
+ def create_trigger_sql(table, name, definition, opts={})
118
+ events = opts[:events] ? Array(opts[:events]) : [:insert, :update, :delete]
119
+ whence = opts[:after] ? 'AFTER' : 'BEFORE'
120
+ inactive = opts[:inactive] ? 'INACTIVE' : 'ACTIVE'
121
+ position = opts.fetch(:position, 0)
122
+ sql = <<-end_sql
123
+ CREATE TRIGGER #{quote_identifier(name)} for #{quote_identifier(table)}
124
+ #{inactive} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} position #{position}
125
+ as #{definition}
126
+ end_sql
127
+ sql
128
+ end
129
+
130
+ def drop_sequence_sql(name)
131
+ "DROP SEQUENCE #{quote_identifier(name)}"
132
+ end
133
+
134
+ def restart_sequence_sql(name, opts={})
135
+ seq_name = quote_identifier(name)
136
+ "ALTER SEQUENCE #{seq_name} RESTART WITH #{opts[:restart_position]}"
137
+ end
138
+
139
+ def tables_or_views(type, opts)
140
+ ds = self[:"rdb$relations"].server(opts[:server]).filter(:"rdb$relation_type" => type, Sequel::SQL::Function.new(:COALESCE, :"rdb$system_flag", 0) => 0).select(:"rdb$relation_name")
141
+ ds.map{|r| ds.send(:output_identifier, r[:"rdb$relation_name"].rstrip)}
142
+ end
143
+
144
+ def type_literal_generic_string(column)
145
+ column[:text] ? :"BLOB SUB_TYPE TEXT" : super
146
+ end
147
+ end
148
+
149
+ module DatasetMethods
150
+ BOOL_TRUE = '1'.freeze
151
+ BOOL_FALSE = '0'.freeze
152
+ NULL = LiteralString.new('NULL').freeze
153
+ COMMA_SEPARATOR = ', '.freeze
154
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct limit columns from join where group having compounds order')
155
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values returning')
156
+
157
+ # Insert given values into the database.
158
+ def insert(*values)
159
+ if @opts[:sql] || @opts[:returning]
160
+ super
161
+ elsif supports_insert_select?
162
+ returning(insert_pk).insert(*values){|r| return r.values.first}
163
+ end
164
+ end
165
+
166
+ # Insert a record returning the record inserted
167
+ def insert_select(*values)
168
+ returning.insert(*values){|r| return r}
169
+ end
170
+
171
+ def requires_sql_standard_datetimes?
172
+ true
173
+ end
174
+
175
+ def supports_insert_select?
176
+ true
177
+ end
178
+
179
+ # Firebird does not support INTERSECT or EXCEPT
180
+ def supports_intersect_except?
181
+ false
182
+ end
183
+
184
+ private
185
+
186
+ def insert_clause_methods
187
+ INSERT_CLAUSE_METHODS
188
+ end
189
+
190
+ def insert_pk(*values)
191
+ pk = db.primary_key(opts[:from].first)
192
+ pk ? Sequel::SQL::Identifier.new(pk) : NULL
193
+ end
194
+
195
+ def literal_false
196
+ BOOL_FALSE
197
+ end
198
+
199
+ def literal_true
200
+ BOOL_TRUE
201
+ end
202
+
203
+ # The order of clauses in the SELECT SQL statement
204
+ def select_clause_methods
205
+ SELECT_CLAUSE_METHODS
206
+ end
207
+
208
+ def select_limit_sql(sql)
209
+ sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
210
+ sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
211
+ end
212
+ end
213
+ end
214
+ end
@@ -1,3 +1,5 @@
1
+ Sequel.require 'adapters/utils/emulate_offset_with_row_number'
2
+
1
3
  module Sequel
2
4
  Dataset::NON_SQL_OPTIONS << :disable_insert_output
3
5
  module MSSQL
@@ -231,6 +233,8 @@ module Sequel
231
233
  end
232
234
 
233
235
  module DatasetMethods
236
+ include EmulateOffsetWithRowNumber
237
+
234
238
  BOOL_TRUE = '1'.freeze
235
239
  BOOL_FALSE = '0'.freeze
236
240
  COMMA_SEPARATOR = ', '.freeze
@@ -242,6 +246,7 @@ module Sequel
242
246
  UPDLOCK = ' WITH (UPDLOCK)'.freeze
243
247
  WILDCARD = LiteralString.new('*').freeze
244
248
  CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
249
+ EXTRACT_MAP = {:year=>"yy", :month=>"m", :day=>"d", :hour=>"hh", :minute=>"n", :second=>"s"}
245
250
 
246
251
  # Allow overriding of the mssql_unicode_strings option at the dataset level.
247
252
  attr_accessor :mssql_unicode_strings
@@ -252,29 +257,6 @@ module Sequel
252
257
  @mssql_unicode_strings = db.mssql_unicode_strings
253
258
  end
254
259
 
255
- # Ugly hack. While MSSQL supports TRUE and FALSE values, you can't
256
- # actually specify them directly in SQL. Unfortunately, you also cannot
257
- # use an integer value when a boolean is required. Also unforunately, you
258
- # cannot use an expression that yields a boolean type in cases where in an
259
- # integer type is needed, such as inserting into a bit field (the closest thing
260
- # MSSQL has to a boolean).
261
- #
262
- # In filters, SQL::BooleanConstants are used more, while in other places
263
- # the ruby true/false values are used more, so use expressions that return booleans
264
- # for SQL::BooleanConstants, and 1/0 for other places.
265
- # The correct fix for this would require separate literalization paths for
266
- # filters compared to other values, but that's more work than I want to do right now.
267
- def boolean_constant_sql(constant)
268
- case constant
269
- when true
270
- '(1 = 1)'
271
- when false
272
- '(1 = 0)'
273
- else
274
- super
275
- end
276
- end
277
-
278
260
  # MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
279
261
  def complex_expression_sql(op, args)
280
262
  case op
@@ -288,6 +270,13 @@ module Sequel
288
270
  "(#{literal(args[0])} * POWER(2, #{literal(args[1])}))"
289
271
  when :>>
290
272
  "(#{literal(args[0])} / POWER(2, #{literal(args[1])}))"
273
+ when :extract
274
+ part = args.at(0)
275
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
276
+ expr = literal(args.at(1))
277
+ s = "datepart(#{format}, #{expr})"
278
+ s = "CAST((#{s} + datepart(ns, #{expr})/1000000000.0) AS double precision)" if part == :second
279
+ s
291
280
  else
292
281
  super(op, args)
293
282
  end
@@ -330,14 +319,6 @@ module Sequel
330
319
  clone(:into => table)
331
320
  end
332
321
 
333
- # SQL Server does not support CTEs on subqueries, so move any CTEs
334
- # on joined datasets to the top level. The user is responsible for
335
- # resolving any name clashes this may cause.
336
- def join_table(type, table, expr=nil, table_alias={}, &block)
337
- return super unless Dataset === table && table.opts[:with]
338
- clone(:with => (opts[:with] || []) + table.opts[:with]).join_table(type, table.clone(:with => nil), expr, table_alias, &block)
339
- end
340
-
341
322
  # MSSQL uses a UNION ALL statement to insert multiple values at once.
342
323
  def multi_insert_sql(columns, values)
343
324
  [insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
@@ -383,29 +364,6 @@ module Sequel
383
364
  "[#{name}]"
384
365
  end
385
366
 
386
- # MSSQL Requires the use of the ROW_NUMBER window function to emulate
387
- # an offset. This implementation requires MSSQL 2005 or greater (offset
388
- # can't be emulated well in MSSQL 2000).
389
- #
390
- # The implementation is ugly, cloning the current dataset and modifying
391
- # the clone to add a ROW_NUMBER window function (and some other things),
392
- # then using the modified clone in a subselect which is selected from.
393
- #
394
- # If offset is used, an order must be provided, because the use of ROW_NUMBER
395
- # requires an order.
396
- def select_sql
397
- return super unless o = @opts[:offset]
398
- raise(Error, 'MSSQL requires an order be provided if using an offset') unless order = @opts[:order]
399
- dsa1 = dataset_alias(1)
400
- rn = row_number_column
401
- subselect_sql(unlimited.
402
- unordered.
403
- select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
404
- from_self(:alias=>dsa1).
405
- limit(@opts[:limit]).
406
- where(SQL::Identifier.new(rn) > o))
407
- end
408
-
409
367
  # The version of the database server.
410
368
  def server_version
411
369
  db.server_version(@opts[:server])
@@ -450,6 +408,11 @@ module Sequel
450
408
  def supports_window_functions?
451
409
  true
452
410
  end
411
+
412
+ # MSSQL cannot use WHERE 1.
413
+ def supports_where_true?
414
+ false
415
+ end
453
416
 
454
417
  protected
455
418
  # MSSQL does not allow ordering in sub-clauses unless 'top' (limit) is specified
@@ -458,6 +421,7 @@ module Sequel
458
421
  end
459
422
 
460
423
  private
424
+
461
425
  def is_2005_or_later?
462
426
  server_version >= 9000000
463
427
  end
@@ -490,22 +454,6 @@ module Sequel
490
454
  alias insert_with_sql delete_with_sql
491
455
  alias update_with_sql delete_with_sql
492
456
 
493
- # Special case when true or false is provided directly to filter.
494
- def filter_expr(expr)
495
- if block_given?
496
- super
497
- else
498
- case expr
499
- when true
500
- Sequel::TRUE
501
- when false
502
- Sequel::FALSE
503
- else
504
- super
505
- end
506
- end
507
- end
508
-
509
457
  # MSSQL raises an error if you try to provide more than 3 decimal places
510
458
  # for a fractional timestamp. This probably doesn't work for smalldatetime
511
459
  # fields.
@@ -552,11 +500,6 @@ module Sequel
552
500
  BOOL_TRUE
553
501
  end
554
502
 
555
- # The alias to use for the row_number column when emulating OFFSET
556
- def row_number_column
557
- :x_sequel_row_number_x
558
- end
559
-
560
503
  # MSSQL adds the limit before the columns
561
504
  def select_clause_methods
562
505
  SELECT_CLAUSE_METHODS
@@ -342,6 +342,13 @@ module Sequel
342
342
  # string concatenation.
343
343
  def complex_expression_sql(op, args)
344
344
  case op
345
+ when :IN, :"NOT IN"
346
+ ds = args.at(1)
347
+ if ds.is_a?(Sequel::Dataset) && ds.opts[:limit]
348
+ super(op, [args.at(0), ds.from_self])
349
+ else
350
+ super
351
+ end
345
352
  when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
346
353
  "(#{literal(args.at(0))} #{'NOT ' if [:'NOT LIKE', :'NOT ILIKE', :'!~', :'!~*'].include?(op)}#{[:~, :'!~', :'~*', :'!~*'].include?(op) ? 'REGEXP' : 'LIKE'} #{'BINARY ' if [:~, :'!~', :LIKE, :'NOT LIKE'].include?(op)}#{literal(args.at(1))})"
347
354
  when :'||'
@@ -481,6 +488,12 @@ module Sequel
481
488
  true
482
489
  end
483
490
 
491
+ # MySQL's DISTINCT ON emulation using GROUP BY does not respect the
492
+ # queries ORDER BY clause.
493
+ def supports_ordered_distinct_on?
494
+ false
495
+ end
496
+
484
497
  # MySQL does support fractional timestamps in literal timestamps, but it
485
498
  # ignores them. Also, using them seems to cause problems on 1.9. Since
486
499
  # they are ignored anyway, not using them is probably best.
@@ -358,6 +358,11 @@ module Sequel
358
358
  @supports_prepared_transactions = self['SHOW max_prepared_transactions'].get.to_i > 0
359
359
  end
360
360
 
361
+ # PostgreSQL supports CREATE TABLE IF NOT EXISTS on 9.1+
362
+ def supports_create_table_if_not_exists?
363
+ server_version >= 90100
364
+ end
365
+
361
366
  # PostgreSQL supports savepoints
362
367
  def supports_savepoints?
363
368
  true
@@ -625,11 +630,14 @@ module Sequel
625
630
  BOOL_TRUE = 'true'.freeze
626
631
  COMMA_SEPARATOR = ', '.freeze
627
632
  DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from using where')
633
+ DELETE_CLAUSE_METHODS_91 = Dataset.clause_methods(:delete, %w'with from using where returning')
628
634
  EXCLUSIVE = 'EXCLUSIVE'.freeze
629
635
  EXPLAIN = 'EXPLAIN '.freeze
630
636
  EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
631
637
  FOR_SHARE = ' FOR SHARE'.freeze
632
- INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values returning_select')
638
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values')
639
+ INSERT_CLAUSE_METHODS_82 = Dataset.clause_methods(:insert, %w'into columns values returning')
640
+ INSERT_CLAUSE_METHODS_91 = Dataset.clause_methods(:insert, %w'with into columns values returning')
633
641
  LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
634
642
  NULL = LiteralString.new('NULL').freeze
635
643
  PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
@@ -643,17 +651,23 @@ module Sequel
643
651
  SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
644
652
  SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
645
653
  UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set from where')
654
+ UPDATE_CLAUSE_METHODS_91 = Dataset.clause_methods(:update, %w'with table set from where returning')
646
655
 
647
656
  # Shared methods for prepared statements when used with PostgreSQL databases.
648
657
  module PreparedStatementMethods
649
658
  # Override insert action to use RETURNING if the server supports it.
659
+ def run(&block)
660
+ if @prepared_type == :insert && supports_insert_select?
661
+ fetch_rows(prepared_sql){|r| return r.values.first}
662
+ else
663
+ super
664
+ end
665
+ end
666
+
650
667
  def prepared_sql
651
668
  return @prepared_sql if @prepared_sql
669
+ @opts[:returning] = insert_pk if @prepared_type == :insert && supports_insert_select?
652
670
  super
653
- if @prepared_type == :insert and !@opts[:disable_insert_returning] and server_version >= 80200
654
- @prepared_sql = insert_returning_pk_sql(*@prepared_modify_values)
655
- meta_def(:insert_returning_pk_sql){|*args| prepared_sql}
656
- end
657
671
  @prepared_sql
658
672
  end
659
673
  end
@@ -707,25 +721,20 @@ module Sequel
707
721
  end
708
722
 
709
723
  # Insert given values into the database.
710
- def insert(*values)
711
- if @opts[:sql]
712
- execute_insert(insert_sql(*values))
713
- elsif @opts[:disable_insert_returning] || server_version < 80200
714
- execute_insert(insert_sql(*values), :table=>opts[:from].first, :values=>values.size == 1 ? values.first : values)
724
+ def insert(*values, &block)
725
+ if @opts[:sql] || @opts[:returning]
726
+ super
727
+ elsif supports_insert_select?
728
+ returning(insert_pk).insert(*values){|r| return r.values.first}
715
729
  else
716
- clone(default_server_opts(:sql=>insert_returning_pk_sql(*values))).single_value
730
+ execute_insert(insert_sql(*values), :table=>opts[:from].first, :values=>values.size == 1 ? values.first : values)
717
731
  end
718
732
  end
719
733
 
720
- # Use the RETURNING clause to return the columns listed in returning.
721
- def insert_returning_sql(returning, *values)
722
- "#{insert_sql(*values)} RETURNING #{column_list(Array(returning))}"
723
- end
724
-
725
734
  # Insert a record returning the record inserted
726
735
  def insert_select(*values)
727
736
  return unless supports_insert_select?
728
- naked.clone(default_server_opts(:sql=>insert_returning_sql(nil, *values))).single_record
737
+ returning.insert(*values){|r| return r}
729
738
  end
730
739
 
731
740
  # Locks all tables in the dataset's FROM clause (but not in JOINs) with
@@ -750,21 +759,30 @@ module Sequel
750
759
  [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
751
760
  end
752
761
 
762
+ # PostgreSQL supports using the WITH clause in subqueries if it
763
+ # supports using WITH at all (i.e. on PostgreSQL 8.4+).
764
+ def supports_cte_in_subqueries?
765
+ supports_cte?
766
+ end
767
+
753
768
  # DISTINCT ON is a PostgreSQL extension
754
769
  def supports_distinct_on?
755
770
  true
756
771
  end
757
772
 
758
- # PostgreSQL support insert_select using the RETURNING clause.
759
- def supports_insert_select?
760
- server_version >= 80200 && !opts[:disable_insert_returning]
761
- end
762
-
763
773
  # PostgreSQL supports modifying joined datasets
764
774
  def supports_modifying_joins?
765
775
  true
766
776
  end
767
777
 
778
+ def supports_returning?(type)
779
+ if type == :insert
780
+ server_version >= 80200 && !opts[:disable_insert_returning]
781
+ else
782
+ server_version >= 90100
783
+ end
784
+ end
785
+
768
786
  # PostgreSQL supports timezones in literal timestamps
769
787
  def supports_timestamp_timezones?
770
788
  true
@@ -784,7 +802,7 @@ module Sequel
784
802
 
785
803
  # PostgreSQL allows deleting from joined datasets
786
804
  def delete_clause_methods
787
- DELETE_CLAUSE_METHODS
805
+ server_version >= 90100 ? DELETE_CLAUSE_METHODS_91 : DELETE_CLAUSE_METHODS
788
806
  end
789
807
 
790
808
  # Only include the primary table in the main delete clause
@@ -799,23 +817,21 @@ module Sequel
799
817
 
800
818
  # PostgreSQL allows a RETURNING clause.
801
819
  def insert_clause_methods
802
- INSERT_CLAUSE_METHODS
820
+ if server_version >= 90100
821
+ INSERT_CLAUSE_METHODS_91
822
+ elsif server_version >= 80200
823
+ INSERT_CLAUSE_METHODS_82
824
+ else
825
+ INSERT_CLAUSE_METHODS
826
+ end
803
827
  end
804
828
 
805
- # Use the RETURNING clause to return the primary key of the inserted record, if it exists
806
- def insert_returning_pk_sql(*values)
829
+ # Return the primary key to use for RETURNING in an INSERT statement
830
+ def insert_pk
807
831
  pk = db.primary_key(opts[:from].first) if opts[:from] && !opts[:from].empty?
808
- insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : NULL, *values)
832
+ pk ? Sequel::SQL::Identifier.new(pk) : NULL
809
833
  end
810
834
 
811
- # Add a RETURNING clause if it is set and the database supports it and
812
- # this dataset hasn't disabled it.
813
- def insert_returning_select_sql(sql)
814
- if supports_insert_select? && opts.has_key?(:returning)
815
- sql << " RETURNING #{column_list(Array(opts[:returning]))}"
816
- end
817
- end
818
-
819
835
  # For multiple table support, PostgreSQL requires at least
820
836
  # two from tables, with joins allowed.
821
837
  def join_from_sql(type, sql)
@@ -882,7 +898,7 @@ module Sequel
882
898
 
883
899
  # PostgreSQL splits the main table from the joined tables
884
900
  def update_clause_methods
885
- UPDATE_CLAUSE_METHODS
901
+ server_version >= 90100 ? UPDATE_CLAUSE_METHODS_91 : UPDATE_CLAUSE_METHODS
886
902
  end
887
903
 
888
904
  # Use FROM to specify additional tables in an update query