Moved schedule to database and made schedule only handle generating from params and returning randomized schedule + genid
parent
7162a1de37
commit
6682f37635
@ -0,0 +1,166 @@
|
|||||||
|
-module(database).
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
-export([start_link/0, init/1, handle_cast/2, handle_call/3, add_teams/1]).
|
||||||
|
|
||||||
|
-export([set_test_config/0, new/1, load/1]).
|
||||||
|
|
||||||
|
-record(division, {teams = [], practice = 0, qualification = 0}).
|
||||||
|
-record(config, {divisions = []}).
|
||||||
|
-record(state, {database_file = none, database = none, teams = [], config = none}).
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
init([]) ->
|
||||||
|
{ok, #state{}}.
|
||||||
|
|
||||||
|
init_db(Database) ->
|
||||||
|
ok = sqlite3:create_table(Database, genids, [{genid, text, [not_null]},
|
||||||
|
{type, text, [not_null]},
|
||||||
|
{size, integer, [not_null]},
|
||||||
|
{seed, integer, [not_null]}],
|
||||||
|
[{primary_key, [genid]},
|
||||||
|
{check, "type IN ('division', 'final')"}]),
|
||||||
|
ok = sqlite3:create_table(Database, teams, [{name, text, [not_null]},
|
||||||
|
{inspection, blob}],
|
||||||
|
[{primary_key, [name]}]),
|
||||||
|
ok = sqlite3:create_table(Database, matches, [{genid, text, [not_null]},
|
||||||
|
{round, text, [not_null]},
|
||||||
|
{number, integer, [not_null]},
|
||||||
|
{red_1, text, [not_null]},
|
||||||
|
{red_2, text, [not_null]},
|
||||||
|
{blue_1, text, [not_null]},
|
||||||
|
{blue_2, text, [not_null]}],
|
||||||
|
[{primary_key, [genid, round, number]},
|
||||||
|
{check, "round IN ('practice', 'qualification', 'elimination', 'final')"},
|
||||||
|
{foreign_key, {[genid], genids, [genid], "ON DELETE RESTRICT"}},
|
||||||
|
{foreign_key, {[blue_1], teams, [name], "ON DELETE RESTRICT"}},
|
||||||
|
{foreign_key, {[blue_2], teams, [name], "ON DELETE RESTRICT"}},
|
||||||
|
{foreign_key, {[red_1], teams, [name], "ON DELETE RESTRICT"}},
|
||||||
|
{foreign_key, {[red_2], teams, [name], "ON DELETE RESTRICT"}}]),
|
||||||
|
|
||||||
|
ok = sqlite3:create_table(Database, match_scores, [{genid, text, [not_null]},
|
||||||
|
{round, text, [not_null]},
|
||||||
|
{number, integer, [not_null]},
|
||||||
|
{instance, integer, [not_null]},
|
||||||
|
{score, blob, [not_null]}],
|
||||||
|
[{primary_key, [genid, round, number, instance]},
|
||||||
|
{foreign_key, {[genid, round, number], matches, [genid, round, number], "ON DELETE CASCADE"}}]),
|
||||||
|
ok = sqlite3:create_table(Database, match_states, [{genid, text, [not_null]},
|
||||||
|
{round, text, [not_null]},
|
||||||
|
{number, integer, [not_null]},
|
||||||
|
{time, integer, [not_null]},
|
||||||
|
{state, blob, [not_null]}],
|
||||||
|
[{primary_key, [genid, round, number, time]},
|
||||||
|
{foreign_key, {[genid, round, number], matches, [genid, round, number], "ON DELETE CASCADE"}}]),
|
||||||
|
|
||||||
|
ok = sqlite3:create_table(Database, skills_scores, [{team, text, [not_null]},
|
||||||
|
{type, text, [not_null]},
|
||||||
|
{attempt, integer, [not_null]},
|
||||||
|
{score, blob, [not_null]}],
|
||||||
|
[{primary_key, [team, type, attempt]},
|
||||||
|
{foreign_key, {[team], teams, [name], "ON DELETE CASCADE"}}]),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
delete_teams(Database, Teams) ->
|
||||||
|
SQL = lists:append(["BEGIN; ",
|
||||||
|
lists:append([io_lib:format("DELETE FROM teams WHERE name = ~p; ",
|
||||||
|
[Team]) || Team <- Teams]),
|
||||||
|
"COMMIT;"]),
|
||||||
|
first_error(sqlite3:sql_exec_script(Database, SQL)).
|
||||||
|
|
||||||
|
add_teams(Database, Teams) ->
|
||||||
|
SQL = lists:append(["BEGIN; ",
|
||||||
|
lists:append([io_lib:format("INSERT INTO teams(name) VALUES('~s') ON CONFLICT(name) DO NOTHING; ",
|
||||||
|
[Team]) || Team <- Teams]),
|
||||||
|
"COMMIT;"]),
|
||||||
|
first_error(sqlite3:sql_exec_script(Database, SQL)).
|
||||||
|
|
||||||
|
first_error([]) -> ok;
|
||||||
|
first_error([ok | Rest]) -> first_error(Rest);
|
||||||
|
first_error([Error | _]) -> Error.
|
||||||
|
|
||||||
|
delete_matches(Database, GenID) ->
|
||||||
|
SQL = io_lib:format("DELETE FROM matches WHERE genid = ~p", [GenID]),
|
||||||
|
first_error(sqlite3:sql_exec_script(Database, SQL)).
|
||||||
|
|
||||||
|
write_matches(Database, Type, Teams, Seed, Size, Round, Matches) ->
|
||||||
|
GenID = schedule:make_genid(Type, Round, Teams, Seed, Size),
|
||||||
|
SQL = lists:append(["BEGIN; ",
|
||||||
|
io_lib:format("INSERT INTO genids(genid, type, size, seed) VALUES('~s', '~s', ~p, ~p); ",
|
||||||
|
[GenID, atom_to_list(Type), Size, Seed]),
|
||||||
|
lists:append([io_lib:format(
|
||||||
|
"INSERT INTO matches(genid, round, number, blue_1, blue_2, red_1, red_2)
|
||||||
|
VALUES ('~s', '~s', ~p, '~s', '~s', '~s', '~s'); ",
|
||||||
|
[GenID, atom_to_list(Round), N, B1, B2, R1, R2])
|
||||||
|
|| {N, [B1, B2, R1, R2]} <- lists:enumerate(Matches)]),
|
||||||
|
"COMMIT;"]),
|
||||||
|
{first_error(sqlite3:sql_exec_script(Database, SQL)), GenID}.
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
-define(TEST_CONFIG, #config{divisions = [#division{teams = ["A", "B", "C", "D"], practice = 4}]}).
|
||||||
|
set_test_config() ->
|
||||||
|
gen_server:call(?MODULE, {set_config, ?TEST_CONFIG}).
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
add_teams(Teams) ->
|
||||||
|
gen_server:call(?MODULE, {add_teams, Teams}).
|
||||||
|
|
||||||
|
new(DatabaseFile) ->
|
||||||
|
gen_server:call(?MODULE, {new_db, DatabaseFile}).
|
||||||
|
|
||||||
|
load(DatabaseFile) ->
|
||||||
|
gen_server:call(?MODULE, {load_db, DatabaseFile}).
|
||||||
|
|
||||||
|
handle_call({set_config, Config}, _, State) when is_record(Config, config) ->
|
||||||
|
{reply, ok, State#state{config = Config}};
|
||||||
|
handle_call({new_db, DatabaseFile}, _, State) ->
|
||||||
|
ok = if State#state.database =:= none -> ok;
|
||||||
|
true -> sqlite3:close(State#state.database)
|
||||||
|
end,
|
||||||
|
Exists = filelib:is_regular(DatabaseFile),
|
||||||
|
ok = if Exists -> file:delete(DatabaseFile);
|
||||||
|
true -> ok
|
||||||
|
end,
|
||||||
|
{ok, Database} = sqlite3:open(schedule_db, [{file, DatabaseFile}]),
|
||||||
|
ok = sqlite3:sql_exec(Database, "PRAGMA foreign_keys = ON;"),
|
||||||
|
{reply, init_db(Database), State#state{database_file = DatabaseFile, database = Database}};
|
||||||
|
handle_call({load_db, DatabaseFile}, _, State) ->
|
||||||
|
true = filelib:is_regular(DatabaseFile),
|
||||||
|
{ok, Database} = if DatabaseFile == State#state.database_file ->
|
||||||
|
{ok, State#state.database};
|
||||||
|
true ->
|
||||||
|
ok = if State#state.database =:= none -> ok;
|
||||||
|
true -> sqlite3:close(State#state.database)
|
||||||
|
end,
|
||||||
|
{ok, DB} = sqlite3:open(schedule_db, [{file, DatabaseFile}]),
|
||||||
|
{sqlite3:sql_exec(DB, "PRAGMA foreign_keys = ON;"), DB}
|
||||||
|
end,
|
||||||
|
{reply, ok, State#state{database_file = DatabaseFile, database = Database}};
|
||||||
|
handle_call({delete_teams, Removed}, _, State) ->
|
||||||
|
Teams = lists:filter(fun(X) -> lists:member(X, Removed) =:= false end, State#state.teams),
|
||||||
|
ok = delete_teams(State#state.database, Removed),
|
||||||
|
{reply, ok, State#state{teams = Teams}};
|
||||||
|
handle_call({add_teams, Teams}, _, State) ->
|
||||||
|
ok = add_teams(State#state.database, Teams),
|
||||||
|
{reply, ok, State#state{teams = Teams}};
|
||||||
|
handle_call({new_schedule, Division, Round, Seed}, _, State) ->
|
||||||
|
DivInfo = lists:nth(Division, State#state.config#config.divisions),
|
||||||
|
Teams = DivInfo#division.teams,
|
||||||
|
Size = case Round of
|
||||||
|
practice when DivInfo#division.practice =/= 0 ->
|
||||||
|
DivInfo#division.practice;
|
||||||
|
qualification when DivInfo#division.qualification =/= 0->
|
||||||
|
DivInfo#division.qualification
|
||||||
|
end,
|
||||||
|
Matches = schedule:create(Seed, Size, Teams),
|
||||||
|
{ok, GenID} = write_matches(State#state.database, division, Teams, Seed, Size, Round, Matches),
|
||||||
|
{reply, GenID, State};
|
||||||
|
handle_call({delete_schedule, GenID}, _, State) ->
|
||||||
|
ok = delete_matches(State#state.database, GenID),
|
||||||
|
{reply, ok, State}.
|
||||||
|
|
||||||
|
handle_cast(_, State) ->
|
||||||
|
{noreply, State}.
|
Loading…
Reference in New Issue