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,576 @@
1
+ # The pg_row extension adds support for Sequel to handle
2
+ # PostgreSQL's row-valued/composite types.
3
+ #
4
+ # This extension integrates with Sequel's native postgres adapter, so
5
+ # that when composite fields are retrieved, they are parsed and returned
6
+ # as instances of Sequel::Postgres::PGRow::(HashRow|ArrayRow), or
7
+ # optionally a custom type. HashRow and ArrayRow are DelegateClasses of
8
+ # of Hash and Array, so they mostly act like a hash or array, but not
9
+ # completely (is_a?(Hash) and is_a?(Array) are false). If you want the
10
+ # actual hash for a HashRow, call HashRow#to_hash, and if you want the
11
+ # actual array for an ArrayRow, call ArrayRow#to_a. This is done so
12
+ # that Sequel does not treat a values like an Array or Hash by default,
13
+ # which would cause issues.
14
+ #
15
+ # In addition to the parsers, this extension comes with literalizers
16
+ # for HashRow and ArrayRow using the standard Sequel literalization callbacks, so
17
+ # they work with on all adapters.
18
+ #
19
+ # The first thing you are going to want to do is to load the extension into
20
+ # your Database object. Make sure you load the :pg_array extension first
21
+ # if you plan to use composite types in bound variables:
22
+ #
23
+ # DB.extension(:pg_array, :pg_row)
24
+ #
25
+ # You can create an anonymous row type by calling the Sequel.pg_row with
26
+ # an array:
27
+ #
28
+ # Sequel.pg_row(array)
29
+ #
30
+ # If you have loaded the {core_extensions extension}[link:files/doc/core_extensions_rdoc.html]),
31
+ # you can also use Array#pg_row:
32
+ #
33
+ # array.pg_row
34
+ #
35
+ # However, in most cases you are going to want something beyond anonymous
36
+ # row types. This extension allows you to register row types on a per
37
+ # database basis, using Database#register_row_type:
38
+ #
39
+ # DB.register_row_type(:foo)
40
+ #
41
+ # When you register the row type, Sequel will query the PostgreSQL
42
+ # system tables to find the related metadata, and will setup
43
+ # a custom HashRow subclass for that type. This includes looking up
44
+ # conversion procs for each column in the type, so that when the composite
45
+ # type is returned from the database, the members of the type have
46
+ # the correct type. Additionally, if the composite type also has an
47
+ # array form, Sequel registers an array type for the composite type,
48
+ # so that array columns of the composite type are converted correctly.
49
+ #
50
+ # You can then create values of that type by using Database#row_type:
51
+ #
52
+ # DB.row_type(:address, ['123 Sesame St.', 'Some City', '12345'])
53
+ #
54
+ # Let's say table address has columns street, city, and zip. This would return
55
+ # something similar to:
56
+ #
57
+ # {:street=>'123 Sesame St.', :city=>'Some City', :zip=>'12345'}
58
+ #
59
+ # You can also use a hash:
60
+ #
61
+ # DB.row_type(:address, :street=>'123 Sesame St.', :city=>'Some City', :zip=>'12345')
62
+ #
63
+ # So if you have a person table that has an address column, here's how you
64
+ # could insert into the column:
65
+ #
66
+ # DB[:table].insert(:address=>DB.row_type(:address, :street=>'123 Sesame St.', :city=>'Some City', :zip=>'12345'))
67
+ #
68
+ # This extension requires both the strscan and delegate libraries.
69
+
70
+ require 'delegate'
71
+ require 'strscan'
72
+ Sequel.require 'adapters/utils/pg_types'
73
+
74
+ module Sequel
75
+ module Postgres
76
+ module PGRow
77
+ ROW = 'ROW'.freeze
78
+ CAST = '::'.freeze
79
+
80
+ # Class for row-valued/composite types that are treated as arrays. By default,
81
+ # this is only used for generic PostgreSQL record types, as registered
82
+ # types use HashRow by default.
83
+ class ArrayRow < DelegateClass(Array)
84
+ include Sequel::SQL::AliasMethods
85
+
86
+ class << self
87
+ # The database type for this class. May be nil if this class
88
+ # done not have a specific database type.
89
+ attr_accessor :db_type
90
+
91
+ # Alias new to call, so that the class itself can be used
92
+ # directly as a converter.
93
+ alias call new
94
+ end
95
+
96
+ # Create a subclass associated with a specific database type.
97
+ # This is done so that instances of this subclass are
98
+ # automatically casted to the database type when literalizing.
99
+ def self.subclass(db_type)
100
+ sc = Class.new(self) do
101
+ @db_type = db_type
102
+ end
103
+ end
104
+
105
+ # Sets the database type associated with this instance. This is
106
+ # used to override the class's default database type.
107
+ attr_writer :db_type
108
+
109
+ # Return the instance's database type, or the class's database
110
+ # type if the instance has not overridden it.
111
+ def db_type
112
+ @db_type || self.class.db_type
113
+ end
114
+
115
+ # Append SQL fragment related to this object to the sql.
116
+ def sql_literal_append(ds, sql)
117
+ sql << ROW
118
+ ds.literal_append(sql, to_a)
119
+ if db_type
120
+ sql << CAST
121
+ ds.quote_schema_table_append(sql, db_type)
122
+ end
123
+ end
124
+ end
125
+
126
+ # Class for row-valued/composite types that are treated as hashes.
127
+ # Types registered via Database#register_row_type will use this
128
+ # class by default.
129
+ class HashRow < DelegateClass(Hash)
130
+ include Sequel::SQL::AliasMethods
131
+
132
+ class << self
133
+ # The columns associated with this class.
134
+ attr_accessor :columns
135
+
136
+ # The database type for this class. May be nil if this class
137
+ # done not have a specific database type.
138
+ attr_accessor :db_type
139
+
140
+ # Alias new to call, so that the class itself can be used
141
+ # directly as a converter.
142
+ alias call new
143
+ end
144
+
145
+ # Create a new subclass of this class with the given database
146
+ # type and columns.
147
+ def self.subclass(db_type, columns)
148
+ sc = Class.new(self) do
149
+ @db_type = db_type
150
+ @columns = columns
151
+ end
152
+ end
153
+
154
+ # Return the underlying hash for this delegate object.
155
+ alias to_hash __getobj__
156
+
157
+ # Sets the columns associated with this instance. This is
158
+ # used to override the class's default columns.
159
+ attr_writer :columns
160
+
161
+ # Sets the database type associated with this instance. This is
162
+ # used to override the class's default database type.
163
+ attr_writer :db_type
164
+
165
+ # Return the instance's columns, or the class's columns
166
+ # if the instance has not overridden it.
167
+ def columns
168
+ @columns || self.class.columns
169
+ end
170
+
171
+ # Return the instance's database type, or the class's columns
172
+ # if the instance has not overridden it.
173
+ def db_type
174
+ @db_type || self.class.db_type
175
+ end
176
+
177
+ # Check that the HashRow has valid columns. This should be used
178
+ # before all attempts to literalize the object, since literalization
179
+ # depends on the columns to get the column order.
180
+ def check_columns!
181
+ if columns.nil? || columns.empty?
182
+ raise Error, 'cannot literalize HashRow without columns'
183
+ end
184
+ end
185
+
186
+ # Append SQL fragment related to this object to the sql.
187
+ def sql_literal_append(ds, sql)
188
+ check_columns!
189
+ sql << ROW
190
+ ds.literal_append(sql, values_at(*columns))
191
+ if db_type
192
+ sql << CAST
193
+ ds.quote_schema_table_append(sql, db_type)
194
+ end
195
+ end
196
+ end
197
+
198
+ # This parser-like class splits the PostgreSQL
199
+ # row-valued/composite type output string format
200
+ # into an array of strings. Note this class makes
201
+ # no attempt to handle all input formats that PostgreSQL
202
+ # will accept, it only handles the output format that
203
+ # PostgreSQL uses.
204
+ class Splitter < StringScanner
205
+ OPEN_PAREN = /\(/.freeze
206
+ CLOSE_PAREN = /\)/.freeze
207
+ UNQUOTED_RE = /[^,)]*/.freeze
208
+ SEP_RE = /[,)]/.freeze
209
+ QUOTE_RE = /"/.freeze
210
+ QUOTE_SEP_RE = /"[,)]/.freeze
211
+ QUOTED_RE = /(\\.|""|[^"])*/.freeze
212
+ REPLACE_RE = /\\(.)|"(")/.freeze
213
+ REPLACE_WITH = '\1\2'.freeze
214
+
215
+ # Split the stored string into an array of strings, handling
216
+ # the different types of quoting.
217
+ def parse
218
+ return @result if @result
219
+ values = []
220
+ skip(OPEN_PAREN)
221
+ if skip(CLOSE_PAREN)
222
+ values << nil
223
+ else
224
+ until eos?
225
+ if skip(QUOTE_RE)
226
+ values << scan(QUOTED_RE).gsub(REPLACE_RE, REPLACE_WITH)
227
+ skip(QUOTE_SEP_RE)
228
+ else
229
+ v = scan(UNQUOTED_RE)
230
+ values << (v unless v.empty?)
231
+ skip(SEP_RE)
232
+ end
233
+ end
234
+ end
235
+ values
236
+ end
237
+ end
238
+
239
+ # The Parser is responsible for taking the input string
240
+ # from PostgreSQL, and returning an appropriate ruby
241
+ # object that the type represents, such as an ArrayRow or
242
+ # HashRow.
243
+ class Parser
244
+ # The columns for the parser, if any. If the parser has
245
+ # no columns, it will treat the input as an array. If
246
+ # it has columns, it will treat the input as a hash.
247
+ # If present, should be an array of strings.
248
+ attr_reader :columns
249
+
250
+ # Converters for each member in the composite type. If
251
+ # not present, no conversion will be done, so values will
252
+ # remain strings. If present, should be an array of
253
+ # callable objects.
254
+ attr_reader :column_converters
255
+
256
+ # The OIDs for each member in the composite type. Not
257
+ # currently used, but made available for user code.
258
+ attr_reader :column_oids
259
+
260
+ # A converter for the object as a whole. Used to wrap
261
+ # the returned array/hash in another object, such as an
262
+ # ArrayRow or HashRow. If present, should be callable.
263
+ attr_reader :converter
264
+
265
+ # The oid for the composite type itself.
266
+ attr_reader :oid
267
+
268
+ # A callable object used for typecasting the object. This
269
+ # is similar to the converter, but it is called by the
270
+ # typecasting code, which has different assumptions than
271
+ # the converter. For instance, the converter should be
272
+ # called with all of the member values already typecast,
273
+ # but the typecaster may not be.
274
+ attr_reader :typecaster
275
+
276
+ # Sets each of the parser's attributes, using options with
277
+ # the same name (e.g. :columns sets the columns attribute).
278
+ def initialize(h={})
279
+ @columns = h[:columns]
280
+ @column_converters = h[:column_converters]
281
+ @column_oids = h[:column_oids]
282
+ @converter = h[:converter]
283
+ @typecaster = h[:typecaster]
284
+ @oid = h[:oid]
285
+ end
286
+
287
+ # Convert the PostgreSQL composite type input format into
288
+ # an appropriate ruby object.
289
+ def call(s)
290
+ convert(convert_format(convert_columns(Splitter.new(s).parse)))
291
+ end
292
+
293
+ # Typecast the given object to the appropriate type using the
294
+ # typecaster. Note that this does not conversion for the members
295
+ # of the composite type, since those conversion expect strings and
296
+ # strings may not be provided.
297
+ def typecast(obj)
298
+ case obj
299
+ when Array
300
+ _typecast(convert_format(obj))
301
+ when Hash
302
+ unless @columns
303
+ raise Error, 'PGRow::Parser without columns cannot typecast from a hash'
304
+ end
305
+ _typecast(obj)
306
+ else
307
+ raise Error, 'PGRow::Parser can only typecast arrays and hashes'
308
+ end
309
+ end
310
+
311
+ private
312
+
313
+ # If the parser has a typecaster, call it with
314
+ # the object, otherwise return the object as is.
315
+ def _typecast(obj)
316
+ if t = @typecaster
317
+ t.call(obj)
318
+ else
319
+ obj
320
+ end
321
+ end
322
+
323
+ # If the parser has column converters, map the
324
+ # array of strings input to a array of appropriate
325
+ # ruby objects, one for each converter.
326
+ def convert_columns(arr)
327
+ if ccs = @column_converters
328
+ arr.zip(ccs).map{|v, pr| (v && pr) ? pr.call(v) : v}
329
+ else
330
+ arr
331
+ end
332
+ end
333
+
334
+ # If the parser has columns, return a hash assuming
335
+ # that the array is ordered by the columns.
336
+ def convert_format(arr)
337
+ if cs = @columns
338
+ h = {}
339
+ arr.zip(cs).each{|v, c| h[c] = v}
340
+ h
341
+ else
342
+ arr
343
+ end
344
+ end
345
+
346
+ # If the parser has a converter, call it with the object,
347
+ # otherwise return the object as is.
348
+ def convert(obj)
349
+ if c = @converter
350
+ c.call(obj)
351
+ else
352
+ obj
353
+ end
354
+ end
355
+ end
356
+
357
+ module DatabaseMethods
358
+ ESCAPE_RE = /("|\\)/.freeze
359
+ ESCAPE_REPLACEMENT = '\\\\\1'.freeze
360
+ COMMA = ','.freeze
361
+
362
+ # A hash mapping row type keys (usually symbols), to option
363
+ # hashes. At the least, the values will contain the :parser
364
+ # option for the Parser instance that the type will use.
365
+ attr_reader :row_types
366
+
367
+ # Do some setup for the data structures the module uses.
368
+ def self.extended(db)
369
+ # Return right away if row_types has already been set. This
370
+ # makes things not break if a user extends the database with
371
+ # this module more than once (since extended is called every
372
+ # time).
373
+ return if db.row_types
374
+
375
+ db.instance_eval do
376
+ @row_types = {}
377
+ @row_schema_types = {}
378
+ extend(@row_type_method_module = Module.new)
379
+ end
380
+ end
381
+
382
+ # Handle ArrayRow and HashRow values in bound variables.
383
+ def bound_variable_arg(arg, conn)
384
+ case arg
385
+ when ArrayRow
386
+ "(#{arg.map{|v| bound_variable_array(v) if v}.join(COMMA)})"
387
+ when HashRow
388
+ arg.check_columns!
389
+ "(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v) if v}.join(COMMA)})"
390
+ else
391
+ super
392
+ end
393
+ end
394
+
395
+ # Register a new row type for the Database instance. db_type should be the type
396
+ # symbol. This parses the PostgreSQL system tables to get information the
397
+ # composite type, and by default has the type return instances of a subclass
398
+ # of HashRow.
399
+ #
400
+ # The following options are supported:
401
+ #
402
+ # :converter :: Use a custom converter for the parser.
403
+ # :typecaster :: Use a custom typecaster for the parser.
404
+ def register_row_type(db_type, opts={})
405
+ procs = @conversion_procs
406
+ rel_oid = nil
407
+ array_oid = nil
408
+ parser_opts = {}
409
+
410
+ # Try to handle schema-qualified types.
411
+ type_schema, type_name = schema_and_table(db_type)
412
+ schema_type_string = type_name.to_s
413
+
414
+ # Get basic oid information for the composite type.
415
+ ds = from(:pg_type).
416
+ select(:pg_type__oid, :typrelid, :typarray).
417
+ where([[:typtype, 'c'], [:typname, type_name.to_s]])
418
+ if type_schema
419
+ ds = ds.join(:pg_namespace, [[:oid, :typnamespace], [:nspname, type_schema.to_s]])
420
+ schema_type_symbol = :"pg_row_#{type_schema}__#{type_name}"
421
+ else
422
+ schema_type_symbol = :"pg_row_#{type_name}"
423
+ end
424
+ unless row = ds.first
425
+ raise Error, "row type #{db_type.inspect} not found in database"
426
+ end
427
+ # Manually cast to integer using to_i, because adapter may not cast oid type
428
+ # correctly (e.g. swift)
429
+ parser_opts[:oid], rel_oid, array_oid = row.values_at(:oid, :typrelid, :typarray).map{|i| i.to_i}
430
+
431
+ # Get column names and oids for each of the members of the composite type.
432
+ res = from(:pg_attribute).
433
+ where(:attrelid=>rel_oid).
434
+ where{attnum > 0}.
435
+ exclude(:attisdropped).
436
+ order(:attnum).
437
+ select_map([:attname, :atttypid])
438
+ if res.empty?
439
+ raise Error, "no columns for row type #{db_type.inspect} in database"
440
+ end
441
+ parser_opts[:columns] = res.map{|r| r[0].to_sym}
442
+ parser_opts[:column_oids] = res.map{|r| r[1].to_i}
443
+
444
+ # Using the conversion_procs, lookup converters for each member of the composite type
445
+ parser_opts[:column_converters] = parser_opts[:column_oids].map do |oid|
446
+ if pr = procs[oid]
447
+ pr
448
+ elsif !Sequel::Postgres::STRING_TYPES.include?(oid)
449
+ # It's not a string type, and it's possible a conversion proc for this
450
+ # oid will be added later, so do a runtime check for it.
451
+ lambda{|s| (pr = procs[oid]) ? pr.call(s) : s}
452
+ end
453
+ end
454
+
455
+ # Setup the converter and typecaster
456
+ parser_opts[:converter] = opts.fetch(:converter){HashRow.subclass(db_type, parser_opts[:columns])}
457
+ parser_opts[:typecaster] = opts.fetch(:typecaster, parser_opts[:converter])
458
+
459
+ parser = Parser.new(parser_opts)
460
+ @conversion_procs[parser.oid] = parser
461
+
462
+ if defined?(PGArray) && PGArray.respond_to?(:register) && array_oid && array_oid > 0
463
+ PGArray.register(db_type, :oid=>array_oid, :converter=>parser, :type_procs=>@conversion_procs, :scalar_typecast=>schema_type_symbol)
464
+ end
465
+
466
+ @row_types[db_type] = opts.merge(:parser=>parser)
467
+ @row_schema_types[schema_type_string] = schema_type_symbol
468
+ @row_type_method_module.class_eval do
469
+ meth = :"typecast_value_#{schema_type_symbol}"
470
+ define_method(meth) do |v|
471
+ row_type(db_type, v)
472
+ end
473
+ private meth
474
+ end
475
+
476
+ nil
477
+ end
478
+
479
+ # When reseting conversion procs, reregister all the row types so that
480
+ # the system tables are introspected again, picking up database changes.
481
+ def reset_conversion_procs
482
+ procs = super
483
+
484
+ row_types.each do |db_type, opts|
485
+ register_row_type(db_type, opts)
486
+ end
487
+
488
+ procs
489
+ end
490
+
491
+ # Handle typecasting of the given object to the given database type.
492
+ # In general, the given database type should already be registered,
493
+ # but if obj is an array, this will handled unregistered types.
494
+ def row_type(db_type, obj)
495
+ (type_hash = @row_types[db_type]) &&
496
+ (parser = type_hash[:parser])
497
+
498
+ case obj
499
+ when ArrayRow, HashRow
500
+ obj
501
+ when Array
502
+ if parser
503
+ parser.typecast(obj)
504
+ else
505
+ obj = ArrayRow.new(obj)
506
+ obj.db_type = db_type
507
+ obj
508
+ end
509
+ when Hash
510
+ if parser
511
+ parser.typecast(obj)
512
+ else
513
+ raise InvalidValue, "Database#row_type requires the #{db_type.inspect} type have a registered parser and typecaster when called with a hash"
514
+ end
515
+ else
516
+ raise InvalidValue, "cannot convert #{obj.inspect} to row type #{db_type.inspect}"
517
+ end
518
+ end
519
+
520
+ # Make the column type detection handle registered row types.
521
+ def schema_column_type(db_type)
522
+ if type = @row_schema_types[db_type]
523
+ type
524
+ else
525
+ super
526
+ end
527
+ end
528
+
529
+ private
530
+
531
+ # Format composite types used in bound variable arrays.
532
+ def bound_variable_array(arg)
533
+ case arg
534
+ when ArrayRow
535
+ "\"(#{arg.map{|v| bound_variable_array(v) if v}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
536
+ when HashRow
537
+ arg.check_columns!
538
+ "\"(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v) if v}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\""
539
+ else
540
+ super
541
+ end
542
+ end
543
+ end
544
+ end
545
+
546
+ # Register the default anonymous record type
547
+ PG_TYPES[2249] = PGRow::Parser.new(:converter=>PGRow::ArrayRow)
548
+ if defined?(PGArray) && PGArray.respond_to?(:register)
549
+ PGArray.register('record', :oid=>2287, :scalar_oid=>2249)
550
+ end
551
+ end
552
+
553
+ module SQL::Builders
554
+ # Wraps the expr array in an anonymous Postgres::PGRow::ArrayRow instance.
555
+ def pg_row(expr)
556
+ case expr
557
+ when Array
558
+ Postgres::PGRow::ArrayRow.new(expr)
559
+ else
560
+ # Will only work if pg_row_ops extension is loaded
561
+ pg_row_op(expr)
562
+ end
563
+ end
564
+ end
565
+
566
+ Database.register_extension(:pg_row, Postgres::PGRow::DatabaseMethods)
567
+ end
568
+
569
+ if Sequel.core_extensions?
570
+ class Array
571
+ # Wraps the receiver in an anonymous Sequel::Postgres::PGRow::ArrayRow instance.
572
+ def pg_row
573
+ Sequel::Postgres::PGRow::ArrayRow.new(self)
574
+ end
575
+ end
576
+ end