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.
- 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
|