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



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

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



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

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



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
71
# File 'lib/temporal_tables/temporal_adapter.rb', line 33

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



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

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
31
# 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
  valid_options = options.except(:temporal, :temporal_bypass)
  if options[:temporal_bypass]
    super(table_name, **valid_options, &block)
  else
    skip_table = TemporalTables.skipped_temporal_tables.include?(table_name.to_sym) || table_name.to_s =~ /_h$/

    super(table_name, **valid_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



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

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)


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

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

#drop_table(table_name, **options) ⇒ Object



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

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)


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

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

#original_primary_key(table_name) ⇒ Object



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

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



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

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



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

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



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

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



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

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



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

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



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

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)


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

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.



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

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



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

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

#truncated_index_name(index_name, required_padding = 0) ⇒ Object



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

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