+ problem_entry_sth => 'SELECT ' . (join ',', @{PROBLEM_PUBLIC_COLUMNS()}, 'statement', 'solution') . ' FROM problems WHERE id = ?',
+ limits_sth => 'SELECT format,timeout FROM limits WHERE problem = ?',
+
+ job_entry_sth => 'SELECT * FROM job_entry WHERE id = ?',
+
+ rerun_problem_sth => 'UPDATE jobs SET daemon=NULL,result=-2,result_text=NULL,results=NULL,errors=NULL WHERE problem = ?',
+ rerun_job_sth => 'UPDATE jobs SET daemon=NULL,result=-2,result_text=NULL,results=NULL,errors=NULL WHERE id = ?',
+ take_job_sth => 'UPDATE jobs SET daemon=? WHERE id = (SELECT id FROM jobs WHERE daemon IS NULL LIMIT 1 FOR UPDATE) RETURNING id',
+);
+
+our $db;
+sub db () { $db }
+
+sub dbinit {
+ $db = DBIx::Simple->new(@_);
+ $db->keep_statements = 100;
+ $db->dbh->do('SET search_path TO gruntmaster, public');
+};
+
+sub purge;
+
+sub _query {
+ my ($stat, @extra) = @_;
+ $db->query($statements{$stat}, @extra)
+}
+
+my (%name_cache, %name_cache_time);
+use constant NAME_CACHE_MAX_AGE => 5;
+
+sub _object_name {
+ my ($table, $id) = @_;
+ $name_cache_time{$table} //= 0;
+ if (time - $name_cache_time{$table} > NAME_CACHE_MAX_AGE) {
+ $name_cache_time{$table} = time;
+ $name_cache{$table} = {};
+ $name_cache{$table} = $db->select($table, 'id,name')->map;
+ }
+
+ $name_cache{$table}{$id}
+}
+
+
+sub _add_names ($) { ## no critic (ProhibitSubroutinePrototypes)
+ my ($el) = @_;
+ return unless defined $el;
+ if (ref $el eq 'ARRAY') {
+ &_add_names ($_) for @$el ## no critic (ProhibitAmpersandSigils)
+ } else {
+ for my $object (qw/contest owner problem/) {
+ my $table = $object eq 'owner' ? 'users' : "${object}s";
+ $el->{"${object}_name"} = _object_name $table, $el->{$object} if defined $el->{$object}
+ }
+ }
+
+ $el
+}