Module: ActiveRecord::Temporal::SystemVersioning::SchemaStatements

Defined in:
lib/activerecord/temporal/system_versioning/schema_statements.rb

Instance Method Summary collapse

Instance Method Details

#change_versioning_hook(source_table, history_table, options) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/activerecord/temporal/system_versioning/schema_statements.rb', line 81

def change_versioning_hook(source_table, history_table, options)
  options.assert_valid_keys(:add_columns, :remove_columns)

  add_columns = (options[:add_columns] || []).map(&:to_s)
  remove_columns = (options[:remove_columns] || []).map(&:to_s)

  assert_table_exists!(source_table)
  assert_table_exists!(history_table)
  assert_columns_match!(source_table, history_table, add_columns)

  hook_definition = versioning_hook(source_table)

  assert_hook_has_columns!(hook_definition, remove_columns)

  drop_versioning_hook(source_table, history_table)

  new_columns = hook_definition.columns + add_columns - remove_columns

  create_versioning_hook source_table,
    history_table,
    columns: new_columns,
    primary_key: hook_definition.primary_key
end

#create_versioning_hook(source_table, history_table, **options) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/activerecord/temporal/system_versioning/schema_statements.rb', line 4

def create_versioning_hook(source_table, history_table, **options)
  options.assert_valid_keys(:columns, :primary_key)

  columns = options.fetch(:columns, :all)
  primary_key = options.fetch(:primary_key, :id)

  column_names = if columns == :all
    columns(source_table).map(&:name)
  else
    Array(columns).map(&:to_s)
  end

  primary_key = if primary_key.is_a?(Array) && primary_key.length == 1
    primary_key.first
  else
    primary_key
  end

  assert_table_exists!(source_table)
  assert_table_exists!(history_table)
  assert_columns_match!(source_table, history_table, column_names)
  assert_columns_exists!(source_table, Array(primary_key))
  assert_primary_key_matches!(source_table, Array(primary_key))

  schema_creation = SchemaCreation.new(self)

  hook_definition = VersioningHookDefinition.new(
    source_table,
    history_table,
    columns: column_names,
    primary_key: primary_key,
    gem_version: VERSION
  )

  execute schema_creation.accept(hook_definition)
end

#drop_versioning_hook(source_table, history_table, **options) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/activerecord/temporal/system_versioning/schema_statements.rb', line 41

def drop_versioning_hook(source_table, history_table, **options)
  options.assert_valid_keys(:columns, :primary_key, :if_exists)

  %i[insert update delete].each do |verb|
    function_name = versioning_function_name(source_table, verb)

    sql = "DROP FUNCTION"
    sql << " IF EXISTS" if options[:if_exists]
    sql << " #{function_name}() CASCADE"

    execute sql
  end
end

#history_table(source_table) ⇒ Object



105
106
107
108
109
# File 'lib/activerecord/temporal/system_versioning/schema_statements.rb', line 105

def history_table(source_table)
  hook_definition = versioning_hook(source_table)

  hook_definition&.history_table
end

#versioning_function_name(source_table, verb) ⇒ Object



111
112
113
114
115
116
# File 'lib/activerecord/temporal/system_versioning/schema_statements.rb', line 111

def versioning_function_name(source_table, verb)
  identifier = "#{source_table}_#{verb}"
  hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)

  "sys_ver_func_#{hashed_identifier}"
end

#versioning_hook(source_table) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/activerecord/temporal/system_versioning/schema_statements.rb', line 55

def versioning_hook(source_table)
  update_function_name = versioning_function_name(source_table, :update)

  row = exec_query(<<~SQL.squish, "SQL", [update_function_name]).first
    SELECT
      pg_proc.proname as function_name,
      obj_description(pg_proc.oid, 'pg_proc') as comment
    FROM pg_proc
    JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
    WHERE pg_namespace.nspname NOT IN ('pg_catalog', 'information_schema')
      AND pg_proc.proname = $1
  SQL

  return unless row

   = JSON.parse(row["comment"])

  VersioningHookDefinition.new(
    ["source_table"],
    ["history_table"],
    columns: ["columns"],
    primary_key: ["primary_key"],
    gem_version: ["gem_version"]
  )
end