sequel 3.33.0 → 3.34.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +140 -0
- data/Rakefile +7 -0
- data/bin/sequel +22 -2
- data/doc/dataset_basics.rdoc +1 -1
- data/doc/mass_assignment.rdoc +3 -1
- data/doc/querying.rdoc +28 -4
- data/doc/reflection.rdoc +23 -3
- data/doc/release_notes/3.34.0.txt +671 -0
- data/doc/schema_modification.rdoc +18 -2
- data/doc/virtual_rows.rdoc +49 -0
- data/lib/sequel/adapters/do/mysql.rb +0 -5
- data/lib/sequel/adapters/ibmdb.rb +9 -4
- data/lib/sequel/adapters/jdbc.rb +9 -4
- data/lib/sequel/adapters/jdbc/h2.rb +8 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
- data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
- data/lib/sequel/adapters/mock.rb +24 -3
- data/lib/sequel/adapters/mysql.rb +29 -50
- data/lib/sequel/adapters/mysql2.rb +13 -28
- data/lib/sequel/adapters/oracle.rb +8 -2
- data/lib/sequel/adapters/postgres.rb +115 -20
- data/lib/sequel/adapters/shared/db2.rb +1 -1
- data/lib/sequel/adapters/shared/mssql.rb +14 -3
- data/lib/sequel/adapters/shared/mysql.rb +59 -11
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
- data/lib/sequel/adapters/shared/oracle.rb +1 -1
- data/lib/sequel/adapters/shared/postgres.rb +127 -30
- data/lib/sequel/adapters/shared/sqlite.rb +55 -38
- data/lib/sequel/adapters/sqlite.rb +9 -3
- data/lib/sequel/adapters/swift.rb +2 -2
- data/lib/sequel/adapters/swift/mysql.rb +0 -5
- data/lib/sequel/adapters/swift/postgres.rb +10 -0
- data/lib/sequel/ast_transformer.rb +4 -0
- data/lib/sequel/connection_pool.rb +8 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
- data/lib/sequel/connection_pool/single.rb +5 -0
- data/lib/sequel/connection_pool/threaded.rb +14 -0
- data/lib/sequel/core.rb +24 -3
- data/lib/sequel/database/connecting.rb +24 -14
- data/lib/sequel/database/dataset_defaults.rb +1 -0
- data/lib/sequel/database/misc.rb +16 -25
- data/lib/sequel/database/query.rb +20 -2
- data/lib/sequel/database/schema_generator.rb +2 -2
- data/lib/sequel/database/schema_methods.rb +120 -23
- data/lib/sequel/dataset/actions.rb +91 -18
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/prepared_statements.rb +6 -2
- data/lib/sequel/dataset/sql.rb +68 -51
- data/lib/sequel/extensions/_pretty_table.rb +79 -0
- data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
- data/lib/sequel/extensions/migration.rb +4 -0
- data/lib/sequel/extensions/null_dataset.rb +90 -0
- data/lib/sequel/extensions/pg_array.rb +460 -0
- data/lib/sequel/extensions/pg_array_ops.rb +220 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
- data/lib/sequel/extensions/pg_hstore.rb +296 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
- data/lib/sequel/extensions/pretty_table.rb +5 -71
- data/lib/sequel/extensions/query_literals.rb +79 -0
- data/lib/sequel/extensions/schema_caching.rb +76 -0
- data/lib/sequel/extensions/schema_dumper.rb +227 -31
- data/lib/sequel/extensions/select_remove.rb +35 -0
- data/lib/sequel/extensions/sql_expr.rb +4 -110
- data/lib/sequel/extensions/to_dot.rb +1 -1
- data/lib/sequel/model.rb +11 -2
- data/lib/sequel/model/associations.rb +35 -7
- data/lib/sequel/model/base.rb +159 -36
- data/lib/sequel/no_core_ext.rb +2 -0
- data/lib/sequel/plugins/caching.rb +25 -18
- data/lib/sequel/plugins/composition.rb +1 -1
- data/lib/sequel/plugins/hook_class_methods.rb +1 -1
- data/lib/sequel/plugins/identity_map.rb +11 -3
- data/lib/sequel/plugins/instance_filters.rb +10 -0
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
- data/lib/sequel/plugins/nested_attributes.rb +4 -3
- data/lib/sequel/plugins/prepared_statements.rb +3 -1
- data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
- data/lib/sequel/plugins/schema.rb +7 -2
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/static_cache.rb +99 -0
- data/lib/sequel/plugins/validation_class_methods.rb +1 -1
- data/lib/sequel/sql.rb +417 -7
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/firebird_spec.rb +1 -1
- data/spec/adapters/mssql_spec.rb +12 -15
- data/spec/adapters/mysql_spec.rb +81 -23
- data/spec/adapters/postgres_spec.rb +444 -77
- data/spec/adapters/spec_helper.rb +2 -0
- data/spec/adapters/sqlite_spec.rb +8 -8
- data/spec/core/connection_pool_spec.rb +85 -0
- data/spec/core/database_spec.rb +29 -5
- data/spec/core/dataset_spec.rb +171 -3
- data/spec/core/expression_filters_spec.rb +364 -0
- data/spec/core/mock_adapter_spec.rb +17 -3
- data/spec/core/schema_spec.rb +133 -0
- data/spec/extensions/association_dependencies_spec.rb +13 -13
- data/spec/extensions/caching_spec.rb +26 -3
- data/spec/extensions/class_table_inheritance_spec.rb +2 -2
- data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
- data/spec/extensions/force_encoding_spec.rb +4 -2
- data/spec/extensions/hook_class_methods_spec.rb +5 -2
- data/spec/extensions/identity_map_spec.rb +17 -0
- data/spec/extensions/instance_filters_spec.rb +1 -1
- data/spec/extensions/lazy_attributes_spec.rb +2 -2
- data/spec/extensions/list_spec.rb +4 -4
- data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
- data/spec/extensions/migration_spec.rb +6 -2
- data/spec/extensions/nested_attributes_spec.rb +20 -0
- data/spec/extensions/null_dataset_spec.rb +85 -0
- data/spec/extensions/optimistic_locking_spec.rb +2 -2
- data/spec/extensions/pg_array_ops_spec.rb +105 -0
- data/spec/extensions/pg_array_spec.rb +196 -0
- data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
- data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
- data/spec/extensions/pg_hstore_spec.rb +195 -0
- data/spec/extensions/pg_statement_cache_spec.rb +209 -0
- data/spec/extensions/prepared_statements_spec.rb +4 -0
- data/spec/extensions/pretty_table_spec.rb +6 -0
- data/spec/extensions/query_literals_spec.rb +168 -0
- data/spec/extensions/schema_caching_spec.rb +41 -0
- data/spec/extensions/schema_dumper_spec.rb +231 -11
- data/spec/extensions/schema_spec.rb +14 -2
- data/spec/extensions/select_remove_spec.rb +38 -0
- data/spec/extensions/sharding_spec.rb +6 -6
- data/spec/extensions/skip_create_refresh_spec.rb +1 -1
- data/spec/extensions/spec_helper.rb +2 -1
- data/spec/extensions/sql_expr_spec.rb +28 -19
- data/spec/extensions/static_cache_spec.rb +145 -0
- data/spec/extensions/touch_spec.rb +1 -1
- data/spec/extensions/typecast_on_load_spec.rb +9 -1
- data/spec/integration/associations_test.rb +6 -6
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +89 -26
- data/spec/integration/migrator_test.rb +2 -3
- data/spec/integration/model_test.rb +3 -3
- data/spec/integration/plugin_test.rb +85 -22
- data/spec/integration/prepared_statement_test.rb +28 -8
- data/spec/integration/schema_test.rb +78 -7
- data/spec/integration/spec_helper.rb +1 -0
- data/spec/integration/timezone_test.rb +1 -1
- data/spec/integration/transaction_test.rb +4 -6
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/associations_spec.rb +94 -8
- data/spec/model/base_spec.rb +4 -4
- data/spec/model/hooks_spec.rb +2 -2
- data/spec/model/model_spec.rb +19 -7
- data/spec/model/record_spec.rb +135 -58
- data/spec/model/spec_helper.rb +1 -0
- metadata +35 -7
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe "pg_auto_parameterize extension" do
|
4
|
+
before do
|
5
|
+
@db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
6
|
+
@db.extend Sequel::Postgres::AutoParameterize::DatabaseMethods
|
7
|
+
@db.synchronize{|c| def c.escape_bytea(v) v*2 end}
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should automatically parameterize queries strings, blobs, numerics, dates, and times" do
|
11
|
+
pr = proc do |ds, sql, *args|
|
12
|
+
arg = args[0]
|
13
|
+
parg = args[1] || arg
|
14
|
+
s = ds.filter(:a=>arg).sql
|
15
|
+
s.should == sql
|
16
|
+
s.args.should == (parg == :nil ? nil : [parg])
|
17
|
+
end
|
18
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::int4)', 1)
|
19
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::int8)', 18446744073709551616)
|
20
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::double precision)', 1.1)
|
21
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::numeric)', BigDecimal.new('1.01'))
|
22
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::text)', "a")
|
23
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::bytea)', "a\0b".to_sequel_blob)
|
24
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = 1)', '1'.lit, :nil)
|
25
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::time)', Sequel::SQLTime.create(1, 2, 3, 500000))
|
26
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::date)', Date.today)
|
27
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::timestamp)', DateTime.new(2012, 1, 2, 3, 4, 5))
|
28
|
+
pr.call(@db[:table], 'SELECT * FROM table WHERE (a = $1::timestamp)', Time.utc(2012, 1, 2, 3, 4, 5))
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should extract parameters from query string when executing" do
|
32
|
+
@db.fetch = {:a=>1}
|
33
|
+
@db.numrows = 1
|
34
|
+
@db.autoid = 1
|
35
|
+
|
36
|
+
@db[:table].filter(:a=>1).all.should == [{:a=>1}]
|
37
|
+
@db.sqls.should == ['SELECT * FROM table WHERE (a = $1::int4) -- args: [1]']
|
38
|
+
|
39
|
+
@db[:table].filter(:a=>1).update(:b=>'a').should == 1
|
40
|
+
@db.sqls.should == ['UPDATE table SET b = $1::text WHERE (a = $2::int4) -- args: ["a", 1]']
|
41
|
+
|
42
|
+
@db[:table].filter(:a=>1).delete.should == 1
|
43
|
+
@db.sqls.should == ['DELETE FROM table WHERE (a = $1::int4) -- args: [1]']
|
44
|
+
|
45
|
+
@db[:table].insert(:a=>1).should == 1
|
46
|
+
@db.sqls.should == ['INSERT INTO table (a) VALUES ($1::int4) RETURNING id -- args: [1]']
|
47
|
+
|
48
|
+
@db.server_version = 80000
|
49
|
+
@db[:table].insert(:a=>1).should == 1
|
50
|
+
@db.sqls.should == ['INSERT INTO table (a) VALUES ($1::int4) -- args: [1]']
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not automatically paramiterize if no_auto_parameterize is used" do
|
54
|
+
@db[:table].no_auto_parameterize.filter(:a=>1).sql.should == 'SELECT * FROM table WHERE (a = 1)'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should not automatically parameterize prepared statements" do
|
58
|
+
@db[:table].filter(:a=>1, :b=>:$b).prepare(:select).sql.should =~ /SELECT \* FROM table WHERE \(\((a = 1|b = \$b)\) AND \((a = 1|b = \$b)\)\)/
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should show args with string when inspecting SQL " do
|
62
|
+
@db[:table].filter(:a=>1).sql.inspect.should == '"SELECT * FROM table WHERE (a = $1::int4); [1]"'
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe "Sequel::Postgres::HStoreOp" do
|
4
|
+
before do
|
5
|
+
@ds = Sequel.connect('mock://postgres', :quote_identifiers=>false).dataset
|
6
|
+
@h = :h.hstore
|
7
|
+
end
|
8
|
+
|
9
|
+
it "#- should use the - operator" do
|
10
|
+
@ds.literal(@h - 'a').should == "(h - 'a')"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "#- should return an HStoreOp" do
|
14
|
+
@ds.literal((@h - 'a')['a']).should == "((h - 'a') -> 'a')"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "#[] should use the -> operator" do
|
18
|
+
@ds.literal(@h['a']).should == "(h -> 'a')"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "#[] should return a string expression" do
|
22
|
+
@ds.literal(@h['a'] + 'b').should == "((h -> 'a') || 'b')"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "#concat and #merge should use the || operator" do
|
26
|
+
@ds.literal(@h.concat(:h1)).should == "(h || h1)"
|
27
|
+
@ds.literal(@h.merge(:h1)).should == "(h || h1)"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "#concat should return an HStoreOp" do
|
31
|
+
@ds.literal(@h.concat(:h1)['a']).should == "((h || h1) -> 'a')"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "#contain_all should use the ?& operator" do
|
35
|
+
@ds.literal(@h.contain_all(:h1)).should == "(h ?& h1)"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "#contain_any should use the ?| operator" do
|
39
|
+
@ds.literal(@h.contain_any(:h1)).should == "(h ?| h1)"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "#contains should use the @> operator" do
|
43
|
+
@ds.literal(@h.contains(:h1)).should == "(h @> h1)"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "#contained_by should use the <@ operator" do
|
47
|
+
@ds.literal(@h.contained_by(:h1)).should == "(h <@ h1)"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "#defined should use the defined function" do
|
51
|
+
@ds.literal(@h.defined('a')).should == "defined(h, 'a')"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "#delete should use the delete function" do
|
55
|
+
@ds.literal(@h.delete('a')).should == "delete(h, 'a')"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "#delete should return an HStoreOp" do
|
59
|
+
@ds.literal(@h.delete('a')['a']).should == "(delete(h, 'a') -> 'a')"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "#each should use the each function" do
|
63
|
+
@ds.literal(@h.each).should == "each(h)"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "#has_key? and aliases should use the ? operator" do
|
67
|
+
@ds.literal(@h.has_key?('a')).should == "(h ? 'a')"
|
68
|
+
@ds.literal(@h.key?('a')).should == "(h ? 'a')"
|
69
|
+
@ds.literal(@h.member?('a')).should == "(h ? 'a')"
|
70
|
+
@ds.literal(@h.include?('a')).should == "(h ? 'a')"
|
71
|
+
@ds.literal(@h.exist?('a')).should == "(h ? 'a')"
|
72
|
+
end
|
73
|
+
|
74
|
+
it "#hstore should return the receiver" do
|
75
|
+
@h.hstore.should equal(@h)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "#keys and #akeys should use the akeys function" do
|
79
|
+
@ds.literal(@h.keys).should == "akeys(h)"
|
80
|
+
@ds.literal(@h.akeys).should == "akeys(h)"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "#populate should use the populate_record function" do
|
84
|
+
@ds.literal(@h.populate(:a)).should == "populate_record(a, h)"
|
85
|
+
end
|
86
|
+
|
87
|
+
it "#record_set should use the #= operator" do
|
88
|
+
@ds.literal(@h.record_set(:a)).should == "(a #= h)"
|
89
|
+
end
|
90
|
+
|
91
|
+
it "#skeys should use the skeys function" do
|
92
|
+
@ds.literal(@h.skeys).should == "skeys(h)"
|
93
|
+
end
|
94
|
+
|
95
|
+
it "#slice should should use the slice function" do
|
96
|
+
@ds.literal(@h.slice(:a)).should == "slice(h, a)"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "#slice should return an HStoreOp" do
|
100
|
+
@ds.literal(@h.slice(:a)['a']).should == "(slice(h, a) -> 'a')"
|
101
|
+
end
|
102
|
+
|
103
|
+
it "#svals should use the svals function" do
|
104
|
+
@ds.literal(@h.svals).should == "svals(h)"
|
105
|
+
end
|
106
|
+
|
107
|
+
it "#to_array should use the hstore_to_array function" do
|
108
|
+
@ds.literal(@h.to_array).should == "hstore_to_array(h)"
|
109
|
+
end
|
110
|
+
|
111
|
+
it "#to_matrix should use the hstore_to_matrix function" do
|
112
|
+
@ds.literal(@h.to_matrix).should == "hstore_to_matrix(h)"
|
113
|
+
end
|
114
|
+
|
115
|
+
it "#values and #avals should use the avals function" do
|
116
|
+
@ds.literal(@h.values).should == "avals(h)"
|
117
|
+
@ds.literal(@h.avals).should == "avals(h)"
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should be able to turn expressions into hstore ops using hstore" do
|
121
|
+
@ds.literal(:a.qualify(:b).hstore['a']).should == "(b.a -> 'a')"
|
122
|
+
@ds.literal(:a.sql_function(:b).hstore['a']).should == "(a(b) -> 'a')"
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should be able to turn literal strings into hstore ops using hstore" do
|
126
|
+
@ds.literal('a'.lit.hstore['a']).should == "(a -> 'a')"
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should be able to turn symbols into hstore ops using hstore" do
|
130
|
+
@ds.literal(:a.hstore['a']).should == "(a -> 'a')"
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should allow transforming HStore instances into HStoreOp instances" do
|
134
|
+
@ds.literal({'a'=>'b'}.hstore.op['a']).should == "('\"a\"=>\"b\"'::hstore -> 'a')"
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe "pg_hstore extension" do
|
4
|
+
before do
|
5
|
+
@db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
|
6
|
+
@db.extend(Module.new{def bound_variable_arg(arg, conn) arg end})
|
7
|
+
@m = Sequel::Postgres
|
8
|
+
@c = @m::HStore
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should parse hstore strings correctly" do
|
12
|
+
@c.parse('').to_hash.should == {}
|
13
|
+
@c.parse('"a"=>"b"').to_hash.should == {'a'=>'b'}
|
14
|
+
@c.parse('"a"=>"b", "c"=>NULL').to_hash.should == {'a'=>'b', 'c'=>nil}
|
15
|
+
@c.parse('"a"=>"b", "c"=>"NULL"').to_hash.should == {'a'=>'b', 'c'=>'NULL'}
|
16
|
+
@c.parse('"a"=>"b", "c"=>"\\\\ \\"\'=>"').to_hash.should == {'a'=>'b', 'c'=>'\ "\'=>'}
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should cache parse results" do
|
20
|
+
r = @c::Parser.new('')
|
21
|
+
o = r.parse
|
22
|
+
o.should == {}
|
23
|
+
r.parse.should equal(o)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should literalize HStores to strings correctly" do
|
27
|
+
@db.literal({}.hstore).should == '\'\'::hstore'
|
28
|
+
@db.literal({"a"=>"b"}.hstore).should == '\'"a"=>"b"\'::hstore'
|
29
|
+
@db.literal({"c"=>nil}.hstore).should == '\'"c"=>NULL\'::hstore'
|
30
|
+
@db.literal({"c"=>'NULL'}.hstore).should == '\'"c"=>"NULL"\'::hstore'
|
31
|
+
@db.literal({'c'=>'\ "\'=>'}.hstore).should == '\'"c"=>"\\\\ \\"\'\'=>"\'::hstore'
|
32
|
+
['\'"a"=>"b","c"=>"d"\'::hstore', '\'"c"=>"d","a"=>"b"\'::hstore'].should include(@db.literal({"a"=>"b","c"=>"d"}.hstore))
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should have Hash#hstore method for creating HStore instances" do
|
36
|
+
{}.hstore.should be_a_kind_of(@c)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should HStore#to_hash method for getting underlying hash" do
|
40
|
+
{}.hstore.to_hash.should be_a_kind_of(Hash)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should convert keys and values to strings on creation" do
|
44
|
+
{1=>2}.hstore.to_hash.should == {"1"=>"2"}
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should convert keys and values to strings on assignment" do
|
48
|
+
v = {}.hstore
|
49
|
+
v[1] = 2
|
50
|
+
v.to_hash.should == {"1"=>"2"}
|
51
|
+
v.store(:'1', 3)
|
52
|
+
v.to_hash.should == {"1"=>"3"}
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not convert nil values to strings on creation" do
|
56
|
+
{:foo=>nil}.hstore.to_hash.should == {"foo"=>nil}
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should not convert nil values to strings on assignment" do
|
60
|
+
v = {}.hstore
|
61
|
+
v[:foo] = nil
|
62
|
+
v.to_hash.should == {"foo"=>nil}
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should convert lookups by key to string" do
|
66
|
+
{'foo'=>'bar'}.hstore[:foo].should == 'bar'
|
67
|
+
{'1'=>'bar'}.hstore[1].should == 'bar'
|
68
|
+
|
69
|
+
{'foo'=>'bar'}.hstore.fetch(:foo).should == 'bar'
|
70
|
+
{'foo'=>'bar'}.hstore.fetch(:foo2, 2).should == 2
|
71
|
+
k = nil
|
72
|
+
{'foo2'=>'bar'}.hstore.fetch(:foo){|key| k = key }.should == 'foo'
|
73
|
+
k.should == 'foo'
|
74
|
+
|
75
|
+
{'foo'=>'bar'}.hstore.has_key?(:foo).should be_true
|
76
|
+
{'foo'=>'bar'}.hstore.has_key?(:bar).should be_false
|
77
|
+
{'foo'=>'bar'}.hstore.key?(:foo).should be_true
|
78
|
+
{'foo'=>'bar'}.hstore.key?(:bar).should be_false
|
79
|
+
{'foo'=>'bar'}.hstore.member?(:foo).should be_true
|
80
|
+
{'foo'=>'bar'}.hstore.member?(:bar).should be_false
|
81
|
+
{'foo'=>'bar'}.hstore.include?(:foo).should be_true
|
82
|
+
{'foo'=>'bar'}.hstore.include?(:bar).should be_false
|
83
|
+
|
84
|
+
{'foo'=>'bar', '1'=>'2'}.hstore.values_at(:foo3, :foo, :foo2, 1).should == [nil, 'bar', nil, '2']
|
85
|
+
|
86
|
+
if RUBY_VERSION >= '1.9.0'
|
87
|
+
{'foo'=>'bar'}.hstore.assoc(:foo).should == ['foo', 'bar']
|
88
|
+
{'foo'=>'bar'}.hstore.assoc(:foo2).should == nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should convert has_value?/value? lookups to string" do
|
93
|
+
{'foo'=>'bar'}.hstore.has_value?(:bar).should be_true
|
94
|
+
{'foo'=>'bar'}.hstore.has_value?(:foo).should be_false
|
95
|
+
{'foo'=>'bar'}.hstore.value?(:bar).should be_true
|
96
|
+
{'foo'=>'bar'}.hstore.value?(:foo).should be_false
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should handle nil values in has_value?/value? lookups" do
|
100
|
+
{'foo'=>''}.hstore.has_value?('').should be_true
|
101
|
+
{'foo'=>''}.hstore.has_value?(nil).should be_false
|
102
|
+
{'foo'=>nil}.hstore.has_value?(nil).should be_true
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should have underlying hash convert lookups by key to string" do
|
106
|
+
{'foo'=>'bar'}.hstore.to_hash[:foo].should == 'bar'
|
107
|
+
{'1'=>'bar'}.hstore.to_hash[1].should == 'bar'
|
108
|
+
end
|
109
|
+
|
110
|
+
if RUBY_VERSION >= '1.9.0'
|
111
|
+
it "should convert key lookups to string" do
|
112
|
+
{'foo'=>'bar'}.hstore.key(:bar).should == 'foo'
|
113
|
+
{'foo'=>'bar'}.hstore.key(:bar2).should be_nil
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should handle nil values in key lookups" do
|
117
|
+
{'foo'=>''}.hstore.key('').should == 'foo'
|
118
|
+
{'foo'=>''}.hstore.key(nil).should == nil
|
119
|
+
{'foo'=>nil}.hstore.key(nil).should == 'foo'
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should convert rassoc lookups to string" do
|
123
|
+
{'foo'=>'bar'}.hstore.rassoc(:bar).should == ['foo', 'bar']
|
124
|
+
{'foo'=>'bar'}.hstore.rassoc(:bar2).should be_nil
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should handle nil values in rassoc lookups" do
|
128
|
+
{'foo'=>''}.hstore.rassoc('').should == ['foo', '']
|
129
|
+
{'foo'=>''}.hstore.rassoc(nil).should == nil
|
130
|
+
{'foo'=>nil}.hstore.rassoc(nil).should == ['foo', nil]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should have delete convert key to string" do
|
135
|
+
v = {'foo'=>'bar'}.hstore
|
136
|
+
v.delete(:foo).should == 'bar'
|
137
|
+
v.to_hash.should == {}
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should handle #replace with hashes that do not use strings" do
|
141
|
+
v = {'foo'=>'bar'}.hstore
|
142
|
+
v.replace(:bar=>1)
|
143
|
+
v.should be_a_kind_of(@c)
|
144
|
+
v.should == {'bar'=>'1'}
|
145
|
+
v.to_hash[:bar].should == '1'
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should handle #merge with hashes that do not use strings" do
|
149
|
+
v = {'foo'=>'bar'}.hstore.merge(:bar=>1)
|
150
|
+
v.should be_a_kind_of(@c)
|
151
|
+
v.should == {'foo'=>'bar', 'bar'=>'1'}
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should handle #merge/#update with hashes that do not use strings" do
|
155
|
+
v = {'foo'=>'bar'}.hstore
|
156
|
+
v.merge!(:bar=>1)
|
157
|
+
v.should be_a_kind_of(@c)
|
158
|
+
v.should == {'foo'=>'bar', 'bar'=>'1'}
|
159
|
+
|
160
|
+
v = {'foo'=>'bar'}.hstore
|
161
|
+
v.update(:bar=>1)
|
162
|
+
v.should be_a_kind_of(@c)
|
163
|
+
v.should == {'foo'=>'bar', 'bar'=>'1'}
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should support using hstores as bound variables" do
|
167
|
+
@db.extend @c::DatabaseMethods
|
168
|
+
@db.bound_variable_arg(1, nil).should == 1
|
169
|
+
@db.bound_variable_arg({'1'=>'2'}, nil).should == '"1"=>"2"'
|
170
|
+
@db.bound_variable_arg({'1'=>'2'}.hstore, nil).should == '"1"=>"2"'
|
171
|
+
@db.bound_variable_arg({'1'=>nil}.hstore, nil).should == '"1"=>NULL'
|
172
|
+
@db.bound_variable_arg({'1'=>"NULL"}.hstore, nil).should == '"1"=>"NULL"'
|
173
|
+
@db.bound_variable_arg({'1'=>"'\\ \"=>"}.hstore, nil).should == '"1"=>"\'\\\\ \\"=>"'
|
174
|
+
['"a"=>"b","c"=>"d"', '"c"=>"d","a"=>"b"'].should include(@db.bound_variable_arg({"a"=>"b","c"=>"d"}.hstore, nil))
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should parse hstore type from the schema correctly" do
|
178
|
+
@db.extend @c::DatabaseMethods
|
179
|
+
@db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'hstore'}]
|
180
|
+
@db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :hstore]
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should support typecasting of the various array types" do
|
184
|
+
@db.extend @c::DatabaseMethods
|
185
|
+
h = {1=>2}.hstore
|
186
|
+
@db.typecast_value(:hstore, h).should equal(h)
|
187
|
+
@db.typecast_value(:hstore, '').should be_a_kind_of(@c)
|
188
|
+
@db.typecast_value(:hstore, '').should == {}.hstore
|
189
|
+
@db.typecast_value(:hstore, '"a"=>"b"').should == {"a"=>"b"}.hstore
|
190
|
+
@db.typecast_value(:hstore, {}).should be_a_kind_of(@c)
|
191
|
+
@db.typecast_value(:hstore, {}).should == {}.hstore
|
192
|
+
@db.typecast_value(:hstore, {'a'=>'b'}).should == {"a"=>"b"}.hstore
|
193
|
+
proc{@db.typecast_value(:hstore, [])}.should raise_error(Sequel::InvalidValue)
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
Sequel::Postgres::StatementCache::PGError = Sequel::Error
|
4
|
+
|
5
|
+
describe "pg_statement_cache and pg_auto_parameterize extensions" do
|
6
|
+
before do
|
7
|
+
@dbp = proc do |opts|
|
8
|
+
@db = Sequel.connect 'mock://postgres', :quote_identifiers=>false, :statement_cache_opts=>{:max_size=>4}.merge(opts),
|
9
|
+
:after_connect=>(proc do |c|
|
10
|
+
c.extend(Module.new do
|
11
|
+
def execute_query(sql, args)
|
12
|
+
raise Sequel::Postgres::StatementCache::PGError if @db.exec_raise
|
13
|
+
@db.execute(sql, :arguments=>args, :no_eq=>true)
|
14
|
+
end
|
15
|
+
def prepare(name, sql)
|
16
|
+
raise Sequel::Postgres::StatementCache::PGError if sql =~ /prepare_raise/
|
17
|
+
@ps ||= {}
|
18
|
+
@ps[name] = sql
|
19
|
+
@db._sqls << "PREPARE #{name} AS #{sql}"
|
20
|
+
end
|
21
|
+
def exec_prepared(name, args=nil)
|
22
|
+
@db._sqls << "EXECUTE #{name} (#{@ps[name]})#{" -- args: #{args.inspect}" if args}"
|
23
|
+
end
|
24
|
+
end)
|
25
|
+
end)
|
26
|
+
@db.extend Sequel::Postgres::AutoParameterize::DatabaseMethods
|
27
|
+
@db.extend Sequel::Postgres::StatementCache::DatabaseMethods
|
28
|
+
@db.extend(Module.new do
|
29
|
+
attr_accessor :exec_raise
|
30
|
+
def _execute(c, sql, opts={})
|
31
|
+
opts[:no_eq] ? super : c.send(:execute_query, sql, opts[:arguments])
|
32
|
+
end
|
33
|
+
def _sqls
|
34
|
+
@sqls
|
35
|
+
end
|
36
|
+
def statement_cache
|
37
|
+
synchronize{|c| c.statement_cache}
|
38
|
+
end
|
39
|
+
end)
|
40
|
+
@db
|
41
|
+
end
|
42
|
+
@db = @dbp.call({})
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should automatically prepare statements executed multiple times" do
|
46
|
+
@db[:table].filter(:b=>2).all
|
47
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
48
|
+
@db.sqls.should == ["SELECT * FROM table WHERE (b = $1::int4) -- args: [2]",
|
49
|
+
"SELECT * FROM table WHERE (a = $1::int4) -- args: [0]",
|
50
|
+
"PREPARE sequel_pgap_2 AS SELECT * FROM table WHERE (a = $1::int4)",
|
51
|
+
"EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [1]",
|
52
|
+
"EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [2]"]
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should work correctly for queries without parameters" do
|
56
|
+
@db[:table].filter(:b).all
|
57
|
+
3.times{|i| @db[:table].filter(:a).all}
|
58
|
+
@db.sqls.should == ["SELECT * FROM table WHERE b",
|
59
|
+
"SELECT * FROM table WHERE a",
|
60
|
+
"PREPARE sequel_pgap_2 AS SELECT * FROM table WHERE a",
|
61
|
+
"EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE a)",
|
62
|
+
"EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE a)"]
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should correctly return the size of the cache" do
|
66
|
+
sc = @db.statement_cache
|
67
|
+
sc.size.should == 0
|
68
|
+
@db[:table].filter(:b=>2).all
|
69
|
+
sc.size.should == 1
|
70
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
71
|
+
sc.size.should == 2
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should correctly clear the cache" do
|
75
|
+
sc = @db.statement_cache
|
76
|
+
sc.size.should == 0
|
77
|
+
@db[:table].filter(:b=>2).all
|
78
|
+
sc.size.should == 1
|
79
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
80
|
+
sc.size.should == 2
|
81
|
+
sc.clear
|
82
|
+
sc.size.should == 0
|
83
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
84
|
+
sc.size.should == 1
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should correctly yield each entry in the cache" do
|
88
|
+
@db[:table].filter(:b=>2).all
|
89
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
90
|
+
a = []
|
91
|
+
@db.statement_cache.each{|k, v| a << [k, v]}
|
92
|
+
a.sort!
|
93
|
+
a[0][0].should == "SELECT * FROM table WHERE (a = $1::int4)"
|
94
|
+
a[1][0].should == "SELECT * FROM table WHERE (b = $1::int4)"
|
95
|
+
s1 = a[1][1]
|
96
|
+
s1.cache_id.should == 1
|
97
|
+
s1.num_executes.should == 1
|
98
|
+
s1 = a[0][1]
|
99
|
+
s1.cache_id.should == 2
|
100
|
+
s1.num_executes.should == 3
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should automatically cleanup the cache when it goes beyond its maximum size" do
|
104
|
+
sc = @db.statement_cache
|
105
|
+
4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
|
106
|
+
sc.size.should == 4
|
107
|
+
@db[:table].filter(:b=>1).all
|
108
|
+
sc.size.should == 2
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should clear statement caches when altering tables" do
|
112
|
+
@db[:table].filter(:b=>2).all
|
113
|
+
sc = @db.statement_cache
|
114
|
+
@db.alter_table(:foo){drop_column :bar}
|
115
|
+
sc.size.should == 0
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should clear statement caches when dropping tables" do
|
119
|
+
@db[:table].filter(:b=>2).all
|
120
|
+
sc = @db.statement_cache
|
121
|
+
@db.drop_table(:foo)
|
122
|
+
sc.size.should == 0
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should deallocate prepared statements when clearing the cache" do
|
126
|
+
3.times{|i| @db[:table].filter(:a=>i).all}
|
127
|
+
@db.sqls
|
128
|
+
@db.statement_cache.clear
|
129
|
+
@db.sqls.should == ["DEALLOCATE sequel_pgap_1"]
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should deallocate prepared statements when cleaning up the cache" do
|
133
|
+
@db = @dbp.call(:sorter=>proc{|t, s| -s.num_executes})
|
134
|
+
4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
|
135
|
+
@db[:table].filter(:a0=>1).all
|
136
|
+
@db.sqls
|
137
|
+
@db[:table].filter(:b=>1).all
|
138
|
+
@db.sqls.should == ["DEALLOCATE sequel_pgap_1", "SELECT * FROM table WHERE (b = $1::int4) -- args: [1]"]
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should not deallocate nonprepared statements when clearing the cache" do
|
142
|
+
4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
|
143
|
+
@db.sqls
|
144
|
+
@db.statement_cache.clear
|
145
|
+
@db.sqls.should == []
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should not deallocate nonprepared statements when cleaning up the cache" do
|
149
|
+
@db = @dbp.call(:sorter=>proc{|t, s| -s.num_executes})
|
150
|
+
4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
|
151
|
+
@db.sqls
|
152
|
+
@db[:table].filter(:b=>1).all
|
153
|
+
@db.sqls.should == ["SELECT * FROM table WHERE (b = $1::int4) -- args: [1]"]
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should have a configurable max_size and min_size" do
|
157
|
+
@db = @dbp.call(:max_size=>10, :min_size=>2)
|
158
|
+
10.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
|
159
|
+
sc = @db.statement_cache
|
160
|
+
sc.size.should == 10
|
161
|
+
@db[:table].filter(:b=>1).all
|
162
|
+
sc.size.should == 2
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should have a configurable prepare_after" do
|
166
|
+
@db = @dbp.call(:prepare_after=>3)
|
167
|
+
4.times{|i| @db[:table].filter(:a=>i).all}
|
168
|
+
@db.sqls.should == ["SELECT * FROM table WHERE (a = $1::int4) -- args: [0]",
|
169
|
+
"SELECT * FROM table WHERE (a = $1::int4) -- args: [1]",
|
170
|
+
"PREPARE sequel_pgap_1 AS SELECT * FROM table WHERE (a = $1::int4)",
|
171
|
+
"EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [2]",
|
172
|
+
"EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [3]"]
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should have a configurable sorter" do
|
176
|
+
@db = @dbp.call(:sorter=>proc{|t, s| s.num_executes})
|
177
|
+
4.times{|i| (i+1).times{@db[:table].filter(:"a#{i}"=>1).all}}
|
178
|
+
@db[:table].filter(:b=>1).all
|
179
|
+
sc = @db.statement_cache
|
180
|
+
a = []
|
181
|
+
sc.each{|k, v| a << [k, v]}
|
182
|
+
a.sort!
|
183
|
+
a[0][0].should == "SELECT * FROM table WHERE (a3 = $1::int4)"
|
184
|
+
a[1][0].should == "SELECT * FROM table WHERE (b = $1::int4)"
|
185
|
+
s1 = a[1][1]
|
186
|
+
s1.num_executes.should == 1
|
187
|
+
s1 = a[0][1]
|
188
|
+
s1.cache_id.should == 4
|
189
|
+
s1.num_executes.should == 4
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should ignore errors when preparing queries" do
|
193
|
+
3.times{|i| @db[:table].filter(:prepare_raise=>1).all}
|
194
|
+
@db.sqls.should == ["SELECT * FROM table WHERE (prepare_raise = $1::int4) -- args: [1]",
|
195
|
+
"SELECT * FROM table WHERE (prepare_raise = $1::int4) -- args: [1]",
|
196
|
+
"SELECT * FROM table WHERE (prepare_raise = $1::int4) -- args: [1]"]
|
197
|
+
end
|
198
|
+
|
199
|
+
it "should ignore errors when deallocating queries" do
|
200
|
+
3.times{|i| @db[:table].filter(:a=>1).all}
|
201
|
+
@db.exec_raise = true
|
202
|
+
@db.statement_cache.clear
|
203
|
+
@db.sqls.should == ["SELECT * FROM table WHERE (a = $1::int4) -- args: [1]",
|
204
|
+
"PREPARE sequel_pgap_1 AS SELECT * FROM table WHERE (a = $1::int4)",
|
205
|
+
"EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [1]",
|
206
|
+
"EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [1]"]
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|