chrono_model 1.2.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +19 -20
  3. data/README.md +62 -40
  4. data/lib/active_record/connection_adapters/chronomodel_adapter.rb +17 -11
  5. data/lib/active_record/tasks/chronomodel_database_tasks.rb +64 -23
  6. data/lib/chrono_model/adapter/ddl.rb +168 -153
  7. data/lib/chrono_model/adapter/indexes.rb +99 -94
  8. data/lib/chrono_model/adapter/migrations.rb +81 -104
  9. data/lib/chrono_model/adapter/migrations_modules/legacy.rb +41 -0
  10. data/lib/chrono_model/adapter/migrations_modules/stable.rb +41 -0
  11. data/lib/chrono_model/adapter/tsrange.rb +20 -5
  12. data/lib/chrono_model/adapter/upgrade.rb +89 -91
  13. data/lib/chrono_model/adapter.rb +64 -31
  14. data/lib/chrono_model/chrono.rb +17 -0
  15. data/lib/chrono_model/conversions.rb +15 -9
  16. data/lib/chrono_model/db_console.rb +9 -0
  17. data/lib/chrono_model/json.rb +9 -6
  18. data/lib/chrono_model/patches/as_of_time_holder.rb +2 -2
  19. data/lib/chrono_model/patches/as_of_time_relation.rb +2 -2
  20. data/lib/chrono_model/patches/association.rb +15 -12
  21. data/lib/chrono_model/patches/batches.rb +17 -0
  22. data/lib/chrono_model/patches/db_console.rb +20 -4
  23. data/lib/chrono_model/patches/join_node.rb +4 -4
  24. data/lib/chrono_model/patches/preloader.rb +41 -11
  25. data/lib/chrono_model/patches/relation.rb +53 -8
  26. data/lib/chrono_model/patches.rb +3 -1
  27. data/lib/chrono_model/railtie.rb +29 -24
  28. data/lib/chrono_model/time_gate.rb +3 -3
  29. data/lib/chrono_model/time_machine/history_model.rb +65 -31
  30. data/lib/chrono_model/time_machine/time_query.rb +65 -49
  31. data/lib/chrono_model/time_machine/timeline.rb +52 -28
  32. data/lib/chrono_model/time_machine.rb +66 -25
  33. data/lib/chrono_model/utilities.rb +3 -3
  34. data/lib/chrono_model/version.rb +3 -1
  35. data/lib/chrono_model.rb +31 -36
  36. metadata +39 -136
  37. data/.gitignore +0 -21
  38. data/.rspec +0 -2
  39. data/.travis.yml +0 -41
  40. data/Gemfile +0 -4
  41. data/README.sql +0 -161
  42. data/Rakefile +0 -25
  43. data/chrono_model.gemspec +0 -33
  44. data/gemfiles/rails_5.0.gemfile +0 -6
  45. data/gemfiles/rails_5.1.gemfile +0 -6
  46. data/gemfiles/rails_5.2.gemfile +0 -6
  47. data/spec/aruba/dbconsole_spec.rb +0 -25
  48. data/spec/aruba/fixtures/database_with_default_username_and_password.yml +0 -14
  49. data/spec/aruba/fixtures/database_without_username_and_password.yml +0 -11
  50. data/spec/aruba/fixtures/empty_structure.sql +0 -27
  51. data/spec/aruba/fixtures/migrations/56/20160812190335_create_impressions.rb +0 -10
  52. data/spec/aruba/fixtures/migrations/56/20171115195229_add_temporal_extension_to_impressions.rb +0 -10
  53. data/spec/aruba/fixtures/railsapp/config/application.rb +0 -17
  54. data/spec/aruba/fixtures/railsapp/config/boot.rb +0 -5
  55. data/spec/aruba/fixtures/railsapp/config/environments/development.rb +0 -38
  56. data/spec/aruba/migrations_spec.rb +0 -48
  57. data/spec/aruba/rake_task_spec.rb +0 -71
  58. data/spec/chrono_model/adapter/base_spec.rb +0 -157
  59. data/spec/chrono_model/adapter/ddl_spec.rb +0 -243
  60. data/spec/chrono_model/adapter/indexes_spec.rb +0 -72
  61. data/spec/chrono_model/adapter/migrations_spec.rb +0 -312
  62. data/spec/chrono_model/conversions_spec.rb +0 -43
  63. data/spec/chrono_model/history_models_spec.rb +0 -32
  64. data/spec/chrono_model/json_ops_spec.rb +0 -59
  65. data/spec/chrono_model/time_machine/as_of_spec.rb +0 -188
  66. data/spec/chrono_model/time_machine/changes_spec.rb +0 -50
  67. data/spec/chrono_model/time_machine/counter_cache_race_spec.rb +0 -46
  68. data/spec/chrono_model/time_machine/default_scope_spec.rb +0 -37
  69. data/spec/chrono_model/time_machine/history_spec.rb +0 -104
  70. data/spec/chrono_model/time_machine/keep_cool_spec.rb +0 -27
  71. data/spec/chrono_model/time_machine/manipulations_spec.rb +0 -84
  72. data/spec/chrono_model/time_machine/model_identification_spec.rb +0 -46
  73. data/spec/chrono_model/time_machine/sequence_spec.rb +0 -74
  74. data/spec/chrono_model/time_machine/sti_spec.rb +0 -100
  75. data/spec/chrono_model/time_machine/time_query_spec.rb +0 -261
  76. data/spec/chrono_model/time_machine/timeline_spec.rb +0 -63
  77. data/spec/chrono_model/time_machine/timestamps_spec.rb +0 -43
  78. data/spec/chrono_model/time_machine/transactions_spec.rb +0 -69
  79. data/spec/config.travis.yml +0 -5
  80. data/spec/config.yml.example +0 -9
  81. data/spec/spec_helper.rb +0 -33
  82. data/spec/support/adapter/helpers.rb +0 -53
  83. data/spec/support/adapter/structure.rb +0 -44
  84. data/spec/support/aruba.rb +0 -44
  85. data/spec/support/connection.rb +0 -70
  86. data/spec/support/matchers/base.rb +0 -56
  87. data/spec/support/matchers/column.rb +0 -99
  88. data/spec/support/matchers/function.rb +0 -79
  89. data/spec/support/matchers/index.rb +0 -69
  90. data/spec/support/matchers/schema.rb +0 -39
  91. data/spec/support/matchers/table.rb +0 -275
  92. data/spec/support/time_machine/helpers.rb +0 -47
  93. data/spec/support/time_machine/structure.rb +0 -111
  94. data/sql/json_ops.sql +0 -56
  95. 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 "ChronoModel: Temporal Temporal tables require a primary key."
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
- return super unless is_chrono?(name)
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 = {}, &block)
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
- case options[:temporal]
74
- when true
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 table_name, options, &block
83
+ super(table_name, **options, &block)
81
84
  end
82
85
 
83
- when false
86
+ else
84
87
  if is_chrono?(table_name)
85
88
  chrono_undo_temporal_table(table_name)
86
89
  end
87
90
 
88
- super table_name, options, &block
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
- on_temporal_schema { yield }
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
- # Recreate the triggers
211
- chrono_public_view_ddl(table_name, options)
212
- end
213
- end
184
+ execute "DROP VIEW #{table_name}"
214
185
 
215
- def chrono_make_temporal_table(table_name, options)
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
- execute "ALTER TABLE #{table_name} SET SCHEMA #{TEMPORAL_SCHEMA}"
223
- on_history_schema { chrono_history_table_ddl(table_name) }
188
+ # Recreate the triggers
224
189
  chrono_public_view_ddl(table_name, options)
225
- chrono_copy_indexes_to_history(table_name)
190
+ end
191
+ end
226
192
 
227
- # Optionally copy the plain table data, setting up history
228
- # retroactively.
229
- #
230
- if options[:copy_data]
231
- chrono_copy_temporal_to_history(table_name, options)
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
- def chrono_copy_temporal_to_history(table_name, options)
236
- seq = on_history_schema { serial_sequence(table_name, primary_key(table_name)) }
237
- from = options[:validity] || '0001-01-01 00:00:00'
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
- execute %[
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
- end
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
- chrono_drop_trigger_functions_for(table_name)
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
- on_history_schema { execute "DROP TABLE #{table_name}" }
232
+ chrono_drop_trigger_functions_for(table_name)
257
233
 
258
- default_schema = select_value 'SELECT current_schema()'
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
- execute "ALTER TABLE #{table_name} SET SCHEMA #{default_schema}"
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
- # Renames a table and its primary key sequence name
269
- #
270
- def rename_table_and_pk(name, new_name)
271
- seq = serial_sequence(name, primary_key(name))
272
- new_seq = seq.sub(name.to_s, new_name.to_s).split('.').last
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
- execute "ALTER SEQUENCE #{seq} RENAME TO #{new_seq}"
275
- execute "ALTER TABLE #{name} RENAME TO #{new_name}"
276
- end
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: (value[1] == ',' || from == '-infinity') ? nil : from[1..-2],
38
- to: (value[-2] == ',' || to == 'infinity') ? nil : to[1..-2],
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
- return tables
30
- end
14
+ # Locate tables needing a structure upgrade
15
+ #
16
+ def chrono_tables_needing_upgrade
17
+ tables = {}
31
18
 
32
- # Emit a warning about tables needing an upgrade
33
- #
34
- def chrono_upgrade_warning
35
- upgrade = chrono_tables_needing_upgrade.map do |table, desc|
36
- "#{table} - priority: #{desc[:priority]}"
37
- end.join('; ')
38
-
39
- return if upgrade.empty?
40
-
41
- logger.warn "ChronoModel: There are tables needing a structure upgrade, and ChronoModel structures need to be recreated."
42
- logger.warn "ChronoModel: Please run ChronoModel.upgrade! to attempt the upgrade. If you have dependant database objects"
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
- # Upgrades existing structure for each table, if required.
49
- #
50
- def chrono_upgrade_structure!
51
- transaction do
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
- chrono_tables_needing_upgrade.each do |table_name, desc|
42
+ return if upgrade.empty?
54
43
 
55
- if desc[:version].blank?
56
- logger.info "ChronoModel: Upgrading legacy PG 9.0 table #{table_name} to #{VERSION}"
57
- chrono_upgrade_from_postgres_9_0(table_name)
58
- logger.info "ChronoModel: legacy #{table_name} upgrade complete"
59
- else
60
- logger.info "ChronoModel: upgrading #{table_name} from #{desc[:version]} to #{VERSION}"
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
- def chrono_upgrade_from_postgres_9_0(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)
70
+ # Quite important, output it also to stderr.
71
+ #
72
+ logger.error message
73
+ warn message
74
+ end
82
75
 
83
- execute "ALTER TABLE #{history_table} ADD COLUMN validity tsrange;"
84
- execute """
85
- UPDATE #{history_table} SET validity = tsrange(valid_from,
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
- 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_create_view_for(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
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