sequel 3.39.0 → 3.40.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.
- 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}
|