logidze 0.8.1 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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.