sequel 5.15.0 → 5.16.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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