sequel 3.33.0 → 3.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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