chrono_model 1.2.2 → 2.0.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 +4 -4
- data/LICENSE +19 -20
- data/README.md +62 -40
- data/lib/active_record/connection_adapters/chronomodel_adapter.rb +17 -11
- data/lib/active_record/tasks/chronomodel_database_tasks.rb +64 -23
- data/lib/chrono_model/adapter/ddl.rb +168 -153
- data/lib/chrono_model/adapter/indexes.rb +99 -94
- data/lib/chrono_model/adapter/migrations.rb +81 -104
- data/lib/chrono_model/adapter/migrations_modules/legacy.rb +41 -0
- data/lib/chrono_model/adapter/migrations_modules/stable.rb +41 -0
- data/lib/chrono_model/adapter/tsrange.rb +20 -5
- data/lib/chrono_model/adapter/upgrade.rb +89 -91
- data/lib/chrono_model/adapter.rb +64 -31
- data/lib/chrono_model/chrono.rb +17 -0
- data/lib/chrono_model/conversions.rb +15 -9
- data/lib/chrono_model/db_console.rb +9 -0
- data/lib/chrono_model/json.rb +9 -6
- data/lib/chrono_model/patches/as_of_time_holder.rb +2 -2
- data/lib/chrono_model/patches/as_of_time_relation.rb +2 -2
- data/lib/chrono_model/patches/association.rb +15 -12
- data/lib/chrono_model/patches/batches.rb +17 -0
- data/lib/chrono_model/patches/db_console.rb +20 -4
- data/lib/chrono_model/patches/join_node.rb +4 -4
- data/lib/chrono_model/patches/preloader.rb +41 -11
- data/lib/chrono_model/patches/relation.rb +53 -8
- data/lib/chrono_model/patches.rb +3 -1
- data/lib/chrono_model/railtie.rb +29 -24
- data/lib/chrono_model/time_gate.rb +3 -3
- data/lib/chrono_model/time_machine/history_model.rb +65 -31
- data/lib/chrono_model/time_machine/time_query.rb +65 -49
- data/lib/chrono_model/time_machine/timeline.rb +52 -28
- data/lib/chrono_model/time_machine.rb +66 -25
- data/lib/chrono_model/utilities.rb +3 -3
- data/lib/chrono_model/version.rb +3 -1
- data/lib/chrono_model.rb +31 -36
- metadata +39 -136
- data/.gitignore +0 -21
- data/.rspec +0 -2
- data/.travis.yml +0 -41
- data/Gemfile +0 -4
- data/README.sql +0 -161
- data/Rakefile +0 -25
- data/chrono_model.gemspec +0 -33
- data/gemfiles/rails_5.0.gemfile +0 -6
- data/gemfiles/rails_5.1.gemfile +0 -6
- data/gemfiles/rails_5.2.gemfile +0 -6
- data/spec/aruba/dbconsole_spec.rb +0 -25
- data/spec/aruba/fixtures/database_with_default_username_and_password.yml +0 -14
- data/spec/aruba/fixtures/database_without_username_and_password.yml +0 -11
- data/spec/aruba/fixtures/empty_structure.sql +0 -27
- data/spec/aruba/fixtures/migrations/56/20160812190335_create_impressions.rb +0 -10
- data/spec/aruba/fixtures/migrations/56/20171115195229_add_temporal_extension_to_impressions.rb +0 -10
- data/spec/aruba/fixtures/railsapp/config/application.rb +0 -17
- data/spec/aruba/fixtures/railsapp/config/boot.rb +0 -5
- data/spec/aruba/fixtures/railsapp/config/environments/development.rb +0 -38
- data/spec/aruba/migrations_spec.rb +0 -48
- data/spec/aruba/rake_task_spec.rb +0 -71
- data/spec/chrono_model/adapter/base_spec.rb +0 -157
- data/spec/chrono_model/adapter/ddl_spec.rb +0 -243
- data/spec/chrono_model/adapter/indexes_spec.rb +0 -72
- data/spec/chrono_model/adapter/migrations_spec.rb +0 -312
- data/spec/chrono_model/conversions_spec.rb +0 -43
- data/spec/chrono_model/history_models_spec.rb +0 -32
- data/spec/chrono_model/json_ops_spec.rb +0 -59
- data/spec/chrono_model/time_machine/as_of_spec.rb +0 -188
- data/spec/chrono_model/time_machine/changes_spec.rb +0 -50
- data/spec/chrono_model/time_machine/counter_cache_race_spec.rb +0 -46
- data/spec/chrono_model/time_machine/default_scope_spec.rb +0 -37
- data/spec/chrono_model/time_machine/history_spec.rb +0 -104
- data/spec/chrono_model/time_machine/keep_cool_spec.rb +0 -27
- data/spec/chrono_model/time_machine/manipulations_spec.rb +0 -84
- data/spec/chrono_model/time_machine/model_identification_spec.rb +0 -46
- data/spec/chrono_model/time_machine/sequence_spec.rb +0 -74
- data/spec/chrono_model/time_machine/sti_spec.rb +0 -100
- data/spec/chrono_model/time_machine/time_query_spec.rb +0 -261
- data/spec/chrono_model/time_machine/timeline_spec.rb +0 -63
- data/spec/chrono_model/time_machine/timestamps_spec.rb +0 -43
- data/spec/chrono_model/time_machine/transactions_spec.rb +0 -69
- data/spec/config.travis.yml +0 -5
- data/spec/config.yml.example +0 -9
- data/spec/spec_helper.rb +0 -33
- data/spec/support/adapter/helpers.rb +0 -53
- data/spec/support/adapter/structure.rb +0 -44
- data/spec/support/aruba.rb +0 -44
- data/spec/support/connection.rb +0 -70
- data/spec/support/matchers/base.rb +0 -56
- data/spec/support/matchers/column.rb +0 -99
- data/spec/support/matchers/function.rb +0 -79
- data/spec/support/matchers/index.rb +0 -69
- data/spec/support/matchers/schema.rb +0 -39
- data/spec/support/matchers/table.rb +0 -275
- data/spec/support/time_machine/helpers.rb +0 -47
- data/spec/support/time_machine/structure.rb +0 -111
- data/sql/json_ops.sql +0 -56
- data/sql/uninstall-json_ops.sql +0 -24
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ChronoModel
|
|
2
4
|
class Adapter < ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
|
3
|
-
|
|
4
5
|
module Migrations
|
|
5
6
|
# Creates the given table, possibly creating the temporal schema
|
|
6
7
|
# objects if the `:temporal` option is given and set to true.
|
|
7
8
|
#
|
|
8
|
-
def create_table(table_name, options
|
|
9
|
+
def create_table(table_name, **options)
|
|
9
10
|
# No temporal features requested, skip
|
|
10
11
|
return super unless options[:temporal]
|
|
11
12
|
|
|
12
13
|
if options[:id] == false
|
|
13
|
-
logger.warn
|
|
14
|
+
logger.warn 'ChronoModel: Temporal Temporal tables require a primary key.'
|
|
14
15
|
logger.warn "ChronoModel: Adding a `__chrono_id' primary key to #{table_name} definition."
|
|
15
16
|
|
|
16
17
|
options[:id] = '__chrono_id'
|
|
@@ -26,8 +27,12 @@ module ChronoModel
|
|
|
26
27
|
|
|
27
28
|
# If renaming a temporal table, rename the history and view as well.
|
|
28
29
|
#
|
|
29
|
-
def rename_table(name, new_name)
|
|
30
|
-
|
|
30
|
+
def rename_table(name, new_name, **options)
|
|
31
|
+
unless is_chrono?(name)
|
|
32
|
+
return super(name, new_name) if method(:rename_table).super_method.arity == 2
|
|
33
|
+
|
|
34
|
+
return super
|
|
35
|
+
end
|
|
31
36
|
|
|
32
37
|
clear_cache!
|
|
33
38
|
|
|
@@ -63,38 +68,35 @@ module ChronoModel
|
|
|
63
68
|
# features on the given table. Please note that you'll lose your history
|
|
64
69
|
# when demoting a temporal table to a plain one.
|
|
65
70
|
#
|
|
66
|
-
def change_table(table_name, options
|
|
71
|
+
def change_table(table_name, **options, &block)
|
|
67
72
|
transaction do
|
|
68
|
-
|
|
69
73
|
# Add an empty proc to support calling change_table without a block.
|
|
70
74
|
#
|
|
71
|
-
block ||= proc {
|
|
75
|
+
block ||= proc {}
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if !is_chrono?(table_name)
|
|
77
|
+
if options[:temporal]
|
|
78
|
+
unless is_chrono?(table_name)
|
|
76
79
|
chrono_make_temporal_table(table_name, options)
|
|
77
80
|
end
|
|
78
81
|
|
|
79
82
|
drop_and_recreate_public_view(table_name, options) do
|
|
80
|
-
super
|
|
83
|
+
super(table_name, **options, &block)
|
|
81
84
|
end
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
else
|
|
84
87
|
if is_chrono?(table_name)
|
|
85
88
|
chrono_undo_temporal_table(table_name)
|
|
86
89
|
end
|
|
87
90
|
|
|
88
|
-
super
|
|
91
|
+
super(table_name, **options, &block)
|
|
89
92
|
end
|
|
90
|
-
|
|
91
93
|
end
|
|
92
94
|
end
|
|
93
95
|
|
|
94
96
|
# If dropping a temporal table, drops it from the temporal schema
|
|
95
97
|
# adding the CASCADE option so to delete the history, view and triggers.
|
|
96
98
|
#
|
|
97
|
-
def drop_table(table_name,
|
|
99
|
+
def drop_table(table_name, **options)
|
|
98
100
|
return super unless is_chrono?(table_name)
|
|
99
101
|
|
|
100
102
|
on_temporal_schema { execute "DROP TABLE #{table_name} CASCADE" }
|
|
@@ -102,39 +104,10 @@ module ChronoModel
|
|
|
102
104
|
chrono_drop_trigger_functions_for(table_name)
|
|
103
105
|
end
|
|
104
106
|
|
|
105
|
-
# If adding an index to a temporal table, add it to the one in the
|
|
106
|
-
# temporal schema and to the history one. If the `:unique` option is
|
|
107
|
-
# present, it is removed from the index created in the history table.
|
|
108
|
-
#
|
|
109
|
-
def add_index(table_name, column_name, options = {})
|
|
110
|
-
return super unless is_chrono?(table_name)
|
|
111
|
-
|
|
112
|
-
transaction do
|
|
113
|
-
on_temporal_schema { super }
|
|
114
|
-
|
|
115
|
-
# Uniqueness constraints do not make sense in the history table
|
|
116
|
-
options = options.dup.tap {|o| o.delete(:unique)} if options[:unique].present?
|
|
117
|
-
|
|
118
|
-
on_history_schema { super table_name, column_name, options }
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# If removing an index from a temporal table, remove it both from the
|
|
123
|
-
# temporal and the history schemas.
|
|
124
|
-
#
|
|
125
|
-
def remove_index(table_name, *)
|
|
126
|
-
return super unless is_chrono?(table_name)
|
|
127
|
-
|
|
128
|
-
transaction do
|
|
129
|
-
on_temporal_schema { super }
|
|
130
|
-
on_history_schema { super }
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
107
|
# If adding a column to a temporal table, creates it in the table in
|
|
135
108
|
# the temporal schema and updates the triggers.
|
|
136
109
|
#
|
|
137
|
-
def add_column(table_name,
|
|
110
|
+
def add_column(table_name, column_name, type, **options)
|
|
138
111
|
return super unless is_chrono?(table_name)
|
|
139
112
|
|
|
140
113
|
transaction do
|
|
@@ -166,8 +139,9 @@ module ChronoModel
|
|
|
166
139
|
# view, then change the column from the table in the temporal schema and
|
|
167
140
|
# eventually recreate the triggers.
|
|
168
141
|
#
|
|
169
|
-
def change_column(table_name,
|
|
142
|
+
def change_column(table_name, column_name, type, **options)
|
|
170
143
|
return super unless is_chrono?(table_name)
|
|
144
|
+
|
|
171
145
|
drop_and_recreate_public_view(table_name) { super }
|
|
172
146
|
end
|
|
173
147
|
|
|
@@ -175,6 +149,7 @@ module ChronoModel
|
|
|
175
149
|
#
|
|
176
150
|
def change_column_default(table_name, *)
|
|
177
151
|
return super unless is_chrono?(table_name)
|
|
152
|
+
|
|
178
153
|
on_temporal_schema { super }
|
|
179
154
|
end
|
|
180
155
|
|
|
@@ -182,6 +157,7 @@ module ChronoModel
|
|
|
182
157
|
#
|
|
183
158
|
def change_column_null(table_name, *)
|
|
184
159
|
return super unless is_chrono?(table_name)
|
|
160
|
+
|
|
185
161
|
on_temporal_schema { super }
|
|
186
162
|
end
|
|
187
163
|
|
|
@@ -189,54 +165,56 @@ module ChronoModel
|
|
|
189
165
|
# view, then drop the column from the table in the temporal schema and
|
|
190
166
|
# eventually recreate the triggers.
|
|
191
167
|
#
|
|
192
|
-
def remove_column(table_name,
|
|
168
|
+
def remove_column(table_name, column_name, type = nil, **options)
|
|
193
169
|
return super unless is_chrono?(table_name)
|
|
170
|
+
|
|
194
171
|
drop_and_recreate_public_view(table_name) { super }
|
|
195
172
|
end
|
|
196
173
|
|
|
197
174
|
private
|
|
198
|
-
# In destructive changes, such as removing columns or changing column
|
|
199
|
-
# types, the view must be dropped and recreated, while the change has
|
|
200
|
-
# to be applied to the table in the temporal schema.
|
|
201
|
-
#
|
|
202
|
-
def drop_and_recreate_public_view(table_name, opts = {})
|
|
203
|
-
transaction do
|
|
204
|
-
options = chrono_metadata_for(table_name).merge(opts)
|
|
205
|
-
|
|
206
|
-
execute "DROP VIEW #{table_name}"
|
|
207
175
|
|
|
208
|
-
|
|
176
|
+
# In destructive changes, such as removing columns or changing column
|
|
177
|
+
# types, the view must be dropped and recreated, while the change has
|
|
178
|
+
# to be applied to the table in the temporal schema.
|
|
179
|
+
#
|
|
180
|
+
def drop_and_recreate_public_view(table_name, opts = {}, &block)
|
|
181
|
+
transaction do
|
|
182
|
+
options = chrono_metadata_for(table_name).merge(opts)
|
|
209
183
|
|
|
210
|
-
|
|
211
|
-
chrono_public_view_ddl(table_name, options)
|
|
212
|
-
end
|
|
213
|
-
end
|
|
184
|
+
execute "DROP VIEW #{table_name}"
|
|
214
185
|
|
|
215
|
-
|
|
216
|
-
# Add temporal features to this table
|
|
217
|
-
#
|
|
218
|
-
if !primary_key(table_name)
|
|
219
|
-
execute "ALTER TABLE #{table_name} ADD __chrono_id SERIAL PRIMARY KEY"
|
|
220
|
-
end
|
|
186
|
+
on_temporal_schema(&block)
|
|
221
187
|
|
|
222
|
-
|
|
223
|
-
on_history_schema { chrono_history_table_ddl(table_name) }
|
|
188
|
+
# Recreate the triggers
|
|
224
189
|
chrono_public_view_ddl(table_name, options)
|
|
225
|
-
|
|
190
|
+
end
|
|
191
|
+
end
|
|
226
192
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
end
|
|
193
|
+
def chrono_make_temporal_table(table_name, options)
|
|
194
|
+
# Add temporal features to this table
|
|
195
|
+
#
|
|
196
|
+
unless primary_key(table_name)
|
|
197
|
+
execute "ALTER TABLE #{table_name} ADD __chrono_id SERIAL PRIMARY KEY"
|
|
233
198
|
end
|
|
234
199
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
200
|
+
execute "ALTER TABLE #{table_name} SET SCHEMA #{TEMPORAL_SCHEMA}"
|
|
201
|
+
on_history_schema { chrono_history_table_ddl(table_name) }
|
|
202
|
+
chrono_public_view_ddl(table_name, options)
|
|
203
|
+
chrono_copy_indexes_to_history(table_name)
|
|
204
|
+
|
|
205
|
+
# Optionally copy the plain table data, setting up history
|
|
206
|
+
# retroactively.
|
|
207
|
+
#
|
|
208
|
+
return unless options[:copy_data]
|
|
209
|
+
|
|
210
|
+
chrono_copy_temporal_to_history(table_name, options)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def chrono_copy_temporal_to_history(table_name, options)
|
|
214
|
+
seq = on_history_schema { pk_and_sequence_for(table_name).last.to_s }
|
|
215
|
+
from = options[:validity] || '0001-01-01 00:00:00'
|
|
238
216
|
|
|
239
|
-
|
|
217
|
+
execute %[
|
|
240
218
|
INSERT INTO #{HISTORY_SCHEMA}.#{table_name}
|
|
241
219
|
SELECT *,
|
|
242
220
|
nextval('#{seq}') AS hid,
|
|
@@ -244,39 +222,38 @@ module ChronoModel
|
|
|
244
222
|
timezone('UTC', now()) AS recorded_at
|
|
245
223
|
FROM #{TEMPORAL_SCHEMA}.#{table_name}
|
|
246
224
|
]
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
# Removes temporal features from this table
|
|
250
|
-
#
|
|
251
|
-
def chrono_undo_temporal_table(table_name)
|
|
252
|
-
execute "DROP VIEW #{table_name}"
|
|
225
|
+
end
|
|
253
226
|
|
|
254
|
-
|
|
227
|
+
# Removes temporal features from this table
|
|
228
|
+
#
|
|
229
|
+
def chrono_undo_temporal_table(table_name)
|
|
230
|
+
execute "DROP VIEW #{table_name}"
|
|
255
231
|
|
|
256
|
-
|
|
232
|
+
chrono_drop_trigger_functions_for(table_name)
|
|
257
233
|
|
|
258
|
-
|
|
259
|
-
on_temporal_schema do
|
|
260
|
-
if primary_key(table_name) == '__chrono_id'
|
|
261
|
-
execute "ALTER TABLE #{table_name} DROP __chrono_id"
|
|
262
|
-
end
|
|
234
|
+
on_history_schema { execute "DROP TABLE #{table_name}" }
|
|
263
235
|
|
|
264
|
-
|
|
236
|
+
default_schema = select_value 'SELECT current_schema()'
|
|
237
|
+
on_temporal_schema do
|
|
238
|
+
if primary_key(table_name) == '__chrono_id'
|
|
239
|
+
execute "ALTER TABLE #{table_name} DROP __chrono_id"
|
|
265
240
|
end
|
|
241
|
+
|
|
242
|
+
execute "ALTER TABLE #{table_name} SET SCHEMA #{default_schema}"
|
|
266
243
|
end
|
|
244
|
+
end
|
|
267
245
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
246
|
+
# Renames a table and its primary key sequence name
|
|
247
|
+
#
|
|
248
|
+
def rename_table_and_pk(name, new_name)
|
|
249
|
+
seq = pk_and_sequence_for(name).last.to_s
|
|
250
|
+
new_seq = seq.sub(name.to_s, new_name.to_s).split('.').last
|
|
273
251
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
252
|
+
execute "ALTER SEQUENCE #{seq} RENAME TO #{new_seq}"
|
|
253
|
+
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
|
254
|
+
end
|
|
277
255
|
|
|
278
256
|
# private
|
|
279
257
|
end
|
|
280
|
-
|
|
281
258
|
end
|
|
282
259
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ChronoModel
|
|
4
|
+
class Adapter
|
|
5
|
+
module MigrationsModules
|
|
6
|
+
module Legacy
|
|
7
|
+
# If adding an index to a temporal table, add it to the one in the
|
|
8
|
+
# temporal schema and to the history one. If the `:unique` option is
|
|
9
|
+
# present, it is removed from the index created in the history table.
|
|
10
|
+
#
|
|
11
|
+
def add_index(table_name, column_name, options = {})
|
|
12
|
+
return super unless is_chrono?(table_name)
|
|
13
|
+
|
|
14
|
+
transaction do
|
|
15
|
+
on_temporal_schema { super }
|
|
16
|
+
|
|
17
|
+
# Uniqueness constraints do not make sense in the history table
|
|
18
|
+
options = options.dup.tap { |o| o.delete(:unique) } if options[:unique].present?
|
|
19
|
+
|
|
20
|
+
on_history_schema { super(table_name, column_name, options) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# If removing an index from a temporal table, remove it both from the
|
|
25
|
+
# temporal and the history schemas.
|
|
26
|
+
#
|
|
27
|
+
def remove_index(table_name, options = {})
|
|
28
|
+
return super unless is_chrono?(table_name)
|
|
29
|
+
|
|
30
|
+
transaction do
|
|
31
|
+
on_temporal_schema { super }
|
|
32
|
+
|
|
33
|
+
on_history_schema { super }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
ChronoModel::Adapter::Migrations.include ChronoModel::Adapter::MigrationsModules::Legacy
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ChronoModel
|
|
4
|
+
class Adapter
|
|
5
|
+
module MigrationsModules
|
|
6
|
+
module Stable
|
|
7
|
+
# If adding an index to a temporal table, add it to the one in the
|
|
8
|
+
# temporal schema and to the history one. If the `:unique` option is
|
|
9
|
+
# present, it is removed from the index created in the history table.
|
|
10
|
+
#
|
|
11
|
+
def add_index(table_name, column_name, **options)
|
|
12
|
+
return super unless is_chrono?(table_name)
|
|
13
|
+
|
|
14
|
+
transaction do
|
|
15
|
+
on_temporal_schema { super }
|
|
16
|
+
|
|
17
|
+
# Uniqueness constraints do not make sense in the history table
|
|
18
|
+
options = options.dup.tap { |o| o.delete(:unique) } if options[:unique].present?
|
|
19
|
+
|
|
20
|
+
on_history_schema { super(table_name, column_name, **options) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# If removing an index from a temporal table, remove it both from the
|
|
25
|
+
# temporal and the history schemas.
|
|
26
|
+
#
|
|
27
|
+
def remove_index(table_name, column_name = nil, **options)
|
|
28
|
+
return super unless is_chrono?(table_name)
|
|
29
|
+
|
|
30
|
+
transaction do
|
|
31
|
+
on_temporal_schema { super }
|
|
32
|
+
|
|
33
|
+
on_history_schema { super }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
ChronoModel::Adapter::Migrations.include ChronoModel::Adapter::MigrationsModules::Stable
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ChronoModel
|
|
2
4
|
class Adapter < ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
|
3
|
-
|
|
4
5
|
module TSRange
|
|
5
6
|
# HACK: Redefine tsrange parsing support, as it is broken currently.
|
|
6
7
|
#
|
|
@@ -26,16 +27,31 @@ module ChronoModel
|
|
|
26
27
|
extracted = extract_bounds(value)
|
|
27
28
|
|
|
28
29
|
from = Conversions.string_to_utc_time extracted[:from]
|
|
29
|
-
to = Conversions.string_to_utc_time extracted[:to
|
|
30
|
+
to = Conversions.string_to_utc_time extracted[:to]
|
|
30
31
|
|
|
31
32
|
[from, to]
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def extract_bounds(value)
|
|
35
36
|
from, to = value[1..-2].split(',')
|
|
37
|
+
|
|
38
|
+
from_bound =
|
|
39
|
+
if value[1] == ',' || from == '-infinity'
|
|
40
|
+
nil
|
|
41
|
+
else
|
|
42
|
+
from[1..-2]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
to_bound =
|
|
46
|
+
if value[-2] == ',' || to == 'infinity'
|
|
47
|
+
nil
|
|
48
|
+
else
|
|
49
|
+
to[1..-2]
|
|
50
|
+
end
|
|
51
|
+
|
|
36
52
|
{
|
|
37
|
-
from:
|
|
38
|
-
to:
|
|
53
|
+
from: from_bound,
|
|
54
|
+
to: to_bound
|
|
39
55
|
}
|
|
40
56
|
end
|
|
41
57
|
end
|
|
@@ -52,6 +68,5 @@ module ChronoModel
|
|
|
52
68
|
end
|
|
53
69
|
end
|
|
54
70
|
end
|
|
55
|
-
|
|
56
71
|
end
|
|
57
72
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ChronoModel
|
|
2
4
|
class Adapter < ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
|
3
|
-
|
|
4
5
|
module Upgrade
|
|
5
6
|
def chrono_upgrade!
|
|
6
7
|
chrono_ensure_schemas
|
|
@@ -9,112 +10,109 @@ module ChronoModel
|
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
private
|
|
12
|
-
# Locate tables needing a structure upgrade
|
|
13
|
-
#
|
|
14
|
-
def chrono_tables_needing_upgrade
|
|
15
|
-
tables = { }
|
|
16
|
-
|
|
17
|
-
on_temporal_schema { self.tables }.each do |table_name|
|
|
18
|
-
next unless is_chrono?(table_name)
|
|
19
|
-
metadata = chrono_metadata_for(table_name)
|
|
20
|
-
version = metadata['chronomodel']
|
|
21
|
-
|
|
22
|
-
if version.blank?
|
|
23
|
-
tables[table_name] = { version: nil, priority: 'CRITICAL' }
|
|
24
|
-
elsif version != VERSION
|
|
25
|
-
tables[table_name] = { version: version, priority: 'LOW' }
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
13
|
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
# Locate tables needing a structure upgrade
|
|
15
|
+
#
|
|
16
|
+
def chrono_tables_needing_upgrade
|
|
17
|
+
tables = {}
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
logger.warn "ChronoModel: the upgrade will fail and you have to drop the dependent objects, run .upgrade! and create them"
|
|
44
|
-
logger.warn "ChronoModel: again. Sorry. Some features or the whole library may not work correctly until upgrade is complete."
|
|
45
|
-
logger.warn "ChronoModel: Tables pending upgrade: #{upgrade}"
|
|
19
|
+
on_temporal_schema { self.tables }.each do |table_name|
|
|
20
|
+
next unless is_chrono?(table_name)
|
|
21
|
+
|
|
22
|
+
metadata = chrono_metadata_for(table_name)
|
|
23
|
+
version = metadata['chronomodel']
|
|
24
|
+
|
|
25
|
+
if version.blank?
|
|
26
|
+
tables[table_name] = { version: nil, priority: 'CRITICAL' }
|
|
27
|
+
elsif version != VERSION
|
|
28
|
+
tables[table_name] = { version: version, priority: 'LOW' }
|
|
29
|
+
end
|
|
46
30
|
end
|
|
47
31
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
tables
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Emit a warning about tables needing an upgrade
|
|
36
|
+
#
|
|
37
|
+
def chrono_upgrade_warning
|
|
38
|
+
upgrade = chrono_tables_needing_upgrade.map do |table, desc|
|
|
39
|
+
"#{table} - priority: #{desc[:priority]}"
|
|
40
|
+
end.join('; ')
|
|
52
41
|
|
|
53
|
-
|
|
42
|
+
return if upgrade.empty?
|
|
54
43
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
chrono_create_view_for(table_name)
|
|
62
|
-
logger.info "ChronoModel: #{table_name} upgrade complete"
|
|
63
|
-
end
|
|
44
|
+
logger.warn 'ChronoModel: There are tables needing a structure upgrade, and ChronoModel structures need to be recreated.'
|
|
45
|
+
logger.warn 'ChronoModel: Please run ChronoModel.upgrade! to attempt the upgrade. If you have dependant database objects'
|
|
46
|
+
logger.warn 'ChronoModel: the upgrade will fail and you have to drop the dependent objects, run .upgrade! and create them'
|
|
47
|
+
logger.warn 'ChronoModel: again. Sorry. Some features or the whole library may not work correctly until upgrade is complete.'
|
|
48
|
+
logger.warn "ChronoModel: Tables pending upgrade: #{upgrade}"
|
|
49
|
+
end
|
|
64
50
|
|
|
51
|
+
# Upgrades existing structure for each table, if required.
|
|
52
|
+
#
|
|
53
|
+
def chrono_upgrade_structure!
|
|
54
|
+
transaction do
|
|
55
|
+
chrono_tables_needing_upgrade.each do |table_name, desc|
|
|
56
|
+
if desc[:version].blank?
|
|
57
|
+
logger.info "ChronoModel: Upgrading legacy PG 9.0 table #{table_name} to #{VERSION}"
|
|
58
|
+
chrono_upgrade_from_postgres_v90(table_name)
|
|
59
|
+
logger.info "ChronoModel: legacy #{table_name} upgrade complete"
|
|
60
|
+
else
|
|
61
|
+
logger.info "ChronoModel: upgrading #{table_name} from #{desc[:version]} to #{VERSION}"
|
|
62
|
+
chrono_public_view_ddl(table_name)
|
|
63
|
+
logger.info "ChronoModel: #{table_name} upgrade complete"
|
|
65
64
|
end
|
|
66
65
|
end
|
|
67
|
-
rescue => e
|
|
68
|
-
message = "ChronoModel structure upgrade failed: #{e.message}. Please drop dependent objects first and then run ChronoModel.upgrade! again."
|
|
69
|
-
|
|
70
|
-
# Quite important, output it also to stderr.
|
|
71
|
-
#
|
|
72
|
-
logger.error message
|
|
73
|
-
$stderr.puts message
|
|
74
66
|
end
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
message = "ChronoModel structure upgrade failed: #{e.message}. Please drop dependent objects first and then run ChronoModel.upgrade! again."
|
|
75
69
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
p_pkey = primary_key(table_name)
|
|
70
|
+
# Quite important, output it also to stderr.
|
|
71
|
+
#
|
|
72
|
+
logger.error message
|
|
73
|
+
warn message
|
|
74
|
+
end
|
|
82
75
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
def chrono_upgrade_from_postgres_v90(table_name)
|
|
77
|
+
# roses are red
|
|
78
|
+
# violets are blue
|
|
79
|
+
# and this is the most boring piece of code ever
|
|
80
|
+
history_table = "#{HISTORY_SCHEMA}.#{table_name}"
|
|
81
|
+
p_pkey = primary_key(table_name)
|
|
82
|
+
|
|
83
|
+
execute "ALTER TABLE #{history_table} ADD COLUMN validity tsrange;"
|
|
84
|
+
execute <<-SQL.squish
|
|
85
|
+
UPDATE #{history_table} SET validity = tsrange(valid_from,
|
|
86
86
|
CASE WHEN extract(year from valid_to) = 9999 THEN NULL
|
|
87
87
|
ELSE valid_to
|
|
88
88
|
END
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
89
|
+
);
|
|
90
|
+
SQL
|
|
91
|
+
|
|
92
|
+
execute "DROP INDEX #{history_table}_temporal_on_valid_from;"
|
|
93
|
+
execute "DROP INDEX #{history_table}_temporal_on_valid_from_and_valid_to;"
|
|
94
|
+
execute "DROP INDEX #{history_table}_temporal_on_valid_to;"
|
|
95
|
+
execute "DROP INDEX #{history_table}_inherit_pkey"
|
|
96
|
+
execute "DROP INDEX #{history_table}_recorded_at"
|
|
97
|
+
execute "DROP INDEX #{history_table}_instance_history"
|
|
98
|
+
execute "ALTER TABLE #{history_table} DROP CONSTRAINT #{table_name}_valid_from_before_valid_to;"
|
|
99
|
+
execute "ALTER TABLE #{history_table} DROP CONSTRAINT #{table_name}_timeline_consistency;"
|
|
100
|
+
execute "DROP RULE #{table_name}_upd_first ON #{table_name};"
|
|
101
|
+
execute "DROP RULE #{table_name}_upd_next ON #{table_name};"
|
|
102
|
+
execute "DROP RULE #{table_name}_del ON #{table_name};"
|
|
103
|
+
execute "DROP RULE #{table_name}_ins ON #{table_name};"
|
|
104
|
+
execute "DROP TRIGGER history_ins ON #{TEMPORAL_SCHEMA}.#{table_name};"
|
|
105
|
+
execute "DROP FUNCTION #{TEMPORAL_SCHEMA}.#{table_name}_ins();"
|
|
106
|
+
execute "ALTER TABLE #{history_table} DROP COLUMN valid_from;"
|
|
107
|
+
execute "ALTER TABLE #{history_table} DROP COLUMN valid_to;"
|
|
108
|
+
|
|
109
|
+
execute 'CREATE EXTENSION IF NOT EXISTS btree_gist;'
|
|
110
|
+
|
|
111
|
+
chrono_public_view_ddl(table_name)
|
|
112
|
+
on_history_schema { add_history_validity_constraint(table_name, p_pkey) }
|
|
113
|
+
on_history_schema { chrono_create_history_indexes_for(table_name, p_pkey) }
|
|
114
|
+
end
|
|
115
115
|
# private
|
|
116
116
|
end
|
|
117
|
-
|
|
118
117
|
end
|
|
119
|
-
|
|
120
118
|
end
|