sequel 3.33.0 → 3.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/CHANGELOG +140 -0
  2. data/Rakefile +7 -0
  3. data/bin/sequel +22 -2
  4. data/doc/dataset_basics.rdoc +1 -1
  5. data/doc/mass_assignment.rdoc +3 -1
  6. data/doc/querying.rdoc +28 -4
  7. data/doc/reflection.rdoc +23 -3
  8. data/doc/release_notes/3.34.0.txt +671 -0
  9. data/doc/schema_modification.rdoc +18 -2
  10. data/doc/virtual_rows.rdoc +49 -0
  11. data/lib/sequel/adapters/do/mysql.rb +0 -5
  12. data/lib/sequel/adapters/ibmdb.rb +9 -4
  13. data/lib/sequel/adapters/jdbc.rb +9 -4
  14. data/lib/sequel/adapters/jdbc/h2.rb +8 -2
  15. data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
  18. data/lib/sequel/adapters/mock.rb +24 -3
  19. data/lib/sequel/adapters/mysql.rb +29 -50
  20. data/lib/sequel/adapters/mysql2.rb +13 -28
  21. data/lib/sequel/adapters/oracle.rb +8 -2
  22. data/lib/sequel/adapters/postgres.rb +115 -20
  23. data/lib/sequel/adapters/shared/db2.rb +1 -1
  24. data/lib/sequel/adapters/shared/mssql.rb +14 -3
  25. data/lib/sequel/adapters/shared/mysql.rb +59 -11
  26. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  28. data/lib/sequel/adapters/shared/postgres.rb +127 -30
  29. data/lib/sequel/adapters/shared/sqlite.rb +55 -38
  30. data/lib/sequel/adapters/sqlite.rb +9 -3
  31. data/lib/sequel/adapters/swift.rb +2 -2
  32. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  33. data/lib/sequel/adapters/swift/postgres.rb +10 -0
  34. data/lib/sequel/ast_transformer.rb +4 -0
  35. data/lib/sequel/connection_pool.rb +8 -0
  36. data/lib/sequel/connection_pool/sharded_single.rb +5 -0
  37. data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
  38. data/lib/sequel/connection_pool/single.rb +5 -0
  39. data/lib/sequel/connection_pool/threaded.rb +14 -0
  40. data/lib/sequel/core.rb +24 -3
  41. data/lib/sequel/database/connecting.rb +24 -14
  42. data/lib/sequel/database/dataset_defaults.rb +1 -0
  43. data/lib/sequel/database/misc.rb +16 -25
  44. data/lib/sequel/database/query.rb +20 -2
  45. data/lib/sequel/database/schema_generator.rb +2 -2
  46. data/lib/sequel/database/schema_methods.rb +120 -23
  47. data/lib/sequel/dataset/actions.rb +91 -18
  48. data/lib/sequel/dataset/features.rb +5 -0
  49. data/lib/sequel/dataset/prepared_statements.rb +6 -2
  50. data/lib/sequel/dataset/sql.rb +68 -51
  51. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  52. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
  53. data/lib/sequel/extensions/migration.rb +4 -0
  54. data/lib/sequel/extensions/null_dataset.rb +90 -0
  55. data/lib/sequel/extensions/pg_array.rb +460 -0
  56. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  57. data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
  58. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  59. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  60. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  61. data/lib/sequel/extensions/pretty_table.rb +5 -71
  62. data/lib/sequel/extensions/query_literals.rb +79 -0
  63. data/lib/sequel/extensions/schema_caching.rb +76 -0
  64. data/lib/sequel/extensions/schema_dumper.rb +227 -31
  65. data/lib/sequel/extensions/select_remove.rb +35 -0
  66. data/lib/sequel/extensions/sql_expr.rb +4 -110
  67. data/lib/sequel/extensions/to_dot.rb +1 -1
  68. data/lib/sequel/model.rb +11 -2
  69. data/lib/sequel/model/associations.rb +35 -7
  70. data/lib/sequel/model/base.rb +159 -36
  71. data/lib/sequel/no_core_ext.rb +2 -0
  72. data/lib/sequel/plugins/caching.rb +25 -18
  73. data/lib/sequel/plugins/composition.rb +1 -1
  74. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  75. data/lib/sequel/plugins/identity_map.rb +11 -3
  76. data/lib/sequel/plugins/instance_filters.rb +10 -0
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  78. data/lib/sequel/plugins/nested_attributes.rb +4 -3
  79. data/lib/sequel/plugins/prepared_statements.rb +3 -1
  80. data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
  81. data/lib/sequel/plugins/schema.rb +7 -2
  82. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  83. data/lib/sequel/plugins/static_cache.rb +99 -0
  84. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  85. data/lib/sequel/sql.rb +417 -7
  86. data/lib/sequel/version.rb +1 -1
  87. data/spec/adapters/firebird_spec.rb +1 -1
  88. data/spec/adapters/mssql_spec.rb +12 -15
  89. data/spec/adapters/mysql_spec.rb +81 -23
  90. data/spec/adapters/postgres_spec.rb +444 -77
  91. data/spec/adapters/spec_helper.rb +2 -0
  92. data/spec/adapters/sqlite_spec.rb +8 -8
  93. data/spec/core/connection_pool_spec.rb +85 -0
  94. data/spec/core/database_spec.rb +29 -5
  95. data/spec/core/dataset_spec.rb +171 -3
  96. data/spec/core/expression_filters_spec.rb +364 -0
  97. data/spec/core/mock_adapter_spec.rb +17 -3
  98. data/spec/core/schema_spec.rb +133 -0
  99. data/spec/extensions/association_dependencies_spec.rb +13 -13
  100. data/spec/extensions/caching_spec.rb +26 -3
  101. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  102. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
  103. data/spec/extensions/force_encoding_spec.rb +4 -2
  104. data/spec/extensions/hook_class_methods_spec.rb +5 -2
  105. data/spec/extensions/identity_map_spec.rb +17 -0
  106. data/spec/extensions/instance_filters_spec.rb +1 -1
  107. data/spec/extensions/lazy_attributes_spec.rb +2 -2
  108. data/spec/extensions/list_spec.rb +4 -4
  109. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  110. data/spec/extensions/migration_spec.rb +6 -2
  111. data/spec/extensions/nested_attributes_spec.rb +20 -0
  112. data/spec/extensions/null_dataset_spec.rb +85 -0
  113. data/spec/extensions/optimistic_locking_spec.rb +2 -2
  114. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  115. data/spec/extensions/pg_array_spec.rb +196 -0
  116. data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
  117. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  118. data/spec/extensions/pg_hstore_spec.rb +195 -0
  119. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  120. data/spec/extensions/prepared_statements_spec.rb +4 -0
  121. data/spec/extensions/pretty_table_spec.rb +6 -0
  122. data/spec/extensions/query_literals_spec.rb +168 -0
  123. data/spec/extensions/schema_caching_spec.rb +41 -0
  124. data/spec/extensions/schema_dumper_spec.rb +231 -11
  125. data/spec/extensions/schema_spec.rb +14 -2
  126. data/spec/extensions/select_remove_spec.rb +38 -0
  127. data/spec/extensions/sharding_spec.rb +6 -6
  128. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  129. data/spec/extensions/spec_helper.rb +2 -1
  130. data/spec/extensions/sql_expr_spec.rb +28 -19
  131. data/spec/extensions/static_cache_spec.rb +145 -0
  132. data/spec/extensions/touch_spec.rb +1 -1
  133. data/spec/extensions/typecast_on_load_spec.rb +9 -1
  134. data/spec/integration/associations_test.rb +6 -6
  135. data/spec/integration/database_test.rb +1 -1
  136. data/spec/integration/dataset_test.rb +89 -26
  137. data/spec/integration/migrator_test.rb +2 -3
  138. data/spec/integration/model_test.rb +3 -3
  139. data/spec/integration/plugin_test.rb +85 -22
  140. data/spec/integration/prepared_statement_test.rb +28 -8
  141. data/spec/integration/schema_test.rb +78 -7
  142. data/spec/integration/spec_helper.rb +1 -0
  143. data/spec/integration/timezone_test.rb +1 -1
  144. data/spec/integration/transaction_test.rb +4 -6
  145. data/spec/integration/type_test.rb +2 -2
  146. data/spec/model/associations_spec.rb +94 -8
  147. data/spec/model/base_spec.rb +4 -4
  148. data/spec/model/hooks_spec.rb +2 -2
  149. data/spec/model/model_spec.rb +19 -7
  150. data/spec/model/record_spec.rb +135 -58
  151. data/spec/model/spec_helper.rb +1 -0
  152. 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