sequel 4.36.0 → 5.61.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 (760) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG +548 -5749
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +265 -159
  5. data/bin/sequel +34 -12
  6. data/doc/advanced_associations.rdoc +228 -187
  7. data/doc/association_basics.rdoc +281 -291
  8. data/doc/bin_sequel.rdoc +5 -3
  9. data/doc/cheat_sheet.rdoc +86 -51
  10. data/doc/code_order.rdoc +25 -19
  11. data/doc/core_extensions.rdoc +104 -63
  12. data/doc/dataset_basics.rdoc +12 -21
  13. data/doc/dataset_filtering.rdoc +99 -86
  14. data/doc/extensions.rdoc +3 -10
  15. data/doc/fork_safety.rdoc +84 -0
  16. data/doc/mass_assignment.rdoc +74 -31
  17. data/doc/migration.rdoc +59 -51
  18. data/doc/model_dataset_method_design.rdoc +129 -0
  19. data/doc/model_hooks.rdoc +15 -25
  20. data/doc/model_plugins.rdoc +12 -12
  21. data/doc/mssql_stored_procedures.rdoc +3 -3
  22. data/doc/object_model.rdoc +58 -68
  23. data/doc/opening_databases.rdoc +85 -95
  24. data/doc/postgresql.rdoc +263 -38
  25. data/doc/prepared_statements.rdoc +29 -24
  26. data/doc/querying.rdoc +189 -167
  27. data/doc/reflection.rdoc +5 -6
  28. data/doc/release_notes/5.0.0.txt +159 -0
  29. data/doc/release_notes/5.1.0.txt +31 -0
  30. data/doc/release_notes/5.10.0.txt +84 -0
  31. data/doc/release_notes/5.11.0.txt +83 -0
  32. data/doc/release_notes/5.12.0.txt +141 -0
  33. data/doc/release_notes/5.13.0.txt +27 -0
  34. data/doc/release_notes/5.14.0.txt +63 -0
  35. data/doc/release_notes/5.15.0.txt +39 -0
  36. data/doc/release_notes/5.16.0.txt +110 -0
  37. data/doc/release_notes/5.17.0.txt +31 -0
  38. data/doc/release_notes/5.18.0.txt +69 -0
  39. data/doc/release_notes/5.19.0.txt +28 -0
  40. data/doc/release_notes/5.2.0.txt +33 -0
  41. data/doc/release_notes/5.20.0.txt +89 -0
  42. data/doc/release_notes/5.21.0.txt +87 -0
  43. data/doc/release_notes/5.22.0.txt +48 -0
  44. data/doc/release_notes/5.23.0.txt +56 -0
  45. data/doc/release_notes/5.24.0.txt +56 -0
  46. data/doc/release_notes/5.25.0.txt +32 -0
  47. data/doc/release_notes/5.26.0.txt +35 -0
  48. data/doc/release_notes/5.27.0.txt +21 -0
  49. data/doc/release_notes/5.28.0.txt +16 -0
  50. data/doc/release_notes/5.29.0.txt +22 -0
  51. data/doc/release_notes/5.3.0.txt +121 -0
  52. data/doc/release_notes/5.30.0.txt +20 -0
  53. data/doc/release_notes/5.31.0.txt +148 -0
  54. data/doc/release_notes/5.32.0.txt +46 -0
  55. data/doc/release_notes/5.33.0.txt +24 -0
  56. data/doc/release_notes/5.34.0.txt +40 -0
  57. data/doc/release_notes/5.35.0.txt +56 -0
  58. data/doc/release_notes/5.36.0.txt +60 -0
  59. data/doc/release_notes/5.37.0.txt +30 -0
  60. data/doc/release_notes/5.38.0.txt +28 -0
  61. data/doc/release_notes/5.39.0.txt +19 -0
  62. data/doc/release_notes/5.4.0.txt +80 -0
  63. data/doc/release_notes/5.40.0.txt +40 -0
  64. data/doc/release_notes/5.41.0.txt +25 -0
  65. data/doc/release_notes/5.42.0.txt +136 -0
  66. data/doc/release_notes/5.43.0.txt +98 -0
  67. data/doc/release_notes/5.44.0.txt +32 -0
  68. data/doc/release_notes/5.45.0.txt +34 -0
  69. data/doc/release_notes/5.46.0.txt +87 -0
  70. data/doc/release_notes/5.47.0.txt +59 -0
  71. data/doc/release_notes/5.48.0.txt +14 -0
  72. data/doc/release_notes/5.49.0.txt +59 -0
  73. data/doc/release_notes/5.5.0.txt +61 -0
  74. data/doc/release_notes/5.50.0.txt +78 -0
  75. data/doc/release_notes/5.51.0.txt +47 -0
  76. data/doc/release_notes/5.52.0.txt +87 -0
  77. data/doc/release_notes/5.53.0.txt +23 -0
  78. data/doc/release_notes/5.54.0.txt +27 -0
  79. data/doc/release_notes/5.55.0.txt +21 -0
  80. data/doc/release_notes/5.56.0.txt +51 -0
  81. data/doc/release_notes/5.57.0.txt +23 -0
  82. data/doc/release_notes/5.58.0.txt +31 -0
  83. data/doc/release_notes/5.59.0.txt +73 -0
  84. data/doc/release_notes/5.6.0.txt +31 -0
  85. data/doc/release_notes/5.60.0.txt +22 -0
  86. data/doc/release_notes/5.61.0.txt +43 -0
  87. data/doc/release_notes/5.7.0.txt +108 -0
  88. data/doc/release_notes/5.8.0.txt +170 -0
  89. data/doc/release_notes/5.9.0.txt +99 -0
  90. data/doc/schema_modification.rdoc +95 -75
  91. data/doc/security.rdoc +109 -80
  92. data/doc/sharding.rdoc +74 -47
  93. data/doc/sql.rdoc +147 -122
  94. data/doc/testing.rdoc +43 -20
  95. data/doc/thread_safety.rdoc +2 -4
  96. data/doc/transactions.rdoc +97 -18
  97. data/doc/validations.rdoc +52 -50
  98. data/doc/virtual_rows.rdoc +90 -109
  99. data/lib/sequel/adapters/ado/access.rb +15 -17
  100. data/lib/sequel/adapters/ado/mssql.rb +6 -15
  101. data/lib/sequel/adapters/ado.rb +150 -20
  102. data/lib/sequel/adapters/amalgalite.rb +11 -23
  103. data/lib/sequel/adapters/ibmdb.rb +47 -55
  104. data/lib/sequel/adapters/jdbc/db2.rb +29 -39
  105. data/lib/sequel/adapters/jdbc/derby.rb +58 -54
  106. data/lib/sequel/adapters/jdbc/h2.rb +93 -35
  107. data/lib/sequel/adapters/jdbc/hsqldb.rb +24 -31
  108. data/lib/sequel/adapters/jdbc/jtds.rb +2 -10
  109. data/lib/sequel/adapters/jdbc/mssql.rb +3 -11
  110. data/lib/sequel/adapters/jdbc/mysql.rb +17 -20
  111. data/lib/sequel/adapters/jdbc/oracle.rb +22 -18
  112. data/lib/sequel/adapters/jdbc/postgresql.rb +69 -71
  113. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +11 -23
  114. data/lib/sequel/adapters/jdbc/sqlite.rb +47 -11
  115. data/lib/sequel/adapters/jdbc/sqlserver.rb +34 -9
  116. data/lib/sequel/adapters/jdbc/transactions.rb +22 -38
  117. data/lib/sequel/adapters/jdbc.rb +145 -130
  118. data/lib/sequel/adapters/mock.rb +100 -111
  119. data/lib/sequel/adapters/mysql.rb +114 -122
  120. data/lib/sequel/adapters/mysql2.rb +147 -63
  121. data/lib/sequel/adapters/odbc/db2.rb +1 -1
  122. data/lib/sequel/adapters/odbc/mssql.rb +8 -14
  123. data/lib/sequel/adapters/odbc/oracle.rb +11 -0
  124. data/lib/sequel/adapters/odbc.rb +20 -25
  125. data/lib/sequel/adapters/oracle.rb +50 -56
  126. data/lib/sequel/adapters/postgres.rb +305 -327
  127. data/lib/sequel/adapters/postgresql.rb +1 -1
  128. data/lib/sequel/adapters/shared/access.rb +74 -78
  129. data/lib/sequel/adapters/shared/db2.rb +118 -71
  130. data/lib/sequel/adapters/shared/mssql.rb +301 -220
  131. data/lib/sequel/adapters/shared/mysql.rb +299 -217
  132. data/lib/sequel/adapters/shared/oracle.rb +226 -65
  133. data/lib/sequel/adapters/shared/postgres.rb +935 -395
  134. data/lib/sequel/adapters/shared/sqlanywhere.rb +105 -126
  135. data/lib/sequel/adapters/shared/sqlite.rb +447 -173
  136. data/lib/sequel/adapters/sqlanywhere.rb +48 -35
  137. data/lib/sequel/adapters/sqlite.rb +156 -111
  138. data/lib/sequel/adapters/tinytds.rb +30 -38
  139. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  140. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +3 -6
  141. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +2 -2
  142. data/lib/sequel/adapters/utils/mysql_mysql2.rb +87 -0
  143. data/lib/sequel/adapters/utils/mysql_prepared_statements.rb +56 -0
  144. data/lib/sequel/adapters/utils/replace.rb +1 -4
  145. data/lib/sequel/adapters/utils/stored_procedures.rb +7 -22
  146. data/lib/sequel/adapters/utils/unmodified_identifiers.rb +28 -0
  147. data/lib/sequel/ast_transformer.rb +17 -89
  148. data/lib/sequel/connection_pool/sharded_single.rb +18 -15
  149. data/lib/sequel/connection_pool/sharded_threaded.rb +130 -111
  150. data/lib/sequel/connection_pool/single.rb +18 -13
  151. data/lib/sequel/connection_pool/threaded.rb +121 -120
  152. data/lib/sequel/connection_pool.rb +48 -29
  153. data/lib/sequel/core.rb +351 -301
  154. data/lib/sequel/database/connecting.rb +69 -57
  155. data/lib/sequel/database/dataset.rb +13 -5
  156. data/lib/sequel/database/dataset_defaults.rb +18 -102
  157. data/lib/sequel/database/features.rb +18 -4
  158. data/lib/sequel/database/logging.rb +12 -11
  159. data/lib/sequel/database/misc.rb +180 -122
  160. data/lib/sequel/database/query.rb +47 -27
  161. data/lib/sequel/database/schema_generator.rb +178 -84
  162. data/lib/sequel/database/schema_methods.rb +172 -97
  163. data/lib/sequel/database/transactions.rb +205 -44
  164. data/lib/sequel/database.rb +17 -2
  165. data/lib/sequel/dataset/actions.rb +339 -155
  166. data/lib/sequel/dataset/dataset_module.rb +46 -0
  167. data/lib/sequel/dataset/features.rb +90 -35
  168. data/lib/sequel/dataset/graph.rb +80 -58
  169. data/lib/sequel/dataset/misc.rb +137 -47
  170. data/lib/sequel/dataset/placeholder_literalizer.rb +63 -25
  171. data/lib/sequel/dataset/prepared_statements.rb +188 -85
  172. data/lib/sequel/dataset/query.rb +530 -222
  173. data/lib/sequel/dataset/sql.rb +590 -368
  174. data/lib/sequel/dataset.rb +26 -16
  175. data/lib/sequel/deprecated.rb +12 -2
  176. data/lib/sequel/exceptions.rb +46 -16
  177. data/lib/sequel/extensions/_model_constraint_validations.rb +16 -0
  178. data/lib/sequel/extensions/_model_pg_row.rb +43 -0
  179. data/lib/sequel/extensions/_pretty_table.rb +2 -5
  180. data/lib/sequel/extensions/any_not_empty.rb +45 -0
  181. data/lib/sequel/extensions/arbitrary_servers.rb +10 -10
  182. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  183. data/lib/sequel/extensions/auto_literal_strings.rb +74 -0
  184. data/lib/sequel/extensions/blank.rb +8 -0
  185. data/lib/sequel/extensions/caller_logging.rb +79 -0
  186. data/lib/sequel/extensions/columns_introspection.rb +4 -3
  187. data/lib/sequel/extensions/connection_expiration.rb +20 -10
  188. data/lib/sequel/extensions/connection_validator.rb +11 -10
  189. data/lib/sequel/extensions/constant_sql_override.rb +65 -0
  190. data/lib/sequel/extensions/constraint_validations.rb +62 -39
  191. data/lib/sequel/extensions/core_extensions.rb +42 -48
  192. data/lib/sequel/extensions/core_refinements.rb +80 -59
  193. data/lib/sequel/extensions/current_datetime_timestamp.rb +1 -4
  194. data/lib/sequel/extensions/date_arithmetic.rb +98 -39
  195. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  196. data/lib/sequel/extensions/datetime_parse_to_time.rb +41 -0
  197. data/lib/sequel/extensions/duplicate_columns_handler.rb +21 -14
  198. data/lib/sequel/extensions/empty_array_consider_nulls.rb +2 -2
  199. data/lib/sequel/extensions/escaped_like.rb +100 -0
  200. data/lib/sequel/extensions/eval_inspect.rb +12 -15
  201. data/lib/sequel/extensions/exclude_or_null.rb +68 -0
  202. data/lib/sequel/extensions/fiber_concurrency.rb +24 -0
  203. data/lib/sequel/extensions/freeze_datasets.rb +3 -0
  204. data/lib/sequel/extensions/from_block.rb +1 -34
  205. data/lib/sequel/extensions/graph_each.rb +4 -4
  206. data/lib/sequel/extensions/identifier_mangling.rb +180 -0
  207. data/lib/sequel/extensions/implicit_subquery.rb +48 -0
  208. data/lib/sequel/extensions/index_caching.rb +109 -0
  209. data/lib/sequel/extensions/inflector.rb +13 -5
  210. data/lib/sequel/extensions/integer64.rb +32 -0
  211. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  212. data/lib/sequel/extensions/looser_typecasting.rb +17 -8
  213. data/lib/sequel/extensions/migration.rb +119 -78
  214. data/lib/sequel/extensions/named_timezones.rb +88 -23
  215. data/lib/sequel/extensions/no_auto_literal_strings.rb +2 -82
  216. data/lib/sequel/extensions/null_dataset.rb +8 -8
  217. data/lib/sequel/extensions/pagination.rb +32 -29
  218. data/lib/sequel/extensions/pg_array.rb +221 -287
  219. data/lib/sequel/extensions/pg_array_ops.rb +17 -9
  220. data/lib/sequel/extensions/pg_enum.rb +63 -23
  221. data/lib/sequel/extensions/pg_extended_date_support.rb +241 -0
  222. data/lib/sequel/extensions/pg_hstore.rb +45 -54
  223. data/lib/sequel/extensions/pg_hstore_ops.rb +58 -6
  224. data/lib/sequel/extensions/pg_inet.rb +31 -12
  225. data/lib/sequel/extensions/pg_inet_ops.rb +2 -2
  226. data/lib/sequel/extensions/pg_interval.rb +56 -29
  227. data/lib/sequel/extensions/pg_json.rb +417 -140
  228. data/lib/sequel/extensions/pg_json_ops.rb +270 -18
  229. data/lib/sequel/extensions/pg_loose_count.rb +4 -2
  230. data/lib/sequel/extensions/pg_multirange.rb +372 -0
  231. data/lib/sequel/extensions/pg_range.rb +131 -191
  232. data/lib/sequel/extensions/pg_range_ops.rb +42 -13
  233. data/lib/sequel/extensions/pg_row.rb +48 -81
  234. data/lib/sequel/extensions/pg_row_ops.rb +33 -14
  235. data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -2
  236. data/lib/sequel/extensions/pg_timestamptz.rb +28 -0
  237. data/lib/sequel/extensions/query.rb +9 -7
  238. data/lib/sequel/extensions/round_timestamps.rb +0 -6
  239. data/lib/sequel/extensions/run_transaction_hooks.rb +72 -0
  240. data/lib/sequel/extensions/s.rb +60 -0
  241. data/lib/sequel/extensions/schema_caching.rb +10 -1
  242. data/lib/sequel/extensions/schema_dumper.rb +71 -48
  243. data/lib/sequel/extensions/select_remove.rb +4 -4
  244. data/lib/sequel/extensions/sequel_4_dataset_methods.rb +85 -0
  245. data/lib/sequel/extensions/server_block.rb +51 -27
  246. data/lib/sequel/extensions/split_array_nil.rb +4 -4
  247. data/lib/sequel/extensions/sql_comments.rb +119 -7
  248. data/lib/sequel/extensions/sql_expr.rb +2 -1
  249. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  250. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  251. data/lib/sequel/extensions/string_agg.rb +11 -8
  252. data/lib/sequel/extensions/string_date_time.rb +19 -23
  253. data/lib/sequel/extensions/symbol_aref.rb +55 -0
  254. data/lib/sequel/extensions/symbol_aref_refinement.rb +43 -0
  255. data/lib/sequel/extensions/symbol_as.rb +23 -0
  256. data/lib/sequel/extensions/symbol_as_refinement.rb +37 -0
  257. data/lib/sequel/extensions/synchronize_sql.rb +45 -0
  258. data/lib/sequel/extensions/to_dot.rb +10 -4
  259. data/lib/sequel/extensions/virtual_row_method_block.rb +44 -0
  260. data/lib/sequel/model/associations.rb +1006 -284
  261. data/lib/sequel/model/base.rb +560 -805
  262. data/lib/sequel/model/dataset_module.rb +11 -10
  263. data/lib/sequel/model/default_inflections.rb +1 -1
  264. data/lib/sequel/model/errors.rb +10 -3
  265. data/lib/sequel/model/exceptions.rb +8 -10
  266. data/lib/sequel/model/inflections.rb +7 -20
  267. data/lib/sequel/model/plugins.rb +114 -0
  268. data/lib/sequel/model.rb +32 -82
  269. data/lib/sequel/plugins/active_model.rb +30 -14
  270. data/lib/sequel/plugins/after_initialize.rb +1 -1
  271. data/lib/sequel/plugins/association_dependencies.rb +25 -18
  272. data/lib/sequel/plugins/association_lazy_eager_option.rb +66 -0
  273. data/lib/sequel/plugins/association_multi_add_remove.rb +85 -0
  274. data/lib/sequel/plugins/association_pks.rb +147 -70
  275. data/lib/sequel/plugins/association_proxies.rb +33 -9
  276. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  277. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  278. data/lib/sequel/plugins/auto_validations.rb +95 -28
  279. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  280. data/lib/sequel/plugins/before_after_save.rb +0 -42
  281. data/lib/sequel/plugins/blacklist_security.rb +21 -12
  282. data/lib/sequel/plugins/boolean_readers.rb +5 -5
  283. data/lib/sequel/plugins/boolean_subsets.rb +13 -8
  284. data/lib/sequel/plugins/caching.rb +25 -16
  285. data/lib/sequel/plugins/class_table_inheritance.rb +179 -100
  286. data/lib/sequel/plugins/column_conflicts.rb +16 -3
  287. data/lib/sequel/plugins/column_encryption.rb +728 -0
  288. data/lib/sequel/plugins/column_select.rb +7 -5
  289. data/lib/sequel/plugins/columns_updated.rb +42 -0
  290. data/lib/sequel/plugins/composition.rb +42 -26
  291. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  292. data/lib/sequel/plugins/constraint_validations.rb +20 -14
  293. data/lib/sequel/plugins/csv_serializer.rb +56 -35
  294. data/lib/sequel/plugins/dataset_associations.rb +40 -17
  295. data/lib/sequel/plugins/def_dataset_method.rb +90 -0
  296. data/lib/sequel/plugins/defaults_setter.rb +65 -10
  297. data/lib/sequel/plugins/delay_add_association.rb +1 -1
  298. data/lib/sequel/plugins/dirty.rb +62 -24
  299. data/lib/sequel/plugins/eager_each.rb +3 -3
  300. data/lib/sequel/plugins/eager_graph_eager.rb +139 -0
  301. data/lib/sequel/plugins/empty_failure_backtraces.rb +38 -0
  302. data/lib/sequel/plugins/enum.rb +124 -0
  303. data/lib/sequel/plugins/error_splitter.rb +17 -12
  304. data/lib/sequel/plugins/finder.rb +246 -0
  305. data/lib/sequel/plugins/forbid_lazy_load.rb +216 -0
  306. data/lib/sequel/plugins/force_encoding.rb +7 -12
  307. data/lib/sequel/plugins/hook_class_methods.rb +37 -54
  308. data/lib/sequel/plugins/input_transformer.rb +18 -10
  309. data/lib/sequel/plugins/insert_conflict.rb +76 -0
  310. data/lib/sequel/plugins/insert_returning_select.rb +2 -2
  311. data/lib/sequel/plugins/instance_filters.rb +10 -8
  312. data/lib/sequel/plugins/instance_hooks.rb +34 -17
  313. data/lib/sequel/plugins/instance_specific_default.rb +113 -0
  314. data/lib/sequel/plugins/inverted_subsets.rb +22 -13
  315. data/lib/sequel/plugins/json_serializer.rb +124 -64
  316. data/lib/sequel/plugins/lazy_attributes.rb +21 -14
  317. data/lib/sequel/plugins/list.rb +35 -21
  318. data/lib/sequel/plugins/many_through_many.rb +134 -21
  319. data/lib/sequel/plugins/modification_detection.rb +15 -5
  320. data/lib/sequel/plugins/mssql_optimistic_locking.rb +6 -5
  321. data/lib/sequel/plugins/nested_attributes.rb +61 -31
  322. data/lib/sequel/plugins/optimistic_locking.rb +3 -3
  323. data/lib/sequel/plugins/pg_array_associations.rb +103 -53
  324. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +350 -0
  325. data/lib/sequel/plugins/pg_row.rb +5 -51
  326. data/lib/sequel/plugins/prepared_statements.rb +60 -72
  327. data/lib/sequel/plugins/prepared_statements_safe.rb +9 -4
  328. data/lib/sequel/plugins/rcte_tree.rb +68 -82
  329. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  330. data/lib/sequel/plugins/serialization.rb +43 -46
  331. data/lib/sequel/plugins/serialization_modification_detection.rb +3 -2
  332. data/lib/sequel/plugins/sharding.rb +15 -10
  333. data/lib/sequel/plugins/single_table_inheritance.rb +67 -28
  334. data/lib/sequel/plugins/skip_create_refresh.rb +3 -3
  335. data/lib/sequel/plugins/skip_saving_columns.rb +108 -0
  336. data/lib/sequel/plugins/split_values.rb +11 -6
  337. data/lib/sequel/plugins/sql_comments.rb +189 -0
  338. data/lib/sequel/plugins/static_cache.rb +77 -53
  339. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  340. data/lib/sequel/plugins/string_stripper.rb +3 -3
  341. data/lib/sequel/plugins/subclasses.rb +43 -10
  342. data/lib/sequel/plugins/subset_conditions.rb +15 -5
  343. data/lib/sequel/plugins/table_select.rb +2 -2
  344. data/lib/sequel/plugins/tactical_eager_loading.rb +96 -12
  345. data/lib/sequel/plugins/throw_failures.rb +110 -0
  346. data/lib/sequel/plugins/timestamps.rb +20 -8
  347. data/lib/sequel/plugins/touch.rb +19 -8
  348. data/lib/sequel/plugins/tree.rb +62 -32
  349. data/lib/sequel/plugins/typecast_on_load.rb +12 -4
  350. data/lib/sequel/plugins/unlimited_update.rb +1 -7
  351. data/lib/sequel/plugins/unused_associations.rb +521 -0
  352. data/lib/sequel/plugins/update_or_create.rb +4 -4
  353. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  354. data/lib/sequel/plugins/update_refresh.rb +26 -15
  355. data/lib/sequel/plugins/uuid.rb +7 -11
  356. data/lib/sequel/plugins/validate_associated.rb +18 -0
  357. data/lib/sequel/plugins/validation_class_methods.rb +38 -19
  358. data/lib/sequel/plugins/validation_contexts.rb +49 -0
  359. data/lib/sequel/plugins/validation_helpers.rb +57 -41
  360. data/lib/sequel/plugins/whitelist_security.rb +122 -0
  361. data/lib/sequel/plugins/xml_serializer.rb +30 -31
  362. data/lib/sequel/sql.rb +471 -331
  363. data/lib/sequel/timezones.rb +78 -47
  364. data/lib/sequel/version.rb +7 -2
  365. data/lib/sequel.rb +1 -1
  366. metadata +217 -521
  367. data/Rakefile +0 -164
  368. data/doc/active_record.rdoc +0 -928
  369. data/doc/release_notes/1.0.txt +0 -38
  370. data/doc/release_notes/1.1.txt +0 -143
  371. data/doc/release_notes/1.3.txt +0 -101
  372. data/doc/release_notes/1.4.0.txt +0 -53
  373. data/doc/release_notes/1.5.0.txt +0 -155
  374. data/doc/release_notes/2.0.0.txt +0 -298
  375. data/doc/release_notes/2.1.0.txt +0 -271
  376. data/doc/release_notes/2.10.0.txt +0 -328
  377. data/doc/release_notes/2.11.0.txt +0 -215
  378. data/doc/release_notes/2.12.0.txt +0 -534
  379. data/doc/release_notes/2.2.0.txt +0 -253
  380. data/doc/release_notes/2.3.0.txt +0 -88
  381. data/doc/release_notes/2.4.0.txt +0 -106
  382. data/doc/release_notes/2.5.0.txt +0 -137
  383. data/doc/release_notes/2.6.0.txt +0 -157
  384. data/doc/release_notes/2.7.0.txt +0 -166
  385. data/doc/release_notes/2.8.0.txt +0 -171
  386. data/doc/release_notes/2.9.0.txt +0 -97
  387. data/doc/release_notes/3.0.0.txt +0 -221
  388. data/doc/release_notes/3.1.0.txt +0 -406
  389. data/doc/release_notes/3.10.0.txt +0 -286
  390. data/doc/release_notes/3.11.0.txt +0 -254
  391. data/doc/release_notes/3.12.0.txt +0 -304
  392. data/doc/release_notes/3.13.0.txt +0 -210
  393. data/doc/release_notes/3.14.0.txt +0 -118
  394. data/doc/release_notes/3.15.0.txt +0 -78
  395. data/doc/release_notes/3.16.0.txt +0 -45
  396. data/doc/release_notes/3.17.0.txt +0 -58
  397. data/doc/release_notes/3.18.0.txt +0 -120
  398. data/doc/release_notes/3.19.0.txt +0 -67
  399. data/doc/release_notes/3.2.0.txt +0 -268
  400. data/doc/release_notes/3.20.0.txt +0 -41
  401. data/doc/release_notes/3.21.0.txt +0 -87
  402. data/doc/release_notes/3.22.0.txt +0 -39
  403. data/doc/release_notes/3.23.0.txt +0 -172
  404. data/doc/release_notes/3.24.0.txt +0 -420
  405. data/doc/release_notes/3.25.0.txt +0 -88
  406. data/doc/release_notes/3.26.0.txt +0 -88
  407. data/doc/release_notes/3.27.0.txt +0 -82
  408. data/doc/release_notes/3.28.0.txt +0 -304
  409. data/doc/release_notes/3.29.0.txt +0 -459
  410. data/doc/release_notes/3.3.0.txt +0 -192
  411. data/doc/release_notes/3.30.0.txt +0 -135
  412. data/doc/release_notes/3.31.0.txt +0 -146
  413. data/doc/release_notes/3.32.0.txt +0 -202
  414. data/doc/release_notes/3.33.0.txt +0 -157
  415. data/doc/release_notes/3.34.0.txt +0 -671
  416. data/doc/release_notes/3.35.0.txt +0 -144
  417. data/doc/release_notes/3.36.0.txt +0 -245
  418. data/doc/release_notes/3.37.0.txt +0 -338
  419. data/doc/release_notes/3.38.0.txt +0 -234
  420. data/doc/release_notes/3.39.0.txt +0 -237
  421. data/doc/release_notes/3.4.0.txt +0 -325
  422. data/doc/release_notes/3.40.0.txt +0 -73
  423. data/doc/release_notes/3.41.0.txt +0 -155
  424. data/doc/release_notes/3.42.0.txt +0 -74
  425. data/doc/release_notes/3.43.0.txt +0 -105
  426. data/doc/release_notes/3.44.0.txt +0 -152
  427. data/doc/release_notes/3.45.0.txt +0 -179
  428. data/doc/release_notes/3.46.0.txt +0 -122
  429. data/doc/release_notes/3.47.0.txt +0 -270
  430. data/doc/release_notes/3.48.0.txt +0 -477
  431. data/doc/release_notes/3.5.0.txt +0 -510
  432. data/doc/release_notes/3.6.0.txt +0 -366
  433. data/doc/release_notes/3.7.0.txt +0 -179
  434. data/doc/release_notes/3.8.0.txt +0 -151
  435. data/doc/release_notes/3.9.0.txt +0 -233
  436. data/doc/release_notes/4.0.0.txt +0 -262
  437. data/doc/release_notes/4.1.0.txt +0 -85
  438. data/doc/release_notes/4.10.0.txt +0 -226
  439. data/doc/release_notes/4.11.0.txt +0 -147
  440. data/doc/release_notes/4.12.0.txt +0 -105
  441. data/doc/release_notes/4.13.0.txt +0 -169
  442. data/doc/release_notes/4.14.0.txt +0 -68
  443. data/doc/release_notes/4.15.0.txt +0 -56
  444. data/doc/release_notes/4.16.0.txt +0 -36
  445. data/doc/release_notes/4.17.0.txt +0 -38
  446. data/doc/release_notes/4.18.0.txt +0 -36
  447. data/doc/release_notes/4.19.0.txt +0 -45
  448. data/doc/release_notes/4.2.0.txt +0 -129
  449. data/doc/release_notes/4.20.0.txt +0 -79
  450. data/doc/release_notes/4.21.0.txt +0 -94
  451. data/doc/release_notes/4.22.0.txt +0 -72
  452. data/doc/release_notes/4.23.0.txt +0 -65
  453. data/doc/release_notes/4.24.0.txt +0 -99
  454. data/doc/release_notes/4.25.0.txt +0 -181
  455. data/doc/release_notes/4.26.0.txt +0 -44
  456. data/doc/release_notes/4.27.0.txt +0 -78
  457. data/doc/release_notes/4.28.0.txt +0 -57
  458. data/doc/release_notes/4.29.0.txt +0 -41
  459. data/doc/release_notes/4.3.0.txt +0 -40
  460. data/doc/release_notes/4.30.0.txt +0 -37
  461. data/doc/release_notes/4.31.0.txt +0 -57
  462. data/doc/release_notes/4.32.0.txt +0 -132
  463. data/doc/release_notes/4.33.0.txt +0 -88
  464. data/doc/release_notes/4.34.0.txt +0 -86
  465. data/doc/release_notes/4.35.0.txt +0 -130
  466. data/doc/release_notes/4.36.0.txt +0 -116
  467. data/doc/release_notes/4.4.0.txt +0 -92
  468. data/doc/release_notes/4.5.0.txt +0 -34
  469. data/doc/release_notes/4.6.0.txt +0 -30
  470. data/doc/release_notes/4.7.0.txt +0 -103
  471. data/doc/release_notes/4.8.0.txt +0 -175
  472. data/doc/release_notes/4.9.0.txt +0 -190
  473. data/lib/sequel/adapters/cubrid.rb +0 -144
  474. data/lib/sequel/adapters/do/mysql.rb +0 -66
  475. data/lib/sequel/adapters/do/postgres.rb +0 -44
  476. data/lib/sequel/adapters/do/sqlite3.rb +0 -42
  477. data/lib/sequel/adapters/do.rb +0 -158
  478. data/lib/sequel/adapters/jdbc/as400.rb +0 -84
  479. data/lib/sequel/adapters/jdbc/cubrid.rb +0 -64
  480. data/lib/sequel/adapters/jdbc/firebirdsql.rb +0 -36
  481. data/lib/sequel/adapters/jdbc/informix-sqli.rb +0 -33
  482. data/lib/sequel/adapters/jdbc/jdbcprogress.rb +0 -33
  483. data/lib/sequel/adapters/odbc/progress.rb +0 -10
  484. data/lib/sequel/adapters/shared/cubrid.rb +0 -245
  485. data/lib/sequel/adapters/shared/firebird.rb +0 -247
  486. data/lib/sequel/adapters/shared/informix.rb +0 -54
  487. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +0 -152
  488. data/lib/sequel/adapters/shared/progress.rb +0 -40
  489. data/lib/sequel/adapters/swift/mysql.rb +0 -49
  490. data/lib/sequel/adapters/swift/postgres.rb +0 -47
  491. data/lib/sequel/adapters/swift/sqlite.rb +0 -49
  492. data/lib/sequel/adapters/swift.rb +0 -160
  493. data/lib/sequel/adapters/utils/pg_types.rb +0 -70
  494. data/lib/sequel/dataset/mutation.rb +0 -111
  495. data/lib/sequel/extensions/empty_array_ignore_nulls.rb +0 -5
  496. data/lib/sequel/extensions/filter_having.rb +0 -63
  497. data/lib/sequel/extensions/hash_aliases.rb +0 -49
  498. data/lib/sequel/extensions/meta_def.rb +0 -35
  499. data/lib/sequel/extensions/query_literals.rb +0 -84
  500. data/lib/sequel/extensions/ruby18_symbol_extensions.rb +0 -24
  501. data/lib/sequel/extensions/sequel_3_dataset_methods.rb +0 -122
  502. data/lib/sequel/extensions/set_overrides.rb +0 -76
  503. data/lib/sequel/no_core_ext.rb +0 -3
  504. data/lib/sequel/plugins/association_autoreloading.rb +0 -9
  505. data/lib/sequel/plugins/identifier_columns.rb +0 -47
  506. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +0 -9
  507. data/lib/sequel/plugins/pg_typecast_on_load.rb +0 -81
  508. data/lib/sequel/plugins/prepared_statements_associations.rb +0 -119
  509. data/lib/sequel/plugins/prepared_statements_with_pk.rb +0 -61
  510. data/lib/sequel/plugins/schema.rb +0 -82
  511. data/lib/sequel/plugins/scissors.rb +0 -35
  512. data/spec/adapter_spec.rb +0 -4
  513. data/spec/adapters/db2_spec.rb +0 -160
  514. data/spec/adapters/firebird_spec.rb +0 -411
  515. data/spec/adapters/informix_spec.rb +0 -100
  516. data/spec/adapters/mssql_spec.rb +0 -733
  517. data/spec/adapters/mysql_spec.rb +0 -1319
  518. data/spec/adapters/oracle_spec.rb +0 -313
  519. data/spec/adapters/postgres_spec.rb +0 -3790
  520. data/spec/adapters/spec_helper.rb +0 -49
  521. data/spec/adapters/sqlanywhere_spec.rb +0 -170
  522. data/spec/adapters/sqlite_spec.rb +0 -688
  523. data/spec/bin_spec.rb +0 -258
  524. data/spec/core/connection_pool_spec.rb +0 -1045
  525. data/spec/core/database_spec.rb +0 -2636
  526. data/spec/core/dataset_spec.rb +0 -5175
  527. data/spec/core/deprecated_spec.rb +0 -70
  528. data/spec/core/expression_filters_spec.rb +0 -1247
  529. data/spec/core/mock_adapter_spec.rb +0 -464
  530. data/spec/core/object_graph_spec.rb +0 -303
  531. data/spec/core/placeholder_literalizer_spec.rb +0 -163
  532. data/spec/core/schema_generator_spec.rb +0 -203
  533. data/spec/core/schema_spec.rb +0 -1676
  534. data/spec/core/spec_helper.rb +0 -34
  535. data/spec/core/version_spec.rb +0 -7
  536. data/spec/core_extensions_spec.rb +0 -699
  537. data/spec/core_model_spec.rb +0 -2
  538. data/spec/core_spec.rb +0 -1
  539. data/spec/extensions/accessed_columns_spec.rb +0 -51
  540. data/spec/extensions/active_model_spec.rb +0 -85
  541. data/spec/extensions/after_initialize_spec.rb +0 -24
  542. data/spec/extensions/arbitrary_servers_spec.rb +0 -109
  543. data/spec/extensions/association_dependencies_spec.rb +0 -117
  544. data/spec/extensions/association_pks_spec.rb +0 -405
  545. data/spec/extensions/association_proxies_spec.rb +0 -86
  546. data/spec/extensions/auto_validations_spec.rb +0 -192
  547. data/spec/extensions/before_after_save_spec.rb +0 -40
  548. data/spec/extensions/blacklist_security_spec.rb +0 -88
  549. data/spec/extensions/blank_spec.rb +0 -69
  550. data/spec/extensions/boolean_readers_spec.rb +0 -93
  551. data/spec/extensions/boolean_subsets_spec.rb +0 -47
  552. data/spec/extensions/caching_spec.rb +0 -270
  553. data/spec/extensions/class_table_inheritance_spec.rb +0 -444
  554. data/spec/extensions/column_conflicts_spec.rb +0 -60
  555. data/spec/extensions/column_select_spec.rb +0 -108
  556. data/spec/extensions/columns_introspection_spec.rb +0 -91
  557. data/spec/extensions/composition_spec.rb +0 -242
  558. data/spec/extensions/connection_expiration_spec.rb +0 -121
  559. data/spec/extensions/connection_validator_spec.rb +0 -127
  560. data/spec/extensions/constraint_validations_plugin_spec.rb +0 -288
  561. data/spec/extensions/constraint_validations_spec.rb +0 -389
  562. data/spec/extensions/core_refinements_spec.rb +0 -519
  563. data/spec/extensions/csv_serializer_spec.rb +0 -180
  564. data/spec/extensions/current_datetime_timestamp_spec.rb +0 -27
  565. data/spec/extensions/dataset_associations_spec.rb +0 -343
  566. data/spec/extensions/dataset_source_alias_spec.rb +0 -51
  567. data/spec/extensions/date_arithmetic_spec.rb +0 -167
  568. data/spec/extensions/defaults_setter_spec.rb +0 -102
  569. data/spec/extensions/delay_add_association_spec.rb +0 -74
  570. data/spec/extensions/dirty_spec.rb +0 -180
  571. data/spec/extensions/duplicate_columns_handler_spec.rb +0 -110
  572. data/spec/extensions/eager_each_spec.rb +0 -66
  573. data/spec/extensions/empty_array_consider_nulls_spec.rb +0 -24
  574. data/spec/extensions/error_splitter_spec.rb +0 -18
  575. data/spec/extensions/error_sql_spec.rb +0 -20
  576. data/spec/extensions/eval_inspect_spec.rb +0 -73
  577. data/spec/extensions/filter_having_spec.rb +0 -40
  578. data/spec/extensions/force_encoding_spec.rb +0 -114
  579. data/spec/extensions/from_block_spec.rb +0 -21
  580. data/spec/extensions/graph_each_spec.rb +0 -119
  581. data/spec/extensions/hash_aliases_spec.rb +0 -24
  582. data/spec/extensions/hook_class_methods_spec.rb +0 -429
  583. data/spec/extensions/identifier_columns_spec.rb +0 -17
  584. data/spec/extensions/inflector_spec.rb +0 -183
  585. data/spec/extensions/input_transformer_spec.rb +0 -54
  586. data/spec/extensions/insert_returning_select_spec.rb +0 -46
  587. data/spec/extensions/instance_filters_spec.rb +0 -79
  588. data/spec/extensions/instance_hooks_spec.rb +0 -276
  589. data/spec/extensions/inverted_subsets_spec.rb +0 -33
  590. data/spec/extensions/json_serializer_spec.rb +0 -304
  591. data/spec/extensions/lazy_attributes_spec.rb +0 -170
  592. data/spec/extensions/list_spec.rb +0 -278
  593. data/spec/extensions/looser_typecasting_spec.rb +0 -43
  594. data/spec/extensions/many_through_many_spec.rb +0 -2172
  595. data/spec/extensions/meta_def_spec.rb +0 -21
  596. data/spec/extensions/migration_spec.rb +0 -728
  597. data/spec/extensions/modification_detection_spec.rb +0 -80
  598. data/spec/extensions/mssql_optimistic_locking_spec.rb +0 -91
  599. data/spec/extensions/named_timezones_spec.rb +0 -108
  600. data/spec/extensions/nested_attributes_spec.rb +0 -697
  601. data/spec/extensions/no_auto_literal_strings_spec.rb +0 -65
  602. data/spec/extensions/null_dataset_spec.rb +0 -85
  603. data/spec/extensions/optimistic_locking_spec.rb +0 -128
  604. data/spec/extensions/pagination_spec.rb +0 -118
  605. data/spec/extensions/pg_array_associations_spec.rb +0 -736
  606. data/spec/extensions/pg_array_ops_spec.rb +0 -143
  607. data/spec/extensions/pg_array_spec.rb +0 -390
  608. data/spec/extensions/pg_enum_spec.rb +0 -92
  609. data/spec/extensions/pg_hstore_ops_spec.rb +0 -236
  610. data/spec/extensions/pg_hstore_spec.rb +0 -206
  611. data/spec/extensions/pg_inet_ops_spec.rb +0 -101
  612. data/spec/extensions/pg_inet_spec.rb +0 -52
  613. data/spec/extensions/pg_interval_spec.rb +0 -76
  614. data/spec/extensions/pg_json_ops_spec.rb +0 -275
  615. data/spec/extensions/pg_json_spec.rb +0 -218
  616. data/spec/extensions/pg_loose_count_spec.rb +0 -17
  617. data/spec/extensions/pg_range_ops_spec.rb +0 -58
  618. data/spec/extensions/pg_range_spec.rb +0 -473
  619. data/spec/extensions/pg_row_ops_spec.rb +0 -60
  620. data/spec/extensions/pg_row_plugin_spec.rb +0 -62
  621. data/spec/extensions/pg_row_spec.rb +0 -360
  622. data/spec/extensions/pg_static_cache_updater_spec.rb +0 -92
  623. data/spec/extensions/pg_typecast_on_load_spec.rb +0 -63
  624. data/spec/extensions/prepared_statements_associations_spec.rb +0 -159
  625. data/spec/extensions/prepared_statements_safe_spec.rb +0 -61
  626. data/spec/extensions/prepared_statements_spec.rb +0 -103
  627. data/spec/extensions/prepared_statements_with_pk_spec.rb +0 -31
  628. data/spec/extensions/pretty_table_spec.rb +0 -92
  629. data/spec/extensions/query_literals_spec.rb +0 -183
  630. data/spec/extensions/query_spec.rb +0 -102
  631. data/spec/extensions/rcte_tree_spec.rb +0 -392
  632. data/spec/extensions/round_timestamps_spec.rb +0 -43
  633. data/spec/extensions/schema_caching_spec.rb +0 -41
  634. data/spec/extensions/schema_dumper_spec.rb +0 -814
  635. data/spec/extensions/schema_spec.rb +0 -117
  636. data/spec/extensions/scissors_spec.rb +0 -26
  637. data/spec/extensions/select_remove_spec.rb +0 -38
  638. data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -101
  639. data/spec/extensions/serialization_modification_detection_spec.rb +0 -98
  640. data/spec/extensions/serialization_spec.rb +0 -362
  641. data/spec/extensions/server_block_spec.rb +0 -90
  642. data/spec/extensions/server_logging_spec.rb +0 -45
  643. data/spec/extensions/set_overrides_spec.rb +0 -61
  644. data/spec/extensions/sharding_spec.rb +0 -198
  645. data/spec/extensions/shared_caching_spec.rb +0 -175
  646. data/spec/extensions/single_table_inheritance_spec.rb +0 -297
  647. data/spec/extensions/singular_table_names_spec.rb +0 -22
  648. data/spec/extensions/skip_create_refresh_spec.rb +0 -17
  649. data/spec/extensions/spec_helper.rb +0 -71
  650. data/spec/extensions/split_array_nil_spec.rb +0 -24
  651. data/spec/extensions/split_values_spec.rb +0 -22
  652. data/spec/extensions/sql_comments_spec.rb +0 -27
  653. data/spec/extensions/sql_expr_spec.rb +0 -60
  654. data/spec/extensions/static_cache_spec.rb +0 -361
  655. data/spec/extensions/string_agg_spec.rb +0 -85
  656. data/spec/extensions/string_date_time_spec.rb +0 -95
  657. data/spec/extensions/string_stripper_spec.rb +0 -68
  658. data/spec/extensions/subclasses_spec.rb +0 -66
  659. data/spec/extensions/subset_conditions_spec.rb +0 -38
  660. data/spec/extensions/table_select_spec.rb +0 -71
  661. data/spec/extensions/tactical_eager_loading_spec.rb +0 -136
  662. data/spec/extensions/thread_local_timezones_spec.rb +0 -67
  663. data/spec/extensions/timestamps_spec.rb +0 -175
  664. data/spec/extensions/to_dot_spec.rb +0 -154
  665. data/spec/extensions/touch_spec.rb +0 -203
  666. data/spec/extensions/tree_spec.rb +0 -274
  667. data/spec/extensions/typecast_on_load_spec.rb +0 -80
  668. data/spec/extensions/unlimited_update_spec.rb +0 -20
  669. data/spec/extensions/update_or_create_spec.rb +0 -87
  670. data/spec/extensions/update_primary_key_spec.rb +0 -100
  671. data/spec/extensions/update_refresh_spec.rb +0 -53
  672. data/spec/extensions/uuid_spec.rb +0 -106
  673. data/spec/extensions/validate_associated_spec.rb +0 -52
  674. data/spec/extensions/validation_class_methods_spec.rb +0 -1027
  675. data/spec/extensions/validation_helpers_spec.rb +0 -554
  676. data/spec/extensions/xml_serializer_spec.rb +0 -207
  677. data/spec/files/bad_down_migration/001_create_alt_basic.rb +0 -4
  678. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +0 -4
  679. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  680. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +0 -9
  681. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +0 -3
  682. data/spec/files/bad_up_migration/001_create_alt_basic.rb +0 -4
  683. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +0 -3
  684. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +0 -9
  685. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +0 -9
  686. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +0 -4
  687. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +0 -9
  688. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +0 -9
  689. data/spec/files/double_migration/001_create_sessions.rb +0 -9
  690. data/spec/files/double_migration/002_create_nodes.rb +0 -19
  691. data/spec/files/double_migration/003_3_create_users.rb +0 -4
  692. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +0 -4
  693. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +0 -4
  694. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  695. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +0 -9
  696. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +0 -4
  697. data/spec/files/empty_migration/001_create_sessions.rb +0 -9
  698. data/spec/files/empty_migration/002_create_nodes.rb +0 -0
  699. data/spec/files/empty_migration/003_3_create_users.rb +0 -4
  700. data/spec/files/integer_migrations/001_create_sessions.rb +0 -9
  701. data/spec/files/integer_migrations/002_create_nodes.rb +0 -9
  702. data/spec/files/integer_migrations/003_3_create_users.rb +0 -4
  703. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  704. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +0 -9
  705. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +0 -9
  706. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +0 -9
  707. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +0 -4
  708. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +0 -4
  709. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +0 -4
  710. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +0 -9
  711. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +0 -4
  712. data/spec/files/reversible_migrations/001_reversible.rb +0 -5
  713. data/spec/files/reversible_migrations/002_reversible.rb +0 -5
  714. data/spec/files/reversible_migrations/003_reversible.rb +0 -5
  715. data/spec/files/reversible_migrations/004_reversible.rb +0 -5
  716. data/spec/files/reversible_migrations/005_reversible.rb +0 -10
  717. data/spec/files/reversible_migrations/006_reversible.rb +0 -10
  718. data/spec/files/reversible_migrations/007_reversible.rb +0 -10
  719. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +0 -9
  720. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +0 -9
  721. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +0 -4
  722. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +0 -4
  723. data/spec/files/transaction_specified_migrations/002_create_basic.rb +0 -4
  724. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +0 -3
  725. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +0 -3
  726. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +0 -9
  727. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +0 -9
  728. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +0 -4
  729. data/spec/guards_helper.rb +0 -55
  730. data/spec/integration/associations_test.rb +0 -2506
  731. data/spec/integration/database_test.rb +0 -113
  732. data/spec/integration/dataset_test.rb +0 -1858
  733. data/spec/integration/eager_loader_test.rb +0 -687
  734. data/spec/integration/migrator_test.rb +0 -262
  735. data/spec/integration/model_test.rb +0 -230
  736. data/spec/integration/plugin_test.rb +0 -2297
  737. data/spec/integration/prepared_statement_test.rb +0 -467
  738. data/spec/integration/schema_test.rb +0 -815
  739. data/spec/integration/spec_helper.rb +0 -56
  740. data/spec/integration/timezone_test.rb +0 -86
  741. data/spec/integration/transaction_test.rb +0 -406
  742. data/spec/integration/type_test.rb +0 -133
  743. data/spec/model/association_reflection_spec.rb +0 -565
  744. data/spec/model/associations_spec.rb +0 -4589
  745. data/spec/model/base_spec.rb +0 -759
  746. data/spec/model/class_dataset_methods_spec.rb +0 -150
  747. data/spec/model/dataset_methods_spec.rb +0 -149
  748. data/spec/model/eager_loading_spec.rb +0 -2197
  749. data/spec/model/hooks_spec.rb +0 -604
  750. data/spec/model/inflector_spec.rb +0 -26
  751. data/spec/model/model_spec.rb +0 -1097
  752. data/spec/model/plugins_spec.rb +0 -299
  753. data/spec/model/record_spec.rb +0 -2162
  754. data/spec/model/spec_helper.rb +0 -46
  755. data/spec/model/validations_spec.rb +0 -193
  756. data/spec/model_no_assoc_spec.rb +0 -1
  757. data/spec/model_spec.rb +0 -1
  758. data/spec/plugin_spec.rb +0 -1
  759. data/spec/sequel_coverage.rb +0 -15
  760. data/spec/spec_config.rb +0 -10
@@ -0,0 +1,728 @@
1
+ # frozen-string-literal: true
2
+
3
+ # :nocov:
4
+ raise(Sequel::Error, "Sequel column_encryption plugin requires ruby 2.3 or greater") unless RUBY_VERSION >= '2.3'
5
+ # :nocov:
6
+
7
+ require 'openssl'
8
+
9
+ begin
10
+ # Test cipher actually works
11
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
12
+ cipher.encrypt
13
+ cipher.key = '1'*32
14
+ cipher_iv = cipher.random_iv
15
+ cipher.auth_data = ''
16
+ cipher_text = cipher.update('2') << cipher.final
17
+ auth_tag = cipher.auth_tag
18
+
19
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
20
+ cipher.decrypt
21
+ cipher.iv = cipher_iv
22
+ cipher.key = '1'*32
23
+ cipher.auth_data = ''
24
+ cipher.auth_tag = auth_tag
25
+ # :nocov:
26
+ unless (cipher.update(cipher_text) << cipher.final) == '2'
27
+ raise OpenSSL::Cipher::CipherError
28
+ end
29
+ rescue RuntimeError, OpenSSL::Cipher::CipherError
30
+ raise LoadError, "Sequel column_encryption plugin requires a working aes-256-gcm cipher"
31
+ # :nocov:
32
+ end
33
+
34
+ require 'base64'
35
+ require 'securerandom'
36
+
37
+ module Sequel
38
+ module Plugins
39
+ # The column_encryption plugin adds support for encrypting the content of individual
40
+ # columns in a table.
41
+ #
42
+ # Column values are encrypted with AES-256-GCM using a per-value cipher key derived from
43
+ # a key provided in the configuration using HMAC-SHA256.
44
+ #
45
+ # = Usage
46
+ #
47
+ # If you would like to support encryption of columns in more than one model, you should
48
+ # probably load the plugin into the parent class of your models and specify the keys:
49
+ #
50
+ # Sequel::Model.plugin :column_encryption do |enc|
51
+ # enc.key 0, ENV["SEQUEL_COLUMN_ENCRYPTION_KEY"]
52
+ # end
53
+ #
54
+ # This specifies a single master encryption key. Unless you are actively rotating keys,
55
+ # it is best to use a single master key. Rotation of encryption keys will be discussed
56
+ # in a later section.
57
+ #
58
+ # In the above call, <tt>0</tt> is the id of the key, and the
59
+ # <tt>ENV["SEQUEL_COLUMN_ENCRYPTION_KEY"]</tt> is the content of the key, which must be
60
+ # a string with exactly 32 bytes. As indicated, this key should not be hardcoded or
61
+ # otherwise committed to the source control repository.
62
+ #
63
+ # For models that need encrypted columns, you load the plugin again, but specify the
64
+ # columns to encrypt:
65
+ #
66
+ # ConfidentialModel.plugin :column_encryption do |enc|
67
+ # enc.column :encrypted_column_name
68
+ # enc.column :searchable_column_name, searchable: true
69
+ # enc.column :ci_searchable_column_name, searchable: :case_insensitive
70
+ # end
71
+ #
72
+ # With this, all three specified columns (+encrypted_column_name+, +searchable_column_name+,
73
+ # and +ci_searchable_column_name+) will be marked as encrypted columns. When you run the
74
+ # following code:
75
+ #
76
+ # ConfidentialModel.create(
77
+ # encrypted_column_name: 'These',
78
+ # searchable_column_name: 'will be',
79
+ # ci_searchable_column_name: 'Encrypted'
80
+ # )
81
+ #
82
+ # It will save encrypted versions to the database. +encrypted_column_name+ will not be
83
+ # searchable, +searchable_column_name+ will be searchable with an exact match, and
84
+ # +ci_searchable_column_name+ will be searchable with a case insensitive match. See section
85
+ # below for details on searching.
86
+ #
87
+ # It is possible to have model-specific keys by specifying both the +key+ and +column+ methods
88
+ # in the model:
89
+ #
90
+ # ConfidentialModel.plugin :column_encryption do |enc|
91
+ # enc.key 0, ENV["SEQUEL_MODEL_SPECIFIC_ENCRYPTION_KEY"]
92
+ #
93
+ # enc.column :encrypted_column_name
94
+ # enc.column :searchable_column_name, searchable: true
95
+ # enc.column :ci_searchable_column_name, searchable: :case_insensitive
96
+ # end
97
+ #
98
+ # When the +key+ method is called inside the plugin block, previous keys are ignored,
99
+ # and only the new keys specified will be used. This approach would allow the
100
+ # +ConfidentialModel+ to use the model specific encryption keys, and other models
101
+ # to use the default keys specified in the parent class.
102
+ #
103
+ # The +key+ and +column+ methods inside the plugin block support additional options.
104
+ # The +key+ method supports the following options:
105
+ #
106
+ # :auth_data :: The authentication data to use for the AES-256-GCM cipher. Defaults
107
+ # to the empty string.
108
+ # :padding :: The number of padding bytes to use. For security, data is padded so that
109
+ # a database administrator cannot determine the exact size of the
110
+ # unencrypted data. By default, this value is 8, which means that
111
+ # unencrypted data will be padded to a multiple of 8 bytes. Up to twice as
112
+ # much padding as specified will be used, as the number of padding bytes
113
+ # is partially randomized.
114
+ #
115
+ # The +column+ method supports the following options:
116
+ #
117
+ # :searchable :: Whether the column is searchable. This should not be used unless
118
+ # searchability is needed, as it can allow the database administrator
119
+ # to determine whether two distinct rows have the same unencrypted
120
+ # data (but not what that data is). This can be set to +true+ to allow
121
+ # searching with an exact match, or +:case_insensitive+ for a case
122
+ # insensitive match.
123
+ # :search_both :: This should only be used if you have previously switched the
124
+ # +:searchable+ option from +true+ to +:case_insensitive+ or vice-versa,
125
+ # and would like the search to return values that have not yet been
126
+ # reencrypted. Note that switching from +true+ to +:case_insensitive+
127
+ # isn't a problem, but switching from +:case_insensitive+ to +true+ and
128
+ # using this option can cause the search to return values that are
129
+ # not an exact match. You should manually filter those objects
130
+ # after decrypting if you want to ensure an exact match.
131
+ # :format :: The format of the column, if you want to perform serialization before
132
+ # encryption and deserialization after decryption. Can be either a
133
+ # symbol registered with the serialization plugin or an array of two
134
+ # callables, the first for serialization and the second for deserialization.
135
+ #
136
+ # The +column+ method also supports a block for column-specific keys:
137
+ #
138
+ # ConfidentialModel.plugin :column_encryption do |enc|
139
+ # enc.column :encrypted_column_name do |cenc|
140
+ # cenc.key 0, ENV["SEQUEL_COLUMN_SPECIFIC_ENCRYPTION_KEY"]
141
+ # end
142
+ #
143
+ # enc.column :searchable_column_name, searchable: true
144
+ # enc.column :ci_searchable_column_name, searchable: :case_insensitive
145
+ # end
146
+ #
147
+ # In this case, the <tt>ENV["SEQUEL_COLUMN_SPECIFIC_ENCRYPTION_KEY"]</tt> key will
148
+ # only be used for the +:encrypted_column_name+ column, and not the other columns.
149
+ #
150
+ # Note that there isn't a security reason to prefer either model-specific or
151
+ # column-specific keys, as the actual cipher key used is unique per column value.
152
+ #
153
+ # Note that changing the key_id, key string, or auth_data for an existing key will
154
+ # break decryption of values encrypted with that key. If you would like to change
155
+ # any aspect of the key, add a new key, rotate to the new encryption key, and then
156
+ # remove the previous key, as described in the section below on key rotation.
157
+ #
158
+ # = Searching Encrypted Values
159
+ #
160
+ # To search searchable encrypted columns, use +with_encrypted_value+. This example
161
+ # code will return the model instance created in the code example in the previous
162
+ # section:
163
+ #
164
+ # ConfidentialModel.
165
+ # with_encrypted_value(:searchable_column_name, "will be")
166
+ # with_encrypted_value(:ci_searchable_column_name, "encrypted").
167
+ # first
168
+ #
169
+ # = Encryption Key Rotation
170
+ #
171
+ # To rotate encryption keys, add a new key above the existing key, with a new key ID:
172
+ #
173
+ # Sequel::Model.plugin :column_encryption do |enc|
174
+ # enc.key 1, ENV["SEQUEL_COLUMN_ENCRYPTION_KEY"]
175
+ # enc.key 0, ENV["SEQUEL_OLD_COLUMN_ENCRYPTION_KEY"]
176
+ # end
177
+ #
178
+ # Newly encrypted data will then use the new key. Records encrypted with the older key
179
+ # will still be decrypted correctly.
180
+ #
181
+ # To force reencryption for existing records that are using the older key, you can use
182
+ # the +needing_reencryption+ dataset method and the +reencrypt+ instance method. For a
183
+ # small number of records, you can probably do:
184
+ #
185
+ # ConfidentialModel.needing_reencryption.all(&:reencrypt)
186
+ #
187
+ # With more than a small number of records, you'll want to do this in batches. It's
188
+ # possible you could use an approach such as:
189
+ #
190
+ # ds = ConfidentialModel.needing_reencryption.limit(100)
191
+ # true until ds.all(&:reencrypt).empty?
192
+ #
193
+ # After all values have been reencrypted for all models, and no models use the older
194
+ # encryption key, you can remove it from the configuration:
195
+ #
196
+ # Sequel::Model.plugin :column_encryption do |enc|
197
+ # enc.key 1, ENV["SEQUEL_COLUMN_ENCRYPTION_KEY"]
198
+ # end
199
+ #
200
+ # Once an encryption key has been removed, after no data uses it, it is safe to reuse
201
+ # the same key id for a new key. This approach allows for up to 256 concurrent keys
202
+ # in the same configuration.
203
+ #
204
+ # = Encrypting Additional Formats
205
+ #
206
+ # By default, the column_encryption plugin assumes that the decrypted data should be
207
+ # returned as a string, and a string will be passed to encrypt. However, using the
208
+ # +:format+ option, you can specify an alternate format. For example, if you want to
209
+ # encrypt a JSON representation of the object, so that you can deal with an array/hash
210
+ # and automatically have it serialized with JSON and then encrypted when saving, and
211
+ # then deserialized with JSON after decryption when it is retrieved:
212
+ #
213
+ # require 'json'
214
+ # ConfidentialModel.plugin :column_encryption do |enc|
215
+ # enc.key 0, ENV["SEQUEL_MODEL_SPECIFIC_ENCRYPTION_KEY"]
216
+ #
217
+ # enc.column :encrypted_column_name
218
+ # enc.column :searchable_column_name, searchable: true
219
+ # enc.column :ci_searchable_column_name, searchable: :case_insensitive
220
+ # enc.column :encrypted_json_column_name, format: :json
221
+ # end
222
+ #
223
+ # The values of the +:format+ are the same values you can pass as the first argument
224
+ # to +serialize_attributes+ (in the serialization plugin). You can pass an array
225
+ # with the serializer and deserializer for custom support.
226
+ #
227
+ # You can use both +:searchable+ and +:format+ together for searchable encrypted
228
+ # serialized columns. However, note that this allows only exact searches of the
229
+ # serialized version of the data. So for JSON, a search for <tt>{'a'=>1, 'b'=>2}</tt>
230
+ # would not match <tt>{'b'=>2, 'a'=>1}</tt> even though the objects are considered
231
+ # equal. If this is an issue, make sure you use a serialization format where all
232
+ # equal objects are serialized to the same string.
233
+ #
234
+ # = Enforcing Uniqueness
235
+ #
236
+ # You cannot enforce uniqueness of unencrypted data at the database level
237
+ # if you also want to support key rotation. However, absent key rotation, a
238
+ # unique index on the first 48 characters of the encrypted column can enforce uniqueness,
239
+ # as long as the column is searchable. If the encrypted column is case-insensitive
240
+ # searchable, the uniqueness is case insensitive as well.
241
+ #
242
+ # = Column Value Cryptography/Format
243
+ #
244
+ # Column values used by this plugin use the following format (+key+ is specified
245
+ # in the plugin configuration and must be exactly 32 bytes):
246
+ #
247
+ # column_value :: urlsafe_base64(flags + NUL + key_id + NUL + search_data + key_data +
248
+ # cipher_iv + cipher_auth_tag + encrypted_data)
249
+ # flags :: 1 byte, the type of record (0: not searchable, 1: searchable, 2: lowercase searchable)
250
+ # NUL :: 1 byte, ASCII NUL
251
+ # key_id :: 1 byte, the key id, supporting 256 concurrently active keys (0 - 255)
252
+ # search_data :: 0 bytes if flags is 0, 32 bytes if flags is 1 or 2.
253
+ # Format is HMAC-SHA256(key, unencrypted_data).
254
+ # Ignored on decryption, only used for searching.
255
+ # key_data :: 32 bytes random data used to construct cipher key
256
+ # cipher_iv :: 12 bytes, AES-256-GCM cipher random initialization vector
257
+ # cipher_auth_tag :: 16 bytes, AES-256-GCM cipher authentication tag
258
+ # encrypted_data :: AES-256-GCM(HMAC-SHA256(key, key_data),
259
+ # padding_size + padding + unencrypted_data)
260
+ # padding_size :: 1 byte, with the amount of padding (0-255 bytes of padding allowed)
261
+ # padding :: number of bytes specified by padding size, ignored on decryption
262
+ # unencrypted_data :: actual column value
263
+ #
264
+ # The reason for <tt>flags + NUL + key_id + NUL</tt> (4 bytes) as the header is to allow for
265
+ # an easy way to search for values needing reencryption using a database index. It takes
266
+ # the first three bytes and converts them to base64, and looks for values less than that value
267
+ # or greater than that value with 'B' appended. The NUL byte in the fourth byte of the header
268
+ # ensures that after base64 encoding, the fifth byte in the column will be 'A'.
269
+ #
270
+ # The reason for <tt>search_data</tt> (32 bytes) directly after is that for searchable values,
271
+ # after base64 encoding of the header and search data, it is 48 bytes and can be used directly
272
+ # as a prefix search on the column, which can be supported by the same database index. This is
273
+ # more efficient than a full column value search for large values, and allows for case-insensitive
274
+ # searching without a separate column, by having the search_data be based on the lowercase value
275
+ # while the unencrypted data is original case.
276
+ #
277
+ # The reason for the padding is so that a database administrator cannot be sure exactly how
278
+ # many bytes are in the column. It is stored encrypted because otherwise the database
279
+ # administrator could calculate it by decoding the base64 data.
280
+ #
281
+ # = Unsupported Features
282
+ #
283
+ # The following features are delibrately not supported:
284
+ #
285
+ # == Compression
286
+ #
287
+ # Allowing compression with encryption is inviting security issues later.
288
+ # While padding can reduce the risk of compression with encryption, it does not
289
+ # eliminate it entirely. Users that must have compression with encryption can use
290
+ # the +:format+ option with a serializer that compresses and a deserializer that
291
+ # decompresses.
292
+ #
293
+ # == Mixing Encrypted/Unencrypted Data
294
+ #
295
+ # Mixing encrypted and unencrypted data increases the complexity and security risk, since there
296
+ # is a chance unencrypted data could look like encrypted data in the pathologic case.
297
+ # If you have existing unencrypted data that would like to encrypt, create a new column for
298
+ # the encrypted data, and then migrate the data from the unencrypted column to the encrypted
299
+ # column. After all unencrypted values have been migrated, drop the unencrypted column.
300
+ #
301
+ # == Arbitrary Encryption Schemes
302
+ #
303
+ # Supporting arbitrary encryption schemes increases the complexity risk.
304
+ # If in the future AES-256-GCM is not considered a secure enough cipher, it is possible to
305
+ # extend the current format using the reserved values in the first two bytes of the header.
306
+ #
307
+ # = Caveats
308
+ #
309
+ # As column_encryption is a model plugin, it only works with using model instance methods.
310
+ # If you directly modify the database using a dataset or an external program that modifies
311
+ # the contents of the encrypted columns, you will probably corrupt the data. To make data
312
+ # corruption less likely, it is best to have a CHECK constraints on the encrypted column
313
+ # with a basic format and length check:
314
+ #
315
+ # DB.alter_table(:table_name) do
316
+ # c = Sequel[:encrypted_column_name]
317
+ # add_constraint(:encrypted_column_name_format,
318
+ # c.like('AA__A%') | c.like('Ag__A%') | c.like('AQ__A%'))
319
+ # add_constraint(:encrypted_column_name_length, Sequel.char_length(c) >= 88)
320
+ # end
321
+ #
322
+ # If possible, it's also best to check that the column is valid urlsafe base64 data of
323
+ # sufficient length. This can be done on PostgreSQL using a combination of octet_length,
324
+ # decode, and regexp_replace:
325
+ #
326
+ # DB.alter_table(:ce_test) do
327
+ # c = Sequel[:encrypted_column_name]
328
+ # add_constraint(:enc_base64) do
329
+ # octet_length(decode(regexp_replace(regexp_replace(c, '_', '/', 'g'), '-', '+', 'g'), 'base64')) >= 65}
330
+ # end
331
+ # end
332
+ #
333
+ # Such constraints will probably be sufficient to protect against most unintentional corruption of
334
+ # encrypted columns.
335
+ #
336
+ # If the database supports transparent data encryption and you trust the database administrator,
337
+ # using the database support is probably a better approach.
338
+ #
339
+ # The column_encryption plugin is only supported on Ruby 2.3+ and when the Ruby openssl standard
340
+ # library supports the AES-256-GCM cipher.
341
+ module ColumnEncryption
342
+ # Cryptor handles the encryption and decryption of rows for a key set.
343
+ # It also provides methods that return search prefixes, which datasets
344
+ # use in queries.
345
+ #
346
+ # The same cryptor can support non-searchable, searchable, and case-insensitive
347
+ # searchable columns.
348
+ class Cryptor # :nodoc:
349
+ # Flags
350
+ NOT_SEARCHABLE = 0
351
+ SEARCHABLE = 1
352
+ LOWERCASE_SEARCHABLE = 2
353
+
354
+ # This is the default padding, but up to 2x the padding can be used for a record.
355
+ DEFAULT_PADDING = 8
356
+
357
+ # Keys should be an array of arrays containing key_id, key string, auth_data, and padding.
358
+ def initialize(keys)
359
+ if !keys || keys.empty?
360
+ raise Error, "Cannot initialize encryptor without encryption key"
361
+ end
362
+
363
+ # First key is used for encryption
364
+ @key_id, @key, @auth_data, @padding = keys[0]
365
+
366
+ # All keys are candidates for decryption
367
+ @key_map = {}
368
+ keys.each do |key_id, key, auth_data, padding|
369
+ @key_map[key_id] = [key, auth_data, padding].freeze
370
+ end
371
+
372
+ freeze
373
+ end
374
+
375
+ # Decrypt using any supported format and any available key.
376
+ def decrypt(data)
377
+ begin
378
+ data = Base64.urlsafe_decode64(data)
379
+ rescue ArgumentError
380
+ raise Error, "Unable to decode encrypted column: invalid base64"
381
+ end
382
+
383
+ unless data.getbyte(1) == 0 && data.getbyte(3) == 0
384
+ raise Error, "Unable to decode encrypted column: invalid format"
385
+ end
386
+
387
+ flags = data.getbyte(0)
388
+
389
+ key, auth_data = @key_map[data.getbyte(2)]
390
+ unless key
391
+ raise Error, "Unable to decode encrypted column: invalid key id"
392
+ end
393
+
394
+ case flags
395
+ when NOT_SEARCHABLE
396
+ if data.bytesize < 65
397
+ raise Error, "Decoded encrypted column smaller than minimum size"
398
+ end
399
+
400
+ data.slice!(0, 4)
401
+ when SEARCHABLE, LOWERCASE_SEARCHABLE
402
+ if data.bytesize < 97
403
+ raise Error, "Decoded encrypted column smaller than minimum size"
404
+ end
405
+
406
+ data.slice!(0, 36)
407
+ else
408
+ raise Error, "Unable to decode encrypted column: invalid flags"
409
+ end
410
+
411
+ key_part = data.slice!(0, 32)
412
+ cipher_iv = data.slice!(0, 12)
413
+ auth_tag = data.slice!(0, 16)
414
+
415
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
416
+ cipher.decrypt
417
+ cipher.iv = cipher_iv
418
+ cipher.key = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, key_part)
419
+ cipher.auth_data = auth_data
420
+ cipher.auth_tag = auth_tag
421
+ begin
422
+ decrypted_data = cipher.update(data) << cipher.final
423
+ rescue OpenSSL::Cipher::CipherError => e
424
+ raise Error, "Unable to decrypt encrypted column: #{e.class} (probably due to encryption key or auth data mismatch or corrupt data)"
425
+ end
426
+
427
+ # Remove padding
428
+ decrypted_data.slice!(0, decrypted_data.getbyte(0) + 1)
429
+
430
+ decrypted_data
431
+ end
432
+
433
+ # Encrypt in not searchable format with the first configured encryption key.
434
+ def encrypt(data)
435
+ _encrypt(data, "#{NOT_SEARCHABLE.chr}\0#{@key_id.chr}\0")
436
+ end
437
+
438
+ # Encrypt in searchable format with the first configured encryption key.
439
+ def searchable_encrypt(data)
440
+ _encrypt(data, _search_prefix(data, SEARCHABLE, @key_id, @key))
441
+ end
442
+
443
+ # Encrypt in case insensitive searchable format with the first configured encryption key.
444
+ def case_insensitive_searchable_encrypt(data)
445
+ _encrypt(data, _search_prefix(data.downcase, LOWERCASE_SEARCHABLE, @key_id, @key))
446
+ end
447
+
448
+ # The prefix string of columns for the given search type and the first configured encryption key.
449
+ # Used to find values that do not use this prefix in order to perform reencryption.
450
+ def current_key_prefix(search_type)
451
+ Base64.urlsafe_encode64("#{search_type.chr}\0#{@key_id.chr}")
452
+ end
453
+
454
+ # The prefix values to search for the given data (an array of strings), assuming the column uses
455
+ # the searchable format.
456
+ def search_prefixes(data)
457
+ _search_prefixes(data, SEARCHABLE)
458
+ end
459
+
460
+ # The prefix values to search for the given data (an array of strings), assuming the column uses
461
+ # the case insensitive searchable format.
462
+ def lowercase_search_prefixes(data)
463
+ _search_prefixes(data.downcase, LOWERCASE_SEARCHABLE)
464
+ end
465
+
466
+ # The prefix values to search for the given data (an array of strings), assuming the column uses
467
+ # either the searchable or the case insensitive searchable format. Should be used only when
468
+ # transitioning between formats (used by the :search_both option when encrypting columns).
469
+ def regular_and_lowercase_search_prefixes(data)
470
+ search_prefixes(data) + lowercase_search_prefixes(data)
471
+ end
472
+
473
+ private
474
+
475
+ # An array of strings, one for each configured encryption key, to find encypted values matching
476
+ # the given data and search format.
477
+ def _search_prefixes(data, search_type)
478
+ @key_map.map do |key_id, (key, _)|
479
+ Base64.urlsafe_encode64(_search_prefix(data, search_type, key_id, key))
480
+ end
481
+ end
482
+
483
+ # The prefix to use for searchable data, including the HMAC-SHA256(key, data).
484
+ def _search_prefix(data, search_type, key_id, key)
485
+ "#{search_type.chr}\0#{key_id.chr}\0#{OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, data)}"
486
+ end
487
+
488
+ # Encrypt the data using AES-256-GCM, with the given prefix.
489
+ def _encrypt(data, prefix)
490
+ padding = @padding
491
+ random_data = SecureRandom.random_bytes(32)
492
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
493
+ cipher.encrypt
494
+ cipher.key = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, @key, random_data)
495
+ cipher_iv = cipher.random_iv
496
+ cipher.auth_data = @auth_data
497
+
498
+ cipher_text = String.new
499
+ data_size = data.bytesize
500
+
501
+ padding_size = if padding
502
+ (padding * rand(1)) + padding - (data.bytesize % padding)
503
+ else
504
+ 0
505
+ end
506
+
507
+ cipher_text << cipher.update(padding_size.chr)
508
+ cipher_text << cipher.update(SecureRandom.random_bytes(padding_size)) if padding_size > 0
509
+ cipher_text << cipher.update(data) if data_size > 0
510
+ cipher_text << cipher.final
511
+
512
+ Base64.urlsafe_encode64("#{prefix}#{random_data}#{cipher_iv}#{cipher.auth_tag}#{cipher_text}")
513
+ end
514
+ end
515
+
516
+ # The object type yielded to blocks passed to the +column+ method inside
517
+ # <tt>plugin :column_encryption</tt> blocks. This is used to configure custom
518
+ # per-column keys.
519
+ class ColumnDSL # :nodoc:
520
+ # An array of arrays for the data for the keys configured inside the block.
521
+ attr_reader :keys
522
+
523
+ def initialize
524
+ @keys = []
525
+ end
526
+
527
+ # Verify that the key_id, key, and options are value.
528
+ def key(key_id, key, opts=OPTS)
529
+ unless key_id.is_a?(Integer) && key_id >= 0 && key_id <= 255
530
+ raise Error, "invalid key_id argument, must be integer between 0 and 255"
531
+ end
532
+
533
+ unless key.is_a?(String) && key.bytesize == 32
534
+ raise Error, "invalid key argument, must be string with exactly 32 bytes"
535
+ end
536
+
537
+ if opts.has_key?(:padding)
538
+ if padding = opts[:padding]
539
+ unless padding.is_a?(Integer) && padding >= 1 && padding <= 120
540
+ raise Error, "invalid :padding option, must be between 1 and 120"
541
+ end
542
+ end
543
+ else
544
+ padding = Cryptor::DEFAULT_PADDING
545
+ end
546
+
547
+ @keys << [key_id, key, opts[:auth_data].to_s, padding].freeze
548
+ end
549
+ end
550
+
551
+ # The object type yielded to <tt>plugin :column_encryption</tt> blocks,
552
+ # used to configure encryption keys and encrypted columns.
553
+ class DSL < ColumnDSL # :nodoc:
554
+ # An array of arrays of data for the columns configured inside the block.
555
+ attr_reader :columns
556
+
557
+ def initialize
558
+ super
559
+ @columns = []
560
+ end
561
+
562
+ # Store the column information.
563
+ def column(column, opts=OPTS, &block)
564
+ @columns << [column, opts, block].freeze
565
+ end
566
+ end
567
+
568
+ def self.apply(model, opts=OPTS)
569
+ model.plugin :serialization
570
+ end
571
+
572
+ def self.configure(model)
573
+ dsl = DSL.new
574
+ yield dsl
575
+
576
+ model.instance_exec do
577
+ unless dsl.keys.empty?
578
+ @column_encryption_keys = dsl.keys.freeze
579
+ @column_encryption_cryptor = nil
580
+ end
581
+
582
+ @column_encryption_metadata = Hash[@column_encryption_metadata || {}]
583
+
584
+ dsl.columns.each do |column, opts, block|
585
+ _encrypt_column(column, opts, &block)
586
+ end
587
+
588
+ @column_encryption_metadata.freeze
589
+ end
590
+ end
591
+
592
+ # This stores four callables for handling encyption, decryption, data searching,
593
+ # and key searching. One of these is created for each encrypted column.
594
+ ColumnEncryptionMetadata = Struct.new(:encryptor, :decryptor, :data_searcher, :key_searcher) # :nodoc:
595
+
596
+ module ClassMethods
597
+ private
598
+
599
+ # A hash with column symbol keys and ColumnEncryptionMetadata values for each
600
+ # encrypted column.
601
+ attr_reader :column_encryption_metadata
602
+
603
+ # The default Cryptor to use for encrypted columns. This is only overridden if
604
+ # per-column keys are used.
605
+ def column_encryption_cryptor
606
+ @column_encryption_cryptor ||= Cryptor.new(@column_encryption_keys)
607
+ end
608
+
609
+ # Setup encryption for the given column.
610
+ def _encrypt_column(column, opts)
611
+ cryptor ||= if defined?(yield)
612
+ dsl = ColumnDSL.new
613
+ yield dsl
614
+ Cryptor.new(dsl.keys)
615
+ else
616
+ column_encryption_cryptor
617
+ end
618
+
619
+ encrypt_method, search_prefixes_method, search_type = case searchable = opts[:searchable]
620
+ when nil, false
621
+ [:encrypt, nil, Cryptor::NOT_SEARCHABLE]
622
+ when true
623
+ [:searchable_encrypt, :search_prefixes, Cryptor::SEARCHABLE]
624
+ when :case_insensitive
625
+ [:case_insensitive_searchable_encrypt, :lowercase_search_prefixes, Cryptor::LOWERCASE_SEARCHABLE]
626
+ else
627
+ raise Error, "invalid :searchable option for encrypted column: #{searchable.inspect}"
628
+ end
629
+
630
+ if searchable && opts[:search_both]
631
+ search_prefixes_method = :regular_and_lowercase_search_prefixes
632
+ end
633
+
634
+ # Setup the callables used in the metadata.
635
+ encryptor = cryptor.method(encrypt_method)
636
+ decryptor = cryptor.method(:decrypt)
637
+ data_searcher = cryptor.method(search_prefixes_method) if search_prefixes_method
638
+ key_searcher = lambda{cryptor.current_key_prefix(search_type)}
639
+
640
+ if format = opts[:format]
641
+ if format.is_a?(Symbol)
642
+ unless format = Sequel.synchronize{Serialization::REGISTERED_FORMATS[format]}
643
+ raise(Error, "Unsupported serialization format: #{format} (valid formats: #{Sequel.synchronize{Serialization::REGISTERED_FORMATS.keys}.inspect})")
644
+ end
645
+ end
646
+
647
+ # If a custom serialization format is used, override the
648
+ # callables to handle serialization and deserialization.
649
+ serializer, deserializer = format
650
+ enc, dec, data_s = encryptor, decryptor, data_searcher
651
+ encryptor = lambda do |data|
652
+ enc.call(serializer.call(data))
653
+ end
654
+ decryptor = lambda do |data|
655
+ deserializer.call(dec.call(data))
656
+ end
657
+ data_searcher = lambda do |data|
658
+ data_s.call(serializer.call(data))
659
+ end
660
+ end
661
+
662
+ # Setup the setter and getter methods to do encryption and decryption using
663
+ # the serialization plugin.
664
+ serialize_attributes([encryptor, decryptor], column)
665
+
666
+ column_encryption_metadata[column] = ColumnEncryptionMetadata.new(encryptor, decryptor, data_searcher, key_searcher).freeze
667
+
668
+ nil
669
+ end
670
+ end
671
+
672
+ module ClassMethods
673
+ Plugins.def_dataset_methods(self, [:with_encrypted_value, :needing_reencryption])
674
+
675
+ Plugins.inherited_instance_variables(self,
676
+ :@column_encryption_cryptor=>nil,
677
+ :@column_encryption_keys=>nil,
678
+ :@column_encryption_metadata=>nil,
679
+ )
680
+ end
681
+
682
+ module InstanceMethods
683
+ # Reencrypt the model if needed. Looks at all of the models encrypted columns
684
+ # and if any were encypted with older keys or a different format, reencrypt
685
+ # with the current key and format and save the object. Returns the object
686
+ # if reencryption was needed, or nil if reencryption was not needed.
687
+ def reencrypt
688
+ do_save = false
689
+
690
+ model.send(:column_encryption_metadata).each do |column, metadata|
691
+ if (value = values[column]) && !value.start_with?(metadata.key_searcher.call)
692
+ do_save = true
693
+ values[column] = metadata.encryptor.call(metadata.decryptor.call(value))
694
+ end
695
+ end
696
+
697
+ save if do_save
698
+ end
699
+ end
700
+
701
+ module DatasetMethods
702
+ # Filter the dataset to only match rows where the column contains an encrypted version
703
+ # of value. Only works on searchable encrypted columns.
704
+ def with_encrypted_value(column, value)
705
+ metadata = model.send(:column_encryption_metadata)[column]
706
+
707
+ unless metadata && metadata.data_searcher
708
+ raise Error, "lookup for encrypted column #{column.inspect} is not supported"
709
+ end
710
+
711
+ prefixes = metadata.data_searcher.call(value)
712
+ where(Sequel.|(*prefixes.map{|v| Sequel.like(column, "#{escape_like(v)}%")}))
713
+ end
714
+
715
+ # Filter the dataset to exclude rows where all encrypted columns are already encrypted
716
+ # with the current key and format.
717
+ def needing_reencryption
718
+ incorrect_column_prefixes = model.send(:column_encryption_metadata).map do |column, metadata|
719
+ prefix = metadata.key_searcher.call
720
+ (Sequel[column] < prefix) | (Sequel[column] > prefix + 'B')
721
+ end
722
+
723
+ where(Sequel.|(*incorrect_column_prefixes))
724
+ end
725
+ end
726
+ end
727
+ end
728
+ end