sequel 5.15.0 → 5.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +28 -0
- data/bin/sequel +4 -0
- data/doc/opening_databases.rdoc +1 -1
- data/doc/release_notes/5.16.0.txt +110 -0
- data/doc/transactions.rdoc +48 -0
- data/lib/sequel/adapters/jdbc/transactions.rb +14 -28
- data/lib/sequel/adapters/mysql.rb +2 -6
- data/lib/sequel/adapters/shared/sqlite.rb +16 -2
- data/lib/sequel/database/transactions.rb +66 -13
- data/lib/sequel/dataset/sql.rb +12 -1
- data/lib/sequel/extensions/migration.rb +4 -3
- data/lib/sequel/model/associations.rb +15 -3
- data/lib/sequel/model/base.rb +5 -3
- data/lib/sequel/plugins/class_table_inheritance.rb +6 -4
- data/lib/sequel/plugins/nested_attributes.rb +4 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/bin_spec.rb +7 -0
- data/spec/core/database_spec.rb +98 -1
- data/spec/core/expression_filters_spec.rb +13 -1
- data/spec/extensions/class_table_inheritance_spec.rb +45 -1
- data/spec/extensions/migration_spec.rb +15 -4
- data/spec/extensions/nested_attributes_spec.rb +40 -0
- data/spec/extensions/pg_array_associations_spec.rb +8 -8
- data/spec/extensions/pg_array_ops_spec.rb +5 -5
- data/spec/extensions/pg_hstore_ops_spec.rb +9 -9
- data/spec/extensions/pg_row_ops_spec.rb +2 -2
- data/spec/extensions/sql_expr_spec.rb +1 -1
- data/spec/extensions/string_agg_spec.rb +1 -1
- data/spec/integration/dataset_test.rb +1 -1
- data/spec/integration/transaction_test.rb +199 -0
- data/spec/model/associations_spec.rb +31 -0
- data/spec/model/base_spec.rb +49 -0
- metadata +5 -4
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -717,8 +717,19 @@ module Sequel
|
|
717
717
|
|
718
718
|
# Append literalization of subscripts (SQL array accesses) to SQL string.
|
719
719
|
def subscript_sql_append(sql, s)
|
720
|
+
case s.expression
|
721
|
+
when Symbol, SQL::Subscript, SQL::Identifier, SQL::QualifiedIdentifier
|
722
|
+
# nothing
|
723
|
+
else
|
724
|
+
wrap_expression = true
|
725
|
+
sql << '('
|
726
|
+
end
|
720
727
|
literal_append(sql, s.expression)
|
721
|
-
|
728
|
+
if wrap_expression
|
729
|
+
sql << ')['
|
730
|
+
else
|
731
|
+
sql << '['
|
732
|
+
end
|
722
733
|
sub = s.sub
|
723
734
|
if sub.length == 1 && (range = sub.first).is_a?(Range)
|
724
735
|
literal_append(sql, range.begin)
|
@@ -389,7 +389,8 @@ module Sequel
|
|
389
389
|
# using the :table and :column options.
|
390
390
|
# :relative :: Run the given number of migrations, with a positive number being migrations to migrate
|
391
391
|
# up, and a negative number being migrations to migrate down (IntegerMigrator only).
|
392
|
-
# :table :: The table containing the schema version (default: :schema_info
|
392
|
+
# :table :: The table containing the schema version (default: :schema_info for integer migrations and
|
393
|
+
# :schema_migrations for timestamped migrations).
|
393
394
|
# :target :: The target version to which to migrate. If not given, migrates to the maximum version.
|
394
395
|
#
|
395
396
|
# Examples:
|
@@ -405,6 +406,7 @@ module Sequel
|
|
405
406
|
# if the version number is greater than 20000101, otherwise uses the IntegerMigrator.
|
406
407
|
def self.migrator_class(directory)
|
407
408
|
if self.equal?(Migrator)
|
409
|
+
raise(Error, "Must supply a valid migration path") unless File.directory?(directory)
|
408
410
|
Dir.new(directory).each do |file|
|
409
411
|
next unless MIGRATION_FILE_PATTERN.match(file)
|
410
412
|
return TimestampMigrator if file.split('_', 2).first.to_i > 20000101
|
@@ -519,7 +521,6 @@ module Sequel
|
|
519
521
|
raise(Error, "No current version available") unless current
|
520
522
|
|
521
523
|
latest_version = latest_migration_version
|
522
|
-
|
523
524
|
@target = if opts[:target]
|
524
525
|
opts[:target]
|
525
526
|
elsif opts[:relative]
|
@@ -528,7 +529,7 @@ module Sequel
|
|
528
529
|
latest_version
|
529
530
|
end
|
530
531
|
|
531
|
-
raise(Error, "No target version available, probably because no migration files found or filenames don't follow the migration filename convention") unless target
|
532
|
+
raise(Error, "No target and/or latest version available, probably because no migration files found or filenames don't follow the migration filename convention") unless target && latest_version
|
532
533
|
|
533
534
|
if @target > latest_version
|
534
535
|
@target = latest_version
|
@@ -2597,13 +2597,25 @@ module Sequel
|
|
2597
2597
|
# Set the given object as the associated object for the given *_to_one association reflection
|
2598
2598
|
def _set_associated_object(opts, o)
|
2599
2599
|
a = associations[opts[:name]]
|
2600
|
-
|
2600
|
+
reciprocal = opts.reciprocal
|
2601
|
+
if set_associated_object_if_same?
|
2602
|
+
if reciprocal
|
2603
|
+
remove_reciprocal = a && (a != o || a.associations[reciprocal] != self)
|
2604
|
+
add_reciprocal = o && o.associations[reciprocal] != self
|
2605
|
+
end
|
2606
|
+
else
|
2607
|
+
return if a && a == o
|
2608
|
+
if reciprocal
|
2609
|
+
remove_reciprocal = a
|
2610
|
+
add_reciprocal = o
|
2611
|
+
end
|
2612
|
+
end
|
2601
2613
|
run_association_callbacks(opts, :before_set, o)
|
2602
|
-
remove_reciprocal_object(opts, a) if
|
2614
|
+
remove_reciprocal_object(opts, a) if remove_reciprocal
|
2603
2615
|
# Allow calling private _setter method
|
2604
2616
|
send(opts[:_setter_method], o)
|
2605
2617
|
associations[opts[:name]] = o
|
2606
|
-
add_reciprocal_object(opts, o) if
|
2618
|
+
add_reciprocal_object(opts, o) if add_reciprocal
|
2607
2619
|
run_association_callbacks(opts, :after_set, o)
|
2608
2620
|
o
|
2609
2621
|
end
|
data/lib/sequel/model/base.rb
CHANGED
@@ -765,9 +765,11 @@ module Sequel
|
|
765
765
|
|
766
766
|
# Create a column accessor for a column with a method name that is hard to use in ruby code.
|
767
767
|
def def_bad_column_accessor(column)
|
768
|
+
im = instance_methods
|
768
769
|
overridable_methods_module.module_eval do
|
769
|
-
|
770
|
-
define_method(
|
770
|
+
meth = :"#{column}="
|
771
|
+
define_method(column){self[column]} unless im.include?(column)
|
772
|
+
define_method(meth){|v| self[column] = v} unless im.include?(meth)
|
771
773
|
end
|
772
774
|
end
|
773
775
|
|
@@ -779,7 +781,7 @@ module Sequel
|
|
779
781
|
bad_columns.each{|x| def_bad_column_accessor(x)}
|
780
782
|
im = instance_methods
|
781
783
|
columns.each do |column|
|
782
|
-
meth = "#{column}="
|
784
|
+
meth = :"#{column}="
|
783
785
|
overridable_methods_module.module_eval("def #{column}; self[:#{column}] end", __FILE__, __LINE__) unless im.include?(column)
|
784
786
|
overridable_methods_module.module_eval("def #{meth}(v); self[:#{column}] = v end", __FILE__, __LINE__) unless im.include?(meth)
|
785
787
|
end
|
@@ -98,7 +98,7 @@ module Sequel
|
|
98
98
|
#
|
99
99
|
# a = Executive.first
|
100
100
|
# a.values # {:id=>1, name=>'S', :kind=>'Executive', :num_staff=>4, :num_managers=>2}
|
101
|
-
#
|
101
|
+
#
|
102
102
|
# Note that when loading from a subclass, because the subclass dataset uses a subquery
|
103
103
|
# that by default uses the same alias at the primary table, any qualified identifiers
|
104
104
|
# should reference the subquery alias (and qualified identifiers should not be needed
|
@@ -281,14 +281,14 @@ module Sequel
|
|
281
281
|
columns = nil
|
282
282
|
if (n = subclass.name) && !n.empty?
|
283
283
|
if table = cti_table_map[n.to_sym]
|
284
|
-
columns = db.
|
284
|
+
columns = db.schema(table).map(&:first)
|
285
285
|
else
|
286
|
-
table = if cti_qualify_tables && (schema = dataset.schema_and_table(
|
286
|
+
table = if cti_qualify_tables && (schema = dataset.schema_and_table(cti_table_name).first)
|
287
287
|
SQL::QualifiedIdentifier.new(schema, subclass.implicit_table_name)
|
288
288
|
else
|
289
289
|
subclass.implicit_table_name
|
290
290
|
end
|
291
|
-
columns = check_non_connection_error(false){db.
|
291
|
+
columns = check_non_connection_error(false){db.schema(table) && db.schema(table).map(&:first)}
|
292
292
|
table = nil if !columns || columns.empty?
|
293
293
|
end
|
294
294
|
end
|
@@ -301,6 +301,7 @@ module Sequel
|
|
301
301
|
if cti_tables.length == 1
|
302
302
|
ds = ds.select(*self.columns.map{|cc| Sequel.qualify(cti_table_name, Sequel.identifier(cc))})
|
303
303
|
end
|
304
|
+
ds.send(:columns=, self.columns)
|
304
305
|
cols = (columns - [pk]) - cti_ignore_subclass_columns
|
305
306
|
dup_cols = cols & ds.columns
|
306
307
|
unless dup_cols.empty?
|
@@ -310,6 +311,7 @@ module Sequel
|
|
310
311
|
@sti_dataset = ds = ds.join(table, pk=>pk).select_append(*sel_app)
|
311
312
|
|
312
313
|
ds = ds.from_self(:alias=>@cti_alias)
|
314
|
+
ds.send(:columns=, self.columns + cols)
|
313
315
|
|
314
316
|
set_dataset(ds)
|
315
317
|
set_columns(self.columns)
|
@@ -191,7 +191,10 @@ module Sequel
|
|
191
191
|
if reflection[:type] == :many_to_one
|
192
192
|
before_save_hook{public_send(reflection[:setter_method], obj.save(:validate=>false))}
|
193
193
|
else
|
194
|
-
after_save_hook
|
194
|
+
after_save_hook do
|
195
|
+
obj.skip_validation_on_next_save!
|
196
|
+
public_send(reflection[:setter_method], obj)
|
197
|
+
end
|
195
198
|
end
|
196
199
|
end
|
197
200
|
add_reciprocal_object(reflection, obj)
|
data/lib/sequel/version.rb
CHANGED
@@ -6,7 +6,7 @@ module Sequel
|
|
6
6
|
|
7
7
|
# The minor version of Sequel. Bumped for every non-patch level
|
8
8
|
# release, generally around once a month.
|
9
|
-
MINOR =
|
9
|
+
MINOR = 16
|
10
10
|
|
11
11
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
12
12
|
# releases that fix regressions from previous versions.
|
data/spec/bin_spec.rb
CHANGED
@@ -99,6 +99,13 @@ END
|
|
99
99
|
DB2.foreign_key_list(:b).must_equal [{:columns=>[:a], :table=>:a, :key=>nil, :on_update=>:no_action, :on_delete=>:no_action}]
|
100
100
|
end
|
101
101
|
|
102
|
+
it "-C should convert integer to bigint when copying from SQLite to other databases" do
|
103
|
+
DB.create_table(:a) do
|
104
|
+
Integer :id
|
105
|
+
end
|
106
|
+
bin(:args=>'-EC', :post=>"mock://postgres").must_include 'CREATE TABLE "a" ("id" bigint)'
|
107
|
+
end
|
108
|
+
|
102
109
|
it "-d and -D should dump generic and specific migrations" do
|
103
110
|
DB.create_table(:a) do
|
104
111
|
primary_key :a
|
data/spec/core/database_spec.rb
CHANGED
@@ -1072,6 +1072,22 @@ DatabaseTransactionSpecs = shared_description do
|
|
1072
1072
|
proc{@db.transaction(:prepare=>'XYZ'){@db.after_rollback{@db.execute('foo')}}}.must_raise(Sequel::Error)
|
1073
1073
|
@db.sqls.must_equal ['BEGIN', 'ROLLBACK']
|
1074
1074
|
end
|
1075
|
+
|
1076
|
+
it "should have rollback_on_exit cause the transaction to rollback on exit" do
|
1077
|
+
@db.transaction{@db.rollback_on_exit}.must_be_nil
|
1078
|
+
@db.sqls.must_equal ['BEGIN', 'ROLLBACK']
|
1079
|
+
catch(:foo){@db.transaction{@db.rollback_on_exit; throw :foo}}
|
1080
|
+
@db.sqls.must_equal ['BEGIN', 'ROLLBACK']
|
1081
|
+
lambda{@db.transaction{@db.rollback_on_exit; return true}}.call
|
1082
|
+
@db.sqls.must_equal ['BEGIN', 'ROLLBACK']
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
it "should have rollback_on_exit with :cancel option will cause the transaction to commit on exit" do
|
1086
|
+
@db.transaction{@db.rollback_on_exit(:cancel=>true)}.must_be_nil
|
1087
|
+
@db.sqls.must_equal ['BEGIN', 'COMMIT']
|
1088
|
+
@db.transaction{@db.rollback_on_exit; @db.rollback_on_exit(:cancel=>true)}.must_be_nil
|
1089
|
+
@db.sqls.must_equal ['BEGIN', 'COMMIT']
|
1090
|
+
end
|
1075
1091
|
end
|
1076
1092
|
|
1077
1093
|
describe "Database#transaction with savepoint support" do
|
@@ -1154,6 +1170,87 @@ describe "Database#transaction with savepoint support" do
|
|
1154
1170
|
@db.transaction(:savepoint=>:only){}
|
1155
1171
|
@db.sqls.must_equal []
|
1156
1172
|
end
|
1173
|
+
|
1174
|
+
it "should have rollback_on_exit with :savepoint option inside transaction cause the transaction to rollback on exit" do
|
1175
|
+
@db.transaction{@db.rollback_on_exit(:savepoint=>true)}.must_be_nil
|
1176
|
+
@db.sqls.must_equal ['BEGIN', 'ROLLBACK']
|
1177
|
+
catch(:foo){@db.transaction{@db.rollback_on_exit(:savepoint=>true); throw :foo}}
|
1178
|
+
@db.sqls.must_equal ['BEGIN', 'ROLLBACK']
|
1179
|
+
lambda{@db.transaction{@db.rollback_on_exit(:savepoint=>true); return true}}.call
|
1180
|
+
@db.sqls.must_equal ['BEGIN', 'ROLLBACK']
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
it "should have rollback_on_exit with :savepoint option inside savepoint cause the savepoint to rollback on exit" do
|
1184
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true)}}.must_be_nil
|
1185
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','ROLLBACK TO SAVEPOINT autopoint_1', 'COMMIT']
|
1186
|
+
catch(:foo){@db.transaction{@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true); throw :foo}}}
|
1187
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','ROLLBACK TO SAVEPOINT autopoint_1', 'COMMIT']
|
1188
|
+
lambda{@db.transaction{@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true); return true}}}.call
|
1189
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','ROLLBACK TO SAVEPOINT autopoint_1', 'COMMIT']
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
it "should have rollback_on_exit with :savepoint option inside nested savepoint cause the current savepoint to rollback on exit" do
|
1193
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true)}}}.must_be_nil
|
1194
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
1195
|
+
catch(:foo){@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true); throw :foo}}}}
|
1196
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
1197
|
+
lambda{@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true); return true}}}}.call
|
1198
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
it "should have rollback_on_exit with :savepoint=>1 option inside nested savepoint cause the current savepoint to rollback on exit" do
|
1202
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>1)}}}.must_be_nil
|
1203
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
1204
|
+
catch(:foo){@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>1); throw :foo}}}}
|
1205
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
1206
|
+
lambda{@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>1); return true}}}}.call
|
1207
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
it "should have rollback_on_exit with :savepoint=>2 option inside nested savepoint cause the current and next savepoint to rollback on exit" do
|
1211
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>2)}}}.must_be_nil
|
1212
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'ROLLBACK TO SAVEPOINT autopoint_1', 'COMMIT']
|
1213
|
+
catch(:foo){@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>2); throw :foo}}}}
|
1214
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'ROLLBACK TO SAVEPOINT autopoint_1', 'COMMIT']
|
1215
|
+
lambda{@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>2); return true}}}}.call
|
1216
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'ROLLBACK TO SAVEPOINT autopoint_1', 'COMMIT']
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
it "should have rollback_on_exit with :savepoint=>3 option inside nested savepoint cause the three enclosing savepoints/transaction to rollback on exit" do
|
1220
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>3)}}}.must_be_nil
|
1221
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'ROLLBACK TO SAVEPOINT autopoint_1', 'ROLLBACK']
|
1222
|
+
catch(:foo){@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>3); throw :foo}}}}
|
1223
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'ROLLBACK TO SAVEPOINT autopoint_1', 'ROLLBACK']
|
1224
|
+
lambda{@db.transaction{@db.transaction(:savepoint=>true){@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>3); return true}}}}.call
|
1225
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'ROLLBACK TO SAVEPOINT autopoint_1', 'ROLLBACK']
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
it "should have rollback_on_exit with :savepoint and :cancel option will cause the transaction to commit on exit" do
|
1229
|
+
@db.transaction{@db.rollback_on_exit(:savepoint=>true, :cancel=>true)}.must_be_nil
|
1230
|
+
@db.sqls.must_equal ['BEGIN', 'COMMIT']
|
1231
|
+
@db.transaction{@db.rollback_on_exit(:savepoint=>true); @db.rollback_on_exit(:savepoint=>true, :cancel=>true)}.must_be_nil
|
1232
|
+
@db.sqls.must_equal ['BEGIN', 'COMMIT']
|
1233
|
+
end
|
1234
|
+
|
1235
|
+
it "should have rollback_on_exit with :savepoint option called at different levels work correctly" do
|
1236
|
+
@db.transaction{@db.rollback_on_exit(:savepoint=>true); @db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true)}}.must_be_nil
|
1237
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1','ROLLBACK TO SAVEPOINT autopoint_1', 'ROLLBACK']
|
1238
|
+
|
1239
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true); @db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true)}}}.must_be_nil
|
1240
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'SAVEPOINT autopoint_2','ROLLBACK TO SAVEPOINT autopoint_2', 'ROLLBACK TO SAVEPOINT autopoint_1', 'COMMIT']
|
1241
|
+
|
1242
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true); @db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true, :cancel=>true)}}}.must_be_nil
|
1243
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'SAVEPOINT autopoint_2','RELEASE SAVEPOINT autopoint_2', 'ROLLBACK TO SAVEPOINT autopoint_1', 'COMMIT']
|
1244
|
+
|
1245
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true); @db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>2, :cancel=>true)}}}.must_be_nil
|
1246
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'SAVEPOINT autopoint_2','RELEASE SAVEPOINT autopoint_2', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
1247
|
+
|
1248
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true); @db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>3, :cancel=>true)}}}.must_be_nil
|
1249
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'SAVEPOINT autopoint_2','RELEASE SAVEPOINT autopoint_2', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
1250
|
+
|
1251
|
+
@db.transaction{@db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>true); @db.transaction(:savepoint=>true){@db.rollback_on_exit(:savepoint=>4, :cancel=>true)}}}.must_be_nil
|
1252
|
+
@db.sqls.must_equal ['BEGIN', 'SAVEPOINT autopoint_1', 'SAVEPOINT autopoint_2','RELEASE SAVEPOINT autopoint_2', 'RELEASE SAVEPOINT autopoint_1', 'COMMIT']
|
1253
|
+
end
|
1157
1254
|
end
|
1158
1255
|
|
1159
1256
|
describe "Database#transaction without savepoint support" do
|
@@ -1167,7 +1264,7 @@ describe "Database#transaction without savepoint support" do
|
|
1167
1264
|
@db.sqls.must_equal ['BEGIN', 'COMMIT']
|
1168
1265
|
end
|
1169
1266
|
|
1170
|
-
it "should automatically use a savepoint if :rollback=>:always given inside a transaction" do
|
1267
|
+
it "should not automatically use a savepoint if :rollback=>:always given inside a transaction" do
|
1171
1268
|
proc do
|
1172
1269
|
@db.transaction do
|
1173
1270
|
@db.transaction(:rollback=>:always) do
|
@@ -1271,7 +1271,7 @@ describe "Sequel::SQL::Wrapper" do
|
|
1271
1271
|
@ds.literal(s**1).must_equal "power(foo, 1)"
|
1272
1272
|
@ds.literal(s & true).must_equal "(foo AND 't')"
|
1273
1273
|
@ds.literal(s < 1).must_equal "(foo < 1)"
|
1274
|
-
@ds.literal(s.sql_subscript(1)).must_equal "foo[1]"
|
1274
|
+
@ds.literal(s.sql_subscript(1)).must_equal "(foo)[1]"
|
1275
1275
|
@ds.literal(s.like('a')).must_equal "(foo LIKE 'a' ESCAPE '\\')"
|
1276
1276
|
@ds.literal(s.as(:a)).must_equal "foo AS a"
|
1277
1277
|
@ds.literal(s.cast(Integer)).must_equal "CAST(foo AS integer)"
|
@@ -1302,6 +1302,18 @@ describe Sequel::SQL::Subscript do
|
|
1302
1302
|
s = @s[2]
|
1303
1303
|
@ds.literal(s).must_equal 'a[1][2]'
|
1304
1304
|
end
|
1305
|
+
|
1306
|
+
it "should not wrap identifiers in parentheses" do
|
1307
|
+
@ds.literal(Sequel::SQL::Subscript.new(:a, [1])).must_equal 'a[1]'
|
1308
|
+
@ds.literal(Sequel::SQL::Subscript.new(Sequel[:a], [1])).must_equal 'a[1]'
|
1309
|
+
@ds.literal(Sequel::SQL::Subscript.new(Sequel[:a][:b], [1])).must_equal 'a.b[1]'
|
1310
|
+
end
|
1311
|
+
|
1312
|
+
it "should wrap other expression types in parentheses" do
|
1313
|
+
@ds.literal(Sequel::SQL::Subscript.new(Sequel.function('a'), [1])).must_equal '(a())[1]'
|
1314
|
+
@ds.literal(Sequel::SQL::Subscript.new(Sequel.lit('a'), [1])).must_equal '(a)[1]'
|
1315
|
+
@ds.literal(Sequel::SQL::Subscript.new(Sequel.lit('a(?)', 2), [1])).must_equal '(a(2))[1]'
|
1316
|
+
end
|
1305
1317
|
end
|
1306
1318
|
|
1307
1319
|
describe Sequel::SQL::CaseExpression, "#with_merged_expression" do
|
@@ -574,6 +574,7 @@ describe "class_table_inheritance plugin with dataset defined with QualifiedIden
|
|
574
574
|
{Sequel[:hr][:employees]=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
|
575
575
|
Sequel[:hr][:managers]=>[[:id, {:type=>:integer}]],
|
576
576
|
Sequel[:hr][:staff]=>[[:id, {:type=>:integer}], [:manager_id, {:type=>:integer}]],
|
577
|
+
Sequel[:hr][:executives]=>[[:id, {:type=>:integer}], [:num_managers, {:type=>:integer}]],
|
577
578
|
}[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
|
578
579
|
end
|
579
580
|
@db.extend_datasets do
|
@@ -583,12 +584,13 @@ describe "class_table_inheritance plugin with dataset defined with QualifiedIden
|
|
583
584
|
[Sequel[:hr][:staff]]=>[:id, :manager_id],
|
584
585
|
[Sequel[:hr][:employees], Sequel[:hr][:managers]]=>[:id, :name, :kind],
|
585
586
|
[Sequel[:hr][:employees], Sequel[:hr][:staff]]=>[:id, :name, :kind, :manager_id],
|
587
|
+
[Sequel[:hr][:employees], Sequel[:hr][:managers], Sequel[:hr][:executives]]=>[:id, :name, :kind, :manager_id, :num_managers],
|
586
588
|
}[opts[:from] + (opts[:join] || []).map{|x| x.table}]
|
587
589
|
end
|
588
590
|
end
|
589
591
|
end
|
590
592
|
after do
|
591
|
-
[:Manager, :Staff, :Employee].each{|s| Object.send(:remove_const, s) if Object.const_defined?(s)}
|
593
|
+
[:Manager, :Staff, :Employee, :Executive].each{|s| Object.send(:remove_const, s) if Object.const_defined?(s)}
|
592
594
|
end
|
593
595
|
|
594
596
|
describe "with table_map used to qualify subclasses" do
|
@@ -675,10 +677,52 @@ describe "class_table_inheritance plugin with dataset defined with QualifiedIden
|
|
675
677
|
class ::Staff < ::Employee
|
676
678
|
many_to_one :manager
|
677
679
|
end
|
680
|
+
class ::Executive < ::Manager
|
681
|
+
end
|
678
682
|
|
679
683
|
Employee.dataset.sql.must_equal 'SELECT * FROM hr.employees'
|
680
684
|
Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN hr.managers ON (hr.managers.id = hr.employees.id)) AS employees'
|
681
685
|
Staff.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind, hr.staff.manager_id FROM hr.employees INNER JOIN hr.staff ON (hr.staff.id = hr.employees.id)) AS employees'
|
686
|
+
Executive.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind, hr.executives.num_managers FROM hr.employees INNER JOIN hr.managers ON (hr.managers.id = hr.employees.id) INNER JOIN hr.executives ON (hr.executives.id = hr.managers.id)) AS employees'
|
687
|
+
end
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
describe "class_table_inheritance plugin with schema_caching extension" do
|
692
|
+
before do
|
693
|
+
@db = Sequel.mock(:autoid=>proc{|sql| 1})
|
694
|
+
def @db.supports_schema_parsing?() true end
|
695
|
+
def @db.schema(table, opts={})
|
696
|
+
{:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
|
697
|
+
:managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer}] ],
|
698
|
+
:executives=>[[:id, {:type=>:integer}], [:num_managers, {:type=>:integer}]],
|
699
|
+
}[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
|
682
700
|
end
|
683
701
|
end
|
702
|
+
after do
|
703
|
+
[:Executive, :Manager, :Employee, :Staff].each{|s| Object.send(:remove_const, s) if Object.const_defined?(s)}
|
704
|
+
end
|
705
|
+
|
706
|
+
it "should not query for columns if the schema cache is present and a table_map is given" do
|
707
|
+
class ::Employee < Sequel::Model(@db)
|
708
|
+
plugin :class_table_inheritance, :table_map=>{:Staff=>:employees, :Manager=>:managers, :Executive=>:executives}
|
709
|
+
end
|
710
|
+
class ::Staff < Employee; end
|
711
|
+
class ::Manager < Employee; end
|
712
|
+
class ::Executive < Manager; end
|
713
|
+
Employee.columns.must_equal [:id, :name, :kind]
|
714
|
+
Staff.columns.must_equal [:id, :name, :kind]
|
715
|
+
Manager.columns.must_equal [:id, :name, :kind, :num_staff]
|
716
|
+
Executive.columns.must_equal [:id, :name, :kind, :num_staff, :num_managers]
|
717
|
+
@db.sqls.must_equal []
|
718
|
+
end
|
719
|
+
|
720
|
+
it "should not query for columns if the schema cache is present and no table_map is given" do
|
721
|
+
class ::Employee < Sequel::Model(@db)
|
722
|
+
plugin :class_table_inheritance
|
723
|
+
end
|
724
|
+
class ::Manager < Employee; end
|
725
|
+
class ::Executive < Manager; end
|
726
|
+
@db.sqls.must_equal []
|
727
|
+
end
|
684
728
|
end
|
@@ -237,6 +237,11 @@ describe "Sequel::Migrator.migrator_class" do
|
|
237
237
|
Sequel::IntegerMigrator.migrator_class("spec/files/timestamped_migrations").must_equal Sequel::IntegerMigrator
|
238
238
|
Sequel::TimestampMigrator.migrator_class("spec/files/integer_migrations").must_equal Sequel::TimestampMigrator
|
239
239
|
end
|
240
|
+
|
241
|
+
it "should raise an error if the migration folder does not exist" do
|
242
|
+
proc{Sequel::Migrator.apply(@db, "spec/files/nonexistant_migration_path")}.must_raise(Sequel::Migrator::Error)
|
243
|
+
end
|
244
|
+
|
240
245
|
end
|
241
246
|
|
242
247
|
describe "Sequel::IntegerMigrator" do
|
@@ -283,19 +288,19 @@ describe "Sequel::IntegerMigrator" do
|
|
283
288
|
after do
|
284
289
|
Object.send(:remove_const, "CreateSessions") if Object.const_defined?("CreateSessions")
|
285
290
|
end
|
286
|
-
|
287
|
-
it "should raise
|
291
|
+
|
292
|
+
it "should raise an error if there is a missing integer migration version" do
|
288
293
|
proc{Sequel::Migrator.apply(@db, "spec/files/missing_integer_migrations")}.must_raise(Sequel::Migrator::Error)
|
289
294
|
end
|
290
295
|
|
291
|
-
it "should not raise
|
296
|
+
it "should not raise an error if there is a missing integer migration version and allow_missing_migration_files is true" do
|
292
297
|
Sequel::Migrator.run(@db, "spec/files/missing_integer_migrations", :allow_missing_migration_files => true)
|
293
298
|
@db.sqls.last.must_equal "UPDATE schema_info SET version = 3"
|
294
299
|
Sequel::Migrator.run(@db, "spec/files/missing_integer_migrations", :allow_missing_migration_files => true, :target=>0)
|
295
300
|
@db.sqls.last.must_equal "UPDATE schema_info SET version = 0"
|
296
301
|
end
|
297
302
|
|
298
|
-
it "should raise
|
303
|
+
it "should raise an error if there is a duplicate integer migration version" do
|
299
304
|
proc{Sequel::Migrator.apply(@db, "spec/files/duplicate_integer_migrations")}.must_raise(Sequel::Migrator::Error)
|
300
305
|
end
|
301
306
|
|
@@ -307,6 +312,12 @@ describe "Sequel::IntegerMigrator" do
|
|
307
312
|
proc{Sequel::Migrator.apply(@db, "spec/files/double_migration")}.must_raise(Sequel::Migrator::Error)
|
308
313
|
end
|
309
314
|
|
315
|
+
it "should raise an error if the most recent migration can't be detected" do
|
316
|
+
# Have to specify a target version, otherwise an earlier check (inability
|
317
|
+
# to detect the target) would raise an error, falsely matching the check.
|
318
|
+
proc{Sequel::Migrator.apply(@db, "spec/files/empty_migration_folder", 2)}.must_raise(Sequel::Migrator::Error)
|
319
|
+
end
|
320
|
+
|
310
321
|
it "should add a column name if it doesn't already exist in the schema_info table" do
|
311
322
|
@db.create_table(:schema_info){Integer :v}
|
312
323
|
def @db.alter_table(*); end
|