sequel 3.36.1 → 3.37.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 (108) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +13 -0
  3. data/bin/sequel +12 -16
  4. data/doc/advanced_associations.rdoc +36 -67
  5. data/doc/association_basics.rdoc +11 -16
  6. data/doc/release_notes/3.37.0.txt +338 -0
  7. data/doc/schema_modification.rdoc +4 -0
  8. data/lib/sequel/adapters/jdbc/h2.rb +1 -1
  9. data/lib/sequel/adapters/jdbc/postgresql.rb +26 -8
  10. data/lib/sequel/adapters/mysql2.rb +4 -3
  11. data/lib/sequel/adapters/odbc/mssql.rb +2 -2
  12. data/lib/sequel/adapters/postgres.rb +4 -60
  13. data/lib/sequel/adapters/shared/mssql.rb +2 -1
  14. data/lib/sequel/adapters/shared/mysql.rb +0 -5
  15. data/lib/sequel/adapters/shared/postgres.rb +68 -2
  16. data/lib/sequel/adapters/shared/sqlite.rb +17 -1
  17. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +12 -1
  18. data/lib/sequel/adapters/utils/pg_types.rb +76 -0
  19. data/lib/sequel/core.rb +13 -0
  20. data/lib/sequel/database/misc.rb +41 -1
  21. data/lib/sequel/database/schema_generator.rb +23 -10
  22. data/lib/sequel/database/schema_methods.rb +26 -4
  23. data/lib/sequel/dataset/graph.rb +2 -1
  24. data/lib/sequel/dataset/query.rb +62 -2
  25. data/lib/sequel/extensions/_pretty_table.rb +7 -3
  26. data/lib/sequel/extensions/arbitrary_servers.rb +5 -4
  27. data/lib/sequel/extensions/blank.rb +4 -0
  28. data/lib/sequel/extensions/columns_introspection.rb +13 -2
  29. data/lib/sequel/extensions/core_extensions.rb +6 -0
  30. data/lib/sequel/extensions/eval_inspect.rb +158 -0
  31. data/lib/sequel/extensions/inflector.rb +4 -0
  32. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  33. data/lib/sequel/extensions/migration.rb +4 -1
  34. data/lib/sequel/extensions/named_timezones.rb +4 -0
  35. data/lib/sequel/extensions/null_dataset.rb +4 -0
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pg_array.rb +219 -168
  38. data/lib/sequel/extensions/pg_array_ops.rb +7 -2
  39. data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -4
  40. data/lib/sequel/extensions/pg_hstore.rb +3 -1
  41. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -2
  42. data/lib/sequel/extensions/pg_inet.rb +28 -3
  43. data/lib/sequel/extensions/pg_interval.rb +192 -0
  44. data/lib/sequel/extensions/pg_json.rb +21 -9
  45. data/lib/sequel/extensions/pg_range.rb +487 -0
  46. data/lib/sequel/extensions/pg_range_ops.rb +122 -0
  47. data/lib/sequel/extensions/pg_statement_cache.rb +3 -2
  48. data/lib/sequel/extensions/pretty_table.rb +12 -1
  49. data/lib/sequel/extensions/query.rb +4 -0
  50. data/lib/sequel/extensions/query_literals.rb +6 -6
  51. data/lib/sequel/extensions/schema_dumper.rb +39 -38
  52. data/lib/sequel/extensions/select_remove.rb +4 -0
  53. data/lib/sequel/extensions/server_block.rb +3 -2
  54. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  55. data/lib/sequel/extensions/sql_expr.rb +4 -0
  56. data/lib/sequel/extensions/string_date_time.rb +4 -0
  57. data/lib/sequel/extensions/thread_local_timezones.rb +9 -3
  58. data/lib/sequel/extensions/to_dot.rb +4 -0
  59. data/lib/sequel/model/associations.rb +150 -91
  60. data/lib/sequel/plugins/identity_map.rb +2 -2
  61. data/lib/sequel/plugins/list.rb +1 -0
  62. data/lib/sequel/plugins/many_through_many.rb +33 -32
  63. data/lib/sequel/plugins/nested_attributes.rb +11 -3
  64. data/lib/sequel/plugins/rcte_tree.rb +2 -2
  65. data/lib/sequel/plugins/schema.rb +1 -1
  66. data/lib/sequel/sql.rb +14 -14
  67. data/lib/sequel/version.rb +2 -2
  68. data/spec/adapters/mysql_spec.rb +25 -0
  69. data/spec/adapters/postgres_spec.rb +572 -28
  70. data/spec/adapters/sqlite_spec.rb +16 -1
  71. data/spec/core/database_spec.rb +61 -2
  72. data/spec/core/dataset_spec.rb +92 -0
  73. data/spec/core/expression_filters_spec.rb +12 -0
  74. data/spec/extensions/arbitrary_servers_spec.rb +1 -1
  75. data/spec/extensions/boolean_readers_spec.rb +25 -25
  76. data/spec/extensions/eval_inspect_spec.rb +58 -0
  77. data/spec/extensions/json_serializer_spec.rb +0 -6
  78. data/spec/extensions/list_spec.rb +1 -1
  79. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  80. data/spec/extensions/many_through_many_spec.rb +81 -0
  81. data/spec/extensions/nested_attributes_spec.rb +21 -4
  82. data/spec/extensions/pg_array_ops_spec.rb +1 -11
  83. data/spec/extensions/pg_array_spec.rb +181 -90
  84. data/spec/extensions/pg_auto_parameterize_spec.rb +3 -3
  85. data/spec/extensions/pg_hstore_spec.rb +1 -3
  86. data/spec/extensions/pg_inet_spec.rb +6 -1
  87. data/spec/extensions/pg_interval_spec.rb +73 -0
  88. data/spec/extensions/pg_json_spec.rb +5 -9
  89. data/spec/extensions/pg_range_ops_spec.rb +49 -0
  90. data/spec/extensions/pg_range_spec.rb +372 -0
  91. data/spec/extensions/pg_statement_cache_spec.rb +1 -2
  92. data/spec/extensions/query_literals_spec.rb +1 -2
  93. data/spec/extensions/schema_dumper_spec.rb +48 -89
  94. data/spec/extensions/serialization_spec.rb +1 -5
  95. data/spec/extensions/server_block_spec.rb +2 -2
  96. data/spec/extensions/spec_helper.rb +12 -2
  97. data/spec/extensions/split_array_nil_spec.rb +24 -0
  98. data/spec/integration/associations_test.rb +4 -4
  99. data/spec/integration/database_test.rb +2 -2
  100. data/spec/integration/dataset_test.rb +4 -4
  101. data/spec/integration/eager_loader_test.rb +6 -6
  102. data/spec/integration/plugin_test.rb +2 -2
  103. data/spec/integration/spec_helper.rb +2 -2
  104. data/spec/model/association_reflection_spec.rb +5 -0
  105. data/spec/model/associations_spec.rb +156 -49
  106. data/spec/model/eager_loading_spec.rb +137 -2
  107. data/spec/model/model_spec.rb +10 -10
  108. metadata +15 -2
@@ -1,7 +1,13 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
2
2
 
3
3
  unless defined?(SQLITE_DB)
4
- SQLITE_URL = 'sqlite:/' unless defined? SQLITE_URL
4
+ unless defined? SQLITE_URL
5
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
6
+ SQLITE_URL = 'jdbc:sqlite::memory:'
7
+ else
8
+ SQLITE_URL = 'sqlite:/'
9
+ end
10
+ end
5
11
  SQLITE_DB = Sequel.connect(ENV['SEQUEL_SQLITE_SPEC_DB']||SQLITE_URL)
6
12
  end
7
13
  INTEGRATION_DB = SQLITE_DB unless defined?(INTEGRATION_DB)
@@ -49,6 +55,15 @@ describe "An SQLite database" do
49
55
  @db.get(Sequel.like('a', 'A')).to_s.should == '0'
50
56
  end
51
57
 
58
+ specify "should support casting to Date by using the date function" do
59
+ @db.get('2012-10-20 11:12:13'.cast(Date)).should == '2012-10-20'
60
+ end
61
+
62
+ specify "should support casting to Time or DateTime by using the datetime function" do
63
+ @db.get('2012-10-20'.cast(Time)).should == '2012-10-20 00:00:00'
64
+ @db.get('2012-10-20'.cast(DateTime)).should == '2012-10-20 00:00:00'
65
+ end
66
+
52
67
  specify "should provide the SQLite version as an integer" do
53
68
  @db.sqlite_version.should be_a_kind_of(Integer)
54
69
  end
@@ -1985,8 +1985,9 @@ end
1985
1985
  describe "Database#schema_autoincrementing_primary_key?" do
1986
1986
  specify "should indicate whether the parsed schema row indicates a primary key" do
1987
1987
  m = Sequel::Database.new.method(:schema_autoincrementing_primary_key?)
1988
- m.call(:primary_key=>true).should == true
1989
- m.call(:primary_key=>false).should == false
1988
+ m.call(:primary_key=>true, :db_type=>'integer').should == true
1989
+ m.call(:primary_key=>true, :db_type=>'varchar(255)').should == false
1990
+ m.call(:primary_key=>false, :db_type=>'integer').should == false
1990
1991
  end
1991
1992
  end
1992
1993
 
@@ -2124,3 +2125,61 @@ describe "Database#column_schema_to_ruby_default" do
2124
2125
  p["((-12.1))", :decimal].should == BigDecimal.new('-12.1')
2125
2126
  end
2126
2127
  end
2128
+
2129
+ describe "Database extensions" do
2130
+ before(:all) do
2131
+ class << Sequel
2132
+ alias _extension extension
2133
+ def extension(*)
2134
+ end
2135
+ end
2136
+ end
2137
+ after(:all) do
2138
+ class << Sequel
2139
+ alias extension _extension
2140
+ end
2141
+ end
2142
+ before do
2143
+ @db = Sequel.mock
2144
+ end
2145
+
2146
+ specify "should be able to register an extension with a module Database#extension extend the module" do
2147
+ Sequel::Database.register_extension(:foo, Module.new{def a; 1; end})
2148
+ @db.extension(:foo).a.should == 1
2149
+ end
2150
+
2151
+ specify "should be able to register an extension with a block and Database#extension call the block" do
2152
+ @db.quote_identifiers = false
2153
+ Sequel::Database.register_extension(:foo){|db| db.quote_identifiers = true}
2154
+ @db.extension(:foo).quote_identifiers?.should be_true
2155
+ end
2156
+
2157
+ specify "should be able to register an extension with a callable and Database#extension call the callable" do
2158
+ @db.quote_identifiers = false
2159
+ Sequel::Database.register_extension(:foo, proc{|db| db.quote_identifiers = true})
2160
+ @db.extension(:foo).quote_identifiers?.should be_true
2161
+ end
2162
+
2163
+ specify "should be able to load multiple extensions in the same call" do
2164
+ @db.quote_identifiers = false
2165
+ @db.identifier_input_method = :downcase
2166
+ Sequel::Database.register_extension(:foo, proc{|db| db.quote_identifiers = true})
2167
+ Sequel::Database.register_extension(:bar, proc{|db| db.identifier_input_method = nil})
2168
+ @db.extension(:foo, :bar)
2169
+ @db.quote_identifiers?.should be_true
2170
+ @db.identifier_input_method.should be_nil
2171
+ end
2172
+
2173
+ specify "should return the receiver" do
2174
+ Sequel::Database.register_extension(:foo, Module.new{def a; 1; end})
2175
+ @db.extension(:foo).should equal(@db)
2176
+ end
2177
+
2178
+ specify "should raise an Error if registering with both a module and a block" do
2179
+ proc{Sequel::Database.register_extension(:foo, Module.new){}}.should raise_error(Sequel::Error)
2180
+ end
2181
+
2182
+ specify "should raise an Error if attempting to load an incompatible extension" do
2183
+ proc{@db.extension(:foo2)}.should raise_error(Sequel::Error)
2184
+ end
2185
+ end
@@ -2152,6 +2152,14 @@ describe "Dataset#join_table" do
2152
2152
  @d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
2153
2153
  end
2154
2154
 
2155
+ specify "should not qualify if :qualify=>false option is given" do
2156
+ @d.from('stats').join(:players, {:id => :player_id}, :qualify=>false).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" ON ("id" = "player_id")'
2157
+ end
2158
+
2159
+ specify "should do deep qualification if :qualify=>:deep option is given" do
2160
+ @d.from('stats').join(:players, {Sequel.function(:f, :id) => Sequel.subscript(:player_id, 0)}, :qualify=>:deep).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" ON (f("players"."id") = "stats"."player_id"[0])'
2161
+ end
2162
+
2155
2163
  specify "should allow for arbitrary conditions in the JOIN clause" do
2156
2164
  @d.join_table(:left_outer, :categories, :status => 0).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" = 0)'
2157
2165
  @d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categorizable_type" = \'Post\')'
@@ -4306,3 +4314,87 @@ describe "Dataset feature defaults" do
4306
4314
  Sequel::Database.new.dataset.send(:offset_returns_row_number_column?).should be_false
4307
4315
  end
4308
4316
  end
4317
+
4318
+ describe "Dataset extensions" do
4319
+ before(:all) do
4320
+ class << Sequel
4321
+ alias _extension extension
4322
+ def extension(*)
4323
+ end
4324
+ end
4325
+ end
4326
+ after(:all) do
4327
+ class << Sequel
4328
+ alias extension _extension
4329
+ end
4330
+ end
4331
+ before do
4332
+ @ds = Sequel::Dataset.new(nil)
4333
+ end
4334
+
4335
+ specify "should be able to register an extension with a module Database#extension extend the module" do
4336
+ Sequel::Dataset.register_extension(:foo, Module.new{def a; 1; end})
4337
+ @ds.extension(:foo).a.should == 1
4338
+ end
4339
+
4340
+ specify "should be able to register an extension with a block and Database#extension call the block" do
4341
+ @ds.quote_identifiers = false
4342
+ Sequel::Dataset.register_extension(:foo){|db| db.quote_identifiers = true}
4343
+ @ds.extension(:foo).quote_identifiers?.should be_true
4344
+ end
4345
+
4346
+ specify "should be able to register an extension with a callable and Database#extension call the callable" do
4347
+ @ds.quote_identifiers = false
4348
+ Sequel::Dataset.register_extension(:foo, proc{|db| db.quote_identifiers = true})
4349
+ @ds.extension(:foo).quote_identifiers?.should be_true
4350
+ end
4351
+
4352
+ specify "should be able to load multiple extensions in the same call" do
4353
+ @ds.quote_identifiers = false
4354
+ @ds.identifier_input_method = :downcase
4355
+ Sequel::Dataset.register_extension(:foo, proc{|ds| ds.quote_identifiers = true})
4356
+ Sequel::Dataset.register_extension(:bar, proc{|ds| ds.identifier_input_method = nil})
4357
+ ds = @ds.extension(:foo, :bar)
4358
+ ds.quote_identifiers?.should be_true
4359
+ ds.identifier_input_method.should be_nil
4360
+ end
4361
+
4362
+ specify "should have #extension not modify the receiver" do
4363
+ Sequel::Dataset.register_extension(:foo, Module.new{def a; 1; end})
4364
+ @ds.extension(:foo)
4365
+ proc{@ds.a}.should raise_error(NoMethodError)
4366
+ end
4367
+
4368
+ specify "should have #extension not return a cloned dataset" do
4369
+ @ds.extend(Module.new{def b; 2; end})
4370
+ Sequel::Dataset.register_extension(:foo, Module.new{def a; 1; end})
4371
+ v = @ds.extension(:foo)
4372
+ v.should_not equal(@ds)
4373
+ v.should be_a_kind_of(Sequel::Dataset)
4374
+ v.b.should == 2
4375
+ end
4376
+
4377
+ specify "should have #extension! modify the receiver" do
4378
+ Sequel::Dataset.register_extension(:foo, Module.new{def a; 1; end})
4379
+ @ds.extension!(:foo)
4380
+ @ds.a.should == 1
4381
+ end
4382
+
4383
+ specify "should have #extension! return the receiver" do
4384
+ Sequel::Dataset.register_extension(:foo, Module.new{def a; 1; end})
4385
+ @ds.extension!(:foo).should equal(@ds)
4386
+ end
4387
+
4388
+ specify "should register a Database extension for modifying all datasets when registering with a module" do
4389
+ Sequel::Dataset.register_extension(:foo, Module.new{def a; 1; end})
4390
+ Sequel.mock.extension(:foo).dataset.a.should == 1
4391
+ end
4392
+
4393
+ specify "should raise an Error if registering with both a module and a block" do
4394
+ proc{Sequel::Dataset.register_extension(:foo, Module.new){}}.should raise_error(Sequel::Error)
4395
+ end
4396
+
4397
+ specify "should raise an Error if attempting to load an incompatible extension" do
4398
+ proc{@ds.extension(:foo2)}.should raise_error(Sequel::Error)
4399
+ end
4400
+ end
@@ -1042,3 +1042,15 @@ describe Sequel::SQL::Subscript do
1042
1042
  @ds.literal(s).should == 'a[1][2]'
1043
1043
  end
1044
1044
  end
1045
+
1046
+ describe "Sequel.recursive_map" do
1047
+ specify "should recursively convert an array using a callable" do
1048
+ Sequel.recursive_map(['1'], proc{|s| s.to_i}).should == [1]
1049
+ Sequel.recursive_map([['1']], proc{|s| s.to_i}).should == [[1]]
1050
+ end
1051
+
1052
+ specify "should not call callable if value is nil" do
1053
+ Sequel.recursive_map([nil], proc{|s| s.to_i}).should == [nil]
1054
+ Sequel.recursive_map([[nil]], proc{|s| s.to_i}).should == [[nil]]
1055
+ end
1056
+ end
@@ -3,7 +3,7 @@ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
3
3
  describe "arbtirary servers" do
4
4
  before do
5
5
  @db = Sequel.mock(:servers=>{})
6
- @db.pool.extend Sequel::ArbitraryServers
6
+ @db.extension :arbitrary_servers
7
7
  end
8
8
 
9
9
  specify "should allow arbitrary server options using a hash" do
@@ -4,12 +4,12 @@ describe Sequel::Model, "BooleanReaders plugin" do
4
4
  before do
5
5
  @db = Sequel::Database.new({})
6
6
  def @db.schema(*args)
7
- [[:id, {}], [:y, {:type=>:integer, :db_type=>'tinyint(1)'}], [:b, {:type=>:boolean, :db_type=>'boolean'}]]
7
+ [[:id, {}], [:z, {:type=>:integer, :db_type=>'tinyint(1)'}], [:b, {:type=>:boolean, :db_type=>'boolean'}]]
8
8
  end
9
9
 
10
10
  @c = Class.new(Sequel::Model(@db[:items]))
11
11
  @p = proc do
12
- @columns = [:id, :b, :y]
12
+ @columns = [:id, :b, :z]
13
13
  def columns; @columns; end
14
14
  end
15
15
  @c.instance_eval(&@p)
@@ -29,7 +29,7 @@ describe Sequel::Model, "BooleanReaders plugin" do
29
29
 
30
30
  specify "should not create attribute? readers for non-boolean attributes" do
31
31
  @c.plugin(:boolean_readers)
32
- proc{@c.new.y?}.should raise_error(NoMethodError)
32
+ proc{@c.new.z?}.should raise_error(NoMethodError)
33
33
  proc{@c.new.id?}.should raise_error(NoMethodError)
34
34
  end
35
35
 
@@ -37,17 +37,17 @@ describe Sequel::Model, "BooleanReaders plugin" do
37
37
  @c.plugin(:boolean_readers){|c| db_schema[c][:db_type] == 'tinyint(1)'}
38
38
  proc{@c.new.b?}.should raise_error(NoMethodError)
39
39
  o = @c.new
40
- o.y.should == nil
41
- o.y?.should == nil
42
- o.y = '1'
43
- o.y.should == 1
44
- o.y?.should == true
45
- o.y = '0'
46
- o.y.should == 0
47
- o.y?.should == false
48
- o.y = ''
49
- o.y.should == nil
50
- o.y?.should == nil
40
+ o.z.should == nil
41
+ o.z?.should == nil
42
+ o.z = '1'
43
+ o.z.should == 1
44
+ o.z?.should == true
45
+ o.z = '0'
46
+ o.z.should == 0
47
+ o.z?.should == false
48
+ o.z = ''
49
+ o.z.should == nil
50
+ o.z?.should == nil
51
51
  end
52
52
 
53
53
  specify "should create boolean readers when set_dataset is defined" do
@@ -70,17 +70,17 @@ describe Sequel::Model, "BooleanReaders plugin" do
70
70
  c.plugin(:boolean_readers){|x| db_schema[x][:db_type] == 'tinyint(1)'}
71
71
  c.set_dataset(@db[:a])
72
72
  o = c.new
73
- o.y.should == nil
74
- o.y?.should == nil
75
- o.y = '1'
76
- o.y.should == 1
77
- o.y?.should == true
78
- o.y = '0'
79
- o.y.should == 0
80
- o.y?.should == false
81
- o.y = ''
82
- o.y.should == nil
83
- o.y?.should == nil
73
+ o.z.should == nil
74
+ o.z?.should == nil
75
+ o.z = '1'
76
+ o.z.should == 1
77
+ o.z?.should == true
78
+ o.z = '0'
79
+ o.z.should == 0
80
+ o.z?.should == false
81
+ o.z = ''
82
+ o.z.should == nil
83
+ o.z?.should == nil
84
84
  proc{o.b?}.should raise_error(NoMethodError)
85
85
  end
86
86
 
@@ -0,0 +1,58 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "eval_inspect extension" do
4
+ before do
5
+ @ds = Sequel.mock.dataset
6
+ @ds.meta_def(:supports_window_functions?){true}
7
+ @ds.meta_def(:literal_blob_append){|sql, s| sql << "X'#{s}'"}
8
+ end
9
+
10
+ specify "should make eval(obj.inspect) == obj for all Sequel::SQL::Expression subclasses" do
11
+ [
12
+ # Objects with components where eval(inspect) == self
13
+ Sequel::SQL::AliasedExpression.new(:b, :a),
14
+ Sequel::SQL::CaseExpression.new({:b=>:a}, :c),
15
+ Sequel::SQL::CaseExpression.new({:b=>:a}, :c, :d),
16
+ Sequel::SQL::Cast.new(:a, :b),
17
+ Sequel::SQL::ColumnAll.new(:a),
18
+ Sequel::SQL::ComplexExpression.new(:'=', :b, :a),
19
+ Sequel::SQL::Constant.new(:a),
20
+ Sequel::SQL::Function.new(:a, :b, :c),
21
+ Sequel::SQL::Identifier.new(:a),
22
+ Sequel::SQL::JoinClause.new(:inner, :b, :c),
23
+ Sequel::SQL::JoinOnClause.new({:d=>:a}, :inner, :b, :c),
24
+ Sequel::SQL::JoinUsingClause.new([:a], :inner, :b, :c),
25
+ Sequel::SQL::PlaceholderLiteralString.new('? = ?', [:a, :b]),
26
+ Sequel::SQL::PlaceholderLiteralString.new(':a = :b', [{:a=>:b, :b=>42}]),
27
+ Sequel::SQL::OrderedExpression.new(:a),
28
+ Sequel::SQL::OrderedExpression.new(:a, false),
29
+ Sequel::SQL::OrderedExpression.new(:a, false, :nulls=>:first),
30
+ Sequel::SQL::OrderedExpression.new(:a, false, :nulls=>:last),
31
+ Sequel::SQL::QualifiedIdentifier.new(:b, :a),
32
+ Sequel::SQL::Subscript.new(:a, [1, 2]),
33
+ Sequel::SQL::Window.new(:order=>:a, :partition=>:b),
34
+ Sequel::SQL::WindowFunction.new(Sequel::SQL::Function.new(:a, :b, :c), Sequel::SQL::Window.new(:order=>:a, :partition=>:b)),
35
+ Sequel::SQL::Wrapper.new(:a),
36
+
37
+ # Objects with components where eval(inspect) != self
38
+ Sequel::SQL::AliasedExpression.new(Sequel::SQL::Blob.new('s'), :a),
39
+ Sequel::SQL::AliasedExpression.new(Sequel::LiteralString.new('s'), :a),
40
+ Sequel::SQL::PlaceholderLiteralString.new('(a, b) IN ?', [Sequel::SQL::ValueList.new([[1, 2]])]),
41
+ Sequel::SQL::CaseExpression.new({{:d=>Sequel::LiteralString.new('e')}=>:a}, :c, :d),
42
+ Sequel::SQL::AliasedExpression.new(Date.new(2011, 10, 11), :a),
43
+ Sequel::SQL::AliasedExpression.new(Sequel::SQLTime.create(10, 20, 30, 500000.125), :a),
44
+ Sequel::SQL::AliasedExpression.new(DateTime.new(2011, 9, 11, 10, 20, 30), :a),
45
+ Sequel::SQL::AliasedExpression.new(DateTime.new(2011, 9, 11, 10, 20, 30, 0.25), :a),
46
+ Sequel::SQL::AliasedExpression.new(DateTime.new(2011, 9, 11, 10, 20, 30, -0.25), :a),
47
+ Sequel::SQL::AliasedExpression.new(Time.local(2011, 9, 11, 10, 20, 30), :a),
48
+ Sequel::SQL::AliasedExpression.new(Time.local(2011, 9, 11, 10, 20, 30, 500000.125), :a),
49
+ Sequel::SQL::AliasedExpression.new(Time.utc(2011, 9, 11, 10, 20, 30), :a),
50
+ Sequel::SQL::AliasedExpression.new(Time.utc(2011, 9, 11, 10, 20, 30, 500000.125), :a),
51
+ Sequel::SQL::AliasedExpression.new(BigDecimal.new('1.000000000000000000000000000000000000000000000001'), :a),
52
+ ].each do |o|
53
+ v = eval(o.inspect)
54
+ v.should == o
55
+ @ds.literal(v).should == @ds.literal(o)
56
+ end
57
+ end
58
+ end
@@ -1,10 +1,5 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
- begin
4
- require 'json'
5
- rescue LoadError => e
6
- skip_warn "json_serializer plugin: can't load json (#{e.class}: #{e})"
7
- else
8
3
  describe "Sequel::Plugins::JsonSerializer" do
9
4
  before do
10
5
  class ::Artist < Sequel::Model
@@ -205,4 +200,3 @@ describe "Sequel::Plugins::JsonSerializer" do
205
200
  Object.send(:remove_const, :Artist3)
206
201
  end
207
202
  end
208
- end
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), '/spec_helper')
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper')
2
2
 
3
3
  describe "List plugin" do
4
4
  def klass(opts={})
@@ -4,18 +4,18 @@ describe "LooserTypecasting Extension" do
4
4
  before do
5
5
  @db = Sequel::Database.new({})
6
6
  def @db.schema(*args)
7
- [[:id, {}], [:y, {:type=>:float}], [:b, {:type=>:integer}]]
7
+ [[:id, {}], [:z, {:type=>:float}], [:b, {:type=>:integer}]]
8
8
  end
9
9
  @c = Class.new(Sequel::Model(@db[:items]))
10
10
  @c.instance_eval do
11
- @columns = [:id, :b, :y]
11
+ @columns = [:id, :b, :z]
12
12
  def columns; @columns; end
13
13
  end
14
14
  end
15
15
 
16
16
  specify "Should use to_i instead of Integer() for typecasting integers" do
17
17
  proc{@c.new(:b=>'a')}.should raise_error(Sequel::InvalidValue)
18
- @db.extend(Sequel::LooserTypecasting)
18
+ @db.extension(:looser_typecasting)
19
19
  @c.new(:b=>'a').b.should == 0
20
20
 
21
21
  o = Object.new
@@ -26,14 +26,14 @@ describe "LooserTypecasting Extension" do
26
26
  end
27
27
 
28
28
  specify "Should use to_f instead of Float() for typecasting floats" do
29
- proc{@c.new(:y=>'a')}.should raise_error(Sequel::InvalidValue)
30
- @db.extend(Sequel::LooserTypecasting)
31
- @c.new(:y=>'a').y.should == 0.0
29
+ proc{@c.new(:z=>'a')}.should raise_error(Sequel::InvalidValue)
30
+ @db.extension(:looser_typecasting)
31
+ @c.new(:z=>'a').z.should == 0.0
32
32
 
33
33
  o = Object.new
34
34
  def o.to_f
35
35
  1.0
36
36
  end
37
- @c.new(:y=>o).y.should == 1.0
37
+ @c.new(:z=>o).z.should == 1.0
38
38
  end
39
39
  end
@@ -21,6 +21,41 @@ describe Sequel::Model, "many_through_many" do
21
21
  Object.send(:remove_const, :Tag)
22
22
  end
23
23
 
24
+ it "should populate :key_hash and :id_map option correctly for custom eager loaders" do
25
+ khs = []
26
+ pr = proc{|h| khs << [h[:key_hash], h[:id_map]]}
27
+ @c1.many_through_many :tags, :through=>[[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :eager_loader=>pr
28
+ @c1.eager(:tags).all
29
+ khs.should == [[{:id=>{1=>[Artist.load(:x=>1, :id=>1)]}}, {1=>[Artist.load(:x=>1, :id=>1)]}]]
30
+
31
+ khs.clear
32
+ @c1.many_through_many :tags, :through=>[[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :left_primary_key=>:id, :left_primary_key_column=>:i, :eager_loader=>pr
33
+ @c1.eager(:tags).all
34
+ khs.should == [[{:id=>{1=>[Artist.load(:x=>1, :id=>1)]}}, {1=>[Artist.load(:x=>1, :id=>1)]}]]
35
+ end
36
+
37
+ it "should support using a custom :left_primary_key option when eager loading many_to_many associations" do
38
+ @c1.send(:define_method, :id3){id*3}
39
+ @c1.dataset._fetch = {:id=>1}
40
+ @c2.dataset._fetch = {:id=>4, :x_foreign_key_x=>3}
41
+ @c1.many_through_many :tags, :through=>[[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :left_primary_key=>:id3
42
+ a = @c1.eager(:tags).all
43
+ a.should == [@c1.load(:id => 1)]
44
+ MODEL_DB.sqls.should == ['SELECT * FROM artists', "SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (3)))"]
45
+ a.first.tags.should == [@c2.load(:id=>4)]
46
+ MODEL_DB.sqls.should == []
47
+ end
48
+
49
+ it "should handle a :eager_loading_predicate_key option to change the SQL used in the lookup" do
50
+ @c1.dataset._fetch = {:id=>1}
51
+ @c2.dataset._fetch = {:id=>4, :x_foreign_key_x=>1}
52
+ @c1.many_through_many :tags, :through=>[[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :eager_loading_predicate_key=>Sequel./(:albums_artists__artist_id, 3)
53
+ a = @c1.eager(:tags).all
54
+ a.should == [@c1.load(:id => 1)]
55
+ MODEL_DB.sqls.should == ['SELECT * FROM artists', "SELECT tags.*, (albums_artists.artist_id / 3) AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND ((albums_artists.artist_id / 3) IN (1)))"]
56
+ a.first.tags.should == [@c2.load(:id=>4)]
57
+ end
58
+
24
59
  it "should default to associating to other models in the same scope" do
25
60
  begin
26
61
  class ::AssociationModuleTest
@@ -598,6 +633,7 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
598
633
  it "should respect the :limit option on a many_through_many association with composite primary keys on the main table using a :window_function strategy" do
599
634
  Tag.dataset.meta_def(:supports_window_functions?){true}
600
635
  @c1.set_primary_key([:id1, :id2])
636
+ @c1.columns :id1, :id2
601
637
  @c1.many_through_many :first_two_tags, [[:albums_artists, [:artist_id1, :artist_id2], :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>Tag, :limit=>2, :eager_limit_strategy=>true, :order=>:name
602
638
  @c1.dataset._fetch = [{:id1=>1, :id2=>2}]
603
639
  Tag.dataset._fetch = [{:x_foreign_key_0_x=>1, :x_foreign_key_1_x=>2, :id=>5}, {:x_foreign_key_0_x=>1, :x_foreign_key_1_x=>2, :id=>6}]
@@ -934,3 +970,48 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
934
970
  @c1.eager_graph(:tags, :albums).sql.should == 'SELECT artists.id, tags.id AS tags_id, albums_0.id AS albums_0_id FROM artists LEFT OUTER JOIN albums_artists ON ((albums_artists.artist_id = artists.id) AND (albums_artists.a = artists.b)) LEFT OUTER JOIN albums ON (albums.id = albums_artists.album_id) LEFT OUTER JOIN albums_tags ON (albums_tags.album_id = albums.id) LEFT OUTER JOIN tags ON (tags.id = albums_tags.tag_id) LEFT OUTER JOIN albums_artists AS albums_artists_0 ON ((albums_artists_0.artist_id = artists.id) AND (albums_artists_0.c = artists.d)) LEFT OUTER JOIN albums AS albums_0 ON (albums_0.id = albums_artists_0.album_id)'
935
971
  end
936
972
  end
973
+
974
+ describe "many_through_many associations with non-column expression keys" do
975
+ before do
976
+ @db = Sequel.mock(:fetch=>{:id=>1, :object_ids=>[2]})
977
+ @Foo = Class.new(Sequel::Model(@db[:foos]))
978
+ @Foo.columns :id, :object_ids
979
+ @Foo.plugin :many_through_many
980
+ m = Module.new{def obj_id; object_ids[0]; end}
981
+ @Foo.include m
982
+
983
+ @Foo.many_through_many :foos, [
984
+ [:f, Sequel.subscript(:l, 0), Sequel.subscript(:r, 0)],
985
+ [:f, Sequel.subscript(:l, 1), Sequel.subscript(:r, 1)]
986
+ ], :class=>@Foo, :left_primary_key=>:obj_id, :left_primary_key_column=>Sequel.subscript(:object_ids, 0), :right_primary_key=>Sequel.subscript(:object_ids, 0), :right_primary_key_method=>:obj_id
987
+ @foo = @Foo.load(:id=>1, :object_ids=>[2])
988
+ @db.sqls
989
+ end
990
+
991
+ it "should have working regular association methods" do
992
+ @Foo.first.foos.should == [@foo]
993
+ @db.sqls.should == ["SELECT * FROM foos LIMIT 1", "SELECT foos.* FROM foos INNER JOIN f ON (f.r[1] = foos.object_ids[0]) INNER JOIN f AS f_0 ON ((f_0.r[0] = f.l[1]) AND (f_0.l[0] = 2))"]
994
+ end
995
+
996
+ it "should have working eager loading methods" do
997
+ @db.fetch = [[{:id=>1, :object_ids=>[2]}], [{:id=>1, :object_ids=>[2], :x_foreign_key_x=>2}]]
998
+ @Foo.eager(:foos).all.map{|o| [o, o.foos]}.should == [[@foo, [@foo]]]
999
+ @db.sqls.should == ["SELECT * FROM foos", "SELECT foos.*, f_0.l[0] AS x_foreign_key_x FROM foos INNER JOIN f ON (f.r[1] = foos.object_ids[0]) INNER JOIN f AS f_0 ON ((f_0.r[0] = f.l[1]) AND (f_0.l[0] IN (2)))"]
1000
+ end
1001
+
1002
+ it "should have working eager graphing methods" do
1003
+ @db.fetch = {:id=>1, :object_ids=>[2], :foos_0_id=>1, :foos_0_object_ids=>[2]}
1004
+ @Foo.eager_graph(:foos).all.map{|o| [o, o.foos]}.should == [[@foo, [@foo]]]
1005
+ @db.sqls.should == ["SELECT foos.id, foos.object_ids, foos_0.id AS foos_0_id, foos_0.object_ids AS foos_0_object_ids FROM foos LEFT OUTER JOIN f ON (f.l[0] = foos.object_ids[0]) LEFT OUTER JOIN f AS f_0 ON (f_0.l[1] = f.r[0]) LEFT OUTER JOIN foos AS foos_0 ON (foos_0.object_ids[0] = f_0.r[1])"]
1006
+ end
1007
+
1008
+ it "should have working filter by associations with model instances" do
1009
+ @Foo.first(:foos=>@foo).should == @foo
1010
+ @db.sqls.should == ["SELECT * FROM foos WHERE (foos.object_ids[0] IN (SELECT f.l[0] FROM f INNER JOIN f AS f_0 ON (f_0.l[1] = f.r[0]) WHERE ((f_0.r[1] = 2) AND (f.l[0] IS NOT NULL)))) LIMIT 1"]
1011
+ end
1012
+
1013
+ it "should have working filter by associations with model datasets" do
1014
+ @Foo.first(:foos=>@Foo.where(:id=>@foo.id)).should == @foo
1015
+ @db.sqls.should == ["SELECT * FROM foos WHERE (foos.object_ids[0] IN (SELECT f.l[0] FROM f INNER JOIN f AS f_0 ON (f_0.l[1] = f.r[0]) WHERE ((f_0.r[1] IN (SELECT foos.object_ids[0] FROM foos WHERE ((id = 1) AND (foos.object_ids[0] IS NOT NULL)))) AND (f.l[0] IS NOT NULL)))) LIMIT 1"]
1016
+ end
1017
+ end