sequel 3.21.0 → 3.41.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 (363) hide show
  1. data/CHANGELOG +1131 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +106 -65
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +68 -52
  7. data/doc/advanced_associations.rdoc +201 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/bin_sequel.rdoc +144 -0
  10. data/doc/cheat_sheet.rdoc +21 -21
  11. data/doc/core_extensions.rdoc +374 -0
  12. data/doc/dataset_basics.rdoc +10 -10
  13. data/doc/dataset_filtering.rdoc +54 -42
  14. data/doc/mass_assignment.rdoc +56 -0
  15. data/doc/migration.rdoc +89 -557
  16. data/doc/model_hooks.rdoc +80 -29
  17. data/doc/object_model.rdoc +576 -0
  18. data/doc/opening_databases.rdoc +121 -53
  19. data/doc/prepared_statements.rdoc +41 -15
  20. data/doc/querying.rdoc +137 -56
  21. data/doc/reflection.rdoc +36 -10
  22. data/doc/release_notes/3.22.0.txt +39 -0
  23. data/doc/release_notes/3.23.0.txt +172 -0
  24. data/doc/release_notes/3.24.0.txt +420 -0
  25. data/doc/release_notes/3.25.0.txt +88 -0
  26. data/doc/release_notes/3.26.0.txt +88 -0
  27. data/doc/release_notes/3.27.0.txt +82 -0
  28. data/doc/release_notes/3.28.0.txt +304 -0
  29. data/doc/release_notes/3.29.0.txt +459 -0
  30. data/doc/release_notes/3.30.0.txt +135 -0
  31. data/doc/release_notes/3.31.0.txt +146 -0
  32. data/doc/release_notes/3.32.0.txt +202 -0
  33. data/doc/release_notes/3.33.0.txt +157 -0
  34. data/doc/release_notes/3.34.0.txt +671 -0
  35. data/doc/release_notes/3.35.0.txt +144 -0
  36. data/doc/release_notes/3.36.0.txt +245 -0
  37. data/doc/release_notes/3.37.0.txt +338 -0
  38. data/doc/release_notes/3.38.0.txt +234 -0
  39. data/doc/release_notes/3.39.0.txt +237 -0
  40. data/doc/release_notes/3.40.0.txt +73 -0
  41. data/doc/release_notes/3.41.0.txt +155 -0
  42. data/doc/schema_modification.rdoc +585 -0
  43. data/doc/sharding.rdoc +99 -8
  44. data/doc/sql.rdoc +154 -112
  45. data/doc/testing.rdoc +169 -0
  46. data/doc/thread_safety.rdoc +17 -0
  47. data/doc/transactions.rdoc +137 -0
  48. data/doc/validations.rdoc +1 -1
  49. data/doc/virtual_rows.rdoc +78 -43
  50. data/lib/sequel/adapters/ado/access.rb +334 -0
  51. data/lib/sequel/adapters/ado/mssql.rb +18 -12
  52. data/lib/sequel/adapters/ado.rb +69 -21
  53. data/lib/sequel/adapters/amalgalite.rb +17 -14
  54. data/lib/sequel/adapters/cubrid.rb +143 -0
  55. data/lib/sequel/adapters/db2.rb +178 -85
  56. data/lib/sequel/adapters/dbi.rb +19 -21
  57. data/lib/sequel/adapters/do/mysql.rb +17 -11
  58. data/lib/sequel/adapters/do/postgres.rb +2 -61
  59. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  60. data/lib/sequel/adapters/do.rb +20 -39
  61. data/lib/sequel/adapters/firebird.rb +33 -210
  62. data/lib/sequel/adapters/ibmdb.rb +452 -0
  63. data/lib/sequel/adapters/informix.rb +6 -29
  64. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  65. data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
  66. data/lib/sequel/adapters/jdbc/db2.rb +78 -0
  67. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  68. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  69. data/lib/sequel/adapters/jdbc/h2.rb +68 -31
  70. data/lib/sequel/adapters/jdbc/hsqldb.rb +189 -0
  71. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  73. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  74. data/lib/sequel/adapters/jdbc/mysql.rb +16 -28
  75. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  76. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  77. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  78. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  79. data/lib/sequel/adapters/jdbc/sqlserver.rb +72 -0
  80. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  81. data/lib/sequel/adapters/jdbc.rb +308 -114
  82. data/lib/sequel/adapters/mock.rb +372 -0
  83. data/lib/sequel/adapters/mysql.rb +130 -230
  84. data/lib/sequel/adapters/mysql2.rb +64 -63
  85. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  86. data/lib/sequel/adapters/odbc.rb +29 -10
  87. data/lib/sequel/adapters/openbase.rb +7 -11
  88. data/lib/sequel/adapters/oracle.rb +364 -57
  89. data/lib/sequel/adapters/postgres.rb +379 -145
  90. data/lib/sequel/adapters/shared/access.rb +236 -11
  91. data/lib/sequel/adapters/shared/cubrid.rb +216 -0
  92. data/lib/sequel/adapters/shared/db2.rb +353 -0
  93. data/lib/sequel/adapters/shared/firebird.rb +231 -0
  94. data/lib/sequel/adapters/shared/informix.rb +53 -0
  95. data/lib/sequel/adapters/shared/mssql.rb +412 -116
  96. data/lib/sequel/adapters/shared/mysql.rb +396 -89
  97. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +166 -0
  98. data/lib/sequel/adapters/shared/oracle.rb +262 -45
  99. data/lib/sequel/adapters/shared/postgres.rb +688 -279
  100. data/lib/sequel/adapters/shared/progress.rb +5 -8
  101. data/lib/sequel/adapters/shared/sqlite.rb +317 -96
  102. data/lib/sequel/adapters/sqlite.rb +109 -46
  103. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  104. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  105. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  106. data/lib/sequel/adapters/swift.rb +31 -27
  107. data/lib/sequel/adapters/tinytds.rb +177 -25
  108. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  109. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  110. data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
  111. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  112. data/lib/sequel/ast_transformer.rb +194 -0
  113. data/lib/sequel/connection_pool/sharded_single.rb +23 -13
  114. data/lib/sequel/connection_pool/sharded_threaded.rb +64 -13
  115. data/lib/sequel/connection_pool/single.rb +11 -3
  116. data/lib/sequel/connection_pool/threaded.rb +69 -20
  117. data/lib/sequel/connection_pool.rb +20 -18
  118. data/lib/sequel/core.rb +143 -11
  119. data/lib/sequel/database/connecting.rb +66 -29
  120. data/lib/sequel/database/dataset.rb +4 -4
  121. data/lib/sequel/database/dataset_defaults.rb +63 -2
  122. data/lib/sequel/database/logging.rb +7 -2
  123. data/lib/sequel/database/misc.rb +192 -51
  124. data/lib/sequel/database/query.rb +299 -114
  125. data/lib/sequel/database/schema_generator.rb +103 -36
  126. data/lib/sequel/database/schema_methods.rb +310 -69
  127. data/lib/sequel/dataset/actions.rb +344 -90
  128. data/lib/sequel/dataset/features.rb +101 -8
  129. data/lib/sequel/dataset/graph.rb +42 -15
  130. data/lib/sequel/dataset/misc.rb +52 -25
  131. data/lib/sequel/dataset/mutation.rb +12 -7
  132. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  133. data/lib/sequel/dataset/query.rb +322 -68
  134. data/lib/sequel/dataset/sql.rb +716 -293
  135. data/lib/sequel/dataset.rb +8 -0
  136. data/lib/sequel/exceptions.rb +4 -0
  137. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  138. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  139. data/lib/sequel/extensions/blank.rb +4 -0
  140. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  141. data/lib/sequel/extensions/connection_validator.rb +109 -0
  142. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  143. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  144. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  145. data/lib/sequel/extensions/inflector.rb +4 -0
  146. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  147. data/lib/sequel/extensions/migration.rb +100 -11
  148. data/lib/sequel/extensions/named_timezones.rb +9 -0
  149. data/lib/sequel/extensions/null_dataset.rb +94 -0
  150. data/lib/sequel/extensions/pagination.rb +4 -0
  151. data/lib/sequel/extensions/pg_array.rb +539 -0
  152. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  153. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  154. data/lib/sequel/extensions/pg_hstore.rb +334 -0
  155. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  156. data/lib/sequel/extensions/pg_inet.rb +113 -0
  157. data/lib/sequel/extensions/pg_interval.rb +191 -0
  158. data/lib/sequel/extensions/pg_json.rb +212 -0
  159. data/lib/sequel/extensions/pg_range.rb +513 -0
  160. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  161. data/lib/sequel/extensions/pg_row.rb +576 -0
  162. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  163. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  164. data/lib/sequel/extensions/pretty_table.rb +17 -72
  165. data/lib/sequel/extensions/query.rb +8 -4
  166. data/lib/sequel/extensions/query_literals.rb +79 -0
  167. data/lib/sequel/extensions/schema_caching.rb +76 -0
  168. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  169. data/lib/sequel/extensions/select_remove.rb +39 -0
  170. data/lib/sequel/extensions/server_block.rb +140 -0
  171. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  172. data/lib/sequel/extensions/sql_expr.rb +8 -110
  173. data/lib/sequel/extensions/string_date_time.rb +4 -0
  174. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  175. data/lib/sequel/extensions/to_dot.rb +99 -83
  176. data/lib/sequel/model/associations.rb +1263 -409
  177. data/lib/sequel/model/base.rb +624 -171
  178. data/lib/sequel/model/errors.rb +1 -1
  179. data/lib/sequel/model/exceptions.rb +24 -2
  180. data/lib/sequel/model/inflections.rb +1 -1
  181. data/lib/sequel/model.rb +30 -11
  182. data/lib/sequel/no_core_ext.rb +2 -0
  183. data/lib/sequel/plugins/active_model.rb +13 -1
  184. data/lib/sequel/plugins/association_pks.rb +137 -16
  185. data/lib/sequel/plugins/caching.rb +25 -18
  186. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  187. data/lib/sequel/plugins/composition.rb +44 -12
  188. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  189. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  190. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  191. data/lib/sequel/plugins/dirty.rb +214 -0
  192. data/lib/sequel/plugins/eager_each.rb +59 -0
  193. data/lib/sequel/plugins/force_encoding.rb +6 -6
  194. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  195. data/lib/sequel/plugins/identity_map.rb +134 -15
  196. data/lib/sequel/plugins/instance_filters.rb +10 -0
  197. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  198. data/lib/sequel/plugins/json_serializer.rb +77 -6
  199. data/lib/sequel/plugins/list.rb +13 -2
  200. data/lib/sequel/plugins/many_through_many.rb +103 -51
  201. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  202. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  203. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  204. data/lib/sequel/plugins/pg_row.rb +121 -0
  205. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  206. data/lib/sequel/plugins/prepared_statements.rb +168 -0
  207. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  208. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  209. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  210. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  211. data/lib/sequel/plugins/schema.rb +8 -3
  212. data/lib/sequel/plugins/serialization.rb +98 -49
  213. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  214. data/lib/sequel/plugins/sharding.rb +21 -54
  215. data/lib/sequel/plugins/single_table_inheritance.rb +57 -12
  216. data/lib/sequel/plugins/static_cache.rb +99 -0
  217. data/lib/sequel/plugins/subclasses.rb +29 -3
  218. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  219. data/lib/sequel/plugins/timestamps.rb +1 -1
  220. data/lib/sequel/plugins/touch.rb +18 -6
  221. data/lib/sequel/plugins/tree.rb +3 -3
  222. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  223. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  224. data/lib/sequel/plugins/validation_class_methods.rb +2 -1
  225. data/lib/sequel/plugins/validation_helpers.rb +57 -4
  226. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  227. data/lib/sequel/sql.rb +721 -141
  228. data/lib/sequel/timezones.rb +67 -40
  229. data/lib/sequel/version.rb +1 -1
  230. data/spec/adapters/db2_spec.rb +146 -0
  231. data/spec/adapters/firebird_spec.rb +53 -39
  232. data/spec/adapters/mssql_spec.rb +261 -66
  233. data/spec/adapters/mysql_spec.rb +597 -352
  234. data/spec/adapters/oracle_spec.rb +92 -117
  235. data/spec/adapters/postgres_spec.rb +2103 -341
  236. data/spec/adapters/spec_helper.rb +16 -6
  237. data/spec/adapters/sqlite_spec.rb +237 -76
  238. data/spec/core/connection_pool_spec.rb +270 -141
  239. data/spec/core/database_spec.rb +980 -524
  240. data/spec/core/dataset_spec.rb +1658 -1243
  241. data/spec/core/expression_filters_spec.rb +741 -334
  242. data/spec/core/mock_adapter_spec.rb +453 -0
  243. data/spec/core/object_graph_spec.rb +88 -129
  244. data/spec/core/schema_generator_spec.rb +4 -4
  245. data/spec/core/schema_spec.rb +556 -53
  246. data/spec/core/spec_helper.rb +7 -48
  247. data/spec/core_extensions_spec.rb +626 -0
  248. data/spec/extensions/active_model_spec.rb +13 -0
  249. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  250. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  251. data/spec/extensions/association_dependencies_spec.rb +15 -25
  252. data/spec/extensions/association_pks_spec.rb +227 -33
  253. data/spec/extensions/association_proxies_spec.rb +4 -4
  254. data/spec/extensions/boolean_readers_spec.rb +25 -25
  255. data/spec/extensions/caching_spec.rb +47 -51
  256. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  257. data/spec/extensions/columns_introspection_spec.rb +91 -0
  258. data/spec/extensions/composition_spec.rb +18 -13
  259. data/spec/extensions/connection_validator_spec.rb +118 -0
  260. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  261. data/spec/extensions/constraint_validations_spec.rb +316 -0
  262. data/spec/extensions/dataset_associations_spec.rb +199 -0
  263. data/spec/extensions/defaults_setter_spec.rb +88 -0
  264. data/spec/extensions/dirty_spec.rb +155 -0
  265. data/spec/extensions/eager_each_spec.rb +34 -0
  266. data/spec/extensions/eval_inspect_spec.rb +67 -0
  267. data/spec/extensions/force_encoding_spec.rb +4 -2
  268. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  269. data/spec/extensions/identity_map_spec.rb +142 -24
  270. data/spec/extensions/inflector_spec.rb +0 -4
  271. data/spec/extensions/instance_filters_spec.rb +11 -21
  272. data/spec/extensions/instance_hooks_spec.rb +72 -0
  273. data/spec/extensions/json_serializer_spec.rb +49 -12
  274. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  275. data/spec/extensions/list_spec.rb +49 -40
  276. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  277. data/spec/extensions/many_through_many_spec.rb +368 -254
  278. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  279. data/spec/extensions/migration_spec.rb +140 -35
  280. data/spec/extensions/named_timezones_spec.rb +29 -11
  281. data/spec/extensions/nested_attributes_spec.rb +268 -89
  282. data/spec/extensions/null_dataset_spec.rb +85 -0
  283. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  284. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  285. data/spec/extensions/pg_array_spec.rb +324 -0
  286. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  287. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  288. data/spec/extensions/pg_hstore_spec.rb +200 -0
  289. data/spec/extensions/pg_inet_spec.rb +47 -0
  290. data/spec/extensions/pg_interval_spec.rb +72 -0
  291. data/spec/extensions/pg_json_spec.rb +99 -0
  292. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  293. data/spec/extensions/pg_range_spec.rb +395 -0
  294. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  295. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  296. data/spec/extensions/pg_row_spec.rb +340 -0
  297. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  298. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  299. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  300. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  301. data/spec/extensions/prepared_statements_spec.rb +87 -0
  302. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  303. data/spec/extensions/pretty_table_spec.rb +7 -6
  304. data/spec/extensions/query_literals_spec.rb +167 -0
  305. data/spec/extensions/query_spec.rb +9 -3
  306. data/spec/extensions/rcte_tree_spec.rb +50 -43
  307. data/spec/extensions/schema_caching_spec.rb +41 -0
  308. data/spec/extensions/schema_dumper_spec.rb +434 -49
  309. data/spec/extensions/schema_spec.rb +30 -49
  310. data/spec/extensions/select_remove_spec.rb +38 -0
  311. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  312. data/spec/extensions/serialization_spec.rb +86 -48
  313. data/spec/extensions/server_block_spec.rb +90 -0
  314. data/spec/extensions/sharding_spec.rb +69 -143
  315. data/spec/extensions/single_table_inheritance_spec.rb +65 -29
  316. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  317. data/spec/extensions/spec_helper.rb +34 -67
  318. data/spec/extensions/split_array_nil_spec.rb +24 -0
  319. data/spec/extensions/sql_expr_spec.rb +29 -60
  320. data/spec/extensions/static_cache_spec.rb +145 -0
  321. data/spec/extensions/subclasses_spec.rb +14 -0
  322. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  323. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  324. data/spec/extensions/timestamps_spec.rb +6 -6
  325. data/spec/extensions/to_dot_spec.rb +8 -11
  326. data/spec/extensions/touch_spec.rb +53 -14
  327. data/spec/extensions/tree_spec.rb +11 -26
  328. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  329. data/spec/extensions/update_primary_key_spec.rb +30 -24
  330. data/spec/extensions/validation_class_methods_spec.rb +77 -68
  331. data/spec/extensions/validation_helpers_spec.rb +48 -37
  332. data/spec/extensions/xml_serializer_spec.rb +29 -4
  333. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  334. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  335. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  336. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  337. data/spec/integration/associations_test.rb +659 -21
  338. data/spec/integration/database_test.rb +25 -5
  339. data/spec/integration/dataset_test.rb +729 -141
  340. data/spec/integration/eager_loader_test.rb +38 -54
  341. data/spec/integration/migrator_test.rb +2 -3
  342. data/spec/integration/model_test.rb +81 -2
  343. data/spec/integration/plugin_test.rb +871 -160
  344. data/spec/integration/prepared_statement_test.rb +272 -128
  345. data/spec/integration/schema_test.rb +377 -57
  346. data/spec/integration/spec_helper.rb +49 -32
  347. data/spec/integration/timezone_test.rb +38 -12
  348. data/spec/integration/transaction_test.rb +183 -8
  349. data/spec/integration/type_test.rb +40 -8
  350. data/spec/model/association_reflection_spec.rb +207 -8
  351. data/spec/model/associations_spec.rb +1206 -693
  352. data/spec/model/base_spec.rb +334 -100
  353. data/spec/model/dataset_methods_spec.rb +45 -27
  354. data/spec/model/eager_loading_spec.rb +828 -692
  355. data/spec/model/hooks_spec.rb +355 -67
  356. data/spec/model/model_spec.rb +251 -179
  357. data/spec/model/plugins_spec.rb +24 -13
  358. data/spec/model/record_spec.rb +506 -232
  359. data/spec/model/spec_helper.rb +16 -77
  360. data/spec/model/validations_spec.rb +38 -2
  361. data/spec/rcov.opts +2 -0
  362. metadata +223 -81
  363. data/spec/core/core_sql_spec.rb +0 -451
@@ -0,0 +1,340 @@
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 handle nils in bound variables" do
162
+ @db.bound_variable_arg(@m::ArrayRow.call([nil, nil]), nil).should == '(,)'
163
+ @db.bound_variable_arg(@m::HashRow.subclass(nil, [:a, :b]).call(:a=>nil, :b=>nil), nil).should == '(,)'
164
+ @db.bound_variable_arg([@m::ArrayRow.call([nil, nil])], nil).should == '{"(,)"}'
165
+ @db.bound_variable_arg([@m::HashRow.subclass(nil, [:a, :b]).call(:a=>nil, :b=>nil)], nil).should == '{"(,)"}'
166
+ end
167
+
168
+ it "should allow registering row type parsers by introspecting system tables" do
169
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
170
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
171
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
172
+ @db.register_row_type(:foo)
173
+ @db.sqls.should == ["SELECT pg_type.oid, typrelid, typarray FROM pg_type WHERE ((typtype = 'c') AND (typname = 'foo')) LIMIT 1",
174
+ "SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
175
+ p1 = @db.conversion_procs[1]
176
+ p1.columns.should == [:bar, :baz]
177
+ p1.column_oids.should == [4, 5]
178
+ p1.column_converters.should == [p4, p5]
179
+ p1.oid.should == 1
180
+ @db.send(:schema_column_type, 'foo').should == :pg_row_foo
181
+ @db.send(:schema_column_type, 'integer').should == :integer
182
+
183
+ c = p1.converter
184
+ c.superclass.should == @m::HashRow
185
+ c.columns.should == [:bar, :baz]
186
+ c.db_type.should == :foo
187
+ p1.typecaster.should == c
188
+
189
+ p1.call('(1,b)').should == {:bar=>1, :baz=>'bb'}
190
+ @db.typecast_value(:pg_row_foo, %w'1 b').should be_a_kind_of(@m::HashRow)
191
+ @db.typecast_value(:pg_row_foo, %w'1 b').should == {:bar=>'1', :baz=>'b'}
192
+ @db.typecast_value(:pg_row_foo, :bar=>'1', :baz=>'b').should == {:bar=>'1', :baz=>'b'}
193
+ @db.literal(p1.call('(1,b)')).should == "ROW(1, 'bb')::foo"
194
+ end
195
+
196
+ it "should allow registering row type parsers for schema qualify types" do
197
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
198
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
199
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
200
+ @db.register_row_type(:foo__bar)
201
+ @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",
202
+ "SELECT attname, atttypid FROM pg_attribute WHERE ((attrelid = 2) AND (attnum > 0) AND NOT attisdropped) ORDER BY attnum"]
203
+ p1 = @db.conversion_procs[1]
204
+ p1.columns.should == [:bar, :baz]
205
+ p1.column_oids.should == [4, 5]
206
+ p1.column_converters.should == [p4, p5]
207
+ p1.oid.should == 1
208
+
209
+ c = p1.converter
210
+ c.superclass.should == @m::HashRow
211
+ c.columns.should == [:bar, :baz]
212
+ c.db_type.should == :foo__bar
213
+ p1.typecaster.should == c
214
+
215
+ p1.call('(1,b)').should == {:bar=>1, :baz=>'bb'}
216
+ @db.typecast_value(:pg_row_foo__bar, %w'1 b').should == {:bar=>'1', :baz=>'b'}
217
+ @db.typecast_value(:pg_row_foo__bar, :bar=>'1', :baz=>'b').should == {:bar=>'1', :baz=>'b'}
218
+ @db.literal(p1.call('(1,b)')).should == "ROW(1, 'bb')::foo.bar"
219
+ end
220
+
221
+ it "should allow registering with a custom converter" do
222
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
223
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
224
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
225
+ c = proc{|h| [h]}
226
+ @db.register_row_type(:foo, :converter=>c)
227
+ o = @db.conversion_procs[1].call('(1,b)')
228
+ o.should == [{:bar=>1, :baz=>'bb'}]
229
+ o.first.should be_a_kind_of(Hash)
230
+ end
231
+
232
+ it "should allow registering with a custom typecaster" do
233
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
234
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
235
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
236
+ @db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2}})
237
+ @db.typecast_value(:pg_row_foo, %w'1 b').should be_a_kind_of(Hash)
238
+ @db.typecast_value(:pg_row_foo, %w'1 b').should == {:bar=>1, :baz=>'bb'}
239
+ @db.typecast_value(:pg_row_foo, :bar=>'1', :baz=>'b').should == {:bar=>1, :baz=>'bb'}
240
+ @db.typecast_value(:pg_row_foo, 'bar'=>'1', 'baz'=>'b').should == {:bar=>0, :baz=>'aa'}
241
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
242
+ @db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar] || h['bar'] || 0).to_i, :baz=>(h[:baz] || h['baz'] || 'a')*2}})
243
+ @db.typecast_value(:pg_row_foo, %w'1 b').should == {:bar=>1, :baz=>'bb'}
244
+ @db.typecast_value(:pg_row_foo, :bar=>'1', :baz=>'b').should == {:bar=>1, :baz=>'bb'}
245
+ @db.typecast_value(:pg_row_foo, 'bar'=>'1', 'baz'=>'b').should == {:bar=>1, :baz=>'bb'}
246
+ end
247
+
248
+ it "should handle conversion procs that aren't added until later" do
249
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
250
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
251
+ c = proc{|h| [h]}
252
+ @db.register_row_type(:foo, :converter=>c)
253
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
254
+ @db.conversion_procs[1].call('(1,b)').should == [{:bar=>1, :baz=>'bb'}]
255
+ end
256
+
257
+ it "should handle nil values when converting columns" do
258
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
259
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}]]
260
+ called = false
261
+ @db.conversion_procs[4] = proc{|s| called = true; s}
262
+ @db.register_row_type(:foo)
263
+ @db.conversion_procs[1].call('()').should == {:bar=>nil}
264
+ called.should be_false
265
+ end
266
+
267
+ it "should registering array type for row type if type has an array oid" do
268
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
269
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
270
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
271
+ @db.register_row_type(:foo, :typecaster=>proc{|h| {:bar=>(h[:bar]||0).to_i, :baz=>(h[:baz] || 'a')*2}})
272
+ p3 = @db.conversion_procs[3]
273
+
274
+ p3.call('{"(1,b)"}').should == [{:bar=>1, :baz=>'bb'}]
275
+ @db.literal(p3.call('{"(1,b)"}')).should == "ARRAY[ROW(1, 'bb')::foo]::foo[]"
276
+ @db.typecast_value(:foo_array, [{:bar=>'1', :baz=>'b'}]).should == [{:bar=>1, :baz=>'bb'}]
277
+ end
278
+
279
+ it "should allow creating unregisted row types via Database#row_type" do
280
+ @db.literal(@db.row_type(:foo, [1, 2])).should == 'ROW(1, 2)::foo'
281
+ end
282
+
283
+ it "should allow typecasting of registered row types via Database#row_type" do
284
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
285
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
286
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
287
+ @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})})
288
+ @db.literal(@db.row_type(:foo, ['1', 'b'])).should == "ROW(1, 'bb')::foo"
289
+ @db.literal(@db.row_type(:foo, {:bar=>'1', :baz=>'b'})).should == "ROW(1, 'bb')::foo"
290
+ end
291
+
292
+ it "should allow parsing when typecasting registered row types via Database#row_type" do
293
+ @db.conversion_procs[4] = p4 = proc{|s| s.to_i}
294
+ @db.conversion_procs[5] = p5 = proc{|s| s * 2}
295
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}], [{:attname=>'bar', :atttypid=>4}, {:attname=>'baz', :atttypid=>5}]]
296
+ @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)})
297
+ @db.literal(@db.row_type(:foo, ['1', 'b'])).should == "ROW(1, 'bb')::foo"
298
+ end
299
+
300
+ it "should raise an error if attempt to use Database#row_type with an unregistered type and hash" do
301
+ proc{@db.literal(@db.row_type(:foo, {:bar=>'1', :baz=>'b'}))}.should raise_error(Sequel::Error)
302
+ end
303
+
304
+ it "should raise an error if attempt to use Database#row_type with an unhandled type" do
305
+ proc{@db.literal(@db.row_type(:foo, 1))}.should raise_error(Sequel::Error)
306
+ end
307
+
308
+ it "should return ArrayRow and HashRow values as-is" do
309
+ h = @m::HashRow.call(:a=>1)
310
+ a = @m::ArrayRow.call([1])
311
+ @db.row_type(:foo, h).should equal(h)
312
+ @db.row_type(:foo, a).should equal(a)
313
+ end
314
+
315
+ it "should have Sequel.pg_row return a plain ArrayRow" do
316
+ @db.literal(Sequel.pg_row([1, 2, 3])).should == 'ROW(1, 2, 3)'
317
+ end
318
+
319
+ it "should raise an error if attempting to typecast a hash for a parser without columns" do
320
+ proc{@m::Parser.new.typecast(:a=>1)}.should raise_error(Sequel::Error)
321
+ end
322
+
323
+ it "should raise an error if attempting to typecast a unhandled value for a parser" do
324
+ proc{@m::Parser.new.typecast(1)}.should raise_error(Sequel::Error)
325
+ end
326
+
327
+ it "should handle typecasting for a parser without a typecaster" do
328
+ @m::Parser.new.typecast([1]).should == [1]
329
+ end
330
+
331
+ it "should raise an error if no columns are returned when registering a custom row type" do
332
+ @db.fetch = [[{:oid=>1, :typrelid=>2, :typarray=>3}]]
333
+ proc{@db.register_row_type(:foo)}.should raise_error(Sequel::Error)
334
+ end
335
+
336
+ it "should raise an error when registering a custom row type if the type is found found" do
337
+ @db.fetch = []
338
+ proc{@db.register_row_type(:foo)}.should raise_error(Sequel::Error)
339
+ end
340
+ 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