terom@44: terom@44: #include "evsql.h" terom@45: #include "../lib/error.h" terom@45: #include "../lib/misc.h" terom@45: terom@45: #include terom@45: #include terom@44: terom@44: const char *evsql_result_error (const struct evsql_result *res) { terom@44: if (!res->error) terom@44: return "No error"; terom@44: terom@44: switch (res->evsql->type) { terom@44: case EVSQL_EVPQ: terom@44: if (!res->result.pq) terom@44: return "unknown error (no result)"; terom@44: terom@44: return PQresultErrorMessage(res->result.pq); terom@44: terom@44: default: terom@44: FATAL("res->evsql->type"); terom@44: } terom@44: terom@44: } terom@44: terom@44: size_t evsql_result_rows (const struct evsql_result *res) { terom@44: switch (res->evsql->type) { terom@44: case EVSQL_EVPQ: terom@44: return PQntuples(res->result.pq); terom@44: terom@44: default: terom@44: FATAL("res->evsql->type"); terom@44: } terom@44: } terom@44: terom@44: size_t evsql_result_cols (const struct evsql_result *res) { terom@44: switch (res->evsql->type) { terom@44: case EVSQL_EVPQ: terom@44: return PQnfields(res->result.pq); terom@44: terom@44: default: terom@44: FATAL("res->evsql->type"); terom@44: } terom@44: } terom@44: terom@44: size_t evsql_result_affected (const struct evsql_result *res) { terom@44: switch (res->evsql->type) { terom@44: case EVSQL_EVPQ: terom@45: // XXX: errors? terom@44: return strtol(PQcmdTuples(res->result.pq), NULL, 10); terom@44: terom@44: default: terom@44: FATAL("res->evsql->type"); terom@44: } terom@44: } terom@44: terom@44: terom@44: int evsql_result_null (const struct evsql_result *res, size_t row, size_t col) { terom@44: switch (res->evsql->type) { terom@44: case EVSQL_EVPQ: terom@44: return PQgetisnull(res->result.pq, row, col); terom@44: terom@44: default: terom@44: FATAL("res->evsql->type"); terom@44: } terom@44: } terom@44: terom@45: int evsql_result_field (const struct evsql_result *res, size_t row, size_t col, char ** const ptr, size_t *size) { terom@44: *ptr = NULL; terom@44: terom@44: switch (res->evsql->type) { terom@44: case EVSQL_EVPQ: terom@44: if (PQfformat(res->result.pq, col) != 1) terom@44: ERROR("[%zu:%zu] PQfformat is not binary: %d", row, col, PQfformat(res->result.pq, col)); terom@44: terom@44: *size = PQgetlength(res->result.pq, row, col); terom@44: *ptr = PQgetvalue(res->result.pq, row, col); terom@44: terom@44: return 0; terom@44: terom@44: default: terom@44: FATAL("res->evsql->type"); terom@44: } terom@44: terom@44: error: terom@44: return -1; terom@44: } terom@44: terom@45: err_t evsql_result_check (struct evsql_result *res) { terom@45: // so simple... terom@45: return res->error ? EIO : 0; terom@45: } terom@45: terom@44: err_t evsql_result_begin (struct evsql_result_info *info, struct evsql_result *res) { terom@44: struct evsql_item_info *col; terom@44: size_t cols = 0, nrows; terom@44: err_t err; terom@44: terom@44: // count info columns terom@44: for (col = info->columns; col->type; col++) terom@44: cols++; terom@44: terom@44: // number of rows returned/affected terom@44: nrows = evsql_result_rows(res) || evsql_result_affected(res); terom@44: terom@44: // did the query fail outright? terom@44: if (res->error) terom@44: // dump error message terom@44: NXERROR(err = EIO, evsql_result_error(res)); terom@44: terom@44: /* terom@44: // SELECT/DELETE/UPDATE WHERE didn't match any rows -> ENOENT terom@44: if (nrows == 0) terom@44: XERROR(err = ENOENT, "no rows returned/affected"); terom@44: */ terom@44: terom@44: // correct number of columns terom@44: if (evsql_result_cols(res) != cols) terom@44: XERROR(err = EINVAL, "wrong number of columns: %zu -> %zu", cols, evsql_result_cols(res)); terom@44: terom@44: // assign terom@44: res->info = info; terom@44: res->row_offset = 0; terom@44: terom@44: // good terom@44: return 0; terom@44: terom@44: error: terom@44: return err; terom@44: terom@44: } terom@44: terom@44: int evsql_result_next (struct evsql_result *res, ...) { terom@44: va_list vargs; terom@44: struct evsql_item_info *col; terom@44: size_t col_idx, row_idx = res->row_offset; terom@45: err_t err; terom@45: terom@45: // ensure that evsql_result_begin has been called terom@45: assert(res->info); terom@44: terom@44: // check if we're past the end terom@44: if (row_idx >= evsql_result_rows(res)) terom@44: return 0; terom@44: terom@44: // varargs terom@45: va_start(vargs, res); terom@44: terom@45: for (col = res->info->columns, col_idx = 0; col->type; col++, col_idx++) { terom@44: char *value = NULL; terom@44: size_t length = 0; terom@44: terom@44: // check for NULLs, then try and get the field value terom@44: if (evsql_result_null(res, row_idx, col_idx)) { terom@44: if (!col->flags.null_ok) terom@44: XERROR(err = EINVAL, "r%zu:c%zu: NULL", row_idx, col_idx); terom@44: terom@45: } else if (evsql_result_field(res, row_idx, col_idx, &value, &length)) { terom@45: SERROR(err = EINVAL); terom@44: terom@44: } terom@44: terom@44: // read the arg terom@44: switch (col->type) { terom@44: case EVSQL_TYPE_BINARY: { terom@44: struct evsql_item_binary *item_ptr = va_arg(vargs, struct evsql_item_binary *); terom@44: terom@44: if (value) { terom@44: item_ptr->ptr = value; terom@44: item_ptr->len = length; terom@44: } terom@44: } break; terom@44: terom@44: case EVSQL_TYPE_STRING: { terom@44: char **str_ptr = va_arg(vargs, char **); terom@44: terom@45: if (value) { terom@44: *str_ptr = value; terom@45: } terom@44: terom@44: } break; terom@44: terom@44: case EVSQL_TYPE_UINT16: { terom@45: uint16_t *uval_ptr = va_arg(vargs, uint16_t *); terom@44: terom@45: if (!value) break; terom@44: terom@45: if (length != sizeof(uint16_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint16_t: %zu", row_idx, col_idx, length); terom@44: terom@45: int16_t sval = ntohs(*((int16_t *) value)); terom@45: terom@45: if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint16_t: %hd", row_idx, col_idx, (signed short) sval); terom@45: terom@45: *uval_ptr = sval; terom@44: } break; terom@45: terom@45: case EVSQL_TYPE_UINT32: { terom@45: uint32_t *uval_ptr = va_arg(vargs, uint32_t *); terom@45: terom@45: if (!value) break; terom@45: terom@45: if (length != sizeof(uint32_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint32_t: %zu", row_idx, col_idx, length); terom@45: terom@45: int32_t sval = ntohl(*((int32_t *) value)); terom@45: terom@45: if (sval < 0) XERROR(err = ERANGE, "r%zu:c%zu: out of range for uint32_t: %ld", row_idx, col_idx, (signed long) sval); terom@45: terom@45: *uval_ptr = sval; terom@45: } break; terom@45: terom@45: case EVSQL_TYPE_UINT64: { terom@45: uint64_t *uval_ptr = va_arg(vargs, uint64_t *); terom@45: terom@45: if (!value) break; terom@45: terom@45: if (length != sizeof(uint64_t)) XERROR(err = EINVAL, "r%zu:c%zu: wrong size for uint64_t: %zu", row_idx, col_idx, length); terom@45: terom@45: int64_t sval = ntohq(*((int64_t *) value)); terom@45: terom@45: 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); terom@45: terom@45: *uval_ptr = sval; terom@45: } break; terom@45: terom@45: default: terom@45: XERROR(err = EINVAL, "r%zu:c%zu: invalid type: %d", row_idx, col_idx, col->type); terom@44: } terom@44: } terom@44: terom@45: // row handled succesfully terom@45: return 1; terom@44: terom@45: error: terom@45: return -err; terom@44: } terom@44: terom@44: void evsql_result_end (struct evsql_result *res) { terom@44: // not much more to it... terom@44: evsql_result_free(res); terom@44: } terom@44: terom@44: void evsql_result_free (struct evsql_result *res) { terom@45: // note that the result itself might be NULL... terom@45: // in the case of internal-error results, these may be free'd multiple times! terom@44: switch (res->evsql->type) { terom@44: case EVSQL_EVPQ: terom@45: if (res->result.pq) terom@45: return PQclear(res->result.pq); terom@44: terom@44: default: terom@44: FATAL("res->evsql->type"); terom@44: } terom@44: } terom@44: terom@44: