sequel 3.25.0 → 3.26.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.
Files changed (53) hide show
  1. data/CHANGELOG +28 -0
  2. data/README.rdoc +3 -3
  3. data/Rakefile +17 -11
  4. data/doc/release_notes/3.26.0.txt +88 -0
  5. data/lib/sequel/adapters/ado.rb +10 -0
  6. data/lib/sequel/adapters/do.rb +12 -0
  7. data/lib/sequel/adapters/jdbc.rb +6 -6
  8. data/lib/sequel/adapters/mysql.rb +8 -2
  9. data/lib/sequel/adapters/mysql2.rb +5 -1
  10. data/lib/sequel/adapters/odbc.rb +10 -2
  11. data/lib/sequel/adapters/oracle.rb +5 -1
  12. data/lib/sequel/adapters/postgres.rb +10 -4
  13. data/lib/sequel/adapters/shared/access.rb +11 -0
  14. data/lib/sequel/adapters/shared/oracle.rb +0 -4
  15. data/lib/sequel/adapters/shared/postgres.rb +0 -12
  16. data/lib/sequel/adapters/tinytds.rb +9 -0
  17. data/lib/sequel/connection_pool.rb +1 -1
  18. data/lib/sequel/connection_pool/threaded.rb +3 -2
  19. data/lib/sequel/core.rb +1 -1
  20. data/lib/sequel/database/connecting.rb +3 -3
  21. data/lib/sequel/database/dataset.rb +1 -1
  22. data/lib/sequel/database/dataset_defaults.rb +1 -1
  23. data/lib/sequel/database/logging.rb +1 -1
  24. data/lib/sequel/database/misc.rb +23 -6
  25. data/lib/sequel/database/query.rb +16 -15
  26. data/lib/sequel/database/schema_methods.rb +21 -16
  27. data/lib/sequel/dataset/actions.rb +19 -16
  28. data/lib/sequel/dataset/features.rb +8 -2
  29. data/lib/sequel/dataset/graph.rb +1 -1
  30. data/lib/sequel/dataset/misc.rb +29 -9
  31. data/lib/sequel/dataset/mutation.rb +3 -3
  32. data/lib/sequel/dataset/prepared_statements.rb +11 -11
  33. data/lib/sequel/dataset/query.rb +28 -7
  34. data/lib/sequel/dataset/sql.rb +2 -2
  35. data/lib/sequel/extensions/migration.rb +1 -0
  36. data/lib/sequel/model.rb +5 -4
  37. data/lib/sequel/model/associations.rb +487 -328
  38. data/lib/sequel/model/base.rb +43 -26
  39. data/lib/sequel/model/exceptions.rb +2 -0
  40. data/lib/sequel/plugins/identity_map.rb +111 -4
  41. data/lib/sequel/plugins/sharding.rb +12 -20
  42. data/lib/sequel/plugins/xml_serializer.rb +2 -2
  43. data/lib/sequel/version.rb +1 -1
  44. data/spec/adapters/postgres_spec.rb +0 -6
  45. data/spec/core/connection_pool_spec.rb +6 -0
  46. data/spec/core/database_spec.rb +12 -0
  47. data/spec/core/schema_spec.rb +9 -2
  48. data/spec/extensions/identity_map_spec.rb +162 -0
  49. data/spec/extensions/many_through_many_spec.rb +3 -19
  50. data/spec/extensions/xml_serializer_spec.rb +4 -4
  51. data/spec/model/eager_loading_spec.rb +7 -21
  52. data/spec/model/record_spec.rb +23 -0
  53. metadata +36 -34
@@ -11,7 +11,7 @@ module Sequel
11
11
  # the Model's dataset with the method of the same name with the given arguments.
12
12
  module ClassMethods
13
13
  # Which columns should be the only columns allowed in a call to a mass assignment method (e.g. set)
14
- # (default: not set, so all columns not otherwise restricted).
14
+ # (default: not set, so all columns not otherwise restricted are allowed).
15
15
  attr_reader :allowed_columns
16
16
 
17
17
  # Array of modules that extend this model's dataset. Stored
@@ -390,8 +390,8 @@ module Sequel
390
390
  # setter methods (methods that end in =) that you want to be used during
391
391
  # mass assignment, they need to be listed here as well (without the =).
392
392
  #
393
- # It may be better to use a method such as +set_only+ instead of this in places where
394
- # only certain columns may be allowed.
393
+ # It may be better to use a method such as +set_only+ or +set_fields+ that lets you specify
394
+ # the allowed fields per call.
395
395
  #
396
396
  # Artist.set_allowed_columns(:name, :hometown)
397
397
  # Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
@@ -445,7 +445,8 @@ module Sequel
445
445
 
446
446
  # Sets the primary key for this model. You can use either a regular
447
447
  # or a composite primary key. To not use a primary key, set to nil
448
- # or use +no_primary_key+.
448
+ # or use +no_primary_key+. On most adapters, Sequel can automatically
449
+ # determine the primary key to use, so this method is not needed often.
449
450
  #
450
451
  # class Person < Sequel::Model
451
452
  # # regular key
@@ -469,10 +470,9 @@ module Sequel
469
470
  # If you have any virtual setter methods (methods that end in =) that you
470
471
  # want not to be used during mass assignment, they need to be listed here as well (without the =).
471
472
  #
472
- # It may be better to use a method such as +set_except+ instead of this in places where
473
- # certain columns are restricted. In general, it's better to have a whitelist approach
474
- # where you specify only what is allowed, as opposed to a blacklist approach that this
475
- # method uses, where everything is allowed other than what you restrict.
473
+ # It's generally a bad idea to rely on a blacklist approach for security. Using a whitelist
474
+ # approach such as set_allowed_columns or the instance level set_only or set_fields methods
475
+ # is usually a better choice. So use of this method is generally a bad idea.
476
476
  #
477
477
  # Artist.set_restricted_column(:records_sold)
478
478
  # Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
@@ -512,6 +512,9 @@ module Sequel
512
512
  # 7 days ago.
513
513
  #
514
514
  # Both the args given and the block are passed to <tt>Dataset#filter</tt>.
515
+ #
516
+ # This method creates dataset methods that do not accept arguments. To create
517
+ # dataset methods that accept arguments, you have to use def_dataset_method.
515
518
  def subset(name, *args, &block)
516
519
  def_dataset_method(name){filter(*args, &block)}
517
520
  end
@@ -527,6 +530,7 @@ module Sequel
527
530
  end
528
531
 
529
532
  # Allow the setting of the primary key(s) when using the mass assignment methods.
533
+ # Using this method can open up security issues, be very careful before using it.
530
534
  #
531
535
  # Artist.set(:id=>1) # Error
532
536
  # Artist.unrestrict_primary_key
@@ -777,7 +781,7 @@ module Sequel
777
781
 
778
782
  # Sets the value for the given column. If typecasting is enabled for
779
783
  # this object, typecast the value based on the column's type.
780
- # If this a a new record or the typecasted value isn't the same
784
+ # If this is a new record or the typecasted value isn't the same
781
785
  # as the current value for the column, mark the column as changed.
782
786
  #
783
787
  # a = Artist.new
@@ -885,7 +889,7 @@ module Sequel
885
889
 
886
890
  # Returns true when current instance exists, false otherwise.
887
891
  # Generally an object that isn't new will exist unless it has
888
- # been deleted.
892
+ # been deleted. Uses a database query to check for existence.
889
893
  #
890
894
  # Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
891
895
  # # => true
@@ -982,7 +986,7 @@ module Sequel
982
986
  end
983
987
 
984
988
  # Returns the primary key value identifying the model instance.
985
- # Raises an error if this model does not have a primary key.
989
+ # Raises an +Error+ if this model does not have a primary key.
986
990
  # If the model has a composite primary key, returns an array of values.
987
991
  #
988
992
  # Artist[1].pk # => 1
@@ -1032,16 +1036,16 @@ module Sequel
1032
1036
  # If it succeeds, it returns self.
1033
1037
  #
1034
1038
  # You can provide an optional list of columns to update, in which
1035
- # case it only updates those columns.
1039
+ # case it only updates those columns, or a options hash.
1036
1040
  #
1037
1041
  # Takes the following options:
1038
1042
  #
1039
- # * :changed - save all changed columns, instead of all columns or the columns given
1040
- # * :transaction - set to true or false to override the current
1041
- # use_transactions setting
1042
- # * :validate - set to false to skip validation
1043
- # * :raise_on_failure - set to true or false to override the current
1044
- # raise_on_save_failure setting
1043
+ # :changed :: save all changed columns, instead of all columns or the columns given
1044
+ # :transaction :: set to true or false to override the current
1045
+ # +use_transactions+ setting
1046
+ # :validate :: set to false to skip validation
1047
+ # :raise_on_failure :: set to true or false to override the current
1048
+ # +raise_on_save_failure+ setting
1045
1049
  def save(*columns)
1046
1050
  opts = columns.last.is_a?(Hash) ? columns.pop : {}
1047
1051
  if opts[:validate] != false
@@ -1088,7 +1092,8 @@ module Sequel
1088
1092
  end
1089
1093
 
1090
1094
  # Set all values using the entries in the hash, except for the keys
1091
- # given in except.
1095
+ # given in except. You should probably use +set_fields+ or +set_only+
1096
+ # instead of this method, as blacklist approaches to security are a bad idea.
1092
1097
  #
1093
1098
  # artist.set_except({:name=>'Jim'}, :hometown)
1094
1099
  # artist.name # => 'Jim'
@@ -1111,12 +1116,13 @@ module Sequel
1111
1116
  end
1112
1117
 
1113
1118
  # Set the values using the entries in the hash, only if the key
1114
- # is included in only.
1119
+ # is included in only. It may be a better idea to use +set_fields+
1120
+ # instead of this method.
1115
1121
  #
1116
1122
  # artist.set_only({:name=>'Jim'}, :name)
1117
1123
  # artist.name # => 'Jim'
1118
1124
  #
1119
- # artist.set_only({:hometown=>'LA'}, :name) # Raise error
1125
+ # artist.set_only({:hometown=>'LA'}, :name) # Raise Error
1120
1126
  def set_only(hash, *only)
1121
1127
  set_restricted(hash, only.flatten, false)
1122
1128
  end
@@ -1135,7 +1141,7 @@ module Sequel
1135
1141
  @this ||= model.dataset.filter(pk_hash).limit(1).naked
1136
1142
  end
1137
1143
 
1138
- # Runs set with the passed hash and then runs save_changes.
1144
+ # Runs #set with the passed hash and then runs save_changes.
1139
1145
  #
1140
1146
  # artist.update(:name=>'Jim') # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1141
1147
  def update(hash)
@@ -1143,7 +1149,7 @@ module Sequel
1143
1149
  end
1144
1150
 
1145
1151
  # Update all values using the entries in the hash, ignoring any setting of
1146
- # allowed_columns or restricted columns in the model.
1152
+ # +allowed_columns+ or +restricted_columns+ in the model.
1147
1153
  #
1148
1154
  # Artist.set_restricted_columns(:name)
1149
1155
  # artist.update_all(:name=>'Jim') # UPDATE artists SET name = 'Jim' WHERE (id = 1)
@@ -1152,7 +1158,8 @@ module Sequel
1152
1158
  end
1153
1159
 
1154
1160
  # Update all values using the entries in the hash, except for the keys
1155
- # given in except.
1161
+ # given in except. You should probably use +update_fields+ or +update_only+
1162
+ # instead of this method, as blacklist approaches to security are a bad idea.
1156
1163
  #
1157
1164
  # artist.update_except({:name=>'Jim'}, :hometown) # UPDATE artists SET name = 'Jim' WHERE (id = 1)
1158
1165
  def update_except(hash, *except)
@@ -1173,7 +1180,8 @@ module Sequel
1173
1180
  end
1174
1181
 
1175
1182
  # Update the values using the entries in the hash, only if the key
1176
- # is included in only.
1183
+ # is included in only. It may be a better idea to use +update_fields+
1184
+ # instead of this method.
1177
1185
  #
1178
1186
  # artist.update_only({:name=>'Jim'}, :name)
1179
1187
  # # UPDATE artists SET name = 'Jim' WHERE (id = 1)
@@ -1488,7 +1496,16 @@ module Sequel
1488
1496
  if meths.include?(m)
1489
1497
  send(m, v)
1490
1498
  elsif strict
1491
- raise Error, "method #{m} doesn't exist or access is restricted to it"
1499
+ # Avoid using respond_to? or creating symbols from user input
1500
+ if public_methods.map{|s| s.to_s}.include?(m)
1501
+ if Array(model.primary_key).map{|s| s.to_s}.member?(k.to_s) && model.restrict_primary_key?
1502
+ raise Error, "#{k} is a restricted primary key"
1503
+ else
1504
+ raise Error, "#{k} is a restricted column"
1505
+ end
1506
+ else
1507
+ raise Error, "method #{m} doesn't exist"
1508
+ end
1492
1509
  end
1493
1510
  end
1494
1511
  self
@@ -2,6 +2,8 @@ module Sequel
2
2
  # Exception class raised when +raise_on_save_failure+ is set and a before hook returns false
3
3
  # or an around hook doesn't call super or yield.
4
4
  class HookFailed < Error; end
5
+
6
+ # Deprecated alias for HookFailed, kept for backwards compatibility
5
7
  BeforeHookFailed = HookFailed
6
8
 
7
9
  # Exception class raised when +require_modification+ is set and an UPDATE or DELETE statement to modify the dataset doesn't
@@ -22,10 +22,7 @@ module Sequel
22
22
  # Identity maps are thread-local and only persist for the duration of the block,
23
23
  # so they should only be considered as a possible performance enhancer.
24
24
  #
25
- # The identity_map plugin is not compatible with the standard eager loading of
26
- # many_to_many and many_through_many associations. If you want to use the identity_map plugin,
27
- # you should use +eager_graph+ instead of +eager+ for those associations. It is also
28
- # not compatible with the eager loading in the +rcte_tree+ plugin.
25
+ # The identity_map plugin is not compatible with the eager loading in the +rcte_tree+ plugin.
29
26
  #
30
27
  # Usage:
31
28
  #
@@ -37,6 +34,116 @@ module Sequel
37
34
  # # would need to do Album.with_identity_map{} to use the identity map
38
35
  module IdentityMap
39
36
  module ClassMethods
37
+ # Override the default :eager_loader option for many_*_many associations to work
38
+ # with an identity_map. If the :eager_graph association option is used, you'll probably have to use
39
+ # :uniq=>true on the current association amd :cartesian_product_number=>2 on the association
40
+ # mentioned by :eager_graph, otherwise you'll end up with duplicates because the row proc will be
41
+ # getting called multiple times for the same object. If you do have duplicates and you use :eager_graph,
42
+ # they'll probably be lost. Making that work correctly would require changing a lot of the core
43
+ # architecture, such as how graphing and eager graphing work.
44
+ def associate(type, name, opts = {}, &block)
45
+ if opts[:eager_loader]
46
+ super
47
+ elsif type == :many_to_many
48
+ opts = super
49
+ el = opts[:eager_loader]
50
+ model = self
51
+ left_pk = opts[:left_primary_key]
52
+ uses_lcks = opts[:uses_left_composite_keys]
53
+ uses_rcks = opts[:uses_right_composite_keys]
54
+ right = opts[:right_key]
55
+ join_table = opts[:join_table]
56
+ left = opts[:left_key]
57
+ lcks = opts[:left_keys]
58
+ left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
59
+ opts[:eager_loader] = lambda do |eo|
60
+ return el.call(eo) unless model.identity_map
61
+ h = eo[:key_hash][left_pk]
62
+ eo[:rows].each{|object| object.associations[name] = []}
63
+ r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
64
+ l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
65
+
66
+ # Replace the row proc to remove the left key alias before calling the previous row proc.
67
+ # Associate the value of the left key alias with the associated object (through its object_id).
68
+ # When loading the associated objects, lookup the left key alias value and associate the
69
+ # associated objects to the main objects if the left key alias value matches the left primary key
70
+ # value of the main object.
71
+ #
72
+ # The deleting of the left key alias from the hash before calling the previous row proc
73
+ # is necessary when an identity map is used, otherwise if the same associated object is returned more than
74
+ # once for the association, it won't know which of current objects to associate it to.
75
+ ds = opts.associated_class.inner_join(join_table, r + l)
76
+ pr = ds.row_proc
77
+ h2 = {}
78
+ ds.row_proc = proc do |hash|
79
+ hash_key = if uses_lcks
80
+ left_key_alias.map{|k| hash.delete(k)}
81
+ else
82
+ hash.delete(left_key_alias)
83
+ end
84
+ obj = pr.call(hash)
85
+ (h2[obj.object_id] ||= []) << hash_key
86
+ obj
87
+ end
88
+ model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo) .all do |assoc_record|
89
+ if hash_keys = h2.delete(assoc_record.object_id)
90
+ hash_keys.each do |hash_key|
91
+ if objects = h[hash_key]
92
+ objects.each{|object| object.associations[name].push(assoc_record)}
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ opts
99
+ elsif type == :many_through_many
100
+ opts = super
101
+ el = opts[:eager_loader]
102
+ model = self
103
+ left_pk = opts[:left_primary_key]
104
+ left_key = opts[:left_key]
105
+ uses_lcks = opts[:uses_left_composite_keys]
106
+ left_keys = Array(left_key)
107
+ left_key_alias = opts[:left_key_alias]
108
+ opts[:eager_loader] = lambda do |eo|
109
+ return el.call(eo) unless model.identity_map
110
+ h = eo[:key_hash][left_pk]
111
+ eo[:rows].each{|object| object.associations[name] = []}
112
+ ds = opts.associated_class
113
+ opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
114
+ ft = opts[:final_reverse_edge]
115
+ conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
116
+
117
+ # See above comment in many_to_many eager_loader
118
+ ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
119
+ pr = ds.row_proc
120
+ h2 = {}
121
+ ds.row_proc = proc do |hash|
122
+ hash_key = if uses_lcks
123
+ left_key_alias.map{|k| hash.delete(k)}
124
+ else
125
+ hash.delete(left_key_alias)
126
+ end
127
+ obj = pr.call(hash)
128
+ (h2[obj.object_id] ||= []) << hash_key
129
+ obj
130
+ end
131
+ model.eager_loading_dataset(opts, ds, Array(opts.select), eo[:associations], eo).all do |assoc_record|
132
+ if hash_keys = h2.delete(assoc_record.object_id)
133
+ hash_keys.each do |hash_key|
134
+ if objects = h[hash_key]
135
+ objects.each{|object| object.associations[name].push(assoc_record)}
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ opts
142
+ else
143
+ super
144
+ end
145
+ end
146
+
40
147
  # Returns the current thread-local identity map. Should be a hash if
41
148
  # there is an active identity map, and nil otherwise.
42
149
  def identity_map
@@ -40,6 +40,18 @@ module Sequel
40
40
  def new_using_server(s, values={}, &block)
41
41
  new(values, &block).set_server(s)
42
42
  end
43
+
44
+ private
45
+
46
+ # Set the server for each graphed dataset to the current server
47
+ # unless the graphed dataset already has a server set.
48
+ def eager_graph_dataset(opts, eager_options)
49
+ ds = super
50
+ if s = eager_options[:self].opts[:server]
51
+ ds = ds.server(s) unless ds.opts[:server]
52
+ end
53
+ ds
54
+ end
43
55
  end
44
56
 
45
57
  module InstanceMethods
@@ -109,26 +121,6 @@ module Sequel
109
121
  end
110
122
  ds
111
123
  end
112
-
113
- private
114
-
115
- # Set the shard of all retrieved objects to the shard of
116
- # the table's dataset, or the current shard if the table's
117
- # dataset does not have a shard.
118
- def graph_each
119
- ta = @opts[:graph][:table_aliases]
120
- s = @opts[:server]
121
- super do |r|
122
- r.each do |k, v|
123
- if ds = ta[k]
124
- dss = ds.opts[:server]
125
- end
126
- vs = dss || s
127
- v.set_server(vs) if vs && v.respond_to?(:set_server)
128
- end
129
- yield r
130
- end
131
- end
132
124
  end
133
125
  end
134
126
  end
@@ -205,9 +205,9 @@ module Sequel
205
205
  klass.from_xml_node(node)
206
206
  end
207
207
  elsif cols.include?(k)
208
- self[k.to_sym] = node[:nil] ? nil : node.children.first.to_s
208
+ self[k.to_sym] = node.key?('nil') ? nil : node.children.first.to_s
209
209
  elsif meths.include?("#{k}=")
210
- send("#{k}=", node[:nil] ? nil : node.children.first.to_s)
210
+ send("#{k}=", node.key?('nil') ? nil : node.children.first.to_s)
211
211
  else
212
212
  raise Error, "Entry in XML not an association or column and no setter method exists: #{k}"
213
213
  end
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 25
6
+ MINOR = 26
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -606,12 +606,6 @@ describe "Postgres::Database schema qualified tables" do
606
606
  POSTGRES_DB.drop_table(:domains)
607
607
  end
608
608
 
609
- specify "#table_exists? should not include tables from the default non-public schemas" do
610
- POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
611
- POSTGRES_DB.table_exists?(:schema_test).should == true
612
- POSTGRES_DB.table_exists?(:domain_udt_usage).should == false
613
- end
614
-
615
609
  specify "#table_exists? should see if the table is in a given schema" do
616
610
  POSTGRES_DB.create_table(:schema_test__schema_test){integer :i}
617
611
  POSTGRES_DB.table_exists?(:schema_test__schema_test).should == true
@@ -245,6 +245,12 @@ shared_examples_for "A threaded connection pool" do
245
245
  t.join
246
246
  end
247
247
 
248
+ it "should not add a disconnected connection back to the pool if the disconnection_proc raises an error" do
249
+ pool = Sequel::ConnectionPool.get_pool(@cp_opts.merge(:max_connections=>1, :pool_timeout=>0, :disconnection_proc=>proc{|c| raise Sequel::Error})) {@invoked_count += 1}
250
+ proc{pool.hold{raise Sequel::DatabaseDisconnectError}}.should raise_error(Sequel::Error)
251
+ pool.available_connections.length.should == 0
252
+ end
253
+
248
254
  specify "should let five threads simultaneously access separate connections" do
249
255
  cc = {}
250
256
  threads = []
@@ -1332,6 +1332,18 @@ describe "Database#typecast_value" do
1332
1332
  proc{@db.typecast_value(:datetime, 4)}.should raise_error(Sequel::InvalidValue)
1333
1333
  end
1334
1334
 
1335
+ specify "should handle integers with leading 0 as base 10" do
1336
+ @db.typecast_value(:integer, "013").should == 13
1337
+ @db.typecast_value(:integer, "08").should == 8
1338
+ @db.typecast_value(:integer, "000013").should == 13
1339
+ @db.typecast_value(:integer, "000008").should == 8
1340
+ end
1341
+
1342
+ specify "should handle integers with leading 0x as base 16" do
1343
+ @db.typecast_value(:integer, "0x013").should == 19
1344
+ @db.typecast_value(:integer, "0x80").should == 128
1345
+ end
1346
+
1335
1347
  specify "should have an underlying exception class available at wrapped_exception" do
1336
1348
  begin
1337
1349
  @db.typecast_value(:date, 'a')
@@ -555,8 +555,15 @@ describe "DB#create_table!" do
555
555
  @db = SchemaDummyDatabase.new
556
556
  end
557
557
 
558
- specify "should drop the table and then create it" do
559
- @db.create_table!(:cats) {}
558
+ specify "should create the table if it does not exist" do
559
+ @db.meta_def(:table_exists?){|a| false}
560
+ @db.create_table!(:cats){|*a|}
561
+ @db.sqls.should == ['CREATE TABLE cats ()']
562
+ end
563
+
564
+ specify "should drop the table before creating it if it already exists" do
565
+ @db.meta_def(:table_exists?){|a| true}
566
+ @db.create_table!(:cats){|*a|}
560
567
  @db.sqls.should == ['DROP TABLE cats', 'CREATE TABLE cats ()']
561
568
  end
562
569
  end