logidze 0.0.1 → 0.1.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.
- checksums.yaml +4 -4
- data/.gitignore +40 -9
- data/.rubocop.yml +3 -1
- data/.travis.yml +36 -3
- data/CHANGELOG.md +0 -0
- data/Gemfile +0 -1
- data/README.md +144 -9
- data/Rakefile +20 -1
- data/bench/Makefile +37 -9
- data/bench/Readme.md +58 -0
- data/bench/jsonb_minus_2_setup.sql +47 -0
- data/bench/jsonb_minus_setup.sql +49 -0
- data/bench/keys2_trigger_setup.sql +44 -0
- data/bin/console +7 -0
- data/bin/setup +9 -0
- data/circle.yml +17 -0
- data/gemfiles/rails42.gemfile +5 -0
- data/gemfiles/rails5.gemfile +5 -0
- data/lib/generators/logidze/install/USAGE +7 -0
- data/lib/generators/logidze/install/install_generator.rb +25 -0
- data/lib/generators/logidze/install/templates/hstore.rb.erb +5 -0
- data/lib/generators/logidze/install/templates/migration.rb.erb +131 -0
- data/lib/generators/logidze/model/USAGE +8 -0
- data/lib/generators/logidze/model/model_generator.rb +43 -0
- data/lib/generators/logidze/model/templates/migration.rb.erb +18 -0
- data/lib/logidze.rb +21 -1
- data/lib/logidze/engine.rb +12 -0
- data/lib/logidze/has_logidze.rb +18 -0
- data/lib/logidze/history.rb +156 -0
- data/lib/logidze/model.rb +128 -0
- data/lib/logidze/version.rb +2 -1
- data/logidze.gemspec +7 -2
- metadata +101 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8df5f1ac5aa84c04a0874f222011418bb4ef0fb7
|
4
|
+
data.tar.gz: f354e24b771387fddfc094dd5ea902827d1f4deb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 971a0acde6848091d4944632b579a37ab71335a9d7cab813aa43eae054fa4ae5359b8d654403d1ed8d7145c53afb87917366404a616f73fc71bb6af84536e0e4
|
7
|
+
data.tar.gz: 61d8ad229bb3d56bb5468b005e7e4907af9195bae3d187934a2acb7906b381628fd188ce99d2fb0af013bff8e22f750ae3c00f3fb7496725605b754ef9bad5bb
|
data/.gitignore
CHANGED
@@ -1,9 +1,40 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
# Numerous always-ignore extensions
|
2
|
+
*.diff
|
3
|
+
*.err
|
4
|
+
*.orig
|
5
|
+
*.log
|
6
|
+
*.rej
|
7
|
+
*.swo
|
8
|
+
*.swp
|
9
|
+
*.vi
|
10
|
+
*~
|
11
|
+
*.sass-cache
|
12
|
+
*.iml
|
13
|
+
.idea/
|
14
|
+
|
15
|
+
# Sublime
|
16
|
+
*.sublime-project
|
17
|
+
*.sublime-workspace
|
18
|
+
|
19
|
+
# OS or Editor folders
|
20
|
+
.DS_Store
|
21
|
+
.cache
|
22
|
+
.project
|
23
|
+
.settings
|
24
|
+
.tmproj
|
25
|
+
Thumbs.db
|
26
|
+
|
27
|
+
.bundle/
|
28
|
+
log/*.log
|
29
|
+
*.gz
|
30
|
+
pkg/
|
31
|
+
spec/dummy/db/*.sqlite3
|
32
|
+
spec/dummy/db/*.sqlite3-journal
|
33
|
+
spec/dummy/tmp/
|
34
|
+
|
35
|
+
Gemfile.lock
|
36
|
+
Gemfile.local
|
37
|
+
.rspec
|
38
|
+
*.gem
|
39
|
+
tmp/
|
40
|
+
coverage/
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
@@ -1,4 +1,37 @@
|
|
1
1
|
language: ruby
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
cache: bundler
|
3
|
+
|
4
|
+
env:
|
5
|
+
global:
|
6
|
+
- LOGIDZE_DB_USER=postgres
|
7
|
+
- LOGIDZE_DB_NAME=logidze
|
8
|
+
|
9
|
+
before_install:
|
10
|
+
- sudo /etc/init.d/postgresql stop
|
11
|
+
- sudo apt-get -y remove --purge postgresql-9.1
|
12
|
+
- sudo apt-get -y remove --purge postgresql-9.2
|
13
|
+
- sudo apt-get -y remove --purge postgresql-9.3
|
14
|
+
- sudo apt-get -y remove --purge postgresql-9.4
|
15
|
+
- sudo apt-get -y autoremove
|
16
|
+
- sudo apt-key adv --keyserver keys.gnupg.net --recv-keys 7FCC7D46ACCC4CF8
|
17
|
+
- sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main 9.5" >> /etc/apt/sources.list.d/postgresql.list'
|
18
|
+
- sudo apt-get update
|
19
|
+
- sudo apt-get -y install postgresql-9.5
|
20
|
+
- sudo sh -c 'echo "local all postgres trust" > /etc/postgresql/9.5/main/pg_hba.conf'
|
21
|
+
- sudo sh -c 'echo -n "host all all 127.0.0.1/32 trust" >> /etc/postgresql/9.5/main/pg_hba.conf'
|
22
|
+
- sudo /etc/init.d/postgresql restart
|
23
|
+
- psql --version
|
24
|
+
|
25
|
+
before_script:
|
26
|
+
- bundle exec rake dummy:db:create
|
27
|
+
- psql -U postgres -d logidze -c 'CREATE EXTENSION IF NOT EXISTS hstore;'
|
28
|
+
- bundle exec rake dummy:db:test:prepare
|
29
|
+
|
30
|
+
matrix:
|
31
|
+
include:
|
32
|
+
- rvm: 2.3.0
|
33
|
+
gemfile: gemfiles/rails5.gemfile
|
34
|
+
- rvm: 2.3.0
|
35
|
+
gemfile: gemfiles/rails42.gemfile
|
36
|
+
allow_failures:
|
37
|
+
- gemfile: gemfiles/rails5.gemfile
|
data/CHANGELOG.md
ADDED
File without changes
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,35 +1,170 @@
|
|
1
|
-
[](https://rubygems.org/gems/logidze) [](https://rubygems.org/gems/logidze) [](https://travis-ci.org/palkan/logidze) [](https://circleci.com/gh/palkan/logidze/tree/master)
|
2
2
|
|
3
3
|
# Logidze
|
4
4
|
|
5
|
-
|
5
|
+
Logidze provides tools for logging DB records changes.
|
6
|
+
**This is not [audited](https://github.com/collectiveidea/audited) or [paper_trail](https://github.com/airblade/paper_trail) alternative!**
|
7
|
+
|
8
|
+
Logidze allows you to create DB-level log (using triggers) and gives you an API to browse this log.
|
9
|
+
Log is stored with the record itself in JSONB column. No additional tables required.
|
10
|
+
Currently, only PostgreSQL 9.5+ is supported.
|
11
|
+
|
12
|
+
Other requirements:
|
13
|
+
- Ruby ~>2.3;
|
14
|
+
- Rails ~>4.2;
|
15
|
+
|
16
|
+
<a href="https://evilmartians.com/">
|
17
|
+
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
6
18
|
|
7
19
|
## Installation
|
8
20
|
|
9
|
-
Add
|
21
|
+
1. Add Logidze your application's Gemfile:
|
10
22
|
|
11
23
|
```ruby
|
12
24
|
gem 'logidze'
|
13
25
|
```
|
14
26
|
|
15
|
-
|
27
|
+
2. Install required DB extensions and create trigger function:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
rails generate logidze:install
|
31
|
+
```
|
32
|
+
|
33
|
+
This creates migration for adding trigger function and enabling hstore extension.
|
34
|
+
|
35
|
+
Run migrations:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
rake db:migrate
|
39
|
+
```
|
40
|
+
|
41
|
+
3. Add log column and triggers to the model:
|
16
42
|
|
17
|
-
|
43
|
+
```ruby
|
44
|
+
rails generate logidze:model Post
|
45
|
+
rake db:migrate
|
46
|
+
```
|
18
47
|
|
19
|
-
|
48
|
+
You can provide `limit` option to `generate` to limit the size of the log (by default it's unlimited):
|
20
49
|
|
21
|
-
|
50
|
+
```ruby
|
51
|
+
rails generate logidze:mode Post --limit=10
|
52
|
+
```
|
53
|
+
|
54
|
+
This also adds `has_logidze` line to your model, which adds methods for working with logs.
|
22
55
|
|
23
56
|
## Usage
|
24
57
|
|
25
|
-
|
58
|
+
Your model now has `log_data` column which stores changes log.
|
59
|
+
|
60
|
+
To retrieve record version at a given time use `#at` or `#at!` methods:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
post = Post.find(27)
|
64
|
+
|
65
|
+
# Show current version
|
66
|
+
post.log_version #=> 3
|
67
|
+
|
68
|
+
# Show log size (number of versions)
|
69
|
+
post.log_size #=> 3
|
70
|
+
|
71
|
+
# Get copy of a record at a given time
|
72
|
+
old_post = post.at(2.days.ago)
|
73
|
+
|
74
|
+
# or revert the record itself to the previous state (without committing to DB)
|
75
|
+
post.at!('201-04-15 12:00:00')
|
76
|
+
|
77
|
+
# If no version found
|
78
|
+
post.at('1945-05-09 09:00:00') #=> nil
|
79
|
+
```
|
80
|
+
|
81
|
+
You can also get revision by version number:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
post.at_version(2)
|
85
|
+
```
|
86
|
+
|
87
|
+
It is also possible to get version for relations:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
Post.where(active: true).at(1.month.ago)
|
91
|
+
```
|
92
|
+
|
93
|
+
You can also get diff from specified time:
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
post.diff_from(1.hour.ago)
|
97
|
+
#=> { "id" => 27, "changes" => { "title" => { "old" => "Logidze sucks!", "new" => "Logidze rulz!" } } }
|
98
|
+
|
99
|
+
# the same for relations
|
100
|
+
Post.where(created_at: Time.zone.today.all_day).diff_from(1.hour.ago)
|
101
|
+
```
|
102
|
+
|
103
|
+
There are also `#undo!` and `#redo!` options (and more general `#switch_to!`):
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
# Revert record to the previous state (and stores this state in DB)
|
107
|
+
post.undo!
|
108
|
+
|
109
|
+
# You can now user redo! to revert back
|
110
|
+
post.redo!
|
111
|
+
|
112
|
+
# More generally you can revert record to arbitrary version
|
113
|
+
post.switch_to!(2)
|
114
|
+
```
|
115
|
+
|
116
|
+
If you update record after `#undo!` or `#switch_to!` you lose all "future" versions and `#redo!` is no longer possible.
|
117
|
+
|
118
|
+
## Disable logging temporary
|
119
|
+
|
120
|
+
If you want to make update without logging (e.g. mass update), you can turn it off the following way:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
Logidze.without_logging { Post.update_all(seen: true) }
|
124
|
+
|
125
|
+
# or
|
126
|
+
|
127
|
+
Post.without_logging { Post.update_all(seen: true) }
|
128
|
+
```
|
129
|
+
|
130
|
+
## Log format
|
131
|
+
|
132
|
+
The `log_data` column has the following format:
|
133
|
+
|
134
|
+
```js
|
135
|
+
{
|
136
|
+
"v": 2, // current record version,
|
137
|
+
"h": // list of changes
|
138
|
+
[
|
139
|
+
{
|
140
|
+
"v": 1, // change number
|
141
|
+
"ts": 1460805759352, // change timestamp in milliseconds
|
142
|
+
"c": {
|
143
|
+
"attr": "new value", // updated fields with new values
|
144
|
+
"attr2": "new value"
|
145
|
+
}
|
146
|
+
}
|
147
|
+
]
|
148
|
+
}
|
149
|
+
```
|
150
|
+
|
151
|
+
If you specified the limit in you trigger definition then log size would not exceed the specified size. When a new change occurs, and there is no more room for it, the two oldest changes get merged.
|
152
|
+
|
153
|
+
## Development
|
154
|
+
|
155
|
+
For development setup run `./bin/setup`. This runs `bundle install` and creates test DB.
|
26
156
|
|
27
157
|
## Contributing
|
28
158
|
|
29
159
|
Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/logidze.
|
30
160
|
|
31
161
|
|
162
|
+
## TODO
|
163
|
+
|
164
|
+
- Exclude columns from log
|
165
|
+
- Enhance `update_all` to support mass-logging
|
166
|
+
- Other DB adapters
|
167
|
+
|
32
168
|
## License
|
33
169
|
|
34
170
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
35
|
-
|
data/Rakefile
CHANGED
@@ -3,4 +3,23 @@ require "rspec/core/rake_task"
|
|
3
3
|
|
4
4
|
RSpec::Core::RakeTask.new(:spec)
|
5
5
|
|
6
|
-
|
6
|
+
namespace :dummy do
|
7
|
+
require_relative "spec/dummy/config/application"
|
8
|
+
Dummy::Application.load_tasks
|
9
|
+
end
|
10
|
+
|
11
|
+
task(:spec).clear
|
12
|
+
desc "Run specs other than spec/acceptance"
|
13
|
+
RSpec::Core::RakeTask.new("spec") do |task|
|
14
|
+
task.exclude_pattern = "spec/acceptance/**/*_spec.rb"
|
15
|
+
task.verbose = false
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Run acceptance specs in spec/acceptance"
|
19
|
+
RSpec::Core::RakeTask.new("spec:acceptance") do |task|
|
20
|
+
task.pattern = "spec/acceptance/**/*_spec.rb"
|
21
|
+
task.verbose = false
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Run the specs and acceptance tests"
|
25
|
+
task default: %w(spec spec:acceptance)
|
data/bench/Makefile
CHANGED
@@ -8,21 +8,49 @@ ifndef T
|
|
8
8
|
T = 10000
|
9
9
|
endif
|
10
10
|
|
11
|
-
all:
|
12
|
-
make plain
|
13
|
-
make hstore
|
14
|
-
make keys
|
11
|
+
all: plain hstore jsonb jsonb2 keys keys2
|
15
12
|
|
16
13
|
setup:
|
17
|
-
|
14
|
+
createdb $(DB) -w
|
15
|
+
psql -q -d $(DB) -c 'CREATE EXTENSION IF NOT EXISTS hstore;'
|
18
16
|
|
19
|
-
plain:
|
17
|
+
plain:
|
18
|
+
$(info )
|
19
|
+
$(info ====== [START] Update without triggers ======)
|
20
|
+
pgbench -i -q $(DB)
|
20
21
|
pgbench -f bench.sql -t $(T) -r $(DB)
|
21
22
|
|
22
|
-
hstore:
|
23
|
+
hstore:
|
24
|
+
$(info )
|
25
|
+
$(info ====== [START] Update with hstore-based triggers ======)
|
26
|
+
pgbench -i -q $(DB)
|
23
27
|
psql -q -d $(DB) -f hstore_trigger_setup.sql
|
24
|
-
pgbench -f bench.sql -t $(T) -r $(DB)
|
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)
|
25
36
|
|
26
|
-
|
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)
|
27
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
|
28
56
|
pgbench -f bench.sql -t $(T) -r $(DB)
|
data/bench/Readme.md
ADDED
@@ -0,0 +1,58 @@
|
|
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 through `DB` variable (by default "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 and jsonb variants are of the same efficiency (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 | 3805 | 0.106 |
|
52
|
+
| hstore | 3061 | 0.165 |
|
53
|
+
| jsonb | 3079 | 0.165 |
|
54
|
+
| jsonb2 | 3057 | 0.166 |
|
55
|
+
| keys | 2606 | 0.209 |
|
56
|
+
| keys2 | 2610 | 0.216 |
|
57
|
+
|
58
|
+
_Logidze_ uses jsonb variant.
|