time-travel 1.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.
@@ -0,0 +1,209 @@
1
+ do $$
2
+ DECLARE
3
+ routine_record text;
4
+ BEGIN
5
+ IF EXISTS ( SELECT 1 FROM Information_Schema.Routines WHERE Routine_Type ='FUNCTION' AND routine_name = 'update_history') THEN
6
+ SELECT CONCAT(routine_schema, '.', routine_name) INTO routine_record FROM Information_Schema.Routines WHERE Routine_Type ='FUNCTION' AND routine_name = 'update_history' limit 1;
7
+ EXECUTE concat('DROP FUNCTION ', routine_record);
8
+ END IF;
9
+ END
10
+ $$;
11
+
12
+ CREATE OR REPLACE FUNCTION update_history(
13
+ query text,
14
+ table_name text,
15
+ update_attrs text,
16
+ empty_obj_attrs text,
17
+ effective_from timestamp,
18
+ effective_till timestamp,
19
+ time_current timestamp,
20
+ infinite_date timestamp
21
+ )
22
+
23
+ RETURNS bigint[]
24
+ LANGUAGE 'plpgsql'
25
+
26
+ COST 100
27
+ VOLATILE
28
+ AS $BODY$
29
+
30
+ DECLARE
31
+ affected_rows bigint[];
32
+ target RECORD;
33
+ pre_post RECORD;
34
+ res jsonb;
35
+ current_id bigint;
36
+ in_between_effective_till timestamp;
37
+ original_effective_till timestamp;
38
+ original_effective_from timestamp;
39
+ update_attrs_json jsonb;
40
+ empty_obj_attrs_json jsonb;
41
+ head_found boolean;
42
+ tail_found boolean;
43
+ timeframe_attrs jsonb[] := '{}';
44
+ timeframe jsonb;
45
+ previous_timeframe jsonb;
46
+ temp_prev jsonb;
47
+ temp_curr jsonb;
48
+ begin
49
+ update_attrs_json := update_attrs::jsonb;
50
+ empty_obj_attrs_json := empty_obj_attrs::jsonb;
51
+
52
+ -- HEAD START
53
+ -- RAISE NOTICE 'head start';
54
+ FOR target IN EXECUTE concat(query, ' AND effective_from <= $1 AND effective_till > $2 order by effective_from ASC limit 1') USING effective_from, effective_from LOOP
55
+ -- RAISE NOTICE 'inside head iteration';
56
+ head_found := true;
57
+ original_effective_till := target.effective_till;
58
+ original_effective_from := target.effective_from;
59
+
60
+ -- HEAD NON-OVERLAPPING (with new) TIMEFRAME
61
+ IF target.effective_from <> effective_from THEN
62
+ target.effective_till := effective_from;
63
+ select get_json_attrs(to_jsonb(target), '{}'::jsonb) into res ;
64
+ timeframe_attrs := timeframe_attrs || res;
65
+ -- RAISE NOTICE 'new ineffective head id %',jsonb_pretty(res);
66
+ END IF;
67
+
68
+ -- HEAD AND NEW OVERLAPPING TIMEFRAME
69
+ target.effective_from := effective_from;
70
+ IF original_effective_till > effective_till THEN
71
+ target.effective_till := effective_till;
72
+ ELSE
73
+ target.effective_till := original_effective_till;
74
+ END IF;
75
+ select get_json_attrs(to_jsonb(target), update_attrs_json) into res ;
76
+ timeframe_attrs := timeframe_attrs || res;
77
+ -- RAISE NOTICE 'new in head id %', jsonb_pretty(res);
78
+
79
+ -- HEAD TAIL SAME, TAIL NON-OVERLAPPING (with new) TIMEFRAME
80
+ IF original_effective_from < effective_till AND original_effective_till > effective_till THEN
81
+ -- RAISE NOTICE 'same record head and tail';
82
+ target.effective_from := effective_till;
83
+ target.effective_till := original_effective_till;
84
+ select get_json_attrs(to_jsonb(target), '{}'::jsonb) into res ;
85
+ timeframe_attrs := timeframe_attrs || res;
86
+ -- RAISE NOTICE 'new tail id %', jsonb_pretty(res);
87
+ END IF;
88
+
89
+ -- NO TAIL, NEW NON-OVERLAPPING (with head) TIMEFRAME
90
+ EXECUTE concat(query, ' AND effective_till >= $1 limit 1') USING effective_till INTO pre_post;
91
+ -- RAISE NOTICE 'extending future pre post %', pre_post;
92
+ IF pre_post.effective_from IS NULL THEN
93
+ -- RAISE NOTICE 'extending future';
94
+ target.effective_from = original_effective_till;
95
+ target.effective_till = effective_till;
96
+ res = jsonb_build_object('effective_from', original_effective_till) || jsonb_build_object('effective_till', effective_till);
97
+ select get_json_attrs(empty_obj_attrs_json, update_attrs_json || res) into res ;
98
+ timeframe_attrs := timeframe_attrs || res;
99
+ -- RAISE NOTICE 'future in head %', jsonb_pretty(res);
100
+ END IF;
101
+
102
+ EXECUTE 'UPDATE ' || table_name|| ' SET valid_till=$1 WHERE id=$2' USING time_current, target.id;
103
+ END LOOP;
104
+
105
+ -- NO HEAD, NEW NON OVERLAPPING(with any) TIMEFRAME
106
+ -- HANGING IN PRE HISTORY
107
+ IF head_found IS NULL THEN
108
+ FOR target IN EXECUTE concat(query, ' AND effective_from > $1 order by effective_from ASC limit 1') USING effective_from LOOP
109
+ IF target.effective_from > effective_till THEN -- hanging
110
+ -- RAISE NOTICE 'hanging';
111
+ res = jsonb_build_object('effective_from', effective_from) || jsonb_build_object('effective_till', effective_till);
112
+ ELSE -- prehistoric
113
+ -- RAISE NOTICE 'pre historic';
114
+ res = jsonb_build_object('effective_from', effective_from) || jsonb_build_object('effective_till', target.effective_from);
115
+ END IF;
116
+ select get_json_attrs(empty_obj_attrs_json, update_attrs_json || res ) into res ;
117
+ timeframe_attrs := timeframe_attrs || res;
118
+ -- RAISE NOTICE 'pre historic/hanging tail %', jsonb_pretty(res);
119
+ END LOOP;
120
+ END IF;
121
+
122
+ -- BETWEEN AND NEW OVERLAPPING TIMEFRAME
123
+ -- RAISE NOTICE 'between start';
124
+ FOR target IN EXECUTE concat(query, ' AND effective_from > $1 AND effective_till < $2 order by effective_from ASC') USING effective_from, effective_till LOOP
125
+ -- RAISE NOTICE ' inside between';
126
+ select get_json_attrs(to_jsonb(target), update_attrs_json) into res ;
127
+ timeframe_attrs := timeframe_attrs || res;
128
+
129
+ in_between_effective_till := target.effective_till;
130
+ -- RAISE NOTICE 'between %', jsonb_pretty(res);
131
+ EXECUTE 'UPDATE ' || table_name|| ' SET valid_till=$1 WHERE id=$2' USING time_current, target.id;
132
+ END LOOP;
133
+
134
+
135
+ -- RAISE NOTICE 'tail start';
136
+ FOR target IN EXECUTE concat(query, ' AND effective_from > $1 AND effective_from < $2 AND effective_till >= $3 order by effective_from ASC limit 1') USING effective_from, effective_till, effective_till LOOP
137
+ -- RAISE NOTICE 'inside tail iteration';
138
+ -- TAIL AND NEW OVERLAPPING TIMEFRAME
139
+ original_effective_from := target.effective_from;
140
+ original_effective_till := target.effective_till;
141
+
142
+ tail_found := true;
143
+ target.effective_till := effective_till ;
144
+ target.effective_from := original_effective_from;
145
+ select get_json_attrs(to_jsonb(target), update_attrs_json) into res ;
146
+ timeframe_attrs := timeframe_attrs || res;
147
+ -- RAISE NOTICE 'new in tail id %', jsonb_pretty(res);
148
+
149
+ -- TAIL NON-OVERLAPPING(with new) TIMEFRAME
150
+ IF original_effective_till <> effective_till THEN
151
+ target.effective_from := effective_till;
152
+ target.effective_till := original_effective_till;
153
+ select get_json_attrs(to_jsonb(target), '{}'::jsonb) into res ;
154
+ timeframe_attrs := timeframe_attrs || res;
155
+ -- RAISE NOTICE 'new ineffective tail id %', jsonb_pretty(res);
156
+ END IF;
157
+ EXECUTE 'UPDATE ' || table_name|| ' SET valid_till=$1 WHERE id=$2' USING time_current, target.id;
158
+ END LOOP;
159
+
160
+ -- ONLY BETWEEN AND NO TAIL, NEW NON-OVERLAPPING TIMEFRAME
161
+ IF in_between_effective_till IS NOT NULL AND tail_found IS NULL THEN
162
+ -- RAISE NOTICE 'in between with no tail';
163
+ res := jsonb_build_object('effective_from', in_between_effective_till) || jsonb_build_object('effective_till', effective_till);
164
+ select get_json_attrs(empty_obj_attrs_json, update_attrs_json || res) into res ;
165
+ timeframe_attrs := timeframe_attrs || res;
166
+ -- RAISE NOTICE 'in between with no tail %', jsonb_pretty(res);
167
+ END IF;
168
+
169
+
170
+ -- HANGING ANYWHERE
171
+ IF array_length(timeframe_attrs, 1) IS NULL THEN
172
+ res := jsonb_build_object('effective_from', effective_from) || jsonb_build_object('effective_till', effective_till);
173
+ select get_json_attrs(empty_obj_attrs_json, update_attrs_json || res) into res ;
174
+ timeframe_attrs := timeframe_attrs || res;
175
+ -- RAISE NOTICE 'hanging %', jsonb_pretty(res);
176
+ END IF;
177
+
178
+
179
+ -- SQUISH RECORDS
180
+ previous_timeframe := null;
181
+
182
+ FOREACH timeframe IN ARRAY timeframe_attrs LOOP
183
+ IF previous_timeframe IS NOT NULL THEN
184
+ temp_prev = previous_timeframe - 'effective_from' - 'effective_till';
185
+ temp_curr = timeframe - 'effective_from' - 'effective_till';
186
+
187
+ IF previous_timeframe -> 'effective_till' < timeframe -> 'effective_from' THEN
188
+ previous_timeframe := previous_timeframe || jsonb_build_object('effective_till', timeframe -> 'effective_from');
189
+ END IF;
190
+
191
+ IF temp_prev @> temp_curr AND temp_prev <@ temp_curr THEN
192
+ previous_timeframe := previous_timeframe || jsonb_build_object('effective_till',timeframe -> 'effective_till');
193
+ ELSE
194
+ select create_column_value(to_json(previous_timeframe), time_current, table_name, '{}'::json, infinite_date) INTO current_id;
195
+ previous_timeframe := timeframe;
196
+ END IF;
197
+ ELSE
198
+ previous_timeframe := timeframe;
199
+ END IF;
200
+ END LOOP;
201
+
202
+ IF previous_timeframe IS NOT NULL THEN
203
+ select create_column_value(to_json(previous_timeframe), time_current, table_name, '{}'::json, infinite_date) INTO current_id;
204
+ END IF;
205
+
206
+ return affected_rows;
207
+ end
208
+
209
+ $BODY$;
@@ -0,0 +1,94 @@
1
+ do $$
2
+ DECLARE
3
+ routine_record text;
4
+ BEGIN
5
+ IF EXISTS ( SELECT 1 FROM Information_Schema.Routines WHERE Routine_Type ='FUNCTION' AND routine_name = 'update_latest') THEN
6
+ SELECT CONCAT(routine_schema, '.', routine_name) INTO routine_record FROM Information_Schema.Routines WHERE Routine_Type ='FUNCTION' AND routine_name = 'update_latest' limit 1;
7
+ EXECUTE concat('DROP FUNCTION ', routine_record);
8
+ END IF;
9
+ END
10
+ $$;
11
+
12
+ CREATE OR REPLACE FUNCTION update_latest(
13
+ query text,
14
+ table_name text,
15
+ update_attrs text,
16
+ empty_obj_attrs text,
17
+ effective_from timestamp,
18
+ effective_till timestamp,
19
+ time_current timestamp,
20
+ infinite_date timestamp
21
+ )
22
+
23
+ RETURNS void
24
+ LANGUAGE 'plpgsql'
25
+
26
+ COST 100
27
+ VOLATILE
28
+ AS $BODY$
29
+
30
+ DECLARE
31
+ affected_rows bigint[];
32
+ target RECORD;
33
+ current_record RECORD;
34
+ res jsonb;
35
+ current_id bigint;
36
+ -- original_effective_till timestamp;
37
+ -- original_effective_from timestamp;
38
+ update_attrs_json jsonb;
39
+ empty_obj_attrs_json jsonb;
40
+ -- head_found boolean;
41
+ -- tail_found boolean;
42
+ timeframe_attrs jsonb[] := '{}';
43
+ timeframe jsonb;
44
+ -- previous_timeframe jsonb;
45
+ -- temp_prev jsonb;
46
+ -- temp_curr jsonb;
47
+ begin
48
+ update_attrs_json := update_attrs::jsonb;
49
+ empty_obj_attrs_json := empty_obj_attrs::jsonb;
50
+
51
+ -- HEAD START
52
+ -- RAISE NOTICE 'head start';
53
+ FOR target IN EXECUTE concat(query, ' AND effective_from <= $1 AND effective_till = $2 order by effective_from ASC limit 1') USING effective_from, infinite_date LOOP
54
+ -- RAISE NOTICE 'inside head iteration';
55
+ -- head_found := true;
56
+ -- original_effective_till := target.effective_till;
57
+ -- original_effective_from := target.effective_from;
58
+
59
+ -- HEAD NON-OVERLAPPING (with new) TIMEFRAME
60
+ IF target.effective_from <> effective_from THEN
61
+ target.effective_till := effective_from;
62
+ select get_json_attrs(to_jsonb(target), '{}'::jsonb) into res ;
63
+ timeframe_attrs := timeframe_attrs || res;
64
+ -- RAISE NOTICE 'new ineffective head id %',jsonb_pretty(res);
65
+ END IF;
66
+
67
+ -- HEAD AND NEW OVERLAPPING TIMEFRAME
68
+ target.effective_from := effective_from;
69
+ target.effective_till := effective_till;
70
+ select get_json_attrs(to_jsonb(target), update_attrs_json) into res ;
71
+ timeframe_attrs := timeframe_attrs || res;
72
+
73
+ EXECUTE 'UPDATE ' || table_name|| ' SET valid_till=$1 WHERE id=$2' USING time_current, target.id;
74
+ END LOOP;
75
+
76
+ -- HANGING ANYWHERE
77
+ IF array_length(timeframe_attrs, 1) IS NULL THEN
78
+ EXECUTE(concat(query, ' LIMIT 1')) INTO current_record;
79
+ IF current_record IS NULL THEN
80
+ res := jsonb_build_object('effective_from', effective_from) || jsonb_build_object('effective_till', effective_till);
81
+ select get_json_attrs(empty_obj_attrs_json, update_attrs_json || res) into res ;
82
+ timeframe_attrs := timeframe_attrs || res;
83
+ ELSE
84
+ RAISE EXCEPTION 'you cannot update non latest values';
85
+ END IF;
86
+ -- RAISE NOTICE 'hanging %', jsonb_pretty(res);
87
+ END IF;
88
+
89
+ FOREACH timeframe IN ARRAY timeframe_attrs LOOP
90
+ select create_column_value(to_json(timeframe), time_current, table_name, '{}'::json, infinite_date) INTO current_id;
91
+ END LOOP;
92
+ end
93
+
94
+ $BODY$;
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: time-travel
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - ''
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-09-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.8'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pg
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: The time travel gem adds history and correction tracking to models.
70
+ email:
71
+ - ''
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - MIT-LICENSE
77
+ - README.md
78
+ - Rakefile
79
+ - lib/generators/time_travel/USAGE
80
+ - lib/generators/time_travel/templates/time_travel_migration_existing.rb.erb
81
+ - lib/generators/time_travel/templates/time_travel_migration_new.rb.erb
82
+ - lib/generators/time_travel/time_travel_generator.rb
83
+ - lib/tasks/create_postgres_function.rake
84
+ - lib/time_travel.rb
85
+ - lib/time_travel/configuration.rb
86
+ - lib/time_travel/railtie.rb
87
+ - lib/time_travel/sql_function_helper.rb
88
+ - lib/time_travel/timeline.rb
89
+ - lib/time_travel/timeline_helper.rb
90
+ - lib/time_travel/update_helper.rb
91
+ - lib/time_travel/version.rb
92
+ - lib/time_travel_backup.rb
93
+ - sql/create_column_value.sql
94
+ - sql/get_json_attrs.sql
95
+ - sql/update_bulk_history.sql
96
+ - sql/update_history.sql
97
+ - sql/update_latest.sql
98
+ homepage: https://weinvest.net
99
+ licenses:
100
+ - MIT
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.7.7
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: The Time travel gem adds history and correction tracking to models.
122
+ test_files: []