sequel 4.9.0 → 4.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +79 -1
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/Rakefile +2 -12
- data/bin/sequel +1 -0
- data/doc/advanced_associations.rdoc +82 -25
- data/doc/association_basics.rdoc +21 -22
- data/doc/core_extensions.rdoc +1 -1
- data/doc/opening_databases.rdoc +7 -0
- data/doc/release_notes/4.10.0.txt +226 -0
- data/doc/security.rdoc +1 -0
- data/doc/testing.rdoc +7 -7
- data/doc/transactions.rdoc +8 -0
- data/lib/sequel/adapters/jdbc.rb +160 -168
- data/lib/sequel/adapters/jdbc/db2.rb +17 -18
- data/lib/sequel/adapters/jdbc/derby.rb +5 -28
- data/lib/sequel/adapters/jdbc/h2.rb +11 -22
- data/lib/sequel/adapters/jdbc/hsqldb.rb +31 -18
- data/lib/sequel/adapters/jdbc/jtds.rb +0 -15
- data/lib/sequel/adapters/jdbc/oracle.rb +36 -35
- data/lib/sequel/adapters/jdbc/postgresql.rb +72 -90
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +18 -16
- data/lib/sequel/adapters/jdbc/sqlite.rb +7 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +10 -30
- data/lib/sequel/adapters/jdbc/transactions.rb +5 -6
- data/lib/sequel/adapters/openbase.rb +1 -7
- data/lib/sequel/adapters/postgres.rb +1 -1
- data/lib/sequel/adapters/shared/access.rb +3 -6
- data/lib/sequel/adapters/shared/cubrid.rb +24 -9
- data/lib/sequel/adapters/shared/db2.rb +13 -5
- data/lib/sequel/adapters/shared/firebird.rb +16 -16
- data/lib/sequel/adapters/shared/informix.rb +2 -5
- data/lib/sequel/adapters/shared/mssql.rb +72 -63
- data/lib/sequel/adapters/shared/mysql.rb +72 -40
- data/lib/sequel/adapters/shared/oracle.rb +27 -15
- data/lib/sequel/adapters/shared/postgres.rb +24 -44
- data/lib/sequel/adapters/shared/progress.rb +1 -5
- data/lib/sequel/adapters/shared/sqlanywhere.rb +26 -18
- data/lib/sequel/adapters/shared/sqlite.rb +21 -6
- data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +8 -1
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -2
- data/lib/sequel/adapters/utils/split_alter_table.rb +8 -0
- data/lib/sequel/core.rb +14 -9
- data/lib/sequel/database/dataset_defaults.rb +1 -0
- data/lib/sequel/database/misc.rb +12 -0
- data/lib/sequel/database/query.rb +4 -1
- data/lib/sequel/database/schema_methods.rb +3 -2
- data/lib/sequel/database/transactions.rb +47 -17
- data/lib/sequel/dataset/features.rb +12 -2
- data/lib/sequel/dataset/mutation.rb +2 -0
- data/lib/sequel/dataset/placeholder_literalizer.rb +12 -4
- data/lib/sequel/dataset/prepared_statements.rb +6 -0
- data/lib/sequel/dataset/query.rb +1 -1
- data/lib/sequel/dataset/sql.rb +132 -70
- data/lib/sequel/extensions/columns_introspection.rb +1 -1
- data/lib/sequel/extensions/null_dataset.rb +8 -4
- data/lib/sequel/extensions/pg_array.rb +4 -4
- data/lib/sequel/extensions/pg_row.rb +1 -0
- data/lib/sequel/model/associations.rb +468 -188
- data/lib/sequel/model/base.rb +88 -13
- data/lib/sequel/plugins/association_pks.rb +23 -64
- data/lib/sequel/plugins/auto_validations.rb +3 -2
- data/lib/sequel/plugins/dataset_associations.rb +1 -3
- data/lib/sequel/plugins/many_through_many.rb +18 -65
- data/lib/sequel/plugins/pg_array_associations.rb +97 -86
- data/lib/sequel/plugins/prepared_statements.rb +2 -1
- data/lib/sequel/plugins/prepared_statements_associations.rb +36 -27
- data/lib/sequel/plugins/rcte_tree.rb +12 -16
- data/lib/sequel/plugins/sharding.rb +21 -3
- data/lib/sequel/plugins/single_table_inheritance.rb +2 -1
- data/lib/sequel/plugins/subclasses.rb +1 -9
- data/lib/sequel/plugins/tactical_eager_loading.rb +9 -0
- data/lib/sequel/plugins/tree.rb +2 -2
- data/lib/sequel/plugins/validation_class_methods.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +57 -15
- data/spec/adapters/mysql_spec.rb +11 -0
- data/spec/bin_spec.rb +2 -2
- data/spec/core/database_spec.rb +38 -4
- data/spec/core/dataset_spec.rb +45 -7
- data/spec/core/placeholder_literalizer_spec.rb +17 -0
- data/spec/core/schema_spec.rb +6 -1
- data/spec/extensions/active_model_spec.rb +18 -9
- data/spec/extensions/association_pks_spec.rb +20 -18
- data/spec/extensions/association_proxies_spec.rb +9 -9
- data/spec/extensions/auto_validations_spec.rb +6 -0
- data/spec/extensions/columns_introspection_spec.rb +1 -0
- data/spec/extensions/constraint_validations_spec.rb +3 -1
- data/spec/extensions/many_through_many_spec.rb +191 -111
- data/spec/extensions/pg_array_associations_spec.rb +133 -103
- data/spec/extensions/prepared_statements_associations_spec.rb +23 -4
- data/spec/extensions/rcte_tree_spec.rb +35 -27
- data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -1
- data/spec/extensions/sharding_spec.rb +2 -2
- data/spec/extensions/tactical_eager_loading_spec.rb +4 -0
- data/spec/extensions/to_dot_spec.rb +1 -0
- data/spec/extensions/touch_spec.rb +2 -2
- data/spec/integration/associations_test.rb +130 -37
- data/spec/integration/dataset_test.rb +17 -0
- data/spec/integration/model_test.rb +17 -0
- data/spec/integration/schema_test.rb +14 -0
- data/spec/integration/transaction_test.rb +25 -1
- data/spec/model/association_reflection_spec.rb +63 -24
- data/spec/model/associations_spec.rb +104 -57
- data/spec/model/base_spec.rb +14 -1
- data/spec/model/class_dataset_methods_spec.rb +1 -0
- data/spec/model/eager_loading_spec.rb +221 -74
- data/spec/model/model_spec.rb +119 -1
- metadata +4 -2
@@ -10,7 +10,7 @@ module Sequel
|
|
10
10
|
end
|
11
11
|
|
12
12
|
module DatasetMethods
|
13
|
-
|
13
|
+
Dataset.def_sql_method(self, :select, %w'select limit distinct columns from join where group order having compounds')
|
14
14
|
|
15
15
|
# Progress requires SQL standard datetimes
|
16
16
|
def requires_sql_standard_datetimes?
|
@@ -24,10 +24,6 @@ module Sequel
|
|
24
24
|
|
25
25
|
private
|
26
26
|
|
27
|
-
def select_clause_methods
|
28
|
-
SELECT_CLAUSE_METHODS
|
29
|
-
end
|
30
|
-
|
31
27
|
# Progress uses TOP for limit, but it is only supported in Progress 10.
|
32
28
|
# The Progress adapter targets Progress 9, so it silently ignores the option.
|
33
29
|
def select_limit_sql(sql)
|
@@ -240,8 +240,6 @@ module Sequel
|
|
240
240
|
module DatasetMethods
|
241
241
|
BOOL_TRUE = '1'.freeze
|
242
242
|
BOOL_FALSE = '0'.freeze
|
243
|
-
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with insert into columns values')
|
244
|
-
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns into from join where group having order compounds lock')
|
245
243
|
WILDCARD = LiteralString.new('%').freeze
|
246
244
|
TOP = " TOP ".freeze
|
247
245
|
START_AT = " START AT ".freeze
|
@@ -261,6 +259,10 @@ module Sequel
|
|
261
259
|
HSTAR = "H*".freeze
|
262
260
|
CROSS_APPLY = 'CROSS APPLY'.freeze
|
263
261
|
OUTER_APPLY = 'OUTER APPLY'.freeze
|
262
|
+
ONLY_OFFSET = " TOP 2147483647".freeze
|
263
|
+
|
264
|
+
Dataset.def_sql_method(self, :insert, %w'with insert into columns values')
|
265
|
+
Dataset.def_sql_method(self, :select, %w'with select distinct limit columns into from join where group having order compounds lock')
|
264
266
|
|
265
267
|
# Whether to convert smallint to boolean arguments for this dataset.
|
266
268
|
# Defaults to the SqlAnywhere module setting.
|
@@ -271,6 +273,10 @@ module Sequel
|
|
271
273
|
# Override the default SqlAnywhere.convert_smallint_to_bool setting for this dataset.
|
272
274
|
attr_writer :convert_smallint_to_bool
|
273
275
|
|
276
|
+
def supports_cte?(type=:select)
|
277
|
+
type == :select || type == :insert
|
278
|
+
end
|
279
|
+
|
274
280
|
def supports_multiple_column_in?
|
275
281
|
false
|
276
282
|
end
|
@@ -401,14 +407,9 @@ module Sequel
|
|
401
407
|
end
|
402
408
|
end
|
403
409
|
|
404
|
-
# Sybase supports
|
405
|
-
|
406
|
-
|
407
|
-
INSERT_CLAUSE_METHODS
|
408
|
-
end
|
409
|
-
|
410
|
-
def select_clause_methods
|
411
|
-
SELECT_CLAUSE_METHODS
|
410
|
+
# Sybase supports multiple rows in INSERT.
|
411
|
+
def multi_insert_sql_strategy
|
412
|
+
:values
|
412
413
|
end
|
413
414
|
|
414
415
|
def select_into_sql(sql)
|
@@ -425,14 +426,21 @@ module Sequel
|
|
425
426
|
# Sybase uses TOP N for limit. For Sybase TOP (N) is used
|
426
427
|
# to allow the limit to be a bound variable.
|
427
428
|
def select_limit_sql(sql)
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
429
|
+
l = @opts[:limit]
|
430
|
+
o = @opts[:offset]
|
431
|
+
if l || o
|
432
|
+
if l
|
433
|
+
sql << TOP
|
434
|
+
literal_append(sql, l)
|
435
|
+
else
|
436
|
+
sql << ONLY_OFFSET
|
437
|
+
end
|
438
|
+
|
439
|
+
if o
|
440
|
+
sql << START_AT + "("
|
441
|
+
literal_append(sql, o)
|
442
|
+
sql << " + 1)"
|
443
|
+
end
|
436
444
|
end
|
437
445
|
end
|
438
446
|
|
@@ -132,7 +132,7 @@ module Sequel
|
|
132
132
|
def sqlite_version
|
133
133
|
return @sqlite_version if defined?(@sqlite_version)
|
134
134
|
@sqlite_version = begin
|
135
|
-
v =
|
135
|
+
v = fetch('SELECT sqlite_version()').single_value
|
136
136
|
[10000, 100, 1].zip(v.split('.')).inject(0){|a, m| a + m[0] * Integer(m[1])}
|
137
137
|
rescue
|
138
138
|
0
|
@@ -497,7 +497,6 @@ module Sequel
|
|
497
497
|
module DatasetMethods
|
498
498
|
include Dataset::Replace
|
499
499
|
|
500
|
-
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
|
501
500
|
CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
|
502
501
|
EMULATED_FUNCTION_MAP = {:char_length=>'length'.freeze}
|
503
502
|
EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}
|
@@ -517,6 +516,11 @@ module Sequel
|
|
517
516
|
HSTAR = "H*".freeze
|
518
517
|
DATE_OPEN = "date(".freeze
|
519
518
|
DATETIME_OPEN = "datetime(".freeze
|
519
|
+
ONLY_OFFSET = " LIMIT -1 OFFSET ".freeze
|
520
|
+
|
521
|
+
Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
|
522
|
+
Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 30803', %w'with insert into columns values'], ["else", %w'insert into columns values']])
|
523
|
+
Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
|
520
524
|
|
521
525
|
def cast_sql_append(sql, expr, type)
|
522
526
|
if type == Time or type == DateTime
|
@@ -604,6 +608,11 @@ module Sequel
|
|
604
608
|
end
|
605
609
|
end
|
606
610
|
|
611
|
+
# SQLite 3.8.3+ supports common table expressions.
|
612
|
+
def supports_cte?(type=:select)
|
613
|
+
db.sqlite_version >= 30803
|
614
|
+
end
|
615
|
+
|
607
616
|
# SQLite does not support INTERSECT ALL or EXCEPT ALL
|
608
617
|
def supports_intersect_except_all?
|
609
618
|
false
|
@@ -677,11 +686,12 @@ module Sequel
|
|
677
686
|
@db.integer_booleans ? '1' : "'t'"
|
678
687
|
end
|
679
688
|
|
680
|
-
# SQLite
|
681
|
-
|
682
|
-
|
689
|
+
# SQLite only supporting multiple rows in the VALUES clause
|
690
|
+
# starting in 3.7.11. On older versions, fallback to using a UNION.
|
691
|
+
def multi_insert_sql_strategy
|
692
|
+
db.sqlite_version >= 30711 ? :values : :union
|
683
693
|
end
|
684
|
-
|
694
|
+
|
685
695
|
# SQLite does not support FOR UPDATE, but silently ignore it
|
686
696
|
# instead of raising an error for compatibility with other
|
687
697
|
# databases.
|
@@ -689,6 +699,11 @@ module Sequel
|
|
689
699
|
super unless @opts[:lock] == :update
|
690
700
|
end
|
691
701
|
|
702
|
+
def select_only_offset_sql(sql)
|
703
|
+
sql << ONLY_OFFSET
|
704
|
+
literal_append(sql, @opts[:offset])
|
705
|
+
end
|
706
|
+
|
692
707
|
# SQLite supports quoted function names.
|
693
708
|
def supports_quoted_function_names?
|
694
709
|
true
|
@@ -18,6 +18,7 @@ module Sequel
|
|
18
18
|
# reversed in the subselect. Note that the order needs to be unambiguous
|
19
19
|
# to work correctly, and you must select all columns that you are ordering on.
|
20
20
|
def select_sql
|
21
|
+
return super if @opts[:sql]
|
21
22
|
return super unless o = @opts[:offset]
|
22
23
|
|
23
24
|
order = @opts[:order] || default_offset_order
|
@@ -26,7 +27,7 @@ module Sequel
|
|
26
27
|
end
|
27
28
|
|
28
29
|
ds = unlimited
|
29
|
-
row_count = @opts[:offset_total_count] || ds.clone(:append_sql=>'').count
|
30
|
+
row_count = @opts[:offset_total_count] || ds.clone(:append_sql=>'', :placeholder_literal_null=>true).count
|
30
31
|
dsa1 = dataset_alias(1)
|
31
32
|
|
32
33
|
if o.is_a?(Symbol) && @opts[:bind_vars] && (match = Sequel::Dataset::PreparedStatementMethods::PLACEHOLDER_RE.match(o.to_s))
|
@@ -57,6 +58,12 @@ module Sequel
|
|
57
58
|
sql
|
58
59
|
end
|
59
60
|
|
61
|
+
# This does not support offsets in correlated subqueries, as it requires a query to get
|
62
|
+
# a count that will be invalid if a correlated subquery is used.
|
63
|
+
def supports_offsets_in_correlated_subqueries?
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
60
67
|
private
|
61
68
|
|
62
69
|
# The default order to use for datasets with offsets, if no order is defined.
|
@@ -20,7 +20,7 @@ module Sequel
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
columns = clone(:append_sql=>'').columns
|
23
|
+
columns = clone(:append_sql=>'', :placeholder_literal_null=>true).columns
|
24
24
|
dsa1 = dataset_alias(1)
|
25
25
|
rn = row_number_column
|
26
26
|
sql = @opts[:append_sql] || ''
|
@@ -35,6 +35,12 @@ module Sequel
|
|
35
35
|
sql
|
36
36
|
end
|
37
37
|
|
38
|
+
# This does not support offsets in correlated subqueries, as it requires a query to get
|
39
|
+
# the columns that will be invalid if a correlated subquery is used.
|
40
|
+
def supports_offsets_in_correlated_subqueries?
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
38
44
|
private
|
39
45
|
|
40
46
|
# The default order to use for datasets with offsets, if no order is defined.
|
@@ -50,7 +56,7 @@ module Sequel
|
|
50
56
|
|
51
57
|
# Whether to use ROW_NUMBER to emulate offsets
|
52
58
|
def emulate_offset_with_row_number?
|
53
|
-
@opts[:offset]
|
59
|
+
@opts[:offset] && !@opts[:sql]
|
54
60
|
end
|
55
61
|
end
|
56
62
|
end
|
@@ -24,6 +24,9 @@ module Sequel::Database::SplitAlterTable
|
|
24
24
|
modified_columns << op[:name] unless modified_columns.include?(op[:name])
|
25
25
|
modified_columns << op[:new_name] unless modified_columns.include?(op[:new_name])
|
26
26
|
end
|
27
|
+
if split_alter_table_op?(op)
|
28
|
+
op_groups << []
|
29
|
+
end
|
27
30
|
op_groups.last << op
|
28
31
|
end
|
29
32
|
|
@@ -33,4 +36,9 @@ module Sequel::Database::SplitAlterTable
|
|
33
36
|
remove_cached_schema(name)
|
34
37
|
end
|
35
38
|
end
|
39
|
+
|
40
|
+
# Whether the given alter table op should start a new group.
|
41
|
+
def split_alter_table_op?(op)
|
42
|
+
false
|
43
|
+
end
|
36
44
|
end
|
data/lib/sequel/core.rb
CHANGED
@@ -219,6 +219,7 @@ module Sequel
|
|
219
219
|
COLUMN_REF_RE1 = /\A((?:(?!__).)+)__((?:(?!___).)+)___(.+)\z/.freeze
|
220
220
|
COLUMN_REF_RE2 = /\A((?:(?!___).)+)___(.+)\z/.freeze
|
221
221
|
COLUMN_REF_RE3 = /\A((?:(?!__).)+)__(.+)\z/.freeze
|
222
|
+
SPLIT_SYMBOL_CACHE = {}
|
222
223
|
|
223
224
|
# Splits the symbol into three parts. Each part will
|
224
225
|
# either be a string or nil.
|
@@ -226,16 +227,20 @@ module Sequel
|
|
226
227
|
# For columns, these parts are the table, column, and alias.
|
227
228
|
# For tables, these parts are the schema, table, and alias.
|
228
229
|
def self.split_symbol(sym)
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
230
|
+
unless v = Sequel.synchronize{SPLIT_SYMBOL_CACHE[sym]}
|
231
|
+
v = case s = sym.to_s
|
232
|
+
when COLUMN_REF_RE1
|
233
|
+
[$1.freeze, $2.freeze, $3.freeze].freeze
|
234
|
+
when COLUMN_REF_RE2
|
235
|
+
[nil, $1.freeze, $2.freeze].freeze
|
236
|
+
when COLUMN_REF_RE3
|
237
|
+
[$1.freeze, $2.freeze, nil].freeze
|
238
|
+
else
|
239
|
+
[nil, s.freeze, nil].freeze
|
240
|
+
end
|
241
|
+
Sequel.synchronize{SPLIT_SYMBOL_CACHE[sym] = v}
|
238
242
|
end
|
243
|
+
v
|
239
244
|
end
|
240
245
|
|
241
246
|
# Converts the given +string+ into a +Date+ object.
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -131,6 +131,7 @@ module Sequel
|
|
131
131
|
@dataset_class = dataset_class_default
|
132
132
|
@cache_schema = typecast_value_boolean(@opts.fetch(:cache_schema, true))
|
133
133
|
@dataset_modules = []
|
134
|
+
@symbol_literal_cache = {}
|
134
135
|
@schema_type_classes = SCHEMA_TYPE_CLASSES.dup
|
135
136
|
self.sql_log_level = @opts[:sql_log_level] ? @opts[:sql_log_level].to_sym : :info
|
136
137
|
@pool = ConnectionPool.get_pool(self, @opts)
|
@@ -235,6 +236,17 @@ module Sequel
|
|
235
236
|
schema_utility_dataset.literal(v)
|
236
237
|
end
|
237
238
|
|
239
|
+
# Return the literalized version of the symbol if cached, or
|
240
|
+
# nil if it is not cached.
|
241
|
+
def literal_symbol(sym)
|
242
|
+
Sequel.synchronize{@symbol_literal_cache[sym]}
|
243
|
+
end
|
244
|
+
|
245
|
+
# Set the cached value of the literal symbol.
|
246
|
+
def literal_symbol_set(sym, lit)
|
247
|
+
Sequel.synchronize{@symbol_literal_cache[sym] = lit}
|
248
|
+
end
|
249
|
+
|
238
250
|
# Synchronize access to the prepared statements cache.
|
239
251
|
def prepared_statement(name)
|
240
252
|
Sequel.synchronize{prepared_statements[name]}
|
@@ -279,7 +279,10 @@ module Sequel
|
|
279
279
|
|
280
280
|
# Remove the cached schema for the given schema name
|
281
281
|
def remove_cached_schema(table)
|
282
|
-
|
282
|
+
if @schemas
|
283
|
+
k = quote_schema_table(table)
|
284
|
+
Sequel.synchronize{@schemas.delete(k)}
|
285
|
+
end
|
283
286
|
end
|
284
287
|
|
285
288
|
# Match the database's column type to a ruby type via a
|
@@ -332,12 +332,13 @@ module Sequel
|
|
332
332
|
# DB.drop_view(:cheap_items)
|
333
333
|
# DB.drop_view(:cheap_items, :pricey_items)
|
334
334
|
# DB.drop_view(:cheap_items, :pricey_items, :cascade=>true)
|
335
|
+
# DB.drop_view(:cheap_items, :pricey_items, :if_exists=>true)
|
335
336
|
#
|
336
337
|
# Options:
|
337
338
|
# :cascade :: Also drop objects depending on this view.
|
339
|
+
# :if_exists :: Do not raise an error if the view does not exist.
|
338
340
|
#
|
339
341
|
# PostgreSQL specific options:
|
340
|
-
# :if_exists :: Do not raise an error if the view does not exist.
|
341
342
|
# :materialized :: Drop a materialized view.
|
342
343
|
def drop_view(*names)
|
343
344
|
options = names.last.is_a?(Hash) ? names.pop : {}
|
@@ -721,7 +722,7 @@ module Sequel
|
|
721
722
|
|
722
723
|
# SQL DDL statement to drop a view with the given name.
|
723
724
|
def drop_view_sql(name, options)
|
724
|
-
"DROP VIEW #{quote_schema_table(name)}#{' CASCADE' if options[:cascade]}"
|
725
|
+
"DROP VIEW#{' IF EXISTS' if options[:if_exists]} #{quote_schema_table(name)}#{' CASCADE' if options[:cascade]}"
|
725
726
|
end
|
726
727
|
|
727
728
|
# Proxy the filter_expr call to the dataset, used for creating constraints.
|
@@ -36,6 +36,8 @@ module Sequel
|
|
36
36
|
#
|
37
37
|
# The following general options are respected:
|
38
38
|
#
|
39
|
+
# :auto_savepoint :: Automatically use a savepoint for Database#transaction calls
|
40
|
+
# inside this transaction block.
|
39
41
|
# :isolation :: The transaction isolation level to use for this transaction,
|
40
42
|
# should be :uncommitted, :committed, :repeatable, or :serializable,
|
41
43
|
# used if given and the database/adapter supports customizable
|
@@ -59,7 +61,8 @@ module Sequel
|
|
59
61
|
# :savepoint :: Whether to create a new savepoint for this transaction,
|
60
62
|
# only respected if the database/adapter supports savepoints. By
|
61
63
|
# default Sequel will reuse an existing transaction, so if you want to
|
62
|
-
# use a savepoint you must use this option.
|
64
|
+
# use a savepoint you must use this option. If the surrounding transaction
|
65
|
+
# uses :auto_savepoint, you can set this to false to not use a savepoint.
|
63
66
|
#
|
64
67
|
# PostgreSQL specific options:
|
65
68
|
#
|
@@ -88,9 +91,14 @@ module Sequel
|
|
88
91
|
if opts[:retrying]
|
89
92
|
raise Sequel::Error, "cannot set :retry_on options if you are already inside a transaction"
|
90
93
|
end
|
91
|
-
|
94
|
+
if opts[:savepoint] != false && (stack = _trans(conn)[:savepoints]) && stack.last
|
95
|
+
_transaction(conn, opts.merge(:savepoint=>true), &block)
|
96
|
+
else
|
97
|
+
return yield(conn)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
_transaction(conn, opts, &block)
|
92
101
|
end
|
93
|
-
_transaction(conn, opts, &block)
|
94
102
|
end
|
95
103
|
end
|
96
104
|
end
|
@@ -151,18 +159,24 @@ module Sequel
|
|
151
159
|
|
152
160
|
# Add the current thread to the list of active transactions
|
153
161
|
def add_transaction(conn, opts)
|
162
|
+
hash = {}
|
163
|
+
|
154
164
|
if supports_savepoints?
|
155
|
-
|
165
|
+
if _trans(conn)
|
166
|
+
hash = nil
|
167
|
+
_trans(conn)[:savepoints].push(opts[:auto_savepoint])
|
168
|
+
else
|
169
|
+
hash[:savepoints] = [opts[:auto_savepoint]]
|
156
170
|
if (prep = opts[:prepare]) && supports_prepared_transactions?
|
157
|
-
|
158
|
-
else
|
159
|
-
Sequel.synchronize{@transactions[conn] = {:savepoint_level=>0}}
|
171
|
+
hash[:prepare] = prep
|
160
172
|
end
|
161
173
|
end
|
162
174
|
elsif (prep = opts[:prepare]) && supports_prepared_transactions?
|
163
|
-
|
164
|
-
|
165
|
-
|
175
|
+
hash[:prepare] = prep
|
176
|
+
end
|
177
|
+
|
178
|
+
if hash
|
179
|
+
Sequel.synchronize{@transactions[conn] = hash}
|
166
180
|
end
|
167
181
|
end
|
168
182
|
|
@@ -199,13 +213,12 @@ module Sequel
|
|
199
213
|
# Start a new database transaction or a new savepoint on the given connection.
|
200
214
|
def begin_transaction(conn, opts=OPTS)
|
201
215
|
if supports_savepoints?
|
202
|
-
|
203
|
-
if
|
204
|
-
log_connection_execute(conn, begin_savepoint_sql(depth))
|
216
|
+
depth = savepoint_level(conn)
|
217
|
+
if depth > 1
|
218
|
+
log_connection_execute(conn, begin_savepoint_sql(depth-1))
|
205
219
|
else
|
206
220
|
begin_new_transaction(conn, opts)
|
207
221
|
end
|
208
|
-
th[:savepoint_level] += 1
|
209
222
|
else
|
210
223
|
begin_new_transaction(conn, opts)
|
211
224
|
end
|
@@ -243,7 +256,7 @@ module Sequel
|
|
243
256
|
# Commit the active transaction on the connection
|
244
257
|
def commit_transaction(conn, opts=OPTS)
|
245
258
|
if supports_savepoints?
|
246
|
-
depth =
|
259
|
+
depth = savepoint_level(conn)
|
247
260
|
log_connection_execute(conn, depth > 1 ? commit_savepoint_sql(depth-1) : commit_transaction_sql)
|
248
261
|
else
|
249
262
|
log_connection_execute(conn, commit_transaction_sql)
|
@@ -263,7 +276,7 @@ module Sequel
|
|
263
276
|
|
264
277
|
# Remove the current thread from the list of active transactions
|
265
278
|
def remove_transaction(conn, committed)
|
266
|
-
if
|
279
|
+
if transaction_finished?(conn)
|
267
280
|
begin
|
268
281
|
if committed
|
269
282
|
after_transaction_commit(conn)
|
@@ -284,7 +297,7 @@ module Sequel
|
|
284
297
|
# Rollback the active transaction on the connection
|
285
298
|
def rollback_transaction(conn, opts=OPTS)
|
286
299
|
if supports_savepoints?
|
287
|
-
depth =
|
300
|
+
depth = savepoint_level(conn)
|
288
301
|
log_connection_execute(conn, depth > 1 ? rollback_savepoint_sql(depth-1) : rollback_transaction_sql)
|
289
302
|
else
|
290
303
|
log_connection_execute(conn, rollback_transaction_sql)
|
@@ -308,6 +321,11 @@ module Sequel
|
|
308
321
|
"SET TRANSACTION ISOLATION LEVEL #{TRANSACTION_ISOLATION_LEVELS[level]}"
|
309
322
|
end
|
310
323
|
|
324
|
+
# Current savepoint level.
|
325
|
+
def savepoint_level(conn)
|
326
|
+
_trans(conn)[:savepoints].length
|
327
|
+
end
|
328
|
+
|
311
329
|
# Raise a database error unless the exception is an Rollback.
|
312
330
|
def transaction_error(e, opts=OPTS)
|
313
331
|
if e.is_a?(Rollback)
|
@@ -316,5 +334,17 @@ module Sequel
|
|
316
334
|
raise_error(e, opts.merge(:classes=>database_error_classes))
|
317
335
|
end
|
318
336
|
end
|
337
|
+
|
338
|
+
# Finish a subtransaction. If savepoints are supported, pops the current
|
339
|
+
# tansaction off the savepoint stack.
|
340
|
+
def transaction_finished?(conn)
|
341
|
+
if supports_savepoints?
|
342
|
+
stack = _trans(conn)[:savepoints]
|
343
|
+
stack.pop
|
344
|
+
stack.empty?
|
345
|
+
else
|
346
|
+
true
|
347
|
+
end
|
348
|
+
end
|
319
349
|
end
|
320
350
|
end
|