#include #include #include #include #include "sql.h" #include "common.h" /** * NOTE: Remember to compile with linker flag * TODO: Implement more functions from SQLite3 lib. **/ /** * Structure: sql_db * Typedef: sql_db_t * Author: Joachim M. Giæver * * Sections: * char *db_file: Path to the DB-file * sqlite3 *db_handle: SQLite3 handler * sql_stmt_t *stmts: SQL-statemnts * *lstmt: Last used SQL-statement (no function by now) * int db_flags: Flags * code: Status/error code * * Description: * Stores information about an SQL-handler and it relations. **/ struct sql_db { char *db_file; sqlite3 *db_handle; sql_stmt_t *stmts, *lstmt; int db_flags, code; }; /** * Structure: sql_stmt * Typedef: sql_stmt_t * Author: Joachim M. Giæver * * Sections: * sqlite3_stmt *db_stmt: SQL-statement * sql_db_t *sql: Related SQL-handler * const char *db_query: Query string * *tail: (Possibly) next query * int cols: #of colums in result * sql_stmt_t *next: Next statement * * Description: * Stores information about an SQL-statement and its relations. * **/ struct sql_stmt { sqlite3_stmt *db_stmt; sql_db_t *sql; const char *db_query, *tail; int cols; sql_stmt_t *next; }; /** * Function: sql_val_flags * Author: Joachim M. Giæver * * Parameters: * - int flags: Flags to check * * Description: * Validates the flag given, also between OR'd * values. Returns 0 (to trigger error) on failure. * * Returns: int, the flag **/ static int sql_val_flags( int flags ); /** * Function: sql_get_code * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to read code from * * Description: * Returns the status code stored in the SQL-handler * * Returns: int **/ static int sql_get_code( sql_db_t *sql ); /** * Function: sql_get_code_from_handle * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to read code from * * Description: * Read status code internal (sqlite) SQL-handler. * * Returns: int **/ static int sql_get_code_from_handle( sql_db_t *sql ); /** * Function: sql_set_code * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to set code for * * Description: * Sets an new errorcode, based on sqlite. * * Returns: int, current (newly set) error code **/ static int sql_set_code( sql_db_t *sql ); /** * Function: sql_set_internal_code * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handerl to set code for * - int code: Code to set * * Description: * Set internal (extended) error code. * * Returns: void **/ static int sql_set_internal_code( sql_db_t *sql, int code ); /** * Function: sql_errstr * Author: Joachim M. Giæver * * Parameters: * - int code: Error code * * Description: * Returns the error string related to the given errcode. * * Returns: const char *, the error string **/ static const char *sql_errstr( int code ); /** * Function: sql_add_stmt * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: Owner (SQL-handler.) * * Description: * Adds a stement to the SQL-handler, to keep * track of relation. * * Returns: sql_stmt_t *, added statement **/ static sql_stmt_t *sql_add_stmt( sql_db_t *sql ); /** * Function: sql_create * Author: Joachim M. Giæver * * Parameters: * - char *db_file: The DB-file to open * - int flags: The flags, doesnt support MUTEX and CACHE flags * * Description: * Creates an SQL-object; init the SQL * * Returns: sql_db_t *, db object **/ sql_db_t *sql_create( char *db_file, int flags ) { sql_db_t *sql = ( sql_db_t *) malloc( sizeof( sql_db_t ) ); if ( sql == NULL ) errandend( "Unable to allocate memory for the database structure." ); sql->db_file = strdup( db_file ); sql->db_flags = sql_val_flags( flags ); sql->stmts = NULL; sqlite3_open_v2( sql->db_file, &sql->db_handle, sql->db_flags, NULL ); sql_set_code( sql ); return sql; } /** * Function: sql_ok * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to check. * * Description: * Checks if last activity agains the DB (e.g a query) * was successfull. * * Returns: int, 1 on success, 0 on failure **/ int sql_ok( sql_db_t *sql ) { return sql == NULL ? 0 : sql->code >= SQL_NOERR_LIMIT; } /** * Function: sql_done * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to check. * * Description: * Determines if the SQL-task is done. * * Returns: int, 1 on success, 0 on failure **/ int sql_done( sql_db_t *sql ) { return sql == NULL ? 1 : sql->code == SQL_DONE; } /** * Function: sql_errmsg * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to look for errors in. * * Description: * Returns the corresponding SQL-error message. * If sql_ok() returns an error, there is an * error present. * * Returns: char *, the error message **/ const char *sql_errmsg( sql_db_t *sql ) { int code = sql_get_code( sql ); /*printf( // Debugging purposes!! "DB OBJECT:\t\t%p\n- Filename\t:\t%s\n- Flags:\t\t%d\n- Internal code:\t%d\n- SQLITE code:\t\t%d\n- Query:\n\"%s\"\n", sql, sql->db_file, sql->db_flags, sql->code, code, sql->lstmt != NULL ? sql->lstmt->db_query : NULL );*/ return code < SQL_ERR_LIMIT ? sql_errstr( code ) : sqlite3_errstr( code ); } /** * Function: sql_close * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to close * * Description: * Close an SQL-handler. This will also free related * statements for the handler, which as unessacary. * Statements should be finalized by designer, but are * implemented as a "gb-collection". * * Returns: void **/ void sql_close( sql_db_t *sql ) { sql_stmt_t *stmt; if ( sql == NULL ) return; stmt = sql->stmts; while( stmt != NULL ) { sql_stmt_finalize( stmt ); stmt = stmt->next; } sqlite3_close_v2( sql->db_handle ); free( sql->db_file ); free( sql ); } /** * Function: sql_query * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to query agains * - const char *query: The SQL-statement/-query * * Description: * Creates an SQL-statement and queries the DB. * * Returns: sql_stmt_t *, the SQL-statement object. **/ sql_stmt_t *sql_query( sql_db_t *sql, const char *query ) { sql_stmt_t *stmt; if ( sql == NULL ) return NULL; stmt = sql_add_stmt( sql ); if ( stmt == NULL ) { sql_set_internal_code( sql, SQL_OUTOFMEM ); return NULL; } stmt->db_query = strdup( (char *)query ); sqlite3_prepare_v2( sql->db_handle, stmt->db_query, strlen( query ), &stmt->db_stmt, &stmt->tail ); sql_set_code( sql ); sql->lstmt = stmt; stmt->cols = sqlite3_column_count( stmt->db_stmt ); return stmt; } /** * Function: sql_column_count * Author: Joachim M. Giæver * * Parameters: * - sql_stmt_t *stmt: SQL-statement to check * * Description: * Returns the amount of colums returned from a query * * Returns: int, #colums returned **/ int sql_column_count( sql_stmt_t *stmt ) { return stmt->cols; } /** * Function: sql_column_name * Author: Joachim M. Giæver * * Parameters: * - sql_stmt_t *stmt: SQL-statment to read from * * Description: * Returns the columname specified by the index. * * Returns: char *, colum name **/ const char *sql_column_name( sql_stmt_t *stmt, int idx ) { return idx <= stmt->cols ? sqlite3_column_name( stmt->db_stmt, idx ) : NULL; } /** * Function: sql_column_text * Author: Joachim M. Giæver * * Parameters: * - sql_stmt_t *stmt: SQL-statement to read from * * Description: * Return the text stored in the column specified by the index. * * Returns: const unsigned char *, column text **/ const unsigned char *sql_column_text( sql_stmt_t *stmt, int idx ) { return idx <= stmt->cols ? sqlite3_column_text( stmt->db_stmt, idx ) : NULL; } /** * Function: sql_stmt_step * Author: Joachim M. Giæver * * Parameters: * - sql_stmt_t *stmt: SQL-statement to read from * * Description: * Go to next entry in the retured query. * * Returns: int, 1 on success, 0 on failure. **/ int sql_stmt_step( sql_stmt_t *stmt ) { if ( stmt == NULL ) return SQL_DONE; sqlite3_step( stmt->db_stmt ); return sql_set_code( sql_stmt_sql( stmt ) ); } /** * Function: sql_stmt_sql * Author: Joachim M. Giæver * * Parameters: * - sql_stmt_t *stmt: SQL-statement to load handler from * * Description: * Returns the related SQL-handler for the SQL-statement. * * Returns: sql_db_t *, SQL-object **/ sql_db_t *sql_stmt_sql( sql_stmt_t *stmt ) { return stmt == NULL ? NULL : stmt->sql; } /** * Function: sql_stmt_tail * Author: Joachim M. Giæver * * Parameters: * - sql_stmt_t *stmt: The parent statement * * Description: * Queries the SQL with the next tail-query. * * Note: This finalizes the parent statement. * * Returns: sql_stmt_t *, tail statement **/ sql_stmt_t *sql_stmt_tail( sql_stmt_t *stmt ) { sql_stmt_t *new_stmt; if ( stmt == NULL || ( stmt != NULL && stmt->tail[0] == '\0' ) ) return NULL; // Query tail new_stmt = sql_query( sql_stmt_sql( stmt ), stmt->tail ); // End current. sql_stmt_finalize( stmt ); return new_stmt; } /** * Function: sql_stmt_finalize * Author: Joachim M. Giæver * * Parameters: * - sql_stmt_t *stmt: SQL-statement to finalize * * Description: * Finalizes an SQL-statement AKA closing it. * * Returns: void **/ void sql_stmt_finalize( sql_stmt_t *stmt ) { sql_db_t *sql; sql_stmt_t *tmp; if ( stmt == NULL ) return; sql = sql_stmt_sql( stmt ); tmp = sql->stmts; if ( tmp == stmt ) // if stmt is head of list; make next to head sql->stmts = tmp->next; else { // Find stmt in list while( tmp != NULL && tmp->next != stmt ) tmp = tmp->next; // Merge prepending with append aka remove stmt if ( tmp != NULL ) tmp->next = tmp->next == NULL ? NULL : tmp->next->next; } free( (char *)stmt->db_query ); sqlite3_finalize( stmt->db_stmt ); free( stmt ); } /** * Function: sql_val_flags * Author: Joachim M. Giæver * * Parameters: * - int flags: Flags to check * * Description: * Validates the flag given, also between OR'd * values. Returns 0 (to trigger error) on failure. * * Returns: int, the flag **/ static int sql_val_flags( int flags ) { if ( !(flags ^ (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)) ) return (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); if ( !(flags ^ SQLITE_OPEN_READWRITE) ) return SQLITE_OPEN_READWRITE; if ( !( flags ^ SQLITE_OPEN_READONLY ) ) return SQLITE_OPEN_READONLY; return flags; } /** * Function: sql_get_code * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to read code from * * Description: * Returns the status code stored in the SQL-handler * * Returns: int **/ static int sql_get_code( sql_db_t *sql ) { return sql->code >= SQL_NOERR_LIMIT ? sql->code - SQL_NOERR_LIMIT : ( sql->code < SQL_ERR_LIMIT ? sql->code : sql->code + -(SQL_ERR_LIMIT) ); } /** * Function: sql_get_code_from_handle * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to read code from * * Description: * Read status code internal (sqlite) SQL-handler. * * Returns: int **/ static int sql_get_code_from_handle( sql_db_t *sql ) { return sqlite3_errcode( sql->db_handle ); } /** * Function: sql_set_code * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handler to set code for * * Description: * Sets an new errorcode, based on sqlite. * * Returns: int, current (newly set) error code **/ static int sql_set_code( sql_db_t *sql ) { int res; res = sql_get_code_from_handle( sql ); switch ( res ) { case SQLITE_OK: sql->code = SQL_OK; break; case SQLITE_ROW: sql->code = SQL_ROW; break; case SQLITE_DONE: sql->code = SQL_DONE; break; case SQLITE_ERROR: sql->code = SQL_ERROR; break; case SQLITE_INTERNAL: sql->code = SQL_INTERNAL; break; case SQLITE_PERM: sql->code = SQL_PERM; break; case SQLITE_ABORT: sql->code = SQL_ABORT; break; case SQLITE_BUSY: sql->code = SQL_BUSY; break; case SQLITE_LOCKED: sql->code = SQL_LOCKED; break; case SQLITE_NOMEM: sql->code = SQL_NOMEM; break; case SQLITE_READONLY: sql->code = SQL_READONLY; break; case SQLITE_INTERRUPT: sql->code = SQL_INTERRUPT; break; case SQLITE_IOERR: sql->code = SQL_IOERR; break; case SQLITE_CORRUPT: sql->code = SQL_CORRUPT; break; case SQLITE_NOTFOUND: sql->code = SQL_NOTFOUND; break; case SQLITE_FULL: sql->code = SQL_FULL; break; case SQLITE_CANTOPEN: sql->code = SQL_CANTOPEN; break; case SQLITE_PROTOCOL: sql->code = SQL_PROTOCOL; break; case SQLITE_EMPTY: sql->code = SQL_EMPTY; break; case SQLITE_SCHEMA: sql->code = SQL_SCHEMA; break; case SQLITE_TOOBIG: sql->code = SQL_TOOBIG; break; case SQLITE_CONSTRAINT: sql->code = SQL_CONSTRAINT; break; case SQLITE_MISMATCH: sql->code = SQL_MISMATCH; break; case SQLITE_MISUSE: sql->code = SQL_MISUSE; break; case SQLITE_NOLFS: sql->code = SQL_NOLFS; break; case SQLITE_AUTH: sql->code = SQL_AUTH; break; case SQLITE_FORMAT: sql->code = SQL_FORMAT; break; case SQLITE_RANGE: sql->code = SQL_RANGE; break; case SQLITE_NOTADB: sql->code = SQL_NOTADB; break; default: printf("OTHER CODE: %d\n", res ); if ( res > 101 ) sql->code = res + -(SQL_ERR_LIMIT); else errandend( "Undefined error-code specified.\n" ); break; } return sql->code; } /** * Function: sql_set_internal_code * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: The SQL-handerl to set code for * - int code: Code to set * * Description: * Set internal (extended) error code. * * Returns: void **/ static int sql_set_internal_code( sql_db_t *sql, int code ) { switch( code ) { case SQL_OUTOFMEM: case SQL_NULL_ABORTION: sql->code = code; break; default: errandend( "Implementation error: Code unknown." ); } return sql->code; } /** * Function: sql_errstr * Author: Joachim M. Giæver * * Parameters: * - int code: Error code * * Description: * Returns the error string related to the given errcode. * * Returns: const char *, the error string **/ static const char *sql_errstr( int code ) { char *errstr; switch( code ) { case SQL_OUTOFMEM: errstr = "Out of memory"; break; case SQL_NULL_ABORTION: errstr = "Aborting: NULL pointer."; break; default: errstr = "Undefined error [SQL WRAPPER]"; break; } return (const char *)errstr; } /** * Function: sql_add_stmt * Author: Joachim M. Giæver * * Parameters: * - sql_db_t *sql: Owner (SQL-handler.) * * Description: * Adds a stement to the SQL-handler, to keep * track of relation. * * Returns: sql_stmt_t *, added statement **/ static sql_stmt_t *sql_add_stmt( sql_db_t *sql ) { sql_stmt_t *tmp, *stmt; tmp = sql->stmts; stmt = ( sql_stmt_t *) malloc( sizeof( sql_stmt_t ) ); if ( stmt == NULL ) { sql_set_internal_code( sql, SQL_OUTOFMEM ); return NULL; } // Put last in list. stmt->next = NULL; stmt->sql = sql; if ( tmp == NULL ) { sql->stmts = stmt; return stmt; } while( tmp != NULL && tmp->next != NULL ) tmp = tmp->next; tmp->next = stmt; return stmt; } /*** * THIS IS FOR DEBUGGING OF THE SQL WRAPPER; * To run this make project as: * $ make sql-test * and run by * $ ./sql-test **/ #ifdef _DEBUG_SQL int main( int argc, char const *argv[] ) { int i; /* Query strings */ char *iquery, *query1, *query2, *query3, *query4; /* DB statements */ sql_stmt_t *istmt, *stmt1, *stmt2, *stmt3, *stmt4; /* DB Connection */ sql_db_t *sql = sql_create( "./sql.sqlite3", SQLITE_OPEN_READONLY ); if ( !sql_ok( sql ) ) { printf( "Error: %s\n", sql_errmsg( sql ) ); goto end_test; } /** Various query strings, note query3 is multiple **/ query1 = "SELECT orderNumber,orderDate from Orders where status = 'Cancelled'"; query2 = "SELECT C.customerName AS Customer, SUM(OD.quantityOrdered) AS Total " \ "FROM Orders O, Customers C, OrderDetails OD " \ "WHERE O.customerNumber = C.customerNumber " \ " AND O.orderNumber = OD.orderNumber " \ "GROUP BY O.customerNumber " \ "ORDER BY Total DESC;"; query3 = "SELECT orderNumber,orderDate from Orders where status = 'Cancelled';"\ "SELECT orderNumber,orderDate from Orders where status != 'Cancelled';"\ "SELECT P.productName, T.totalQuantityOrdered " \ "FROM Products P NATURAL JOIN " \ " (SELECT productCode, SUM(quantityOrdered) AS totalQuantityOrdered " \ " FROM OrderDetails GROUP BY productCode) AS T " \ "WHERE T.totalQuantityOrdered >= 1000"; query4 = "SELECT customerName FROM Customers"; iquery = "INSERT INTO Customers (customerName, contactFirstName, contactLastName, phone, addressLine1, city, country) VALUES('Rotor klubben', 'Anna', 'Banana', 123456789, 'Flystripa', 'Tromsø', 'Noreg');"; /** * Do insert **/ puts("\nQUERY insert"); istmt = sql_query( sql, (const char *)iquery); if ( !sql_ok( sql ) ) { printf("Error on insert: %s\n", sql_errmsg( sql )); goto query1_start; } // Just to prevent notice about unused... if ( istmt == NULL); /** * Do query 1: **/ query1_start: puts("\nQUERY 1"); stmt1 = sql_query( sql, (const char *)query1 ); // Look for errors if ( !sql_ok( sql ) ) { printf("Error on query 1: %s\n", sql_errmsg( sql ) ); goto query2_start; // Jumt over rest in query 1 } // Loop through all columns for ( i = 0; i < sql_column_count( stmt1 ); i++ ) printf( "Col: %s\n", sql_column_name( stmt1, i ) ); // End statement sql_stmt_finalize( stmt1 ); /** * Do query 2: **/ query2_start: puts("\nQUERY 2"); stmt2 = sql_query( sql, (const char *)query2 ); // And do the same as query 1.. if ( !sql_ok( sql ) ) { printf("Error on query 2: %s\n", sql_errmsg( sql ) ); goto query3_start; // Jumt over rest in query 2 } for ( i = 0; i < sql_column_count( stmt2 ); i++ ) printf( "Col: %s\n", sql_column_name( stmt2, i ) ); sql_stmt_finalize( stmt2 ); /** * Do query 3: A multiple query string **/ query3_start: puts("\nQUERY 3"); stmt3 = sql_query( sql, (const char *)query3 ); // Loop through all statement while( stmt3 != NULL ) { if ( !sql_ok( sql ) ) { printf("Error on query 3: %s\n", sql_errmsg( sql ) ); goto query4_start; } /** Make some output **/ for ( i = 0; i < sql_column_count( stmt3 ); i++ ) { printf( "%s:\n", sql_column_name( stmt3, i ) ); sql_stmt_step( stmt3 ); while( !sql_done( sql_stmt_sql( stmt3 ) ) ) { printf("%s", sql_column_text( stmt3, i ) ); sql_stmt_step( stmt3 ); if ( !sql_done( sql_stmt_sql( stmt3 ) ) ) printf(", "); } if ( sql_done( sql_stmt_sql( stmt3 ) ) ) printf("\n\n"); } stmt3 = sql_stmt_tail( stmt3 ); i++; } query4_start: puts("\nQUERY 4"); stmt4 = sql_query( sql, (const char *)query4 ); if ( !sql_ok( sql ) ) { printf("Error on query 4: %s\n", sql_errmsg( sql ) ); goto end_test; // End test } /** Make some output **/ for ( i = 0; i < sql_column_count( stmt4 ); i++ ) { printf( "%s:\n", sql_column_name( stmt4, i ) ); sql_stmt_step( stmt4 ); while( !sql_done( sql_stmt_sql( stmt4 ) ) ) { printf("%s", sql_column_text( stmt4, i ) ); sql_stmt_step( stmt4 ); if ( !sql_done( sql_stmt_sql( stmt4 ) ) ) printf(", "); } if ( sql_done( sql_stmt_sql( stmt4 ) ) ) printf("\n\n"); } /* sql_stmt_finalize( stmt3 ): SQL-close finalizes stmt3 */ end_test: sql_close( sql ); printf( "Exists. Test completed.\n" ); return EXIT_SUCCESS; } #endif