sequel 5.15.0 → 5.16.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.
- 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
|