src/evsql/result.c
author Tero Marttila <terom@fixme.fi>
Sat, 29 Nov 2008 00:39:47 +0200
branchnew-evsql
changeset 46 75cecfc4603b
parent 45 424ce5ab82fd
child 51 c65d0f4c3bff
permissions -rw-r--r--
moar testing

#include "evsql.h"
#include "../lib/error.h"
#include "../lib/misc.h"

#include <stdlib.h>
#include <assert.h>

const char *evsql_result_error (const struct evsql_result *res) {
    if (!res->error)
        return "No error";

    switch (res->evsql->type) {
        case EVSQL_EVPQ:
            if (!res->result.pq)
                return "unknown error (no result)";
            
            return PQresultErrorMessage(res->result.pq);

        default:
            FATAL("res->evsql->type");
    }

}

size_t evsql_result_rows (const struct evsql_result *res) {
    switch (res->evsql->type) {
        case EVSQL_EVPQ:
            return PQntuples(res->result.pq);

        default:
            FATAL("res->evsql->type");
    }
}

size_t evsql_result_cols (const struct evsql_result *res) {
    switch (res->evsql->type) {
        case EVSQL_EVPQ:
            return PQnfields(res->result.pq);

        default:
            FATAL("res->evsql->type");
    }
}

size_t evsql_result_affected (const struct evsql_result *res) {
    switch (res->evsql->type) {
        case EVSQL_EVPQ:
            // XXX: errors?
            return strtol(PQcmdTuples(res->result.pq), NULL, 10);

        default:
            FATAL("res->evsql->type");
    }
}


int evsql_result_null (const struct evsql_result *res, size_t row, size_t col) {
    switch (res->evsql->type) {
        case EVSQL_EVPQ:
            return PQgetisnull(res->result.pq, row, col);

        default:
            FATAL("res->evsql->type");
    }
}

int evsql_result_field (const struct evsql_result *res, size_t row, size_t col, char ** const ptr, size_t *size) {
    *ptr = NULL;

    switch (res->evsql->type) {
        case EVSQL_EVPQ:
            if (PQfformat(res->result.pq, col) != 1)
                ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, col));
    
            *size = PQgetlength(res->result.pq, row, col);
            *ptr  = PQgetvalue(res->result.pq, row, col);

            return 0;

        default:
            FATAL("res->evsql->type");
    }

error:
    return -1;
}

err_t evsql_result_check (struct evsql_result *res) {
    // so simple...
    return res->error ? EIO : 0;
}

err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res) {
    struct evsql_item_info *col;
    size_t cols = 0, nrows;
    err_t err;

    // count info columns
    for (col = info->columns; col->type; col++)
        cols++;

    // number of rows returned/affected
    nrows = evsql_result_rows(res) || evsql_result_affected(res);

    // did the query fail outright?
    if (res->error)
        // dump error message
        NXERROR(err = EIO, evsql_result_error(res));

/*
    // SELECT/DELETE/UPDATE WHERE didn't match any rows -> ENOENT
    if (nrows == 0)
        XERROR(err = ENOENT, "no rows returned/affected");
*/

    // correct number of columns
    if (evsql_result_cols(res) != cols)
        XERROR(err = EINVAL, "wrong number of columns: %zu, should be %zu", evsql_result_cols(res), cols);
    
    // assign
    res->info = info;
    res->row_offset = 0;

    // good
    return 0;

error:
    return err;

}

int evsql_result_next (struct evsql_result *res, ...) {
    va_list vargs;
    struct evsql_item_info *col;
    size_t col_idx, row_idx = res->row_offset;
    err_t err;
    
    // ensure that evsql_result_begin has been called
    assert(res->info);
    
    // check if we're past the end
    if (row_idx >= evsql_result_rows(res))
        return 0;
    
    // varargs
    va_start(vargs, res);

    for (col = res->info->columns, col_idx = 0; col->type; col++, col_idx++) {
        char *value = NULL;
        size_t length = 0;
        
        // check for NULLs, then try and get the field value
        if (evsql_result_null(res, row_idx, col_idx)) {
            if (!col->flags.null_ok)
                XERROR(err = EINVAL, "r%zu:c%zu: NULL", row_idx, col_idx);

        } else if (evsql_result_field(res, row_idx, col_idx, &value, &length)) {
            SERROR(err = EINVAL);

        }
        
        // read the arg
        switch (col->type) {
            case EVSQL_TYPE_BINARY: {
                struct evsql_item_binary *item_ptr = va_arg(vargs, struct evsql_item_binary *);

                if (value) {
                    item_ptr->ptr = value;
                    item_ptr->len = length;
                }
            } break;

            case EVSQL_TYPE_STRING: {
                char **str_ptr = va_arg(vargs, char **);

                if (value) {
                    *str_ptr = value;
                }

            } break;

            case EVSQL_TYPE_UINT16: {
                uint16_t *uval_ptr = va_arg(vargs, uint16_t *);

                if (!value) break;

                if (length != sizeof(uint16_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint16_t: %zu", row_idx, col_idx, length);

                int16_t sval = ntohs(*((int16_t *) value));

                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint16_t: %hd", row_idx, col_idx, (signed short) sval);

                *uval_ptr = sval;
            } break;
            
            case EVSQL_TYPE_UINT32: {
                uint32_t *uval_ptr = va_arg(vargs, uint32_t *);

                if (!value) break;

                if (length != sizeof(uint32_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint32_t: %zu", row_idx, col_idx, length);

                int32_t sval = ntohl(*((int32_t *) value));

                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint32_t: %ld", row_idx, col_idx, (signed long) sval);

                *uval_ptr = sval;
            } break;
            
            case EVSQL_TYPE_UINT64: {
                uint64_t *uval_ptr = va_arg(vargs, uint64_t *);

                if (!value) break;

                if (length != sizeof(uint64_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint64_t: %zu", row_idx, col_idx, length);

                int64_t sval = ntohq(*((int64_t *) value));

                if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint64_t: %lld", row_idx, col_idx, (signed long long) sval);

                *uval_ptr = sval;
            } break;
            
            default:
                XERROR(err = EINVAL, "r%zu:c%zu: invalid type: %d", row_idx, col_idx, col->type);
        }
    }

    // advance row index
    res->row_offset++;

    // row handled succesfully
    return 1;

error:
    return -err;
}

void evsql_result_end (struct evsql_result *res) {
    // not much more to it...
    evsql_result_free(res);
}

void evsql_result_free (struct evsql_result *res) {
    // note that the result itself might be NULL...
    // in the case of internal-error results, these may be free'd multiple times!
    switch (res->evsql->type) {
        case EVSQL_EVPQ:
            if (res->result.pq)
                return PQclear(res->result.pq);

        default:
            FATAL("res->evsql->type");
    }
}