sequel 3.29.0 → 3.30.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 (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