Module: TemporalTables::TemporalAdapter

Defined in:
lib/temporal_tables/temporal_adapter.rb

Overview

rubocop:disable Metrics/ModuleLength

Instance Method Summary collapse

Instance Method Details

#add_column(table_name, column_name, type, **options) ⇒ Object



96
97
98
99
100
101
102
103
# File 'lib/temporal_tables/temporal_adapter.rb', line 96

def add_column(table_name, column_name, type, **options)
  super(table_name, column_name, type, **options)

  return unless table_exists?(temporal_name(table_name))

  super temporal_name(table_name), column_name, type, **options
  create_temporal_triggers table_name, original_primary_key(table_name)
end

#add_index(table_name, column_name, **options) ⇒ Object



141
142
143
144
145
146
147
148
# File 'lib/temporal_tables/temporal_adapter.rb', line 141

def add_index(table_name, column_name, **options)
  super(table_name, column_name, **options)

  return unless table_exists?(temporal_name(table_name))

  idx_name = temporal_index_name(options[:name] || index_name(table_name, column: column_name))
  super temporal_name(table_name), column_name, **options.except(:unique).merge(name: idx_name)
end

#add_temporal_table(table_name, **options) ⇒ Object

rubocop:disable Metrics/MethodLength, Metrics/AbcSize



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/temporal_tables/temporal_adapter.rb', line 32

def add_temporal_table(table_name, **options) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
  create_table(
    temporal_name(table_name),
    **options.merge(id: false, primary_key: 'history_id', temporal_bypass: true)
  ) do |t|
    t.datetime :eff_from, null: false, limit: 6
    t.datetime :eff_to,   null: false, limit: 6, default: TemporalTables::END_OF_TIME

    columns(table_name).each do |c|
      column_options = { limit: c.limit }
      column_type = c.type
      if column_type == :enum
        enum_type = c..sql_type
        if t.respond_to?(:enum)
          column_options[:enum_type] = enum_type
        else
          column_type = enum_type
        end
      end

      t.column c.name, column_type, **column_options
    end
  end

  if TemporalTables.add_updated_by_field && !column_exists?(table_name, :updated_by)
    change_table table_name do |t|
      t.column :updated_by, TemporalTables.updated_by_type
    end
  end

  original_primary_key = original_primary_key(table_name)
  temporal_table_index_name = index_name(temporal_name(table_name), [original_primary_key, :eff_to])
  if temporal_table_index_name.length > index_name_length
    temporal_table_index_name = truncated_index_name(temporal_table_index_name)
  end
  add_index temporal_name(table_name), [original_primary_key, :eff_to], name: temporal_table_index_name
  create_temporal_triggers table_name, original_primary_key
  create_temporal_indexes table_name
end

#change_column(table_name, column_name, type, **options) ⇒ Object



123
124
125
126
127
128
129
130
# File 'lib/temporal_tables/temporal_adapter.rb', line 123

def change_column(table_name, column_name, type, **options)
  super(table_name, column_name, type, **options)

  return unless table_exists?(temporal_name(table_name))

  super temporal_name(table_name), column_name, type, **options
  # Don't need to update triggers here...
end

#create_table(table_name, **options, &block) ⇒ Object

rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/temporal_tables/temporal_adapter.rb', line 7

def create_table(table_name, **options, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
  if options[:temporal_bypass]
    super(table_name, **options, &block)
  else
    skip_table = TemporalTables.skipped_temporal_tables.include?(table_name.to_sym) || table_name.to_s =~ /_h$/

    super(table_name, **options) do |t|
      block.call t

      if TemporalTables.add_updated_by_field && !skip_table
        updated_by_already_exists = t.columns.any? { |c| c.name == 'updated_by' }
        if updated_by_already_exists
          puts "consider adding #{table_name} to TemporalTables skip_table" # rubocop:disable Rails/Output
        else
          t.column(:updated_by, TemporalTables.updated_by_type)
        end
      end
    end

    if options[:temporal] || (TemporalTables.create_by_default && !skip_table)
      add_temporal_table table_name, **options
    end
  end
end

#create_temporal_indexes(table_name) ⇒ Object

rubocop:disable Metrics/MethodLength



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/temporal_tables/temporal_adapter.rb', line 160

def create_temporal_indexes(table_name) # rubocop:disable Metrics/MethodLength
  indexes = ActiveRecord::Base.connection.indexes(table_name)

  indexes.each do |index|
    index_name = temporal_index_name(index.name)

    next if temporal_index_exists?(table_name, index_name)

    add_index(
      temporal_name(table_name),
      index.columns,
      # exclude unique constraints for temporal tables
      name: index_name,
      length: index.lengths,
      order: index.orders
    )
  end
end

#create_temporal_triggers(_table_name) ⇒ Object

Raises:

  • (NotImplementedError)


183
184
185
# File 'lib/temporal_tables/temporal_adapter.rb', line 183

def create_temporal_triggers(_table_name)
  raise NotImplementedError, 'create_temporal_triggers is not implemented'
end

#drop_table(table_name, **options) ⇒ Object



79
80
81
82
83
# File 'lib/temporal_tables/temporal_adapter.rb', line 79

def drop_table(table_name, **options)
  super(table_name, **options)

  super(temporal_name(table_name), **options) if table_exists?(temporal_name(table_name))
end

#drop_temporal_triggers(_table_name) ⇒ Object

Raises:

  • (NotImplementedError)


187
188
189
# File 'lib/temporal_tables/temporal_adapter.rb', line 187

def drop_temporal_triggers(_table_name)
  raise NotImplementedError, 'drop_temporal_triggers is not implemented'
end

#original_primary_key(table_name) ⇒ Object



207
208
209
210
211
212
# File 'lib/temporal_tables/temporal_adapter.rb', line 207

def original_primary_key(table_name)
  original_primary_key = primary_key(table_name)
  raise 'temporal_adapter requires that the table has a single primary key' unless original_primary_key.is_a? String

  original_primary_key
end

#remove_column(table_name, column_name, type = nil, **options) ⇒ Object



114
115
116
117
118
119
120
121
# File 'lib/temporal_tables/temporal_adapter.rb', line 114

def remove_column(table_name, column_name, type = nil, **options)
  super(table_name, column_name, type, **options)

  return unless table_exists?(temporal_name(table_name))

  super temporal_name(table_name), column_name, type, **options
  create_temporal_triggers table_name, original_primary_key(table_name)
end

#remove_columns(table_name, *column_names, **options) ⇒ Object



105
106
107
108
109
110
111
112
# File 'lib/temporal_tables/temporal_adapter.rb', line 105

def remove_columns(table_name, *column_names, **options)
  super(table_name, *column_names, **options)

  return unless table_exists?(temporal_name(table_name))

  super temporal_name(table_name), *column_names, **options
  create_temporal_triggers table_name, original_primary_key(table_name)
end

#remove_index(table_name, column_name = nil, **options) ⇒ Object



150
151
152
153
154
155
156
157
158
# File 'lib/temporal_tables/temporal_adapter.rb', line 150

def remove_index(table_name, column_name = nil, **options)
  original_index_name = index_name_for_remove(table_name, column_name, options)
  super(table_name, column_name, **options)

  return unless table_exists?(temporal_name(table_name))

  idx_name = temporal_index_name(options[:name] || original_index_name)
  super temporal_name(table_name), column_name, name: idx_name
end

#remove_temporal_table(table_name) ⇒ Object



72
73
74
75
76
77
# File 'lib/temporal_tables/temporal_adapter.rb', line 72

def remove_temporal_table(table_name)
  return unless table_exists?(temporal_name(table_name))

  drop_temporal_triggers table_name
  drop_table_without_temporal temporal_name(table_name)
end

#rename_column(table_name, column_name, new_column_name) ⇒ Object



132
133
134
135
136
137
138
139
# File 'lib/temporal_tables/temporal_adapter.rb', line 132

def rename_column(table_name, column_name, new_column_name)
  super(table_name, column_name, new_column_name)

  return unless table_exists?(temporal_name(table_name))

  super temporal_name(table_name), column_name, new_column_name
  create_temporal_triggers table_name, original_primary_key(table_name)
end

#rename_table(name, new_name) ⇒ Object



85
86
87
88
89
90
91
92
93
94
# File 'lib/temporal_tables/temporal_adapter.rb', line 85

def rename_table(name, new_name)
  drop_temporal_triggers name if table_exists?(temporal_name(name))

  super name, new_name

  return unless table_exists?(temporal_name(name))

  super(temporal_name(name), temporal_name(new_name))
  create_temporal_triggers new_name, original_primary_key(table_name)
end

#temporal_index_exists?(table_name, index_name) ⇒ Boolean

Returns:

  • (Boolean)


203
204
205
# File 'lib/temporal_tables/temporal_adapter.rb', line 203

def temporal_index_exists?(table_name, index_name)
  index_name_exists?(temporal_name(table_name), index_name)
end

#temporal_index_name(index_name) ⇒ Object

Index names max out at 63 characters. If appending _h to the index name would surpass that limit, we can trim the index name and append a deterministically generated 5 character hash as well as _h.



193
194
195
# File 'lib/temporal_tables/temporal_adapter.rb', line 193

def temporal_index_name(index_name)
  "#{index_name.length < 62 ? index_name : truncated_index_name(index_name, 2)}_h"
end

#temporal_name(table_name) ⇒ Object



179
180
181
# File 'lib/temporal_tables/temporal_adapter.rb', line 179

def temporal_name(table_name)
  "#{table_name}_h"
end

#truncated_index_name(index_name, required_padding = 0) ⇒ Object



197
198
199
200
201
# File 'lib/temporal_tables/temporal_adapter.rb', line 197

def truncated_index_name(index_name, required_padding = 0)
  max_length = index_name_length - required_padding
  index_name_hash = Digest::SHA1.base64digest(index_name.to_s)[0, 5]
  "#{index_name[0, max_length - 6]}_#{index_name_hash}"
end