sequel 3.21.0 → 3.36.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 (311) hide show
  1. data/CHANGELOG +855 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +23 -7
  4. data/Rakefile +42 -24
  5. data/bin/sequel +26 -8
  6. data/doc/active_record.rdoc +1 -0
  7. data/doc/advanced_associations.rdoc +171 -11
  8. data/doc/association_basics.rdoc +293 -43
  9. data/doc/dataset_basics.rdoc +5 -5
  10. data/doc/dataset_filtering.rdoc +8 -0
  11. data/doc/mass_assignment.rdoc +56 -0
  12. data/doc/migration.rdoc +85 -552
  13. data/doc/model_hooks.rdoc +78 -27
  14. data/doc/object_model.rdoc +541 -0
  15. data/doc/opening_databases.rdoc +97 -50
  16. data/doc/prepared_statements.rdoc +40 -14
  17. data/doc/querying.rdoc +28 -4
  18. data/doc/reflection.rdoc +30 -4
  19. data/doc/release_notes/3.22.0.txt +39 -0
  20. data/doc/release_notes/3.23.0.txt +172 -0
  21. data/doc/release_notes/3.24.0.txt +420 -0
  22. data/doc/release_notes/3.25.0.txt +88 -0
  23. data/doc/release_notes/3.26.0.txt +88 -0
  24. data/doc/release_notes/3.27.0.txt +82 -0
  25. data/doc/release_notes/3.28.0.txt +304 -0
  26. data/doc/release_notes/3.29.0.txt +459 -0
  27. data/doc/release_notes/3.30.0.txt +135 -0
  28. data/doc/release_notes/3.31.0.txt +146 -0
  29. data/doc/release_notes/3.32.0.txt +202 -0
  30. data/doc/release_notes/3.33.0.txt +157 -0
  31. data/doc/release_notes/3.34.0.txt +671 -0
  32. data/doc/release_notes/3.35.0.txt +144 -0
  33. data/doc/release_notes/3.36.0.txt +245 -0
  34. data/doc/schema_modification.rdoc +572 -0
  35. data/doc/sharding.rdoc +95 -3
  36. data/doc/testing.rdoc +129 -0
  37. data/doc/thread_safety.rdoc +17 -0
  38. data/doc/transactions.rdoc +137 -0
  39. data/doc/virtual_rows.rdoc +51 -2
  40. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  41. data/lib/sequel/adapters/ado.rb +40 -18
  42. data/lib/sequel/adapters/amalgalite.rb +17 -9
  43. data/lib/sequel/adapters/db2.rb +175 -86
  44. data/lib/sequel/adapters/dbi.rb +15 -15
  45. data/lib/sequel/adapters/do/mysql.rb +17 -11
  46. data/lib/sequel/adapters/do/postgres.rb +1 -57
  47. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  48. data/lib/sequel/adapters/do.rb +18 -36
  49. data/lib/sequel/adapters/firebird.rb +27 -208
  50. data/lib/sequel/adapters/ibmdb.rb +453 -0
  51. data/lib/sequel/adapters/informix.rb +6 -23
  52. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  53. data/lib/sequel/adapters/jdbc/db2.rb +47 -0
  54. data/lib/sequel/adapters/jdbc/derby.rb +287 -0
  55. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  56. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  57. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  58. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  59. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  61. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  62. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
  64. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  65. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  66. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  67. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  68. data/lib/sequel/adapters/jdbc.rb +265 -94
  69. data/lib/sequel/adapters/mock.rb +361 -0
  70. data/lib/sequel/adapters/mysql.rb +118 -222
  71. data/lib/sequel/adapters/mysql2.rb +58 -57
  72. data/lib/sequel/adapters/odbc/mssql.rb +15 -7
  73. data/lib/sequel/adapters/odbc.rb +28 -9
  74. data/lib/sequel/adapters/openbase.rb +3 -5
  75. data/lib/sequel/adapters/oracle.rb +349 -53
  76. data/lib/sequel/adapters/postgres.rb +356 -107
  77. data/lib/sequel/adapters/shared/access.rb +32 -9
  78. data/lib/sequel/adapters/shared/db2.rb +342 -0
  79. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  80. data/lib/sequel/adapters/shared/informix.rb +53 -0
  81. data/lib/sequel/adapters/shared/mssql.rb +335 -118
  82. data/lib/sequel/adapters/shared/mysql.rb +340 -76
  83. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  84. data/lib/sequel/adapters/shared/oracle.rb +243 -45
  85. data/lib/sequel/adapters/shared/postgres.rb +524 -279
  86. data/lib/sequel/adapters/shared/progress.rb +5 -8
  87. data/lib/sequel/adapters/shared/sqlite.rb +265 -89
  88. data/lib/sequel/adapters/sqlite.rb +101 -39
  89. data/lib/sequel/adapters/swift/mysql.rb +9 -12
  90. data/lib/sequel/adapters/swift/postgres.rb +9 -74
  91. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  92. data/lib/sequel/adapters/swift.rb +8 -7
  93. data/lib/sequel/adapters/tinytds.rb +161 -21
  94. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  95. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  96. data/lib/sequel/ast_transformer.rb +194 -0
  97. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  98. data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
  99. data/lib/sequel/connection_pool/single.rb +5 -0
  100. data/lib/sequel/connection_pool/threaded.rb +38 -14
  101. data/lib/sequel/connection_pool.rb +10 -2
  102. data/lib/sequel/core.rb +118 -11
  103. data/lib/sequel/database/connecting.rb +38 -28
  104. data/lib/sequel/database/dataset.rb +4 -4
  105. data/lib/sequel/database/dataset_defaults.rb +63 -2
  106. data/lib/sequel/database/logging.rb +7 -2
  107. data/lib/sequel/database/misc.rb +150 -46
  108. data/lib/sequel/database/query.rb +215 -65
  109. data/lib/sequel/database/schema_generator.rb +64 -20
  110. data/lib/sequel/database/schema_methods.rb +212 -54
  111. data/lib/sequel/dataset/actions.rb +322 -85
  112. data/lib/sequel/dataset/features.rb +96 -8
  113. data/lib/sequel/dataset/graph.rb +38 -11
  114. data/lib/sequel/dataset/misc.rb +50 -23
  115. data/lib/sequel/dataset/mutation.rb +12 -7
  116. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  117. data/lib/sequel/dataset/query.rb +237 -44
  118. data/lib/sequel/dataset/sql.rb +698 -278
  119. data/lib/sequel/dataset.rb +8 -0
  120. data/lib/sequel/exceptions.rb +4 -0
  121. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  122. data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
  123. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  124. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
  125. data/lib/sequel/extensions/migration.rb +97 -11
  126. data/lib/sequel/extensions/named_timezones.rb +5 -0
  127. data/lib/sequel/extensions/null_dataset.rb +90 -0
  128. data/lib/sequel/extensions/pg_array.rb +460 -0
  129. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  130. data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
  131. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  132. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  133. data/lib/sequel/extensions/pg_inet.rb +89 -0
  134. data/lib/sequel/extensions/pg_json.rb +178 -0
  135. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  136. data/lib/sequel/extensions/pretty_table.rb +5 -71
  137. data/lib/sequel/extensions/query.rb +1 -1
  138. data/lib/sequel/extensions/query_literals.rb +79 -0
  139. data/lib/sequel/extensions/schema_caching.rb +76 -0
  140. data/lib/sequel/extensions/schema_dumper.rb +251 -37
  141. data/lib/sequel/extensions/select_remove.rb +35 -0
  142. data/lib/sequel/extensions/server_block.rb +139 -0
  143. data/lib/sequel/extensions/sql_expr.rb +4 -110
  144. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  145. data/lib/sequel/extensions/to_dot.rb +95 -83
  146. data/lib/sequel/model/associations.rb +1187 -393
  147. data/lib/sequel/model/base.rb +578 -170
  148. data/lib/sequel/model/errors.rb +1 -1
  149. data/lib/sequel/model/exceptions.rb +5 -1
  150. data/lib/sequel/model/inflections.rb +1 -1
  151. data/lib/sequel/model.rb +30 -11
  152. data/lib/sequel/no_core_ext.rb +2 -0
  153. data/lib/sequel/plugins/active_model.rb +13 -1
  154. data/lib/sequel/plugins/association_pks.rb +22 -4
  155. data/lib/sequel/plugins/caching.rb +25 -18
  156. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  157. data/lib/sequel/plugins/composition.rb +44 -12
  158. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  159. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  160. data/lib/sequel/plugins/dirty.rb +214 -0
  161. data/lib/sequel/plugins/eager_each.rb +59 -0
  162. data/lib/sequel/plugins/force_encoding.rb +6 -6
  163. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  164. data/lib/sequel/plugins/identity_map.rb +123 -8
  165. data/lib/sequel/plugins/instance_filters.rb +10 -0
  166. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  167. data/lib/sequel/plugins/json_serializer.rb +58 -6
  168. data/lib/sequel/plugins/list.rb +12 -2
  169. data/lib/sequel/plugins/many_through_many.rb +79 -28
  170. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  171. data/lib/sequel/plugins/nested_attributes.rb +139 -63
  172. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  173. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  174. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  175. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  176. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  177. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  178. data/lib/sequel/plugins/schema.rb +7 -2
  179. data/lib/sequel/plugins/serialization.rb +98 -49
  180. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  181. data/lib/sequel/plugins/sharding.rb +21 -54
  182. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  183. data/lib/sequel/plugins/static_cache.rb +99 -0
  184. data/lib/sequel/plugins/subclasses.rb +10 -2
  185. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  186. data/lib/sequel/plugins/timestamps.rb +1 -1
  187. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  188. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  189. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  190. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  191. data/lib/sequel/sql.rb +526 -59
  192. data/lib/sequel/timezones.rb +57 -38
  193. data/lib/sequel/version.rb +1 -1
  194. data/spec/adapters/db2_spec.rb +146 -0
  195. data/spec/adapters/firebird_spec.rb +1 -1
  196. data/spec/adapters/mssql_spec.rb +142 -40
  197. data/spec/adapters/mysql_spec.rb +380 -234
  198. data/spec/adapters/oracle_spec.rb +67 -77
  199. data/spec/adapters/postgres_spec.rb +1050 -212
  200. data/spec/adapters/spec_helper.rb +8 -5
  201. data/spec/adapters/sqlite_spec.rb +139 -27
  202. data/spec/core/connection_pool_spec.rb +196 -93
  203. data/spec/core/database_spec.rb +879 -510
  204. data/spec/core/dataset_spec.rb +1415 -1086
  205. data/spec/core/expression_filters_spec.rb +554 -109
  206. data/spec/core/mock_adapter_spec.rb +449 -0
  207. data/spec/core/object_graph_spec.rb +86 -114
  208. data/spec/core/schema_generator_spec.rb +4 -4
  209. data/spec/core/schema_spec.rb +482 -38
  210. data/spec/core/spec_helper.rb +6 -48
  211. data/spec/extensions/active_model_spec.rb +13 -0
  212. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  213. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  214. data/spec/extensions/association_dependencies_spec.rb +15 -25
  215. data/spec/extensions/association_pks_spec.rb +66 -32
  216. data/spec/extensions/association_proxies_spec.rb +4 -4
  217. data/spec/extensions/caching_spec.rb +47 -51
  218. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  219. data/spec/extensions/columns_introspection_spec.rb +91 -0
  220. data/spec/extensions/composition_spec.rb +18 -13
  221. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
  222. data/spec/extensions/dataset_associations_spec.rb +199 -0
  223. data/spec/extensions/defaults_setter_spec.rb +64 -0
  224. data/spec/extensions/dirty_spec.rb +155 -0
  225. data/spec/extensions/eager_each_spec.rb +34 -0
  226. data/spec/extensions/force_encoding_spec.rb +4 -2
  227. data/spec/extensions/hook_class_methods_spec.rb +69 -92
  228. data/spec/extensions/identity_map_spec.rb +131 -22
  229. data/spec/extensions/inflector_spec.rb +0 -4
  230. data/spec/extensions/instance_filters_spec.rb +11 -21
  231. data/spec/extensions/instance_hooks_spec.rb +72 -0
  232. data/spec/extensions/json_serializer_spec.rb +49 -6
  233. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  234. data/spec/extensions/list_spec.rb +48 -39
  235. data/spec/extensions/many_through_many_spec.rb +283 -250
  236. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  237. data/spec/extensions/migration_spec.rb +140 -35
  238. data/spec/extensions/named_timezones_spec.rb +29 -11
  239. data/spec/extensions/nested_attributes_spec.rb +251 -89
  240. data/spec/extensions/null_dataset_spec.rb +85 -0
  241. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  242. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  243. data/spec/extensions/pg_array_spec.rb +196 -0
  244. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  245. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  246. data/spec/extensions/pg_hstore_spec.rb +195 -0
  247. data/spec/extensions/pg_inet_spec.rb +44 -0
  248. data/spec/extensions/pg_json_spec.rb +101 -0
  249. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  250. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  251. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  252. data/spec/extensions/prepared_statements_spec.rb +87 -0
  253. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  254. data/spec/extensions/pretty_table_spec.rb +7 -6
  255. data/spec/extensions/query_literals_spec.rb +168 -0
  256. data/spec/extensions/query_spec.rb +6 -0
  257. data/spec/extensions/rcte_tree_spec.rb +50 -43
  258. data/spec/extensions/schema_caching_spec.rb +41 -0
  259. data/spec/extensions/schema_dumper_spec.rb +429 -22
  260. data/spec/extensions/schema_spec.rb +25 -21
  261. data/spec/extensions/select_remove_spec.rb +38 -0
  262. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  263. data/spec/extensions/serialization_spec.rb +85 -43
  264. data/spec/extensions/server_block_spec.rb +90 -0
  265. data/spec/extensions/sharding_spec.rb +69 -143
  266. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  267. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  268. data/spec/extensions/spec_helper.rb +22 -61
  269. data/spec/extensions/sql_expr_spec.rb +29 -60
  270. data/spec/extensions/static_cache_spec.rb +145 -0
  271. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  272. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  273. data/spec/extensions/timestamps_spec.rb +6 -6
  274. data/spec/extensions/to_dot_spec.rb +4 -7
  275. data/spec/extensions/touch_spec.rb +13 -14
  276. data/spec/extensions/tree_spec.rb +11 -26
  277. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  278. data/spec/extensions/update_primary_key_spec.rb +30 -24
  279. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  280. data/spec/extensions/validation_helpers_spec.rb +16 -35
  281. data/spec/extensions/xml_serializer_spec.rb +29 -4
  282. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  283. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  284. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  285. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  286. data/spec/integration/associations_test.rb +659 -21
  287. data/spec/integration/database_test.rb +9 -3
  288. data/spec/integration/dataset_test.rb +605 -91
  289. data/spec/integration/eager_loader_test.rb +19 -21
  290. data/spec/integration/migrator_test.rb +2 -3
  291. data/spec/integration/model_test.rb +81 -2
  292. data/spec/integration/plugin_test.rb +454 -143
  293. data/spec/integration/prepared_statement_test.rb +263 -128
  294. data/spec/integration/schema_test.rb +309 -43
  295. data/spec/integration/spec_helper.rb +40 -30
  296. data/spec/integration/timezone_test.rb +38 -12
  297. data/spec/integration/transaction_test.rb +183 -8
  298. data/spec/integration/type_test.rb +18 -4
  299. data/spec/model/association_reflection_spec.rb +201 -7
  300. data/spec/model/associations_spec.rb +1067 -661
  301. data/spec/model/base_spec.rb +329 -95
  302. data/spec/model/dataset_methods_spec.rb +7 -27
  303. data/spec/model/eager_loading_spec.rb +677 -676
  304. data/spec/model/hooks_spec.rb +349 -67
  305. data/spec/model/model_spec.rb +240 -168
  306. data/spec/model/plugins_spec.rb +24 -13
  307. data/spec/model/record_spec.rb +490 -232
  308. data/spec/model/spec_helper.rb +14 -71
  309. data/spec/model/validations_spec.rb +11 -0
  310. data/spec/rcov.opts +2 -0
  311. metadata +178 -80
@@ -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 for the hstore type" 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,44 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "pg_inet extension" do
4
+ ipv6_broken = (IPAddr.new('::1'); false) rescue true
5
+ before do
6
+ @db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
7
+ @db.extend(Module.new{def bound_variable_arg(arg, conn) arg end})
8
+ @db.extend(Sequel::Postgres::InetDatabaseMethods)
9
+ end
10
+
11
+ it "should literalize IPAddr v4 instances to strings correctly" do
12
+ @db.literal(IPAddr.new('127.0.0.1')).should == "'127.0.0.1/32'"
13
+ @db.literal(IPAddr.new('127.0.0.0/8')).should == "'127.0.0.0/8'"
14
+ end
15
+
16
+ it "should literalize IPAddr v6 instances to strings correctly" do
17
+ @db.literal(IPAddr.new('2001:4f8:3:ba::/64')).should == "'2001:4f8:3:ba::/64'"
18
+ @db.literal(IPAddr.new('2001:4f8:3:ba:2e0:81ff:fe22:d1f1')).should == "'2001:4f8:3:ba:2e0:81ff:fe22:d1f1/128'"
19
+ end unless ipv6_broken
20
+
21
+ it "should not affect literalization of custom objects" do
22
+ o = Object.new
23
+ def o.sql_literal(ds) 'v' end
24
+ @db.literal(o).should == 'v'
25
+ end
26
+
27
+ it "should support using IPAddr as bound variables" do
28
+ @db.bound_variable_arg(1, nil).should == 1
29
+ @db.bound_variable_arg(IPAddr.new('127.0.0.1'), nil).should == '127.0.0.1/32'
30
+ end
31
+
32
+ it "should parse inet/cidr type from the schema correctly" do
33
+ @db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'inet'}, {:name=>'c', :db_type=>'cidr'}]
34
+ @db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :ipaddr, :ipaddr]
35
+ end
36
+
37
+ it "should support typecasting for the ipaddr type" do
38
+ ip = IPAddr.new('127.0.0.1')
39
+ @db.typecast_value(:ipaddr, ip).should equal(ip)
40
+ @db.typecast_value(:ipaddr, ip.to_s).should == ip
41
+ proc{@db.typecast_value(:ipaddr, '')}.should raise_error(Sequel::InvalidValue)
42
+ proc{@db.typecast_value(:ipaddr, 1)}.should raise_error(Sequel::InvalidValue)
43
+ end
44
+ end
@@ -0,0 +1,101 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ begin
4
+ Sequel.extension :pg_json
5
+ rescue LoadError => e
6
+ skip_warn "can't load pg_json extension (#{e.class}: #{e})"
7
+ else
8
+ describe "pg_json extension" do
9
+ before(:all) do
10
+ m = Sequel::Postgres
11
+ @m = m::JSONDatabaseMethods
12
+ @hc = m::JSONHash
13
+ @ac = m::JSONArray
14
+
15
+ # Create subclass in correct namespace for easily overriding methods
16
+ j = m::JSON = JSON.dup
17
+ j.instance_eval do
18
+ Parser = JSON::Parser
19
+ alias old_parse parse
20
+ def parse(s)
21
+ return 1 if s == '1'
22
+ old_parse(s)
23
+ end
24
+ end
25
+ end
26
+ before do
27
+ @db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
28
+ @db.extend(Module.new{def bound_variable_arg(arg, conn) arg end})
29
+ end
30
+
31
+ it "should parse json strings correctly" do
32
+ @m.parse_json('[]').should be_a_kind_of(@ac)
33
+ @m.parse_json('[]').to_a.should == []
34
+ @m.parse_json('[1]').to_a.should == [1]
35
+ @m.parse_json('[1, 2]').to_a.should == [1, 2]
36
+ @m.parse_json('[1, [2], {"a": "b"}]').to_a.should == [1, [2], {'a'=>'b'}]
37
+ @m.parse_json('{}').should be_a_kind_of(@hc)
38
+ @m.parse_json('{}').to_hash.should == {}
39
+ @m.parse_json('{"a": "b"}').to_hash.should == {'a'=>'b'}
40
+ @m.parse_json('{"a": "b", "c": [1, 2, 3]}').to_hash.should == {'a'=>'b', 'c'=>[1, 2, 3]}
41
+ @m.parse_json('{"a": "b", "c": {"d": "e"}}').to_hash.should == {'a'=>'b', 'c'=>{'d'=>'e'}}
42
+ end
43
+
44
+ it "should raise an error when attempting to parse invalid json" do
45
+ proc{@m.parse_json('')}.should raise_error(Sequel::InvalidValue)
46
+ proc{@m.parse_json('1')}.should raise_error(Sequel::InvalidValue)
47
+ end
48
+
49
+ it "should literalize HStores to strings correctly" do
50
+ @db.literal([].pg_json).should == "'[]'::json"
51
+ @db.literal([1, [2], {'a'=>'b'}].pg_json).should == "'[1,[2],{\"a\":\"b\"}]'::json"
52
+ @db.literal({}.pg_json).should == "'{}'::json"
53
+ @db.literal({'a'=>'b'}.pg_json).should == "'{\"a\":\"b\"}'::json"
54
+ end
55
+
56
+ it "should have Hash#pg_json method for creating JSONHash instances" do
57
+ {}.pg_json.should be_a_kind_of(@hc)
58
+ end
59
+
60
+ it "should have Array#pg_json method for creating JSONArray instances" do
61
+ [].pg_json.should be_a_kind_of(@ac)
62
+ end
63
+
64
+ it "should have JSONHash#to_hash method for getting underlying hash" do
65
+ {}.pg_json.to_hash.should be_a_kind_of(Hash)
66
+ end
67
+
68
+ it "should have JSONArray#to_a method for getting underlying array" do
69
+ [].pg_json.to_a.should be_a_kind_of(Array)
70
+ end
71
+
72
+ it "should support using JSONHash and JSONArray as bound variables" do
73
+ @db.extend @m
74
+ @db.bound_variable_arg(1, nil).should == 1
75
+ @db.bound_variable_arg([1].pg_json, nil).should == '[1]'
76
+ @db.bound_variable_arg({'a'=>'b'}.pg_json, nil).should == '{"a":"b"}'
77
+ end
78
+
79
+ it "should parse json type from the schema correctly" do
80
+ @db.extend @m
81
+ @db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'json'}]
82
+ @db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :json]
83
+ end
84
+
85
+ it "should support typecasting for the json type" do
86
+ @db.extend @m
87
+ h = {1=>2}.pg_json
88
+ a = [1].pg_json
89
+ @db.typecast_value(:json, h).should equal(h)
90
+ @db.typecast_value(:json, h.to_hash).should == h
91
+ @db.typecast_value(:json, h.to_hash).should be_a_kind_of(@hc)
92
+ @db.typecast_value(:json, a).should equal(a)
93
+ @db.typecast_value(:json, a.to_a).should == a
94
+ @db.typecast_value(:json, a.to_a).should be_a_kind_of(@ac)
95
+ @db.typecast_value(:json, '[]').should == [].pg_json
96
+ @db.typecast_value(:json, '{"a": "b"}').should == {"a"=>"b"}.pg_json
97
+ proc{@db.typecast_value(:json, '')}.should raise_error(Sequel::InvalidValue)
98
+ proc{@db.typecast_value(:json, 1)}.should raise_error(Sequel::InvalidValue)
99
+ end
100
+ end
101
+ 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
@@ -0,0 +1,111 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "Sequel::Plugins::AssociationPks" do
4
+ before do
5
+ @db = Sequel.mock
6
+ @db.extend_datasets do
7
+ def select_sql
8
+ sql = super
9
+ sql << ' -- prepared' if is_a?(Sequel::Dataset::PreparedStatementMethods)
10
+ sql
11
+ end
12
+ end
13
+ @Artist = Class.new(Sequel::Model(@db[:artists]))
14
+ @Artist.columns :id, :id2
15
+ @Album= Class.new(Sequel::Model(@db[:albums]))
16
+ @Album.columns :id, :artist_id, :id2, :artist_id2
17
+ @Tag = Class.new(Sequel::Model(@db[:tags]))
18
+ @Tag.columns :id, :id2
19
+ @Artist.plugin :prepared_statements_associations
20
+ @Album.plugin :prepared_statements_associations
21
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
22
+ @Album.many_to_one :artist, :class=>@Artist
23
+ @Album.many_to_many :tags, :class=>@Tag, :join_table=>:albums_tags, :left_key=>:album_id
24
+ @Artist.plugin :many_through_many
25
+ @Artist.many_through_many :tags, [[:albums, :artist_id, :id], [:albums_tags, :album_id, :tag_id]], :class=>@Tag
26
+ @db.sqls
27
+ end
28
+
29
+ specify "should run correct SQL for associations" do
30
+ @Artist.load(:id=>1).albums
31
+ @db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1) -- prepared"]
32
+
33
+ @Album.load(:id=>1, :artist_id=>2).artist
34
+ @db.sqls.should == ["SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1 -- prepared"]
35
+
36
+ @Album.load(:id=>1, :artist_id=>2).tags
37
+ @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.album_id = 1)) -- prepared"]
38
+
39
+ @Artist.load(:id=>1).tags
40
+ @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.artist_id = 1)) -- prepared"]
41
+ end
42
+
43
+ specify "should run correct SQL for composite key associations" do
44
+ @Artist.one_to_many :albums, :class=>@Album, :key=>[:artist_id, :artist_id2], :primary_key=>[:id, :id2]
45
+ @Album.many_to_one :artist, :class=>@Artist, :key=>[:artist_id, :artist_id2], :primary_key=>[:id, :id2]
46
+ @Album.many_to_many :tags, :class=>@Tag, :join_table=>:albums_tags, :left_key=>[:album_id, :album_id2], :right_key=>[:tag_id, :tag_id2], :right_primary_key=>[:id, :id2], :left_primary_key=>[:id, :id2]
47
+ @Artist.many_through_many :tags, [[:albums, [:artist_id, :artist_id2], [:id, :id2]], [:albums_tags, [:album_id, :album_id2], [:tag_id, :tag_id2]]], :class=>@Tag, :right_primary_key=>[:id, :id2], :left_primary_key=>[:id, :id2]
48
+
49
+ @Artist.load(:id=>1, :id2=>2).albums
50
+ @db.sqls.should == ["SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2)) -- prepared"]
51
+
52
+ @Album.load(:id=>1, :artist_id=>2, :artist_id2=>3).artist
53
+ @db.sqls.should == ["SELECT * FROM artists WHERE ((artists.id = 2) AND (artists.id2 = 3)) LIMIT 1 -- prepared"]
54
+
55
+ @Album.load(:id=>1, :artist_id=>2, :id2=>3).tags
56
+ @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2) AND (albums_tags.album_id = 1) AND (albums_tags.album_id2 = 3)) -- prepared"]
57
+
58
+ @Artist.load(:id=>1, :id2=>2).tags
59
+ @db.sqls.should == ["SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.id2 = albums_tags.album_id2) AND (albums.artist_id = 1) AND (albums.artist_id2 = 2)) -- prepared"]
60
+ end
61
+
62
+ specify "should not run query if no objects can be associated" do
63
+ @Artist.new.albums.should == []
64
+ @Album.new.artist.should == nil
65
+ @db.sqls.should == []
66
+ end
67
+
68
+ specify "should run a regular query if there is a callback" do
69
+ @Artist.load(:id=>1).albums(proc{|ds| ds})
70
+ @db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1)"]
71
+ end
72
+
73
+ specify "should run a regular query if :prepared_statement=>false option is used for the association" do
74
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :prepared_statement=>false
75
+ @Artist.load(:id=>1).albums
76
+ @db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1)"]
77
+ end
78
+
79
+ specify "should run a regular query if unrecognized association is used" do
80
+ a = @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
81
+ a[:type] = :foo
82
+ @Artist.load(:id=>1).albums
83
+ @db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1)"]
84
+ end
85
+
86
+ specify "should run a regular query if a block is used when defining the association" do
87
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id do |ds| ds end
88
+ @Artist.load(:id=>1).albums
89
+ @db.sqls.should == ["SELECT * FROM albums WHERE (albums.artist_id = 1)"]
90
+ end
91
+
92
+ specify "should run a regular query if :conditions option is used when defining the association" do
93
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :conditions=>{:a=>1}
94
+ @Artist.load(:id=>1).albums
95
+ @db.sqls.should == ["SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (a = 1))"]
96
+ end
97
+
98
+ specify "should run a regular query if :dataset option is used when defining the association" do
99
+ album = @Album
100
+ @Artist.one_to_many :albums, :class=>@Album, :dataset=>proc{album.filter(:artist_id=>id)}
101
+ @Artist.load(:id=>1).albums
102
+ @db.sqls.should == ["SELECT * FROM albums WHERE (artist_id = 1)"]
103
+ end
104
+
105
+ specify "should run a regular query if :cloning an association that doesn't used prepared statements" do
106
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :conditions=>{:a=>1}
107
+ @Artist.one_to_many :oalbums, :clone=>:albums
108
+ @Artist.load(:id=>1).oalbums
109
+ @db.sqls.should == ["SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (a = 1))"]
110
+ end
111
+ end