Changed database layout

main
noah metz 2024-01-29 11:23:39 -07:00
parent e37708cac3
commit 90afd62436
1 changed files with 90 additions and 104 deletions

@ -46,82 +46,97 @@ init([DatabaseFile]) ->
end. end.
init_db(Database) -> init_db(Database) ->
ok = sqlite3:create_table(Database, divisions, [{division, integer, [not_null]}, ok = sqlite3:create_table(Database, divisions, [{division, integer, [not_null]}],
{psize, integer, [not_null]},
{qsize, integer, [not_null]},
{esize, integer, [not_null]}],
[{primary_key, [division]}]), [{primary_key, [division]}]),
ok = sqlite3:create_table(Database, teams, [{team, text, [not_null]}, ok = sqlite3:create_table(Database, teams, [{team, text, [not_null]},
{division, integer}, {division, integer},
{alliance, integer},
{inspection, blob}], {inspection, blob}],
[{primary_key, [team]}, [{primary_key, [team]},
{foreign_key, {[division], divisions, [division], ""}}]), {foreign_key, {[division], divisions, [division], ""}}]),
ok = sqlite3:create_table(Database, matches, [{division, integer, [not_null]}, ok = sqlite3:create_table(Database, matches, [{division, integer},
{type, text, [not_null]}, {type, text, [not_null]},
{number, integer, [not_null]}, {number, integer, [not_null]},
{red_1, text, [not_null]}, {instance, integer, [not_null]}],
{red_2, text, [not_null]}, [{primary_key, [division, type, number, instance]},
{blue_1, text, [not_null]}, {check, "type IN ('practice', 'qualification', 'elimination', 'final')"},
{blue_2, text, [not_null]}], {foreign_key, {[division], divisions, [division], "ON DELETE CASCADE"}}]),
[{primary_key, [division, type, number]},
{check, "type IN ('practice', 'qualification', 'elimination')"},
{foreign_key, {[division], divisions, [division], "ON DELETE CASCADE"}},
{foreign_key, {[blue_1], teams, [team], ""}},
{foreign_key, {[blue_2], teams, [team], ""}},
{foreign_key, {[red_1], teams, [team], ""}},
{foreign_key, {[red_2], teams, [team], ""}}]),
ok = sqlite3:create_table(Database, match_scores, [{division, integer, [not_null]}, ok = sqlite3:create_table(Database, match_teams, [{division, integer, [not_null]},
{type, text, [not_null]}, {type, text, [not_null]},
{number, integer, [not_null]}, {number, integer, [not_null]},
{instance, integer, [not_null]}, {instance, integer, [not_null]},
{red, integer, [not_null]}, {side, text, [not_null]},
{blue, integer, [not_null]}, {anum, integer, [not_null]},
{score, blob, [not_null]}], {team, text, [not_null]}],
[{primary_key, [division, type, number, instance]}, [{primary_key, [division, type, number, instance, side, anum]},
{foreign_key, {[division, type, number], matches, [division, type, number], "ON DELETE CASCADE"}}]), {check, "side IN ('red', 'blue')"},
{check, "anum >= 1 AND anum <= 2"},
{foreign_key, {[division, type, number, instance], matches, [division, type, number, instance], "ON DELETE CASCADE"}},
{foreign_key, {[team], teams, [team], ""}}]),
ok = sqlite3:create_table(Database, match_states, [{division, integer, [not_null]}, ok = sqlite3:create_table(Database, match_scores, [{division, integer},
{type, text, [not_null]}, {type, text, [not_null]},
{number, integer, [not_null]}, {number, integer, [not_null]},
{instance, integer, [not_null]},
{history, integer, [not_null]},
{autonomous, integer, [not_null]},
{red_elevation_1, integer, [not_null]},
{red_elevation_2, integer, [not_null]},
{red_goal, integer, [not_null]},
{red_zone, integer, [not_null]},
{red_agoal, integer, [not_null]},
{red_azone, integer, [not_null]},
{red_1, text},
{red_2, text},
{blue_elevation_1, integer, [not_null]},
{blue_elevation_2, integer, [not_null]},
{blue_goal, integer, [not_null]},
{blue_zone, integer, [not_null]},
{blue_agoal, integer, [not_null]},
{blue_azone, integer, [not_null]},
{blue_1, text},
{blue_2, text}],
[{primary_key, [division, type, number, instance, history]},
{check, "red_1 IS NULL OR red_1 IN ('dq', 'no show')"},
{check, "red_2 IS NULL OR red_2 IN ('dq', 'no show')"},
{check, "blue_1 IS NULL OR blue_1 IN ('dq', 'no show')"},
{check, "blue_2 IS NULL OR blue_2 IN ('dq', 'no show')"},
{foreign_key, {[division, type, number, instance], matches, [division, type, number, instance], "ON DELETE CASCADE"}}]),
ok = sqlite3:create_table(Database, match_states, [{division, integer},
{type, text, [not_null]},
{number, integer, [not_null]},
{instance, integer, [not_null]},
{tick, integer, [not_null]}, {tick, integer, [not_null]},
{state, text, [not_null]}], {state, text, [not_null]}],
[{primary_key, [division, type, number, tick]}, [{primary_key, [division, type, number, instance, tick]},
{foreign_key, {[division, type, number], matches, [division, type, number], "ON DELETE CASCADE"}}]), {foreign_key, {[division, type, number, instance], matches, [division, type, number, instance], "ON DELETE CASCADE"}}]),
ok = sqlite3:create_table(Database, finals, [{number, integer, [not_null]}, ok = sqlite3:create_table(Database, alliances, [{division, integer, [not_null]},
{red_1, text, [not_null]}, {number, integer, [not_null]},
{red_2, text, [not_null]}, {team1, text, [not_null]},
{blue_1, text, [not_null]}, {team2, text, [not_null]}],
{blue_2, text, [not_null]}], [{primary_key, [division, number]},
[{primary_key, [number]}, {foreign_key, {[team1], teams, [team], "ON DELETE CASCADE"}},
{foreign_key, {[blue_1], teams, [team], ""}}, {foreign_key, {[team2], teams, [team], "ON DELETE CASCADE"}}]),
{foreign_key, {[blue_2], teams, [team], ""}},
{foreign_key, {[red_1], teams, [team], ""}}, ok = sqlite3:create_table(Database, rankings, [{division, integer, [not_null]},
{foreign_key, {[red_2], teams, [team], ""}}]), {rank, integer, [not_null]},
{team, text, [not_null]}],
[{primary_key, [division, rank]},
ok = sqlite3:create_table(Database, finals_scores, [{number, integer, [not_null]}, {foreign_key, {[division], divisions, [division], "ON DELETE CASCADE"}},
{instance, integer, [not_null]}, {foreign_key, {[team], teams, [team], "ON DELETE CASCADE"}}]),
{red, integer, [not_null]},
{blue, integer, [not_null]},
{score, blob, [not_null]}],
[{primary_key, [number, instance]},
{foreign_key, {[number], finals, [number], ""}}]),
ok = sqlite3:create_table(Database, finals_states, [{number, integer, [not_null]},
{tick, integer, [not_null]},
{state, blob, [not_null]}],
[{primary_key, [number, tick]},
{foreign_key, {[number], finals, [number], ""}}]),
ok = sqlite3:create_table(Database, skills_scores, [{team, text, [not_null]}, ok = sqlite3:create_table(Database, skills_scores, [{team, text, [not_null]},
{type, text, [not_null]}, {type, text, [not_null]},
{attempt, integer, [not_null]}, {attempt, integer, [not_null]},
{score, blob, [not_null]}], {elevation, integer, [not_null]},
{goal, integer, [not_null]},
{zone, integer, [not_null]},
{agoal, integer, [not_null]},
{azone, integer, [not_null]}],
[{primary_key, [team, type, attempt]}, [{primary_key, [team, type, attempt]},
{check, "type IN ('driver', 'autonomous')"}, {check, "type IN ('driver', 'autonomous')"},
{foreign_key, {[team], teams, [team], ""}}]), {foreign_key, {[team], teams, [team], ""}}]),
@ -153,12 +168,17 @@ assign_divisions(Database, Teams) ->
first_error(sqlite3:sql_exec_script(Database, SQL)). first_error(sqlite3:sql_exec_script(Database, SQL)).
write_division_matches(Database, Division, Type, Matches) -> write_division_matches(Database, Division, Type, Matches) ->
Round = atom_to_list(Type),
SQL = lists:append(["BEGIN; ", SQL = lists:append(["BEGIN; ",
io_lib:format("DELETE FROM matches WHERE division = ~p AND type = '~s';", io_lib:format("DELETE FROM matches WHERE division = ~p AND type = '~s';",
[Division, atom_to_list(Type)]), [Division, Round]),
lists:append([io_lib:format( lists:append([io_lib:format(
"INSERT INTO matches(division, type, number, blue_1, blue_2, red_1, red_2) VALUES (~p, '~s', ~p, '~s', '~s', '~s', '~s'); ", "INSERT INTO matches(division, type, number, instance) VALUES (~p, '~s', ~p, 1);
[Division, atom_to_list(Type), N, B1, B2, R1, R2]) INSERT INTO match_teams(division, type, number, instance, side, anum, team) VALUES (~p, '~s', ~p, 1, 'red', 1, '~s');
INSERT INTO match_teams(division, type, number, instance, side, anum, team) VALUES (~p, '~s', ~p, 1, 'red', 2, '~s');
INSERT INTO match_teams(division, type, number, instance, side, anum, team) VALUES (~p, '~s', ~p, 1, 'blue', 1, '~s');
INSERT INTO match_teams(division, type, number, instance, side, anum, team) VALUES (~p, '~s', ~p, 1, 'blue', 2, '~s');",
[Division, Round, N, Division, Round, N, B1, Division, Round, N, B2, Division, Round, N, R1, Division, Round, N, R2])
|| {N, [B1, B2, R1, R2]} <- lists:enumerate(Matches)]), || {N, [B1, B2, R1, R2]} <- lists:enumerate(Matches)]),
"COMMIT;"]), "COMMIT;"]),
first_error(sqlite3:sql_exec_script(Database, SQL)). first_error(sqlite3:sql_exec_script(Database, SQL)).
@ -222,77 +242,43 @@ handle_call({delete_teams, Removed}, _, State) ->
ok = delete_teams(State#state.database, Removed), ok = delete_teams(State#state.database, Removed),
{reply, ok, State}; {reply, ok, State};
handle_call({match_score, Division, Round, Number, Red, Blue, Score}, _, State) -> handle_call({match_score, Division, Round, Number, Instance, Red, Blue, Score}, _, State) ->
[{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(State#state.database, [{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(State#state.database,
io_lib:format( io_lib:format(
"SELECT instance FROM match_scores WHERE Division = ~p AND type = '~s' AND number = ~p;", "SELECT history FROM match_scores WHERE Division = ~p AND type = '~s' AND number = ~p AND instance = ~p;",
[Division, atom_to_list(Round), Number])), [Division, atom_to_list(Round), Number, Instance])),
Instances = get_column("instance", Columns, Rows), Instances = get_column("history", Columns, Rows),
NextInstance = if length(Instances) == 0 -> 1; NextInstance = if length(Instances) == 0 -> 1;
true -> lists:max(Instances) + 1 true -> lists:max(Instances) + 1
end, end,
ok = first_error(sqlite3:sql_exec_script(State#state.database, lists:append( ok = first_error(sqlite3:sql_exec_script(State#state.database, lists:append(
[io_lib:format("INSERT INTO match_scores(division, type, number, instance, red, blue, score) VALUES(~p, '~s', ~p, ~p, ~p, ~p, '~s');", [Division, atom_to_list(Round), Number, NextInstance, Red, Blue, Score]), [io_lib:format("INSERT INTO match_scores(division, type, number, instance, history, red, blue, score) VALUES(~p, '~s', ~p, ~p, ~p, ~p, ~p, '~s');", [Division, atom_to_list(Round), Number, Instance, NextInstance, Red, Blue, Score]),
io_lib:format("INSERT INTO match_states(division, type, number, tick, state) VALUES(~p, '~s', ~p, ~p, '~s');", [Division, atom_to_list(Round), Number, get_tick(State), "scored"])]))), io_lib:format("INSERT INTO match_states(division, type, number, instance, tick, state) VALUES(~p, '~s', ~p, ~p, ~p, '~s');", [Division, atom_to_list(Round), Number, Instance, get_tick(State), "scored"])]))),
{reply, ok, State}; {reply, ok, State};
handle_call({match_state, Division, Round, Number, MatchState}, _, State) -> handle_call({match_state, Division, Round, Number, Instance, MatchState}, _, State) ->
{rowid, _} = sqlite3:sql_exec(State#state.database, {rowid, _} = sqlite3:sql_exec(State#state.database,
io_lib:format("INSERT INTO match_states(division, type, number, tick, state) VALUES(~p, '~s', ~p, ~p, '~s');", io_lib:format("INSERT INTO match_states(division, type, number, instance, tick, state) VALUES(~p, '~s', ~p, ~p, ~p, '~s');",
[Division, atom_to_list(Round), Number, get_tick(State), MatchState])), [Division, atom_to_list(Round), Number, Instance, get_tick(State), MatchState])),
{reply, ok, State}; {reply, ok, State};
handle_call({finals_score, Number, Red, Blue, Score}, _, State) -> handle_call(add_division, _, State) ->
[{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(State#state.database,
io_lib:format(
"SELECT instance FROM finals_scores WHERE number = ~p;",
[Number])),
Instances = get_column("instance", Columns, Rows),
NextInstance = if length(Instances) == 0 -> 1;
true -> lists:max(Instances) + 1
end,
ok = first_error(sqlite3:sql_exec_script(State#state.database, lists:append(
[io_lib:format("INSERT INTO finals_scores(number, instance, red, blue, score) VALUES(~p, ~p, ~p, ~p, '~s');", [Number, NextInstance, Red, Blue, Score]),
io_lib:format("INSERT INTO finals_states(number, tick, state) VALUES(~p, ~p, '~s');", [Number, get_tick(State), "scored"])]))),
{reply, ok, State};
handle_call({finals_state, Number, MatchState}, _, State) ->
{rowid, _} = sqlite3:sql_exec(State#state.database,
io_lib:format("INSERT INTO finals_states(number, tick, state) VALUES(~p, ~p, '~s');",
[Number, get_tick(State), MatchState])),
{reply, ok, State};
handle_call({add_division, PSize, QSize, ESize}, _, State) ->
[{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(State#state.database, "SELECT division FROM divisions;"), [{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(State#state.database, "SELECT division FROM divisions;"),
Divisions = lists:sort(get_column("division", Columns, Rows)), Divisions = lists:sort(get_column("division", Columns, Rows)),
NextDivision = first_empty([0 | Divisions]), NextDivision = first_empty([0 | Divisions]),
{rowid, _} = sqlite3:sql_exec(State#state.database, {rowid, _} = sqlite3:sql_exec(State#state.database,
io_lib:format("INSERT INTO divisions(division, psize, qsize, esize) VALUES(~p, ~p, ~p, ~p);", io_lib:format("INSERT INTO divisions(division) VALUES(~p);",
[NextDivision, PSize, QSize, ESize])), [NextDivision])),
{reply, ok, State};
handle_call({edit_division, Division, PSize, QSize, ESize}, _, State) ->
ok = sqlite3:sql_exec(State#state.database,
io_lib:format(
"UPDATE divisions SET psize = ~p, qsize = ~p, esize = ~p WHERE division = ~p;",
[PSize, QSize, ESize, Division])),
{reply, ok, State}; {reply, ok, State};
handle_call({delete_division, Division}, _, State) -> handle_call({delete_division, Division}, _, State) ->
ok = first_error(sqlite3:sql_exec_script(State#state.database, io_lib:format("UPDATE teams SET division = NULL WHERE division = ~p; DELETE FROM divisions WHERE division = ~p;", [Division, Division]))), ok = first_error(sqlite3:sql_exec_script(State#state.database, io_lib:format("UPDATE teams SET division = NULL WHERE division = ~p; DELETE FROM divisions WHERE division = ~p;", [Division, Division]))),
{reply, ok, State}; {reply, ok, State};
handle_call({generate_division, Division, Round, Seed}, _, State) -> handle_call({generate_division, Division, Round, Size, Seed}, _, State) ->
Size = get_div_size(State#state.database, Division, Round),
Teams = get_div_teams(State#state.database, Division), Teams = get_div_teams(State#state.database, Division),
io:fwrite("Generating division ~p/~s with teams ~p, size ~p, and seed ~p~n", [Division, Round, Teams, Size, Seed]), io:fwrite("Generating division ~p/~s with teams ~p, size ~p, and seed ~p~n", [Division, Round, Teams, Size, Seed]),
Matches = schedule:create(Seed, Size, Teams), Matches = schedule:create(Seed, Size, Teams),
ok = write_division_matches(State#state.database, Division, Round, Matches), ok = write_division_matches(State#state.database, Division, Round, Matches),
{reply, ok, State}. {reply, ok, State}.
get_div_size(Database, Division, Round) ->
Column = if Round =:= practice -> "psize";
Round =:= qualification -> "qsize"
end,
[{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(Database, io_lib:format("SELECT ~s FROM divisions WHERE division = ~p;", [Column, Division])),
[Size] = get_column(Column, Columns, Rows),
Size.
get_div_teams(Database, Division) -> get_div_teams(Database, Division) ->
[{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(Database, io_lib:format("SELECT team FROM teams WHERE division = ~p;", [Division])), [{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(Database, io_lib:format("SELECT team FROM teams WHERE division = ~p;", [Division])),
get_column("team", Columns, Rows). get_column("team", Columns, Rows).