From 3c2742a21417d667c986e7c3bda73c765f949f53 Mon Sep 17 00:00:00 2001 From: avsdev-cw Date: Tue, 15 Sep 2020 13:47:54 +0100 Subject: [PATCH] Direct database side of things implemented --- src/database.c | 10 ++ src/database.h | 12 ++ src/db_column.c | 329 +++++++++++++++++++++++++++++++++++++++++++ src/db_column.h | 93 ++++++++++++ src/db_connection.c | 123 +++++++--------- src/db_connection.h | 31 ++-- src/db_query.c | 300 +++++++++++++++++++++++++++++++++++++++ src/db_query.h | 24 ++++ src/db_timeout.c | 33 +++++ src/db_timeout.h | 12 ++ src/db_transaction.c | 55 ++++++++ src/db_transaction.h | 11 ++ 12 files changed, 945 insertions(+), 88 deletions(-) create mode 100644 src/database.c create mode 100644 src/database.h create mode 100644 src/db_column.c create mode 100644 src/db_column.h create mode 100644 src/db_query.c create mode 100644 src/db_query.h create mode 100644 src/db_timeout.c create mode 100644 src/db_timeout.h create mode 100644 src/db_transaction.c create mode 100644 src/db_transaction.h diff --git a/src/database.c b/src/database.c new file mode 100644 index 0000000..9be0a74 --- /dev/null +++ b/src/database.c @@ -0,0 +1,10 @@ +#include + +#include "database.h" + +void sessionEnd() +{ + destroyAllConnections(); + mysql_library_end(); + setDefaultTimeout(-1); +} \ No newline at end of file diff --git a/src/database.h b/src/database.h new file mode 100644 index 0000000..c821819 --- /dev/null +++ b/src/database.h @@ -0,0 +1,12 @@ +#ifndef H__DATABASE__ +#define H__DATABASE__ + +#include "db_connection.h" +#include "db_timeout.h" +#include "db_transaction.h" +#include "db_column.h" +#include "db_query.h" + +void sessionEnd(); + +#endif // H__DATABASE__ diff --git a/src/db_column.c b/src/db_column.c new file mode 100644 index 0000000..7f6af02 --- /dev/null +++ b/src/db_column.c @@ -0,0 +1,329 @@ +#include +#include +#include +#include +#include + +#include "db_column.h" + + +size_t columnTypeToByteSize(enum e_column_type type) +{ + switch(type) { + case TYPE_BOOL: + return sizeof(uint8_t); + + case TYPE_INT8: + case TYPE_UINT8: + return sizeof(uint8_t); + + case TYPE_INT16: + case TYPE_UINT16: + return sizeof(uint16_t); + + case TYPE_INT32: + case TYPE_UINT32: + return sizeof(uint32_t); + + case TYPE_INT64: + case TYPE_UINT64: + return sizeof(uint64_t); + + case TYPE_FLOAT: + return sizeof(float); + case TYPE_DOUBLE: + return sizeof(double); + + case TYPE_STRING: + return sizeof(char *); + case TYPE_BLOB: + return sizeof(char *); + + case TYPE_TIMESTAMP: + return sizeof(uint32_t); + + case TYPE_RAW: + return sizeof(char *); + } + return sizeof(char *); +} + +enum e_column_type simplifyFieldType(enum enum_field_types sql_type, int is_unsigned) +{ + switch(sql_type) { + case MYSQL_TYPE_TINY: + return is_unsigned ? TYPE_UINT8 : TYPE_INT8; + + case MYSQL_TYPE_SHORT: + return is_unsigned ? TYPE_UINT16 : TYPE_INT16; + + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_INT24: + return is_unsigned ? TYPE_UINT32 : TYPE_INT32; + + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_BIT: + return is_unsigned ? TYPE_UINT64 : TYPE_INT64; + + case MYSQL_TYPE_FLOAT: + return TYPE_FLOAT; + + case MYSQL_TYPE_DOUBLE: + return TYPE_DOUBLE; + + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_TIMESTAMP2: + return TYPE_TIMESTAMP; + + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_VARCHAR: + return TYPE_STRING; + + case MYSQL_TYPE_JSON: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + return TYPE_BLOB; + + case MYSQL_TYPE_YEAR: + + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_NEWDATE: + + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_TIME2: + + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_DATETIME2: + + case MYSQL_TYPE_SET: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_NULL: + default: + return TYPE_RAW; + } +} + + +struct column_data_t *columnFromResult(struct stored_conn_t *sconn, MYSQL_RES *result, + uint64_t num_rows) +{ + MYSQL_FIELD *field; + struct column_data_t *col; + + field = mysql_fetch_field(result); + if (field == NULL) { + fprintf( + stderr, "[%d]mysql_fetch_field: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + ); + return 0; + } + + col = (struct column_data_t *)malloc(sizeof(struct column_data_t)); + if (col == NULL) { + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); + return 0; + } + memset(col, 0, sizeof(struct column_data_t)); + + col->name_size = field->name_length; + col->name = (char*)malloc(field->name_length + 1); + if (col->name == NULL) { + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); + free(col); + return 0; + } + memcpy(col->name, field->name, col->name_size); + *(col->name + col->name_size) = '\0'; + + col->type = simplifyFieldType(field->type, (field->flags & UNSIGNED_FLAG) > 0); + col->type_bytes = columnTypeToByteSize(col->type); + + col->n_values = num_rows; + + col->hasPointers = col->type == TYPE_STRING + || col->type == TYPE_BLOB + || col->type == TYPE_RAW; + col->isNullable = (field->flags & NOT_NULL_FLAG) == 0; + col->isBlob = (field->flags & BLOB_FLAG) > 0; + col->isTimestamp = (field->flags & TIMESTAMP_FLAG) > 0; + + col->data.vptr = malloc(col->type_bytes * num_rows); + if (col->data.vptr == NULL) { + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); + free(col->name); + free(col); + return 0; + } + memset(col->data.vptr, 0, col->type_bytes * num_rows); + + col->data_sizes = 0; + if (col->hasPointers) { + col->data_sizes = (size_t *)malloc(sizeof(size_t) * num_rows); + if (col->data_sizes == NULL) { + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); + free(col->data.vptr); + free(col->name); + free(col); + return 0; + } + memset(col->data_sizes, 0, sizeof(size_t) * num_rows); + } + + col->nulls = 0; + if (col->isNullable) { + col->nulls = malloc(num_rows / 8 + 1); + if (col->nulls == NULL) { + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); + if (col->data_sizes) { + free(col->data_sizes); + } + free(col->data.vptr); + free(col->name); + free(col); + return 0; + } + memset(col->nulls, 0, num_rows / 8 + 1); + } + + return col; +} + +void freeColumn(struct column_data_t *col) +{ + if (col->hasPointers) { + for (unsigned int r = 0; r < col->n_values; r++) { + if (!columnRowIsNull(col, r)) { + free(*(col->data.ptr_str + r)); + *(col->data.ptr_str + r) = 0; + } + } + + free(col->data_sizes); + col->data_sizes = 0; + } + + if (col->data.vptr) { + free(col->data.vptr); + col->data.vptr = 0; + } + + if (col->isNullable) { + free(col->nulls); + col->nulls = 0; + } + + free(col->name); + col->name = 0; + + free(col); +} +void freeColumns(struct column_data_t **col_data, size_t n_cols) +{ + for (size_t c = 0; c < n_cols; c++) { + freeColumn(*(col_data + c)); + } + free(col_data); +} + + +int setColumnValue(struct column_data_t *col, uint64_t row, const char *value, size_t value_size) +{ + if (value == NULL) { + columnRowSetNull(col, row); + return 0; + } + + switch(col->type) { + case TYPE_BOOL: + { + *(col->data.ptr_uint8 + row) = value[0] == '1'; + break; + } + + case TYPE_INT8: + { + *(col->data.ptr_int8 + row) = (int8_t)strtol(value, NULL, 10); + break; + } + case TYPE_UINT8: + { + *(col->data.ptr_uint8 + row) = (uint8_t)strtoul(value, NULL, 10); + break; + } + + case TYPE_INT16: + { + *(col->data.ptr_int16 + row) = (int16_t)strtol(value, NULL, 10); + break; + } + case TYPE_UINT16: + { + *(col->data.ptr_uint16 + row) = (uint16_t)strtoul(value, NULL, 10); + break; + } + + case TYPE_INT32: + { + *(col->data.ptr_int32 + row) = (int32_t)strtol(value, NULL, 10); + break; + } + case TYPE_UINT32: + { + *(col->data.ptr_uint32 + row) = (uint32_t)strtoul(value, NULL, 10); + break; + } + + case TYPE_INT64: + { + *(col->data.ptr_int64 + row) = strtoll(value, NULL, 10); + break; + } + case TYPE_UINT64: + { + *(col->data.ptr_uint64 + row) = strtoull(value, NULL, 10); + break; + } + + case TYPE_FLOAT: + { + *(col->data.ptr_float + row) = strtof(value, NULL); + break; + } + case TYPE_DOUBLE: + { + *(col->data.ptr_double + row) = strtod(value, NULL); + break; + } + + case TYPE_TIMESTAMP: + { + *(col->data.ptr_uint32 + row) = (uint32_t)strtoul(value, NULL, 10); + break; + } + case TYPE_STRING: + case TYPE_BLOB: + case TYPE_RAW: + { + *(col->data.ptr_str + row) = (char *)malloc(value_size + 1); + if (*(col->data.ptr_str + row) == 0) { + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); + return -1; + } + memcpy(*(col->data.ptr_str + row), value, value_size); + *(*(col->data.ptr_str + row) + value_size) = 0; + *(col->data_sizes + row) = value_size; + break; + } + } + + return 0; +} + + diff --git a/src/db_column.h b/src/db_column.h new file mode 100644 index 0000000..dc38a7a --- /dev/null +++ b/src/db_column.h @@ -0,0 +1,93 @@ +#ifndef H__DB_COLUMN__ +#define H__DB_COLUMN__ + +#include +#include +#include "db_connection.h" + +enum e_column_type { + TYPE_BOOL, + TYPE_INT8, + TYPE_UINT8, + TYPE_INT16, + TYPE_UINT16, + TYPE_INT32, + TYPE_UINT32, + TYPE_INT64, + TYPE_UINT64, + TYPE_FLOAT, + TYPE_DOUBLE, + TYPE_STRING, + TYPE_BLOB, + TYPE_TIMESTAMP, + TYPE_RAW +}; + +struct column_data_t { + char *name; + size_t name_size; + + enum e_column_type type; + size_t type_bytes; + + size_t n_values; + + uint8_t hasPointers :1; + uint8_t isNullable :1; + uint8_t isBlob :1; + uint8_t isTimestamp :1; + + union u_data { + void *vptr; + + int8_t *ptr_int8; + uint8_t *ptr_uint8; + + int16_t *ptr_int16; + uint16_t *ptr_uint16; + + int32_t *ptr_int32; + uint32_t *ptr_uint32; + + int64_t *ptr_int64; + uint64_t *ptr_uint64; + + float *ptr_float; + double *ptr_double; + + char **ptr_str; + } data; + size_t *data_sizes; + + uint8_t *nulls; +}; + + +// Null column value handling - maybe convert these to macros? +static inline int columnRowIsNull(struct column_data_t *col, uint64_t row) +{ + return col->isNullable && (*(col->nulls + (row / 8)) & (1 << (row % 8))) > 0; +} +static inline void columnRowSetNull(struct column_data_t *col, uint64_t row) +{ + if (col->isNullable) { + *(col->nulls + (row / 8)) |= (1 << (row % 8)); + } +} +static inline void columnRowClearNull(struct column_data_t *col, uint64_t row) +{ + if (col->isNullable) { + *(col->nulls + (row / 8)) &= ~(1 << (row % 8)); + } +} + + +struct column_data_t *columnFromResult(struct stored_conn_t *sconn, MYSQL_RES *result, + uint64_t num_rows); +void freeColumn(struct column_data_t *col); +void freeColumns(struct column_data_t **col_data, size_t n_cols); + + +int setColumnValue(struct column_data_t *col, uint64_t row, const char *value, size_t value_size); + +#endif // H__DB_COLUMN__ diff --git a/src/db_connection.c b/src/db_connection.c index d902f6b..d9c1992 100644 --- a/src/db_connection.c +++ b/src/db_connection.c @@ -4,14 +4,13 @@ #include #include -#include "database.h" - -#define SQCONN(s) (MYSQL *)s->conn +#include "db_connection.h" +#include "db_timeout.h" +#include "db_transaction.h" -struct stored_conn_t *storedConnections = 0; -int nextConnId = 1; -int defaultTimeout = -1; +static struct stored_conn_t *storedConnections = 0; +static int nextConnId = 1; struct stored_conn_t *connectionById(int conn_id) @@ -31,8 +30,11 @@ struct stored_conn_t *connectionByName(const char *name) { struct stored_conn_t *ptr = storedConnections; + size_t name_len = strlen(name); while (ptr != 0) { - if (ptr->name != 0 && strcmp(ptr->name, name) == 0) { + if (ptr->name != 0 + && strlen(ptr->name) == name_len + && strncmp(name, ptr->name, name_len) == 0) { return ptr; } ptr = ptr->next; @@ -41,6 +43,18 @@ struct stored_conn_t *connectionByName(const char *name) return 0; } +int connectionCount() +{ + int i = 0; + struct stored_conn_t *ptr = storedConnections; + + while (ptr != 0) { + i++; + ptr = ptr->next; + } + + return i; +} struct stored_conn_t *createStoredConnection(const char *name) { @@ -48,14 +62,14 @@ struct stored_conn_t *createStoredConnection(const char *name) if (name && strlen(name) > 0) { if (connectionByName(name) != 0) { - fprintf(stderr, "createStoredConnection: Named connection already exists\n"); + fprintf(stderr, "[%d]createStoredConnection: Named connection already exists\n", __LINE__); return 0; } } sconn = (struct stored_conn_t *)malloc(sizeof(struct stored_conn_t)); if (sconn == 0) { - fprintf(stderr, "malloc: (%d) %s\n", errno, strerror(errno)); + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); return 0; } @@ -63,36 +77,38 @@ struct stored_conn_t *createStoredConnection(const char *name) sconn->name = 0; sconn->conn = (MYSQL *)malloc(sizeof(MYSQL)); sconn->isOpen = 0; - sconn->isTransact = 0; + sconn->inTransaction = 0; sconn->needsReset = 0; - sconn->timeout = -1; + sconn->timeout = (unsigned int)-1; sconn->prev = 0; sconn->next = 0; if (sconn->conn == 0) { - fprintf(stderr, "malloc: (%d) %s\n", errno, strerror(errno)); + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); destroyStoredConnection(sconn); return 0; } if (name && strlen(name) > 0) { - sconn->name = (char *)malloc(sizeof(char) * strlen(name)); + sconn->name_len = sizeof(char) * strlen(name); + sconn->name = (char *)malloc(sconn->name_len + 1); if (sconn->name == 0) { - fprintf(stderr, "malloc: (%d) %s\n", errno, strerror(errno)); + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); destroyStoredConnection(sconn); return 0; } - memcpy(sconn->name, name, strlen(name) * sizeof(char)); + memcpy(sconn->name, name, sconn->name_len); + sconn->name[sconn->name_len] = '\0'; } if (mysql_init(SQCONN(sconn)) == 0) { - fprintf(stderr, "mysql_init: unknown error\n"); + fprintf(stderr, "[%d]mysql_init: unknown error\n", __LINE__); destroyStoredConnection(sconn); return 0; } - if (defaultTimeout >= 0) { - setTimeout(sconn, defaultTimeout); + if (getDefaultTimeout() != (unsigned int)-1) { + setTimeout(sconn, getDefaultTimeout()); } if (storedConnections == 0) { @@ -114,19 +130,20 @@ struct stored_conn_t *resetStoredConnection(struct stored_conn_t *sconn) new_conn = (MYSQL *)malloc(sizeof(MYSQL)); if (new_conn == 0) { - fprintf(stderr, "malloc: (%d) %s\n", errno, strerror(errno)); + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); return 0; } if (mysql_init(new_conn) == 0) { - fprintf(stderr, "mysql_init: unknown error\n"); + fprintf(stderr, "[%d]mysql_init: unknown error\n", __LINE__); return 0; } + mysql_close(sconn->conn); free(sconn->conn); sconn->conn = new_conn; - if (sconn->timeout >= 0) { + if (sconn->timeout != (unsigned int)-1) { setTimeout(sconn, sconn->timeout); } @@ -136,12 +153,8 @@ struct stored_conn_t *resetStoredConnection(struct stored_conn_t *sconn) } void destroyStoredConnection(struct stored_conn_t *sconn) { - if (sconn->isTransact) { - // rollback transaction - } - if (sconn->isOpen) { - mysql_close(SQCONN(sconn)); + closeConnection(sconn); } if (sconn->name) { @@ -181,43 +194,7 @@ void destroyAllConnections() } -int connectionCount() -{ - int i = 0; - struct stored_conn_t *ptr = storedConnections; - - while (ptr != 0) { - i++; - ptr = ptr->next; - } - - return i; -} - - -int setTimeout(struct stored_conn_t *sconn, unsigned int timeout) -{ - sconn->timeout = timeout; - - if (mysql_optionsv(SQCONN(sconn), MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout) != 0) { - return -1; - } - if (mysql_optionsv(SQCONN(sconn), MYSQL_OPT_READ_TIMEOUT, (void *)&timeout) != 0) { - return -1; - } - if (mysql_optionsv(SQCONN(sconn), MYSQL_OPT_WRITE_TIMEOUT, (void *)&timeout) != 0) { - return -1; - } - - return 0; -} -void setDefaultTimeout(unsigned int timeout) -{ - defaultTimeout = timeout; -} - - -int connectToHost(struct stored_conn_t *sconn, +int connectToHost(struct stored_conn_t *sconn, const char *host, unsigned int port, const char *user, const char *passwd, const char *db) { @@ -233,7 +210,7 @@ int connectToHost(struct stored_conn_t *sconn, } if (sconn->isOpen) { - fprintf(stderr, "connectToHost: Connection already open\n"); + fprintf(stderr, "[%d]connectToHost: Connection already open\n", __LINE__); return -1; } @@ -246,10 +223,11 @@ int connectToHost(struct stored_conn_t *sconn, if (ret == 0) { fprintf( - stderr, "mysql_real_connect: (%d) %s\n", mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + stderr, "[%d]mysql_real_connect: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) ); sconn->needsReset = 1; - errno = -mysql_errno(SQCONN(sconn)); + errno = -(int)mysql_errno(SQCONN(sconn)); if (freeCon) { destroyStoredConnection(sconn); } @@ -276,7 +254,7 @@ int connectToSocket(struct stored_conn_t *sconn, } if (sconn->isOpen) { - fprintf(stderr, "connectToSocket: Connection already open\n"); + fprintf(stderr, "[%d]connectToSocket: Connection already open\n", __LINE__); return -1; } @@ -289,10 +267,11 @@ int connectToSocket(struct stored_conn_t *sconn, if (ret == 0) { fprintf( - stderr, "mysql_real_connect: (%d) %s\n", mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + stderr, "[%d]mysql_real_connect: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) ); sconn->needsReset = 1; - errno = -mysql_errno(SQCONN(sconn)); + errno = -(int)mysql_errno(SQCONN(sconn)); if (freeCon) { destroyStoredConnection(sconn); } @@ -306,8 +285,8 @@ int connectToSocket(struct stored_conn_t *sconn, void closeConnection(struct stored_conn_t *sconn) { - if (sconn->isTransact) { - // rollback transaction + if (sconn->inTransaction) { + transactionRollback(sconn); } if (sconn->isOpen) { @@ -327,4 +306,4 @@ void closeAllConnections() ptr = ptr->next; destroyStoredConnection(c); } -} \ No newline at end of file +} diff --git a/src/db_connection.h b/src/db_connection.h index 6389736..7313630 100644 --- a/src/db_connection.h +++ b/src/db_connection.h @@ -1,17 +1,21 @@ -#ifndef __CONNECTION_H__ -#define __CONNECTION_H__ +#ifndef H__DB_CONNECTION__ +#define H__DB_CONNECTION__ + +#include + +#define SQCONN(s) (MYSQL *)s->conn struct stored_conn_t { int conn_id; char *name; + size_t name_len; void *conn; - int isOpen :1; - int isTransact :1; - int needsReset :1; - int __FLAGS; + uint8_t isOpen :1; + uint8_t inTransaction :1; + uint8_t needsReset :1; unsigned int timeout; @@ -19,30 +23,25 @@ struct stored_conn_t { struct stored_conn_t *next; }; - +// Connections are stored in a linked list, these functions provide access struct stored_conn_t *connectionById(int conn_id); struct stored_conn_t *connectionByName(const char *name); +int connectionCount(); +// Connection management struct stored_conn_t *createStoredConnection(const char *name); struct stored_conn_t *resetStoredConnection(struct stored_conn_t *sconn); void destroyStoredConnection(struct stored_conn_t *sconn); void destroyAllConnections(); -int connectionCount(); - -int setTimeout(struct stored_conn_t *sconn, unsigned int timeout); -void setDefaultTimeout(unsigned int timeout); - - -int connectToHost(struct stored_conn_t *sconn, +int connectToHost(struct stored_conn_t *sconn, const char *host, unsigned int port, const char *user, const char *passwd, const char *db); int connectToSocket(struct stored_conn_t *sconn, const char *unix_socket, const char *user, const char *passwd, const char *db); - void closeConnection(struct stored_conn_t *sconn); void closeAllConnections(); -#endif // __CONNECTION_H__ \ No newline at end of file +#endif // H__DB_CONNECTION__ diff --git a/src/db_query.c b/src/db_query.c new file mode 100644 index 0000000..0c86e1d --- /dev/null +++ b/src/db_query.c @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include +#include "db_connection.h" +#include "db_column.h" + + +uint64_t simpleQuery(struct stored_conn_t *sconn, const char *qry, size_t qry_len) +{ + if (mysql_real_query(SQCONN(sconn), qry, qry_len) != 0) { + fprintf( + stderr, "[%d]mysql_real_query: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + ); + return (uint64_t)-1; + } + + return mysql_insert_id(SQCONN(sconn)); +} + +uint64_t scalarQuery(struct stored_conn_t *sconn, const char *qry, size_t qry_len, + char **res, size_t *res_len) +{ + my_ulonglong insertId; + my_ulonglong n_row, n_col; + MYSQL_RES *result; + MYSQL_ROW row; + unsigned long *lens; + + insertId = simpleQuery(sconn, qry, qry_len); + if (insertId == (uint64_t)-1) { + return insertId; + } + + if (mysql_field_count(SQCONN(sconn)) == 0) { + // insert/update query + // TODO: res = makeInt64() // lastInsertId + return insertId; + } + + + result = mysql_store_result(SQCONN(sconn)); + if (result == NULL) { + fprintf( + stderr, "[%d]mysql_store_result: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + ); + return (uint64_t)-1; + } + + + n_row = mysql_num_rows(result); + if (n_row == 0) { + fprintf(stderr, "[%d]scalarQuery: Not enough rows in result\n", __LINE__); + return (uint64_t)-1; + } + if (n_row > 1) { + fprintf(stderr, "[%d]scalarQuery: WARN: Too many rows in result, only returning first value\n", __LINE__); + } + + n_col = mysql_num_fields(result); + if (n_col > 1) { + fprintf(stderr, "[%d]scalarQuery: WARN: Too many columns in result, only returning first value\n", __LINE__); + } + + row = mysql_fetch_row(result); + lens = mysql_fetch_lengths(result); + if (row == NULL) { + fprintf(stderr, "[%d]scalarQuery: Not enough rows in result\n", __LINE__); + return (uint64_t)-1; + } + + if (*row == NULL) { + (*res_len) = 0; + (*res) = 0; + } else { + (*res_len) = *(lens + 0); + (*res) = malloc(*(lens + 0) * sizeof(char) + 1); + if ((*res) == 0) { + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); + return (uint64_t)-1; + } + memcpy((*res), *(row + 0), *(lens + 0)); + *((*res) + *(lens + 0)) = '\0'; + } + + mysql_free_result(result); + + return insertId; +} + +uint64_t tableQuery(struct stored_conn_t *sconn, const char *qry, size_t qry_len, int scalar_result, + struct column_data_t ***col_data_ptr, size_t *n_cols) +{ + my_ulonglong insertId; + my_ulonglong n_rows; + MYSQL_RES *result; + MYSQL_ROW row; + unsigned long *lens; + int failed = 0; + struct column_data_t **col_data; + + insertId = simpleQuery(sconn, qry, qry_len); + if (insertId == (uint64_t)-1) { + return insertId; + } + + *n_cols = mysql_field_count(SQCONN(sconn)); + if (scalar_result && (*n_cols) > 1) { + // TODO: throw warning due to column sizing issue + (*n_cols) = 1; + } + if (*n_cols == 0) { + // insert/update query + // TODO: res = makeInt64() // lastInsertId + return insertId; + } + + result = mysql_store_result(SQCONN(sconn)); + if (result == NULL) { + fprintf( + stderr, "[%d]mysql_real_query: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + ); + return (uint64_t)-1; + } + + + col_data = (struct column_data_t **)malloc(sizeof(struct column_data_t *) * *n_cols); + if (result == NULL) { + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); + mysql_free_result(result); + return insertId; + } + + n_rows = mysql_num_rows(result); + if (scalar_result && n_rows > 1) { + // TODO: throw warning due to column sizing issue + n_rows = 1; + } + + + for (unsigned int i = 0; i < *n_cols; i++) { + *(col_data + i) = columnFromResult(sconn, result, n_rows); + + if (*(col_data + i) == 0) { + failed++; + } + } + if (failed > 0) { + for (unsigned int i = 0; i < *n_cols; i++) { + if (*(col_data + i) != 0) { + freeColumn(*(col_data + i)); + } + } + free(col_data); + mysql_free_result(result); + return insertId; + } + + for (my_ulonglong r = 0; r < n_rows; r++) { + row = mysql_fetch_row(result); + lens = mysql_fetch_lengths(result); + + if (row == NULL) { + fprintf( + stderr, "[%d]mysql_fetch_row: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + ); + return insertId; + } + + for (unsigned int c = 0; c < *n_cols; c++) { + struct column_data_t *col = (*(col_data + c)); + + if (setColumnValue(col, r, *(row + c), *(lens + c)) < 0) { + failed++; + } + } + + if (failed > 0) { + r = n_rows; + } + } + + if (failed > 0) { + for (size_t c = 0; c < *n_cols; c++) { + if (*(col_data + c) != 0) { + freeColumn(*(col_data + c)); + } + } + free(col_data); + mysql_free_result(result); + return insertId; + } + + mysql_free_result(result); + + *col_data_ptr = col_data; + + return n_rows; +} + + + +int scalarInt(struct stored_conn_t *sconn, const char *qry, size_t qry_len, int default_value) +{ + struct column_data_t **col_data; + size_t n_cols; + + if (tableQuery(sconn, qry, qry_len, 1, &col_data, &n_cols) != (uint64_t)-1) { + if (!((*col_data)->isNullable && columnRowIsNull(*col_data, 0))) { + default_value = *((*col_data)->data.ptr_int32); + } + freeColumn(*col_data); + free(col_data); + } + + return default_value; +} + +unsigned int scalarUInt(struct stored_conn_t *sconn, const char *qry, size_t qry_len, + unsigned int default_value) +{ + struct column_data_t **col_data; + size_t n_cols; + + if (tableQuery(sconn, qry, qry_len, 1, &col_data, &n_cols) != (uint64_t)-1) { + if (!((*col_data)->isNullable && columnRowIsNull(*col_data, 0))) { + default_value = *((*col_data)->data.ptr_uint32); + } + freeColumn(*col_data); + free(col_data); + } + + return default_value; +} + +double scalarReal(struct stored_conn_t *sconn, const char *qry, size_t qry_len, + double default_value) +{ + struct column_data_t **col_data; + size_t n_cols; + + if (tableQuery(sconn, qry, qry_len, 1, &col_data, &n_cols) != (uint64_t)-1) { + if (!((*col_data)->isNullable && columnRowIsNull(*col_data, 0))) { + default_value = *((*col_data)->data.ptr_double); + } + freeColumn(*col_data); + free(col_data); + } + + return default_value; +} + +char scalarChar(struct stored_conn_t *sconn, const char *qry, size_t qry_len, char default_value) +{ + struct column_data_t **col_data; + size_t n_cols; + + if (tableQuery(sconn, qry, qry_len, 1, &col_data, &n_cols) != (uint64_t)-1) { + if (!((*col_data)->isNullable && columnRowIsNull(*col_data, 0))) { + default_value = *((*col_data)->data.ptr_str)[0]; + } + freeColumn(*col_data); + free(col_data); + } + + return default_value; +} + +char *scalarString(struct stored_conn_t *sconn, const char *qry, size_t qry_len, + char *default_value) +{ + struct column_data_t **col_data; + size_t n_cols; + char *retval; + + if (tableQuery(sconn, qry, qry_len, 1, &col_data, &n_cols) == (uint64_t)-1) { + return default_value; + } + + if ((*col_data)->isNullable && columnRowIsNull(*col_data, 0)) { + retval = NULL; + } else { + retval = (char *)malloc((*col_data)->data_sizes[0] + 1); + if (retval == 0) { + fprintf(stderr, "[%d]malloc: (%d) %s\n", __LINE__, errno, strerror(errno)); + return default_value; + } + memcpy(retval, *((*col_data)->data.ptr_str), (*col_data)->data_sizes[0]); + retval[(*col_data)->data_sizes[0]] = '\0'; + } + freeColumn(*col_data); + free(col_data); + + return retval; +} diff --git a/src/db_query.h b/src/db_query.h new file mode 100644 index 0000000..844c4dc --- /dev/null +++ b/src/db_query.h @@ -0,0 +1,24 @@ +#ifndef H__DB_QUERY__ +#define H__DB_QUERY__ + +#include +#include "db_connection.h" + +// Generic query methods +uint64_t simpleQuery(struct stored_conn_t *sconn, const char *qry, size_t qry_len); +uint64_t scalarQuery(struct stored_conn_t *sconn, const char *qry, size_t qry_len, + char **res, size_t *res_len); +uint64_t tableQuery(struct stored_conn_t *sconn, const char *qry, size_t qry_len, int scalar_result, + struct column_data_t ***col_data, size_t *n_cols); + +// Scalar helpers +int scalarInt(struct stored_conn_t *sconn, const char *qry, size_t qry_len, int default_value); +unsigned int scalarUInt(struct stored_conn_t *sconn, const char *qry, size_t qry_len, + unsigned int default_value); +double scalarReal(struct stored_conn_t *sconn, const char *qry, size_t qry_len, + double default_value); +char scalarChar(struct stored_conn_t *sconn, const char *qry, size_t qry_len, char default_value); +char *scalarString(struct stored_conn_t *sconn, const char *qry, size_t qry_len, + char *default_value); + +#endif // H__DB_QUERY__ diff --git a/src/db_timeout.c b/src/db_timeout.c new file mode 100644 index 0000000..71e283f --- /dev/null +++ b/src/db_timeout.c @@ -0,0 +1,33 @@ +#include + +#include "db_timeout.h" + + +int defaultTimeout = -1; + + +void setDefaultTimeout(unsigned int timeout) +{ + defaultTimeout = timeout; +} +unsigned int getDefaultTimeout() +{ + return defaultTimeout; +} + +int setTimeout(struct stored_conn_t *sconn, unsigned int timeout) +{ + sconn->timeout = timeout; + + if (mysql_optionsv(SQCONN(sconn), MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout) != 0) { + return -1; + } + if (mysql_optionsv(SQCONN(sconn), MYSQL_OPT_READ_TIMEOUT, (void *)&timeout) != 0) { + return -1; + } + if (mysql_optionsv(SQCONN(sconn), MYSQL_OPT_WRITE_TIMEOUT, (void *)&timeout) != 0) { + return -1; + } + + return 0; +} \ No newline at end of file diff --git a/src/db_timeout.h b/src/db_timeout.h new file mode 100644 index 0000000..7781556 --- /dev/null +++ b/src/db_timeout.h @@ -0,0 +1,12 @@ +#ifndef __DB_TIMEOUT_H__ +#define __DB_TIMEOUT_H__ + +#include "db_connection.h" + +// Timeouts can either be set globally or on a per-connection basis +void setDefaultTimeout(unsigned int timeout); +unsigned int getDefaultTimeout(); + +int setTimeout(struct stored_conn_t *sconn, unsigned int timeout); + +#endif // __DB_TIMEOUT_H__ \ No newline at end of file diff --git a/src/db_transaction.c b/src/db_transaction.c new file mode 100644 index 0000000..cd8eb08 --- /dev/null +++ b/src/db_transaction.c @@ -0,0 +1,55 @@ +#include +#include + +#include "db_transaction.h" + +int transactionStart(struct stored_conn_t *sconn) +{ + if (mysql_autocommit(SQCONN(sconn), 1) != 0) { + fprintf( + stderr, "[%d]mysql_autocommit: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + ); + return -1; + } + sconn->inTransaction = 1; + return 0; +} +int transactionCommit(struct stored_conn_t *sconn) +{ + if (mysql_commit(SQCONN(sconn)) != 0) { + fprintf( + stderr, "[%d]mysql_commit: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + ); + return -1; + } + if (mysql_autocommit(SQCONN(sconn), 0) != 0) { + fprintf( + stderr, "[%d]mysql_autocommit: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + ); + return -1; + } + sconn->inTransaction = 0; + return 0; +} +int transactionRollback(struct stored_conn_t *sconn) +{ + if (mysql_rollback(SQCONN(sconn)) != 0) { + fprintf( + stderr, "[%d]mysql_rollback: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + ); + return -1; + } + if (mysql_autocommit(SQCONN(sconn), 0) != 0) { + fprintf( + stderr, "[%d]mysql_autocommit: (%d) %s\n", + __LINE__, mysql_errno(SQCONN(sconn)), mysql_error(SQCONN(sconn)) + ); + return -1; + } + sconn->inTransaction = 0; + return 0; +} \ No newline at end of file diff --git a/src/db_transaction.h b/src/db_transaction.h new file mode 100644 index 0000000..d2709e6 --- /dev/null +++ b/src/db_transaction.h @@ -0,0 +1,11 @@ +#ifndef H__DB_TRANSACTION__ +#define H__DB_TRANSACTION__ + +#include "db_connection.h" + +// Transaction management +int transactionStart(struct stored_conn_t *sconn); +int transactionCommit(struct stored_conn_t *sconn); +int transactionRollback(struct stored_conn_t *sconn); + +#endif // H__DB_TRANSACTION__