postqueue 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0ab5c97e45f6171bf141d58d1cd8ce8c5524f5b2
4
- data.tar.gz: 0a1b8ef785008bec26a6907024617a4ef4a82a3e
3
+ metadata.gz: 169eb71de24fab5bde343459df49bf1395881fc4
4
+ data.tar.gz: fb06468f9fe5412dc655188823d5544be0ba9d48
5
5
  SHA512:
6
- metadata.gz: 31fad4af31402d2084e07c0b60baa14bce82a53efba6714056956f52da1360cfd5b92a6a0f6599789f687290ec743f867604346c4bc7a16c930aa52a69071585
7
- data.tar.gz: a7c2117c1abdee7840cfa66714e99ad15bd8a02456454d4f4d44ce9a00e019df5224e722c7e4f415116331bb3c3e2a08bb14b77d0204dd9c51a0941dcaaa305a
6
+ metadata.gz: 5589efb14a39dedb9b56c207eaffdbef1e8533cc9717991a0b8d2cdb12a0966b15cbf712b7720ff2df3c1b0e75b7f66f9402b8bfa9eeb3b62308cff3cd6ed6a6
7
+ data.tar.gz: 26cf306354c7a91c9c1bb172cca2aaf2639195aa25c0b9005d4244335f9a8e46c5a85848fa78afe869addb0e0d03423eba53c1bcebb0f3128b3d00abae3bd192
@@ -1,4 +1,5 @@
1
1
  require "active_record"
2
+ require "simple/sql"
2
3
 
3
4
  module Postqueue
4
5
  #
@@ -7,52 +8,11 @@ module Postqueue
7
8
  # This source file provides multiple implementations to insert Postqueue::Items.
8
9
  # Which one will be used depends on the "extend XXXInserter" line below.
9
10
  class Item < ActiveRecord::Base
10
- module ActiveRecordInserter
11
- def insert_item(op:, entity_id:)
12
- create!(op: op, entity_id: entity_id)
13
- end
11
+ def self.insert_item(op:, entity_id:)
12
+ # In contrast to ActiveRecord, which clocks in around 600µs per item,
13
+ # Simple::SQL's insert only takes 100µs per item. Using prepared
14
+ # statements would further reduce the runtime to 50µs
15
+ ::Simple::SQL.insert table_name, op: op, entity_id: entity_id
14
16
  end
15
-
16
- module RawInserter
17
- def insert_sql
18
- "INSERT INTO #{table_name}(op, entity_id) VALUES($1, $2)"
19
- end
20
-
21
- def insert_item(op:, entity_id:)
22
- connection.raw_connection.exec_params(insert_sql, [op, entity_id])
23
- end
24
- end
25
-
26
- module PreparedRawInserter
27
- def insert_sql
28
- "INSERT INTO #{table_name}(op, entity_id) VALUES($1, $2)"
29
- end
30
-
31
- def prepared_inserter_statement(raw_connection)
32
- @prepared_inserter_statements ||= {}
33
-
34
- # a prepared connection is PER DATABASE CONNECTION. It is not shared across
35
- # connections, and it is not per thread, since a Thread might use different
36
- # connections during its lifetime.
37
- @prepared_inserter_statements[raw_connection.object_id] ||= create_prepared_inserter_statement(raw_connection)
38
- end
39
-
40
- # prepares the INSERT statement, and returns its name
41
- def create_prepared_inserter_statement(raw_connection)
42
- name = "postqueue-insert-#{table_name}-#{raw_connection.object_id}"
43
- raw_connection.prepare(name, insert_sql)
44
- name
45
- end
46
-
47
- def insert_item(op:, entity_id:)
48
- raw_connection = connection.raw_connection
49
- statement_name = prepared_inserter_statement(raw_connection)
50
- raw_connection.exec_prepared(statement_name, [op, entity_id])
51
- end
52
- end
53
-
54
- # extend ActiveRecordInserter # 600µs per item
55
- extend RawInserter # 100µs per item
56
- # extend PreparedRawInserter # 50µs per item
57
17
  end
58
18
  end
@@ -0,0 +1,198 @@
1
+ -- An tracker history is important on most tables. Provide an tracker trigger that logs to
2
+ -- a dedicated tracker table for the major relations.
3
+ --
4
+ -- This file should be generic and not depend on application roles or structures,
5
+ -- as it's being listed here:
6
+ --
7
+ -- https://wiki.postgresql.org/wiki/Audit_trigger_91plus
8
+ --
9
+ -- This trigger was originally based on
10
+ -- http://wiki.postgresql.org/wiki/Audit_trigger
11
+ -- but has been completely rewritten.
12
+ --
13
+ -- Should really be converted into a relocatable EXTENSION, with control and upgrade files.
14
+
15
+ CREATE EXTENSION IF NOT EXISTS hstore;
16
+
17
+ <% if true %>
18
+ DROP SCHEMA IF EXISTS tracker CASCADE;
19
+ <% end %>
20
+ CREATE SCHEMA tracker;
21
+ REVOKE ALL ON SCHEMA tracker FROM public;
22
+
23
+ COMMENT ON SCHEMA tracker IS 'Out-of-table tracing';
24
+
25
+ CREATE TYPE tracker.actions AS ENUM(
26
+ 'INSERT', 'UPDATE', 'DELETE'
27
+ );
28
+
29
+ --
30
+ -- Audited data. Lots of information is available, it's just a matter of how much
31
+ -- you really want to record. See:
32
+ --
33
+ -- http://www.postgresql.org/docs/9.1/static/functions-info.html
34
+ --
35
+ -- Remember, every column you add takes up more tracker table space and slows tracker
36
+ -- inserts.
37
+ --
38
+ -- Every index you add has a big impact too, so avoid adding indexes to the
39
+ -- tracker table unless you REALLY need them. The hstore GIST indexes are
40
+ -- particularly expensive.
41
+ --
42
+ -- It is sometimes worth copying the tracker table, or a coarse subset of it that
43
+ -- you're interested in, into a temporary table where you CREATE any useful
44
+ -- indexes and do your analysis.
45
+ --
46
+ CREATE TABLE tracker.events (
47
+ id bigserial primary key, -- id IS 'Unique identifier for each tracked event';
48
+ row_data hstore, -- row_data IS 'Record value. For INSERT this is the new tuple. For DELETE and UPDATE it is the old tuple.';
49
+ changed_fields hstore, -- changed_fields IS 'New values of fields changed by UPDATE. Null except for UPDATE events.';
50
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL, -- created_at IS 'Wall clock time at which tracked event''s trigger call occurred';
51
+ checked_out_at TIMESTAMP WITH TIME ZONE -- checked_out_at IS 'Wall clock time at which event is checked out for processing';
52
+ );
53
+
54
+
55
+ REVOKE ALL ON tracker.events FROM public;
56
+
57
+ /*
58
+
59
+ [TODO] - add comments on columns
60
+
61
+ */
62
+
63
+ COMMENT ON TABLE tracker.events IS 'History of tracked actions on tracked tables, from tracker.if_modified_func()';
64
+ -- COMMENT ON COLUMN tracker.events.session_user_name IS 'Login / session user whose statement caused the tracked event';
65
+
66
+ /*
67
+
68
+ [TODO] - create indices for common search patterns
69
+
70
+ */
71
+
72
+ -- CREATE INDEX events_table_oid_idx ON tracker.events(table_oid);
73
+ -- CREATE INDEX events_action_idx ON tracker.events(action);
74
+
75
+ /* [TODO] - change checked_out_at to default to 'infinity' */
76
+
77
+
78
+ /*
79
+
80
+ Track changes to a table at row level.
81
+
82
+ Optional parameters to trigger in CREATE TRIGGER call:
83
+
84
+ param 0: text, name of primary key column;
85
+ param 1: text[], columns to ignore in updates. Default [].
86
+
87
+ Updates to ignored cols are omitted from changed_fields.
88
+
89
+ Updates with only ignored cols changed are not inserted
90
+ into the tracker log.
91
+
92
+ Note that the user name logged is the login role for the session.
93
+ The tracker trigger cannot obtain the active role because it is reset
94
+ by the SECURITY DEFINER invocation of the tracker trigger its self.
95
+
96
+ */
97
+
98
+ CREATE OR REPLACE FUNCTION tracker.if_modified_func() RETURNS TRIGGER AS $body$
99
+ DECLARE
100
+ event fq_table_name;
101
+ h_old hstore;
102
+ h_new hstore;
103
+ entity_pkey_name text;
104
+ BEGIN
105
+ IF TG_WHEN <> 'AFTER' THEN
106
+ RAISE EXCEPTION 'tracker.if_modified_func() may only run as an AFTER trigger';
107
+ END IF;
108
+
109
+ -- get args
110
+
111
+ entity_pkey_name = TG_ARGV[0];
112
+
113
+ -- fill row
114
+
115
+ IF (TG_OP = 'UPDATE') THEN
116
+ event.new_fields = jsonb(NEW.*);
117
+ event.old_fields = jsonb(OLD.*);
118
+ event.entity_id = event.new_fields->entity_pkey_name
119
+ ELSIF (TG_OP = 'DELETE') THEN
120
+ event.old_fields = jsonb(OLD.*);
121
+ event.entity_id = event.old_fields->entity_pkey_name
122
+ ELSIF (TG_OP = 'INSERT') THEN
123
+ event.new_fields = jsonb(NEW.*);
124
+ event.entity_id = event.new_fields->entity_pkey_name
125
+ END IF;
126
+
127
+ INSERT INTO tracker.events VALUES (event.*);
128
+ RETURN NULL;
129
+ END;
130
+ $body$
131
+ LANGUAGE plpgsql
132
+ SECURITY DEFINER
133
+ SET search_path = pg_catalog, public;
134
+
135
+ /*
136
+
137
+ Add tracking support to a table.
138
+
139
+ Arguments:
140
+ target_table: Table name, schema qualified if not on search_path
141
+ primary_key_name: Name of primary key column
142
+ ignored_cols: Columns to exclude from update diffs, ignore updates that change only ignored cols.
143
+
144
+ */
145
+
146
+ CREATE OR REPLACE FUNCTION tracker.track_table(target_table regclass, entity_pkey_name text, ignored_cols text[])
147
+ RETURNS void AS $body$
148
+ DECLARE
149
+ _q_txt text;
150
+ _ignored_cols_snip text = '';
151
+ BEGIN
152
+ IF array_length(ignored_cols,1) > 0 THEN
153
+ _ignored_cols_snip = ', ' || quote_literal(ignored_cols);
154
+ END IF;
155
+
156
+ EXECUTE 'DROP TRIGGER IF EXISTS track_trigger_row ON ' || quote_ident(target_table::TEXT);
157
+
158
+ _q_txt = 'CREATE TRIGGER track_trigger_row AFTER INSERT OR UPDATE OR DELETE ON ' ||
159
+ quote_ident(target_table::TEXT) ||
160
+ ' FOR EACH ROW EXECUTE PROCEDURE tracker.if_modified_func(' ||
161
+ quote_literal(entity_pkey_name) ||
162
+ _ignored_cols_snip || ');';
163
+ EXECUTE _q_txt;
164
+ END;
165
+ $body$
166
+ language 'plpgsql';
167
+
168
+
169
+ /*
170
+
171
+ Add tracking support to the given table. No cols are ignored. (Shortcut)
172
+
173
+ */
174
+
175
+ CREATE OR REPLACE FUNCTION tracker.track_table(target_table regclass) RETURNS void AS $body$
176
+ SELECT tracker.track_table($1, 'id', ARRAY[]::text[]);
177
+ $body$ LANGUAGE 'sql';
178
+
179
+ COMMENT ON FUNCTION tracker.track_table(regclass) IS $body$
180
+ $body$;
181
+
182
+
183
+ --
184
+ ------------------------------------------------------------------------------------
185
+ --
186
+
187
+ -- SELECT tracker.track_table('public.posts');
188
+
189
+ --SELECT tracker.track_table('public.users', 'id', array['created_at','updated_at']);
190
+ -- -- SELECT tracker.track_table('public.users', ARRAY[]::text[]);
191
+ -- -- SELECT tracker.track_table('public.users');
192
+ --
193
+ -- INSERT INTO users (email, created_at, updated_at) VALUES('me@mo' || (1000 * random())::integer , NOW(), NOW());
194
+ -- UPDATE users SET username='mimi' WHERE email LIKE '%mo%';
195
+ -- UPDATE users SET username='momo' WHERE email LIKE '%mo%';
196
+ -- DELETE FROM users WHERE email LIKE '%mo%';
197
+ --
198
+ -- SELECT table_schema || '.' || table_name || '.' || entity_pkey, action, row_data, changed_fields, created_at FROM tracker.events;
@@ -1,3 +1,3 @@
1
1
  module Postqueue
2
- VERSION = "0.6.1"
2
+ VERSION = "0.7.0"
3
3
  end
data/spec/spec_helper.rb CHANGED
@@ -11,7 +11,10 @@ end
11
11
  require "postqueue"
12
12
  require "./spec/support/configure_active_record"
13
13
 
14
- Postqueue.logger = Logger.new(File.open("log/test.log", "a"))
14
+ logger = Logger.new(File.open("log/test.log", "a"))
15
+ logger.level = Logger::INFO
16
+
17
+ Simple::SQL.logger = Postqueue.logger = logger
15
18
 
16
19
  RSpec.configure do |config|
17
20
  config.run_all_when_everything_filtered = true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postqueue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - radiospiel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-15 00:00:00.000000000 Z
11
+ date: 2018-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simple-sql
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.2.6
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.2.6
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: table_print
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +191,7 @@ files:
177
191
  - lib/postqueue/queue/runner.rb
178
192
  - lib/postqueue/queue/select_and_lock.rb
179
193
  - lib/postqueue/queue/timing.rb
194
+ - lib/postqueue/tracker/tracker.sql
180
195
  - lib/postqueue/version.rb
181
196
  - lib/tracker.rb
182
197
  - lib/tracker/advisory_lock.rb