// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2008 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC.  If not, see <http://www.gnu.org/licenses/>.

#include "config.h"
#include <cstdlib>
#include <string>
#include <cstring>
#include <ctime>
#include <unistd.h>
#include <cmath>

// For machines with finite() defined in ieeefp.h
#if HAVE_IEEEFP_H
#include <ieeefp.h>
#endif

#include "common_defs.h"
#include "str_util.h"
#include "str_replace.h"
#include "util.h"
#include "error_numbers.h"
#include "boinc_db.h"

#ifdef _USING_FCGI_
#include "fcgi_stdio.h"
#endif

using std::string;

extern "C" {
    int isnan(double);
}

DB_CONN boinc_db;

static struct random_init {
    random_init() {
        srand48(getpid() + time(0));
    }
} random_init;

#define ESCAPE(x) escape_string(x, sizeof(x))
#define UNESCAPE(x) unescape_string(x, sizeof(x))

#define strcpy2(x, y) \
    { \
        const char* z = y; \
        if (!z) { \
            x[0]=0; \
        } else { \
            strlcpy(x, z, sizeof(x)); \
        } \
    }


void PLATFORM::clear() {memset(this, 0, sizeof(*this));}
void APP::clear() {memset(this, 0, sizeof(*this));}
void APP_VERSION::clear() {memset(this, 0, sizeof(*this));}
void USER::clear() {memset(this, 0, sizeof(*this));}
void TEAM::clear() {memset(this, 0, sizeof(*this));}
void HOST::clear() {memset(this, 0, sizeof(*this));}
void RESULT::clear() {
    memset(this, 0, sizeof(*this));
    size_class = -1;
}
void WORKUNIT::clear() {
    memset(this, 0, sizeof(*this));
    size_class = -1;
}
void CREDITED_JOB::clear() {memset(this, 0, sizeof(*this));}
void MSG_FROM_HOST::clear() {memset(this, 0, sizeof(*this));}
void MSG_TO_HOST::clear() {memset(this, 0, sizeof(*this));}
void ASSIGNMENT::clear() {memset(this, 0, sizeof(*this));}
void TRANSITIONER_ITEM::clear() {memset(this, 0, sizeof(*this));}
void VALIDATOR_ITEM::clear() {memset(this, 0, sizeof(*this));}
void SCHED_RESULT_ITEM::clear() {memset(this, 0, sizeof(*this));}
void HOST_APP_VERSION::clear() {memset(this, 0, sizeof(*this));}
void USER_SUBMIT::clear() {memset(this, 0, sizeof(*this));}
void STATE_COUNTS::clear() {memset(this, 0, sizeof(*this));}
void FILE_ITEM::clear() {memset(this, 0, sizeof(*this));}
void FILESET_ITEM::clear() {memset(this, 0, sizeof(*this));}
void FILESET_FILE_ITEM::clear() {memset(this, 0, sizeof(*this));}
void SCHED_TRIGGER_ITEM::clear() {
    id = 0;
    fileset_id = 0;
    need_work = false;
    work_available = false;
    no_work_available = false;
    working_set_removal = false;
}
void FILESET_SCHED_TRIGGER_ITEM::clear() {memset(this, 0, sizeof(*this));}
void VDA_FILE::clear() {memset(this, 0, sizeof(*this));}
void VDA_CHUNK_HOST::clear() {memset(this, 0, sizeof(*this));}
void BADGE::clear() {memset(this, 0, sizeof(*this));}
void BADGE_USER::clear() {memset(this, 0, sizeof(*this));}
void BADGE_TEAM::clear() {memset(this, 0, sizeof(*this));}
void CREDIT_USER::clear() {memset(this, 0, sizeof(*this));}
void CREDIT_TEAM::clear() {memset(this, 0, sizeof(*this));}

DB_PLATFORM::DB_PLATFORM(DB_CONN* dc) :
    DB_BASE("platform", dc?dc:&boinc_db){}
DB_APP::DB_APP(DB_CONN* dc) :
    DB_BASE("app", dc?dc:&boinc_db){}
DB_APP_VERSION::DB_APP_VERSION(DB_CONN* dc) :
    DB_BASE("app_version", dc?dc:&boinc_db){}
DB_USER::DB_USER(DB_CONN* dc) :
    DB_BASE("user", dc?dc:&boinc_db){}
DB_TEAM::DB_TEAM(DB_CONN* dc) :
    DB_BASE("team", dc?dc:&boinc_db){}
DB_HOST::DB_HOST(DB_CONN* dc) :
    DB_BASE("host", dc?dc:&boinc_db){}
DB_WORKUNIT::DB_WORKUNIT(DB_CONN* dc) :
    DB_BASE("workunit", dc?dc:&boinc_db){}
DB_CREDITED_JOB::DB_CREDITED_JOB(DB_CONN* dc) :
    DB_BASE("credited_job", dc?dc:&boinc_db){}
DB_RESULT::DB_RESULT(DB_CONN* dc) :
    DB_BASE("result", dc?dc:&boinc_db), RESULT() {}
DB_MSG_FROM_HOST::DB_MSG_FROM_HOST(DB_CONN* dc) :
    DB_BASE("msg_from_host", dc?dc:&boinc_db){}
DB_MSG_TO_HOST::DB_MSG_TO_HOST(DB_CONN* dc) :
    DB_BASE("msg_to_host", dc?dc:&boinc_db){}
DB_ASSIGNMENT::DB_ASSIGNMENT(DB_CONN* dc) :
    DB_BASE("assignment", dc?dc:&boinc_db){}
DB_HOST_APP_VERSION::DB_HOST_APP_VERSION(DB_CONN* dc) :
    DB_BASE("host_app_version", dc?dc:&boinc_db){}
DB_USER_SUBMIT::DB_USER_SUBMIT(DB_CONN* dc) :
    DB_BASE("user_submit", dc?dc:&boinc_db){}
DB_STATE_COUNTS::DB_STATE_COUNTS(DB_CONN* dc) :
    DB_BASE("state_counts", dc?dc:&boinc_db){}
DB_TRANSITIONER_ITEM_SET::DB_TRANSITIONER_ITEM_SET(DB_CONN* dc) :
    DB_BASE_SPECIAL(dc?dc:&boinc_db){}
DB_VALIDATOR_ITEM_SET::DB_VALIDATOR_ITEM_SET(DB_CONN* dc) :
    DB_BASE_SPECIAL(dc?dc:&boinc_db){}
DB_WORK_ITEM::DB_WORK_ITEM(DB_CONN* dc) :
    DB_BASE_SPECIAL(dc?dc:&boinc_db
){
    start_id = 0;
}
DB_IN_PROGRESS_RESULT::DB_IN_PROGRESS_RESULT(DB_CONN* dc) :
    DB_BASE_SPECIAL(dc?dc:&boinc_db){}
DB_SCHED_RESULT_ITEM_SET::DB_SCHED_RESULT_ITEM_SET(DB_CONN* dc) :
    DB_BASE_SPECIAL(dc?dc:&boinc_db){}
DB_FILE::DB_FILE(DB_CONN* dc) :
    DB_BASE("file", dc?dc:&boinc_db){}
DB_FILESET::DB_FILESET(DB_CONN* dc) :
    DB_BASE("fileset", dc?dc:&boinc_db){}
DB_FILESET_FILE::DB_FILESET_FILE(DB_CONN* dc) :
    DB_BASE("fileset_file", dc?dc:&boinc_db){}
DB_SCHED_TRIGGER::DB_SCHED_TRIGGER(DB_CONN* dc) :
    DB_BASE("sched_trigger", dc?dc:&boinc_db) {
    id = 0;
    fileset_id = 0;
    need_work = false;
    work_available = false;
    no_work_available = false;
    working_set_removal = false;
}
DB_FILESET_SCHED_TRIGGER_ITEM::DB_FILESET_SCHED_TRIGGER_ITEM(DB_CONN* dc) :
    DB_BASE_SPECIAL(dc?dc:&boinc_db){}
DB_FILESET_SCHED_TRIGGER_ITEM_SET::DB_FILESET_SCHED_TRIGGER_ITEM_SET(DB_CONN* dc) :
    DB_BASE_SPECIAL(dc?dc:&boinc_db){}
DB_VDA_FILE::DB_VDA_FILE(DB_CONN* dc) :
    DB_BASE("vda_file", dc?dc:&boinc_db){}
DB_VDA_CHUNK_HOST::DB_VDA_CHUNK_HOST(DB_CONN* dc) :
    DB_BASE("vda_chunk_host", dc?dc:&boinc_db){}
DB_BADGE::DB_BADGE(DB_CONN* dc) :
    DB_BASE("badge", dc?dc:&boinc_db){}
DB_BADGE_USER::DB_BADGE_USER(DB_CONN* dc) :
    DB_BASE("badge_user", dc?dc:&boinc_db){}
DB_BADGE_TEAM::DB_BADGE_TEAM(DB_CONN* dc) :
    DB_BASE("badge_team", dc?dc:&boinc_db){}
DB_CREDIT_USER::DB_CREDIT_USER(DB_CONN* dc) :
    DB_BASE("credit_user", dc?dc:&boinc_db){}
DB_CREDIT_TEAM::DB_CREDIT_TEAM(DB_CONN* dc) :
    DB_BASE("credit_team", dc?dc:&boinc_db){}

DB_ID_TYPE DB_PLATFORM::get_id() {return id;}
DB_ID_TYPE DB_APP::get_id() {return id;}
DB_ID_TYPE DB_APP_VERSION::get_id() {return id;}
DB_ID_TYPE DB_USER::get_id() {return id;}
DB_ID_TYPE DB_TEAM::get_id() {return id;}
DB_ID_TYPE DB_HOST::get_id() {return id;}
DB_ID_TYPE DB_WORKUNIT::get_id() {return id;}
DB_ID_TYPE DB_RESULT::get_id() {return id;}
DB_ID_TYPE DB_MSG_FROM_HOST::get_id() {return id;}
DB_ID_TYPE DB_MSG_TO_HOST::get_id() {return id;}
DB_ID_TYPE DB_ASSIGNMENT::get_id() {return id;}
DB_ID_TYPE DB_STATE_COUNTS::get_id() {return appid;}
DB_ID_TYPE DB_FILE::get_id() {return id;}
DB_ID_TYPE DB_FILESET::get_id() {return id;}
DB_ID_TYPE DB_SCHED_TRIGGER::get_id() {return id;}
DB_ID_TYPE DB_VDA_FILE::get_id() {return id;}

void DB_PLATFORM::db_print(char* buf){
    sprintf(buf,
        "create_time=%d, name='%s', user_friendly_name='%s', "
        "deprecated=%d",
        create_time, name, user_friendly_name,
        deprecated
    );
}

void DB_PLATFORM::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    strcpy2(name, r[i++]);
    strcpy2(user_friendly_name, r[i++]);
    deprecated = atoi(r[i++]);
}

void DB_APP::db_print(char* buf){
    sprintf(buf,
        "create_time=%d, "
        "name='%s', "
        "min_version=%d, "
        "deprecated=%d, "
        "user_friendly_name='%s', "
        "homogeneous_redundancy=%d, "
        "weight=%.15e, "
        "beta=%d, "
        "target_nresults=%d, "
        "min_avg_pfc=%.15e, "
        "host_scale_check=%d, "
        "homogeneous_app_version=%d, "
        "non_cpu_intensive=%d, "
        "locality_scheduling=%d, "
        "n_size_classes=%d, "
        "fraction_done_exact=%d ",
        create_time,
        name,
        min_version,
        deprecated?1:0,
        user_friendly_name,
        homogeneous_redundancy,
        weight,
        beta?1:0,
        target_nresults,
        min_avg_pfc,
        host_scale_check?1:0,
        homogeneous_app_version?1:0,
        non_cpu_intensive?1:0,
        locality_scheduling,
        n_size_classes,
        fraction_done_exact?1:0
    );
}

void DB_APP::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    strcpy2(name, r[i++]);
    min_version = atoi(r[i++]);
    deprecated = atoi(r[i++]);
    strcpy2(user_friendly_name, r[i++]);
    homogeneous_redundancy = atoi(r[i++]);
    weight = atof(r[i++]);
    beta = atoi(r[i++]);
    target_nresults = atoi(r[i++]);
    min_avg_pfc = atof(r[i++]);
    host_scale_check = (atoi(r[i++]) != 0);
    homogeneous_app_version = (atoi(r[i++]) != 0);
    non_cpu_intensive = (atoi(r[i++]) != 0);
    locality_scheduling = atoi(r[i++]);
    n_size_classes = atoi(r[i++]);
    fraction_done_exact = (atoi(r[i++]) != 0);
}

void DB_APP_VERSION::db_print(char* buf){
    sprintf(buf,
        "create_time=%d, "
        "appid=%lu, "
        "version_num=%d, "
        "platformid=%lu, "
        "xml_doc='%s', "
        "min_core_version=%d, "
        "max_core_version=%d, "
        "deprecated=%d, "
        "plan_class='%s', "
        "pfc_n=%.15e, "
        "pfc_avg=%.15e, "
        "pfc_scale=%.15e, "
        "expavg_credit=%.15e, "
        "expavg_time=%.15e, "
        "beta=%d ",
        create_time,
        appid,
        version_num,
        platformid,
        xml_doc,
        min_core_version,
        max_core_version,
        deprecated,
        plan_class,
        pfc.n,
        pfc.avg,
        pfc_scale,
        expavg_credit,
        expavg_time,
        beta
    );
}

void DB_APP_VERSION::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    appid = atol(r[i++]);
    version_num = atoi(r[i++]);
    platformid = atol(r[i++]);
    strcpy2(xml_doc, r[i++]);
    min_core_version = atoi(r[i++]);
    max_core_version = atoi(r[i++]);
    deprecated = atoi(r[i++]);
    strcpy2(plan_class, r[i++]);
    pfc.n = atof(r[i++]);
    pfc.avg = atof(r[i++]);
    pfc_scale = atof(r[i++]);
    expavg_credit = atof(r[i++]);
    expavg_time = atof(r[i++]);
    beta = atoi(r[i++]);
}

void DB_USER::db_print(char* buf){
    ESCAPE(email_addr);
    ESCAPE(name);
    ESCAPE(country);
    ESCAPE(postal_code);
    ESCAPE(global_prefs);
    ESCAPE(project_prefs);
    ESCAPE(url);
    ESCAPE(signature);
    sprintf(buf,
        "create_time=%d, email_addr='%s', name='%s', "
        "authenticator='%s', "
        "country='%s', postal_code='%s', "
        "total_credit=%.15e, expavg_credit=%.15e, expavg_time=%.15e, "
        "global_prefs='%s', project_prefs='%s', "
        "teamid=%lu, venue='%s', url='%s', send_email=%d, show_hosts=%d, "
        "posts=%d, "
        "seti_id=%d, seti_nresults=%d, seti_last_result_time=%d, "
        "seti_total_cpu=%.15e, signature='%s', has_profile=%d, "
        "cross_project_id='%s', passwd_hash='%s', "
        "email_validated=%d, donated=%d, "
        "login_token='%s', login_token_time=%f",
        create_time, email_addr, name,
        authenticator,
        country, postal_code,
        total_credit, expavg_credit, expavg_time,
        global_prefs, project_prefs,
        teamid, venue, url, send_email, show_hosts,
        posts,
        seti_id, seti_nresults, seti_last_result_time,
        seti_total_cpu, signature, has_profile,
        cross_project_id, passwd_hash,
        email_validated, donated,
        login_token, login_token_time
    );
    UNESCAPE(email_addr);
    UNESCAPE(name);
    UNESCAPE(country);
    UNESCAPE(postal_code);
    UNESCAPE(global_prefs);
    UNESCAPE(project_prefs);
    UNESCAPE(url);
    UNESCAPE(signature);
}

void DB_USER::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    strcpy2(email_addr, r[i++]);
    strcpy2(name, r[i++]);
    strcpy2(authenticator, r[i++]);
    strcpy2(country, r[i++]);
    strcpy2(postal_code, r[i++]);
    total_credit = atof(r[i++]);
    expavg_credit = atof(r[i++]);
    expavg_time = atof(r[i++]);
    strcpy2(global_prefs, r[i++]);
    strcpy2(project_prefs, r[i++]);
    teamid = atol(r[i++]);
    strcpy2(venue, r[i++]);
    strcpy2(url, r[i++]);
    send_email = atoi(r[i++]);
    show_hosts = atoi(r[i++]);
    posts = safe_atoi(r[i++]);
    seti_id = safe_atoi(r[i++]);
    seti_nresults = safe_atoi(r[i++]);
    seti_last_result_time = safe_atoi(r[i++]);
    seti_total_cpu = safe_atof(r[i++]);
    strcpy2(signature, r[i++]);
    has_profile = atoi(r[i++]);
    strcpy2(cross_project_id, r[i++]);
    strcpy2(passwd_hash, r[i++]);
    email_validated = atoi(r[i++]);
    donated = atoi(r[i++]);
    strcpy2(login_token, r[i++]);
    login_token_time = atof(r[i++]);
}

void DB_TEAM::db_print(char* buf){
    ESCAPE(name);
    ESCAPE(name_lc);
    ESCAPE(url);
    ESCAPE(name_html);
    ESCAPE(description);
    sprintf(buf,
        "create_time=%d, userid=%lu, name='%s', "
        "name_lc='%s', url='%s', "
        "type=%d, name_html='%s', description='%s', nusers=%d, "
        "country='%s', "
        "total_credit=%.15e, expavg_credit=%.15e, expavg_time=%.15e, "
        "seti_id=%d, ping_user=%d, ping_time=%d",
        create_time,
        userid,
        name,
        name_lc,
        url,
        type,
        name_html,
        description,
        nusers,
        country,
        total_credit,
        expavg_credit,
        expavg_time,
        seti_id,
        ping_user,
        ping_time
    );
    UNESCAPE(name);
    UNESCAPE(name_lc);
    UNESCAPE(url);
    UNESCAPE(name_html);
    UNESCAPE(description);
}

void DB_TEAM::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    userid = atol(r[i++]);
    strcpy2(name, r[i++]);
    strcpy2(name_lc, r[i++]);
    strcpy2(url, r[i++]);
    type = atoi(r[i++]);
    strcpy2(name_html, r[i++]);
    strcpy2(description, r[i++]);
    nusers = atoi(r[i++]);
    strcpy2(country, r[i++]);
    total_credit = atof(r[i++]);
    expavg_credit = atof(r[i++]);
    expavg_time = atof(r[i++]);
    seti_id = safe_atoi(r[i++]);
    ping_user = safe_atoi(r[i++]);
    ping_time = safe_atoi(r[i++]);
}

// set NaNs and infs to zeroes
//
void HOST::fix_nans() {
    if (!finite(p_fpops)) p_fpops = 0;
    if (!finite(p_iops)) p_iops = 0;
    if (!finite(p_membw)) p_membw = 0;
    if (!finite(m_nbytes)) m_nbytes = 0;
    if (!finite(m_cache)) m_cache = 0;
    if (!finite(m_swap)) m_swap = 0;
    if (!finite(d_total)) d_total = 0;
    if (!finite(d_free)) d_free = 0;
    if (!finite(d_boinc_used_total)) d_boinc_used_total = 0;
    if (!finite(d_boinc_used_project)) d_boinc_used_project = 0;
    if (!finite(d_boinc_max)) d_boinc_max = 0;
    if (!finite(n_bwup)) n_bwup = 0;
    if (!finite(n_bwdown)) n_bwdown = 0;
}

void DB_HOST::db_print(char* buf){
    ESCAPE(domain_name);
    ESCAPE(serialnum);
    ESCAPE(last_ip_addr);
    ESCAPE(host_cpid);
    ESCAPE(p_vendor);
    ESCAPE(p_model);
    ESCAPE(os_name);
    ESCAPE(os_version);
    ESCAPE(product_name);
    sprintf(buf,
        "create_time=%d, userid=%lu, "
        "rpc_seqno=%d, rpc_time=%d, "
        "total_credit=%.12e, expavg_credit=%.12e, expavg_time=%.15e, "
        "timezone=%d, domain_name='%s', serialnum='%s', "
        "last_ip_addr='%s', nsame_ip_addr=%d, "
        "on_frac=%.15e, connected_frac=%.15e, "
        "active_frac=%.15e, cpu_efficiency=%.15e, "
        "duration_correction_factor=%.15e, "
        "p_ncpus=%d, p_vendor='%s', p_model='%s', "
        "p_fpops=%.15e, p_iops=%.15e, p_membw=%.15e, "
        "os_name='%s', os_version='%s', "
        "m_nbytes=%.15e, m_cache=%.15e, m_swap=%.15e, "
        "d_total=%.15e, d_free=%.15e, "
        "d_boinc_used_total=%.15e, d_boinc_used_project=%.15e, d_boinc_max=%.15e, "
        "n_bwup=%.15e, n_bwdown=%.15e, "
        "credit_per_cpu_sec=%.15e, "
        "venue='%s', nresults_today=%d, "
        "avg_turnaround=%.15e, "
        "host_cpid='%s', external_ip_addr='%s', max_results_day=%d, "
        "error_rate=%.15e, "
        "product_name='%s', "
        "gpu_active_frac=%.15e, "
        "p_ngpus=%d, p_gpu_fpops=%.15e ",
        create_time, userid,
        rpc_seqno, rpc_time,
        total_credit, expavg_credit, expavg_time,
        timezone, domain_name, serialnum,
        last_ip_addr, nsame_ip_addr,
        on_frac, connected_frac,
        active_frac, cpu_efficiency,
        duration_correction_factor,
        p_ncpus, p_vendor, p_model,
        p_fpops, p_iops, p_membw,
        os_name, os_version,
        m_nbytes, m_cache, m_swap,
        d_total, d_free,
        d_boinc_used_total, d_boinc_used_project, d_boinc_max,
        n_bwup, n_bwdown,
        credit_per_cpu_sec,
        venue, nresults_today,
        avg_turnaround,
        host_cpid, external_ip_addr, _max_results_day,
        _error_rate,
        product_name,
        gpu_active_frac,
        p_ngpus, p_gpu_fpops
    );
    UNESCAPE(domain_name);
    UNESCAPE(serialnum);
    UNESCAPE(last_ip_addr);
    UNESCAPE(p_vendor);
    UNESCAPE(p_model);
    UNESCAPE(os_name);
    UNESCAPE(os_version);
    UNESCAPE(host_cpid);
    UNESCAPE(product_name);
}

void DB_HOST::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    userid = atol(r[i++]);
    rpc_seqno = atoi(r[i++]);
    rpc_time = atoi(r[i++]);
    total_credit = atof(r[i++]);
    expavg_credit = atof(r[i++]);
    expavg_time = atof(r[i++]);
    timezone = atoi(r[i++]);
    strcpy2(domain_name, r[i++]);
    strcpy2(serialnum, r[i++]);
    strcpy2(last_ip_addr, r[i++]);
    nsame_ip_addr = atoi(r[i++]);
    on_frac = atof(r[i++]);
    connected_frac = atof(r[i++]);
    active_frac = atof(r[i++]);
    cpu_efficiency = atof(r[i++]);
    duration_correction_factor = atof(r[i++]);
    p_ncpus = atoi(r[i++]);
    strcpy2(p_vendor, r[i++]);
    strcpy2(p_model, r[i++]);
    p_fpops = atof(r[i++]);
    p_iops = atof(r[i++]);
    p_membw = atof(r[i++]);
    strcpy2(os_name, r[i++]);
    strcpy2(os_version, r[i++]);
    m_nbytes = atof(r[i++]);
    m_cache = atof(r[i++]);
    m_swap = atof(r[i++]);
    d_total = atof(r[i++]);
    d_free = atof(r[i++]);
    d_boinc_used_total = atof(r[i++]);
    d_boinc_used_project = atof(r[i++]);
    d_boinc_max = atof(r[i++]);
    n_bwup = atof(r[i++]);
    n_bwdown = atof(r[i++]);
    credit_per_cpu_sec = atof(r[i++]);
    strcpy2(venue, r[i++]);
    nresults_today = atoi(r[i++]);
    avg_turnaround = atof(r[i++]);
    strcpy2(host_cpid, r[i++]);
    strcpy2(external_ip_addr, r[i++]);
    _max_results_day = atoi(r[i++]);
    _error_rate = atof(r[i++]);
    strcpy2(product_name, r[i++]);
    gpu_active_frac = atof(r[i++]);
}

int DB_HOST::update_diff_validator(HOST& h) {
    char buf[BLOB_SIZE], updates[BLOB_SIZE], query[BLOB_SIZE];
    strcpy(updates, "");
    if (avg_turnaround != h.avg_turnaround) {
        sprintf(buf, " avg_turnaround=%.15e,", avg_turnaround);
        strcat(updates, buf);
    }
#if 0
    if (error_rate != h.error_rate) {
        sprintf(buf, " error_rate=%.15e,", error_rate);
        strcat(updates, buf);
    }
#endif
    if (total_credit != h.total_credit) {
        sprintf(buf, " total_credit=total_credit+%.15e,",
            total_credit-h.total_credit
        );
        strcat(updates, buf);
    }
    if (expavg_credit != h.expavg_credit) {
        sprintf(buf, " expavg_credit=%.15e,", expavg_credit);
        strcat(updates, buf);
    }
    if (expavg_time != h.expavg_time) {
        sprintf(buf, " expavg_time=%.15e,", expavg_time);
        strcat(updates, buf);
    }
#if 0
    if (credit_per_cpu_sec != h.credit_per_cpu_sec) {
        sprintf(buf, " credit_per_cpu_sec=%.15e,", credit_per_cpu_sec);
        strcat(updates, buf);
    }
#endif
    int n = strlen(updates);
    if (n == 0) return 0;
    updates[n-1] = 0;        // trim the final comma
    sprintf(query, "update host set %s where id=%lu", updates, id);
    return db->do_query(query);
}

// Update fields that differ from the argument HOST.
// Called from scheduler (handle_request.cpp),
// so only include fields modified by the scheduler.
//
int DB_HOST::update_diff_sched(HOST& h) {
    char buf[BLOB_SIZE], updates[BLOB_SIZE], query[BLOB_SIZE];
    strcpy(updates, "");
    if (rpc_seqno != h.rpc_seqno) {
        sprintf(buf, " rpc_seqno=%d,", rpc_seqno);
        strcat(updates, buf);
    }
    if (rpc_time != h.rpc_time) {
        sprintf(buf, " rpc_time=%d,", rpc_time);
        strcat(updates, buf);
    }
    if (timezone != h.timezone) {
        sprintf(buf, " timezone=%d,", timezone);
        strcat(updates, buf);
    }
    if (strcmp(domain_name, h.domain_name)) {
        escape_string(domain_name, sizeof(domain_name));
        sprintf(buf, " domain_name='%s',", domain_name);
        unescape_string(domain_name, sizeof(domain_name));
        strcat(updates, buf);
    }
    if (strcmp(serialnum, h.serialnum)) {
        escape_string(serialnum, sizeof(serialnum));
        sprintf(buf, " serialnum='%s',", serialnum);
        unescape_string(serialnum, sizeof(serialnum));
        strcat(updates, buf);
    }
    if (strcmp(last_ip_addr, h.last_ip_addr)) {
        escape_string(last_ip_addr, sizeof(last_ip_addr));
        sprintf(buf, " last_ip_addr='%s',", last_ip_addr);
        unescape_string(last_ip_addr, sizeof(last_ip_addr));
        strcat(updates, buf);
    }
    if (nsame_ip_addr != h.nsame_ip_addr) {
        sprintf(buf, " nsame_ip_addr=%d,", nsame_ip_addr);
        strcat(updates, buf);
    }
    if (on_frac != h.on_frac) {
        sprintf(buf, " on_frac=%.15e,", on_frac);
        strcat(updates, buf);
    }
    if (connected_frac != h.connected_frac) {
        sprintf(buf, " connected_frac=%.15e,", connected_frac);
        strcat(updates, buf);
    }
    if (active_frac != h.active_frac) {
        sprintf(buf, " active_frac=%.15e,", active_frac);
        strcat(updates, buf);
    }
    if (cpu_efficiency != h.cpu_efficiency) {
        sprintf(buf, " cpu_efficiency=%.15e,", cpu_efficiency);
        strcat(updates, buf);
    }
    if (duration_correction_factor != h.duration_correction_factor) {
        sprintf(buf, " duration_correction_factor=%.15e,", duration_correction_factor);
        strcat(updates, buf);
    }
    if (p_ncpus != h.p_ncpus) {
        sprintf(buf, " p_ncpus=%d,", p_ncpus);
        strcat(updates, buf);
    }
    if (strcmp(p_vendor, h.p_vendor)) {
        escape_string(p_vendor, sizeof(p_vendor));
        sprintf(buf, " p_vendor='%s',", p_vendor);
        unescape_string(p_vendor, sizeof(p_vendor));
        strcat(updates, buf);
    }
    if (strcmp(p_model, h.p_model)) {
        escape_string(p_model, sizeof(p_model));
        sprintf(buf, " p_model='%s',", p_model);
        unescape_string(p_model, sizeof(p_model));
        strcat(updates, buf);
    }
    if (p_fpops != h.p_fpops) {
        sprintf(buf, " p_fpops=%.15e,", p_fpops);
        strcat(updates, buf);
    }
    if (p_iops != h.p_iops) {
        sprintf(buf, " p_iops=%.15e,", p_iops);
        strcat(updates, buf);
    }
    if (p_membw != h.p_membw) {
        sprintf(buf, " p_membw=%.15e,", p_membw);
        strcat(updates, buf);
    }
    if (strcmp(os_name, h.os_name)) {
        escape_string(os_name, sizeof(os_name));
        sprintf(buf, " os_name='%s',", os_name);
        unescape_string(os_name, sizeof(os_name));
        strcat(updates, buf);
    }
    if (strcmp(os_version, h.os_version)) {
        escape_string(os_version, sizeof(os_version));
        sprintf(buf, " os_version='%s',", os_version);
        unescape_string(os_version, sizeof(os_version));
        strcat(updates, buf);
    }
    if (m_nbytes != h.m_nbytes) {
        sprintf(buf, " m_nbytes=%.15e,", m_nbytes);
        strcat(updates, buf);
    }
    if (m_cache != h.m_cache) {
        sprintf(buf, " m_cache=%.15e,", m_cache);
        strcat(updates, buf);
    }
    if (m_swap != h.m_swap) {
        sprintf(buf, " m_swap=%.15e,", m_swap);
        strcat(updates, buf);
    }
    if (d_total != h.d_total) {
        sprintf(buf, " d_total=%.15e,", d_total);
        strcat(updates, buf);
    }
    if (d_free != h.d_free) {
        sprintf(buf, " d_free=%.15e,", d_free);
        strcat(updates, buf);
    }
    if (d_boinc_used_total != h.d_boinc_used_total) {
        sprintf(buf, " d_boinc_used_total=%.15e,", d_boinc_used_total);
        strcat(updates, buf);
    }
    if (d_boinc_used_project != h.d_boinc_used_project) {
        sprintf(buf, " d_boinc_used_project=%.15e,", d_boinc_used_project);
        strcat(updates, buf);
    }
    if (d_boinc_max != h.d_boinc_max) {
        sprintf(buf, " d_boinc_max=%.15e,", d_boinc_max);
        strcat(updates, buf);
    }
    if (n_bwdown != h.n_bwdown) {
        sprintf(buf, " n_bwdown=%.15e,", n_bwdown);
        strcat(updates, buf);
    }
    if (n_bwup != h.n_bwup) {
        sprintf(buf, " n_bwup=%.15e,", n_bwup);
        strcat(updates, buf);
    }
    if (strcmp(venue, h.venue)) {
        escape_string(venue, sizeof(venue));
        sprintf(buf, " venue='%s',", venue);
        unescape_string(venue, sizeof(venue));
        strcat(updates, buf);
    }
    if (nresults_today != h.nresults_today) {
        sprintf(buf, " nresults_today=%d,", nresults_today);
        strcat(updates, buf);
    }
    if (avg_turnaround != h.avg_turnaround) {
        sprintf(buf, " avg_turnaround=%.15e,", avg_turnaround);
        strcat(updates, buf);
    }
    if (strcmp(host_cpid, h.host_cpid)) {
        escape_string(host_cpid, sizeof(host_cpid));
        sprintf(buf, " host_cpid='%s',", host_cpid);
        unescape_string(host_cpid, sizeof(host_cpid));
        strcat(updates, buf);
    }
    if (strcmp(external_ip_addr, h.external_ip_addr)) {
        escape_string(external_ip_addr, sizeof(external_ip_addr));
        sprintf(buf, " external_ip_addr='%s',", external_ip_addr);
        unescape_string(external_ip_addr, sizeof(external_ip_addr));
        strcat(updates, buf);
    }
#if 0
    if (max_results_day != h.max_results_day) {
        sprintf(buf, " max_results_day=%d,", max_results_day);
        strcat(updates, buf);
    }
#endif
    if (strcmp(product_name, h.product_name)) {
        escape_string(product_name, sizeof(product_name));
        sprintf(buf, " product_name='%s',", product_name);
        unescape_string(product_name, sizeof(product_name));
        strcat(updates, buf);
    }
    if (gpu_active_frac != h.gpu_active_frac) {
        sprintf(buf, " gpu_active_frac=%.15e,", gpu_active_frac);
        strcat(updates, buf);
    }
    if (p_ngpus != h.p_ngpus) {
        sprintf(buf, " p_ngpus=%d,", p_ngpus);
        strcat(updates, buf);
    }
    if (p_gpu_fpops != h.p_gpu_fpops) {
        sprintf(buf, " p_gpu_fpops=%.15e,", p_gpu_fpops);
        strcat(updates, buf);
    }

    int n = strlen(updates);
    if (n == 0) return 0;
    updates[n-1] = 0;        // trim the final comma
    sprintf(query, "update host set %s where id=%lu", updates, id);
    return db->do_query(query);
}

int DB_HOST::fpops_percentile(double percentile, double& fpops) {
    char query[256];
    int retval;
    long n;

    sprintf(query, "where expavg_credit>10");
    retval = count(n, query);
    if (retval) return retval;
    if (n==0) return ERR_NULL;
    int m = (int)(n*percentile/100.);
    sprintf(query,
        "select p_fpops from host where expavg_credit>10 order by p_fpops limit %d,1",
        m
    );
    return db->get_double(query, fpops);
}

int DB_HOST::fpops_mean(double& mean) {
    char query[256];
    sprintf(query,
        "select avg(p_fpops) from host where expavg_credit>10"
    );
    return db->get_double(query, mean);
}

int DB_HOST::fpops_stddev(double& stddev) {
    char query[256];
    sprintf(query,
        "select stddev(p_fpops) from host where expavg_credit>10"
    );
    return db->get_double(query, stddev);
}

void DB_WORKUNIT::db_print(char* buf){
    sprintf(buf,
        "create_time=%d, appid=%lu, "
        "name='%s', xml_doc='%s', batch=%d, "
        "rsc_fpops_est=%.15e, rsc_fpops_bound=%.15e, "
        "rsc_memory_bound=%.15e, rsc_disk_bound=%.15e, "
        "need_validate=%d, "
        "canonical_resultid=%lu, canonical_credit=%.15e, "
        "transition_time=%d, delay_bound=%d, "
        "error_mask=%d, file_delete_state=%d, assimilate_state=%d, "
        "hr_class=%d, opaque=%.15e, "
        "min_quorum=%d, target_nresults=%d, max_error_results=%d, "
        "max_total_results=%d, max_success_results=%d, "
        "result_template_file='%s', "
        "priority=%d, "
        "rsc_bandwidth_bound=%.15e, "
        "fileset_id=%lu, "
        "app_version_id=%ld, "
        "transitioner_flags=%d, "
        "size_class=%d, "
        "keywords='%s', "
        "app_version_num=%d ",
        create_time, appid,
        name, xml_doc, batch,
        rsc_fpops_est, rsc_fpops_bound, rsc_memory_bound, rsc_disk_bound,
        need_validate,
        canonical_resultid, canonical_credit,
        transition_time, delay_bound,
        error_mask, file_delete_state, assimilate_state,
        hr_class, opaque,
        min_quorum,
        target_nresults,
        max_error_results,
        max_total_results,
        max_success_results,
        result_template_file,
        priority,
        rsc_bandwidth_bound,
        fileset_id,
        app_version_id,
        transitioner_flags,
        size_class,
        keywords,
        app_version_num
    );
}

void DB_WORKUNIT::db_print_values(char* buf) {
    sprintf(buf,
        "(0, %d, %lu, "
        "'%s', '%s', %d, "
        "%f, %f, "
        "%f, %f, "
        "%d, "
        "%lu, %f, "
        "%d, %d, "
        "%d, %d, %d, "
        "%d, %f, "
        "%d, %d, %d, "
        "%d, %d, "
        "'%s', "
        "%d, NOW(), "
        "%f, "
        "%lu, "
        "%ld, "
        "%d, "
        "%d, "
        "'%s', "
        "%d )",
        create_time, appid,
        name, xml_doc, batch,
        rsc_fpops_est, rsc_fpops_bound,
        rsc_memory_bound, rsc_disk_bound,
        need_validate,
        canonical_resultid, canonical_credit,
        transition_time, delay_bound,
        error_mask, file_delete_state, assimilate_state,
        hr_class, opaque,
        min_quorum,
        target_nresults,
        max_error_results,
        max_total_results,
        max_success_results,
        result_template_file,
        priority,
        rsc_bandwidth_bound,
        fileset_id,
        app_version_id,
        transitioner_flags,
        size_class,
        keywords,
        app_version_num
    );
}

void DB_WORKUNIT::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    appid = atol(r[i++]);
    strcpy2(name, r[i++]);
    strcpy2(xml_doc, r[i++]);
    batch = atoi(r[i++]);
    rsc_fpops_est = atof(r[i++]);
    rsc_fpops_bound = atof(r[i++]);
    rsc_memory_bound = atof(r[i++]);
    rsc_disk_bound = atof(r[i++]);
    need_validate = atoi(r[i++]);
    canonical_resultid = atol(r[i++]);
    canonical_credit = atof(r[i++]);
    transition_time = atoi(r[i++]);
    delay_bound = atoi(r[i++]);
    error_mask = atoi(r[i++]);
    file_delete_state = atoi(r[i++]);
    assimilate_state = atoi(r[i++]);
    hr_class = atoi(r[i++]);
    opaque = atof(r[i++]);
    min_quorum = atoi(r[i++]);
    target_nresults = atoi(r[i++]);
    max_error_results = atoi(r[i++]);
    max_total_results = atoi(r[i++]);
    max_success_results = atoi(r[i++]);
    strcpy2(result_template_file, r[i++]);
    priority = atoi(r[i++]);
    strcpy2(mod_time, r[i++]);
    rsc_bandwidth_bound = atof(r[i++]);
    fileset_id = atol(r[i++]);
    app_version_id = atol(r[i++]);
    transitioner_flags = atoi(r[i++]);
    size_class = atoi(r[i++]);
    strcpy2(keywords, r[i++]);
    app_version_num = atoi(r[i++]);
}

void DB_CREDITED_JOB::db_print(char* buf){
    sprintf(buf,
        "userid=%lu, workunitid=%lu",
        userid, workunitid
    );
}

void DB_CREDITED_JOB::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    userid = atol(r[i++]);
    workunitid = atol(r[i++]);
}

void DB_RESULT::db_print(char* buf){
    ESCAPE(xml_doc_out);
    ESCAPE(stderr_out);
    sprintf(
        buf,
        "create_time=%d, workunitid=%lu, "
        "server_state=%d, outcome=%d, client_state=%d, "
        "hostid=%lu, userid=%lu, "
        "report_deadline=%d, sent_time=%d, received_time=%d, "
        "name='%s', cpu_time=%.15e, "
        "xml_doc_in='%s', xml_doc_out='%s', stderr_out='%s', "
        "batch=%d, file_delete_state=%d, validate_state=%d, "
        "claimed_credit=%.15e, granted_credit=%.15e, opaque=%.15e, random=%d, "
        "app_version_num=%d, appid=%lu, exit_status=%d, teamid=%lu, "
        "priority=%d, elapsed_time=%.15e, flops_estimate=%.15e, "
        "app_version_id=%ld, runtime_outlier=%d, size_class=%d, "
        "peak_working_set_size=%.0f, "
        "peak_swap_size=%.0f, "
        "peak_disk_usage=%.0f ",
        create_time, workunitid,
        server_state, outcome, client_state,
        hostid, userid,
        report_deadline, sent_time, received_time,
        name, cpu_time,
        xml_doc_in, xml_doc_out, stderr_out,
        batch, file_delete_state, validate_state,
        claimed_credit, granted_credit, opaque, random,
        app_version_num, appid, exit_status, teamid,
        priority, elapsed_time, flops_estimate,
        app_version_id,
        runtime_outlier?1:0,
        size_class,
        peak_working_set_size,
        peak_swap_size,
        peak_disk_usage
    );
    UNESCAPE(xml_doc_out);
    UNESCAPE(stderr_out);
}

// the following used for "batch insert" from transitioner
//
void DB_RESULT::db_print_values(char* buf){
    ESCAPE(xml_doc_out);
    ESCAPE(stderr_out);
    sprintf(
        buf,
        "(0, %d, %lu, "
        "%d, %d, %d, "
        "%lu, %lu, "
        "%d, %d, %d, "
        "'%s', %.15e, "
        "'%s', '%s', '%s', "
        "%d, %d, %d, "
        "%.15e, %.15e, %.15e, %d, "
        "%d, %lu, %d, %lu, %d, NOW(), 0, 0, 0, 0, %d, 0, 0, 0)",
        create_time, workunitid,
        server_state, outcome, client_state,
        hostid, userid,
        report_deadline, sent_time, received_time,
        name, cpu_time,
        xml_doc_in, xml_doc_out, stderr_out,
        batch, file_delete_state, validate_state,
        claimed_credit, granted_credit, opaque, random,
        app_version_num, appid, exit_status, teamid, priority, size_class
    );
    UNESCAPE(xml_doc_out);
    UNESCAPE(stderr_out);
}

// called from scheduler when dispatch this result.
// The "... and server_state=%d" is a safeguard against
// the case where another scheduler tries to send this result at the same time
//
int DB_RESULT::mark_as_sent(int old_server_state, int report_grace_period) {
    char query[MAX_QUERY_LEN];
    int retval;

    sprintf(query,
        "update result set server_state=%d, hostid=%lu, userid=%lu, sent_time=%d, report_deadline=%d, flops_estimate=%.15e, app_version_id=%ld  where id=%lu and server_state=%d",
        server_state,
        hostid,
        userid,
        sent_time,
        report_deadline + report_grace_period,
        flops_estimate,
        app_version_id,
        id,
        old_server_state
    );
    retval = db->do_query(query);
    if (retval) return retval;
    if (db->affected_rows() != 1) return ERR_DB_NOT_FOUND;
    return 0;
}

void DB_RESULT::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    workunitid = atol(r[i++]);
    server_state = atoi(r[i++]);
    outcome = atoi(r[i++]);
    client_state = atoi(r[i++]);
    hostid = atol(r[i++]);
    userid = atol(r[i++]);
    report_deadline = atoi(r[i++]);
    sent_time = atoi(r[i++]);
    received_time = atoi(r[i++]);
    strcpy2(name, r[i++]);
    cpu_time = atof(r[i++]);
    strcpy2(xml_doc_in, r[i++]);
    strcpy2(xml_doc_out, r[i++]);
    strcpy2(stderr_out, r[i++]);
    batch = atoi(r[i++]);
    file_delete_state = atoi(r[i++]);
    validate_state = atoi(r[i++]);
    claimed_credit = atof(r[i++]);
    granted_credit = atof(r[i++]);
    opaque = atof(r[i++]);
    random = atoi(r[i++]);
    app_version_num = atoi(r[i++]);
    appid = atol(r[i++]);
    exit_status = atoi(r[i++]);
    teamid = atol(r[i++]);
    priority = atoi(r[i++]);
    strcpy2(mod_time, r[i++]);
    elapsed_time = atof(r[i++]);
    flops_estimate = atof(r[i++]);
    app_version_id = atol(r[i++]);
    runtime_outlier = (atoi(r[i++]) != 0);
    size_class = atoi(r[i++]);
    peak_working_set_size = atof(r[i++]);
    peak_swap_size = atof(r[i++]);
    peak_disk_usage = atof(r[i++]);
}

// faster version.
// return unsent count up to a max of "count_max"
//
int DB_RESULT::get_unsent_counts(APP& app, int* unsent_count, int count_max) {
    char query[1024];
    int retval;
    MYSQL_RES *rp;

    for (int i=0; i<app.n_size_classes; i++) {
        sprintf(query,
            "select id from result where appid=%lu and server_state=%d and size_class=%d limit %d",
            app.id, RESULT_SERVER_STATE_UNSENT, i, count_max
        );
        retval = db->do_query(query);
        if (retval) return mysql_errno(db->mysql);
        rp = mysql_store_result(db->mysql);
        if (!rp) return mysql_errno(db->mysql);
        int count = 0;
        while (1) {
            MYSQL_ROW row = mysql_fetch_row(rp);
            if (!row) break;
            count++;
        }
        mysql_free_result(rp);
        unsent_count[i] = count;
    }
    return 0;
}

#if 0
// the following is too slow if result table is large.
//
int DB_RESULT::get_unsent_counts(APP& app, int* unsent_count) {
    char query[1024];
    MYSQL_RES *rp;

    for (int i=0; i<app.n_size_classes; i++) {
        unsent_count[i] = 0;
    }

    sprintf(query,
        "select size_class, count(size_class) from result where appid=%lu and server_state=%d group by size_class",
        app.id, RESULT_SERVER_STATE_UNSENT
    );
    int retval = db->do_query(query);
    if (retval) return mysql_errno(db->mysql);
    rp = mysql_store_result(db->mysql);
    if (!rp) return mysql_errno(db->mysql);
    while (1) {
        MYSQL_ROW row = mysql_fetch_row(rp);
        if (!row) break;
        int sc = atoi(row[0]);
        int count = atoi(row[1]);
        if (sc >= app.n_size_classes) {
            fprintf(stderr, "size class %d too large\n", sc);
            retval = -1;
            break;
        }
        unsent_count[sc] = count;
    };
    mysql_free_result(rp);
    return retval;
}
#endif

int DB_RESULT::make_unsent(
    APP& app, int size_class, int n, const char* order_clause, int& nchanged
) {
    char query[1024];
    sprintf(query,
        "update result set server_state=%d where appid=%lu and server_state=%d and size_class=%d %s limit %d",
        RESULT_SERVER_STATE_UNSENT,
        app.id,
        RESULT_SERVER_STATE_INACTIVE,
        size_class,
        order_clause,
        n
    );
    int retval = db->do_query(query);
    if (retval) return mysql_errno(db->mysql);
    nchanged = db->affected_rows();
    return 0;
}

void DB_MSG_FROM_HOST::db_print(char* buf) {
    ESCAPE(xml);
    ESCAPE(variety);
    sprintf(buf,
        "create_time=%d, "
        "hostid=%lu, variety='%s', "
        "handled=%d, xml='%s'",

        create_time,
        hostid, variety,
        handled, xml

    );
    UNESCAPE(xml);
    UNESCAPE(variety);
}

void DB_MSG_FROM_HOST::db_parse(MYSQL_ROW& r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    hostid = atol(r[i++]);
    strcpy2(variety, r[i++]);
    handled = atoi(r[i++]);
    strcpy2(xml, r[i++]);
}

void DB_MSG_TO_HOST::db_print(char* buf) {
    ESCAPE(xml);
    ESCAPE(variety);
    sprintf(buf,
        "create_time=%d, "
        "hostid=%lu, variety='%s', "
        "handled=%d, xml='%s'",
        create_time,
        hostid, variety,
        handled, xml
    );
    UNESCAPE(xml);
    UNESCAPE(variety);
}

void DB_MSG_TO_HOST::db_parse(MYSQL_ROW& r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    hostid = atol(r[i++]);
    strcpy2(variety, r[i++]);
    handled = atoi(r[i++]);
    strcpy2(xml, r[i++]);
}

void DB_ASSIGNMENT::db_print(char* buf) {
    sprintf(buf,
        "create_time=%d, "
        "target_id=%lu, "
        "target_type=%d, "
        "multi=%d, "
        "workunitid=%lu, "
        "resultid=%lu",
        create_time,
        target_id,
        target_type,
        multi,
        workunitid,
        _resultid
    );
}

void DB_ASSIGNMENT::db_parse(MYSQL_ROW& r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atoi(r[i++]);
    target_id = atol(r[i++]);
    target_type = atoi(r[i++]);
    multi = atoi(r[i++]);
    workunitid = atol(r[i++]);
    _resultid = atol(r[i++]);
}

int DB_HOST_APP_VERSION::update_scheduler(DB_HOST_APP_VERSION& orig) {
    char query[1024], clause[512];

    if (consecutive_valid == orig.consecutive_valid
        && max_jobs_per_day == orig.max_jobs_per_day
        && n_jobs_today == orig.n_jobs_today
    ) {
        return 0;
    }
    sprintf(query,
        "consecutive_valid=%d, max_jobs_per_day=%d, n_jobs_today=%d",
        consecutive_valid,
        max_jobs_per_day,
        n_jobs_today
    );
    sprintf(clause,
        "host_id=%lu and app_version_id=%ld",
        host_id, app_version_id
    );
    return update_fields_noid(query, clause);
}

int DB_HOST_APP_VERSION::update_validator(DB_HOST_APP_VERSION& orig) {
    char query[8192], clause[512];

    if (pfc.n == orig.pfc.n
        && pfc.avg == orig.pfc.avg
        && et.n == orig.et.n
        && et.avg == orig.et.avg
        && et.q == orig.et.q
        && et.var == orig.et.var
        && turnaround.n == orig.turnaround.n
        && turnaround.avg == orig.turnaround.avg
        && turnaround.q == orig.turnaround.q
        && turnaround.var == orig.turnaround.var
        && consecutive_valid == orig.consecutive_valid
        && max_jobs_per_day == orig.max_jobs_per_day
    ) {
        return 0;
    }
    sprintf(query,
        "pfc_n=%.15e, "
        "pfc_avg=%.15e, "
        "et_n=%.15e, "
        "et_avg=%.15e, "
        "et_q=%.15e, "
        "et_var=%.15e, "
        "turnaround_n=%.15e, "
        "turnaround_avg=%.15e, "
        "turnaround_q=%.15e, "
        "turnaround_var=%.15e, "
        "consecutive_valid=%d, "
        "max_jobs_per_day=%d ",
        pfc.n,
        pfc.avg,
        et.n,
        et.avg,
        et.q,
        et.var,
        turnaround.n,
        turnaround.avg,
        turnaround.q,
        turnaround.var,
        consecutive_valid,
        max_jobs_per_day
    );
    sprintf(clause,
        "host_id=%lu and app_version_id=%ld ",
        host_id, app_version_id
    );
    return update_fields_noid(query, clause);
}

void DB_HOST_APP_VERSION::db_print(char* buf) {
    sprintf(buf,
        "host_id=%lu, "
        "app_version_id=%ld, "
        "pfc_n=%.15e, "
        "pfc_avg=%.15e, "
        "et_n=%.15e, "
        "et_avg=%.15e, "
        "et_var=%.15e, "
        "et_q=%.15e, "
        "max_jobs_per_day=%d, "
        "n_jobs_today=%d, "
        "turnaround_n=%.15e, "
        "turnaround_avg=%.15e, "
        "turnaround_var=%.15e, "
        "turnaround_q=%.15e, "
        "consecutive_valid=%d ",
        host_id,
        app_version_id,
        pfc.n,
        pfc.avg,
        et.n,
        et.avg,
        et.var,
        et.q,
        max_jobs_per_day,
        n_jobs_today,
        turnaround.n,
        turnaround.avg,
        turnaround.var,
        turnaround.q,
        consecutive_valid
    );
}

void DB_HOST_APP_VERSION::db_parse(MYSQL_ROW& r) {
    int i=0;
    clear();
    host_id = atol(r[i++]);
    app_version_id = atol(r[i++]);
    pfc.n = atof(r[i++]);
    pfc.avg = atof(r[i++]);
    et.n = atof(r[i++]);
    et.avg = atof(r[i++]);
    et.var = atof(r[i++]);
    et.q = atof(r[i++]);
    max_jobs_per_day = atoi(r[i++]);
    n_jobs_today = atoi(r[i++]);
    turnaround.n = atof(r[i++]);
    turnaround.avg = atof(r[i++]);
    turnaround.var = atof(r[i++]);
    turnaround.q = atof(r[i++]);
    consecutive_valid = atoi(r[i++]);
}

void DB_USER_SUBMIT::db_print(char* buf) {
    sprintf(buf,
        "user_id=%lu, "
        "quota=%.15e, "
        "logical_start_time=%.15e, "
        "submit_all=%d, "
        "manage_all=%d ",
        user_id,
        quota,
        logical_start_time,
        submit_all?1:0,
        manage_all?1:0
    );
}

void DB_USER_SUBMIT::db_parse(MYSQL_ROW& r) {
    int i=0;
    clear();
    user_id = atol(r[i++]);
    quota = atof(r[i++]);
    logical_start_time = atof(r[i++]);
    submit_all = (atoi(r[i++]) != 0);
    manage_all = (atoi(r[i++]) != 0);
}

void DB_STATE_COUNTS::db_print(char* buf) {
    sprintf(buf,
        "appid=%lu, "
        "last_update_time=%d, "
        "result_server_state_2=%d, "
        "result_server_state_4=%d, "
        "result_file_delete_state_1=%d, "
        "result_file_delete_state_2=%d, "
        "result_server_state_5_and_file_delete_state_0=%d, "
        "workunit_need_validate_1=%d, "
        "workunit_assimilate_state_1=%d, "
        "workunit_file_delete_state_1=%d, "
        "workunit_file_delete_state_2=%d, ",
        appid,
        last_update_time,   
        result_server_state_2,      
        result_server_state_4,      
        result_file_delete_state_1, 
        result_file_delete_state_2, 
        result_server_state_5_and_file_delete_state_0,      
        workunit_need_validate_1,   
        workunit_assimilate_state_1,
        workunit_file_delete_state_1,       
        workunit_file_delete_state_2  
    );
}

void DB_STATE_COUNTS::db_parse(MYSQL_ROW& r) {
    int i=0;
    clear();
    appid = atoi(r[i++]);
    last_update_time = atoi(r[i++]);
    result_server_state_2 = atoi(r[i++]);      
    result_server_state_4 = atoi(r[i++]);      
    result_file_delete_state_1 = atoi(r[i++]); 
    result_file_delete_state_2 = atoi(r[i++]); 
    result_server_state_5_and_file_delete_state_0 = atoi(r[i++]);      
    workunit_need_validate_1 = atoi(r[i++]);   
    workunit_assimilate_state_1 = atoi(r[i++]);
    workunit_file_delete_state_1 = atoi(r[i++]);       
    workunit_file_delete_state_2 = atoi(r[i++]);
}

void TRANSITIONER_ITEM::parse(MYSQL_ROW& r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    strcpy2(name, r[i++]);
    appid = atol(r[i++]);
    min_quorum = atoi(r[i++]);
    need_validate = atoi(r[i++]);
    canonical_resultid = atol(r[i++]);
    transition_time = atoi(r[i++]);
    delay_bound = atoi(r[i++]);
    error_mask = atoi(r[i++]);
    max_error_results = atoi(r[i++]);
    max_total_results = atoi(r[i++]);
    file_delete_state = atoi(r[i++]);
    assimilate_state = atoi(r[i++]);
    target_nresults = atoi(r[i++]);
    strcpy2(result_template_file, r[i++]);
    priority = atoi(r[i++]);
    hr_class = atoi(r[i++]);
    batch = atoi(r[i++]);
    app_version_id = atol(r[i++]);
    transitioner_flags = atoi(r[i++]);
    size_class = atoi(r[i++]);

    // use safe_atoi() from here on cuz they might not be there
    //
    res_id = safe_atol(r[i++]);
    strcpy2(res_name, r[i++]);
    res_report_deadline = safe_atoi(r[i++]);
    res_server_state = safe_atoi(r[i++]);
    res_outcome = safe_atoi(r[i++]);
    res_validate_state = safe_atoi(r[i++]);
    res_file_delete_state = safe_atoi(r[i++]);
    res_sent_time = safe_atoi(r[i++]);
    res_hostid = safe_atol(r[i++]);
    res_received_time = safe_atoi(r[i++]);
    res_app_version_id = safe_atol(r[i++]);
    res_exit_status = safe_atoi(r[i++]);
}

int DB_TRANSITIONER_ITEM_SET::enumerate(
    int transition_time, int nresult_limit,
    int wu_id_modulus, int wu_id_remainder,
    std::vector<TRANSITIONER_ITEM>& items
) {
    int retval;
    char query[MAX_QUERY_LEN];
    char mod_clause[256];;
    char time_clause[256];
    MYSQL_ROW row;
    TRANSITIONER_ITEM new_item;

    if (!cursor.active) {
        sprintf(time_clause, " wu.transition_time < %d ", transition_time);
        if (wu_id_modulus) {
            // terrible kludge: if rem >= mod, treat it as a WU ID
            // This is to support the --wu_id debugging feature
            //
            if (wu_id_remainder < wu_id_modulus) {
                sprintf(mod_clause,
                    " and wu.id %% %d = %d ",
                    wu_id_modulus, wu_id_remainder
                );
            } else {
                sprintf(mod_clause, " and wu.id = %u ", wu_id_remainder);
                strcpy(time_clause, " true ");
            }
        } else {
            strcpy(mod_clause, "");
        }


        sprintf(query,
            "SELECT "
            "   wu.id, "
            "   wu.name, "
            "   wu.appid, "
            "   wu.min_quorum, "
            "   wu.need_validate, "
            "   wu.canonical_resultid, "
            "   wu.transition_time, "
            "   wu.delay_bound, "
            "   wu.error_mask, "
            "   wu.max_error_results, "
            "   wu.max_total_results, "
            "   wu.file_delete_state, "
            "   wu.assimilate_state, "
            "   wu.target_nresults, "
            "   wu.result_template_file, "
            "   wu.priority, "
            "   wu.hr_class, "
            "   wu.batch, "
            "   wu.app_version_id, "
            "   wu.transitioner_flags, "
            "   wu.size_class, "
            "   res.id, "
            "   res.name, "
            "   res.report_deadline, "
            "   res.server_state, "
            "   res.outcome, "
            "   res.validate_state, "
            "   res.file_delete_state, "
            "   res.sent_time, "
            "   res.hostid, "
            "   res.received_time, "
            "   res.app_version_id, "
            "   res.exit_status "
            "FROM "
            "   workunit AS wu "
            "       LEFT JOIN result AS res ON wu.id = res.workunitid "
            "WHERE "
            "   %s %s and transitioner_flags<>%d "
            "LIMIT "
            "   %d ",
            time_clause, mod_clause, TRANSITION_NONE, nresult_limit
        );

        retval = db->do_query(query);
        if (retval) return mysql_errno(db->mysql);

        // the following stores the entire result set in memory
        //
        cursor.rp = mysql_store_result(db->mysql);
        if (!cursor.rp) return mysql_errno(db->mysql);
        cursor.active = true;

        row = mysql_fetch_row(cursor.rp);
        if (!row) {
            mysql_free_result(cursor.rp);
            cursor.active = false;
            retval = mysql_errno(db->mysql);
            if (retval) return ERR_DB_CONN_LOST;
            return ERR_DB_NOT_FOUND;
        }
        last_item.parse(row);
        nitems_this_query = 1;
    }

    items.clear();
    while (true) {
        items.push_back(last_item);
        row = mysql_fetch_row(cursor.rp);
        if (!row) {
            mysql_free_result(cursor.rp);
            cursor.active = false;

            // if got fewer rows than requested, last group is complete
            //
            if (nitems_this_query < nresult_limit) {
                return 0;
            } else {
                return ERR_DB_NOT_FOUND;
            }
        }
        new_item.parse(row);
        nitems_this_query++;
        if (new_item.id != last_item.id) {
            last_item = new_item;
            return 0;
        }
        last_item = new_item;
    }

    return 0;
}

int DB_TRANSITIONER_ITEM_SET::update_result(TRANSITIONER_ITEM& ti) {
    char query[MAX_QUERY_LEN];

    sprintf(query,
        "update result set server_state=%d, outcome=%d, "
        "validate_state=%d, file_delete_state=%d where id=%lu",
        ti.res_server_state,
        ti.res_outcome,
        ti.res_validate_state,
        ti.res_file_delete_state,
        ti.res_id
    );
    int retval = db->do_query(query);
    if (db->affected_rows() != 1) return ERR_DB_NOT_FOUND;
    return retval;
}

int DB_TRANSITIONER_ITEM_SET::update_workunit(
    TRANSITIONER_ITEM& ti, TRANSITIONER_ITEM& ti_original
) {
    char query[MAX_QUERY_LEN];
    char updates[4096], buf[256];

    strcpy(updates, "");
    if (ti.need_validate != ti_original.need_validate) {
        sprintf(buf, " need_validate=%d,", ti.need_validate);
        strcat(updates, buf);
    }
    if (ti.error_mask != ti_original.error_mask) {
        sprintf(buf, " error_mask=%d,", ti.error_mask);
        strcat(updates, buf);
    }
    if (ti.assimilate_state != ti_original.assimilate_state) {
        sprintf(buf, " assimilate_state=%d,", ti.assimilate_state);
        strcat(updates, buf);
    }
    if (ti.file_delete_state != ti_original.file_delete_state) {
        sprintf(buf, " file_delete_state=%d,", ti.file_delete_state);
        strcat(updates, buf);
    }
    if (ti.transition_time != ti_original.transition_time) {
        sprintf(buf, " transition_time=%d,", ti.transition_time);
        strcat(updates, buf);
    }
    if (ti.hr_class != ti_original.hr_class) {
        sprintf(buf, " hr_class=%d,", ti.hr_class);
        strcat(updates, buf);
    }
    if (ti.app_version_id != ti_original.app_version_id) {
        sprintf(buf, " app_version_id=%lu,", ti.app_version_id);
        strcat(updates, buf);
    }
    int n = strlen(updates);
    if (n == 0) {
        return 0;
    }

    // trim the final comma
    //
    updates[n-1] = 0;

    sprintf(query, "update workunit set %s where id=%lu", updates, ti.id);
    return db->do_query(query);
}

void VALIDATOR_ITEM::parse(MYSQL_ROW& r) {
    int i=0;
    clear();
    wu.id = atol(r[i++]);
    strcpy2(wu.name, r[i++]);
    wu.canonical_resultid = atol(r[i++]);
    wu.canonical_credit = atof(r[i++]);
    wu.min_quorum = atoi(r[i++]);
    wu.assimilate_state = atoi(r[i++]);
    wu.transition_time = atoi(r[i++]);
    wu.opaque = atof(r[i++]);
    wu.batch = atoi(r[i++]);
    wu.target_nresults = atoi(r[i++]);
    wu.max_success_results = atoi(r[i++]);
    wu.error_mask = atoi(r[i++]);
    wu.rsc_fpops_est = atof(r[i++]);
    wu.rsc_fpops_bound = atof(r[i++]);

    res.id = atol(r[i++]);
    res.workunitid = atol(r[i++]);
    strcpy2(res.name, r[i++]);
    res.validate_state = atoi(r[i++]);
    res.server_state = atoi(r[i++]);
    res.outcome = atoi(r[i++]);
    res.granted_credit = atof(r[i++]);
    strcpy2(res.xml_doc_in, r[i++]);
    strcpy2(res.xml_doc_out, r[i++]);
    strcpy2(res.stderr_out, r[i++]);
    res.cpu_time = atof(r[i++]);
    res.batch = atoi(r[i++]);
    res.opaque = atof(r[i++]);
    res.exit_status = atoi(r[i++]);
    res.hostid = atol(r[i++]);
    res.userid = atol(r[i++]);
    res.teamid = atol(r[i++]);
    res.sent_time = atoi(r[i++]);
    res.received_time = atoi(r[i++]);
    res.appid = atol(r[i++]);
    res.app_version_id = atol(r[i++]);
    res.app_version_num = atoi(r[i++]);
    res.elapsed_time = atof(r[i++]);
    res.flops_estimate = atof(r[i++]);
    res.app_version_id = atol(r[i++]);
    res.runtime_outlier = (atoi(r[i++]) != 0);
}

int DB_VALIDATOR_ITEM_SET::enumerate(
    DB_ID_TYPE appid, int nresult_limit,
    int wu_id_modulus, int wu_id_remainder,
    DB_ID_TYPE wu_id_min, DB_ID_TYPE wu_id_max,
    std::vector<VALIDATOR_ITEM>& items
) {
    int retval;
    char query[MAX_QUERY_LEN], mod_clause[256];
    char main_clause[256];
    MYSQL_ROW row;
    VALIDATOR_ITEM new_item;

    if (!cursor.active) {
        sprintf(main_clause,
            " and wu.appid = %lu and wu.need_validate > 0 ", appid
        );
        if (wu_id_modulus) {
            // terrible kludge: if rem >= mod, treat it as a WU ID
            // This is to support the --wu_id debugging feature
            //
            if (wu_id_remainder < wu_id_modulus) {
                sprintf(mod_clause,
                    " and wu.id %% %d = %d ",
                    wu_id_modulus, wu_id_remainder
                );
            } else {
                sprintf(mod_clause, " and wu.id = %u ", wu_id_remainder);
                strcpy(main_clause, "");
            }
        } else {
            strcpy(mod_clause, "");
        }
        if (wu_id_min) {
          sprintf(mod_clause+(strlen(mod_clause)), " and wu.id >= %lu", wu_id_min);
        }
        if (wu_id_max) {
          sprintf(mod_clause+(strlen(mod_clause)), " and wu.id <= %lu", wu_id_max);
        }

        sprintf(query,
            "SELECT "
            "   wu.id, "
            "   wu.name, "
            "   wu.canonical_resultid, "
            "   wu.canonical_credit, "
            "   wu.min_quorum, "
            "   wu.assimilate_state, "
            "   wu.transition_time, "
            "   wu.opaque, "
            "   wu.batch, "
            "   wu.target_nresults, "
            "   wu.max_success_results, "
            "   wu.error_mask, "
            "   wu.rsc_fpops_est, "
            "   wu.rsc_fpops_bound, "
            "   res.id, "
            "   res.workunitid, "
            "   res.name, "
            "   res.validate_state, "
            "   res.server_state, "
            "   res.outcome, "
            "   res.granted_credit, "
            "   res.xml_doc_in, "
            "   res.xml_doc_out, "
            "   res.stderr_out, "
            "   res.cpu_time, "
            "   res.batch, "
            "   res.opaque, "
            "   res.exit_status, "
            "   res.hostid, "
            "   res.userid, "
            "   res.teamid, "
            "   res.sent_time, "
            "   res.received_time, "
            "   res.appid, "
            "   res.app_version_id, "
            "   res.app_version_num, "
            "   res.elapsed_time, "
            "   res.flops_estimate, "
            "   res.app_version_id, "
            "   res.runtime_outlier "
            "FROM "
            "   workunit AS wu, result AS res where wu.id = res.workunitid "
            "   %s %s "
            "LIMIT "
            "   %d ",
            main_clause, mod_clause, nresult_limit
        );

        retval = db->do_query(query);
        if (retval) return mysql_errno(db->mysql);

        // the following stores the entire result set in memory
        cursor.rp = mysql_store_result(db->mysql);
        if (!cursor.rp) return mysql_errno(db->mysql);
        cursor.active = true;

        row = mysql_fetch_row(cursor.rp);
        if (!row) {
            mysql_free_result(cursor.rp);
            cursor.active = false;
            retval = mysql_errno(db->mysql);
            if (retval) return ERR_DB_CONN_LOST;
            return ERR_DB_NOT_FOUND;
        }
        last_item.parse(row);
        nitems_this_query = 1;
    }

    items.clear();
    while (true) {
        items.push_back(last_item);
        row = mysql_fetch_row(cursor.rp);
        if (!row) {
            mysql_free_result(cursor.rp);
            cursor.active = false;

            // if got fewer rows than requested, last group is complete
            //
            if (nitems_this_query < nresult_limit) {
                return 0;
            } else {
                return ERR_DB_NOT_FOUND;
            }
        }
        new_item.parse(row);
        nitems_this_query++;
        if (new_item.wu.id != last_item.wu.id) {
            last_item = new_item;
            return 0;
        }
        last_item = new_item;
    }

    return 0;
}

int DB_VALIDATOR_ITEM_SET::update_result(RESULT& res) {
    char query[MAX_QUERY_LEN];

    sprintf(query,
        "update result set validate_state=%d, granted_credit=%.15e, "
        "server_state=%d, outcome=%d, opaque=%lf, runtime_outlier=%d "
        "where id=%lu",
        res.validate_state,
        res.granted_credit,
        res.server_state,
        res.outcome,
        res.opaque,
        res.runtime_outlier?1:0,
        res.id
    );
    int retval = db->do_query(query);
    if (db->affected_rows() != 1) return ERR_DB_NOT_FOUND;
    return retval;
}


int DB_VALIDATOR_ITEM_SET::update_workunit(WORKUNIT& wu) {
    char query[MAX_QUERY_LEN];

    sprintf(query,
        "update workunit set need_validate=0, error_mask=%d, "
        "assimilate_state=%d, transition_time=%d, "
        "target_nresults=%d, "
        "canonical_resultid=%lu, canonical_credit=%.15e "
        "where id=%lu",
        wu.error_mask,
        wu.assimilate_state,
        wu.transition_time,
        wu.target_nresults,
        wu.canonical_resultid,
        wu.canonical_credit,
        wu.id
    );
    int retval = db->do_query(query);
    if (db->affected_rows() != 1) return ERR_DB_NOT_FOUND;
    return retval;
}

void WORK_ITEM::parse(MYSQL_ROW& r) {
    int i=0;
    memset(this, 0, sizeof(WORK_ITEM));
    res_id = atol(r[i++]);
    res_priority = atoi(r[i++]);
    res_server_state = atoi(r[i++]);
    res_report_deadline = atof(r[i++]);
    wu.id = atol(r[i++]);
    wu.create_time = atoi(r[i++]);
    wu.appid = atol(r[i++]);
    strcpy2(wu.name, r[i++]);
    strcpy2(wu.xml_doc, r[i++]);
    wu.batch = atoi(r[i++]);
    wu.rsc_fpops_est = atof(r[i++]);
    wu.rsc_fpops_bound = atof(r[i++]);
    wu.rsc_memory_bound = atof(r[i++]);
    wu.rsc_disk_bound = atof(r[i++]);
    wu.need_validate = atoi(r[i++]);
    wu.canonical_resultid = atol(r[i++]);
    wu.canonical_credit = atof(r[i++]);
    wu.transition_time = atoi(r[i++]);
    wu.delay_bound = atoi(r[i++]);
    wu.error_mask = atoi(r[i++]);
    wu.file_delete_state = atoi(r[i++]);
    wu.assimilate_state = atoi(r[i++]);
    wu.hr_class = atoi(r[i++]);
    wu.opaque = atof(r[i++]);
    wu.min_quorum = atoi(r[i++]);
    wu.target_nresults = atoi(r[i++]);
    wu.max_error_results = atoi(r[i++]);
    wu.max_total_results = atoi(r[i++]);
    wu.max_success_results = atoi(r[i++]);
    strcpy2(wu.result_template_file, r[i++]);
    wu.priority = atoi(r[i++]);
    strcpy2(wu.mod_time, r[i++]);
    wu.rsc_bandwidth_bound = atof(r[i++]);
    wu.fileset_id = atol(r[i++]);
    wu.app_version_id = atol(r[i++]);
    wu.transitioner_flags = atoi(r[i++]);
    wu.size_class = atoi(r[i++]);
    strcpy2(wu.keywords, r[i++]);
    wu.app_version_num = atoi(r[i++]);
}

int DB_WORK_ITEM::enumerate(
    int limit, const char* select_clause, const char* order_clause
) {
    char query[MAX_QUERY_LEN];
    int retval;
    MYSQL_ROW row;
    if (!cursor.active) {
        // use "r1" to refer to the result, since the feeder assumes that
        // (historical reasons)
        //
        sprintf(query,
            "select high_priority r1.id, r1.priority, r1.server_state, r1.report_deadline, workunit.* from result r1 force index(ind_res_st), workunit, app "
            " where r1.server_state=%d "
            " and r1.workunitid=workunit.id "
            " and workunit.appid=app.id "
            " and app.deprecated=0 "
            " and workunit.transitioner_flags=0 "
            " %s "
            " %s "
            "limit %d",
            RESULT_SERVER_STATE_UNSENT,
            select_clause,
            order_clause,
            limit
        );
        retval = db->do_query(query);
        if (retval) return mysql_errno(db->mysql);
        cursor.rp = mysql_store_result(db->mysql);
        if (!cursor.rp) return mysql_errno(db->mysql);
        cursor.active = true;
    }
    row = mysql_fetch_row(cursor.rp);
    if (!row) {
        mysql_free_result(cursor.rp);
        cursor.active = false;
        retval = mysql_errno(db->mysql);
        if (retval) return ERR_DB_CONN_LOST;
        return ERR_DB_NOT_FOUND;
    } else {
        parse(row);
    }
    return 0;
}

int DB_WORK_ITEM::enumerate_all(
    int limit, const char* select_clause
) {
    char query[MAX_QUERY_LEN];
    int retval;
    MYSQL_ROW row;
    if (!cursor.active) {
        // use "r1" to refer to the result, since the feeder assumes that
        // (historical reasons)
        //
        sprintf(query,
            "select high_priority r1.id, r1.priority, r1.server_state, r1.report_deadline, workunit.* from result r1 force index(ind_res_st), workunit force index(primary), app"
            " where r1.server_state=%d and r1.workunitid=workunit.id and r1.id>%lu "
            " and workunit.appid=app.id and app.deprecated=0 "
            " and workunit.transitioner_flags=0 "
            " %s "
            "limit %d",
            RESULT_SERVER_STATE_UNSENT,
            start_id,
            select_clause,
            limit
        );
        retval = db->do_query(query);
        if (retval) return mysql_errno(db->mysql);
        cursor.rp = mysql_store_result(db->mysql);
        if (!cursor.rp) return mysql_errno(db->mysql);

        // if query gets no rows, start over in ID space
        //
        if (mysql_num_rows(cursor.rp) == 0) {
            mysql_free_result(cursor.rp);
            start_id = 0;
            return ERR_DB_NOT_FOUND;
        }
        cursor.active = true;
    }
    row = mysql_fetch_row(cursor.rp);
    if (!row) {
        mysql_free_result(cursor.rp);
        cursor.active = false;
        retval = mysql_errno(db->mysql);
        if (retval) return ERR_DB_CONN_LOST;
        return ERR_DB_NOT_FOUND;
    } else {
        parse(row);
        start_id = res_id;
    }
    return 0;
}


void IN_PROGRESS_RESULT::parse(MYSQL_ROW& r) {
    int i=0;
    memset(this, 0, sizeof(IN_PROGRESS_RESULT));
    strcpy2(result_name,r[i++]);
    error_mask = atoi(r[i++]);
    assimilate_state = atoi(r[i++]);
    server_state = atoi(r[i++]);
    outcome = atoi(r[i++]);
}

int DB_IN_PROGRESS_RESULT::enumerate(DB_ID_TYPE hostid, const char* result_names) {
    char query[MAX_QUERY_LEN];
    int retval;
    MYSQL_ROW row;
    if (!cursor.active) {
        sprintf(query,
            "select high_priority result.name, workunit.error_mask, workunit.assimilate_state, result.server_state, result.outcome "
            " from result, workunit "
            " where result.hostid = %lu and workunit.id = result.workunitid "
            " and (result.server_state=%d or ( result.server_state = %d and result.outcome = %d ) ) "
            " and result.name in (%s) ",
            hostid,
            RESULT_SERVER_STATE_IN_PROGRESS,
            RESULT_SERVER_STATE_OVER,
            RESULT_OUTCOME_NO_REPLY,
            result_names
        );
        retval = db->do_query(query);
        if (retval) return mysql_errno(db->mysql);
        cursor.rp = mysql_store_result(db->mysql);
        if (!cursor.rp) return mysql_errno(db->mysql);
        cursor.active = true;
    }
    row = mysql_fetch_row(cursor.rp);
    if (!row) {
        mysql_free_result(cursor.rp);
        cursor.active = false;
        retval = mysql_errno(db->mysql);
        if (retval) return ERR_DB_CONN_LOST;
        return ERR_DB_NOT_FOUND;
    } else {
        parse(row);
    }
    return 0;
}

// The items that appear here must agree with those that appear in the
// enumerate method just below!
//
void SCHED_RESULT_ITEM::parse(MYSQL_ROW& r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    strcpy2(name, r[i++]);
    workunitid = atol(r[i++]);
    appid = atol(r[i++]);
    server_state = atoi(r[i++]);
    hostid = atol(r[i++]);
    userid = atol(r[i++]);
    sent_time = atoi(r[i++]);
    received_time = atoi(r[i++]);
    validate_state = atoi(r[i++]);
    outcome = atoi(r[i++]);
    client_state = atoi(r[i++]);
    file_delete_state = atoi(r[i++]);
    app_version_id = atol(r[i++]);
}

int DB_SCHED_RESULT_ITEM_SET::add_result(char* result_name) {
    SCHED_RESULT_ITEM result;
    result.id = 0;
    strcpy2(result.queried_name, result_name);
    results.push_back(result);
    return 0;
}

int DB_SCHED_RESULT_ITEM_SET::enumerate() {
    string query;
    int retval;
    unsigned int i;
    MYSQL_RES* rp;
    MYSQL_ROW row;
    SCHED_RESULT_ITEM ri;


    query =
        "SELECT "
        "   id, "
        "   name, "
        "   workunitid, "
        "   appid, "
        "   server_state, "
        "   hostid, "
        "   userid, "
        "   sent_time, "
        "   received_time, "
        "   validate_state, "
        "   outcome, "
        "   client_state, "
        "   file_delete_state, "
        "   app_version_id "
        "FROM "
        "   result "
        "WHERE "
        "   name IN ( "
    ;

    for (i=0; i<results.size(); i++) {
        if (i>0) query += ",";
        query += "'";
        query += results[i].queried_name;
        query += "'";
    }
    query += ")";

    retval = db->do_query(query.c_str());
    if (retval) return retval;

    // the following stores the entire result set in memory
    //
    rp = mysql_store_result(db->mysql);
    if (!rp) return mysql_errno(db->mysql);

    do {
        row = mysql_fetch_row(rp);
        if (!row) {
            mysql_free_result(rp);
        } else {
            ri.parse(row);
            for (i=0; i<results.size(); i++) {
                if (!strcmp(results[i].queried_name, ri.name)) {
                    results[i] = ri;
                }
            }
        }
    } while (row);

    return 0;
}

int DB_SCHED_RESULT_ITEM_SET::lookup_result(char* result_name, SCHED_RESULT_ITEM** rip) {
    unsigned int i;
    for (i=0; i<results.size(); i++) {
        if (!strcmp(results[i].name, result_name)) {
            *rip = &results[i];
            return 0;
        }
    }
    return -1;
}

int DB_SCHED_RESULT_ITEM_SET::update_result(SCHED_RESULT_ITEM& ri) {
    char query[MAX_QUERY_LEN];
    int retval;

    ESCAPE(ri.xml_doc_out);
    ESCAPE(ri.stderr_out);
    sprintf(query,
        "UPDATE result SET "
        "    hostid=%lu, "
        "    received_time=%d, "
        "    client_state=%d, "
        "    cpu_time=%.15e, "
        "    exit_status=%d, "
        "    app_version_num=%d, "
        "    server_state=%d, "
        "    outcome=%d, "
        "    stderr_out='%s', "
        "    xml_doc_out='%s', "
        "    validate_state=%d, "
        "    teamid=%lu, "
        "    elapsed_time=%.15e, "
        "    peak_working_set_size=%.0f, "
        "    peak_swap_size=%.0f, "
        "    peak_disk_usage=%.0f "
        "WHERE "
        "    id=%lu",
        ri.hostid,
        ri.received_time,
        ri.client_state,
        ri.cpu_time,
        ri.exit_status,
        ri.app_version_num,
        ri.server_state,
        ri.outcome,
        ri.stderr_out,
        ri.xml_doc_out,
        ri.validate_state,
        ri.teamid,
        ri.elapsed_time,
        ri.peak_working_set_size,
        ri.peak_swap_size,
        ri.peak_disk_usage,
        ri.id
    );
    retval = db->do_query(query);
    UNESCAPE(ri.xml_doc_out);
    UNESCAPE(ri.stderr_out);
    if (db->affected_rows() != 1) return ERR_DB_NOT_FOUND;
    return retval;
}

// set transition times of workunits -
// but only those corresponding to updated results
// (i.e. those that passed "sanity checks")
//
int DB_SCHED_RESULT_ITEM_SET::update_workunits() {
    char query[MAX_QUERY_LEN], buf[256];
    unsigned int i;
    bool first= true;

    sprintf(query,
        "UPDATE workunit SET transition_time=%d WHERE id in (",
        (int)time(0)
    );
    for (i=0; i<results.size(); i++) {
        if (results[i].id == 0) continue;
            // skip non-updated results
        if (!first) strcat(query, ",");
        first = false;
        sprintf(buf, "%lu", results[i].workunitid);
        strcat(query, buf);
    }
    strcat(query, ")");
    if (first) {
        return 0;
    } else {
        return db->do_query(query);
    }
}

void DB_FILE::db_print(char* buf){
    snprintf(buf, MAX_QUERY_LEN,
        "name='%s', md5sum='%s', size=%.15e",
        name, md5sum, size
    );
}

void DB_FILE::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    strcpy2(name, r[i++]);
    strcpy2(md5sum, r[i++]);
    size = atof(r[i++]);
}

void DB_FILESET::db_print(char* buf){
    snprintf(buf, MAX_QUERY_LEN, "name='%s'", name);
}

void DB_FILESET::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    strcpy2(name, r[i++]);
}

int DB_FILESET::select_by_name(const char* _name) {
    char where_clause[MAX_QUERY_LEN] = {0};

    // construct where clause and select single record
    snprintf(where_clause, MAX_QUERY_LEN, "WHERE name = '%s'", _name);
    return lookup(where_clause);
}

void DB_FILESET_FILE::db_print(char* buf){
    snprintf(buf, MAX_QUERY_LEN,
        "fileset_id=%lu, file_id=%lu",
        fileset_id, file_id
    );
}

void DB_FILESET_FILE::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    fileset_id = atol(r[i++]);
    file_id = atol(r[i++]);
}

void DB_SCHED_TRIGGER::db_print(char* buf){
    snprintf(buf, MAX_QUERY_LEN,
        "fileset_id=%lu, need_work=%d, work_available=%d, no_work_available=%d, working_set_removal=%d",
        fileset_id, need_work?1:0, work_available?1:0, no_work_available?1:0, working_set_removal?1:0
    );
}

void DB_SCHED_TRIGGER::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    fileset_id = atol(r[i++]);
    need_work = atoi(r[i++]);
    work_available = atoi(r[i++]);
    no_work_available = atoi(r[i++]);
    working_set_removal = atoi(r[i++]);
}

int DB_SCHED_TRIGGER::select_unique_by_fileset_name(const char* fileset_name) {
    char query[MAX_QUERY_LEN];
    int retval;
    int nrows = 0;
    MYSQL_RES* recordset;
    MYSQL_ROW row;

    //if (!cursor.active) {
        // prepare statement
        snprintf(query, MAX_QUERY_LEN,
            "SELECT"
            "  t.id,"
            "  t.fileset_id,"
            "  t.need_work,"
            "  t.work_available,"
            "  t.no_work_available,"
            "  t.working_set_removal "
            "FROM"
            "  fileset fs INNER JOIN sched_trigger t ON fs.id = t.fileset_id "
            "WHERE"
            "  fs.name = '%s'",
            fileset_name
        );

        retval = db->do_query(query);

        if (retval) return mysql_errno(db->mysql);

        recordset = mysql_store_result(db->mysql);
        if (!recordset) return mysql_errno(db->mysql);
    //}

    // determine number of records, fetch first
    nrows = mysql_num_rows(recordset);
    row = mysql_fetch_row(recordset);

    if (!row || nrows != 1) {
        // something bad happened
        if (!row) {
            // no row returned, due to an error?
            retval = mysql_errno(db->mysql);
            mysql_free_result(recordset);

            // yes, probably lost DB connection
            if (retval) return ERR_DB_CONN_LOST;

            // no, just no record available
            return ERR_DB_NOT_FOUND;
        }
        else {
            // we got more records than expected
            mysql_free_result(recordset);
            return ERR_DB_NOT_UNIQUE;
        }
    } else {
        // all fine, parse single record
        db_parse(row);
        mysql_free_result(recordset);
    }
    return 0;
}

int DB_SCHED_TRIGGER::update_single_state(const DB_SCHED_TRIGGER::STATE state, const bool value) {
    char column_clause[MAX_QUERY_LEN] = {0};
    int retval = 0;

    switch(state) {
    case DB_SCHED_TRIGGER::state_need_work:
        snprintf(column_clause, MAX_QUERY_LEN, "need_work = %d", value?1:0);
        need_work = value;
        break;
    case DB_SCHED_TRIGGER::state_work_available:
        snprintf(column_clause, MAX_QUERY_LEN, "work_available = %d", value?1:0);
        work_available = value;
        break;
    case DB_SCHED_TRIGGER::state_no_work_available:
        snprintf(column_clause, MAX_QUERY_LEN, "no_work_available = %d", value?1:0);
        no_work_available = value;
        break;
    case DB_SCHED_TRIGGER::state_working_set_removal:
        snprintf(column_clause, MAX_QUERY_LEN, "working_set_removal = %d", value?1:0);
        working_set_removal = value;
        break;
    default:
        // unknown state
        return -1;
    }

    // run actual update on current trigger (retrieved earlier)
    retval = update_field(column_clause, NULL);

    if (retval) return retval;
    return 0;
}

void DB_FILESET_SCHED_TRIGGER_ITEM::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    fileset.id = atol(r[i++]);
    strcpy2(fileset.name, r[i++]);
    trigger.id = atol(r[i++]);
    trigger.fileset_id = atol(r[i++]);
    trigger.need_work = atoi(r[i++]);
    trigger.work_available = atoi(r[i++]);
    trigger.no_work_available = atoi(r[i++]);
    trigger.working_set_removal = atoi(r[i++]);
}

int DB_FILESET_SCHED_TRIGGER_ITEM_SET::select_by_name_state(
    const char* fileset_name = NULL,
    const bool use_regexp = false,
    const DB_SCHED_TRIGGER::STATE state = DB_SCHED_TRIGGER::none,
    const bool state_value = true
) {
    char where_clause[MAX_QUERY_LEN] = {0};
    char query[MAX_QUERY_LEN] = {0};
    int retval = 0;
    int count = 0;
    MYSQL_RES* recordset;
    MYSQL_ROW row;
    DB_FILESET_SCHED_TRIGGER_ITEM fileset_trigger;

    // prepare requested compare mode
    const char* comparator = use_regexp ? "REGEXP" : "=";

    // prepare optional state filter
    char state_filter[MAX_QUERY_LEN] = {0};
    switch(state) {
    case DB_SCHED_TRIGGER::state_need_work:
        snprintf(state_filter, MAX_QUERY_LEN, "need_work = %d", state_value?1:0);
        break;
    case DB_SCHED_TRIGGER::state_work_available:
        snprintf(state_filter, MAX_QUERY_LEN, "work_available = %d", state_value?1:0);
        break;
    case DB_SCHED_TRIGGER::state_no_work_available:
        snprintf(state_filter, MAX_QUERY_LEN, "no_work_available = %d", state_value?1:0);
        break;
    case DB_SCHED_TRIGGER::state_working_set_removal:
        snprintf(state_filter, MAX_QUERY_LEN, "working_set_removal = %d", state_value?1:0);
        break;
    default:
        // none or unknown state (keep empty filter)
        break;
    }

    // prepare WHERE clause
    if(fileset_name && !state) {
        snprintf(where_clause, MAX_QUERY_LEN, "WHERE fs.name %s '%s'", comparator, fileset_name);
    } else if(!fileset_name && state) {
        snprintf(where_clause, MAX_QUERY_LEN, "WHERE %s", state_filter);
    } else if(fileset_name && state) {
        snprintf(where_clause, MAX_QUERY_LEN, "WHERE fs.name %s '%s' AND %s", comparator, fileset_name, state_filter);
    }

    // prepare final statement
    snprintf(query, MAX_QUERY_LEN,
        "SELECT"
        "  fs.id,"
        "  fs.name,"
        "  t.id,"
        "  t.fileset_id,"
        "  t.need_work,"
        "  t.work_available,"
        "  t.no_work_available,"
        "  t.working_set_removal "
        "FROM"
        "  fileset fs INNER JOIN sched_trigger t ON fs.id = t.fileset_id "
        "%s",
        where_clause
    );

    retval = db->do_query(query);
    if (retval) return retval;

    recordset = mysql_store_result(db->mysql);
    if (!recordset) return mysql_errno(db->mysql);

    // check if we got at least one record
    count = mysql_num_rows(recordset);
    if(count == 0) {
        mysql_free_result(recordset);
        return ERR_DB_NOT_FOUND;
    }

    // all fine, iterate over recordset
    do {
        row = mysql_fetch_row(recordset);
        if (!row) {
            // clean up
            mysql_free_result(recordset);

            // no row returned, due to an error?
            retval = mysql_errno(db->mysql);
            // yes, probably lost DB connection
            if (retval) return ERR_DB_CONN_LOST;
        } else {
            // parse record, add to vector
            fileset_trigger.db_parse(row);
            items.push_back(fileset_trigger);
        }
    } while (row);

    return 0;
}

int DB_FILESET_SCHED_TRIGGER_ITEM_SET::contains_trigger(
    const char* fileset_name
) {
    // iterate over item vector
    for (unsigned int i=0; i<items.size(); ++i) {
        if (strcmp(items[i].fileset.name, fileset_name) == 0) {
            // return 1-indexed position for boolean tests
            return i+1;
        }
    }
    return 0;
}

void DB_VDA_FILE::db_print(char* buf){
    sprintf(buf,
        "create_time=%f, "
        "dir='%s', "
        "file_name='%s', "
        "size=%f, "
        "chunk_size=%f, "
        "need_update=%d, "
        "initialized=%d, "
        "retrieving=%d, "
        "retrieved=%d",
        create_time,
        dir,
        file_name,
        size,
        chunk_size,
        need_update?1:0,
        initialized?1:0,
        retrieving?1:0,
        retrieved?1:0
    );
}

void DB_VDA_FILE::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atof(r[i++]);
    strcpy2(dir, r[i++]);
    strcpy2(file_name, r[i++]);
    size = atof(r[i++]);
    chunk_size = atof(r[i++]);
    need_update = (atoi(r[i++]) != 0);
    initialized = (atoi(r[i++]) != 0);
    retrieving = (atoi(r[i++]) != 0);
    retrieved = (atoi(r[i++]) != 0);
}

void DB_VDA_CHUNK_HOST::db_print(char* buf) {
    sprintf(buf,
        "create_time=%f, "
        "vda_file_id=%lu, "
        "host_id=%lu, "
        "physical_file_name='%s', "
        "present_on_host=%d, "
        "transfer_in_progress=%d, "
        "transfer_wait=%d, "
        "transfer_request_time=%f, "
        "transfer_send_time=%f ",
        create_time,
        vda_file_id,
        host_id,
        physical_file_name,
        present_on_host,
        transfer_in_progress,
        transfer_wait,
        transfer_request_time,
        transfer_send_time
    );
}

void DB_VDA_CHUNK_HOST::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    create_time = atof(r[i++]);
    vda_file_id = atol(r[i++]);
    host_id = atol(r[i++]);
    strcpy2(physical_file_name, r[i++]);
    present_on_host = (atoi(r[i++]) != 0);
    transfer_in_progress = (atoi(r[i++]) != 0);
    transfer_wait = (atoi(r[i++]) != 0);
    transfer_request_time = atof(r[i++]);
    transfer_send_time = atof(r[i++]);
}

void DB_BADGE::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    id = atol(r[i++]);
    create_time = atof(r[i++]);
    type = atoi(r[i++]);
    strcpy2(name, r[i++]);
    strcpy2(title, r[i++]);
    strcpy2(description, r[i++]);
    strcpy2(image_url, r[i++]);
    strcpy2(level, r[i++]);
    strcpy2(tags, r[i++]);
    strcpy2(sql_rule, r[i++]);
}

void DB_BADGE_USER::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    badge_id = atol(r[i++]);
    user_id = atol(r[i++]);
    create_time = atof(r[i++]);
    reassign_time = atof(r[i++]);
}

void DB_BADGE_TEAM::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    badge_id = atol(r[i++]);
    team_id = atol(r[i++]);
    create_time = atof(r[i++]);
    reassign_time = atof(r[i++]);
}

void DB_CREDIT_USER::db_print(char* buf) {
    sprintf(buf,
        "userid=%lu, "
        "appid=%lu, "
        "njobs=%d, "
        "total=%.15e, "
        "expavg=%.15e, "
        "expavg_time=%.15e, "
        "credit_type=%d ",
        userid,
        appid,
        njobs,
        total,
        expavg,
        expavg_time,
        credit_type
    );
}

void DB_CREDIT_USER::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    userid = atol(r[i++]);
    appid = atol(r[i++]);
    njobs = atoi(r[i++]);
    total = atof(r[i++]);
    expavg = atof(r[i++]);
    expavg_time = atof(r[i++]);
    credit_type = atoi(r[i++]);
}

void DB_CREDIT_TEAM::db_print(char* buf) {
    sprintf(buf,
        "teamid=%lu, "
        "appid=%lu, "
        "njobs=%d, "
        "total=%.15e, "
        "expavg=%.15e, "
        "expavg_time=%.15e, "
        "credit_type=%d ",
        teamid,
        appid,
        njobs,
        total,
        expavg,
        expavg_time,
        credit_type
    );
}

void DB_CREDIT_TEAM::db_parse(MYSQL_ROW &r) {
    int i=0;
    clear();
    teamid = atol(r[i++]);
    appid = atol(r[i++]);
    njobs = atoi(r[i++]);
    total = atof(r[i++]);
    expavg = atof(r[i++]);
    expavg_time = atof(r[i++]);
    credit_type = atoi(r[i++]);
}

const char *BOINC_RCSID_ac374386c8 = "$Id$";
