require "set" module ActiveRecord module ConnectionAdapters # A module for mapping CipherStash internal columns to columns that ActiveRecord can work with. # # @private class CipherStashColumnMapper ENCRYPTED_INDEX_COLUMN_REGEX = /^__.+_(match|ore|unique)$/ ENCRYPTED_SOURCE_COLUMN_REGEX = /^__(.+)_encrypted$/ class << self # Maps the given column definitions by filtering out CipherStash internal columns. This method # also adds in plaintext columns if necessary (for example, when using the driver in "encrypted" # mode after the original plaintext columns have been dropped). def map_column_definitions(column_definitions) all_column_names = column_definitions.map(&:first).to_set column_definitions .reject { |column_definition| encrypted_index_column?(column_definition) } .map { |column_definition| maybe_rename_source_column(column_definition, all_column_names) } .reject { |column_definition| encrypted_source_column?(column_definition) } end private def encrypted_index_column?(column_definition) ENCRYPTED_INDEX_COLUMN_REGEX =~ column_name(column_definition) end def encrypted_source_column?(column_definition) ENCRYPTED_SOURCE_COLUMN_REGEX =~ column_name(column_definition) end def column_name(column_definition) column_definition.first end def maybe_rename_source_column(column_definition, all_column_names) column_name, *rest = column_definition match = column_name.match(ENCRYPTED_SOURCE_COLUMN_REGEX) if match && !all_column_names.include?(match.captures.first) [match.captures.first, *rest] else column_definition end end end end end end