logidze 0.8.1 → 1.0.0.rc1

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +124 -5
  3. data/LICENSE.txt +1 -1
  4. data/README.md +310 -80
  5. data/lib/generators/logidze/fx_helper.rb +17 -0
  6. data/lib/generators/logidze/inject_sql.rb +18 -0
  7. data/lib/generators/logidze/install/USAGE +6 -1
  8. data/lib/generators/logidze/install/functions/logidze_compact_history.sql +38 -0
  9. data/lib/generators/logidze/install/functions/logidze_filter_keys.sql +27 -0
  10. data/lib/generators/logidze/install/functions/logidze_logger.sql +150 -0
  11. data/lib/generators/logidze/install/functions/logidze_snapshot.sql +24 -0
  12. data/lib/generators/logidze/install/functions/logidze_version.sql +20 -0
  13. data/lib/generators/logidze/install/install_generator.rb +61 -3
  14. data/lib/generators/logidze/install/templates/hstore.rb.erb +1 -1
  15. data/lib/generators/logidze/install/templates/migration.rb.erb +19 -232
  16. data/lib/generators/logidze/install/templates/migration_fx.rb.erb +41 -0
  17. data/lib/generators/logidze/model/model_generator.rb +56 -20
  18. data/lib/generators/logidze/model/templates/migration.rb.erb +57 -36
  19. data/lib/generators/logidze/model/triggers/logidze.sql +6 -0
  20. data/lib/logidze.rb +43 -20
  21. data/lib/logidze/engine.rb +4 -1
  22. data/lib/logidze/has_logidze.rb +12 -3
  23. data/lib/logidze/history.rb +7 -15
  24. data/lib/logidze/history/type.rb +1 -1
  25. data/lib/logidze/history/version.rb +6 -5
  26. data/lib/logidze/ignore_log_data.rb +22 -0
  27. data/lib/logidze/ignore_log_data/cast_attribute_patch.rb +17 -0
  28. data/lib/logidze/meta.rb +44 -17
  29. data/lib/logidze/model.rb +65 -37
  30. data/lib/logidze/version.rb +2 -1
  31. data/lib/logidze/versioned_association.rb +0 -1
  32. metadata +47 -103
  33. data/.gitignore +0 -40
  34. data/.hound.yml +0 -3
  35. data/.rubocop.yml +0 -94
  36. data/.travis.yml +0 -39
  37. data/Gemfile +0 -13
  38. data/Rakefile +0 -28
  39. data/bench/performance/README.md +0 -109
  40. data/bench/performance/diff_bench.rb +0 -36
  41. data/bench/performance/insert_bench.rb +0 -20
  42. data/bench/performance/memory_profile.rb +0 -53
  43. data/bench/performance/setup.rb +0 -308
  44. data/bench/performance/update_bench.rb +0 -36
  45. data/bench/triggers/Makefile +0 -56
  46. data/bench/triggers/Readme.md +0 -58
  47. data/bench/triggers/bench.sql +0 -6
  48. data/bench/triggers/hstore_trigger_setup.sql +0 -38
  49. data/bench/triggers/jsonb_minus_2_setup.sql +0 -47
  50. data/bench/triggers/jsonb_minus_setup.sql +0 -49
  51. data/bench/triggers/keys2_trigger_setup.sql +0 -44
  52. data/bench/triggers/keys_trigger_setup.sql +0 -50
  53. data/bin/console +0 -8
  54. data/bin/setup +0 -9
  55. data/gemfiles/rails42.gemfile +0 -5
  56. data/gemfiles/rails5.gemfile +0 -6
  57. data/gemfiles/rails52.gemfile +0 -6
  58. data/gemfiles/railsmaster.gemfile +0 -7
  59. data/lib/logidze/migration.rb +0 -19
  60. data/logidze.gemspec +0 -33
@@ -1,308 +0,0 @@
1
- begin
2
- require 'bundler/inline'
3
- rescue LoadError => e
4
- $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
5
- raise e
6
- end
7
-
8
- gemfile(true) do
9
- source 'https://rubygems.org'
10
- gem 'activerecord', '~>4.2'
11
- gem 'pg'
12
- gem 'paper_trail', '~>4.2', require: false
13
- gem 'pry-byebug'
14
- gem 'faker'
15
- gem 'benchmark-ips'
16
- gem 'memory_profiler'
17
- end
18
-
19
- DB_NAME = ENV['DB_NAME'] || 'logidze_query_bench'
20
-
21
- begin
22
- system("createdb #{DB_NAME}")
23
- rescue
24
- $stdout.puts "DB already exists"
25
- end
26
-
27
- $LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
28
-
29
- require 'active_record'
30
- require 'logger'
31
- require 'logidze'
32
-
33
- ActiveRecord::Base.send :include, Logidze::HasLogidze
34
-
35
- ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: DB_NAME)
36
-
37
- at_exit do
38
- ActiveRecord::Base.connection.disconnect!
39
- end
40
-
41
- require 'paper_trail'
42
-
43
- module LogidzeBench
44
- module_function
45
- def setup_db
46
- ActiveRecord::Schema.define do
47
- # PaperTrail setup
48
- create_table :versions, force: true do |t|
49
- t.string :item_type, null: false
50
- t.integer :item_id, null: false
51
- t.string :event, null: false
52
- t.string :whodunnit
53
- t.text :object
54
- t.jsonb :object_changes
55
-
56
- t.datetime :created_at
57
- end
58
-
59
- add_index :versions, [:item_type, :item_id]
60
-
61
- # Logidze setup
62
- enable_extension :hstore
63
-
64
- execute <<~SQL
65
- DO $$
66
- BEGIN
67
- EXECUTE 'ALTER DATABASE ' || current_database() || ' SET logidze.disabled TO off';
68
- END;
69
- $$
70
- LANGUAGE plpgsql;
71
- SQL
72
-
73
- execute <<~SQL
74
- CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$
75
- DECLARE
76
- changes jsonb;
77
- new_v integer;
78
- ts bigint;
79
- size integer;
80
- history_limit integer;
81
- current_version integer;
82
- merged jsonb;
83
- iterator integer;
84
- item record;
85
- BEGIN
86
- ts := (extract(epoch from now()) * 1000)::bigint;
87
-
88
- IF TG_OP = 'INSERT' THEN
89
- changes := to_jsonb(NEW.*) - 'log_data';
90
- new_v := 1;
91
-
92
- NEW.log_data := json_build_object(
93
- 'v',
94
- 1,
95
- 'h',
96
- jsonb_build_array(
97
- jsonb_build_object(
98
- 'ts',
99
- ts,
100
- 'v',
101
- new_v,
102
- 'c',
103
- changes
104
- )
105
- )
106
- );
107
- ELSIF TG_OP = 'UPDATE' THEN
108
- history_limit := TG_ARGV[0];
109
- current_version := (NEW.log_data->>'v')::int;
110
-
111
- IF NEW = OLD THEN
112
- RETURN NEW;
113
- END IF;
114
-
115
- IF current_version < (NEW.log_data#>>'{h,-1,v}')::int THEN
116
- iterator := 0;
117
- FOR item in SELECT * FROM jsonb_array_elements(NEW.log_data->'h')
118
- LOOP
119
- IF (item.value->>'v')::int > current_version THEN
120
- NEW.log_data := jsonb_set(
121
- NEW.log_data,
122
- '{h}',
123
- (NEW.log_data->'h') - iterator
124
- );
125
- END IF;
126
- iterator := iterator + 1;
127
- END LOOP;
128
- END IF;
129
-
130
- changes := hstore_to_jsonb_loose(
131
- hstore(NEW.*) - hstore(OLD.*)
132
- ) - 'log_data';
133
-
134
- new_v := (NEW.log_data#>>'{h,-1,v}')::int + 1;
135
-
136
- size := jsonb_array_length(NEW.log_data->'h');
137
-
138
- NEW.log_data := jsonb_set(
139
- NEW.log_data,
140
- ARRAY['h', size::text],
141
- jsonb_build_object(
142
- 'ts',
143
- ts,
144
- 'v',
145
- new_v,
146
- 'c',
147
- changes
148
- ),
149
- true
150
- );
151
-
152
- NEW.log_data := jsonb_set(
153
- NEW.log_data,
154
- '{v}',
155
- to_jsonb(new_v)
156
- );
157
-
158
- IF history_limit IS NOT NULL AND history_limit = size THEN
159
- merged := jsonb_build_object(
160
- 'ts',
161
- NEW.log_data#>'{h,1,ts}',
162
- 'v',
163
- NEW.log_data#>'{h,1,v}',
164
- 'c',
165
- (NEW.log_data#>'{h,0,c}') || (NEW.log_data#>'{h,1,c}')
166
- );
167
-
168
- NEW.log_data := jsonb_set(
169
- NEW.log_data,
170
- '{h}',
171
- jsonb_set(
172
- NEW.log_data->'h',
173
- '{1}',
174
- merged
175
- ) - 0
176
- );
177
- END IF;
178
- END IF;
179
-
180
- return NEW;
181
- END;
182
- $body$
183
- LANGUAGE plpgsql;
184
- SQL
185
-
186
- create_table :users, force: true do |t|
187
- t.string :email
188
- t.integer :position
189
- t.string :name
190
- t.text :bio
191
- t.integer :age
192
- t.timestamps
193
- end
194
-
195
- create_table :logidze_users, force: true do |t|
196
- t.string :email
197
- t.integer :position
198
- t.string :name
199
- t.text :bio
200
- t.integer :age
201
- t.jsonb :log_data, default: '{}', null: false
202
- t.timestamps
203
- end
204
-
205
- execute <<~SQL
206
- CREATE TRIGGER logidze_on_logidze_users
207
- BEFORE UPDATE OR INSERT ON logidze_users FOR EACH ROW
208
- WHEN (current_setting('logidze.disabled') <> 'on')
209
- EXECUTE PROCEDURE logidze_logger();
210
- SQL
211
- end
212
- end
213
-
214
- module_function
215
- def populate(n = 1_000)
216
- n.times do
217
- params = fake_params
218
- User.create!(params)
219
- LogidzeUser.create!(params)
220
- end
221
- end
222
-
223
- module_function
224
- def cleanup
225
- LogidzeUser.delete_all
226
- User.delete_all
227
- PaperTrail::Version.delete_all
228
- end
229
-
230
- module_function
231
- def generate_versions(num = 1)
232
- num.times do
233
- User.find_each do |u|
234
- u.update!(fake_params(sample: true))
235
- end
236
-
237
- LogidzeUser.find_each do |u|
238
- u.update!(fake_params(sample: true))
239
- end
240
-
241
- # make at least 1 second between versions
242
- sleep 1
243
- end
244
- Time.now
245
- end
246
-
247
- module_function
248
- def fake_params(sample: false)
249
- params = {
250
- email: Faker::Internet.email,
251
- position: Faker::Number.number(3),
252
- name: Faker::Name.name,
253
- age: Faker::Number.number(2),
254
- bio: Faker::Lorem.paragraph
255
- }
256
-
257
- return params.slice(%i(email position name age bio).sample) if sample
258
- params
259
- end
260
- end
261
-
262
- module ARandom
263
- def random(num = 1)
264
- rel = order('random()')
265
- num == 1 ? rel.first : rel.limit(num)
266
- end
267
- end
268
-
269
- class User < ActiveRecord::Base
270
- extend ARandom
271
- has_paper_trail
272
-
273
- def self.diff_from(ts)
274
- includes(:versions).map { |u| { 'id' => u.id, 'changes' => u.diff_from(ts) } }
275
- end
276
-
277
- def self.diff_from_joined(ts)
278
- eager_load(:versions).map { |u| { 'id' => u.id, 'changes' => u.diff_from(ts) } }
279
- end
280
-
281
- def diff_from(ts)
282
- changes = {}
283
- versions.each do |v|
284
- next if v.created_at < ts
285
- merge_changeset(changes, v.changeset)
286
- end
287
- changes
288
- end
289
-
290
- private
291
-
292
- def merge_changeset(acc, data)
293
- data.each do |k,v|
294
- unless acc.key?(k)
295
- acc[k] = { 'old' => v[0] }
296
- end
297
- acc[k]['new'] = v[1]
298
- end
299
- end
300
- end
301
-
302
- class LogidzeUser < ActiveRecord::Base
303
- extend ARandom
304
- has_logidze
305
- end
306
-
307
- # Run migration only if neccessary
308
- LogidzeBench.setup_db if ENV['FORCE'].present? || !ActiveRecord::Base.connection.tables.include?('logidze_users')
@@ -1,36 +0,0 @@
1
- require 'benchmark/ips'
2
- require './setup'
3
-
4
- params = {
5
- age: Faker::Number.number(2),
6
- email: Faker::Internet.email
7
- }
8
-
9
- params2 = {
10
- email: Faker::Internet.email,
11
- position: Faker::Number.number(3),
12
- name: Faker::Name.name,
13
- age: Faker::Number.number(2),
14
- bio: Faker::Lorem.paragraph
15
- }
16
-
17
- LogidzeBench.cleanup
18
- LogidzeBench.populate
19
-
20
- Benchmark.ips do |x|
21
- x.report('PT UPDATE #1') do
22
- User.random.update!(params)
23
- end
24
-
25
- x.report('Logidze UPDATE #1') do
26
- LogidzeUser.random.update!(params)
27
- end
28
-
29
- x.report('PT UPDATE #2') do
30
- User.random.update!(params2)
31
- end
32
-
33
- x.report('Logidze UPDATE #2') do
34
- LogidzeUser.random.update!(params2)
35
- end
36
- end
@@ -1,56 +0,0 @@
1
- # Database name
2
- ifndef DB
3
- DB = logidze_bench
4
- endif
5
-
6
- # Transactions count
7
- ifndef T
8
- T = 10000
9
- endif
10
-
11
- all: plain hstore jsonb jsonb2 keys keys2
12
-
13
- setup:
14
- createdb $(DB) -w
15
- psql -q -d $(DB) -c 'CREATE EXTENSION IF NOT EXISTS hstore;'
16
-
17
- plain:
18
- $(info )
19
- $(info ====== [START] Update without triggers ======)
20
- pgbench -i -q $(DB)
21
- pgbench -f bench.sql -t $(T) -r $(DB)
22
-
23
- hstore:
24
- $(info )
25
- $(info ====== [START] Update with hstore-based triggers ======)
26
- pgbench -i -q $(DB)
27
- psql -q -d $(DB) -f hstore_trigger_setup.sql
28
- pgbench -f bench.sql -t $(T) -r $(DB)
29
-
30
- jsonb:
31
- $(info )
32
- $(info ====== [START] Update with jsonb-minus triggers ======)
33
- pgbench -i -q $(DB)
34
- psql -q -d $(DB) -f jsonb_minus_setup.sql
35
- pgbench -f bench.sql -t $(T) -r $(DB)
36
-
37
- jsonb2:
38
- $(info )
39
- $(info ====== [START] Update with jsonb-minus triggers ======)
40
- pgbench -i -q $(DB)
41
- psql -q -d $(DB) -f jsonb_minus_2_setup.sql
42
- pgbench -f bench.sql -t $(T) -r $(DB)
43
-
44
- keys:
45
- $(info )
46
- $(info ====== [START] Update with loop thru keys triggers (v1) ======)
47
- pgbench -i -q $(DB)
48
- psql -q -d $(DB) -f keys_trigger_setup.sql
49
- pgbench -f bench.sql -t $(T) -r $(DB)
50
-
51
- keys2:
52
- $(info )
53
- $(info ====== [START] Update with loop thru keys triggers (v2) ======)
54
- pgbench -i -q $(DB)
55
- psql -q -d $(DB) -f keys2_trigger_setup.sql
56
- pgbench -f bench.sql -t $(T) -r $(DB)
@@ -1,58 +0,0 @@
1
- # Triggers benchmarks
2
-
3
- This benchmark uses standard _pg\_bench_ table `pgbench_accounts`.
4
- We consider several approaches for calculating records diff: one uses _hstore_ extension, two uses jsonb functions and two others iterate through record fields.
5
-
6
- # Usage
7
-
8
- Create database:
9
-
10
- ```sh
11
- make setup
12
- ```
13
-
14
- You can provide database name by `DB` variable (defaults to "logidze_bench").
15
-
16
- Run all benchmarks:
17
-
18
- ```sh
19
- make
20
- ```
21
-
22
- or separate benchmark:
23
-
24
- ```sh
25
- make hstore
26
-
27
- make jsonb
28
-
29
- make jsonb2
30
-
31
- make keys
32
-
33
- make keys2
34
-
35
- # Raw update, no triggers
36
- make plain
37
- ```
38
-
39
- You can specify the number of transactions by `T` variable (defaults to 10000):
40
-
41
- ```sh
42
- make T=1000000
43
- ```
44
-
45
- # Results
46
-
47
- The benchmark shows that hstore variant is the most efficient (running on MacPro 2013, 2.4 GHz Core i5, 4GB, SSD, 1 million transactions per test):
48
-
49
- |Mode | TPS | Statement latency (ms) |
50
- |--------|------|------------------------|
51
- | plain | 3628 | 0.113 |
52
- | hstore | 3015 | 0.168 |
53
- | jsonb | 1647 | 0.363 |
54
- | jsonb2 | 1674 | 0.354 |
55
- | keys | 2355 | 0.219 |
56
- | keys2 | 2542 | 0.210 |
57
-
58
- _Logidze_ uses hstore variant.