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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +124 -5
- data/LICENSE.txt +1 -1
- data/README.md +310 -80
- data/lib/generators/logidze/fx_helper.rb +17 -0
- data/lib/generators/logidze/inject_sql.rb +18 -0
- data/lib/generators/logidze/install/USAGE +6 -1
- data/lib/generators/logidze/install/functions/logidze_compact_history.sql +38 -0
- data/lib/generators/logidze/install/functions/logidze_filter_keys.sql +27 -0
- data/lib/generators/logidze/install/functions/logidze_logger.sql +150 -0
- data/lib/generators/logidze/install/functions/logidze_snapshot.sql +24 -0
- data/lib/generators/logidze/install/functions/logidze_version.sql +20 -0
- data/lib/generators/logidze/install/install_generator.rb +61 -3
- data/lib/generators/logidze/install/templates/hstore.rb.erb +1 -1
- data/lib/generators/logidze/install/templates/migration.rb.erb +19 -232
- data/lib/generators/logidze/install/templates/migration_fx.rb.erb +41 -0
- data/lib/generators/logidze/model/model_generator.rb +56 -20
- data/lib/generators/logidze/model/templates/migration.rb.erb +57 -36
- data/lib/generators/logidze/model/triggers/logidze.sql +6 -0
- data/lib/logidze.rb +43 -20
- data/lib/logidze/engine.rb +4 -1
- data/lib/logidze/has_logidze.rb +12 -3
- data/lib/logidze/history.rb +7 -15
- data/lib/logidze/history/type.rb +1 -1
- data/lib/logidze/history/version.rb +6 -5
- data/lib/logidze/ignore_log_data.rb +22 -0
- data/lib/logidze/ignore_log_data/cast_attribute_patch.rb +17 -0
- data/lib/logidze/meta.rb +44 -17
- data/lib/logidze/model.rb +65 -37
- data/lib/logidze/version.rb +2 -1
- data/lib/logidze/versioned_association.rb +0 -1
- metadata +47 -103
- data/.gitignore +0 -40
- data/.hound.yml +0 -3
- data/.rubocop.yml +0 -94
- data/.travis.yml +0 -39
- data/Gemfile +0 -13
- data/Rakefile +0 -28
- data/bench/performance/README.md +0 -109
- data/bench/performance/diff_bench.rb +0 -36
- data/bench/performance/insert_bench.rb +0 -20
- data/bench/performance/memory_profile.rb +0 -53
- data/bench/performance/setup.rb +0 -308
- data/bench/performance/update_bench.rb +0 -36
- data/bench/triggers/Makefile +0 -56
- data/bench/triggers/Readme.md +0 -58
- data/bench/triggers/bench.sql +0 -6
- data/bench/triggers/hstore_trigger_setup.sql +0 -38
- data/bench/triggers/jsonb_minus_2_setup.sql +0 -47
- data/bench/triggers/jsonb_minus_setup.sql +0 -49
- data/bench/triggers/keys2_trigger_setup.sql +0 -44
- data/bench/triggers/keys_trigger_setup.sql +0 -50
- data/bin/console +0 -8
- data/bin/setup +0 -9
- data/gemfiles/rails42.gemfile +0 -5
- data/gemfiles/rails5.gemfile +0 -6
- data/gemfiles/rails52.gemfile +0 -6
- data/gemfiles/railsmaster.gemfile +0 -7
- data/lib/logidze/migration.rb +0 -19
- data/logidze.gemspec +0 -33
data/bench/performance/setup.rb
DELETED
@@ -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
|
data/bench/triggers/Makefile
DELETED
@@ -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)
|
data/bench/triggers/Readme.md
DELETED
@@ -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.
|