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