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.
@@ -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
- sql << '['
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
- return if a && a == o && !set_associated_object_if_same?
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 a
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 o
2618
+ add_reciprocal_object(opts, o) if add_reciprocal
2607
2619
  run_association_callbacks(opts, :after_set, o)
2608
2620
  o
2609
2621
  end
@@ -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
- define_method(column){self[column]}
770
- define_method("#{column}="){|v| self[column] = v}
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.from(table).columns
284
+ columns = db.schema(table).map(&:first)
285
285
  else
286
- table = if cti_qualify_tables && (schema = dataset.schema_and_table(table_name).first)
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.from(table).columns}
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{public_send(reflection[:setter_method], obj)}
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)
@@ -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 = 15
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.
@@ -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
@@ -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 and error if there is a missing integer migration version" do
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 and error if there is a missing integer migration version and allow_missing_migration_files is true" do
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 and error if there is a duplicate integer migration version" do
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