sequel 3.29.0 → 3.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/CHANGELOG +35 -3
  2. data/Rakefile +2 -1
  3. data/doc/association_basics.rdoc +11 -0
  4. data/doc/opening_databases.rdoc +2 -0
  5. data/doc/release_notes/3.30.0.txt +135 -0
  6. data/doc/testing.rdoc +17 -3
  7. data/lib/sequel/adapters/amalgalite.rb +2 -2
  8. data/lib/sequel/adapters/do/mysql.rb +5 -2
  9. data/lib/sequel/adapters/ibmdb.rb +2 -2
  10. data/lib/sequel/adapters/jdbc.rb +126 -43
  11. data/lib/sequel/adapters/jdbc/as400.rb +11 -3
  12. data/lib/sequel/adapters/jdbc/db2.rb +2 -1
  13. data/lib/sequel/adapters/jdbc/derby.rb +44 -19
  14. data/lib/sequel/adapters/jdbc/h2.rb +32 -19
  15. data/lib/sequel/adapters/jdbc/hsqldb.rb +21 -17
  16. data/lib/sequel/adapters/jdbc/jtds.rb +9 -4
  17. data/lib/sequel/adapters/jdbc/mssql.rb +3 -1
  18. data/lib/sequel/adapters/jdbc/mysql.rb +2 -1
  19. data/lib/sequel/adapters/jdbc/oracle.rb +21 -7
  20. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -2
  21. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -1
  22. data/lib/sequel/adapters/jdbc/sqlserver.rb +48 -18
  23. data/lib/sequel/adapters/mock.rb +2 -1
  24. data/lib/sequel/adapters/mysql.rb +4 -2
  25. data/lib/sequel/adapters/mysql2.rb +2 -2
  26. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  27. data/lib/sequel/adapters/openbase.rb +1 -1
  28. data/lib/sequel/adapters/oracle.rb +6 -6
  29. data/lib/sequel/adapters/postgres.rb +25 -12
  30. data/lib/sequel/adapters/shared/access.rb +14 -6
  31. data/lib/sequel/adapters/shared/db2.rb +36 -13
  32. data/lib/sequel/adapters/shared/firebird.rb +12 -5
  33. data/lib/sequel/adapters/shared/informix.rb +11 -3
  34. data/lib/sequel/adapters/shared/mssql.rb +94 -47
  35. data/lib/sequel/adapters/shared/mysql.rb +107 -49
  36. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +2 -2
  37. data/lib/sequel/adapters/shared/oracle.rb +54 -27
  38. data/lib/sequel/adapters/shared/postgres.rb +65 -26
  39. data/lib/sequel/adapters/shared/progress.rb +4 -1
  40. data/lib/sequel/adapters/shared/sqlite.rb +36 -20
  41. data/lib/sequel/adapters/sqlite.rb +2 -3
  42. data/lib/sequel/adapters/swift/mysql.rb +3 -2
  43. data/lib/sequel/adapters/swift/sqlite.rb +2 -2
  44. data/lib/sequel/adapters/tinytds.rb +14 -8
  45. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +7 -4
  46. data/lib/sequel/database/misc.rb +6 -2
  47. data/lib/sequel/dataset/graph.rb +33 -7
  48. data/lib/sequel/dataset/prepared_statements.rb +19 -5
  49. data/lib/sequel/dataset/sql.rb +611 -201
  50. data/lib/sequel/model/associations.rb +12 -5
  51. data/lib/sequel/model/base.rb +20 -5
  52. data/lib/sequel/plugins/sharding.rb +9 -29
  53. data/lib/sequel/sql.rb +2 -1
  54. data/lib/sequel/timezones.rb +14 -4
  55. data/lib/sequel/version.rb +1 -1
  56. data/spec/adapters/mysql_spec.rb +10 -0
  57. data/spec/adapters/oracle_spec.rb +1 -1
  58. data/spec/core/core_sql_spec.rb +3 -1
  59. data/spec/core/database_spec.rb +42 -0
  60. data/spec/core/dataset_spec.rb +10 -3
  61. data/spec/core/mock_adapter_spec.rb +4 -0
  62. data/spec/core/object_graph_spec.rb +38 -0
  63. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  64. data/spec/extensions/association_dependencies_spec.rb +2 -12
  65. data/spec/extensions/association_pks_spec.rb +35 -39
  66. data/spec/extensions/caching_spec.rb +23 -50
  67. data/spec/extensions/class_table_inheritance_spec.rb +30 -82
  68. data/spec/extensions/composition_spec.rb +18 -13
  69. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  70. data/spec/extensions/identity_map_spec.rb +33 -103
  71. data/spec/extensions/instance_filters_spec.rb +10 -21
  72. data/spec/extensions/instance_hooks_spec.rb +6 -24
  73. data/spec/extensions/json_serializer_spec.rb +4 -5
  74. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  75. data/spec/extensions/list_spec.rb +17 -39
  76. data/spec/extensions/many_through_many_spec.rb +135 -277
  77. data/spec/extensions/migration_spec.rb +18 -15
  78. data/spec/extensions/named_timezones_spec.rb +1 -1
  79. data/spec/extensions/nested_attributes_spec.rb +97 -92
  80. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  81. data/spec/extensions/prepared_statements_associations_spec.rb +22 -37
  82. data/spec/extensions/prepared_statements_safe_spec.rb +9 -27
  83. data/spec/extensions/prepared_statements_spec.rb +11 -30
  84. data/spec/extensions/prepared_statements_with_pk_spec.rb +6 -13
  85. data/spec/extensions/pretty_table_spec.rb +1 -6
  86. data/spec/extensions/rcte_tree_spec.rb +41 -43
  87. data/spec/extensions/schema_dumper_spec.rb +3 -6
  88. data/spec/extensions/serialization_spec.rb +20 -32
  89. data/spec/extensions/sharding_spec.rb +66 -140
  90. data/spec/extensions/single_table_inheritance_spec.rb +14 -36
  91. data/spec/extensions/spec_helper.rb +10 -64
  92. data/spec/extensions/sql_expr_spec.rb +20 -60
  93. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  94. data/spec/extensions/timestamps_spec.rb +6 -6
  95. data/spec/extensions/to_dot_spec.rb +1 -2
  96. data/spec/extensions/touch_spec.rb +13 -14
  97. data/spec/extensions/tree_spec.rb +11 -26
  98. data/spec/extensions/update_primary_key_spec.rb +30 -24
  99. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  100. data/spec/extensions/validation_helpers_spec.rb +16 -35
  101. data/spec/integration/dataset_test.rb +16 -4
  102. data/spec/integration/prepared_statement_test.rb +4 -2
  103. data/spec/model/eager_loading_spec.rb +16 -0
  104. data/spec/model/model_spec.rb +15 -1
  105. data/spec/model/record_spec.rb +60 -0
  106. metadata +23 -40
@@ -718,6 +718,10 @@ module Sequel
718
718
  # :eager_loader_key :: A symbol for the key column to use to populate the key hash
719
719
  # for the eager loader.
720
720
  # :extend :: A module or array of modules to extend the dataset with.
721
+ # :graph_alias_base :: The base name to use for the table alias when eager graphing. Defaults to the name
722
+ # of the association. If the alias name has already been used in the query, Sequel will create
723
+ # a unique alias by appending a numeric suffix (e.g. alias_0, alias_1, ...) until the alias is
724
+ # unique.
721
725
  # :graph_block :: The block to pass to join_table when eagerly loading
722
726
  # the association via +eager_graph+.
723
727
  # :graph_conditions :: The additional conditions to use on the SQL join when eagerly loading
@@ -822,6 +826,7 @@ module Sequel
822
826
  opts[:graph_join_type] ||= :left_outer
823
827
  opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
824
828
  conds = opts[:conditions]
829
+ opts[:graph_alias_base] ||= name
825
830
  opts[:graph_conditions] = conds if !opts.include?(:graph_conditions) and Sequel.condition_specifier?(conds)
826
831
  opts[:graph_conditions] = opts.fetch(:graph_conditions, []).to_a
827
832
  opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
@@ -1618,7 +1623,7 @@ module Sequel
1618
1623
  # association and the values of the foreign/primary keys of +y+. For most association
1619
1624
  # types, this is a simple transformation, but for +many_to_many+ associations this
1620
1625
  # creates a subquery to the join table.
1621
- def complex_expression_sql(op, args)
1626
+ def complex_expression_sql_append(sql, op, args)
1622
1627
  r = args.at(1)
1623
1628
  if (((op == :'=' || op == :'!=') and r.is_a?(Sequel::Model)) ||
1624
1629
  (multiple = ((op == :IN || op == :'NOT IN') and ((is_ds = r.is_a?(Sequel::Dataset)) or r.all?{|x| x.is_a?(Sequel::Model)}))))
@@ -1646,7 +1651,7 @@ module Sequel
1646
1651
  end
1647
1652
 
1648
1653
  if exp = association_filter_expression(op, ar, r)
1649
- literal(exp)
1654
+ literal_append(sql, exp)
1650
1655
  else
1651
1656
  raise Sequel::Error, "invalid association type #{ar[:type].inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}"
1652
1657
  end
@@ -1725,8 +1730,10 @@ module Sequel
1725
1730
  # Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
1726
1731
  # call +each+, it will yield plain hashes, each containing all columns from all the tables.
1727
1732
  def eager_graph(*associations)
1728
- ds = if @opts[:eager_graph]
1729
- self
1733
+ ds = if eg = @opts[:eager_graph]
1734
+ eg = eg.dup
1735
+ [:requirements, :reflections, :reciprocals].each{|k| eg[k] = eg[k].dup}
1736
+ clone(:eager_graph=>eg)
1730
1737
  else
1731
1738
  # Each of the following have a symbol key for the table alias, with the following values:
1732
1739
  # :reciprocals - the reciprocal instance variable to use for this association
@@ -1761,7 +1768,7 @@ module Sequel
1761
1768
  # *associations :: any associations dependent on this one
1762
1769
  def eager_graph_association(ds, model, ta, requirements, r, *associations)
1763
1770
  assoc_name = r[:name]
1764
- assoc_table_alias = ds.unused_table_alias(assoc_name)
1771
+ assoc_table_alias = ds.unused_table_alias(r[:graph_alias_base])
1765
1772
  loader = r[:eager_grapher]
1766
1773
  if !associations.empty?
1767
1774
  if associations.first.respond_to?(:call)
@@ -1139,13 +1139,16 @@ module Sequel
1139
1139
  # Takes the following options:
1140
1140
  #
1141
1141
  # :changed :: save all changed columns, instead of all columns or the columns given
1142
+ # :raise_on_failure :: set to true or false to override the current
1143
+ # +raise_on_save_failure+ setting
1144
+ # :server :: set the server/shard on the object before saving, and use that
1145
+ # server/shard in any transaction.
1142
1146
  # :transaction :: set to true or false to override the current
1143
1147
  # +use_transactions+ setting
1144
1148
  # :validate :: set to false to skip validation
1145
- # :raise_on_failure :: set to true or false to override the current
1146
- # +raise_on_save_failure+ setting
1147
1149
  def save(*columns)
1148
1150
  opts = columns.last.is_a?(Hash) ? columns.pop : {}
1151
+ set_server(opts[:server]) if opts[:server]
1149
1152
  if opts[:validate] != false
1150
1153
  unless checked_save_failure(opts){_valid?(true, opts)}
1151
1154
  raise(ValidationFailed.new(errors)) if raise_on_failure?(opts)
@@ -1225,6 +1228,13 @@ module Sequel
1225
1228
  set_restricted(hash, only.flatten, false)
1226
1229
  end
1227
1230
 
1231
+ # Set the shard that this object is tied to. Returns self.
1232
+ def set_server(s)
1233
+ @server = s
1234
+ @this.opts[:server] = s if @this
1235
+ self
1236
+ end
1237
+
1228
1238
  # Replace the current values with hash. Should definitely not be
1229
1239
  # used with untrusted input, and should probably not be called
1230
1240
  # directly by user code.
@@ -1243,7 +1253,7 @@ module Sequel
1243
1253
  # Artist[1].this
1244
1254
  # # SELECT * FROM artists WHERE (id = 1) LIMIT 1
1245
1255
  def this
1246
- @this ||= model.dataset.filter(pk_hash).limit(1).naked
1256
+ @this ||= use_server(model.dataset.filter(pk_hash).limit(1).naked)
1247
1257
  end
1248
1258
 
1249
1259
  # Runs #set with the passed hash and then runs save_changes.
@@ -1382,7 +1392,7 @@ module Sequel
1382
1392
  # The dataset to use when inserting a new object. The same as the model's
1383
1393
  # dataset by default.
1384
1394
  def _insert_dataset
1385
- model.dataset
1395
+ use_server(model.dataset)
1386
1396
  end
1387
1397
 
1388
1398
  # Insert into the given dataset and return the primary key created (if any).
@@ -1676,6 +1686,11 @@ module Sequel
1676
1686
  set_restricted(hash, only, except)
1677
1687
  save_changes
1678
1688
  end
1689
+
1690
+ # Set the given dataset to use the current object's shard.
1691
+ def use_server(ds)
1692
+ @server ? ds.server(@server) : ds
1693
+ end
1679
1694
 
1680
1695
  # Whether to use a transaction for this action. If the :transaction
1681
1696
  # option is present in the hash, use that, otherwise, fallback to the
@@ -1716,7 +1731,7 @@ module Sequel
1716
1731
  # # ...
1717
1732
  def destroy
1718
1733
  pr = proc{all{|r| r.destroy}.length}
1719
- model.use_transactions ? @db.transaction(&pr) : pr.call
1734
+ model.use_transactions ? @db.transaction(:server=>opts[:server], &pr) : pr.call
1720
1735
  end
1721
1736
 
1722
1737
  # This allows you to call +to_hash+ without any arguments, which will
@@ -1,13 +1,14 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # The sharding plugin makes it easy to use Sequel's sharding features
4
- # with models. It lets you create model objects on specific shards,
5
- # and any models retrieved from specific shards are automatically
6
- # saved back to those shards. It also works with associations,
7
- # so that model objects retrieved from specific shards will only
8
- # return associated objects from that shard, and using the
9
- # add/remove/remove_all association methods will only affect
10
- # that shard.
3
+ # The sharding plugin augments Sequel's default model sharding support
4
+ # in the following ways:
5
+ #
6
+ # 1) It automatically sets model instances to be saved back to the
7
+ # shard they were retreived from.
8
+ # 2) It makes model associations use the same shard as the model
9
+ # object.
10
+ # 3) It adds a slightly nicer API for creating model instances on
11
+ # specific shards.
11
12
  #
12
13
  # Usage:
13
14
  #
@@ -55,12 +56,6 @@ module Sequel
55
56
  end
56
57
 
57
58
  module InstanceMethods
58
- # Set the shard that this object is tied to. Returns self.
59
- def set_server(s)
60
- @server = s
61
- self
62
- end
63
-
64
59
  # Set the server that this object is tied to, unless it has
65
60
  # already been set. Returns self.
66
61
  def set_server?(s)
@@ -68,11 +63,6 @@ module Sequel
68
63
  self
69
64
  end
70
65
 
71
- # Ensure that the instance dataset is tied to the correct shard.
72
- def this
73
- use_server(super)
74
- end
75
-
76
66
  private
77
67
 
78
68
  # Ensure that association datasets are tied to the correct shard.
@@ -80,11 +70,6 @@ module Sequel
80
70
  use_server(super)
81
71
  end
82
72
 
83
- # Ensure that the object is inserted into the correct shard.
84
- def _insert_dataset
85
- use_server(super)
86
- end
87
-
88
73
  # Ensure that the join table for many_to_many associations uses the correct shard.
89
74
  def _join_table_dataset(opts)
90
75
  use_server(super)
@@ -97,11 +82,6 @@ module Sequel
97
82
  o.set_server?(@server) if o.respond_to?(:set_server?)
98
83
  super
99
84
  end
100
-
101
- # Set the given dataset to use the current object's shard.
102
- def use_server(ds)
103
- @server ? ds.server(@server) : ds
104
- end
105
85
  end
106
86
 
107
87
  module DatasetMethods
@@ -79,7 +79,8 @@ module Sequel
79
79
  # the method provided on the dataset with args as the argument (self by default).
80
80
  # Used to DRY up some code.
81
81
  def self.to_s_method(meth, args=:self) # :nodoc:
82
- class_eval("def to_s(ds); ds.#{meth}(#{args}) end", __FILE__, __LINE__)
82
+ class_eval("def to_s(ds) ds.#{meth}(#{args}) end", __FILE__, __LINE__)
83
+ class_eval("def to_s_append(ds, sql) ds.#{meth}_append(sql, #{args}) end", __FILE__, __LINE__)
83
84
  end
84
85
  private_class_method :to_s_method
85
86
 
@@ -145,14 +145,24 @@ module Sequel
145
145
  v2
146
146
  end
147
147
  when Array
148
- y, mo, d, h, mi, s = v
148
+ y, mo, d, h, mi, s, ns, off = v
149
149
  if datetime_class == DateTime
150
- convert_input_datetime_no_offset(DateTime.civil(y, mo, d, h, mi, s, 0), input_timezone)
150
+ s += (defined?(Rational) ? Rational(ns, 1000000000) : ns/1000000000.0) if ns
151
+ if off
152
+ DateTime.civil(y, mo, d, h, mi, s, off)
153
+ else
154
+ convert_input_datetime_no_offset(DateTime.civil(y, mo, d, h, mi, s), input_timezone)
155
+ end
151
156
  else
152
- Time.send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s)
157
+ Time.send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s, (ns ? ns / 1000.0 : 0))
153
158
  end
154
159
  when Hash
155
- convert_input_timestamp([:year, :month, :day, :hour, :minute, :second].map{|x| (v[x] || v[x.to_s]).to_i}, input_timezone)
160
+ ary = [:year, :month, :day, :hour, :minute, :second, :nanos].map{|x| (v[x] || v[x.to_s]).to_i}
161
+ if (offset = (v[:offset] || v['offset']))
162
+ ary << offset
163
+ end
164
+ convert_input_timestamp(ary, input_timezone)
165
+ convert_input_timestamp(ary, input_timezone)
156
166
  when Time
157
167
  if datetime_class == DateTime
158
168
  v.respond_to?(:to_datetime) ? v.to_datetime : string_to_datetime(v.iso8601)
@@ -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 = 29
6
+ MINOR = 30
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
@@ -247,6 +247,16 @@ describe "A MySQL dataset" do
247
247
 
248
248
  @d.first[:name].should == ':\\'
249
249
  end
250
+
251
+ specify "should handle prepared statements with on_duplicate_key_update" do
252
+ @d.db.add_index :items, :value, :unique=>true
253
+ ds = @d.on_duplicate_key_update
254
+ ps = ds.prepare(:insert, :insert_user_id_feature_name, :value => :$v, :name => :$n)
255
+ ps.call(:v => 1, :n => 'a')
256
+ ds.all.should == [{:value=>1, :name=>'a'}]
257
+ ps.call(:v => 1, :n => 'b')
258
+ ds.all.should == [{:value=>1, :name=>'b'}]
259
+ end
250
260
  end
251
261
 
252
262
  describe "MySQL datasets" do
@@ -187,7 +187,7 @@ describe "An Oracle dataset" do
187
187
  @d << {:name => 'def', :value => 789}
188
188
  @d.filter('value > 500').update(:date_created => "to_timestamp('2009-09-09', 'YYYY-MM-DD')".lit)
189
189
 
190
- @d[:name => 'def'][:date_created].should == Time.parse('2009-09-09')
190
+ @d[:name => 'def'][:date_created].strftime('%F').should == '2009-09-09'
191
191
  end
192
192
 
193
193
  specify "should delete records correctly" do
@@ -177,7 +177,9 @@ end
177
177
  describe "Column references" do
178
178
  before do
179
179
  @ds = Sequel::Database.new.dataset
180
- def @ds.quoted_identifier(c); "`#{c}`"; end
180
+ def @ds.quoted_identifier_append(sql, c)
181
+ sql << "`#{c}`"
182
+ end
181
183
  @ds.quote_identifiers = true
182
184
  end
183
185
 
@@ -739,6 +739,13 @@ describe "Database#transaction" do
739
739
  proc{@db.after_rollback}.should raise_error(Sequel::Error)
740
740
  end
741
741
 
742
+ specify "should have after_commit and after_rollback respect :server option" do
743
+ @db.transaction(:server=>:test){@db.after_commit(:server=>:test){@db.execute('foo', :server=>:test)}}
744
+ @db.sqls.should == ['BEGIN -- test', 'COMMIT -- test', 'foo -- test']
745
+ @db.transaction(:server=>:test){@db.after_rollback(:server=>:test){@db.execute('foo', :server=>:test)}; raise Sequel::Rollback}
746
+ @db.sqls.should == ['BEGIN -- test', 'ROLLBACK -- test', 'foo -- test']
747
+ end
748
+
742
749
  specify "should execute after_commit outside transactions" do
743
750
  @db.after_commit{@db.execute('foo')}
744
751
  @db.sqls.should == ['foo']
@@ -1774,6 +1781,41 @@ describe "Database#typecast_value" do
1774
1781
  end
1775
1782
  end
1776
1783
 
1784
+ specify "should handle arrays when typecasting timestamps" do
1785
+ begin
1786
+ @db.typecast_value(:datetime, [2011, 10, 11, 12, 13, 14]).should == Time.local(2011, 10, 11, 12, 13, 14)
1787
+ @db.typecast_value(:datetime, [2011, 10, 11, 12, 13, 14, 500000000]).should == Time.local(2011, 10, 11, 12, 13, 14, 500000)
1788
+
1789
+ Sequel.datetime_class = DateTime
1790
+ @db.typecast_value(:datetime, [2011, 10, 11, 12, 13, 14]).should == DateTime.civil(2011, 10, 11, 12, 13, 14)
1791
+ @db.typecast_value(:datetime, [2011, 10, 11, 12, 13, 14, 500000000]).should == DateTime.civil(2011, 10, 11, 12, 13, (defined?(Rational) ? Rational(29, 2) : 14.5))
1792
+ @db.typecast_value(:datetime, [2011, 10, 11, 12, 13, 14, 500000000, (defined?(Rational) ? Rational(1, 2) : 0.5)]).should == DateTime.civil(2011, 10, 11, 12, 13, (defined?(Rational) ? Rational(29, 2) : 14.5), (defined?(Rational) ? Rational(1, 2) : 0.5))
1793
+ ensure
1794
+ Sequel.datetime_class = Time
1795
+ end
1796
+ end
1797
+
1798
+ specify "should handle hashes when typecasting timestamps" do
1799
+ begin
1800
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).should == Time.local(2011, 10, 11, 12, 13, 14)
1801
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).should == Time.local(2011, 10, 11, 12, 13, 14, 500000)
1802
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).should == Time.local(2011, 10, 11, 12, 13, 14)
1803
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).should == Time.local(2011, 10, 11, 12, 13, 14, 500000)
1804
+
1805
+ Sequel.datetime_class = DateTime
1806
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).should == DateTime.civil(2011, 10, 11, 12, 13, 14)
1807
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).should == DateTime.civil(2011, 10, 11, 12, 13, (defined?(Rational) ? Rational(29, 2) : 14.5))
1808
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).should == DateTime.civil(2011, 10, 11, 12, 13, 14)
1809
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).should == DateTime.civil(2011, 10, 11, 12, 13, (defined?(Rational) ? Rational(29, 2) : 14.5))
1810
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :offset=>(defined?(Rational) ? Rational(1, 2) : 0.5)).should == DateTime.civil(2011, 10, 11, 12, 13, 14, (defined?(Rational) ? Rational(1, 2) : 0.5))
1811
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000, :offset=>(defined?(Rational) ? Rational(1, 2) : 0.5)).should == DateTime.civil(2011, 10, 11, 12, 13, (defined?(Rational) ? Rational(29, 2) : 14.5), (defined?(Rational) ? Rational(1, 2) : 0.5))
1812
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'offset'=>(defined?(Rational) ? Rational(1, 2) : 0.5)).should == DateTime.civil(2011, 10, 11, 12, 13, 14, (defined?(Rational) ? Rational(1, 2) : 0.5))
1813
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000, 'offset'=>(defined?(Rational) ? Rational(1, 2) : 0.5)).should == DateTime.civil(2011, 10, 11, 12, 13, (defined?(Rational) ? Rational(29, 2) : 14.5), (defined?(Rational) ? Rational(1, 2) : 0.5))
1814
+ ensure
1815
+ Sequel.datetime_class = Time
1816
+ end
1817
+ end
1818
+
1777
1819
  specify "should typecast decimal values to BigDecimal" do
1778
1820
  [1.0, 1, '1.0', BigDecimal('1.0')].each do |i|
1779
1821
  v = @db.typecast_value(:decimal, i)
@@ -3066,6 +3066,13 @@ describe "Dataset prepared statements and bound variables " do
3066
3066
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3067
3067
  end
3068
3068
 
3069
+ specify "should handle columns on prepared statements correctly" do
3070
+ @db.columns = [:num]
3071
+ @ds.meta_def(:select_where_sql){|sql| super(sql); sql << " OR #{columns.first} = 1" if opts[:where]}
3072
+ @ds.filter(:num=>:$n).prepare(:select, :sn).sql.should == 'SELECT * FROM items WHERE (num = $n) OR num = 1'
3073
+ @db.sqls.should == ['SELECT * FROM items LIMIT 1']
3074
+ end
3075
+
3069
3076
  specify "should handle datasets using static sql and placeholders" do
3070
3077
  @db["SELECT * FROM items WHERE (num = ?)", :$n].call(:select, :n=>1)
3071
3078
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
@@ -3412,7 +3419,7 @@ describe "Sequel::Dataset #with and #with_recursive" do
3412
3419
 
3413
3420
  specify "#with should work on insert, update, and delete statements if they support it" do
3414
3421
  [:insert, :update, :delete].each do |m|
3415
- @ds.meta_def(:"#{m}_clause_methods"){super() + [:"#{m}_with_sql"]}
3422
+ @ds.meta_def(:"#{m}_clause_methods"){[:"#{m}_with_sql"] + super()}
3416
3423
  end
3417
3424
  @ds.with(:t, @db[:x]).insert_sql(1).should == 'WITH t AS (SELECT * FROM x) INSERT INTO t VALUES (1)'
3418
3425
  @ds.with(:t, @db[:x]).update_sql(:foo=>1).should == 'WITH t AS (SELECT * FROM x) UPDATE t SET foo = 1'
@@ -3888,8 +3895,8 @@ describe "Dataset emulating bitwise operator support" do
3888
3895
  before do
3889
3896
  @ds = Sequel::Database.new.dataset
3890
3897
  @ds.quote_identifiers = true
3891
- def @ds.complex_expression_sql(op, args)
3892
- complex_expression_arg_pairs(args){|a, b| "bitand(#{literal(a)}, #{literal(b)})"}
3898
+ def @ds.complex_expression_sql_append(sql, op, args)
3899
+ sql << complex_expression_arg_pairs(args){|a, b| "bitand(#{literal(a)}, #{literal(b)})"}
3893
3900
  end
3894
3901
  end
3895
3902
 
@@ -7,6 +7,10 @@ describe "Sequel Mock Adapter" do
7
7
  db.adapter_scheme.should == :mock
8
8
  end
9
9
 
10
+ specify "should have constructor accept no arguments" do
11
+ Sequel::Mock::Database.new.should be_a_kind_of(Sequel::Mock::Database)
12
+ end
13
+
10
14
  specify "should each not return any rows by default" do
11
15
  called = false
12
16
  Sequel.mock[:t].each{|r| called = true}
@@ -28,11 +28,49 @@ describe Sequel::Dataset, " graphing" do
28
28
  ds1.opts.should_not == o1
29
29
  end
30
30
 
31
+ it "#graph should not modify the current dataset's opts if current dataset is already graphed" do
32
+ ds2 = @ds1.graph(@ds2)
33
+ proc{@ds1.graph(@ds2)}.should_not raise_error
34
+ proc{ds2.graph(@ds3)}.should_not raise_error
35
+ proc{ds2.graph(@ds3)}.should_not raise_error
36
+ end
37
+
31
38
  it "#graph should accept a simple dataset and pass the table to join" do
32
39
  ds = @ds1.graph(@ds2, :x=>:id)
33
40
  ds.sql.should == 'SELECT points.id, points.x, points.y, lines.id AS lines_id, lines.x AS lines_x, lines.y AS lines_y, lines.graph_id FROM points LEFT OUTER JOIN lines ON (lines.x = points.id)'
34
41
  end
35
42
 
43
+ it "#graph should use currently selected columns as the basis for the selected columns in a new graph" do
44
+ ds = @ds1.select(:id).graph(@ds2, :x=>:id)
45
+ ds.sql.should == 'SELECT points.id, lines.id AS lines_id, lines.x, lines.y, lines.graph_id FROM points LEFT OUTER JOIN lines ON (lines.x = points.id)'
46
+ ds._fetch = {:id=>1, :lines_id=>2, :x=>3, :y=>4, :graph_id=>5}
47
+ ds.all.should == [{:points=>{:id=>1}, :lines=>{:id=>2, :x=>3, :y=>4, :graph_id=>5}}]
48
+
49
+ ds = @ds1.select(:id, :x).graph(@ds2, :x=>:id)
50
+ ds.sql.should == 'SELECT points.id, points.x, lines.id AS lines_id, lines.x AS lines_x, lines.y, lines.graph_id FROM points LEFT OUTER JOIN lines ON (lines.x = points.id)'
51
+ ds._fetch = {:id=>1, :x=>-1, :lines_id=>2, :lines_x=>3, :y=>4, :graph_id=>5}
52
+ ds.all.should == [{:points=>{:id=>1, :x=>-1}, :lines=>{:id=>2, :x=>3, :y=>4, :graph_id=>5}}]
53
+
54
+ ds = @ds1.select(:id.identifier, :x.qualify(:points)).graph(@ds2, :x=>:id)
55
+ ds.sql.should == 'SELECT points.id, points.x, lines.id AS lines_id, lines.x AS lines_x, lines.y, lines.graph_id FROM points LEFT OUTER JOIN lines ON (lines.x = points.id)'
56
+ ds._fetch = {:id=>1, :x=>-1, :lines_id=>2, :lines_x=>3, :y=>4, :graph_id=>5}
57
+ ds.all.should == [{:points=>{:id=>1, :x=>-1}, :lines=>{:id=>2, :x=>3, :y=>4, :graph_id=>5}}]
58
+
59
+ ds = @ds1.select(:id.identifier.qualify(:points), :x.identifier.as(:y)).graph(@ds2, :x=>:id)
60
+ ds.sql.should == 'SELECT points.id, points.x AS y, lines.id AS lines_id, lines.x, lines.y AS lines_y, lines.graph_id FROM points LEFT OUTER JOIN lines ON (lines.x = points.id)'
61
+ ds._fetch = {:id=>1, :y=>-1, :lines_id=>2, :x=>3, :lines_y=>4, :graph_id=>5}
62
+ ds.all.should == [{:points=>{:id=>1, :y=>-1}, :lines=>{:id=>2, :x=>3, :y=>4, :graph_id=>5}}]
63
+
64
+ ds = @ds1.select(:id, :x.identifier.qualify(:points.identifier).as(:y.identifier)).graph(@ds2, :x=>:id)
65
+ ds.sql.should == 'SELECT points.id, points.x AS y, lines.id AS lines_id, lines.x, lines.y AS lines_y, lines.graph_id FROM points LEFT OUTER JOIN lines ON (lines.x = points.id)'
66
+ ds._fetch = {:id=>1, :y=>-1, :lines_id=>2, :x=>3, :lines_y=>4, :graph_id=>5}
67
+ ds.all.should == [{:points=>{:id=>1, :y=>-1}, :lines=>{:id=>2, :x=>3, :y=>4, :graph_id=>5}}]
68
+ end
69
+
70
+ it "#graph should raise error if currently selected expressions cannot be handled" do
71
+ proc{@ds1.select(1).graph(@ds2, :x=>:id)}.should raise_error(Sequel::Error)
72
+ end
73
+
36
74
  it "#graph should accept a complex dataset and pass it directly to join" do
37
75
  ds = @ds1.graph(@ds2.filter(:x=>1), {:x=>:id})
38
76
  ds.sql.should == 'SELECT points.id, points.x, points.y, t1.id AS t1_id, t1.x AS t1_x, t1.y AS t1_y, t1.graph_id FROM points LEFT OUTER JOIN (SELECT * FROM lines WHERE (x = 1)) AS t1 ON (t1.x = points.id)'
@@ -5,11 +5,7 @@ describe "AssociationAutoreloading plugin" do
5
5
  @c = Class.new(Sequel::Model)
6
6
  @c.plugin :association_autoreloading
7
7
  @Artist = Class.new(@c).set_dataset(:artists)
8
- ds1 = @Artist.dataset
9
- def ds1.fetch_rows(s)
10
- (MODEL_DB.sqls ||= []) << s
11
- yield({:id=>2, :name=>'Ar'})
12
- end
8
+ @Artist.dataset._fetch = {:id=>2, :name=>'Ar'}
13
9
  @Album = Class.new(@c).set_dataset(:albums)
14
10
  @Artist.columns :id, :name
15
11
  @Album.columns :id, :name, :artist_id
@@ -22,7 +18,6 @@ describe "AssociationAutoreloading plugin" do
22
18
  album = @Album.load(:id => 1, :name=>'Al', :artist_id=>2)
23
19
  album.artist
24
20
  MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1']
25
- MODEL_DB.reset
26
21
 
27
22
  album.artist_id = 1
28
23
  album.artist
@@ -33,7 +28,6 @@ describe "AssociationAutoreloading plugin" do
33
28
  album = @Album.load(:id => 1, :name=>'Al', :artist_id=>2)
34
29
  album.artist
35
30
  MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1']
36
- MODEL_DB.reset
37
31
 
38
32
  album.artist_id = 2
39
33
  album.artist
@@ -54,7 +48,6 @@ describe "AssociationAutoreloading plugin" do
54
48
  album.artist_id = 1
55
49
  album.artist
56
50
  MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 1) LIMIT 1']
57
- MODEL_DB.reset
58
51
 
59
52
  album.other_artist
60
53
  MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 1) LIMIT 1']
@@ -69,7 +62,6 @@ describe "AssociationAutoreloading plugin" do
69
62
  album.artist_id = 1
70
63
  album.composite_artist
71
64
  MODEL_DB.sqls.should == ["SELECT * FROM artists WHERE ((artists.id = 1) AND (artists.name = 'Al')) LIMIT 1"]
72
- MODEL_DB.reset
73
65
 
74
66
  album.name = 'Al2'
75
67
  album.composite_artist
@@ -84,7 +76,6 @@ describe "AssociationAutoreloading plugin" do
84
76
  album = salbum.load(:id => 1, :name=>'Al', :artist_id=>2)
85
77
  album.artist
86
78
  MODEL_DB.sqls.should == ['SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1']
87
- MODEL_DB.reset
88
79
 
89
80
  album.artist_id = 1
90
81
  album.artist