sequel 3.39.0 → 3.40.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +30 -0
- data/README.rdoc +4 -3
- data/doc/active_record.rdoc +1 -1
- data/doc/opening_databases.rdoc +7 -0
- data/doc/release_notes/3.40.0.txt +73 -0
- data/lib/sequel/adapters/ado.rb +29 -3
- data/lib/sequel/adapters/ado/access.rb +334 -0
- data/lib/sequel/adapters/ado/mssql.rb +0 -6
- data/lib/sequel/adapters/cubrid.rb +143 -0
- data/lib/sequel/adapters/jdbc.rb +26 -18
- data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
- data/lib/sequel/adapters/jdbc/derby.rb +7 -7
- data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -4
- data/lib/sequel/adapters/mysql.rb +0 -3
- data/lib/sequel/adapters/mysql2.rb +0 -3
- data/lib/sequel/adapters/oracle.rb +4 -1
- data/lib/sequel/adapters/postgres.rb +4 -4
- data/lib/sequel/adapters/shared/access.rb +205 -3
- data/lib/sequel/adapters/shared/cubrid.rb +216 -0
- data/lib/sequel/adapters/shared/db2.rb +7 -2
- data/lib/sequel/adapters/shared/mssql.rb +3 -34
- data/lib/sequel/adapters/shared/mysql.rb +4 -33
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +11 -0
- data/lib/sequel/adapters/shared/oracle.rb +5 -0
- data/lib/sequel/adapters/shared/postgres.rb +2 -1
- data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/query.rb +30 -7
- data/lib/sequel/database/schema_methods.rb +7 -2
- data/lib/sequel/dataset/query.rb +9 -10
- data/lib/sequel/dataset/sql.rb +14 -26
- data/lib/sequel/extensions/pg_hstore.rb +19 -0
- data/lib/sequel/extensions/pg_row.rb +5 -5
- data/lib/sequel/plugins/association_pks.rb +121 -18
- data/lib/sequel/plugins/json_serializer.rb +19 -0
- data/lib/sequel/sql.rb +11 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +42 -0
- data/spec/core/database_spec.rb +17 -0
- data/spec/core/dataset_spec.rb +11 -0
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/extensions/association_pks_spec.rb +163 -3
- data/spec/extensions/pg_hstore_spec.rb +6 -0
- data/spec/extensions/pg_row_spec.rb +17 -0
- data/spec/integration/associations_test.rb +1 -1
- data/spec/integration/dataset_test.rb +13 -13
- data/spec/integration/plugin_test.rb +232 -7
- data/spec/integration/schema_test.rb +8 -12
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/type_test.rb +6 -0
- metadata +9 -2
@@ -61,6 +61,25 @@ module Sequel
|
|
61
61
|
#
|
62
62
|
# Album.to_json(:array=>[Album[1], Album[2]])
|
63
63
|
#
|
64
|
+
# Note that active_support/json makes incompatible changes to the to_json API,
|
65
|
+
# and breaks some aspects of the json_serializer plugin. You can undo the damage
|
66
|
+
# done by active_support/json by doing:
|
67
|
+
#
|
68
|
+
# class Array
|
69
|
+
# def to_json(options = {})
|
70
|
+
# JSON.generate(self)
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# class Hash
|
75
|
+
# def to_json(options = {})
|
76
|
+
# JSON.generate(self)
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# Note that this will probably cause active_support/json to no longer work
|
81
|
+
# correctly in some cases.
|
82
|
+
#
|
64
83
|
# Usage:
|
65
84
|
#
|
66
85
|
# # Add JSON output capability to all model subclass instances (called before loading subclasses)
|
data/lib/sequel/sql.rb
CHANGED
@@ -1030,6 +1030,17 @@ module Sequel
|
|
1030
1030
|
!@no_expression
|
1031
1031
|
end
|
1032
1032
|
|
1033
|
+
# Merge the CASE expression into the conditions, useful for databases that
|
1034
|
+
# don't support CASE expressions.
|
1035
|
+
def with_merged_expression
|
1036
|
+
if expression?
|
1037
|
+
e = expression
|
1038
|
+
CaseExpression.new(conditions.map{|c, r| [::Sequel::SQL::BooleanExpression.new(:'=', e, c), r]}, default)
|
1039
|
+
else
|
1040
|
+
self
|
1041
|
+
end
|
1042
|
+
end
|
1043
|
+
|
1033
1044
|
to_s_method :case_expression_sql
|
1034
1045
|
end
|
1035
1046
|
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
6
|
+
MINOR = 40
|
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
|
@@ -1320,6 +1320,26 @@ if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && POSTGRE
|
|
1320
1320
|
end.should == 'foo'
|
1321
1321
|
called.should be_true
|
1322
1322
|
|
1323
|
+
# Check weird identifier names
|
1324
|
+
called = false
|
1325
|
+
@db.listen('FOO bar', :after_listen=>proc{@db.notify('FOO bar')}) do |ev, pid, payload|
|
1326
|
+
ev.should == 'FOO bar'
|
1327
|
+
pid.should == notify_pid
|
1328
|
+
['', nil].should include(payload)
|
1329
|
+
called = true
|
1330
|
+
end.should == 'FOO bar'
|
1331
|
+
called.should be_true
|
1332
|
+
|
1333
|
+
# Check identifier symbols
|
1334
|
+
called = false
|
1335
|
+
@db.listen(:foo, :after_listen=>proc{@db.notify(:foo)}) do |ev, pid, payload|
|
1336
|
+
ev.should == 'foo'
|
1337
|
+
pid.should == notify_pid
|
1338
|
+
['', nil].should include(payload)
|
1339
|
+
called = true
|
1340
|
+
end.should == 'foo'
|
1341
|
+
called.should be_true
|
1342
|
+
|
1323
1343
|
called = false
|
1324
1344
|
@db.listen('foo', :after_listen=>proc{@db.notify('foo', :payload=>'bar')}) do |ev, pid, payload|
|
1325
1345
|
ev.should == 'foo'
|
@@ -1592,6 +1612,11 @@ describe 'PostgreSQL array handling' do
|
|
1592
1612
|
@ds.filter(:i=>:$i).call(:first, :i=>[1,2]).should == {:i=>[1,2]}
|
1593
1613
|
@ds.filter(:i=>:$i).call(:first, :i=>[1,3]).should == nil
|
1594
1614
|
|
1615
|
+
# NULL values
|
1616
|
+
@ds.delete
|
1617
|
+
@ds.call(:insert, {:i=>[nil,nil]}, {:i=>:$i})
|
1618
|
+
@ds.first.should == {:i=>[nil, nil]}
|
1619
|
+
|
1595
1620
|
@db.create_table!(:items) do
|
1596
1621
|
column :i, 'text[]'
|
1597
1622
|
end
|
@@ -1751,6 +1776,10 @@ describe 'PostgreSQL hstore handling' do
|
|
1751
1776
|
@ds.filter(:i=>:$i).call(:first, :i=>Sequel.hstore(@h)).should == {:i=>@h}
|
1752
1777
|
@ds.filter(:i=>:$i).call(:first, :i=>Sequel.hstore({})).should == nil
|
1753
1778
|
|
1779
|
+
@ds.delete
|
1780
|
+
@ds.call(:insert, {:i=>Sequel.hstore('a'=>nil)}, {:i=>:$i})
|
1781
|
+
@ds.get(:i).should == Sequel.hstore('a'=>nil)
|
1782
|
+
|
1754
1783
|
@ds.delete
|
1755
1784
|
@ds.call(:insert, {:i=>@h}, {:i=>:$i})
|
1756
1785
|
@ds.get(:i).should == @h
|
@@ -2014,6 +2043,10 @@ describe 'PostgreSQL json type' do
|
|
2014
2043
|
@ds.filter(Sequel.cast(:i, String)=>:$i).call(:first, :i=>Sequel.pg_json(@a)).should == {:i=>@a}
|
2015
2044
|
@ds.filter(Sequel.cast(:i, String)=>:$i).call(:first, :i=>Sequel.pg_json([])).should == nil
|
2016
2045
|
|
2046
|
+
@ds.delete
|
2047
|
+
@ds.call(:insert, {:i=>Sequel.pg_json('a'=>nil)}, {:i=>:$i})
|
2048
|
+
@ds.get(:i).should == Sequel.pg_json('a'=>nil)
|
2049
|
+
|
2017
2050
|
@db.create_table!(:items){column :i, 'json[]'}
|
2018
2051
|
j = Sequel.pg_array([Sequel.pg_json('a'=>1), Sequel.pg_json(['b', 2])], :text)
|
2019
2052
|
@ds.call(:insert, {:i=>j}, {:i=>:$i})
|
@@ -2499,6 +2532,10 @@ describe 'PostgreSQL row-valued/composite types' do
|
|
2499
2532
|
@ds.get(:address).should == {:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}
|
2500
2533
|
@ds.filter(:address=>Sequel.cast(:$address, :address)).call(:first, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345']))[:id].should == 1
|
2501
2534
|
@ds.filter(:address=>Sequel.cast(:$address, :address)).call(:first, :address=>Sequel.pg_row(['123 Sesame St', 'Somewhere', '12356'])).should == nil
|
2535
|
+
|
2536
|
+
@ds.delete
|
2537
|
+
@ds.call(:insert, {:address=>Sequel.pg_row([nil, nil, nil])}, {:address=>:$address, :id=>1})
|
2538
|
+
@ds.get(:address).should == {:street=>nil, :city=>nil, :zip=>nil}
|
2502
2539
|
end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
|
2503
2540
|
|
2504
2541
|
specify 'use arrays of row types in bound variables' do
|
@@ -2507,6 +2544,11 @@ describe 'PostgreSQL row-valued/composite types' do
|
|
2507
2544
|
@ds.get(:company).should == {:id=>1, :employees=>[{:id=>1, :address=>{:street=>'123 Sesame St', :city=>'Somewhere', :zip=>'12345'}}]}
|
2508
2545
|
@ds.filter(:employees=>Sequel.cast(:$employees, 'person[]')).call(:first, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12345'])])]))[:id].should == 1
|
2509
2546
|
@ds.filter(:employees=>Sequel.cast(:$employees, 'person[]')).call(:first, :employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row(['123 Sesame St', 'Somewhere', '12356'])])])).should == nil
|
2547
|
+
|
2548
|
+
|
2549
|
+
@ds.delete
|
2550
|
+
@ds.call(:insert, {:employees=>Sequel.pg_array([@db.row_type(:person, [1, Sequel.pg_row([nil, nil, nil])])])}, {:employees=>:$employees, :id=>1})
|
2551
|
+
@ds.get(:employees).should == [{:address=>{:city=>nil, :zip=>nil, :street=>nil}, :id=>1}]
|
2510
2552
|
end if POSTGRES_DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG
|
2511
2553
|
|
2512
2554
|
specify 'operations/functions with pg_row_ops' do
|
data/spec/core/database_spec.rb
CHANGED
@@ -617,6 +617,18 @@ shared_examples_for "Database#transaction" do
|
|
617
617
|
'BEGIN', 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE', 'DROP TABLE serializable', 'COMMIT']
|
618
618
|
end
|
619
619
|
|
620
|
+
specify "should support :disconnect=>:retry option for automatically retrying on disconnect" do
|
621
|
+
a = []
|
622
|
+
@db.transaction(:disconnect=>:retry){a << 1; raise Sequel::DatabaseDisconnectError if a.length < 2}
|
623
|
+
@db.sqls.should == ['BEGIN', 'ROLLBACK', 'BEGIN', 'COMMIT']
|
624
|
+
a.should == [1, 1]
|
625
|
+
end
|
626
|
+
|
627
|
+
specify "should raise an error if attempting to use :disconnect=>:retry inside another transaction" do
|
628
|
+
proc{@db.transaction{@db.transaction(:disconnect=>:retry){}}}.should raise_error(Sequel::Error)
|
629
|
+
@db.sqls.should == ['BEGIN', 'ROLLBACK']
|
630
|
+
end
|
631
|
+
|
620
632
|
specify "should handle returning inside of the block by committing" do
|
621
633
|
def @db.ret_commit
|
622
634
|
transaction do
|
@@ -2067,12 +2079,16 @@ describe "Database#column_schema_to_ruby_default" do
|
|
2067
2079
|
db = Sequel::Database.new
|
2068
2080
|
p = lambda{|d,t| db.send(:column_schema_to_ruby_default, d, t)}
|
2069
2081
|
p[nil, :integer].should be_nil
|
2082
|
+
p[1, :integer].should == 1
|
2070
2083
|
p['1', :integer].should == 1
|
2071
2084
|
p['-1', :integer].should == -1
|
2085
|
+
p[1.0, :float].should == 1.0
|
2072
2086
|
p['1.0', :float].should == 1.0
|
2073
2087
|
p['-1.0', :float].should == -1.0
|
2074
2088
|
p['1.0', :decimal].should == BigDecimal.new('1.0')
|
2075
2089
|
p['-1.0', :decimal].should == BigDecimal.new('-1.0')
|
2090
|
+
p[true, :boolean].should == true
|
2091
|
+
p[false, :boolean].should == false
|
2076
2092
|
p['1', :boolean].should == true
|
2077
2093
|
p['0', :boolean].should == false
|
2078
2094
|
p['true', :boolean].should == true
|
@@ -2085,6 +2101,7 @@ describe "Database#column_schema_to_ruby_default" do
|
|
2085
2101
|
p["''", :string].should == ''
|
2086
2102
|
p["'\\a''b'", :string].should == "\\a'b"
|
2087
2103
|
p["'NULL'", :string].should == "NULL"
|
2104
|
+
p[Date.today, :date].should == Date.today
|
2088
2105
|
p["'2009-10-29'", :date].should == Date.new(2009,10,29)
|
2089
2106
|
p["CURRENT_TIMESTAMP", :date].should == Sequel::CURRENT_DATE
|
2090
2107
|
p["CURRENT_DATE", :date].should == Sequel::CURRENT_DATE
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -3020,6 +3020,11 @@ describe "Dataset#insert_sql" do
|
|
3020
3020
|
@ds.insert_sql('c' => 'd').should == "INSERT INTO items (c) VALUES ('d')"
|
3021
3021
|
end
|
3022
3022
|
|
3023
|
+
specify "should quote string keys" do
|
3024
|
+
@ds.quote_identifiers = true
|
3025
|
+
@ds.insert_sql('c' => 'd').should == "INSERT INTO \"items\" (\"c\") VALUES ('d')"
|
3026
|
+
end
|
3027
|
+
|
3023
3028
|
specify "should accept array subscript references" do
|
3024
3029
|
@ds.insert_sql((Sequel.subscript(:day, 1)) => 'd').should == "INSERT INTO items (day[1]) VALUES ('d')"
|
3025
3030
|
end
|
@@ -3666,6 +3671,12 @@ describe "Sequel::Dataset #with and #with_recursive" do
|
|
3666
3671
|
@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c]).sql.should == 'WITH t(b, c) AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
|
3667
3672
|
end
|
3668
3673
|
|
3674
|
+
specify "#with and #with_recursive should quote the columns in the :args option" do
|
3675
|
+
@ds.quote_identifiers = true
|
3676
|
+
@ds.with(:t, @db[:x], :args=>[:b]).sql.should == 'WITH "t"("b") AS (SELECT * FROM x) SELECT * FROM "t"'
|
3677
|
+
@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c]).sql.should == 'WITH "t"("b", "c") AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM "t"'
|
3678
|
+
end
|
3679
|
+
|
3669
3680
|
specify "#with_recursive should take an :union_all=>false option" do
|
3670
3681
|
@ds.with_recursive(:t, @db[:x], @db[:t], :union_all=>false).sql.should == 'WITH t AS (SELECT * FROM x UNION SELECT * FROM t) SELECT * FROM t'
|
3671
3682
|
end
|
@@ -942,6 +942,19 @@ describe Sequel::SQL::Subscript do
|
|
942
942
|
end
|
943
943
|
end
|
944
944
|
|
945
|
+
describe Sequel::SQL::CaseExpression, "#with_merged_expression" do
|
946
|
+
specify "should return self if it has no expression" do
|
947
|
+
c = Sequel.case({1=>0}, 3)
|
948
|
+
c.with_merged_expression.should equal(c)
|
949
|
+
end
|
950
|
+
|
951
|
+
specify "should merge expression into conditions if it has an expression" do
|
952
|
+
db = Sequel::Database.new
|
953
|
+
c = Sequel.case({1=>0}, 3, 4)
|
954
|
+
db.literal(c.with_merged_expression).should == db.literal(Sequel.case({{4=>1}=>0}, 3))
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
945
958
|
describe "Sequel.recursive_map" do
|
946
959
|
specify "should recursively convert an array using a callable" do
|
947
960
|
Sequel.recursive_map(['1'], proc{|s| s.to_i}).should == [1]
|
@@ -12,16 +12,49 @@ describe "Sequel::Plugins::AssociationPks" do
|
|
12
12
|
a << {:tag_id=>2} if $1 != '3'
|
13
13
|
a << {:tag_id=>3} if $1 == '2'
|
14
14
|
a
|
15
|
+
when "SELECT first, last FROM vocalists WHERE (vocalists.album_id = 1)"
|
16
|
+
[{:first=>"F1", :last=>"L1"}, {:first=>"F2", :last=>"L2"}]
|
17
|
+
when /SELECT first, last FROM albums_vocalists WHERE \(album_id = (\d)\)/
|
18
|
+
a = []
|
19
|
+
a << {:first=>"F1", :last=>"L1"} if $1 == '1'
|
20
|
+
a << {:first=>"F2", :last=>"L2"} if $1 != '3'
|
21
|
+
a << {:first=>"F3", :last=>"L3"} if $1 == '2'
|
22
|
+
a
|
23
|
+
when "SELECT id FROM instruments WHERE ((instruments.first = 'F1') AND (instruments.last = 'L1'))"
|
24
|
+
[{:id=>1}, {:id=>2}]
|
25
|
+
when /SELECT instrument_id FROM vocalists_instruments WHERE \(\((?:first|last) = '?[FL1](\d)/
|
26
|
+
a = []
|
27
|
+
a << {:instrument_id=>1} if $1 == "1"
|
28
|
+
a << {:instrument_id=>2} if $1 != "3"
|
29
|
+
a << {:instrument_id=>3} if $1 == "2"
|
30
|
+
a
|
31
|
+
when "SELECT year, week FROM hits WHERE ((hits.first = 'F1') AND (hits.last = 'L1'))"
|
32
|
+
[{:year=>1997, :week=>1}, {:year=>1997, :week=>2}]
|
33
|
+
when /SELECT year, week FROM vocalists_hits WHERE \(\((?:first|last) = '?[FL1](\d)/
|
34
|
+
a = []
|
35
|
+
a << {:year=>1997, :week=>1} if $1 == "1"
|
36
|
+
a << {:year=>1997, :week=>2} if $1 != "3"
|
37
|
+
a << {:year=>1997, :week=>3} if $1 == "2"
|
38
|
+
a
|
15
39
|
end
|
16
40
|
end)
|
17
41
|
@Artist = Class.new(Sequel::Model(@db[:artists]))
|
18
42
|
@Artist.columns :id
|
19
|
-
@Album= Class.new(Sequel::Model(@db[:albums]))
|
43
|
+
@Album = Class.new(Sequel::Model(@db[:albums]))
|
20
44
|
@Album.columns :id, :artist_id
|
21
45
|
@Tag = Class.new(Sequel::Model(@db[:tags]))
|
22
46
|
@Tag.columns :id
|
47
|
+
@Vocalist = Class.new(Sequel::Model(@db[:vocalists]))
|
48
|
+
@Vocalist.columns :first, :last, :album_id
|
49
|
+
@Vocalist.set_primary_key [:first, :last]
|
50
|
+
@Instrument = Class.new(Sequel::Model(@db[:instruments]))
|
51
|
+
@Instrument.columns :id, :first, :last
|
52
|
+
@Hit = Class.new(Sequel::Model(@db[:hits]))
|
53
|
+
@Hit.columns :year, :week, :first, :last
|
54
|
+
@Hit.set_primary_key [:year, :week]
|
23
55
|
@Artist.plugin :association_pks
|
24
56
|
@Album.plugin :association_pks
|
57
|
+
@Vocalist.plugin :association_pks
|
25
58
|
@Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
|
26
59
|
@Album.many_to_many :tags, :class=>@Tag, :join_table=>:albums_tags, :left_key=>:album_id
|
27
60
|
@db.sqls
|
@@ -60,6 +93,103 @@ describe "Sequel::Plugins::AssociationPks" do
|
|
60
93
|
sqls.length.should == 3
|
61
94
|
end
|
62
95
|
|
96
|
+
specify "should return correct right-side associated cpks for one_to_many associations" do
|
97
|
+
@Album.one_to_many :vocalists, :class=>@Vocalist, :key=>:album_id
|
98
|
+
@Album.load(:id=>1).vocalist_pks.should == [["F1", "L1"], ["F2", "L2"]]
|
99
|
+
@Album.load(:id=>2).vocalist_pks.should == []
|
100
|
+
end
|
101
|
+
|
102
|
+
specify "should return correct right-side associated cpks for many_to_many associations" do
|
103
|
+
@Album.many_to_many :vocalists, :class=>@Vocalist, :join_table=>:albums_vocalists, :left_key=>:album_id, :right_key=>[:first, :last]
|
104
|
+
@Album.load(:id=>1).vocalist_pks.should == [["F1", "L1"], ["F2", "L2"]]
|
105
|
+
@Album.load(:id=>2).vocalist_pks.should == [["F2", "L2"], ["F3", "L3"]]
|
106
|
+
@Album.load(:id=>3).vocalist_pks.should == []
|
107
|
+
end
|
108
|
+
|
109
|
+
specify "should set associated right-side cpks correctly for a one_to_many association" do
|
110
|
+
@Album.one_to_many :vocalists, :class=>@Vocalist, :key=>:album_id
|
111
|
+
@Album.load(:id=>1).vocalist_pks = [["F1", "L1"], ["F2", "L2"]]
|
112
|
+
@db.sqls.should == ["UPDATE vocalists SET album_id = 1 WHERE ((first, last) IN (('F1', 'L1'), ('F2', 'L2')))",
|
113
|
+
"UPDATE vocalists SET album_id = NULL WHERE ((vocalists.album_id = 1) AND ((first, last) NOT IN (('F1', 'L1'), ('F2', 'L2'))))"]
|
114
|
+
end
|
115
|
+
|
116
|
+
specify "should set associated right-side cpks correctly for a many_to_many association" do
|
117
|
+
@Album.many_to_many :vocalists, :class=>@Vocalist, :join_table=>:albums_vocalists, :left_key=>:album_id, :right_key=>[:first, :last]
|
118
|
+
@Album.load(:id=>2).vocalist_pks = [["F1", "L1"], ["F2", "L2"]]
|
119
|
+
sqls = @db.sqls
|
120
|
+
sqls[0].should == "DELETE FROM albums_vocalists WHERE ((album_id = 2) AND ((first, last) NOT IN (('F1', 'L1'), ('F2', 'L2'))))"
|
121
|
+
sqls[1].should == 'SELECT first, last FROM albums_vocalists WHERE (album_id = 2)'
|
122
|
+
match = sqls[2].match(/INSERT INTO albums_vocalists \((.*)\) VALUES \((.*)\)/)
|
123
|
+
Hash[match[1].split(', ').zip(match[2].split(', '))].should == {"first"=>"'F1'", "last"=>"'L1'", "album_id"=>"2"}
|
124
|
+
sqls.length.should == 3
|
125
|
+
end
|
126
|
+
|
127
|
+
specify "should return correct associated pks for left-side cpks for one_to_many associations" do
|
128
|
+
@Vocalist.one_to_many :instruments, :class=>@Instrument, :key=>[:first, :last]
|
129
|
+
@Vocalist.load(:first=>'F1', :last=>'L1').instrument_pks.should == [1, 2]
|
130
|
+
@Vocalist.load(:first=>'F2', :last=>'L2').instrument_pks.should == []
|
131
|
+
end
|
132
|
+
|
133
|
+
specify "should return correct associated pks for left-side cpks for many_to_many associations" do
|
134
|
+
@Vocalist.many_to_many :instruments, :class=>@Instrument, :join_table=>:vocalists_instruments, :left_key=>[:first, :last]
|
135
|
+
@Vocalist.load(:first=>'F1', :last=>'L1').instrument_pks.should == [1, 2]
|
136
|
+
@Vocalist.load(:first=>'F2', :last=>'L2').instrument_pks.should == [2, 3]
|
137
|
+
@Vocalist.load(:first=>'F3', :last=>'L3').instrument_pks.should == []
|
138
|
+
end
|
139
|
+
|
140
|
+
specify "should set associated pks correctly for left-side cpks for a one_to_many association" do
|
141
|
+
@Vocalist.one_to_many :instruments, :class=>@Instrument, :key=>[:first, :last]
|
142
|
+
@Vocalist.load(:first=>'F1', :last=>'L1').instrument_pks = [1, 2]
|
143
|
+
sqls = @db.sqls
|
144
|
+
sqls[0].should =~ /UPDATE instruments SET (first = 'F1', last = 'L1'|last = 'L1', first = 'F1') WHERE \(id IN \(1, 2\)\)/
|
145
|
+
sqls[1].should =~ /UPDATE instruments SET (first = NULL, last = NULL|last = NULL, first = NULL) WHERE \(\(instruments.first = 'F1'\) AND \(instruments.last = 'L1'\) AND \(id NOT IN \(1, 2\)\)\)/
|
146
|
+
sqls.length.should == 2
|
147
|
+
end
|
148
|
+
|
149
|
+
specify "should set associated pks correctly for left-side cpks for a many_to_many association" do
|
150
|
+
@Vocalist.many_to_many :instruments, :class=>@Instrument, :join_table=>:vocalists_instruments, :left_key=>[:first, :last]
|
151
|
+
@Vocalist.load(:first=>'F2', :last=>'L2').instrument_pks = [1, 2]
|
152
|
+
sqls = @db.sqls
|
153
|
+
sqls[0].should == "DELETE FROM vocalists_instruments WHERE ((first = 'F2') AND (last = 'L2') AND (instrument_id NOT IN (1, 2)))"
|
154
|
+
sqls[1].should == "SELECT instrument_id FROM vocalists_instruments WHERE ((first = 'F2') AND (last = 'L2'))"
|
155
|
+
match = sqls[2].match(/INSERT INTO vocalists_instruments \((.*)\) VALUES \((.*)\)/)
|
156
|
+
Hash[match[1].split(', ').zip(match[2].split(', '))].should == {"first"=>"'F2'", "last"=>"'L2'", "instrument_id"=>"1"}
|
157
|
+
sqls.length.should == 3
|
158
|
+
end
|
159
|
+
|
160
|
+
specify "should return correct right-side associated cpks for left-side cpks for one_to_many associations" do
|
161
|
+
@Vocalist.one_to_many :hits, :class=>@Hit, :key=>[:first, :last]
|
162
|
+
@Vocalist.load(:first=>'F1', :last=>'L1').hit_pks.should == [[1997, 1], [1997, 2]]
|
163
|
+
@Vocalist.load(:first=>'F2', :last=>'L2').hit_pks.should == []
|
164
|
+
end
|
165
|
+
|
166
|
+
specify "should return correct right-side associated cpks for left-side cpks for many_to_many associations" do
|
167
|
+
@Vocalist.many_to_many :hits, :class=>@Hit, :join_table=>:vocalists_hits, :left_key=>[:first, :last], :right_key=>[:year, :week]
|
168
|
+
@Vocalist.load(:first=>'F1', :last=>'L1').hit_pks.should == [[1997, 1], [1997, 2]]
|
169
|
+
@Vocalist.load(:first=>'F2', :last=>'L2').hit_pks.should == [[1997, 2], [1997, 3]]
|
170
|
+
@Vocalist.load(:first=>'F3', :last=>'L3').hit_pks.should == []
|
171
|
+
end
|
172
|
+
|
173
|
+
specify "should set associated right-side cpks correctly for left-side cpks for a one_to_many association" do
|
174
|
+
@Vocalist.one_to_many :hits, :class=>@Hit, :key=>[:first, :last], :order=>:week
|
175
|
+
@Vocalist.load(:first=>'F1', :last=>'L1').hit_pks = [[1997, 1], [1997, 2]]
|
176
|
+
sqls = @db.sqls
|
177
|
+
sqls[0].should =~ /UPDATE hits SET (first = 'F1', last = 'L1'|last = 'L1', first = 'F1') WHERE \(\(year, week\) IN \(\(1997, 1\), \(1997, 2\)\)\)/
|
178
|
+
sqls[1].should =~ /UPDATE hits SET (first = NULL, last = NULL|last = NULL, first = NULL) WHERE \(\(hits.first = 'F1'\) AND \(hits.last = 'L1'\) AND \(\(year, week\) NOT IN \(\(1997, 1\), \(1997, 2\)\)\)\)/
|
179
|
+
sqls.length.should == 2
|
180
|
+
end
|
181
|
+
|
182
|
+
specify "should set associated right-side cpks correctly for left-side cpks for a many_to_many association" do
|
183
|
+
@Vocalist.many_to_many :hits, :class=>@Hit, :join_table=>:vocalists_hits, :left_key=>[:first, :last], :right_key=>[:year, :week]
|
184
|
+
@Vocalist.load(:first=>'F2', :last=>'L2').hit_pks = [[1997, 1], [1997, 2]]
|
185
|
+
sqls = @db.sqls
|
186
|
+
sqls[0].should == "DELETE FROM vocalists_hits WHERE ((first = 'F2') AND (last = 'L2') AND ((year, week) NOT IN ((1997, 1), (1997, 2))))"
|
187
|
+
sqls[1].should == "SELECT year, week FROM vocalists_hits WHERE ((first = 'F2') AND (last = 'L2'))"
|
188
|
+
match = sqls[2].match(/INSERT INTO vocalists_hits \((.*)\) VALUES \((.*)\)/)
|
189
|
+
Hash[match[1].split(', ').zip(match[2].split(', '))].should == {"first"=>"'F2'", "last"=>"'L2'", "year"=>"1997", "week"=>"1"}
|
190
|
+
sqls.length.should == 3
|
191
|
+
end
|
192
|
+
|
63
193
|
specify "should use transactions if the object is configured to use transactions" do
|
64
194
|
artist = @Artist.load(:id=>1)
|
65
195
|
artist.use_transactions = true
|
@@ -88,14 +218,14 @@ describe "Sequel::Plugins::AssociationPks" do
|
|
88
218
|
"UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN (1, 2)))"]
|
89
219
|
end
|
90
220
|
|
91
|
-
specify "should not automatically convert keys if the primary key is not an integer for
|
221
|
+
specify "should not automatically convert keys if the primary key is not an integer for one_to_many associations" do
|
92
222
|
@Album.db_schema[:id][:type] = :string
|
93
223
|
@Artist.load(:id=>1).album_pks = %w'1 2'
|
94
224
|
@db.sqls.should == ["UPDATE albums SET artist_id = 1 WHERE (id IN ('1', '2'))",
|
95
225
|
"UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN ('1', '2')))"]
|
96
226
|
end
|
97
227
|
|
98
|
-
specify "should automatically convert keys to numbers if the primary key is an integer for
|
228
|
+
specify "should automatically convert keys to numbers if the primary key is an integer for many_to_many associations" do
|
99
229
|
@Tag.db_schema[:id][:type] = :integer
|
100
230
|
@Album.load(:id=>2).tag_pks = %w'1 3'
|
101
231
|
sqls = @db.sqls
|
@@ -116,4 +246,34 @@ describe "Sequel::Plugins::AssociationPks" do
|
|
116
246
|
sqls.length.should == 4
|
117
247
|
end
|
118
248
|
|
249
|
+
specify "should automatically convert keys to numbers for appropriate integer primary key for composite key associations" do
|
250
|
+
@Hit.db_schema[:year][:type] = :integer
|
251
|
+
@Hit.db_schema[:week][:type] = :integer
|
252
|
+
@Vocalist.many_to_many :hits, :class=>@Hit, :join_table=>:vocalists_hits, :left_key=>[:first, :last], :right_key=>[:year, :week]
|
253
|
+
@Vocalist.load(:first=>'F2', :last=>'L2').hit_pks = [['1997', '1'], ['1997', '2']]
|
254
|
+
sqls = @db.sqls
|
255
|
+
sqls[0].should == "DELETE FROM vocalists_hits WHERE ((first = 'F2') AND (last = 'L2') AND ((year, week) NOT IN ((1997, 1), (1997, 2))))"
|
256
|
+
sqls[1].should == "SELECT year, week FROM vocalists_hits WHERE ((first = 'F2') AND (last = 'L2'))"
|
257
|
+
match = sqls[2].match(/INSERT INTO vocalists_hits \((.*)\) VALUES \((.*)\)/)
|
258
|
+
Hash[match[1].split(', ').zip(match[2].split(', '))].should == {"first"=>"'F2'", "last"=>"'L2'", "year"=>"1997", "week"=>"1"}
|
259
|
+
sqls.length.should == 3
|
260
|
+
|
261
|
+
@Vocalist.db_schema[:first][:type] = :integer
|
262
|
+
@Vocalist.db_schema[:last][:type] = :integer
|
263
|
+
@Album.one_to_many :vocalists, :class=>@Vocalist, :key=>:album_id
|
264
|
+
@Album.load(:id=>1).vocalist_pks = [["11", "11"], ["12", "12"]]
|
265
|
+
@db.sqls.should == ["UPDATE vocalists SET album_id = 1 WHERE ((first, last) IN ((11, 11), (12, 12)))",
|
266
|
+
"UPDATE vocalists SET album_id = NULL WHERE ((vocalists.album_id = 1) AND ((first, last) NOT IN ((11, 11), (12, 12))))"]
|
267
|
+
|
268
|
+
@Album.many_to_many :vocalists, :class=>@Vocalist, :join_table=>:albums_vocalists, :left_key=>:album_id, :right_key=>[:first, :last]
|
269
|
+
@Album.load(:id=>2).vocalist_pks = [["11", "11"], ["12", "12"]]
|
270
|
+
sqls = @db.sqls
|
271
|
+
sqls[0].should == "DELETE FROM albums_vocalists WHERE ((album_id = 2) AND ((first, last) NOT IN ((11, 11), (12, 12))))"
|
272
|
+
sqls[1].should == 'SELECT first, last FROM albums_vocalists WHERE (album_id = 2)'
|
273
|
+
match = sqls[2].match(/INSERT INTO albums_vocalists \((.*)\) VALUES \((.*)\)/)
|
274
|
+
Hash[match[1].split(', ').zip(match[2].split(', '))].should == {"first"=>"11", "last"=>"11", "album_id"=>"2"}
|
275
|
+
match = sqls[3].match(/INSERT INTO albums_vocalists \((.*)\) VALUES \((.*)\)/)
|
276
|
+
Hash[match[1].split(', ').zip(match[2].split(', '))].should == {"first"=>"12", "last"=>"12", "album_id"=>"2"}
|
277
|
+
sqls.length.should == 4
|
278
|
+
end
|
119
279
|
end
|
@@ -191,4 +191,10 @@ describe "pg_hstore extension" do
|
|
191
191
|
@db.typecast_value(:hstore, {'a'=>'b'}).should == Sequel.hstore("a"=>"b")
|
192
192
|
proc{@db.typecast_value(:hstore, [])}.should raise_error(Sequel::InvalidValue)
|
193
193
|
end
|
194
|
+
|
195
|
+
it "should be serializable" do
|
196
|
+
v = Sequel.hstore('foo'=>'bar')
|
197
|
+
dump = Marshal.dump(v)
|
198
|
+
Marshal.load(dump).should == v
|
199
|
+
end
|
194
200
|
end
|
@@ -158,6 +158,13 @@ describe "pg_row extension" do
|
|
158
158
|
@db.bound_variable_arg([@m::HashRow.subclass(nil, [:a, :b]).call(:a=>"1", :b=>"abc\\'\",")], nil).should == '{"(\\"1\\",\\"abc\\\\\\\\\'\\\\\\",\\")"}'
|
159
159
|
end
|
160
160
|
|
161
|
+
it "should handle nils in bound variables" do
|
162
|
+
@db.bound_variable_arg(@m::ArrayRow.call([nil, nil]), nil).should == '(,)'
|
163
|
+
@db.bound_variable_arg(@m::HashRow.subclass(nil, [:a, :b]).call(:a=>nil, :b=>nil), nil).should == '(,)'
|
164
|
+
@db.bound_variable_arg([@m::ArrayRow.call([nil, nil])], nil).should == '{"(,)"}'
|
165
|
+
@db.bound_variable_arg([@m::HashRow.subclass(nil, [:a, :b]).call(:a=>nil, :b=>nil)], nil).should == '{"(,)"}'
|
166
|
+
end
|
167
|
+
|
161
168
|
it "should allow registering row type parsers by introspecting system tables" do
|
162
169
|
@db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
163
170
|
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
@@ -247,6 +254,16 @@ describe "pg_row extension" do
|
|
247
254
|
@db.conversion_procs[1].call('(1,b)').should == [{:bar=>1, :baz=>'bb'}]
|
248
255
|
end
|
249
256
|
|
257
|
+
it "should handle nil values when converting columns" do
|
258
|
+
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|
259
|
+
@db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}]]
|
260
|
+
called = false
|
261
|
+
@db.conversion_procs[4] = proc{|s| called = true; s}
|
262
|
+
@db.register_row_type(:foo)
|
263
|
+
@db.conversion_procs[1].call('()').should == {:bar=>nil}
|
264
|
+
called.should be_false
|
265
|
+
end
|
266
|
+
|
250
267
|
it "should registering array type for row type if type has an array oid" do
|
251
268
|
@db.conversion_procs[4] = p4 = proc{|s| s.to_i}
|
252
269
|
@db.conversion_procs[5] = p5 = proc{|s| s * 2}
|