sequel 3.21.0 → 3.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. data/CHANGELOG +855 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +23 -7
  4. data/Rakefile +42 -24
  5. data/bin/sequel +26 -8
  6. data/doc/active_record.rdoc +1 -0
  7. data/doc/advanced_associations.rdoc +171 -11
  8. data/doc/association_basics.rdoc +293 -43
  9. data/doc/dataset_basics.rdoc +5 -5
  10. data/doc/dataset_filtering.rdoc +8 -0
  11. data/doc/mass_assignment.rdoc +56 -0
  12. data/doc/migration.rdoc +85 -552
  13. data/doc/model_hooks.rdoc +78 -27
  14. data/doc/object_model.rdoc +541 -0
  15. data/doc/opening_databases.rdoc +97 -50
  16. data/doc/prepared_statements.rdoc +40 -14
  17. data/doc/querying.rdoc +28 -4
  18. data/doc/reflection.rdoc +30 -4
  19. data/doc/release_notes/3.22.0.txt +39 -0
  20. data/doc/release_notes/3.23.0.txt +172 -0
  21. data/doc/release_notes/3.24.0.txt +420 -0
  22. data/doc/release_notes/3.25.0.txt +88 -0
  23. data/doc/release_notes/3.26.0.txt +88 -0
  24. data/doc/release_notes/3.27.0.txt +82 -0
  25. data/doc/release_notes/3.28.0.txt +304 -0
  26. data/doc/release_notes/3.29.0.txt +459 -0
  27. data/doc/release_notes/3.30.0.txt +135 -0
  28. data/doc/release_notes/3.31.0.txt +146 -0
  29. data/doc/release_notes/3.32.0.txt +202 -0
  30. data/doc/release_notes/3.33.0.txt +157 -0
  31. data/doc/release_notes/3.34.0.txt +671 -0
  32. data/doc/release_notes/3.35.0.txt +144 -0
  33. data/doc/release_notes/3.36.0.txt +245 -0
  34. data/doc/schema_modification.rdoc +572 -0
  35. data/doc/sharding.rdoc +95 -3
  36. data/doc/testing.rdoc +129 -0
  37. data/doc/thread_safety.rdoc +17 -0
  38. data/doc/transactions.rdoc +137 -0
  39. data/doc/virtual_rows.rdoc +51 -2
  40. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  41. data/lib/sequel/adapters/ado.rb +40 -18
  42. data/lib/sequel/adapters/amalgalite.rb +17 -9
  43. data/lib/sequel/adapters/db2.rb +175 -86
  44. data/lib/sequel/adapters/dbi.rb +15 -15
  45. data/lib/sequel/adapters/do/mysql.rb +17 -11
  46. data/lib/sequel/adapters/do/postgres.rb +1 -57
  47. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  48. data/lib/sequel/adapters/do.rb +18 -36
  49. data/lib/sequel/adapters/firebird.rb +27 -208
  50. data/lib/sequel/adapters/ibmdb.rb +453 -0
  51. data/lib/sequel/adapters/informix.rb +6 -23
  52. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  53. data/lib/sequel/adapters/jdbc/db2.rb +47 -0
  54. data/lib/sequel/adapters/jdbc/derby.rb +287 -0
  55. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  56. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  57. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  58. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  59. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  61. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  62. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
  64. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  65. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  66. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  67. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  68. data/lib/sequel/adapters/jdbc.rb +265 -94
  69. data/lib/sequel/adapters/mock.rb +361 -0
  70. data/lib/sequel/adapters/mysql.rb +118 -222
  71. data/lib/sequel/adapters/mysql2.rb +58 -57
  72. data/lib/sequel/adapters/odbc/mssql.rb +15 -7
  73. data/lib/sequel/adapters/odbc.rb +28 -9
  74. data/lib/sequel/adapters/openbase.rb +3 -5
  75. data/lib/sequel/adapters/oracle.rb +349 -53
  76. data/lib/sequel/adapters/postgres.rb +356 -107
  77. data/lib/sequel/adapters/shared/access.rb +32 -9
  78. data/lib/sequel/adapters/shared/db2.rb +342 -0
  79. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  80. data/lib/sequel/adapters/shared/informix.rb +53 -0
  81. data/lib/sequel/adapters/shared/mssql.rb +335 -118
  82. data/lib/sequel/adapters/shared/mysql.rb +340 -76
  83. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  84. data/lib/sequel/adapters/shared/oracle.rb +243 -45
  85. data/lib/sequel/adapters/shared/postgres.rb +524 -279
  86. data/lib/sequel/adapters/shared/progress.rb +5 -8
  87. data/lib/sequel/adapters/shared/sqlite.rb +265 -89
  88. data/lib/sequel/adapters/sqlite.rb +101 -39
  89. data/lib/sequel/adapters/swift/mysql.rb +9 -12
  90. data/lib/sequel/adapters/swift/postgres.rb +9 -74
  91. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  92. data/lib/sequel/adapters/swift.rb +8 -7
  93. data/lib/sequel/adapters/tinytds.rb +161 -21
  94. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  95. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  96. data/lib/sequel/ast_transformer.rb +194 -0
  97. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  98. data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
  99. data/lib/sequel/connection_pool/single.rb +5 -0
  100. data/lib/sequel/connection_pool/threaded.rb +38 -14
  101. data/lib/sequel/connection_pool.rb +10 -2
  102. data/lib/sequel/core.rb +118 -11
  103. data/lib/sequel/database/connecting.rb +38 -28
  104. data/lib/sequel/database/dataset.rb +4 -4
  105. data/lib/sequel/database/dataset_defaults.rb +63 -2
  106. data/lib/sequel/database/logging.rb +7 -2
  107. data/lib/sequel/database/misc.rb +150 -46
  108. data/lib/sequel/database/query.rb +215 -65
  109. data/lib/sequel/database/schema_generator.rb +64 -20
  110. data/lib/sequel/database/schema_methods.rb +212 -54
  111. data/lib/sequel/dataset/actions.rb +322 -85
  112. data/lib/sequel/dataset/features.rb +96 -8
  113. data/lib/sequel/dataset/graph.rb +38 -11
  114. data/lib/sequel/dataset/misc.rb +50 -23
  115. data/lib/sequel/dataset/mutation.rb +12 -7
  116. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  117. data/lib/sequel/dataset/query.rb +237 -44
  118. data/lib/sequel/dataset/sql.rb +698 -278
  119. data/lib/sequel/dataset.rb +8 -0
  120. data/lib/sequel/exceptions.rb +4 -0
  121. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  122. data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
  123. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  124. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
  125. data/lib/sequel/extensions/migration.rb +97 -11
  126. data/lib/sequel/extensions/named_timezones.rb +5 -0
  127. data/lib/sequel/extensions/null_dataset.rb +90 -0
  128. data/lib/sequel/extensions/pg_array.rb +460 -0
  129. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  130. data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
  131. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  132. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  133. data/lib/sequel/extensions/pg_inet.rb +89 -0
  134. data/lib/sequel/extensions/pg_json.rb +178 -0
  135. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  136. data/lib/sequel/extensions/pretty_table.rb +5 -71
  137. data/lib/sequel/extensions/query.rb +1 -1
  138. data/lib/sequel/extensions/query_literals.rb +79 -0
  139. data/lib/sequel/extensions/schema_caching.rb +76 -0
  140. data/lib/sequel/extensions/schema_dumper.rb +251 -37
  141. data/lib/sequel/extensions/select_remove.rb +35 -0
  142. data/lib/sequel/extensions/server_block.rb +139 -0
  143. data/lib/sequel/extensions/sql_expr.rb +4 -110
  144. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  145. data/lib/sequel/extensions/to_dot.rb +95 -83
  146. data/lib/sequel/model/associations.rb +1187 -393
  147. data/lib/sequel/model/base.rb +578 -170
  148. data/lib/sequel/model/errors.rb +1 -1
  149. data/lib/sequel/model/exceptions.rb +5 -1
  150. data/lib/sequel/model/inflections.rb +1 -1
  151. data/lib/sequel/model.rb +30 -11
  152. data/lib/sequel/no_core_ext.rb +2 -0
  153. data/lib/sequel/plugins/active_model.rb +13 -1
  154. data/lib/sequel/plugins/association_pks.rb +22 -4
  155. data/lib/sequel/plugins/caching.rb +25 -18
  156. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  157. data/lib/sequel/plugins/composition.rb +44 -12
  158. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  159. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  160. data/lib/sequel/plugins/dirty.rb +214 -0
  161. data/lib/sequel/plugins/eager_each.rb +59 -0
  162. data/lib/sequel/plugins/force_encoding.rb +6 -6
  163. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  164. data/lib/sequel/plugins/identity_map.rb +123 -8
  165. data/lib/sequel/plugins/instance_filters.rb +10 -0
  166. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  167. data/lib/sequel/plugins/json_serializer.rb +58 -6
  168. data/lib/sequel/plugins/list.rb +12 -2
  169. data/lib/sequel/plugins/many_through_many.rb +79 -28
  170. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  171. data/lib/sequel/plugins/nested_attributes.rb +139 -63
  172. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  173. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  174. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  175. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  176. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  177. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  178. data/lib/sequel/plugins/schema.rb +7 -2
  179. data/lib/sequel/plugins/serialization.rb +98 -49
  180. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  181. data/lib/sequel/plugins/sharding.rb +21 -54
  182. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  183. data/lib/sequel/plugins/static_cache.rb +99 -0
  184. data/lib/sequel/plugins/subclasses.rb +10 -2
  185. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  186. data/lib/sequel/plugins/timestamps.rb +1 -1
  187. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  188. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  189. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  190. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  191. data/lib/sequel/sql.rb +526 -59
  192. data/lib/sequel/timezones.rb +57 -38
  193. data/lib/sequel/version.rb +1 -1
  194. data/spec/adapters/db2_spec.rb +146 -0
  195. data/spec/adapters/firebird_spec.rb +1 -1
  196. data/spec/adapters/mssql_spec.rb +142 -40
  197. data/spec/adapters/mysql_spec.rb +380 -234
  198. data/spec/adapters/oracle_spec.rb +67 -77
  199. data/spec/adapters/postgres_spec.rb +1050 -212
  200. data/spec/adapters/spec_helper.rb +8 -5
  201. data/spec/adapters/sqlite_spec.rb +139 -27
  202. data/spec/core/connection_pool_spec.rb +196 -93
  203. data/spec/core/database_spec.rb +879 -510
  204. data/spec/core/dataset_spec.rb +1415 -1086
  205. data/spec/core/expression_filters_spec.rb +554 -109
  206. data/spec/core/mock_adapter_spec.rb +449 -0
  207. data/spec/core/object_graph_spec.rb +86 -114
  208. data/spec/core/schema_generator_spec.rb +4 -4
  209. data/spec/core/schema_spec.rb +482 -38
  210. data/spec/core/spec_helper.rb +6 -48
  211. data/spec/extensions/active_model_spec.rb +13 -0
  212. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  213. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  214. data/spec/extensions/association_dependencies_spec.rb +15 -25
  215. data/spec/extensions/association_pks_spec.rb +66 -32
  216. data/spec/extensions/association_proxies_spec.rb +4 -4
  217. data/spec/extensions/caching_spec.rb +47 -51
  218. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  219. data/spec/extensions/columns_introspection_spec.rb +91 -0
  220. data/spec/extensions/composition_spec.rb +18 -13
  221. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
  222. data/spec/extensions/dataset_associations_spec.rb +199 -0
  223. data/spec/extensions/defaults_setter_spec.rb +64 -0
  224. data/spec/extensions/dirty_spec.rb +155 -0
  225. data/spec/extensions/eager_each_spec.rb +34 -0
  226. data/spec/extensions/force_encoding_spec.rb +4 -2
  227. data/spec/extensions/hook_class_methods_spec.rb +69 -92
  228. data/spec/extensions/identity_map_spec.rb +131 -22
  229. data/spec/extensions/inflector_spec.rb +0 -4
  230. data/spec/extensions/instance_filters_spec.rb +11 -21
  231. data/spec/extensions/instance_hooks_spec.rb +72 -0
  232. data/spec/extensions/json_serializer_spec.rb +49 -6
  233. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  234. data/spec/extensions/list_spec.rb +48 -39
  235. data/spec/extensions/many_through_many_spec.rb +283 -250
  236. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  237. data/spec/extensions/migration_spec.rb +140 -35
  238. data/spec/extensions/named_timezones_spec.rb +29 -11
  239. data/spec/extensions/nested_attributes_spec.rb +251 -89
  240. data/spec/extensions/null_dataset_spec.rb +85 -0
  241. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  242. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  243. data/spec/extensions/pg_array_spec.rb +196 -0
  244. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  245. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  246. data/spec/extensions/pg_hstore_spec.rb +195 -0
  247. data/spec/extensions/pg_inet_spec.rb +44 -0
  248. data/spec/extensions/pg_json_spec.rb +101 -0
  249. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  250. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  251. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  252. data/spec/extensions/prepared_statements_spec.rb +87 -0
  253. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  254. data/spec/extensions/pretty_table_spec.rb +7 -6
  255. data/spec/extensions/query_literals_spec.rb +168 -0
  256. data/spec/extensions/query_spec.rb +6 -0
  257. data/spec/extensions/rcte_tree_spec.rb +50 -43
  258. data/spec/extensions/schema_caching_spec.rb +41 -0
  259. data/spec/extensions/schema_dumper_spec.rb +429 -22
  260. data/spec/extensions/schema_spec.rb +25 -21
  261. data/spec/extensions/select_remove_spec.rb +38 -0
  262. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  263. data/spec/extensions/serialization_spec.rb +85 -43
  264. data/spec/extensions/server_block_spec.rb +90 -0
  265. data/spec/extensions/sharding_spec.rb +69 -143
  266. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  267. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  268. data/spec/extensions/spec_helper.rb +22 -61
  269. data/spec/extensions/sql_expr_spec.rb +29 -60
  270. data/spec/extensions/static_cache_spec.rb +145 -0
  271. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  272. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  273. data/spec/extensions/timestamps_spec.rb +6 -6
  274. data/spec/extensions/to_dot_spec.rb +4 -7
  275. data/spec/extensions/touch_spec.rb +13 -14
  276. data/spec/extensions/tree_spec.rb +11 -26
  277. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  278. data/spec/extensions/update_primary_key_spec.rb +30 -24
  279. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  280. data/spec/extensions/validation_helpers_spec.rb +16 -35
  281. data/spec/extensions/xml_serializer_spec.rb +29 -4
  282. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  283. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  284. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  285. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  286. data/spec/integration/associations_test.rb +659 -21
  287. data/spec/integration/database_test.rb +9 -3
  288. data/spec/integration/dataset_test.rb +605 -91
  289. data/spec/integration/eager_loader_test.rb +19 -21
  290. data/spec/integration/migrator_test.rb +2 -3
  291. data/spec/integration/model_test.rb +81 -2
  292. data/spec/integration/plugin_test.rb +454 -143
  293. data/spec/integration/prepared_statement_test.rb +263 -128
  294. data/spec/integration/schema_test.rb +309 -43
  295. data/spec/integration/spec_helper.rb +40 -30
  296. data/spec/integration/timezone_test.rb +38 -12
  297. data/spec/integration/transaction_test.rb +183 -8
  298. data/spec/integration/type_test.rb +18 -4
  299. data/spec/model/association_reflection_spec.rb +201 -7
  300. data/spec/model/associations_spec.rb +1067 -661
  301. data/spec/model/base_spec.rb +329 -95
  302. data/spec/model/dataset_methods_spec.rb +7 -27
  303. data/spec/model/eager_loading_spec.rb +677 -676
  304. data/spec/model/hooks_spec.rb +349 -67
  305. data/spec/model/model_spec.rb +240 -168
  306. data/spec/model/plugins_spec.rb +24 -13
  307. data/spec/model/record_spec.rb +490 -232
  308. data/spec/model/spec_helper.rb +14 -71
  309. data/spec/model/validations_spec.rb +11 -0
  310. data/spec/rcov.opts +2 -0
  311. metadata +178 -80
@@ -0,0 +1,460 @@
1
+ # The pg_array extension adds support for Sequel to handle
2
+ # PostgreSQL's string and numeric array types. It supports both
3
+ # single and multi-dimensional arrays. For integer and
4
+ # float arrays, it uses a JSON-based parser which is usually written in C
5
+ # and should be fairly fast. For string and decimal arrays, it uses
6
+ # a hand coded parser written in ruby that is unoptimized and probably
7
+ # slow.
8
+ #
9
+ # This extension integrates with Sequel's native postgres adapter, so
10
+ # that when array fields are retrieved, they are parsed and returned
11
+ # as instances of Sequel::Postgres::PGArray subclasses. PGArray is
12
+ # a DelegateClass of Array, so it mostly acts like an array, but not
13
+ # completely (is_a?(Array) is false). If you want the actual array,
14
+ # you can call PGArray#to_a. This is done so that Sequel does not
15
+ # treat a PGArray like an Array by default, which would cause issues.
16
+ #
17
+ # In addition to the parsers, this extension comes with literalizers
18
+ # for PGArray using the standard Sequel literalization callbacks, so
19
+ # they work with on all adapters.
20
+ #
21
+ # To turn an existing Array into a PGArray:
22
+ #
23
+ # array.pg_array
24
+ #
25
+ # You can also provide a type, though it many cases it isn't necessary:
26
+ #
27
+ # array.pg_array(:varchar) # or :int4, :"double precision", etc.
28
+ #
29
+ # So if you want to insert an array into an int4[] database column:
30
+ #
31
+ # DB[:table].insert(:column=>[1, 2, 3].pg_array)
32
+ #
33
+ # If you would like to use PostgreSQL arrays in your model objects, you
34
+ # probably want to modify the schema parsing/typecasting so that it
35
+ # recognizes and correctly handles the arrays, which you can do by:
36
+ #
37
+ # DB.extend Sequel::Postgres::PGArray::DatabaseMethods
38
+ #
39
+ # If you are not using the native postgres adapter, you probably
40
+ # also want to use the typecast_on_load plugin in the model, and
41
+ # set it to typecast the array column(s) on load.
42
+ #
43
+ # If you want an easy way to call PostgreSQL array functions and
44
+ # operators, look into the pg_array_ops extension.
45
+ #
46
+ # This extension requires both the json and delegate libraries.
47
+ #
48
+ # == Additional License
49
+ #
50
+ # PGArray::Parser code was translated from Javascript code in the
51
+ # node-postgres project and has the following additional license:
52
+ #
53
+ # Copyright (c) 2010 Brian Carlson (brian.m.carlson@gmail.com)
54
+ #
55
+ # Permission is hereby granted, free of charge, to any person obtaining
56
+ # a copy of this software and associated documentation files (the
57
+ # "Software"), to deal in the Software without restriction, including
58
+ # without limitation the rights to use, copy, modify, merge, publish,
59
+ # distribute, sublicense, and/or sell copies of the Software, and to
60
+ # permit persons to whom the Software is furnished to do so, subject
61
+ # to the following conditions:
62
+ #
63
+ # The above copyright notice and this permission notice shall be included
64
+ # in all copies or substantial portions of the Software.
65
+ #
66
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
67
+ # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
68
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
69
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
70
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
71
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
72
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
73
+
74
+ require 'delegate'
75
+ require 'json'
76
+
77
+ module Sequel
78
+ module Postgres
79
+ # Base class for the PostgreSQL array types. Subclasses generally
80
+ # just deal with parsing, so instances manually created from arrays
81
+ # can use this class correctly.
82
+ class PGArray < DelegateClass(Array)
83
+ ARRAY = "ARRAY".freeze
84
+ DOUBLE_COLON = '::'.freeze
85
+ EMPTY_BRACKET = '[]'.freeze
86
+ OPEN_BRACKET = '['.freeze
87
+ CLOSE_BRACKET = ']'.freeze
88
+ COMMA = ','.freeze
89
+ BACKSLASH = '\\'.freeze
90
+ EMPTY_STRING = ''.freeze
91
+ OPEN_BRACE = '{'.freeze
92
+ CLOSE_BRACE = '}'.freeze
93
+ NULL = 'NULL'.freeze
94
+ QUOTE = '"'.freeze
95
+
96
+ module DatabaseMethods
97
+ ESCAPE_RE = /("|\\)/.freeze
98
+ ESCAPE_REPLACEMENT = '\\\\\1'.freeze
99
+
100
+ # Reset the conversion procs when extending the Database object, so
101
+ # it will pick up the array convertors. This is only done for the native
102
+ # postgres adapter.
103
+ def self.extended(db)
104
+ db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
105
+ end
106
+
107
+ # Handle arrays in bound variables
108
+ def bound_variable_arg(arg, conn)
109
+ case arg
110
+ when PGArray
111
+ bound_variable_array(arg.to_a)
112
+ when Array
113
+ bound_variable_array(arg)
114
+ else
115
+ super
116
+ end
117
+ end
118
+
119
+ # Make the column type detection deal with string and numeric array types.
120
+ def schema_column_type(db_type)
121
+ case db_type
122
+ when /\A(character( varying)?|text).*\[\]\z/io
123
+ :string_array
124
+ when /\A(integer|bigint|smallint)\[\]\z/io
125
+ :integer_array
126
+ when /\A(real|double precision)\[\]\z/io
127
+ :float_array
128
+ when /\Anumeric.*\[\]\z/io
129
+ :decimal_array
130
+ else
131
+ super
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ # Format arrays used in bound variables.
138
+ def bound_variable_array(a)
139
+ case a
140
+ when Array
141
+ "{#{a.map{|i| bound_variable_array(i)}.join(COMMA)}}"
142
+ when String
143
+ "\"#{a.gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)}\""
144
+ else
145
+ literal(a)
146
+ end
147
+ end
148
+
149
+ # Given a value to typecast and the type of PGArray subclass:
150
+ # * If given a PGArray, just return the value (even if different subclass)
151
+ # * If given an Array, create a new instance of the subclass
152
+ # * If given a String, call the parser for the subclass with it.
153
+ def typecast_value_pg_array(value, klass)
154
+ case value
155
+ when PGArray
156
+ value
157
+ when Array
158
+ klass.new(value)
159
+ when String
160
+ klass.parse(value)
161
+ else
162
+ raise Sequel::InvalidValue, "invalid value for #{klass}: #{value.inspect}"
163
+ end
164
+ end
165
+
166
+ # Create typecast methods for the supported array types that
167
+ # delegate to typecast_value_pg_array with the related class.
168
+ %w'string integer float decimal'.each do |t|
169
+ class_eval("def typecast_value_#{t}_array(v) typecast_value_pg_array(v, PG#{t.capitalize}Array) end", __FILE__, __LINE__)
170
+ end
171
+ end
172
+
173
+ # PostgreSQL array parser that handles both text and numeric
174
+ # input. Because PostgreSQL arrays can contain objects that
175
+ # can be literalized in any number of ways, it is not possible
176
+ # to make a fully generic parser.
177
+ #
178
+ # This parser is very simple and unoptimized, but should still
179
+ # be O(n) where n is the length of the input string.
180
+ class Parser
181
+ attr_reader :pos
182
+
183
+ # Set the source for the input, and any converter callable
184
+ # to call with objects to be created. For nested parsers
185
+ # the source may contain text after the end current parse,
186
+ # which will be ignored.
187
+ def initialize(source, converter=nil)
188
+ @source = source
189
+ @source_length = source.length
190
+ @converter = converter
191
+ @pos = -1
192
+ @entries = []
193
+ @recorded = ""
194
+ @dimension = 0
195
+ end
196
+
197
+ # Return 2 objects, whether the next character in the input
198
+ # was escaped with a backslash, and what the next character is.
199
+ def next_char
200
+ @pos += 1
201
+ if (c = @source[@pos..@pos]) == BACKSLASH
202
+ @pos += 1
203
+ [true, @source[@pos..@pos]]
204
+ else
205
+ [false, c]
206
+ end
207
+ end
208
+
209
+ # Add a new character to the buffer of recorded characters.
210
+ def record(c)
211
+ @recorded << c
212
+ end
213
+
214
+ # Take the buffer of recorded characters and add it to the array
215
+ # of entries, and use a new buffer for recorded characters.
216
+ def new_entry(include_empty=false)
217
+ if !@recorded.empty? || include_empty
218
+ entry = @recorded
219
+ if entry == NULL && !include_empty
220
+ entry = nil
221
+ elsif @converter
222
+ entry = @converter.call(entry)
223
+ end
224
+ @entries.push(entry)
225
+ @recorded = ""
226
+ end
227
+ end
228
+
229
+ # Parse the input character by character, returning an array
230
+ # of parsed (and potentially converted) objects.
231
+ def parse(nested=false)
232
+ # quote sets whether we are inside of a quoted string.
233
+ quote = false
234
+ until @pos >= @source_length
235
+ escaped, char = next_char
236
+ if char == OPEN_BRACE && !quote
237
+ @dimension += 1
238
+ if (@dimension > 1)
239
+ # Multi-dimensional array encounter, use a subparser
240
+ # to parse the next level down.
241
+ subparser = self.class.new(@source[@pos..-1], @converter)
242
+ @entries.push(subparser.parse(true))
243
+ @pos += subparser.pos - 1
244
+ end
245
+ elsif char == CLOSE_BRACE && !quote
246
+ @dimension -= 1
247
+ if (@dimension == 0)
248
+ new_entry
249
+ # Exit early if inside a subparser, since the
250
+ # text after parsing the current level should be
251
+ # ignored as it is handled by the parent parser.
252
+ return @entries if nested
253
+ end
254
+ elsif char == QUOTE && !escaped
255
+ # If already inside the quoted string, this is the
256
+ # ending quote, so add the entry. Otherwise, this
257
+ # is the opening quote, so set the quote flag.
258
+ new_entry(true) if quote
259
+ quote = !quote
260
+ elsif char == COMMA && !quote
261
+ # If not inside a string and a comma occurs, it indicates
262
+ # the end of the entry, so add the entry.
263
+ new_entry
264
+ else
265
+ # Add the character to the recorded character buffer.
266
+ record(char)
267
+ end
268
+ end
269
+ raise Sequel::Error, "array dimensions not balanced" unless @dimension == 0
270
+ @entries
271
+ end
272
+ end
273
+
274
+ # Parse the string using the generalized parser, setting the type
275
+ # if given.
276
+ def self.parse(string, type=nil)
277
+ new(Parser.new(string, method(:convert_item)).parse, type)
278
+ end
279
+
280
+ # Return the item as-is by default, making conversion a no-op.
281
+ def self.convert_item(s)
282
+ s
283
+ end
284
+ private_class_method :convert_item
285
+
286
+ # The type of this array. May be nil if no type was given. If a type
287
+ # is provided, the array is automatically casted to this type when
288
+ # literalizing. This type is the underlying type, not the array type
289
+ # itself, so for an int4[] database type, it should be :int4 or 'int4'
290
+ attr_accessor :array_type
291
+
292
+ # Set the array to delegate to, and a database type.
293
+ def initialize(array, type=nil)
294
+ super(array)
295
+ self.array_type = type
296
+ end
297
+
298
+ # The delegated object is always an array.
299
+ alias to_a __getobj__
300
+
301
+ # Append the array SQL to the given sql string.
302
+ # If the receiver has a type, add a cast to the
303
+ # database array type.
304
+ def sql_literal_append(ds, sql)
305
+ sql << ARRAY
306
+ _literal_append(sql, ds, to_a)
307
+ if at = array_type
308
+ sql << DOUBLE_COLON << at.to_s << EMPTY_BRACKET
309
+ end
310
+ end
311
+
312
+ private
313
+
314
+ # Recursive method that handles multi-dimensional
315
+ # arrays, surrounding each with [] and interspersing
316
+ # entries with ,.
317
+ def _literal_append(sql, ds, array)
318
+ sql << OPEN_BRACKET
319
+ comma = false
320
+ commas = COMMA
321
+ array.each do |i|
322
+ sql << commas if comma
323
+ if i.is_a?(Array)
324
+ _literal_append(sql, ds, i)
325
+ else
326
+ ds.literal_append(sql, i)
327
+ end
328
+ comma = true
329
+ end
330
+ sql << CLOSE_BRACKET
331
+ end
332
+ end
333
+
334
+ # PGArray subclass handling integer and float types, using a fast JSON
335
+ # parser. Does not handle numeric/decimal types, since JSON does deal
336
+ # with arbitrary precision (see PGDecimalArray for that).
337
+ class PGNumericArray < PGArray
338
+ # Character conversion map mapping input strings to JSON replacements
339
+ SUBST = {'{'.freeze=>'['.freeze, '}'.freeze=>']'.freeze, 'NULL'.freeze=>'null'.freeze}
340
+
341
+ # Regular expression matching input strings to convert
342
+ SUBST_RE = %r[\{|\}|NULL].freeze
343
+
344
+ # Parse the input string by using a gsub to convert non-JSON characters to
345
+ # JSON, running it through a regular JSON parser, and the doing a recursive
346
+ # map over the output to make sure the entries are in the correct type (mostly,
347
+ # to make sure real/double precision database types are returned as float and
348
+ # not integer).
349
+ def self.parse(string, type=nil)
350
+ new(recursive_map(JSON.parse(string.gsub(SUBST_RE){|m| SUBST[m]})), type)
351
+ end
352
+
353
+ # Convert each item in the array to the correct type, handling multi-dimensional
354
+ # arrays.
355
+ def self.recursive_map(array)
356
+ array.map do |i|
357
+ if i.is_a?(Array)
358
+ recursive_map(i)
359
+ elsif i
360
+ convert_item(i)
361
+ end
362
+ end
363
+ end
364
+ private_class_method :recursive_map
365
+ end
366
+
367
+ # PGArray subclass for decimal/numeric types. Uses the general
368
+ # parser as the JSON parser cannot handle arbitrary precision numbers.
369
+ class PGDecimalArray < PGArray
370
+ # Convert the item to a BigDecimal.
371
+ def self.convert_item(s)
372
+ BigDecimal.new(s.to_s)
373
+ end
374
+ private_class_method :convert_item
375
+
376
+ ARRAY_TYPE = 'decimal'.freeze
377
+
378
+ # Use the decimal type by default.
379
+ def array_type
380
+ super || ARRAY_TYPE
381
+ end
382
+ end
383
+
384
+ # PGArray subclass for handling real/double precision arrays.
385
+ class PGFloatArray < PGNumericArray
386
+ # Convert the item to a float.
387
+ def self.convert_item(s)
388
+ s.to_f
389
+ end
390
+ private_class_method :convert_item
391
+
392
+ ARRAY_TYPE = 'double precision'.freeze
393
+
394
+ # Use the double precision type by default.
395
+ def array_type
396
+ super || ARRAY_TYPE
397
+ end
398
+ end
399
+
400
+ # PGArray subclass for handling int2/int4/int8 arrays.
401
+ class PGIntegerArray < PGNumericArray
402
+ ARRAY_TYPE = 'int4'.freeze
403
+
404
+ # Use the int4 type by default.
405
+ def array_type
406
+ super || ARRAY_TYPE
407
+ end
408
+ end
409
+
410
+ # PGArray subclass for handling char/varchar/text arrays.
411
+ class PGStringArray < PGArray
412
+ CHAR = 'char'.freeze
413
+ VARCHAR = 'varchar'.freeze
414
+ TEXT = 'text'.freeze
415
+
416
+ # By default, use a text array. If char is given without
417
+ # a size, use varchar instead, as otherwise Postgres assumes
418
+ # length of 1, which is likely to cause data loss.
419
+ def array_type
420
+ case (c = super)
421
+ when nil
422
+ TEXT
423
+ when CHAR, :char
424
+ VARCHAR
425
+ else
426
+ c
427
+ end
428
+ end
429
+ end
430
+
431
+ PG_TYPES = {} unless defined?(PG_TYPES)
432
+
433
+ # Automatically convert the built-in numeric and text array
434
+ # types. to PGArray instances on retrieval if the native
435
+ # postgres adapter is used.
436
+ [ [1005, PGIntegerArray, 'int2'.freeze],
437
+ [1007, PGIntegerArray, 'int4'.freeze],
438
+ [1016, PGIntegerArray, 'int8'.freeze],
439
+ [1021, PGFloatArray, 'real'.freeze],
440
+ [1022, PGFloatArray, 'double precision'.freeze],
441
+ [1231, PGDecimalArray, 'numeric'.freeze],
442
+ [1009, PGStringArray, 'text'.freeze],
443
+ [1014, PGStringArray, 'char'.freeze],
444
+ [1015, PGStringArray, 'varchar'.freeze]
445
+ ].each do |ftype, klass, type|
446
+ meth = klass.method(:parse)
447
+ PG_TYPES[ftype] = lambda{|s| meth.call(s, type)}
448
+ end
449
+ end
450
+ end
451
+
452
+ class Array
453
+ # Return a PGArray proxy to the receiver, using a
454
+ # specific database type if given. This is mostly useful
455
+ # as a short cut for creating PGArray objects that didn't
456
+ # come from the database.
457
+ def pg_array(type=nil)
458
+ Sequel::Postgres::PGArray.new(self, type)
459
+ end
460
+ end
@@ -0,0 +1,220 @@
1
+ # The pg_array_ops extension adds support to Sequel's DSL to make
2
+ # it easier to call PostgreSQL array functions and operators. The
3
+ # most common usage is taking an object that represents an SQL
4
+ # identifier (such as a :symbol), and calling #pg_array on it:
5
+ #
6
+ # ia = :int_array_column.pg_array
7
+ #
8
+ # This creates a Sequel::Postgres::ArrayOp object that can be used
9
+ # for easier querying:
10
+ #
11
+ # ia[1] # int_array_column[1]
12
+ # ia[1][2] # int_array_column[1][2]
13
+ #
14
+ # ia.contains(:other_int_array_column) # @>
15
+ # ia.contained_by(:other_int_array_column) # <@
16
+ # ia.overlaps(:other_int_array_column) # &&
17
+ # ia.concat(:other_int_array_column) # ||
18
+ #
19
+ # ia.push(1) # int_array_column || 1
20
+ # ia.unshift(1) # 1 || int_array_column
21
+ #
22
+ # ia.any # ANY(int_array_column)
23
+ # ia.all # ALL(int_array_column)
24
+ # ia.dims # array_dims(int_array_column)
25
+ # ia.length # array_length(int_array_column, 1)
26
+ # ia.length(2) # array_length(int_array_column, 2)
27
+ # ia.lower # array_lower(int_array_column, 1)
28
+ # ia.lower(2) # array_lower(int_array_column, 2)
29
+ # ia.join # array_to_string(int_array_column, '', NULL)
30
+ # ia.join(':') # array_to_string(int_array_column, ':', NULL)
31
+ # ia.join(':', ' ') # array_to_string(int_array_column, ':', ' ')
32
+ # ia.unnest # unnest(int_array_column)
33
+ #
34
+ # See the PostgreSQL array function and operator documentation for more
35
+ # details on what these functions and operators do.
36
+ #
37
+ # If you are also using the pg_array extension, you should load it before
38
+ # loading this extension. Doing so will allow you to use PGArray#op to get
39
+ # an ArrayOp, allowing you to perform array operations on array literals.
40
+ module Sequel
41
+ module Postgres
42
+ # The ArrayOp class is a simple container for a single object that
43
+ # defines methods that yield Sequel expression objects representing
44
+ # PostgreSQL array operators and functions.
45
+ #
46
+ # In the method documentation examples, assume that:
47
+ #
48
+ # array_op = :array.pg_array
49
+ class ArrayOp < Sequel::SQL::Wrapper
50
+ CONCAT = ["(".freeze, " || ".freeze, ")".freeze].freeze
51
+ CONTAINS = ["(".freeze, " @> ".freeze, ")".freeze].freeze
52
+ CONTAINED_BY = ["(".freeze, " <@ ".freeze, ")".freeze].freeze
53
+ OVERLAPS = ["(".freeze, " && ".freeze, ")".freeze].freeze
54
+
55
+ # Access a member of the array, returns an SQL::Subscript instance:
56
+ #
57
+ # array_op[1] # array[1]
58
+ def [](key)
59
+ Sequel::SQL::Subscript.new(self, [key])
60
+ end
61
+
62
+ # Call the ALL function:
63
+ #
64
+ # array_op.all # ALL(array)
65
+ #
66
+ # Usually used like:
67
+ #
68
+ # dataset.where(1=>array_op.all)
69
+ # # WHERE (1 = ALL(array))
70
+ def all
71
+ function(:ALL)
72
+ end
73
+
74
+ # Call the ANY function:
75
+ #
76
+ # array_op.all # ANY(array)
77
+ #
78
+ # Usually used like:
79
+ #
80
+ # dataset.where(1=>array_op.any)
81
+ # # WHERE (1 = ANY(array))
82
+ def any
83
+ function(:ANY)
84
+ end
85
+
86
+ # Use the contains (@>) operator:
87
+ #
88
+ # array_op.contains(:a) # (array @> a)
89
+ def contains(other)
90
+ bool_op(CONTAINS, other)
91
+ end
92
+
93
+ # Use the contained by (<@) operator:
94
+ #
95
+ # array_op.contained_by(:a) # (array <@ a)
96
+ def contained_by(other)
97
+ bool_op(CONTAINED_BY, other)
98
+ end
99
+
100
+ # Call the array_dims method:
101
+ #
102
+ # array_op.dims # array_dims(array)
103
+ def dims
104
+ function(:array_dims)
105
+ end
106
+
107
+ # Call the array_length method:
108
+ #
109
+ # array_op.length # array_length(array, 1)
110
+ # array_op.length(2) # array_length(array, 2)
111
+ def length(dimension = 1)
112
+ function(:array_length, dimension)
113
+ end
114
+
115
+ # Call the array_lower method:
116
+ #
117
+ # array_op.lower # array_lower(array, 1)
118
+ # array_op.lower(2) # array_lower(array, 2)
119
+ def lower(dimension = 1)
120
+ function(:array_lower, dimension)
121
+ end
122
+
123
+ # Use the overlaps (&&) operator:
124
+ #
125
+ # array_op.overlaps(:a) # (array && a)
126
+ def overlaps(other)
127
+ bool_op(OVERLAPS, other)
128
+ end
129
+
130
+ # Use the concatentation (||) operator:
131
+ #
132
+ # array_op.push(:a) # (array || a)
133
+ # array_op.concat(:a) # (array || a)
134
+ def push(other)
135
+ array_op(CONCAT, [self, other])
136
+ end
137
+ alias concat push
138
+
139
+ # Return the receiver.
140
+ def pg_array
141
+ self
142
+ end
143
+
144
+ # Call the array_to_string method:
145
+ #
146
+ # array_op.join # array_to_string(array, '', NULL)
147
+ # array_op.to_string # array_to_string(array, '', NULL)
148
+ # array_op.join(":") # array_to_string(array, ':', NULL)
149
+ # array_op.join(":", "*") # array_to_string(array, ':', '*')
150
+ def to_string(joiner="", null=nil)
151
+ function(:array_to_string, joiner, null)
152
+ end
153
+ alias join to_string
154
+
155
+ # Call the unnest method:
156
+ #
157
+ # array_op.unnest # unnest(array)
158
+ def unnest
159
+ function(:unnest)
160
+ end
161
+
162
+ # Use the concatentation (||) operator, reversing the order:
163
+ #
164
+ # array_op.unshift(:a) # (a || array)
165
+ def unshift(other)
166
+ array_op(CONCAT, [other, self])
167
+ end
168
+
169
+ private
170
+
171
+ # Return a placeholder literal with the given str and args, wrapped
172
+ # in an ArrayOp, used by operators that return arrays.
173
+ def array_op(str, args)
174
+ ArrayOp.new(Sequel::SQL::PlaceholderLiteralString.new(str, args))
175
+ end
176
+
177
+ # Return a placeholder literal with the given str and args, wrapped
178
+ # in a boolean expression, used by operators that return booleans.
179
+ def bool_op(str, other)
180
+ Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(str, [value, other]))
181
+ end
182
+
183
+ # Return a function with the given name, and the receiver as the first
184
+ # argument, with any additional arguments given.
185
+ def function(name, *args)
186
+ SQL::Function.new(name, self, *args)
187
+ end
188
+ end
189
+
190
+ module ArrayOpMethods
191
+ # Wrap the receiver in an ArrayOp so you can easily use the PostgreSQL
192
+ # array functions and operators with it.
193
+ def pg_array
194
+ ArrayOp.new(self)
195
+ end
196
+ end
197
+
198
+ if defined?(PGArray)
199
+ class PGArray
200
+ # Wrap the PGArray instance in an ArrayOp, allowing you to easily use
201
+ # the PostgreSQL array functions and operators with literal arrays.
202
+ def op
203
+ ArrayOp.new(self)
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ class SQL::GenericExpression
210
+ include Sequel::Postgres::ArrayOpMethods
211
+ end
212
+
213
+ class LiteralString
214
+ include Sequel::Postgres::ArrayOpMethods
215
+ end
216
+ end
217
+
218
+ class Symbol
219
+ include Sequel::Postgres::ArrayOpMethods
220
+ end