-
Notifications
You must be signed in to change notification settings - Fork 0
/
schema.sql
397 lines (355 loc) · 11.2 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
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
-- Transaction isolation (see https://github.com/cavoke-project/cavoke/issues/178)
set default_transaction_isolation to 'serializable';
create table users
(
id varchar not null
constraint user_pk
primary key,
display_name varchar default 'Guest':: character varying
);
create table sessions
(
id uuid not null
constraint session_pk
primary key,
game_id varchar not null,
host_id varchar null,
game_settings json,
constraint fk_host foreign key (host_id) references users (id)
);
create table rooms
(
id uuid not null
constraint room_pk
primary key,
display_name varchar not null,
invite_code varchar not null,
host_id varchar null,
constraint fk_host foreign key (host_id) references users (id)
);
create table room_joins
(
room_id uuid not null
constraint joins_room_id_fk
references rooms
on delete cascade,
user_id varchar not null
constraint joins_user_id_fk
references users
on delete restrict,
constraint room_join_pk
primary key (room_id, user_id)
);
alter table rooms
add constraint rooms_joins_room_id_user_id_fk
foreign key (id, host_id) references room_joins (room_id, user_id);
alter table rooms
add column session_id uuid null;
alter table rooms
add constraint fk_session foreign key (session_id) references sessions (id);
create table statuses
(
session_id uuid not null
constraint status_session_id_fk
references sessions
on delete cascade,
status integer,
saved_on timestamp default current_timestamp
);
create unique index session_id_uindex
on sessions (id);
create unique index room_invite_code_uindex
on rooms (invite_code);
create table players
(
session_id uuid not null
constraint player_session_id_fk
references sessions
on delete cascade,
user_id varchar not null
constraint player_user_id_fk
references users
on delete restrict,
player_id integer not null,
score integer,
playerstate text not null,
constraint player_pk
primary key (session_id, user_id),
constraint uq_playerid_sessionid
unique (session_id, player_id)
);
create table globalstates
(
session_id uuid not null
constraint globalstates_pk
primary key
constraint globalstates_sessions_id_fk
references sessions
on delete cascade,
globalstate text,
is_terminal boolean default false not null,
saved_on timestamp default current_timestamp
);
create table gamesubmissions
(
id uuid default gen_random_uuid() not null
constraint gamesubmissions_pk
primary key,
game_id varchar,
package_type varchar,
git_repo varchar,
review_status integer default 0,
submitted_at timestamp default CURRENT_TIMESTAMP,
display_name varchar
);
create unique index gamesubmissions_id_uindex
on gamesubmissions (id);
create or replace function leave_session(m_session_id uuid, m_user_id varchar) returns void as
$$
declare
begin
if
(select host_id
from sessions
where id = m_session_id) = m_user_id then
if (select user_id
from players
where session_id = m_session_id
and user_id != m_user_id
limit 1) IS NOT NULL then
update sessions
set host_id = (select user_id from players where session_id = m_session_id and user_id != m_user_id limit 1)
where id = m_session_id;
else
update rooms
set session_id = NULL
where rooms.session_id = m_session_id;
delete
from sessions
where id = m_session_id;
end if;
end if;
delete
from players
where user_id = m_user_id
and session_id = m_session_id;
end
$$
language plpgsql;
create or replace function leave_room(m_room_id uuid, m_user_id varchar) returns void as
$$
declare
begin
if
(select host_id
from rooms
where id = m_room_id) = m_user_id then
if (select user_id
from room_joins
where room_id = m_room_id
and user_id != m_user_id
limit 1) IS NOT NULL then
update rooms
set host_id = (select user_id from room_joins where room_id = m_room_id and user_id != m_user_id limit 1)
where id = m_room_id;
else
delete
from rooms
where id = m_room_id;
end if;
end if;
delete
from room_joins
where user_id = m_user_id
and room_id = m_room_id;
end
$$
language plpgsql;
-- STAISTICS
create or replace function get_sessions_info(game_id_ varchar)
returns table
(
session_id uuid,
duration numeric,
max_status integer
)
as
$$
declare
begin
-- noinspection SqlAggregates
RETURN QUERY SELECT statuses.session_id,
EXTRACT(epoch from (MAX(statuses.saved_on) - MIN(statuses.saved_on))) as duration,
MAX(statuses.status) as max_status
FROM (statuses inner join sessions s on s.id = statuses.session_id)
WHERE s.game_id = game_id_
and (statuses.status = 1 or statuses.status = 2)
GROUP BY statuses.session_id
HAVING MAX(statuses.status) >= 2;
end;
$$
language plpgsql;
create or replace function get_average_session_time_sec(game_id_ varchar) returns int as
$$
declare
begin
RETURN (SELECT AVG(durations.duration)
FROM get_sessions_info(game_id_) as durations);
end;
$$
language plpgsql;
create or replace function get_total_time_sec(game_id_ varchar) returns int as
$$
declare
begin
RETURN (SELECT COALESCE(SUM(durations.duration), 0)
FROM get_sessions_info(game_id_) as durations);
end;
$$
language plpgsql;
create or replace function get_sessions_num(game_id_ varchar) returns int as
$$
declare
begin
RETURN (SELECT COUNT(durations.duration)
FROM get_sessions_info(game_id_) as durations);
end;
$$
language plpgsql;
create or replace function get_average_players_num(game_id_ varchar) returns int as
$$
declare
begin
RETURN (SELECT AVG(players_nums.players_num)
FROM (SELECT COUNT(*) AS players_num
FROM (players inner join sessions s on s.id = players.session_id)
WHERE game_id = game_id_
GROUP BY session_id) as players_nums);
end;
$$
language plpgsql;
create or replace function get_participations(game_id_ varchar, user_id_ varchar)
returns table
(
session_id uuid,
player_id integer,
score integer
)
as
$$
declare
begin
RETURN QUERY SELECT players.session_id, players.player_id, players.score
FROM (players inner join sessions s on s.id = players.session_id)
WHERE players.user_id = user_id_
AND s.game_id = game_id_;
end;
$$
language plpgsql;
create or replace function get_total_time_sec_for_user(game_id_ varchar, user_id_ varchar) returns int as
$$
declare
begin
RETURN (SELECT COALESCE(SUM(user_sessions.duration), 0)
FROM (get_participations(game_id_, user_id_) p left join get_sessions_info(game_id_) s
on p.session_id = s.session_id) as user_sessions);
end;
$$
language plpgsql;
create or replace function get_sessions_num_for_user(game_id_ varchar, user_id_ varchar) returns int as
$$
declare
begin
RETURN (SELECT (COUNT(*) FILTER ( WHERE user_sessions.score is not null))
FROM (get_participations(game_id_, user_id_) p left join get_sessions_info(game_id_) s
on p.session_id = s.session_id) as user_sessions);
end;
$$
language plpgsql;
create or replace function get_win_rate(game_id_ varchar, user_id_ varchar) returns float as
$$
declare
begin
RETURN (SELECT CAST((COUNT(*) FILTER ( WHERE p.score > 0)) AS FLOAT) /
NULLIF((COUNT(*) FILTER ( WHERE p.score is not null)), 0)
FROM get_participations(game_id_, user_id_) as p);
end;
$$
language plpgsql;
create or replace function all_games()
returns table
(
game_id varchar
)
as
$$
declare
begin
RETURN QUERY (SELECT DISTINCT sessions.game_id FROM sessions);
end;
$$
language plpgsql;
create or replace function get_cavoke_time_sec_for_user(user_id_ varchar) returns int as
$$
declare
begin
RETURN (SELECT COALESCE(SUM(get_total_time_sec_for_user(g.game_id, user_id_)), 0)
FROM all_games() as g);
end;
$$
language plpgsql;
create or replace function get_cavoke_sessions_num_for_user(user_id_ varchar) returns int as
$$
declare
begin
RETURN (SELECT SUM(get_sessions_num_for_user(g.game_id, user_id_))
FROM all_games() as g);
end;
$$
language plpgsql;
create extension if not exists "pgcrypto";
-- Generates unique invite codes for `rooms`
create or replace function unique_short_invite_code()
returns trigger as
$$
declare
key text;
qry text;
found text;
len int;
begin
-- generate the first part of a query as a string with safely
-- escaped table name, using || to concat the parts
qry := 'SELECT invite_code FROM ' || quote_ident(TG_TABLE_NAME) || ' WHERE invite_code=';
len := 3;
-- This loop will probably only run once per call until we've generated
-- millions of ids.
loop
-- Generate our string bytes and re-encode as a hex string.
key := encode(gen_random_bytes(len), 'hex');
-- Concat the generated key (safely quoted) with the generated query
-- and run it.
-- SELECT invite_code FROM "test" WHERE invite_code='blahblah' INTO found
-- Now "found" will be the duplicated id or NULL.
execute qry || quote_literal(key) into found;
-- Check to see if found is NULL.
if found is null then
-- If we didn't find a collision then leave the LOOP.
exit;
end if;
-- We haven't EXITed yet, so return to the top of the LOOP
-- and try again with a longer key.
len := len + 1;
end loop;
-- We're replacing invite_code, regardless of what it was before
-- with our key variable.
NEW.invite_code = key;
return NEW;
end;
$$ language 'plpgsql';
-- Auto generates unique invite codes for `rooms`
create or replace trigger trigger_gen_invite_code
before insert
on rooms
for each row
execute procedure unique_short_invite_code();