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.
- checksums.yaml +5 -5
- data/CHANGELOG +548 -5749
- data/MIT-LICENSE +1 -1
- data/README.rdoc +265 -159
- data/bin/sequel +34 -12
- data/doc/advanced_associations.rdoc +228 -187
- data/doc/association_basics.rdoc +281 -291
- data/doc/bin_sequel.rdoc +5 -3
- data/doc/cheat_sheet.rdoc +86 -51
- data/doc/code_order.rdoc +25 -19
- data/doc/core_extensions.rdoc +104 -63
- data/doc/dataset_basics.rdoc +12 -21
- data/doc/dataset_filtering.rdoc +99 -86
- data/doc/extensions.rdoc +3 -10
- data/doc/fork_safety.rdoc +84 -0
- data/doc/mass_assignment.rdoc +74 -31
- data/doc/migration.rdoc +59 -51
- data/doc/model_dataset_method_design.rdoc +129 -0
- data/doc/model_hooks.rdoc +15 -25
- data/doc/model_plugins.rdoc +12 -12
- data/doc/mssql_stored_procedures.rdoc +3 -3
- data/doc/object_model.rdoc +58 -68
- data/doc/opening_databases.rdoc +85 -95
- data/doc/postgresql.rdoc +263 -38
- data/doc/prepared_statements.rdoc +29 -24
- data/doc/querying.rdoc +189 -167
- data/doc/reflection.rdoc +5 -6
- data/doc/release_notes/5.0.0.txt +159 -0
- data/doc/release_notes/5.1.0.txt +31 -0
- data/doc/release_notes/5.10.0.txt +84 -0
- data/doc/release_notes/5.11.0.txt +83 -0
- data/doc/release_notes/5.12.0.txt +141 -0
- data/doc/release_notes/5.13.0.txt +27 -0
- data/doc/release_notes/5.14.0.txt +63 -0
- data/doc/release_notes/5.15.0.txt +39 -0
- data/doc/release_notes/5.16.0.txt +110 -0
- data/doc/release_notes/5.17.0.txt +31 -0
- data/doc/release_notes/5.18.0.txt +69 -0
- data/doc/release_notes/5.19.0.txt +28 -0
- data/doc/release_notes/5.2.0.txt +33 -0
- data/doc/release_notes/5.20.0.txt +89 -0
- data/doc/release_notes/5.21.0.txt +87 -0
- data/doc/release_notes/5.22.0.txt +48 -0
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/doc/release_notes/5.25.0.txt +32 -0
- data/doc/release_notes/5.26.0.txt +35 -0
- data/doc/release_notes/5.27.0.txt +21 -0
- data/doc/release_notes/5.28.0.txt +16 -0
- data/doc/release_notes/5.29.0.txt +22 -0
- data/doc/release_notes/5.3.0.txt +121 -0
- data/doc/release_notes/5.30.0.txt +20 -0
- data/doc/release_notes/5.31.0.txt +148 -0
- data/doc/release_notes/5.32.0.txt +46 -0
- data/doc/release_notes/5.33.0.txt +24 -0
- data/doc/release_notes/5.34.0.txt +40 -0
- data/doc/release_notes/5.35.0.txt +56 -0
- data/doc/release_notes/5.36.0.txt +60 -0
- data/doc/release_notes/5.37.0.txt +30 -0
- data/doc/release_notes/5.38.0.txt +28 -0
- data/doc/release_notes/5.39.0.txt +19 -0
- data/doc/release_notes/5.4.0.txt +80 -0
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/release_notes/5.45.0.txt +34 -0
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.5.0.txt +61 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/release_notes/5.55.0.txt +21 -0
- data/doc/release_notes/5.56.0.txt +51 -0
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.6.0.txt +31 -0
- data/doc/release_notes/5.60.0.txt +22 -0
- data/doc/release_notes/5.61.0.txt +43 -0
- data/doc/release_notes/5.7.0.txt +108 -0
- data/doc/release_notes/5.8.0.txt +170 -0
- data/doc/release_notes/5.9.0.txt +99 -0
- data/doc/schema_modification.rdoc +95 -75
- data/doc/security.rdoc +109 -80
- data/doc/sharding.rdoc +74 -47
- data/doc/sql.rdoc +147 -122
- data/doc/testing.rdoc +43 -20
- data/doc/thread_safety.rdoc +2 -4
- data/doc/transactions.rdoc +97 -18
- data/doc/validations.rdoc +52 -50
- data/doc/virtual_rows.rdoc +90 -109
- data/lib/sequel/adapters/ado/access.rb +15 -17
- data/lib/sequel/adapters/ado/mssql.rb +6 -15
- data/lib/sequel/adapters/ado.rb +150 -20
- data/lib/sequel/adapters/amalgalite.rb +11 -23
- data/lib/sequel/adapters/ibmdb.rb +47 -55
- data/lib/sequel/adapters/jdbc/db2.rb +29 -39
- data/lib/sequel/adapters/jdbc/derby.rb +58 -54
- data/lib/sequel/adapters/jdbc/h2.rb +93 -35
- data/lib/sequel/adapters/jdbc/hsqldb.rb +24 -31
- data/lib/sequel/adapters/jdbc/jtds.rb +2 -10
- data/lib/sequel/adapters/jdbc/mssql.rb +3 -11
- data/lib/sequel/adapters/jdbc/mysql.rb +17 -20
- data/lib/sequel/adapters/jdbc/oracle.rb +22 -18
- data/lib/sequel/adapters/jdbc/postgresql.rb +69 -71
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +11 -23
- data/lib/sequel/adapters/jdbc/sqlite.rb +47 -11
- data/lib/sequel/adapters/jdbc/sqlserver.rb +34 -9
- data/lib/sequel/adapters/jdbc/transactions.rb +22 -38
- data/lib/sequel/adapters/jdbc.rb +145 -130
- data/lib/sequel/adapters/mock.rb +100 -111
- data/lib/sequel/adapters/mysql.rb +114 -122
- data/lib/sequel/adapters/mysql2.rb +147 -63
- data/lib/sequel/adapters/odbc/db2.rb +1 -1
- data/lib/sequel/adapters/odbc/mssql.rb +8 -14
- data/lib/sequel/adapters/odbc/oracle.rb +11 -0
- data/lib/sequel/adapters/odbc.rb +20 -25
- data/lib/sequel/adapters/oracle.rb +50 -56
- data/lib/sequel/adapters/postgres.rb +305 -327
- data/lib/sequel/adapters/postgresql.rb +1 -1
- data/lib/sequel/adapters/shared/access.rb +74 -78
- data/lib/sequel/adapters/shared/db2.rb +118 -71
- data/lib/sequel/adapters/shared/mssql.rb +301 -220
- data/lib/sequel/adapters/shared/mysql.rb +299 -217
- data/lib/sequel/adapters/shared/oracle.rb +226 -65
- data/lib/sequel/adapters/shared/postgres.rb +935 -395
- data/lib/sequel/adapters/shared/sqlanywhere.rb +105 -126
- data/lib/sequel/adapters/shared/sqlite.rb +447 -173
- data/lib/sequel/adapters/sqlanywhere.rb +48 -35
- data/lib/sequel/adapters/sqlite.rb +156 -111
- data/lib/sequel/adapters/tinytds.rb +30 -38
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +3 -6
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +2 -2
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +87 -0
- data/lib/sequel/adapters/utils/mysql_prepared_statements.rb +56 -0
- data/lib/sequel/adapters/utils/replace.rb +1 -4
- data/lib/sequel/adapters/utils/stored_procedures.rb +7 -22
- data/lib/sequel/adapters/utils/unmodified_identifiers.rb +28 -0
- data/lib/sequel/ast_transformer.rb +17 -89
- data/lib/sequel/connection_pool/sharded_single.rb +18 -15
- data/lib/sequel/connection_pool/sharded_threaded.rb +130 -111
- data/lib/sequel/connection_pool/single.rb +18 -13
- data/lib/sequel/connection_pool/threaded.rb +121 -120
- data/lib/sequel/connection_pool.rb +48 -29
- data/lib/sequel/core.rb +351 -301
- data/lib/sequel/database/connecting.rb +69 -57
- data/lib/sequel/database/dataset.rb +13 -5
- data/lib/sequel/database/dataset_defaults.rb +18 -102
- data/lib/sequel/database/features.rb +18 -4
- data/lib/sequel/database/logging.rb +12 -11
- data/lib/sequel/database/misc.rb +180 -122
- data/lib/sequel/database/query.rb +47 -27
- data/lib/sequel/database/schema_generator.rb +178 -84
- data/lib/sequel/database/schema_methods.rb +172 -97
- data/lib/sequel/database/transactions.rb +205 -44
- data/lib/sequel/database.rb +17 -2
- data/lib/sequel/dataset/actions.rb +339 -155
- data/lib/sequel/dataset/dataset_module.rb +46 -0
- data/lib/sequel/dataset/features.rb +90 -35
- data/lib/sequel/dataset/graph.rb +80 -58
- data/lib/sequel/dataset/misc.rb +137 -47
- data/lib/sequel/dataset/placeholder_literalizer.rb +63 -25
- data/lib/sequel/dataset/prepared_statements.rb +188 -85
- data/lib/sequel/dataset/query.rb +530 -222
- data/lib/sequel/dataset/sql.rb +590 -368
- data/lib/sequel/dataset.rb +26 -16
- data/lib/sequel/deprecated.rb +12 -2
- data/lib/sequel/exceptions.rb +46 -16
- data/lib/sequel/extensions/_model_constraint_validations.rb +16 -0
- data/lib/sequel/extensions/_model_pg_row.rb +43 -0
- data/lib/sequel/extensions/_pretty_table.rb +2 -5
- data/lib/sequel/extensions/any_not_empty.rb +45 -0
- data/lib/sequel/extensions/arbitrary_servers.rb +10 -10
- data/lib/sequel/extensions/async_thread_pool.rb +438 -0
- data/lib/sequel/extensions/auto_literal_strings.rb +74 -0
- data/lib/sequel/extensions/blank.rb +8 -0
- data/lib/sequel/extensions/caller_logging.rb +79 -0
- data/lib/sequel/extensions/columns_introspection.rb +4 -3
- data/lib/sequel/extensions/connection_expiration.rb +20 -10
- data/lib/sequel/extensions/connection_validator.rb +11 -10
- data/lib/sequel/extensions/constant_sql_override.rb +65 -0
- data/lib/sequel/extensions/constraint_validations.rb +62 -39
- data/lib/sequel/extensions/core_extensions.rb +42 -48
- data/lib/sequel/extensions/core_refinements.rb +80 -59
- data/lib/sequel/extensions/current_datetime_timestamp.rb +1 -4
- data/lib/sequel/extensions/date_arithmetic.rb +98 -39
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +41 -0
- data/lib/sequel/extensions/duplicate_columns_handler.rb +21 -14
- data/lib/sequel/extensions/empty_array_consider_nulls.rb +2 -2
- data/lib/sequel/extensions/escaped_like.rb +100 -0
- data/lib/sequel/extensions/eval_inspect.rb +12 -15
- data/lib/sequel/extensions/exclude_or_null.rb +68 -0
- data/lib/sequel/extensions/fiber_concurrency.rb +24 -0
- data/lib/sequel/extensions/freeze_datasets.rb +3 -0
- data/lib/sequel/extensions/from_block.rb +1 -34
- data/lib/sequel/extensions/graph_each.rb +4 -4
- data/lib/sequel/extensions/identifier_mangling.rb +180 -0
- data/lib/sequel/extensions/implicit_subquery.rb +48 -0
- data/lib/sequel/extensions/index_caching.rb +109 -0
- data/lib/sequel/extensions/inflector.rb +13 -5
- data/lib/sequel/extensions/integer64.rb +32 -0
- data/lib/sequel/extensions/is_distinct_from.rb +141 -0
- data/lib/sequel/extensions/looser_typecasting.rb +17 -8
- data/lib/sequel/extensions/migration.rb +119 -78
- data/lib/sequel/extensions/named_timezones.rb +88 -23
- data/lib/sequel/extensions/no_auto_literal_strings.rb +2 -82
- data/lib/sequel/extensions/null_dataset.rb +8 -8
- data/lib/sequel/extensions/pagination.rb +32 -29
- data/lib/sequel/extensions/pg_array.rb +221 -287
- data/lib/sequel/extensions/pg_array_ops.rb +17 -9
- data/lib/sequel/extensions/pg_enum.rb +63 -23
- data/lib/sequel/extensions/pg_extended_date_support.rb +241 -0
- data/lib/sequel/extensions/pg_hstore.rb +45 -54
- data/lib/sequel/extensions/pg_hstore_ops.rb +58 -6
- data/lib/sequel/extensions/pg_inet.rb +31 -12
- data/lib/sequel/extensions/pg_inet_ops.rb +2 -2
- data/lib/sequel/extensions/pg_interval.rb +56 -29
- data/lib/sequel/extensions/pg_json.rb +417 -140
- data/lib/sequel/extensions/pg_json_ops.rb +270 -18
- data/lib/sequel/extensions/pg_loose_count.rb +4 -2
- data/lib/sequel/extensions/pg_multirange.rb +372 -0
- data/lib/sequel/extensions/pg_range.rb +131 -191
- data/lib/sequel/extensions/pg_range_ops.rb +42 -13
- data/lib/sequel/extensions/pg_row.rb +48 -81
- data/lib/sequel/extensions/pg_row_ops.rb +33 -14
- data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -2
- data/lib/sequel/extensions/pg_timestamptz.rb +28 -0
- data/lib/sequel/extensions/query.rb +9 -7
- data/lib/sequel/extensions/round_timestamps.rb +0 -6
- data/lib/sequel/extensions/run_transaction_hooks.rb +72 -0
- data/lib/sequel/extensions/s.rb +60 -0
- data/lib/sequel/extensions/schema_caching.rb +10 -1
- data/lib/sequel/extensions/schema_dumper.rb +71 -48
- data/lib/sequel/extensions/select_remove.rb +4 -4
- data/lib/sequel/extensions/sequel_4_dataset_methods.rb +85 -0
- data/lib/sequel/extensions/server_block.rb +51 -27
- data/lib/sequel/extensions/split_array_nil.rb +4 -4
- data/lib/sequel/extensions/sql_comments.rb +119 -7
- data/lib/sequel/extensions/sql_expr.rb +2 -1
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
- data/lib/sequel/extensions/string_agg.rb +11 -8
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/extensions/symbol_aref.rb +55 -0
- data/lib/sequel/extensions/symbol_aref_refinement.rb +43 -0
- data/lib/sequel/extensions/symbol_as.rb +23 -0
- data/lib/sequel/extensions/symbol_as_refinement.rb +37 -0
- data/lib/sequel/extensions/synchronize_sql.rb +45 -0
- data/lib/sequel/extensions/to_dot.rb +10 -4
- data/lib/sequel/extensions/virtual_row_method_block.rb +44 -0
- data/lib/sequel/model/associations.rb +1006 -284
- data/lib/sequel/model/base.rb +560 -805
- data/lib/sequel/model/dataset_module.rb +11 -10
- data/lib/sequel/model/default_inflections.rb +1 -1
- data/lib/sequel/model/errors.rb +10 -3
- data/lib/sequel/model/exceptions.rb +8 -10
- data/lib/sequel/model/inflections.rb +7 -20
- data/lib/sequel/model/plugins.rb +114 -0
- data/lib/sequel/model.rb +32 -82
- data/lib/sequel/plugins/active_model.rb +30 -14
- data/lib/sequel/plugins/after_initialize.rb +1 -1
- data/lib/sequel/plugins/association_dependencies.rb +25 -18
- data/lib/sequel/plugins/association_lazy_eager_option.rb +66 -0
- data/lib/sequel/plugins/association_multi_add_remove.rb +85 -0
- data/lib/sequel/plugins/association_pks.rb +147 -70
- data/lib/sequel/plugins/association_proxies.rb +33 -9
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +95 -28
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/before_after_save.rb +0 -42
- data/lib/sequel/plugins/blacklist_security.rb +21 -12
- data/lib/sequel/plugins/boolean_readers.rb +5 -5
- data/lib/sequel/plugins/boolean_subsets.rb +13 -8
- data/lib/sequel/plugins/caching.rb +25 -16
- data/lib/sequel/plugins/class_table_inheritance.rb +179 -100
- data/lib/sequel/plugins/column_conflicts.rb +16 -3
- data/lib/sequel/plugins/column_encryption.rb +728 -0
- data/lib/sequel/plugins/column_select.rb +7 -5
- data/lib/sequel/plugins/columns_updated.rb +42 -0
- data/lib/sequel/plugins/composition.rb +42 -26
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/constraint_validations.rb +20 -14
- data/lib/sequel/plugins/csv_serializer.rb +56 -35
- data/lib/sequel/plugins/dataset_associations.rb +40 -17
- data/lib/sequel/plugins/def_dataset_method.rb +90 -0
- data/lib/sequel/plugins/defaults_setter.rb +65 -10
- data/lib/sequel/plugins/delay_add_association.rb +1 -1
- data/lib/sequel/plugins/dirty.rb +62 -24
- data/lib/sequel/plugins/eager_each.rb +3 -3
- data/lib/sequel/plugins/eager_graph_eager.rb +139 -0
- data/lib/sequel/plugins/empty_failure_backtraces.rb +38 -0
- data/lib/sequel/plugins/enum.rb +124 -0
- data/lib/sequel/plugins/error_splitter.rb +17 -12
- data/lib/sequel/plugins/finder.rb +246 -0
- data/lib/sequel/plugins/forbid_lazy_load.rb +216 -0
- data/lib/sequel/plugins/force_encoding.rb +7 -12
- data/lib/sequel/plugins/hook_class_methods.rb +37 -54
- data/lib/sequel/plugins/input_transformer.rb +18 -10
- data/lib/sequel/plugins/insert_conflict.rb +76 -0
- data/lib/sequel/plugins/insert_returning_select.rb +2 -2
- data/lib/sequel/plugins/instance_filters.rb +10 -8
- data/lib/sequel/plugins/instance_hooks.rb +34 -17
- data/lib/sequel/plugins/instance_specific_default.rb +113 -0
- data/lib/sequel/plugins/inverted_subsets.rb +22 -13
- data/lib/sequel/plugins/json_serializer.rb +124 -64
- data/lib/sequel/plugins/lazy_attributes.rb +21 -14
- data/lib/sequel/plugins/list.rb +35 -21
- data/lib/sequel/plugins/many_through_many.rb +134 -21
- data/lib/sequel/plugins/modification_detection.rb +15 -5
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +6 -5
- data/lib/sequel/plugins/nested_attributes.rb +61 -31
- data/lib/sequel/plugins/optimistic_locking.rb +3 -3
- data/lib/sequel/plugins/pg_array_associations.rb +103 -53
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +350 -0
- data/lib/sequel/plugins/pg_row.rb +5 -51
- data/lib/sequel/plugins/prepared_statements.rb +60 -72
- data/lib/sequel/plugins/prepared_statements_safe.rb +9 -4
- data/lib/sequel/plugins/rcte_tree.rb +68 -82
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/serialization.rb +43 -46
- data/lib/sequel/plugins/serialization_modification_detection.rb +3 -2
- data/lib/sequel/plugins/sharding.rb +15 -10
- data/lib/sequel/plugins/single_table_inheritance.rb +67 -28
- data/lib/sequel/plugins/skip_create_refresh.rb +3 -3
- data/lib/sequel/plugins/skip_saving_columns.rb +108 -0
- data/lib/sequel/plugins/split_values.rb +11 -6
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +77 -53
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/plugins/string_stripper.rb +3 -3
- data/lib/sequel/plugins/subclasses.rb +43 -10
- data/lib/sequel/plugins/subset_conditions.rb +15 -5
- data/lib/sequel/plugins/table_select.rb +2 -2
- data/lib/sequel/plugins/tactical_eager_loading.rb +96 -12
- data/lib/sequel/plugins/throw_failures.rb +110 -0
- data/lib/sequel/plugins/timestamps.rb +20 -8
- data/lib/sequel/plugins/touch.rb +19 -8
- data/lib/sequel/plugins/tree.rb +62 -32
- data/lib/sequel/plugins/typecast_on_load.rb +12 -4
- data/lib/sequel/plugins/unlimited_update.rb +1 -7
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/update_or_create.rb +4 -4
- data/lib/sequel/plugins/update_primary_key.rb +1 -1
- data/lib/sequel/plugins/update_refresh.rb +26 -15
- data/lib/sequel/plugins/uuid.rb +7 -11
- data/lib/sequel/plugins/validate_associated.rb +18 -0
- data/lib/sequel/plugins/validation_class_methods.rb +38 -19
- data/lib/sequel/plugins/validation_contexts.rb +49 -0
- data/lib/sequel/plugins/validation_helpers.rb +57 -41
- data/lib/sequel/plugins/whitelist_security.rb +122 -0
- data/lib/sequel/plugins/xml_serializer.rb +30 -31
- data/lib/sequel/sql.rb +471 -331
- data/lib/sequel/timezones.rb +78 -47
- data/lib/sequel/version.rb +7 -2
- data/lib/sequel.rb +1 -1
- metadata +217 -521
- data/Rakefile +0 -164
- data/doc/active_record.rdoc +0 -928
- data/doc/release_notes/1.0.txt +0 -38
- data/doc/release_notes/1.1.txt +0 -143
- data/doc/release_notes/1.3.txt +0 -101
- data/doc/release_notes/1.4.0.txt +0 -53
- data/doc/release_notes/1.5.0.txt +0 -155
- data/doc/release_notes/2.0.0.txt +0 -298
- data/doc/release_notes/2.1.0.txt +0 -271
- data/doc/release_notes/2.10.0.txt +0 -328
- data/doc/release_notes/2.11.0.txt +0 -215
- data/doc/release_notes/2.12.0.txt +0 -534
- data/doc/release_notes/2.2.0.txt +0 -253
- data/doc/release_notes/2.3.0.txt +0 -88
- data/doc/release_notes/2.4.0.txt +0 -106
- data/doc/release_notes/2.5.0.txt +0 -137
- data/doc/release_notes/2.6.0.txt +0 -157
- data/doc/release_notes/2.7.0.txt +0 -166
- data/doc/release_notes/2.8.0.txt +0 -171
- data/doc/release_notes/2.9.0.txt +0 -97
- data/doc/release_notes/3.0.0.txt +0 -221
- data/doc/release_notes/3.1.0.txt +0 -406
- data/doc/release_notes/3.10.0.txt +0 -286
- data/doc/release_notes/3.11.0.txt +0 -254
- data/doc/release_notes/3.12.0.txt +0 -304
- data/doc/release_notes/3.13.0.txt +0 -210
- data/doc/release_notes/3.14.0.txt +0 -118
- data/doc/release_notes/3.15.0.txt +0 -78
- data/doc/release_notes/3.16.0.txt +0 -45
- data/doc/release_notes/3.17.0.txt +0 -58
- data/doc/release_notes/3.18.0.txt +0 -120
- data/doc/release_notes/3.19.0.txt +0 -67
- data/doc/release_notes/3.2.0.txt +0 -268
- data/doc/release_notes/3.20.0.txt +0 -41
- data/doc/release_notes/3.21.0.txt +0 -87
- data/doc/release_notes/3.22.0.txt +0 -39
- data/doc/release_notes/3.23.0.txt +0 -172
- data/doc/release_notes/3.24.0.txt +0 -420
- data/doc/release_notes/3.25.0.txt +0 -88
- data/doc/release_notes/3.26.0.txt +0 -88
- data/doc/release_notes/3.27.0.txt +0 -82
- data/doc/release_notes/3.28.0.txt +0 -304
- data/doc/release_notes/3.29.0.txt +0 -459
- data/doc/release_notes/3.3.0.txt +0 -192
- data/doc/release_notes/3.30.0.txt +0 -135
- data/doc/release_notes/3.31.0.txt +0 -146
- data/doc/release_notes/3.32.0.txt +0 -202
- data/doc/release_notes/3.33.0.txt +0 -157
- data/doc/release_notes/3.34.0.txt +0 -671
- data/doc/release_notes/3.35.0.txt +0 -144
- data/doc/release_notes/3.36.0.txt +0 -245
- data/doc/release_notes/3.37.0.txt +0 -338
- data/doc/release_notes/3.38.0.txt +0 -234
- data/doc/release_notes/3.39.0.txt +0 -237
- data/doc/release_notes/3.4.0.txt +0 -325
- data/doc/release_notes/3.40.0.txt +0 -73
- data/doc/release_notes/3.41.0.txt +0 -155
- data/doc/release_notes/3.42.0.txt +0 -74
- data/doc/release_notes/3.43.0.txt +0 -105
- data/doc/release_notes/3.44.0.txt +0 -152
- data/doc/release_notes/3.45.0.txt +0 -179
- data/doc/release_notes/3.46.0.txt +0 -122
- data/doc/release_notes/3.47.0.txt +0 -270
- data/doc/release_notes/3.48.0.txt +0 -477
- data/doc/release_notes/3.5.0.txt +0 -510
- data/doc/release_notes/3.6.0.txt +0 -366
- data/doc/release_notes/3.7.0.txt +0 -179
- data/doc/release_notes/3.8.0.txt +0 -151
- data/doc/release_notes/3.9.0.txt +0 -233
- data/doc/release_notes/4.0.0.txt +0 -262
- data/doc/release_notes/4.1.0.txt +0 -85
- data/doc/release_notes/4.10.0.txt +0 -226
- data/doc/release_notes/4.11.0.txt +0 -147
- data/doc/release_notes/4.12.0.txt +0 -105
- data/doc/release_notes/4.13.0.txt +0 -169
- data/doc/release_notes/4.14.0.txt +0 -68
- data/doc/release_notes/4.15.0.txt +0 -56
- data/doc/release_notes/4.16.0.txt +0 -36
- data/doc/release_notes/4.17.0.txt +0 -38
- data/doc/release_notes/4.18.0.txt +0 -36
- data/doc/release_notes/4.19.0.txt +0 -45
- data/doc/release_notes/4.2.0.txt +0 -129
- data/doc/release_notes/4.20.0.txt +0 -79
- data/doc/release_notes/4.21.0.txt +0 -94
- data/doc/release_notes/4.22.0.txt +0 -72
- data/doc/release_notes/4.23.0.txt +0 -65
- data/doc/release_notes/4.24.0.txt +0 -99
- data/doc/release_notes/4.25.0.txt +0 -181
- data/doc/release_notes/4.26.0.txt +0 -44
- data/doc/release_notes/4.27.0.txt +0 -78
- data/doc/release_notes/4.28.0.txt +0 -57
- data/doc/release_notes/4.29.0.txt +0 -41
- data/doc/release_notes/4.3.0.txt +0 -40
- data/doc/release_notes/4.30.0.txt +0 -37
- data/doc/release_notes/4.31.0.txt +0 -57
- data/doc/release_notes/4.32.0.txt +0 -132
- data/doc/release_notes/4.33.0.txt +0 -88
- data/doc/release_notes/4.34.0.txt +0 -86
- data/doc/release_notes/4.35.0.txt +0 -130
- data/doc/release_notes/4.36.0.txt +0 -116
- data/doc/release_notes/4.4.0.txt +0 -92
- data/doc/release_notes/4.5.0.txt +0 -34
- data/doc/release_notes/4.6.0.txt +0 -30
- data/doc/release_notes/4.7.0.txt +0 -103
- data/doc/release_notes/4.8.0.txt +0 -175
- data/doc/release_notes/4.9.0.txt +0 -190
- data/lib/sequel/adapters/cubrid.rb +0 -144
- data/lib/sequel/adapters/do/mysql.rb +0 -66
- data/lib/sequel/adapters/do/postgres.rb +0 -44
- data/lib/sequel/adapters/do/sqlite3.rb +0 -42
- data/lib/sequel/adapters/do.rb +0 -158
- data/lib/sequel/adapters/jdbc/as400.rb +0 -84
- data/lib/sequel/adapters/jdbc/cubrid.rb +0 -64
- data/lib/sequel/adapters/jdbc/firebirdsql.rb +0 -36
- data/lib/sequel/adapters/jdbc/informix-sqli.rb +0 -33
- data/lib/sequel/adapters/jdbc/jdbcprogress.rb +0 -33
- data/lib/sequel/adapters/odbc/progress.rb +0 -10
- data/lib/sequel/adapters/shared/cubrid.rb +0 -245
- data/lib/sequel/adapters/shared/firebird.rb +0 -247
- data/lib/sequel/adapters/shared/informix.rb +0 -54
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +0 -152
- data/lib/sequel/adapters/shared/progress.rb +0 -40
- data/lib/sequel/adapters/swift/mysql.rb +0 -49
- data/lib/sequel/adapters/swift/postgres.rb +0 -47
- data/lib/sequel/adapters/swift/sqlite.rb +0 -49
- data/lib/sequel/adapters/swift.rb +0 -160
- data/lib/sequel/adapters/utils/pg_types.rb +0 -70
- data/lib/sequel/dataset/mutation.rb +0 -111
- data/lib/sequel/extensions/empty_array_ignore_nulls.rb +0 -5
- data/lib/sequel/extensions/filter_having.rb +0 -63
- data/lib/sequel/extensions/hash_aliases.rb +0 -49
- data/lib/sequel/extensions/meta_def.rb +0 -35
- data/lib/sequel/extensions/query_literals.rb +0 -84
- data/lib/sequel/extensions/ruby18_symbol_extensions.rb +0 -24
- data/lib/sequel/extensions/sequel_3_dataset_methods.rb +0 -122
- data/lib/sequel/extensions/set_overrides.rb +0 -76
- data/lib/sequel/no_core_ext.rb +0 -3
- data/lib/sequel/plugins/association_autoreloading.rb +0 -9
- data/lib/sequel/plugins/identifier_columns.rb +0 -47
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +0 -9
- data/lib/sequel/plugins/pg_typecast_on_load.rb +0 -81
- data/lib/sequel/plugins/prepared_statements_associations.rb +0 -119
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +0 -61
- data/lib/sequel/plugins/schema.rb +0 -82
- data/lib/sequel/plugins/scissors.rb +0 -35
- data/spec/adapter_spec.rb +0 -4
- data/spec/adapters/db2_spec.rb +0 -160
- data/spec/adapters/firebird_spec.rb +0 -411
- data/spec/adapters/informix_spec.rb +0 -100
- data/spec/adapters/mssql_spec.rb +0 -733
- data/spec/adapters/mysql_spec.rb +0 -1319
- data/spec/adapters/oracle_spec.rb +0 -313
- data/spec/adapters/postgres_spec.rb +0 -3790
- data/spec/adapters/spec_helper.rb +0 -49
- data/spec/adapters/sqlanywhere_spec.rb +0 -170
- data/spec/adapters/sqlite_spec.rb +0 -688
- data/spec/bin_spec.rb +0 -258
- data/spec/core/connection_pool_spec.rb +0 -1045
- data/spec/core/database_spec.rb +0 -2636
- data/spec/core/dataset_spec.rb +0 -5175
- data/spec/core/deprecated_spec.rb +0 -70
- data/spec/core/expression_filters_spec.rb +0 -1247
- data/spec/core/mock_adapter_spec.rb +0 -464
- data/spec/core/object_graph_spec.rb +0 -303
- data/spec/core/placeholder_literalizer_spec.rb +0 -163
- data/spec/core/schema_generator_spec.rb +0 -203
- data/spec/core/schema_spec.rb +0 -1676
- data/spec/core/spec_helper.rb +0 -34
- data/spec/core/version_spec.rb +0 -7
- data/spec/core_extensions_spec.rb +0 -699
- data/spec/core_model_spec.rb +0 -2
- data/spec/core_spec.rb +0 -1
- data/spec/extensions/accessed_columns_spec.rb +0 -51
- data/spec/extensions/active_model_spec.rb +0 -85
- data/spec/extensions/after_initialize_spec.rb +0 -24
- data/spec/extensions/arbitrary_servers_spec.rb +0 -109
- data/spec/extensions/association_dependencies_spec.rb +0 -117
- data/spec/extensions/association_pks_spec.rb +0 -405
- data/spec/extensions/association_proxies_spec.rb +0 -86
- data/spec/extensions/auto_validations_spec.rb +0 -192
- data/spec/extensions/before_after_save_spec.rb +0 -40
- data/spec/extensions/blacklist_security_spec.rb +0 -88
- data/spec/extensions/blank_spec.rb +0 -69
- data/spec/extensions/boolean_readers_spec.rb +0 -93
- data/spec/extensions/boolean_subsets_spec.rb +0 -47
- data/spec/extensions/caching_spec.rb +0 -270
- data/spec/extensions/class_table_inheritance_spec.rb +0 -444
- data/spec/extensions/column_conflicts_spec.rb +0 -60
- data/spec/extensions/column_select_spec.rb +0 -108
- data/spec/extensions/columns_introspection_spec.rb +0 -91
- data/spec/extensions/composition_spec.rb +0 -242
- data/spec/extensions/connection_expiration_spec.rb +0 -121
- data/spec/extensions/connection_validator_spec.rb +0 -127
- data/spec/extensions/constraint_validations_plugin_spec.rb +0 -288
- data/spec/extensions/constraint_validations_spec.rb +0 -389
- data/spec/extensions/core_refinements_spec.rb +0 -519
- data/spec/extensions/csv_serializer_spec.rb +0 -180
- data/spec/extensions/current_datetime_timestamp_spec.rb +0 -27
- data/spec/extensions/dataset_associations_spec.rb +0 -343
- data/spec/extensions/dataset_source_alias_spec.rb +0 -51
- data/spec/extensions/date_arithmetic_spec.rb +0 -167
- data/spec/extensions/defaults_setter_spec.rb +0 -102
- data/spec/extensions/delay_add_association_spec.rb +0 -74
- data/spec/extensions/dirty_spec.rb +0 -180
- data/spec/extensions/duplicate_columns_handler_spec.rb +0 -110
- data/spec/extensions/eager_each_spec.rb +0 -66
- data/spec/extensions/empty_array_consider_nulls_spec.rb +0 -24
- data/spec/extensions/error_splitter_spec.rb +0 -18
- data/spec/extensions/error_sql_spec.rb +0 -20
- data/spec/extensions/eval_inspect_spec.rb +0 -73
- data/spec/extensions/filter_having_spec.rb +0 -40
- data/spec/extensions/force_encoding_spec.rb +0 -114
- data/spec/extensions/from_block_spec.rb +0 -21
- data/spec/extensions/graph_each_spec.rb +0 -119
- data/spec/extensions/hash_aliases_spec.rb +0 -24
- data/spec/extensions/hook_class_methods_spec.rb +0 -429
- data/spec/extensions/identifier_columns_spec.rb +0 -17
- data/spec/extensions/inflector_spec.rb +0 -183
- data/spec/extensions/input_transformer_spec.rb +0 -54
- data/spec/extensions/insert_returning_select_spec.rb +0 -46
- data/spec/extensions/instance_filters_spec.rb +0 -79
- data/spec/extensions/instance_hooks_spec.rb +0 -276
- data/spec/extensions/inverted_subsets_spec.rb +0 -33
- data/spec/extensions/json_serializer_spec.rb +0 -304
- data/spec/extensions/lazy_attributes_spec.rb +0 -170
- data/spec/extensions/list_spec.rb +0 -278
- data/spec/extensions/looser_typecasting_spec.rb +0 -43
- data/spec/extensions/many_through_many_spec.rb +0 -2172
- data/spec/extensions/meta_def_spec.rb +0 -21
- data/spec/extensions/migration_spec.rb +0 -728
- data/spec/extensions/modification_detection_spec.rb +0 -80
- data/spec/extensions/mssql_optimistic_locking_spec.rb +0 -91
- data/spec/extensions/named_timezones_spec.rb +0 -108
- data/spec/extensions/nested_attributes_spec.rb +0 -697
- data/spec/extensions/no_auto_literal_strings_spec.rb +0 -65
- data/spec/extensions/null_dataset_spec.rb +0 -85
- data/spec/extensions/optimistic_locking_spec.rb +0 -128
- data/spec/extensions/pagination_spec.rb +0 -118
- data/spec/extensions/pg_array_associations_spec.rb +0 -736
- data/spec/extensions/pg_array_ops_spec.rb +0 -143
- data/spec/extensions/pg_array_spec.rb +0 -390
- data/spec/extensions/pg_enum_spec.rb +0 -92
- data/spec/extensions/pg_hstore_ops_spec.rb +0 -236
- data/spec/extensions/pg_hstore_spec.rb +0 -206
- data/spec/extensions/pg_inet_ops_spec.rb +0 -101
- data/spec/extensions/pg_inet_spec.rb +0 -52
- data/spec/extensions/pg_interval_spec.rb +0 -76
- data/spec/extensions/pg_json_ops_spec.rb +0 -275
- data/spec/extensions/pg_json_spec.rb +0 -218
- data/spec/extensions/pg_loose_count_spec.rb +0 -17
- data/spec/extensions/pg_range_ops_spec.rb +0 -58
- data/spec/extensions/pg_range_spec.rb +0 -473
- data/spec/extensions/pg_row_ops_spec.rb +0 -60
- data/spec/extensions/pg_row_plugin_spec.rb +0 -62
- data/spec/extensions/pg_row_spec.rb +0 -360
- data/spec/extensions/pg_static_cache_updater_spec.rb +0 -92
- data/spec/extensions/pg_typecast_on_load_spec.rb +0 -63
- data/spec/extensions/prepared_statements_associations_spec.rb +0 -159
- data/spec/extensions/prepared_statements_safe_spec.rb +0 -61
- data/spec/extensions/prepared_statements_spec.rb +0 -103
- data/spec/extensions/prepared_statements_with_pk_spec.rb +0 -31
- data/spec/extensions/pretty_table_spec.rb +0 -92
- data/spec/extensions/query_literals_spec.rb +0 -183
- data/spec/extensions/query_spec.rb +0 -102
- data/spec/extensions/rcte_tree_spec.rb +0 -392
- data/spec/extensions/round_timestamps_spec.rb +0 -43
- data/spec/extensions/schema_caching_spec.rb +0 -41
- data/spec/extensions/schema_dumper_spec.rb +0 -814
- data/spec/extensions/schema_spec.rb +0 -117
- data/spec/extensions/scissors_spec.rb +0 -26
- data/spec/extensions/select_remove_spec.rb +0 -38
- data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -101
- data/spec/extensions/serialization_modification_detection_spec.rb +0 -98
- data/spec/extensions/serialization_spec.rb +0 -362
- data/spec/extensions/server_block_spec.rb +0 -90
- data/spec/extensions/server_logging_spec.rb +0 -45
- data/spec/extensions/set_overrides_spec.rb +0 -61
- data/spec/extensions/sharding_spec.rb +0 -198
- data/spec/extensions/shared_caching_spec.rb +0 -175
- data/spec/extensions/single_table_inheritance_spec.rb +0 -297
- data/spec/extensions/singular_table_names_spec.rb +0 -22
- data/spec/extensions/skip_create_refresh_spec.rb +0 -17
- data/spec/extensions/spec_helper.rb +0 -71
- data/spec/extensions/split_array_nil_spec.rb +0 -24
- data/spec/extensions/split_values_spec.rb +0 -22
- data/spec/extensions/sql_comments_spec.rb +0 -27
- data/spec/extensions/sql_expr_spec.rb +0 -60
- data/spec/extensions/static_cache_spec.rb +0 -361
- data/spec/extensions/string_agg_spec.rb +0 -85
- data/spec/extensions/string_date_time_spec.rb +0 -95
- data/spec/extensions/string_stripper_spec.rb +0 -68
- data/spec/extensions/subclasses_spec.rb +0 -66
- data/spec/extensions/subset_conditions_spec.rb +0 -38
- data/spec/extensions/table_select_spec.rb +0 -71
- data/spec/extensions/tactical_eager_loading_spec.rb +0 -136
- data/spec/extensions/thread_local_timezones_spec.rb +0 -67
- data/spec/extensions/timestamps_spec.rb +0 -175
- data/spec/extensions/to_dot_spec.rb +0 -154
- data/spec/extensions/touch_spec.rb +0 -203
- data/spec/extensions/tree_spec.rb +0 -274
- data/spec/extensions/typecast_on_load_spec.rb +0 -80
- data/spec/extensions/unlimited_update_spec.rb +0 -20
- data/spec/extensions/update_or_create_spec.rb +0 -87
- data/spec/extensions/update_primary_key_spec.rb +0 -100
- data/spec/extensions/update_refresh_spec.rb +0 -53
- data/spec/extensions/uuid_spec.rb +0 -106
- data/spec/extensions/validate_associated_spec.rb +0 -52
- data/spec/extensions/validation_class_methods_spec.rb +0 -1027
- data/spec/extensions/validation_helpers_spec.rb +0 -554
- data/spec/extensions/xml_serializer_spec.rb +0 -207
- data/spec/files/bad_down_migration/001_create_alt_basic.rb +0 -4
- data/spec/files/bad_down_migration/002_create_alt_advanced.rb +0 -4
- data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +0 -9
- data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +0 -9
- data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +0 -3
- data/spec/files/bad_up_migration/001_create_alt_basic.rb +0 -4
- data/spec/files/bad_up_migration/002_create_alt_advanced.rb +0 -3
- data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +0 -9
- data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +0 -9
- data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +0 -4
- data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +0 -9
- data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +0 -9
- data/spec/files/double_migration/001_create_sessions.rb +0 -9
- data/spec/files/double_migration/002_create_nodes.rb +0 -19
- data/spec/files/double_migration/003_3_create_users.rb +0 -4
- data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +0 -4
- data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +0 -4
- data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +0 -9
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +0 -9
- data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +0 -4
- data/spec/files/empty_migration/001_create_sessions.rb +0 -9
- data/spec/files/empty_migration/002_create_nodes.rb +0 -0
- data/spec/files/empty_migration/003_3_create_users.rb +0 -4
- data/spec/files/integer_migrations/001_create_sessions.rb +0 -9
- data/spec/files/integer_migrations/002_create_nodes.rb +0 -9
- data/spec/files/integer_migrations/003_3_create_users.rb +0 -4
- data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +0 -9
- data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +0 -9
- data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +0 -9
- data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +0 -9
- data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +0 -4
- data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +0 -4
- data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +0 -4
- data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +0 -9
- data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +0 -4
- data/spec/files/reversible_migrations/001_reversible.rb +0 -5
- data/spec/files/reversible_migrations/002_reversible.rb +0 -5
- data/spec/files/reversible_migrations/003_reversible.rb +0 -5
- data/spec/files/reversible_migrations/004_reversible.rb +0 -5
- data/spec/files/reversible_migrations/005_reversible.rb +0 -10
- data/spec/files/reversible_migrations/006_reversible.rb +0 -10
- data/spec/files/reversible_migrations/007_reversible.rb +0 -10
- data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +0 -9
- data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +0 -9
- data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +0 -4
- data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +0 -4
- data/spec/files/transaction_specified_migrations/002_create_basic.rb +0 -4
- data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +0 -3
- data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +0 -3
- data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +0 -9
- data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +0 -9
- data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +0 -4
- data/spec/guards_helper.rb +0 -55
- data/spec/integration/associations_test.rb +0 -2506
- data/spec/integration/database_test.rb +0 -113
- data/spec/integration/dataset_test.rb +0 -1858
- data/spec/integration/eager_loader_test.rb +0 -687
- data/spec/integration/migrator_test.rb +0 -262
- data/spec/integration/model_test.rb +0 -230
- data/spec/integration/plugin_test.rb +0 -2297
- data/spec/integration/prepared_statement_test.rb +0 -467
- data/spec/integration/schema_test.rb +0 -815
- data/spec/integration/spec_helper.rb +0 -56
- data/spec/integration/timezone_test.rb +0 -86
- data/spec/integration/transaction_test.rb +0 -406
- data/spec/integration/type_test.rb +0 -133
- data/spec/model/association_reflection_spec.rb +0 -565
- data/spec/model/associations_spec.rb +0 -4589
- data/spec/model/base_spec.rb +0 -759
- data/spec/model/class_dataset_methods_spec.rb +0 -150
- data/spec/model/dataset_methods_spec.rb +0 -149
- data/spec/model/eager_loading_spec.rb +0 -2197
- data/spec/model/hooks_spec.rb +0 -604
- data/spec/model/inflector_spec.rb +0 -26
- data/spec/model/model_spec.rb +0 -1097
- data/spec/model/plugins_spec.rb +0 -299
- data/spec/model/record_spec.rb +0 -2162
- data/spec/model/spec_helper.rb +0 -46
- data/spec/model/validations_spec.rb +0 -193
- data/spec/model_no_assoc_spec.rb +0 -1
- data/spec/model_spec.rb +0 -1
- data/spec/plugin_spec.rb +0 -1
- data/spec/sequel_coverage.rb +0 -15
- 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
|