sequel 3.27.0 → 3.28.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +96 -0
- data/README.rdoc +2 -2
- data/Rakefile +1 -1
- data/doc/association_basics.rdoc +48 -0
- data/doc/opening_databases.rdoc +29 -5
- data/doc/prepared_statements.rdoc +1 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/testing.rdoc +42 -0
- data/doc/transactions.rdoc +97 -0
- data/lib/sequel/adapters/db2.rb +95 -65
- data/lib/sequel/adapters/firebird.rb +25 -219
- data/lib/sequel/adapters/ibmdb.rb +440 -0
- data/lib/sequel/adapters/jdbc.rb +12 -0
- data/lib/sequel/adapters/jdbc/as400.rb +0 -7
- data/lib/sequel/adapters/jdbc/db2.rb +49 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
- data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
- data/lib/sequel/adapters/mysql.rb +10 -15
- data/lib/sequel/adapters/odbc.rb +1 -2
- data/lib/sequel/adapters/odbc/db2.rb +5 -5
- data/lib/sequel/adapters/postgres.rb +71 -11
- data/lib/sequel/adapters/shared/db2.rb +290 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/mssql.rb +18 -75
- data/lib/sequel/adapters/shared/mysql.rb +13 -0
- data/lib/sequel/adapters/shared/postgres.rb +52 -36
- data/lib/sequel/adapters/shared/sqlite.rb +32 -36
- data/lib/sequel/adapters/sqlite.rb +4 -8
- data/lib/sequel/adapters/tinytds.rb +7 -3
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -5
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -1
- data/lib/sequel/dataset/actions.rb +149 -33
- data/lib/sequel/dataset/features.rb +44 -7
- data/lib/sequel/dataset/misc.rb +9 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -2
- data/lib/sequel/dataset/query.rb +63 -10
- data/lib/sequel/dataset/sql.rb +22 -5
- data/lib/sequel/model.rb +3 -3
- data/lib/sequel/model/associations.rb +250 -27
- data/lib/sequel/model/base.rb +10 -16
- data/lib/sequel/plugins/many_through_many.rb +34 -2
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
- data/lib/sequel/sql.rb +94 -51
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/postgres_spec.rb +74 -6
- data/spec/adapters/spec_helper.rb +1 -0
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/database_spec.rb +7 -0
- data/spec/core/dataset_spec.rb +180 -17
- data/spec/core/expression_filters_spec.rb +107 -41
- data/spec/core/spec_helper.rb +11 -0
- data/spec/extensions/many_through_many_spec.rb +115 -1
- data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
- data/spec/integration/associations_test.rb +193 -15
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +215 -19
- data/spec/integration/plugin_test.rb +8 -5
- data/spec/integration/prepared_statement_test.rb +91 -98
- data/spec/integration/schema_test.rb +27 -11
- data/spec/integration/spec_helper.rb +10 -0
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/association_reflection_spec.rb +91 -0
- data/spec/model/associations_spec.rb +13 -0
- data/spec/model/base_spec.rb +8 -21
- data/spec/model/eager_loading_spec.rb +243 -9
- data/spec/model/model_spec.rb +15 -2
- 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
|
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
|
-
|
713
|
-
elsif
|
714
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
806
|
-
def
|
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
|
-
|
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
|