sequel 3.25.0 → 3.26.0

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