sequel 3.21.0 → 3.39.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 (352) hide show
  1. data/CHANGELOG +1061 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +103 -63
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +67 -51
  7. data/doc/advanced_associations.rdoc +189 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/cheat_sheet.rdoc +21 -21
  10. data/doc/core_extensions.rdoc +374 -0
  11. data/doc/dataset_basics.rdoc +10 -10
  12. data/doc/dataset_filtering.rdoc +54 -42
  13. data/doc/mass_assignment.rdoc +56 -0
  14. data/doc/migration.rdoc +89 -557
  15. data/doc/model_hooks.rdoc +80 -29
  16. data/doc/object_model.rdoc +547 -0
  17. data/doc/opening_databases.rdoc +114 -53
  18. data/doc/prepared_statements.rdoc +41 -15
  19. data/doc/querying.rdoc +137 -56
  20. data/doc/reflection.rdoc +36 -10
  21. data/doc/release_notes/3.22.0.txt +39 -0
  22. data/doc/release_notes/3.23.0.txt +172 -0
  23. data/doc/release_notes/3.24.0.txt +420 -0
  24. data/doc/release_notes/3.25.0.txt +88 -0
  25. data/doc/release_notes/3.26.0.txt +88 -0
  26. data/doc/release_notes/3.27.0.txt +82 -0
  27. data/doc/release_notes/3.28.0.txt +304 -0
  28. data/doc/release_notes/3.29.0.txt +459 -0
  29. data/doc/release_notes/3.30.0.txt +135 -0
  30. data/doc/release_notes/3.31.0.txt +146 -0
  31. data/doc/release_notes/3.32.0.txt +202 -0
  32. data/doc/release_notes/3.33.0.txt +157 -0
  33. data/doc/release_notes/3.34.0.txt +671 -0
  34. data/doc/release_notes/3.35.0.txt +144 -0
  35. data/doc/release_notes/3.36.0.txt +245 -0
  36. data/doc/release_notes/3.37.0.txt +338 -0
  37. data/doc/release_notes/3.38.0.txt +234 -0
  38. data/doc/release_notes/3.39.0.txt +237 -0
  39. data/doc/schema_modification.rdoc +585 -0
  40. data/doc/sharding.rdoc +99 -8
  41. data/doc/sql.rdoc +154 -112
  42. data/doc/testing.rdoc +169 -0
  43. data/doc/thread_safety.rdoc +17 -0
  44. data/doc/transactions.rdoc +137 -0
  45. data/doc/validations.rdoc +1 -1
  46. data/doc/virtual_rows.rdoc +78 -43
  47. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  48. data/lib/sequel/adapters/ado.rb +40 -18
  49. data/lib/sequel/adapters/amalgalite.rb +17 -9
  50. data/lib/sequel/adapters/db2.rb +175 -86
  51. data/lib/sequel/adapters/dbi.rb +15 -15
  52. data/lib/sequel/adapters/do/mysql.rb +17 -11
  53. data/lib/sequel/adapters/do/postgres.rb +2 -61
  54. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  55. data/lib/sequel/adapters/do.rb +18 -36
  56. data/lib/sequel/adapters/firebird.rb +27 -208
  57. data/lib/sequel/adapters/ibmdb.rb +453 -0
  58. data/lib/sequel/adapters/informix.rb +6 -23
  59. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  60. data/lib/sequel/adapters/jdbc/db2.rb +56 -0
  61. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  62. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  63. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  64. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  65. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  66. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  67. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  68. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  69. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  70. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  71. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  73. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  74. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  75. data/lib/sequel/adapters/jdbc.rb +279 -97
  76. data/lib/sequel/adapters/mock.rb +372 -0
  77. data/lib/sequel/adapters/mysql.rb +125 -222
  78. data/lib/sequel/adapters/mysql2.rb +65 -56
  79. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  80. data/lib/sequel/adapters/odbc.rb +28 -9
  81. data/lib/sequel/adapters/openbase.rb +3 -5
  82. data/lib/sequel/adapters/oracle.rb +349 -53
  83. data/lib/sequel/adapters/postgres.rb +368 -138
  84. data/lib/sequel/adapters/shared/access.rb +32 -9
  85. data/lib/sequel/adapters/shared/db2.rb +343 -0
  86. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  87. data/lib/sequel/adapters/shared/informix.rb +53 -0
  88. data/lib/sequel/adapters/shared/mssql.rb +401 -116
  89. data/lib/sequel/adapters/shared/mysql.rb +424 -89
  90. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  91. data/lib/sequel/adapters/shared/oracle.rb +257 -45
  92. data/lib/sequel/adapters/shared/postgres.rb +675 -279
  93. data/lib/sequel/adapters/shared/progress.rb +5 -8
  94. data/lib/sequel/adapters/shared/sqlite.rb +285 -96
  95. data/lib/sequel/adapters/sqlite.rb +101 -39
  96. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  97. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  98. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  99. data/lib/sequel/adapters/swift.rb +28 -19
  100. data/lib/sequel/adapters/tinytds.rb +174 -22
  101. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  102. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  103. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  104. data/lib/sequel/ast_transformer.rb +194 -0
  105. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  106. data/lib/sequel/connection_pool/sharded_threaded.rb +33 -2
  107. data/lib/sequel/connection_pool/single.rb +5 -0
  108. data/lib/sequel/connection_pool/threaded.rb +43 -14
  109. data/lib/sequel/connection_pool.rb +10 -2
  110. data/lib/sequel/core.rb +143 -11
  111. data/lib/sequel/database/connecting.rb +38 -28
  112. data/lib/sequel/database/dataset.rb +4 -4
  113. data/lib/sequel/database/dataset_defaults.rb +63 -2
  114. data/lib/sequel/database/logging.rb +7 -2
  115. data/lib/sequel/database/misc.rb +190 -46
  116. data/lib/sequel/database/query.rb +272 -110
  117. data/lib/sequel/database/schema_generator.rb +103 -36
  118. data/lib/sequel/database/schema_methods.rb +300 -67
  119. data/lib/sequel/dataset/actions.rb +344 -90
  120. data/lib/sequel/dataset/features.rb +101 -8
  121. data/lib/sequel/dataset/graph.rb +42 -15
  122. data/lib/sequel/dataset/misc.rb +52 -25
  123. data/lib/sequel/dataset/mutation.rb +12 -7
  124. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  125. data/lib/sequel/dataset/query.rb +305 -54
  126. data/lib/sequel/dataset/sql.rb +720 -292
  127. data/lib/sequel/dataset.rb +8 -0
  128. data/lib/sequel/exceptions.rb +4 -0
  129. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  130. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  131. data/lib/sequel/extensions/blank.rb +4 -0
  132. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  133. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  134. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  135. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  136. data/lib/sequel/extensions/inflector.rb +4 -0
  137. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  138. data/lib/sequel/extensions/migration.rb +100 -11
  139. data/lib/sequel/extensions/named_timezones.rb +9 -0
  140. data/lib/sequel/extensions/null_dataset.rb +94 -0
  141. data/lib/sequel/extensions/pagination.rb +4 -0
  142. data/lib/sequel/extensions/pg_array.rb +537 -0
  143. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  144. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  145. data/lib/sequel/extensions/pg_hstore.rb +313 -0
  146. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  147. data/lib/sequel/extensions/pg_inet.rb +113 -0
  148. data/lib/sequel/extensions/pg_interval.rb +191 -0
  149. data/lib/sequel/extensions/pg_json.rb +208 -0
  150. data/lib/sequel/extensions/pg_range.rb +512 -0
  151. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  152. data/lib/sequel/extensions/pg_row.rb +572 -0
  153. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  154. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  155. data/lib/sequel/extensions/pretty_table.rb +17 -72
  156. data/lib/sequel/extensions/query.rb +8 -4
  157. data/lib/sequel/extensions/query_literals.rb +79 -0
  158. data/lib/sequel/extensions/schema_caching.rb +76 -0
  159. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  160. data/lib/sequel/extensions/select_remove.rb +39 -0
  161. data/lib/sequel/extensions/server_block.rb +140 -0
  162. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  163. data/lib/sequel/extensions/sql_expr.rb +8 -110
  164. data/lib/sequel/extensions/string_date_time.rb +4 -0
  165. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  166. data/lib/sequel/extensions/to_dot.rb +99 -83
  167. data/lib/sequel/model/associations.rb +1263 -409
  168. data/lib/sequel/model/base.rb +624 -171
  169. data/lib/sequel/model/errors.rb +1 -1
  170. data/lib/sequel/model/exceptions.rb +24 -2
  171. data/lib/sequel/model/inflections.rb +1 -1
  172. data/lib/sequel/model.rb +30 -11
  173. data/lib/sequel/no_core_ext.rb +2 -0
  174. data/lib/sequel/plugins/active_model.rb +13 -1
  175. data/lib/sequel/plugins/association_pks.rb +22 -4
  176. data/lib/sequel/plugins/caching.rb +25 -18
  177. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  178. data/lib/sequel/plugins/composition.rb +44 -12
  179. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  180. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  181. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  182. data/lib/sequel/plugins/dirty.rb +214 -0
  183. data/lib/sequel/plugins/eager_each.rb +59 -0
  184. data/lib/sequel/plugins/force_encoding.rb +6 -6
  185. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  186. data/lib/sequel/plugins/identity_map.rb +134 -15
  187. data/lib/sequel/plugins/instance_filters.rb +10 -0
  188. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  189. data/lib/sequel/plugins/json_serializer.rb +58 -6
  190. data/lib/sequel/plugins/list.rb +13 -2
  191. data/lib/sequel/plugins/many_through_many.rb +103 -51
  192. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  193. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  194. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  195. data/lib/sequel/plugins/pg_row.rb +121 -0
  196. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  197. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  198. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  199. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  200. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  201. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  202. data/lib/sequel/plugins/schema.rb +8 -3
  203. data/lib/sequel/plugins/serialization.rb +98 -49
  204. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  205. data/lib/sequel/plugins/sharding.rb +21 -54
  206. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  207. data/lib/sequel/plugins/static_cache.rb +99 -0
  208. data/lib/sequel/plugins/subclasses.rb +29 -3
  209. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  210. data/lib/sequel/plugins/timestamps.rb +1 -1
  211. data/lib/sequel/plugins/tree.rb +3 -3
  212. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  213. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  214. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  215. data/lib/sequel/plugins/validation_helpers.rb +55 -4
  216. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  217. data/lib/sequel/sql.rb +649 -122
  218. data/lib/sequel/timezones.rb +67 -40
  219. data/lib/sequel/version.rb +1 -1
  220. data/spec/adapters/db2_spec.rb +146 -0
  221. data/spec/adapters/firebird_spec.rb +1 -1
  222. data/spec/adapters/mssql_spec.rb +194 -66
  223. data/spec/adapters/mysql_spec.rb +475 -306
  224. data/spec/adapters/oracle_spec.rb +92 -117
  225. data/spec/adapters/postgres_spec.rb +1982 -325
  226. data/spec/adapters/spec_helper.rb +9 -6
  227. data/spec/adapters/sqlite_spec.rb +203 -75
  228. data/spec/core/connection_pool_spec.rb +218 -93
  229. data/spec/core/database_spec.rb +956 -522
  230. data/spec/core/dataset_spec.rb +1631 -1242
  231. data/spec/core/expression_filters_spec.rb +690 -334
  232. data/spec/core/mock_adapter_spec.rb +453 -0
  233. data/spec/core/object_graph_spec.rb +88 -129
  234. data/spec/core/schema_generator_spec.rb +4 -4
  235. data/spec/core/schema_spec.rb +556 -53
  236. data/spec/core/spec_helper.rb +7 -48
  237. data/spec/core_extensions_spec.rb +626 -0
  238. data/spec/extensions/active_model_spec.rb +13 -0
  239. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  240. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  241. data/spec/extensions/association_dependencies_spec.rb +15 -25
  242. data/spec/extensions/association_pks_spec.rb +66 -32
  243. data/spec/extensions/association_proxies_spec.rb +4 -4
  244. data/spec/extensions/boolean_readers_spec.rb +25 -25
  245. data/spec/extensions/caching_spec.rb +47 -51
  246. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  247. data/spec/extensions/columns_introspection_spec.rb +91 -0
  248. data/spec/extensions/composition_spec.rb +18 -13
  249. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  250. data/spec/extensions/constraint_validations_spec.rb +316 -0
  251. data/spec/extensions/dataset_associations_spec.rb +199 -0
  252. data/spec/extensions/defaults_setter_spec.rb +88 -0
  253. data/spec/extensions/dirty_spec.rb +155 -0
  254. data/spec/extensions/eager_each_spec.rb +34 -0
  255. data/spec/extensions/eval_inspect_spec.rb +67 -0
  256. data/spec/extensions/force_encoding_spec.rb +4 -2
  257. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  258. data/spec/extensions/identity_map_spec.rb +142 -24
  259. data/spec/extensions/inflector_spec.rb +0 -4
  260. data/spec/extensions/instance_filters_spec.rb +11 -21
  261. data/spec/extensions/instance_hooks_spec.rb +72 -0
  262. data/spec/extensions/json_serializer_spec.rb +49 -12
  263. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  264. data/spec/extensions/list_spec.rb +49 -40
  265. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  266. data/spec/extensions/many_through_many_spec.rb +368 -254
  267. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  268. data/spec/extensions/migration_spec.rb +140 -35
  269. data/spec/extensions/named_timezones_spec.rb +29 -11
  270. data/spec/extensions/nested_attributes_spec.rb +268 -89
  271. data/spec/extensions/null_dataset_spec.rb +85 -0
  272. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  273. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  274. data/spec/extensions/pg_array_spec.rb +320 -0
  275. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  276. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  277. data/spec/extensions/pg_hstore_spec.rb +194 -0
  278. data/spec/extensions/pg_inet_spec.rb +47 -0
  279. data/spec/extensions/pg_interval_spec.rb +72 -0
  280. data/spec/extensions/pg_json_spec.rb +99 -0
  281. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  282. data/spec/extensions/pg_range_spec.rb +395 -0
  283. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  284. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  285. data/spec/extensions/pg_row_spec.rb +323 -0
  286. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  287. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  288. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  289. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  290. data/spec/extensions/prepared_statements_spec.rb +87 -0
  291. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  292. data/spec/extensions/pretty_table_spec.rb +7 -6
  293. data/spec/extensions/query_literals_spec.rb +167 -0
  294. data/spec/extensions/query_spec.rb +9 -3
  295. data/spec/extensions/rcte_tree_spec.rb +50 -43
  296. data/spec/extensions/schema_caching_spec.rb +41 -0
  297. data/spec/extensions/schema_dumper_spec.rb +434 -49
  298. data/spec/extensions/schema_spec.rb +30 -49
  299. data/spec/extensions/select_remove_spec.rb +38 -0
  300. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  301. data/spec/extensions/serialization_spec.rb +86 -48
  302. data/spec/extensions/server_block_spec.rb +90 -0
  303. data/spec/extensions/sharding_spec.rb +69 -143
  304. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  305. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  306. data/spec/extensions/spec_helper.rb +34 -67
  307. data/spec/extensions/split_array_nil_spec.rb +24 -0
  308. data/spec/extensions/sql_expr_spec.rb +29 -60
  309. data/spec/extensions/static_cache_spec.rb +145 -0
  310. data/spec/extensions/subclasses_spec.rb +14 -0
  311. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  312. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  313. data/spec/extensions/timestamps_spec.rb +6 -6
  314. data/spec/extensions/to_dot_spec.rb +8 -11
  315. data/spec/extensions/touch_spec.rb +13 -14
  316. data/spec/extensions/tree_spec.rb +11 -26
  317. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  318. data/spec/extensions/update_primary_key_spec.rb +30 -24
  319. data/spec/extensions/validation_class_methods_spec.rb +58 -67
  320. data/spec/extensions/validation_helpers_spec.rb +31 -37
  321. data/spec/extensions/xml_serializer_spec.rb +29 -4
  322. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  323. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  324. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  325. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  326. data/spec/integration/associations_test.rb +659 -21
  327. data/spec/integration/database_test.rb +11 -5
  328. data/spec/integration/dataset_test.rb +717 -129
  329. data/spec/integration/eager_loader_test.rb +38 -54
  330. data/spec/integration/migrator_test.rb +2 -3
  331. data/spec/integration/model_test.rb +81 -2
  332. data/spec/integration/plugin_test.rb +604 -147
  333. data/spec/integration/prepared_statement_test.rb +272 -128
  334. data/spec/integration/schema_test.rb +367 -57
  335. data/spec/integration/spec_helper.rb +42 -32
  336. data/spec/integration/timezone_test.rb +38 -12
  337. data/spec/integration/transaction_test.rb +183 -8
  338. data/spec/integration/type_test.rb +31 -8
  339. data/spec/model/association_reflection_spec.rb +207 -8
  340. data/spec/model/associations_spec.rb +1206 -693
  341. data/spec/model/base_spec.rb +334 -100
  342. data/spec/model/dataset_methods_spec.rb +45 -27
  343. data/spec/model/eager_loading_spec.rb +828 -692
  344. data/spec/model/hooks_spec.rb +355 -67
  345. data/spec/model/model_spec.rb +251 -179
  346. data/spec/model/plugins_spec.rb +24 -13
  347. data/spec/model/record_spec.rb +506 -232
  348. data/spec/model/spec_helper.rb +16 -77
  349. data/spec/model/validations_spec.rb +38 -2
  350. data/spec/rcov.opts +2 -0
  351. metadata +209 -80
  352. data/spec/core/core_sql_spec.rb +0 -451
@@ -0,0 +1,323 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "pg_row extension" do
4
+ before do
5
+ @db = Sequel.connect('mock://postgres', :quote_identifiers=>false)
6
+ @db.extension(:pg_array, :pg_row)
7
+ @m = Sequel::Postgres::PGRow
8
+ @db.sqls
9
+ end
10
+
11
+ it "should parse record objects as arrays" do
12
+ a = Sequel::Postgres::PG_TYPES[2249].call("(a,b,c)")
13
+ a.should be_a_kind_of(@m::ArrayRow)
14
+ a.to_a.should be_a_kind_of(Array)
15
+ a[0].should == 'a'
16
+ a.should == %w'a b c'
17
+ a.db_type.should == nil
18
+ @db.literal(a).should == "ROW('a', 'b', 'c')"
19
+ end
20
+
21
+ it "should parse arrays of record objects as arrays of arrays" do
22
+ as = Sequel::Postgres::PG_TYPES[2287].call('{"(a,b,c)","(d,e,f)"}')
23
+ as.should == [%w'a b c', %w'd e f']
24
+ as.each do |a|
25
+ a.should be_a_kind_of(@m::ArrayRow)
26
+ a.to_a.should be_a_kind_of(Array)
27
+ a.db_type.should == nil
28
+ end
29
+ @db.literal(as).should == "ARRAY[ROW('a', 'b', 'c'),ROW('d', 'e', 'f')]::record[]"
30
+ end
31
+
32
+ it "should be able to register custom parsing of row types as array-like objects" do
33
+ klass = @m::ArrayRow.subclass(:foo)
34
+ parser = @m::Parser.new(:converter=>klass)
35
+ a = parser.call("(a,b,c)")
36
+ a.should be_a_kind_of(klass)
37
+ a.to_a.should be_a_kind_of(Array)
38
+ a[0].should == 'a'
39
+ a.should == %w'a b c'
40
+ a.db_type.should == :foo
41
+ @db.literal(a).should == "ROW('a', 'b', 'c')::foo"
42
+ end
43
+
44
+ it "should be able to register custom parsing of row types as hash-like objects" do
45
+ klass = @m::HashRow.subclass(:foo, [:a, :b, :c])
46
+ parser = @m::Parser.new(:converter=>klass, :columns=>[:a, :b, :c])
47
+ a = parser.call("(a,b,c)")
48
+ a.should be_a_kind_of(klass)
49
+ a.to_hash.should be_a_kind_of(Hash)
50
+ a[:a].should == 'a'
51
+ a.should == {:a=>'a', :b=>'b', :c=>'c'}
52
+ a.db_type.should == :foo
53
+ a.columns.should == [:a, :b, :c]
54
+ @db.literal(a).should == "ROW('a', 'b', 'c')::foo"
55
+ end
56
+
57
+ it "should raise an error if attempting to literalize a HashRow without column information" do
58
+ h = @m::HashRow.call(:a=>'a', :b=>'b', :c=>'c')
59
+ proc{@db.literal(h)}.should raise_error(Sequel::Error)
60
+ end
61
+
62
+ it "should be able to manually override db_type per ArrayRow instance" do
63
+ a = @m::ArrayRow.call(%w'a b c')
64
+ a.db_type = :foo
65
+ @db.literal(a).should == "ROW('a', 'b', 'c')::foo"
66
+ end
67
+
68
+ it "should be able to manually override db_type and columns per HashRow instance" do
69
+ h = @m::HashRow.call(:a=>'a', :c=>'c', :b=>'b')
70
+ h.db_type = :foo
71
+ h.columns = [:a, :b, :c]
72
+ @db.literal(h).should == "ROW('a', 'b', 'c')::foo"
73
+ end
74
+
75
+ it "should correctly split an empty row" do
76
+ @m::Splitter.new("()").parse.should == [nil]
77
+ end
78
+
79
+ it "should correctly split a row with a single value" do
80
+ @m::Splitter.new("(1)").parse.should == %w'1'
81
+ end
82
+
83
+ it "should correctly split a row with multiple values" do
84
+ @m::Splitter.new("(1,2)").parse.should == %w'1 2'
85
+ end
86
+
87
+ it "should correctly NULL values when splitting" do
88
+ @m::Splitter.new("(1,)").parse.should == ['1', nil]
89
+ end
90
+
91
+ it "should correctly empty string values when splitting" do
92
+ @m::Splitter.new('(1,"")').parse.should == ['1', '']
93
+ end
94
+
95
+ it "should handle quoted values when splitting" do
96
+ @m::Splitter.new('("1","2")').parse.should == %w'1 2'
97
+ end
98
+
99
+ it "should handle escaped backslashes in quoted values when splitting" do
100
+ @m::Splitter.new('("\\\\1","2\\\\")').parse.should == ['\\1', '2\\']
101
+ end
102
+
103
+ it "should handle doubled quotes in quoted values when splitting" do
104
+ @m::Splitter.new('("""1","2""")').parse.should == ['"1', '2"']
105
+ end
106
+
107
+ it "should correctly convert types when parsing into an array" do
108
+ @m::Parser.new(:column_converters=>[proc{|s| s*2}, proc{|s| s*3}, proc{|s| s*4}]).call("(a,b,c)").should == %w'aa bbb cccc'
109
+ end
110
+
111
+ it "should correctly convert types into hashes if columns are known" do
112
+ @m::Parser.new(:columns=>[:a, :b, :c]).call("(a,b,c)").should == {:a=>'a', :b=>'b', :c=>'c'}
113
+ end
114
+
115
+ it "should correctly handle type conversion when converting into hashes" do
116
+ @m::Parser.new(:column_converters=>[proc{|s| s*2}, proc{|s| s*3}, proc{|s| s*4}], :columns=>[:a, :b, :c]).call("(a,b,c)").should == {:a=>'aa', :b=>'bbb', :c=>'cccc'}
117
+ end
118
+
119
+ it "should correctly wrap arrays when converting" do
120
+ @m::Parser.new(:converter=>proc{|s| [:foo, s]}).call("(a,b,c)").should == [:foo, %w'a b c']
121
+ end
122
+
123
+ it "should correctly wrap hashes when converting" do
124
+ @m::Parser.new(:converter=>proc{|s| [:foo, s]}, :columns=>[:a, :b, :c]).call("(a,b,c)").should == [:foo, {:a=>'a', :b=>'b', :c=>'c'}]
125
+ end
126
+
127
+ it "should have parser store reflection information" do
128
+ p = @m::Parser.new(:oid=>1, :column_oids=>[2], :columns=>[:a], :converter=>Array, :typecaster=>Hash, :column_converters=>[Array])
129
+ p.oid.should == 1
130
+ p.column_oids.should == [2]
131
+ p.columns.should == [:a]
132
+ p.converter.should == Array
133
+ p.typecaster.should == Hash
134
+ p.column_converters.should == [Array]
135
+ end
136
+
137
+ it "should reload registered row types when reseting conversion procs" do
138
+ db = Sequel.mock(:host=>'postgres')
139
+ db.extension(:pg_row)
140
+ db.conversion_procs[4] = p4 = proc{|s| s.to_i}
141
+ db.conversion_procs[5] = p5 = proc{|s| s * 2}
142
+ db.sqls
143
+ db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
144
+ db.register_row_type(:foo)
145
+ db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type WHERE ((typtype = 'c') AND (typname = 'foo')) LIMIT 1",
146
+ "SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
147
+ end
148
+
149
+ it "should handle ArrayRows and HashRows in bound variables" do
150
+ @db.bound_variable_arg(1, nil).should == 1
151
+ @db.bound_variable_arg(@m::ArrayRow.call(["1", "abc\\'\","]), nil).should == '("1","abc\\\\\'\\",")'
152
+ @db.bound_variable_arg(@m::HashRow.subclass(nil, [:a, :b]).call(:a=>"1", :b=>"abc\\'\","), nil).should == '("1","abc\\\\\'\\",")'
153
+ end
154
+
155
+ it "should handle ArrayRows and HashRows in arrays in bound variables" do
156
+ @db.bound_variable_arg(1, nil).should == 1
157
+ @db.bound_variable_arg([@m::ArrayRow.call(["1", "abc\\'\","])], nil).should == '{"(\\"1\\",\\"abc\\\\\\\\\'\\\\\\",\\")"}'
158
+ @db.bound_variable_arg([@m::HashRow.subclass(nil, [:a, :b]).call(:a=>"1", :b=>"abc\\'\",")], nil).should == '{"(\\"1\\",\\"abc\\\\\\\\\'\\\\\\",\\")"}'
159
+ end
160
+
161
+ it "should allow registering row type parsers by introspecting system tables" do
162
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
163
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
164
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
165
+ @db.register_row_type(:foo)
166
+ @db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type WHERE ((typtype = 'c') AND (typname = 'foo')) LIMIT 1",
167
+ "SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
168
+ p1 = @db.conversion_procs[1]
169
+ p1.columns.should == [:bar, :baz]
170
+ p1.column_oids.should == [4, 5]
171
+ p1.column_converters.should == [p4, p5]
172
+ p1.oid.should == 1
173
+ @db.send(:schema_column_type, 'foo').should == :pg_row_foo
174
+ @db.send(:schema_column_type, 'integer').should == :integer
175
+
176
+ c = p1.converter
177
+ c.superclass.should == @m::HashRow
178
+ c.columns.should == [:bar, :baz]
179
+ c.db_type.should == :foo
180
+ p1.typecaster.should == c
181
+
182
+ p1.call('(1,b)').should == {:bar=>1, :baz=>'bb'}
183
+ @db.typecast_value(:pg_row_foo, %w'1 b').should be_a_kind_of(@m::HashRow)
184
+ @db.typecast_value(:pg_row_foo, %w'1 b').should == {:bar=>'1', :baz=>'b'}
185
+ @db.typecast_value(:pg_row_foo, :bar=>'1', :baz=>'b').should == {:bar=>'1', :baz=>'b'}
186
+ @db.literal(p1.call('(1,b)')).should == "ROW(1, 'bb')::foo"
187
+ end
188
+
189
+ it "should allow registering row type parsers for schema qualify types" do
190
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
191
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
192
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
193
+ @db.register_row_type(:foo__bar)
194
+ @db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type INNER JOIN pg_namespace ON ((pg_namespace.oid = pg_type.typnamespace) AND (pg_namespace.nspname = 'foo')) WHERE ((typtype = 'c') AND (typname = 'bar')) LIMIT 1",
195
+ "SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
196
+ p1 = @db.conversion_procs[1]
197
+ p1.columns.should == [:bar, :baz]
198
+ p1.column_oids.should == [4, 5]
199
+ p1.column_converters.should == [p4, p5]
200
+ p1.oid.should == 1
201
+
202
+ c = p1.converter
203
+ c.superclass.should == @m::HashRow
204
+ c.columns.should == [:bar, :baz]
205
+ c.db_type.should == :foo__bar
206
+ p1.typecaster.should == c
207
+
208
+ p1.call('(1,b)').should == {:bar=>1, :baz=>'bb'}
209
+ @db.typecast_value(:pg_row_foo__bar, %w'1 b').should == {:bar=>'1', :baz=>'b'}
210
+ @db.typecast_value(:pg_row_foo__bar, :bar=>'1', :baz=>'b').should == {:bar=>'1', :baz=>'b'}
211
+ @db.literal(p1.call('(1,b)')).should == "ROW(1, 'bb')::foo.bar"
212
+ end
213
+
214
+ it "should allow registering with a custom converter" do
215
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
216
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
217
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
218
+ c = proc{|h| [h]}
219
+ @db.register_row_type(:foo, :converter=>c)
220
+ o = @db.conversion_procs[1].call('(1,b)')
221
+ o.should == [{:bar=>1, :baz=>'bb'}]
222
+ o.first.should be_a_kind_of(Hash)
223
+ end
224
+
225
+ it "should allow registering with a custom typecaster" do
226
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
227
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
228
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
229
+ @db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2}})
230
+ @db.typecast_value(:pg_row_foo, %w'1 b').should be_a_kind_of(Hash)
231
+ @db.typecast_value(:pg_row_foo, %w'1 b').should == {:bar=>1, :baz=>'bb'}
232
+ @db.typecast_value(:pg_row_foo, :bar=>'1', :baz=>'b').should == {:bar=>1, :baz=>'bb'}
233
+ @db.typecast_value(:pg_row_foo, 'bar'=>'1', 'baz'=>'b').should == {:bar=>0, :baz=>'aa'}
234
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
235
+ @db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar] || h['bar'] || 0).to_i, :baz=>(h[:baz] || h['baz'] || 'a')*2}})
236
+ @db.typecast_value(:pg_row_foo, %w'1 b').should == {:bar=>1, :baz=>'bb'}
237
+ @db.typecast_value(:pg_row_foo, :bar=>'1', :baz=>'b').should == {:bar=>1, :baz=>'bb'}
238
+ @db.typecast_value(:pg_row_foo, 'bar'=>'1', 'baz'=>'b').should == {:bar=>1, :baz=>'bb'}
239
+ end
240
+
241
+ it "should handle conversion procs that aren't added until later" do
242
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
243
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
244
+ c = proc{|h| [h]}
245
+ @db.register_row_type(:foo, :converter=>c)
246
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
247
+ @db.conversion_procs[1].call('(1,b)').should == [{:bar=>1, :baz=>'bb'}]
248
+ end
249
+
250
+ it "should registering array type for row type if type has an array oid" do
251
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
252
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
253
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
254
+ @db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2}})
255
+ p3 = @db.conversion_procs[3]
256
+
257
+ p3.call('{"(1,b)"}').should == [{:bar=>1, :baz=>'bb'}]
258
+ @db.literal(p3.call('{"(1,b)"}')).should == "ARRAY[ROW(1, 'bb')::foo]::foo[]"
259
+ @db.typecast_value(:foo_array, [{:bar=>'1', :baz=>'b'}]).should == [{:bar=>1, :baz=>'bb'}]
260
+ end
261
+
262
+ it "should allow creating unregisted row types via Database#row_type" do
263
+ @db.literal(@db.row_type(:foo, [1, 2])).should == 'ROW(1, 2)::foo'
264
+ end
265
+
266
+ it "should allow typecasting of registered row types via Database#row_type" do
267
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
268
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
269
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
270
+ @db.register_row_type(:foo, :typecaster=>proc{|h| @m::HashRow.subclass(:foo, [:bar, :baz]).new({:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2})})
271
+ @db.literal(@db.row_type(:foo, ['1', 'b'])).should == "ROW(1, 'bb')::foo"
272
+ @db.literal(@db.row_type(:foo, {:bar=>'1', :baz=>'b'})).should == "ROW(1, 'bb')::foo"
273
+ end
274
+
275
+ it "should allow parsing when typecasting registered row types via Database#row_type" do
276
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
277
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
278
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
279
+ @db.register_row_type(:foo, :typecaster=>proc{|h| @m::HashRow.subclass(:foo, [:bar, :baz]).new(:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2)})
280
+ @db.literal(@db.row_type(:foo, ['1', 'b'])).should == "ROW(1, 'bb')::foo"
281
+ end
282
+
283
+ it "should raise an error if attempt to use Database#row_type with an unregistered type and hash" do
284
+ proc{@db.literal(@db.row_type(:foo, {:bar=>'1', :baz=>'b'}))}.should raise_error(Sequel::Error)
285
+ end
286
+
287
+ it "should raise an error if attempt to use Database#row_type with an unhandled type" do
288
+ proc{@db.literal(@db.row_type(:foo, 1))}.should raise_error(Sequel::Error)
289
+ end
290
+
291
+ it "should return ArrayRow and HashRow values as-is" do
292
+ h = @m::HashRow.call(:a=>1)
293
+ a = @m::ArrayRow.call([1])
294
+ @db.row_type(:foo, h).should equal(h)
295
+ @db.row_type(:foo, a).should equal(a)
296
+ end
297
+
298
+ it "should have Sequel.pg_row return a plain ArrayRow" do
299
+ @db.literal(Sequel.pg_row([1, 2, 3])).should == 'ROW(1, 2, 3)'
300
+ end
301
+
302
+ it "should raise an error if attempting to typecast a hash for a parser without columns" do
303
+ proc{@m::Parser.new.typecast(:a=>1)}.should raise_error(Sequel::Error)
304
+ end
305
+
306
+ it "should raise an error if attempting to typecast a unhandled value for a parser" do
307
+ proc{@m::Parser.new.typecast(1)}.should raise_error(Sequel::Error)
308
+ end
309
+
310
+ it "should handle typecasting for a parser without a typecaster" do
311
+ @m::Parser.new.typecast([1]).should == [1]
312
+ end
313
+
314
+ it "should raise an error if no columns are returned when registering a custom row type" do
315
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}]]
316
+ proc{@db.register_row_type(:foo)}.should raise_error(Sequel::Error)
317
+ end
318
+
319
+ it "should raise an error when registering a custom row type if the type is found found" do
320
+ @db.fetch = []
321
+ proc{@db.register_row_type(:foo)}.should raise_error(Sequel::Error)
322
+ end
323
+ end
@@ -0,0 +1,208 @@
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.extension :pg_auto_parameterize, :pg_statement_cache
27
+ @db.extend(Module.new do
28
+ attr_accessor :exec_raise
29
+ def _execute(c, sql, opts={})
30
+ opts[:no_eq] ? super : c.send(:execute_query, sql, opts[:arguments])
31
+ end
32
+ def _sqls
33
+ @sqls
34
+ end
35
+ def statement_cache
36
+ synchronize{|c| c.statement_cache}
37
+ end
38
+ end)
39
+ @db
40
+ end
41
+ @db = @dbp.call({})
42
+ end
43
+
44
+ it "should automatically prepare statements executed multiple times" do
45
+ @db[:table].filter(:b=>2).all
46
+ 3.times{|i| @db[:table].filter(:a=>i).all}
47
+ @db.sqls.should == ["SELECT * FROM table WHERE (b = $1::int4) -- args: [2]",
48
+ "SELECT * FROM table WHERE (a = $1::int4) -- args: [0]",
49
+ "PREPARE sequel_pgap_2 AS SELECT * FROM table WHERE (a = $1::int4)",
50
+ "EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [1]",
51
+ "EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [2]"]
52
+ end
53
+
54
+ it "should work correctly for queries without parameters" do
55
+ @db[:table].filter(:b).all
56
+ 3.times{|i| @db[:table].filter(:a).all}
57
+ @db.sqls.should == ["SELECT * FROM table WHERE b",
58
+ "SELECT * FROM table WHERE a",
59
+ "PREPARE sequel_pgap_2 AS SELECT * FROM table WHERE a",
60
+ "EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE a)",
61
+ "EXECUTE sequel_pgap_2 (SELECT * FROM table WHERE a)"]
62
+ end
63
+
64
+ it "should correctly return the size of the cache" do
65
+ sc = @db.statement_cache
66
+ sc.size.should == 0
67
+ @db[:table].filter(:b=>2).all
68
+ sc.size.should == 1
69
+ 3.times{|i| @db[:table].filter(:a=>i).all}
70
+ sc.size.should == 2
71
+ end
72
+
73
+ it "should correctly clear the cache" do
74
+ sc = @db.statement_cache
75
+ sc.size.should == 0
76
+ @db[:table].filter(:b=>2).all
77
+ sc.size.should == 1
78
+ 3.times{|i| @db[:table].filter(:a=>i).all}
79
+ sc.size.should == 2
80
+ sc.clear
81
+ sc.size.should == 0
82
+ 3.times{|i| @db[:table].filter(:a=>i).all}
83
+ sc.size.should == 1
84
+ end
85
+
86
+ it "should correctly yield each entry in the cache" do
87
+ @db[:table].filter(:b=>2).all
88
+ 3.times{|i| @db[:table].filter(:a=>i).all}
89
+ a = []
90
+ @db.statement_cache.each{|k, v| a << [k, v]}
91
+ a.sort!
92
+ a[0][0].should == "SELECT * FROM table WHERE (a = $1::int4)"
93
+ a[1][0].should == "SELECT * FROM table WHERE (b = $1::int4)"
94
+ s1 = a[1][1]
95
+ s1.cache_id.should == 1
96
+ s1.num_executes.should == 1
97
+ s1 = a[0][1]
98
+ s1.cache_id.should == 2
99
+ s1.num_executes.should == 3
100
+ end
101
+
102
+ it "should automatically cleanup the cache when it goes beyond its maximum size" do
103
+ sc = @db.statement_cache
104
+ 4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
105
+ sc.size.should == 4
106
+ @db[:table].filter(:b=>1).all
107
+ sc.size.should == 2
108
+ end
109
+
110
+ it "should clear statement caches when altering tables" do
111
+ @db[:table].filter(:b=>2).all
112
+ sc = @db.statement_cache
113
+ @db.alter_table(:foo){drop_column :bar}
114
+ sc.size.should == 0
115
+ end
116
+
117
+ it "should clear statement caches when dropping tables" do
118
+ @db[:table].filter(:b=>2).all
119
+ sc = @db.statement_cache
120
+ @db.drop_table(:foo)
121
+ sc.size.should == 0
122
+ end
123
+
124
+ it "should deallocate prepared statements when clearing the cache" do
125
+ 3.times{|i| @db[:table].filter(:a=>i).all}
126
+ @db.sqls
127
+ @db.statement_cache.clear
128
+ @db.sqls.should == ["DEALLOCATE sequel_pgap_1"]
129
+ end
130
+
131
+ it "should deallocate prepared statements when cleaning up the cache" do
132
+ @db = @dbp.call(:sorter=>proc{|t, s| -s.num_executes})
133
+ 4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
134
+ @db[:table].filter(:a0=>1).all
135
+ @db.sqls
136
+ @db[:table].filter(:b=>1).all
137
+ @db.sqls.should == ["DEALLOCATE sequel_pgap_1", "SELECT * FROM table WHERE (b = $1::int4) -- args: [1]"]
138
+ end
139
+
140
+ it "should not deallocate nonprepared statements when clearing the cache" do
141
+ 4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
142
+ @db.sqls
143
+ @db.statement_cache.clear
144
+ @db.sqls.should == []
145
+ end
146
+
147
+ it "should not deallocate nonprepared statements when cleaning up the cache" do
148
+ @db = @dbp.call(:sorter=>proc{|t, s| -s.num_executes})
149
+ 4.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
150
+ @db.sqls
151
+ @db[:table].filter(:b=>1).all
152
+ @db.sqls.should == ["SELECT * FROM table WHERE (b = $1::int4) -- args: [1]"]
153
+ end
154
+
155
+ it "should have a configurable max_size and min_size" do
156
+ @db = @dbp.call(:max_size=>10, :min_size=>2)
157
+ 10.times{|i| @db[:table].filter(:"a#{i}"=>1).all}
158
+ sc = @db.statement_cache
159
+ sc.size.should == 10
160
+ @db[:table].filter(:b=>1).all
161
+ sc.size.should == 2
162
+ end
163
+
164
+ it "should have a configurable prepare_after" do
165
+ @db = @dbp.call(:prepare_after=>3)
166
+ 4.times{|i| @db[:table].filter(:a=>i).all}
167
+ @db.sqls.should == ["SELECT * FROM table WHERE (a = $1::int4) -- args: [0]",
168
+ "SELECT * FROM table WHERE (a = $1::int4) -- args: [1]",
169
+ "PREPARE sequel_pgap_1 AS SELECT * FROM table WHERE (a = $1::int4)",
170
+ "EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [2]",
171
+ "EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [3]"]
172
+ end
173
+
174
+ it "should have a configurable sorter" do
175
+ @db = @dbp.call(:sorter=>proc{|t, s| s.num_executes})
176
+ 4.times{|i| (i+1).times{@db[:table].filter(:"a#{i}"=>1).all}}
177
+ @db[:table].filter(:b=>1).all
178
+ sc = @db.statement_cache
179
+ a = []
180
+ sc.each{|k, v| a << [k, v]}
181
+ a.sort!
182
+ a[0][0].should == "SELECT * FROM table WHERE (a3 = $1::int4)"
183
+ a[1][0].should == "SELECT * FROM table WHERE (b = $1::int4)"
184
+ s1 = a[1][1]
185
+ s1.num_executes.should == 1
186
+ s1 = a[0][1]
187
+ s1.cache_id.should == 4
188
+ s1.num_executes.should == 4
189
+ end
190
+
191
+ it "should ignore errors when preparing queries" do
192
+ 3.times{|i| @db[:table].filter(:prepare_raise=>1).all}
193
+ @db.sqls.should == ["SELECT * FROM table WHERE (prepare_raise = $1::int4) -- args: [1]",
194
+ "SELECT * FROM table WHERE (prepare_raise = $1::int4) -- args: [1]",
195
+ "SELECT * FROM table WHERE (prepare_raise = $1::int4) -- args: [1]"]
196
+ end
197
+
198
+ it "should ignore errors when deallocating queries" do
199
+ 3.times{|i| @db[:table].filter(:a=>1).all}
200
+ @db.exec_raise = true
201
+ @db.statement_cache.clear
202
+ @db.sqls.should == ["SELECT * FROM table WHERE (a = $1::int4) -- args: [1]",
203
+ "PREPARE sequel_pgap_1 AS SELECT * FROM table WHERE (a = $1::int4)",
204
+ "EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [1]",
205
+ "EXECUTE sequel_pgap_1 (SELECT * FROM table WHERE (a = $1::int4)) -- args: [1]"]
206
+ end
207
+
208
+ end
@@ -0,0 +1,58 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe Sequel::Model, "PgTypecastOnLoad plugin" do
4
+ before do
5
+ @db = Sequel.mock(:host=>'postgres', :fetch=>{:id=>1, :b=>"t", :y=>"0"}, :columns=>[:id, :b, :y], :numrows=>1)
6
+ def @db.schema(*args)
7
+ [[:id, {}], [:b, {:type=>:boolean, :oid=>16}], [:y, {:type=>:integer, :oid=>20}]]
8
+ end
9
+ @c = Class.new(Sequel::Model(@db[:items]))
10
+ @c.plugin :pg_typecast_on_load, :b, :y
11
+ end
12
+
13
+ specify "should call the database conversion proc for all given columns" do
14
+ @c.first.values.should == {:id=>1, :b=>true, :y=>0}
15
+ end
16
+
17
+ specify "should call the database conversion proc with value when reloading the object, for all given columns" do
18
+ @c.first.refresh.values.should == {:id=>1, :b=>true, :y=>0}
19
+ end
20
+
21
+ specify "should call the database conversion proc with value when automatically reloading the object on creation via insert_select" do
22
+ @c.dataset.meta_def(:insert_select){|h| insert(h); first}
23
+ @c.create.values.should == {:id=>1, :b=>true, :y=>0}
24
+ end
25
+
26
+ specify "should allowing setting columns separately via add_typecast_on_load_columns" do
27
+ @c = Class.new(Sequel::Model(@db[:items]))
28
+ @c.plugin :pg_typecast_on_load
29
+ @c.first.values.should == {:id=>1, :b=>"t", :y=>"0"}
30
+ @c.add_pg_typecast_on_load_columns :b
31
+ @c.first.values.should == {:id=>1, :b=>true, :y=>"0"}
32
+ @c.add_pg_typecast_on_load_columns :y
33
+ @c.first.values.should == {:id=>1, :b=>true, :y=>0}
34
+ end
35
+
36
+ specify "should work with subclasses" do
37
+ @c = Class.new(Sequel::Model(@db[:items]))
38
+ @c.plugin :pg_typecast_on_load
39
+ @c.first.values.should == {:id=>1, :b=>"t", :y=>"0"}
40
+
41
+ c1 = Class.new(@c)
42
+ @c.add_pg_typecast_on_load_columns :b
43
+ @c.first.values.should == {:id=>1, :b=>true, :y=>"0"}
44
+ c1.first.values.should == {:id=>1, :b=>"t", :y=>"0"}
45
+
46
+ c2 = Class.new(@c)
47
+ @c.add_pg_typecast_on_load_columns :y
48
+ @c.first.values.should == {:id=>1, :b=>true, :y=>0}
49
+ c2.first.values.should == {:id=>1, :b=>true, :y=>"0"}
50
+
51
+ c1.add_pg_typecast_on_load_columns :y
52
+ c1.first.values.should == {:id=>1, :b=>"t", :y=>0}
53
+ end
54
+
55
+ specify "should not mark the object as modified" do
56
+ @c.first.modified?.should == false
57
+ end
58
+ end