-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathschema.sql
303 lines (275 loc) · 10.3 KB
/
schema.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
CREATE DATABASE "iwwc-stats";
CREATE USER "iwwc-stats";
ALTER USER "iwwc-stats" LOGIN;
GRANT ALL PRIVILEGES ON DATABASE "iwwc-stats" TO "iwwc-stats";
\c "iwwc-stats" -
GRANT ALL PRIVILEGES ON SCHEMA public TO "iwwc-stats";
\c - "iwwc-stats"
\conninfo
show search_path;
CREATE SEQUENCE IF NOT EXISTS agent_pk;
CREATE TABLE IF NOT EXISTS agent (
agent_id INTEGER DEFAULT nextval('agent_pk') NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE
);
CREATE SEQUENCE IF NOT EXISTS stat_pk;
CREATE TABLE IF NOT EXISTS stat (
stat_id INTEGER DEFAULT nextval('stat_pk') NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE
);
CREATE SEQUENCE IF NOT EXISTS upload_pk;
CREATE TABLE IF NOT EXISTS upload (
upload_id INTEGER DEFAULT nextval('upload_pk') NOT NULL PRIMARY KEY,
agent_id INTEGER REFERENCES agent(agent_id),
uploaded_at TIMESTAMP NOT NULL,
faction VARCHAR(255),
UNIQUE (agent_id, uploaded_at)
);
CREATE INDEX IF NOT EXISTS upload_at ON upload(uploaded_at);
CREATE SEQUENCE IF NOT EXISTS stat_pk;
CREATE TABLE IF NOT EXISTS history (
upload_id INTEGER REFERENCES upload(upload_id),
stat_id INTEGER REFERENCES stat(stat_id),
num_value DECIMAL,
str_value VARCHAR(255),
PRIMARY KEY (stat_id, upload_id)
);
CREATE SEQUENCE IF NOT EXISTS source_pk;
CREATE TABLE IF NOT EXISTS source (
source_id INTEGER DEFAULT nextval('source_pk') NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE
);
CREATE SEQUENCE IF NOT EXISTS league_pk;
CREATE TABLE IF NOT EXISTS league (
league_id INTEGER DEFAULT nextval('league_pk') NOT NULL PRIMARY KEY,
source_id INTEGER REFERENCES source(source_id),
external_id TEXT NOT NULL,
UNIQUE (source_id, external_id)
);
CREATE TABLE IF NOT EXISTS league_membership (
league_id INTEGER REFERENCES league(league_id),
agent_id INTEGER REFERENCES agent(agent_id),
from_date TIMESTAMP WITH TIME ZONE NOT NULL,
thru_date TIMESTAMP WITH TIME ZONE,
UNIQUE (league_id, agent_id, from_date)
);
CREATE OR REPLACE FUNCTION isnumeric(text)
RETURNS pg_catalog.bool AS $BODY$
DECLARE x NUMERIC;
BEGIN
x = $1::NUMERIC;
RETURN TRUE;
EXCEPTION WHEN others THEN
RETURN FALSE;
END;
$BODY$
LANGUAGE 'plpgsql' IMMUTABLE STRICT COST 100
;
DROP FUNCTION IF EXISTS import_agent_upload(agent_name TEXT, agent_info JSONB);
CREATE FUNCTION import_agent_upload(_agent_name TEXT, _agent_info JSONB) RETURNS VOID
SECURITY INVOKER AS $$
DECLARE
_agent_id INTEGER;
_upload_id INTEGER;
_uploaded_at TIMESTAMP;
_old_faction TEXT;
_new_faction TEXT;
_key TEXT;
_value TEXT;
_stat_id INTEGER;
_new_num_value DECIMAL;
_new_str_value TEXT;
BEGIN
INSERT INTO agent(name) VALUES (_agent_name) ON CONFLICT DO NOTHING;
SELECT agent_id INTO _agent_id FROM agent WHERE name = _agent_name;
_uploaded_at := (_agent_info ->> 'last_submit')::timestamp;
_new_faction := (_agent_info ->> 'faction');
INSERT INTO upload(agent_id, uploaded_at, faction) VALUES (_agent_id, _uploaded_at, _new_faction) ON CONFLICT DO NOTHING;
SELECT upload_id, faction INTO _upload_id, _old_faction FROM upload WHERE agent_id = _agent_id AND uploaded_at = _uploaded_at;
IF _new_faction != _old_faction THEN
UPDATE upload SET faction = _new_faction WHERE agent_id = _agent_id AND uploaded_at = _uploaded_at;
END IF;
-- RAISE NOTICE 'import_agent_upload(%=%, %=%)" ', _agent_name, _agent_id, _uploaded_at, _upload_id;
FOR _key, _value IN
SELECT * FROM jsonb_each_text(_agent_info)
LOOP
IF _key = 'faction' THEN
ELSIF _key = 'last_submit' THEN
ELSE
INSERT INTO stat(name) VALUES (_key) ON CONFLICT DO NOTHING;
SELECT stat_id INTO _stat_id FROM stat WHERE name = _key;
_new_num_value := CASE isnumeric(_value) WHEN TRUE THEN _value::numeric ELSE NULL END;
_new_str_value := CASE isnumeric(_value) WHEN TRUE THEN NULL ELSE _value END;
INSERT INTO history
(upload_id, stat_id, num_value, str_value)
VALUES
(
_upload_id,
_stat_id,
_new_num_value,
_new_str_value
)
ON CONFLICT DO NOTHING;
IF NOT EXISTS (SELECT 1 FROM history WHERE upload_id = _upload_id AND stat_id = _stat_id AND num_value = _new_num_value AND str_value = _new_str_value) THEN
UPDATE history SET num_value = _new_num_value, str_value = _new_str_value WHERE upload_id = _upload_id AND stat_id = _stat_id;
END IF;
END IF;
END LOOP;
END
$$ LANGUAGE 'plpgsql' VOLATILE;
CREATE SEQUENCE IF NOT EXISTS source_pk;
CREATE TABLE IF NOT EXISTS source (
source_id INTEGER DEFAULT nextval('source_pk') NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE
);
CREATE SEQUENCE IF NOT EXISTS league_pk;
CREATE TABLE IF NOT EXISTS league (
league_id INTEGER DEFAULT nextval('league_pk') NOT NULL PRIMARY KEY,
source_id INTEGER REFERENCES source(source_id),
external_id TEXT NOT NULL,
UNIQUE (source_id, external_id)
);
CREATE TABLE IF NOT EXISTS league_membership (
league_id INTEGER REFERENCES league(league_id),
agent_id INTEGER REFERENCES agent(agent_id),
from_date TIMESTAMP WITH TIME ZONE NOT NULL,
thru_date TIMESTAMP WITH TIME ZONE,
UNIQUE (league_id, agent_id, from_date)
);
CREATE OR REPLACE FUNCTION _ensure_agent_id(_agent_name TEXT) RETURNS INTEGER
SECURITY INVOKER AS $$
DECLARE
_agent_id INTEGER;
BEGIN
INSERT INTO agent(name) VALUES (_agent_name) ON CONFLICT DO NOTHING;
SELECT agent_id INTO _agent_id FROM agent WHERE name = _agent_name;
RETURN _agent_id;
END
$$ LANGUAGE 'plpgsql' VOLATILE;
CREATE OR REPLACE FUNCTION _ensure_stat_id(_stat_name TEXT) RETURNS INTEGER
SECURITY INVOKER AS $$
DECLARE
_stat_id INTEGER;
BEGIN
INSERT INTO stat(name) VALUES (_stat_name) ON CONFLICT DO NOTHING;
SELECT stat_id INTO _stat_id FROM stat WHERE name = _stat_name;
RETURN _stat_id;
END
$$ LANGUAGE 'plpgsql' VOLATILE;
CREATE OR REPLACE FUNCTION _ensure_upload_id(_agent_id INTEGER, _uploaded_at TIMESTAMP WITHOUT TIME ZONE, _faction TEXT) RETURNS INTEGER
SECURITY INVOKER AS $$
DECLARE
_upload_id INTEGER;
_is_new BOOLEAN;
_current_faction TEXT;
BEGIN
INSERT INTO upload(agent_id, uploaded_at, faction) VALUES (_agent_Id, _uploaded_at, _faction) ON CONFLICT DO NOTHING RETURNING upload_id, faction, (xmax = 0) INTO _upload_id, _current_faction, _is_new;
IF _is_new THEN
RETURN _upload_id;
END IF;
IF _faction != _current_faction THEN
UPDATE upload SET faction = _faction WHERE agent_id = _agent_id AND uploaded_at = _uploaded_at;
END IF;
RETURN _upload_id;
END
$$ LANGUAGE 'plpgsql' VOLATILE;
CREATE OR REPLACE FUNCTION _ensure_source_id(_source_name TEXT) RETURNS INTEGER
SECURITY INVOKER AS $$
DECLARE
_source_id INTEGER;
BEGIN
INSERT INTO source(name) VALUES (_source_name) ON CONFLICT DO NOTHING;
SELECT source_id INTO _source_id FROM source WHERE name = _source_name;
RETURN _source_id;
END
$$ LANGUAGE 'plpgsql' VOLATILE;
CREATE OR REPLACE FUNCTION _ensure_league_id(_source_id INTEGER, _league_name TEXT) RETURNS INTEGER
SECURITY INVOKER AS $$
DECLARE
_league_id INTEGER;
BEGIN
INSERT INTO league(source_id, external_id) VALUES (_source_id, _league_name) ON CONFLICT DO NOTHING;
SELECT league_id INTO _league_id FROM league WHERE source_id = _source_id AND external_id = _league_name;
RETURN _league_id;
END
$$ LANGUAGE 'plpgsql' VOLATILE;
CREATE OR REPLACE FUNCTION _ensure_history_entry(_upload_id INTEGER, _stat_name TEXT, _stat_value JSONB) RETURNS VOID
SECURITY INVOKER AS $$
DECLARE
_stat_id INTEGER := _ensure_stat_id(_stat_name);
_num_value DECIMAL;
_str_value TEXT;
BEGIN
CASE jsonb_typeof(_stat_value)
WHEN 'string', 'number' THEN
ELSE
RAISE EXCEPTION 'Unknown type of value % => %', _stat_name, _stat_value;
END CASE;
_num_value := CASE jsonb_typeof(_stat_value) WHEN 'number' THEN _stat_value ELSE NULL END;
_str_value := CASE jsonb_typeof(_stat_value) WHEN 'string' THEN _stat_value ELSE NULL END;
INSERT INTO history
(upload_id, stat_id, num_value, str_value)
VALUES
(
_upload_id,
_stat_id,
_num_value,
_str_value
)
ON CONFLICT DO NOTHING;
IF NOT EXISTS (SELECT 1 FROM history WHERE upload_id = _upload_id AND stat_id = _stat_id AND num_value = _num_value AND str_value = _str_value) THEN
UPDATE history SET num_value = _num_value, str_value = _str_value WHERE upload_id = _upload_id AND stat_id = _stat_id;
END IF;
END
$$ LANGUAGE 'plpgsql' VOLATILE;
DROP FUNCTION IF EXISTS bulk_as_import(_source_name TEXT, _league_name TEXT, _timestamp TEXT, _custom JSONB);
CREATE FUNCTION bulk_as_import(_source_name TEXT, _league_name TEXT, _timestamp TEXT, _custom JSONB) RETURNS VOID
SECURITY INVOKER AS $$
DECLARE
_source_id INTEGER := _ensure_source_id(_source_name);
_league_id INTEGER := _ensure_league_id(_source_id, _league_name);
_from_date TIMESTAMP WITH TIME ZONE := _timestamp::TIMESTAMP WITH TIME ZONE;
_agent_name TEXT;
_agent_info JSONB;
_key TEXT;
_value JSONB;
_agent_id INTEGER;
_upload_id INTEGER;
_agent_ids INTEGER[] DEFAULT '{}';
BEGIN
FOR _agent_name, _agent_info IN SELECT * FROM jsonb_each(_custom) LOOP
_agent_id := _ensure_agent_id(_agent_name);
_agent_ids := _agent_ids || _agent_id;
_upload_id := _ensure_upload_id(_agent_id, (_agent_info ->> 'last_submit')::timestamp, (_agent_info ->> 'faction'));
IF _upload_id IS NULL THEN
CONTINUE;
END IF;
FOR _key, _value IN SELECT * FROM jsonb_each(_agent_info) LOOP
CASE _key
WHEN 'last_submit' THEN
CONTINUE;
ELSE
END CASE;
PERFORM _ensure_history_entry(_upload_id, _key, _value);
END LOOP;
END LOOP;
UPDATE league_membership SET thru_date = _from_date WHERE thru_date IS NULL AND league_id = league_id AND NOT agent_id = ANY (_agent_ids);
INSERT INTO league_membership (league_id, agent_id, from_date) SELECT _league_id, arr.agent_id, _from_date FROM unnest(_agent_ids) AS arr(agent_id) WHERE arr.agent_id NOT IN (SELECT agent_id FROM league_membership WHERE league_id = _league_id AND from_date <= from_date AND thru_date IS NULL) ON CONFLICT DO NOTHING;
END
$$ LANGUAGE 'plpgsql' VOLATILE;
CREATE OR REPLACE AGGREGATE jsonb_object_agg(jsonb) (
SFUNC = 'jsonb_concat',
STYPE = jsonb,
INITCOND = '{}'
);
DROP FUNCTION IF EXISTS agent_info(_upload UPLOAD);
CREATE FUNCTION agent_info(_upload upload) RETURNS JSONB
SECURITY INVOKER AS $$
SELECT jsonb_object_agg(val::jsonb) FROM (
SELECT json_object_agg(stat.name, history.num_value) FROM history JOIN stat USING (stat_id) WHERE history.upload_id = _upload.upload_id
UNION ALL
SELECT json_build_object('faction', _upload.faction)
UNION ALL
SELECT json_build_object('last_submit', _upload.uploaded_at)
) a(val)
$$ LANGUAGE 'sql' STABLE;