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,169 @@
1
+ # This extension allows Sequel's postgres adapter to automatically
2
+ # parameterize all common queries. Sequel's default behavior has always
3
+ # been to literalize all arguments unless specifically using
4
+ # parameters (via :$arg placeholders and the prepare/call methods).
5
+ # This extension makes Sequel take all string, numeric, date, and
6
+ # time types and automatically turn them into parameters. Example:
7
+ #
8
+ # # Default
9
+ # DB[:test].where(:a=>1)
10
+ # # SQL: SELECT * FROM test WHERE a = 1
11
+ #
12
+ # DB.extend Sequel::Postgres::AutoParameterize::DatabaseMethods
13
+ # DB[:test].where(:a=>1)
14
+ # # SQL: SELECT * FROM test WHERE a = $1 (args: [1])
15
+ #
16
+ # This extension is not necessarily faster or more safe than the
17
+ # default behavior. In some cases it is faster, such as when using
18
+ # large strings. However, there are also some known issues with
19
+ # this approach:
20
+ #
21
+ # 1. Because of the way it operates, it has no context to make a
22
+ # determination about whether to literalize an object or not.
23
+ # For example, if it comes across an integer, it will turn it
24
+ # into a parameter. That breaks code such as:
25
+ #
26
+ # DB[:table].select(:a, :b).order(2, 1)
27
+ #
28
+ # Since it will use the following SQL (which isn't valid):
29
+ #
30
+ # SELECT a, b FROM table ORDER BY $1, $2
31
+ #
32
+ # To work around this, you can either specify the columns
33
+ # manually or use a literal string:
34
+ #
35
+ # DB[:table].select(:a, :b).order(:b, :a)
36
+ # DB[:table].select(:a, :b).order('2, 1'.lit)
37
+ #
38
+ # 2. In order to avoid many type errors, it attempts to guess the
39
+ # appropriate type and automatically casts all placeholders.
40
+ # Unfortunately, if the type guess is incorrect, the query will
41
+ # be rejected. For example, the following works without
42
+ # automatic parameterization, but fails with it:
43
+ #
44
+ # DB[:table].insert(:interval=>'1 day')
45
+ #
46
+ # To work around this, you can just add the necessary casts
47
+ # manually:
48
+ #
49
+ # DB[:table].insert(:interval=>'1 day'.cast(:interval))
50
+ #
51
+ # You can also work around any issues that come up by disabling automatic
52
+ # parameterization by calling the no_auto_parameterize method on the
53
+ # dataset (which returns a clone of the dataset).
54
+ #
55
+ # It is likely there are other corner cases I am not yet aware of
56
+ # when using this extension, so use this extension with caution.
57
+ #
58
+ # This extension is only compatible when using the pg driver, not
59
+ # when using the old postgres driver or the postgres-pr driver.
60
+
61
+ module Sequel
62
+ module Postgres
63
+ # Enable automatically parameterizing queries by hijacking the
64
+ # SQL query string that Sequel builds to also hold the array
65
+ # of parameters.
66
+ module AutoParameterize
67
+ # String that holds an array of parameters
68
+ class StringWithArray < ::String
69
+ # The array of parameters used by this query.
70
+ attr_reader :args
71
+
72
+ # Add a new parameter to this query, which adds
73
+ # the parameter to the array of parameters, and an
74
+ # SQL placeholder to the query itself.
75
+ def add_arg(s, type)
76
+ @args ||= []
77
+ @args << s
78
+ self << "$#{@args.length}::#{type}"
79
+ end
80
+
81
+ # Show args when the string is inspected
82
+ def inspect
83
+ @args ? "#{self}; #{@args.inspect}".inspect : super
84
+ end
85
+ end
86
+
87
+ module DatabaseMethods
88
+ # Extend the database's datasets with the necessary code.
89
+ def self.extended(db)
90
+ db.extend_datasets(DatasetMethods)
91
+ end
92
+
93
+ # If the sql string has an embedded parameter array,
94
+ # extract the arguments from that.
95
+ def execute(sql, opts={})
96
+ if sql.is_a?(StringWithArray) && (args = sql.args)
97
+ opts = opts.merge(:arguments=>args)
98
+ end
99
+ super
100
+ end
101
+ end
102
+
103
+ module DatasetMethods
104
+ # Return a clone of the dataset that will not do
105
+ # automatic parameterization.
106
+ def no_auto_parameterize
107
+ clone(:no_auto_parameterize=>true)
108
+ end
109
+
110
+ # For strings, numeric arguments, and date/time arguments, add
111
+ # them as parameters to the query instead of literalizing them
112
+ # into the SQL.
113
+ def literal_append(sql, v)
114
+ if sql.is_a?(StringWithArray)
115
+ case v
116
+ when String
117
+ case v
118
+ when LiteralString
119
+ super
120
+ when Sequel::SQL::Blob
121
+ sql.add_arg(v, :bytea)
122
+ else
123
+ sql.add_arg(v, :text)
124
+ end
125
+ when Bignum
126
+ sql.add_arg(v, :int8)
127
+ when Fixnum
128
+ sql.add_arg(v, :int4)
129
+ when Float
130
+ sql.add_arg(v, :"double precision")
131
+ when BigDecimal
132
+ sql.add_arg(v, :numeric)
133
+ when Sequel::SQLTime
134
+ sql.add_arg(v, :time)
135
+ when Time, DateTime
136
+ sql.add_arg(v, :timestamp)
137
+ when Date
138
+ sql.add_arg(v, :date)
139
+ else
140
+ super
141
+ end
142
+ else
143
+ super
144
+ end
145
+ end
146
+
147
+ def use_cursor(*)
148
+ super.no_auto_parameterize
149
+ end
150
+
151
+ protected
152
+
153
+ # Disable automatic parameterization for prepared statements,
154
+ # since they will use manual parameterization.
155
+ def to_prepared_statement(*a)
156
+ opts[:no_auto_parameterize] ? super : no_auto_parameterize.to_prepared_statement(*a)
157
+ end
158
+
159
+ private
160
+
161
+ # Unless auto parameterization is turned off, use a string that
162
+ # can store the parameterized arguments.
163
+ def sql_string_origin
164
+ opts[:no_auto_parameterize] ? super : StringWithArray.new
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,296 @@
1
+ # The pg_hstore extension adds support for the PostgreSQL hstore type
2
+ # to Sequel. hstore is an extension that ships with PostgreSQL, and
3
+ # the hstore type stores an arbitrary key-value table, where the keys
4
+ # are strings and the values are strings or NULL.
5
+ #
6
+ # This extension integrates with Sequel's native postgres adapter, so
7
+ # that when hstore fields are retrieved, they are parsed and returned
8
+ # as instances of Sequel::Postgres::HStore. HStore is
9
+ # a DelegateClass of Hash, so it mostly acts like a hash, but not
10
+ # completely (is_a?(Hash) is false). If you want the actual hash,
11
+ # you can call Hstore#to_hash. This is done so that Sequel does not
12
+ # treat a HStore like a Hash by default, which would cause issues.
13
+ #
14
+ # In addition to the parsers, this extension comes with literalizers
15
+ # for HStore using the standard Sequel literalization callbacks, so
16
+ # they work with on all adapters.
17
+ #
18
+ # To turn an existing Hash into an HStore:
19
+ #
20
+ # hash.hstore
21
+ #
22
+ # Since the hstore type only supports strings, non string keys and
23
+ # values are converted to strings
24
+ #
25
+ # {:foo=>1}.hstore.to_hash # {'foo'=>'1'}
26
+ # v = {}.hstore
27
+ # v[:foo] = 1
28
+ # v # {'foo'=>'1'}
29
+ #
30
+ # However, to make life easier, lookups by key are converted to
31
+ # strings (even when accessing the underlying hash directly):
32
+ #
33
+ # {'foo'=>'bar'}.hstore[:foo] # 'bar'
34
+ # {'foo'=>'bar'}.hstore.to_hash[:foo] # 'bar'
35
+ #
36
+ # HStore instances mostly just delegate to the underlying hash
37
+ # instance, so Hash methods that modify the receiver or returned
38
+ # modified copies of the receiver may not do string conversion.
39
+ # The following methods will handle string conversion, and more
40
+ # can be added later if desired:
41
+ #
42
+ # * \[\]
43
+ # * \[\]=
44
+ # * assoc (ruby 1.9 only)
45
+ # * delete
46
+ # * fetch
47
+ # * has_key?
48
+ # * has_value?
49
+ # * include?
50
+ # * key (ruby 1.9 only)
51
+ # * key?
52
+ # * member?
53
+ # * merge
54
+ # * merge!
55
+ # * rassoc (ruby 1.9 only)
56
+ # * replace
57
+ # * store
58
+ # * update
59
+ # * value?
60
+ #
61
+ # If you want to insert a hash into an hstore database column:
62
+ #
63
+ # DB[:table].insert(:column=>{'foo'=>'bar'}.hstore)
64
+ #
65
+ # If you would like to use hstore columns in your model objects, you
66
+ # probably want to modify the schema parsing/typecasting so that it
67
+ # recognizes and correctly handles the hstore columns, which you can
68
+ # do by:
69
+ #
70
+ # DB.extend Sequel::Postgres::HStore::DatabaseMethods
71
+ #
72
+ # If you are not using the native postgres adapter, you probably
73
+ # also want to use the typecast_on_load plugin in the model, and
74
+ # set it to typecast the hstore column(s) on load.
75
+ #
76
+ # This extension requires the delegate and strscan libraries.
77
+
78
+ require 'delegate'
79
+ require 'strscan'
80
+
81
+ module Sequel
82
+ module Postgres
83
+ class HStore < DelegateClass(Hash)
84
+ # Parser for PostgreSQL hstore output format.
85
+ class Parser < StringScanner
86
+ QUOTE_RE = /"/.freeze
87
+ KV_SEP_RE = /"\s*=>\s*/.freeze
88
+ NULL_RE = /NULL/.freeze
89
+ SEP_RE = /,\s*/.freeze
90
+ QUOTED_RE = /(\\"|[^"])*/.freeze
91
+ REPLACE_RE = /\\(.)/.freeze
92
+ REPLACE_WITH = '\1'.freeze
93
+
94
+ # Parse the output format that PostgreSQL uses for hstore
95
+ # columns. Note that this does not attempt to parse all
96
+ # input formats that PostgreSQL will accept. For instance,
97
+ # it expects all keys and non-NULL values to be quoted.
98
+ #
99
+ # Return the resulting hash of objects. This can be called
100
+ # multiple times, it will cache the parsed hash on the first
101
+ # call and use it for subsequent calls.
102
+ def parse
103
+ return @result if @result
104
+ hash = {}
105
+ while !eos?
106
+ skip(QUOTE_RE)
107
+ k = parse_quoted
108
+ skip(KV_SEP_RE)
109
+ if skip(QUOTE_RE)
110
+ v = parse_quoted
111
+ skip(QUOTE_RE)
112
+ else
113
+ scan(NULL_RE)
114
+ v = nil
115
+ end
116
+ skip(SEP_RE)
117
+ hash[k] = v
118
+ end
119
+ @result = hash
120
+ end
121
+
122
+ private
123
+
124
+ # Parse and unescape a quoted key/value.
125
+ def parse_quoted
126
+ scan(QUOTED_RE).gsub(REPLACE_RE, REPLACE_WITH)
127
+ end
128
+ end
129
+
130
+ module DatabaseMethods
131
+ # Reset the conversion procs if using the native postgres adapter.
132
+ def self.extended(db)
133
+ db.reset_conversion_procs if db.respond_to?(:reset_conversion_procs)
134
+ end
135
+
136
+ # Handle hstores in bound variables
137
+ def bound_variable_arg(arg, conn)
138
+ case arg
139
+ when HStore
140
+ arg.unquoted_literal
141
+ when Hash
142
+ HStore.new(arg).unquoted_literal
143
+ else
144
+ super
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ # Recognize the hstore database type.
151
+ def schema_column_type(db_type)
152
+ db_type == 'hstore' ? :hstore : super
153
+ end
154
+
155
+ # Typecast value correctly to HStore. If already an
156
+ # HStore instance, return as is. If a hash, return
157
+ # an HStore version of it. If a string, assume it is
158
+ # in PostgreSQL output format and parse it using the
159
+ # parser.
160
+ def typecast_value_hstore(value)
161
+ case value
162
+ when HStore
163
+ value
164
+ when Hash
165
+ HStore.new(value)
166
+ when String
167
+ HStore.parse(value)
168
+ else
169
+ raise Sequel::InvalidValue, "invalid value for hstore: #{value.inspect}"
170
+ end
171
+ end
172
+ end
173
+
174
+ # Default proc used for all underlying HStore hashes, so that even
175
+ # if you grab the underlying hash, it will still convert non-string
176
+ # keys to strings during lookup.
177
+ DEFAULT_PROC = lambda{|h, k| h[k.to_s] unless k.is_a?(String)}
178
+
179
+ QUOTE = '"'.freeze
180
+ COMMA = ",".freeze
181
+ KV_SEP = "=>".freeze
182
+ NULL = "NULL".freeze
183
+ ESCAPE_RE = /("|\\)/.freeze
184
+ ESCAPE_REPLACE = '\\\\\1'.freeze
185
+ HSTORE_CAST = '::hstore'.freeze
186
+
187
+ # Parse the given string into an HStore, assuming the str is in PostgreSQL
188
+ # hstore output format.
189
+ def self.parse(str)
190
+ new(Parser.new(str).parse)
191
+ end
192
+
193
+ # Override methods that accept key argument to convert to string.
194
+ (%w'[] delete has_key? include? key? member?' + Array((%w'assoc' if RUBY_VERSION >= '1.9.0'))).each do |m|
195
+ class_eval("def #{m}(k) super(k.to_s) end", __FILE__, __LINE__)
196
+ end
197
+
198
+ # Override methods that accept value argument to convert to string unless nil.
199
+ (%w'has_value? value?' + Array((%w'key rassoc' if RUBY_VERSION >= '1.9.0'))).each do |m|
200
+ class_eval("def #{m}(v) super(convert_value(v)) end", __FILE__, __LINE__)
201
+ end
202
+
203
+ # Override methods that accept key and value arguments to convert to string appropriately.
204
+ %w'[]= store'.each do |m|
205
+ class_eval("def #{m}(k, v) super(k.to_s, convert_value(v)) end", __FILE__, __LINE__)
206
+ end
207
+
208
+ # Override methods that take hashes to convert the hashes to using strings for keys and
209
+ # values before using them.
210
+ %w'initialize merge! update replace'.each do |m|
211
+ class_eval("def #{m}(h, &block) super(convert_hash(h), &block) end", __FILE__, __LINE__)
212
+ end
213
+
214
+ # Override to force the key argument to a string.
215
+ def fetch(key, *args, &block)
216
+ super(key.to_s, *args, &block)
217
+ end
218
+
219
+ # Convert the input hash to string keys and values before merging,
220
+ # and return a new HStore instance with the merged hash.
221
+ def merge(hash, &block)
222
+ self.class.new(super(convert_hash(hash), &block))
223
+ end
224
+
225
+ # Return the underlying hash used by this HStore instance.
226
+ alias to_hash __getobj__
227
+
228
+ # Append a literalize version of the hstore to the sql.
229
+ def sql_literal_append(ds, sql)
230
+ ds.literal_append(sql, unquoted_literal)
231
+ sql << HSTORE_CAST
232
+ end
233
+
234
+ # Return a string containing the unquoted, unstring-escaped
235
+ # literal version of the hstore. Separated out for use by
236
+ # the bound argument code.
237
+ def unquoted_literal
238
+ str = ''
239
+ comma = false
240
+ commas = COMMA
241
+ quote = QUOTE
242
+ kv_sep = KV_SEP
243
+ null = NULL
244
+ each do |k, v|
245
+ str << commas if comma
246
+ str << quote << escape_value(k) << quote
247
+ str << kv_sep
248
+ if v.nil?
249
+ str << null
250
+ else
251
+ str << quote << escape_value(v) << quote
252
+ end
253
+ comma = true
254
+ end
255
+ str
256
+ end
257
+
258
+ private
259
+
260
+ # Return a new hash based on the input hash with string
261
+ # keys and string or nil values.
262
+ def convert_hash(h)
263
+ hash = Hash.new(&DEFAULT_PROC)
264
+ h.each{|k,v| hash[k.to_s] = convert_value(v)}
265
+ hash
266
+ end
267
+
268
+ # Return value v as a string unless it is already nil.
269
+ def convert_value(v)
270
+ v.to_s unless v.nil?
271
+ end
272
+
273
+ # Escape key/value strings when literalizing to
274
+ # correctly handle backslash and quote characters.
275
+ def escape_value(k)
276
+ k.to_s.gsub(ESCAPE_RE, ESCAPE_REPLACE)
277
+ end
278
+ end
279
+
280
+ PG_NAMED_TYPES = {} unless defined?(PG_NAMED_TYPES)
281
+ # Associate the named types by default.
282
+ PG_NAMED_TYPES[:hstore] = HStore.method(:parse)
283
+ end
284
+ end
285
+
286
+ class Hash
287
+ # Create a new HStore using the receiver as the input
288
+ # hash. Note that the HStore created will not use the
289
+ # receiver as the backing store, since it has to
290
+ # modify the hash. To get the new backing store, use:
291
+ #
292
+ # hash.hstore.to_hash
293
+ def hstore
294
+ Sequel::Postgres::HStore.new(self)
295
+ end
296
+ end