/* =========================================================================== Copyright (C) 2007 HermitWorks Entertainment Corporation This file is part of the Space Trader source code. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. =========================================================================== */ #include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" #include "sql.h" #ifdef DEVELOPER const char * CURRENT_STMT; #endif /* =============== sql_parse parses an sql statement. data_p is the text buffer containing the statement. data_p is moved to the where sql_parse stops. if tmp is used it points to a buffer SQL_STMT_ALLOC bytes in length which is used to parse the statement into. =============== */ stmtInfo_t * sql_parse( sqlInfo_t * db, const char ** data_p, char * tmp ) { const char * s = *data_p; char * t; stmtInfo_t * stmt; sqlStack_t * save; #ifdef DEVELOPER CURRENT_STMT = s; #endif save = db->stmt_buffer.c; if ( tmp ) { db->stmt_buffer.c = (sqlStack_t*)tmp; db->stmt_buffer.c->top = sizeof(sqlStack_t); } else { db->stmt_buffer.c = db->stmt_buffer.p; } ASSERT( db->stmt_buffer.c || db->stmt_buffer.c == db->stmt_buffer.p ); for ( stmt=0;!stmt; ) { const char * stmt_src = s; t = parse_temp( &s ); if ( t[ 0 ] == '\0' ) { s = 0; break; } switch( SWITCHSTRING(t) ) { case CS('c','r','e','a'): stmt = sql_create_parse( db, &s ); break; case CS('s','e','l','e'): stmt = sql_select_parse( db, &s ); break; case CS('i','n','s','e'): stmt = sql_insert_parse( db, &s ); break; case CS('d','e','l','e'): stmt = sql_delete_parse( db, &s ); break; case CS('u','p','d','a'): stmt = sql_update_parse( db, &s ); break; case CS('a','l','t','e'): stmt = sql_alter_parse( db, &s ); break; case CS('b','e','g','i'): case CS('c','o','m','m'): break; default: Com_Error( ERR_FATAL, "invalid sql statement: '%s'\n%s\n", t, s ); } if ( stmt ) { stmt->src = stmt_src; } if ( !parse_tonextstatement( &s ) ) break; } *data_p = s; db->stmt_buffer.c = (save)?save:db->stmt_buffer.p; return stmt; } /* =============== sql_work executes a statement =============== */ static int sql_work( sqlInfo_t * db, stmtInfo_t * stmt ) { switch ( stmt->type ) { case SQL_SELECT: sql_select_work( db, (selectInfo_t*)stmt ); break; case SQL_INSERT: sql_insert_work( db, (insertInfo_t*)stmt ); break; case SQL_UPDATE: sql_update_work( db, (updateInfo_t*)stmt ); break; case SQL_DELETE: sql_delete_work( db, (deleteInfo_t*)stmt ); break; case SQL_FORMAT: sql_format_work( db, (formatInfo_t*)stmt ); break; } return 1; } /* =============== sql_prompt executes a statement and prints out the result =============== */ void sql_prompt( sqlInfo_t * db, char * src ) { int f=0; sql_prepare( db, src ); while ( sql_step( db ) ) { int i; int n = sql_columncount( db ); if ( f==0 ) { f=1; for ( i=0; isrc = sql_alloc_string( db, src ); insert_stmt( db, stmt ); } } ASSERT( stmt ); if ( stmt && (db->pc ==0 || stmt != db->callstack[ db->pc-1 ]) ) { db->callstack[ db->pc++ ] = stmt; stmt->step = -1; } return stmt; } int sql_compile( sqlInfo_t * db, const char * src ) { int index = db->stmts_byindex_count++; //ASSERT( index >= 0 && index < MAX_STMTS_PER_DB ); if (!db->stmts_byindex[ index ]) db->stmts_byindex[ index ] = sql_format_parse( db, &src ); return index; } const char * sql_run( sqlInfo_t * db, int index ) { return sql_format_work( db, (formatInfo_t*)db->stmts_byindex[ index ] ); } int sql_expr( sqlInfo_t * db, const char * src ) { Expr e; int r; parseInfo_t pi = { 0 }; pi.db = db; e = parse_expression( &src, &pi ); ASSERT( pi.rt == INTEGER ); ASSERT( pi.more == 0 ); r = sql_eval( db, e, 0, 0, 0, 0, 0, 0 ).integer; sql_free( db, e ); return r; } int sql_bind( sqlInfo_t * db, intptr_t *args ) { int i; stmtInfo_t * stmt; sql_prepare( db, VMA(1) ); stmt = db->callstack[ db->pc-1 ]; for ( i=0; iparams[ i ].format != INVALID; i++ ) { if ( stmt->params[ i ].format == STRING ) { stmt->params[ i ].payload.string = VMA(2+i); } else { stmt->params[ i ].payload.integer = args[2+i]; } } return 1; } int sql_bindtext( sqlInfo_t * db, int arg, const char * text ) { stmtInfo_t * stmt = db->callstack[ db->pc-1 ]; ASSERT( db->pc>0 && stmt ); stmt->params[ arg-1 ].format = STRING; stmt->params[ arg-1 ].payload.string = (char*)text; return 1; } int sql_bindint( sqlInfo_t * db, int arg, int integer ) { stmtInfo_t * stmt = db->callstack[ db->pc-1 ]; ASSERT( db->pc>0 && stmt ); stmt->params[ arg-1 ].format = INTEGER; stmt->params[ arg-1 ].payload.integer = integer; return 1; } int sql_step( sqlInfo_t * db ) { stmtInfo_t * stmt = db->callstack[ db->pc-1 ]; ASSERT( db->pc>0 && stmt ); #ifdef DEVELOPER CURRENT_STMT = stmt->src; #endif switch( stmt->type ) { case SQL_SELECT: { selectInfo_t * select = ((selectInfo_t*)stmt); if ( stmt->step == -1 ) { sql_select_work( db, select ); } stmt->step++; if ( stmt->step < select->cache.row_count ) return 1; } break; default: sql_work( db, stmt ); break; } return 0; } int sql_columncount( sqlInfo_t * db ) { stmtInfo_t * stmt = db->callstack[ db->pc-1 ]; ASSERT( db->pc>0 ); ASSERT( stmt ); ASSERT( stmt->type == SQL_SELECT ); return ((selectInfo_t*)stmt)->column_count; } int sql_rowcount( sqlInfo_t * db ) { stmtInfo_t * stmt = db->callstack[ db->pc-1 ]; ASSERT( db->pc>0 ); ASSERT( stmt ); ASSERT( stmt->type == SQL_SELECT ); return ((selectInfo_t*)stmt)->cache.row_count; } const char * sql_columnastext( sqlInfo_t * db, int column ) { stmtInfo_t * stmt = db->callstack[ db->pc-1 ]; selectInfo_t * select = (selectInfo_t*)stmt; cellInfo_t cell; ASSERT( db->pc>0 ); ASSERT( stmt ); ASSERT( stmt->type == SQL_SELECT ); ASSERT( select->cache.result ); cell = select->cache.result[ stmt->step*select->column_count + column ]; if ( select->column_type[ column ] == STRING ) { return (cell.string)?cell.string:"(NULL)"; } return fn( cell.integer, FN_PLAIN ); } int sql_columnasint( sqlInfo_t * db, int column ) { stmtInfo_t * stmt = db->callstack[ db->pc-1 ]; selectInfo_t * select = (selectInfo_t*)stmt; cellInfo_t cell; ASSERT( db->pc>0 ); ASSERT( stmt ); ASSERT( stmt->type == SQL_SELECT ); ASSERT( select->cache.result ); cell = select->cache.result[ stmt->step*select->column_count + column ]; return ( select->column_type[ column ] == STRING )?atoi( cell.string ):cell.integer; } const char * sql_columnname( sqlInfo_t * db, int column ) { stmtInfo_t * stmt = db->callstack[ db->pc-1 ]; selectInfo_t * select = (selectInfo_t*)stmt; ASSERT( db->pc>0 ); ASSERT( stmt ); ASSERT( stmt->type == SQL_SELECT ); return select->column_name[ column ]; } int sql_done( sqlInfo_t * db ) { stmtInfo_t * stmt = db->callstack[ db->pc-1 ]; ASSERT( db->pc>0 ); --db->pc; db->callstack[ db->pc ]->step = -1; db->callstack[ db->pc ] = NULL; switch ( stmt->type ) { case SQL_UPDATE: return ((updateInfo_t*)stmt)->rows_updated; case SQL_SELECT: { selectInfo_t * select = (selectInfo_t*)stmt; if ( select->cache.row_count > 0 ) sql_cache_pop( db, select->cache.result ); } break; } return 0; } int sql_select( sqlInfo_t * db, const char * src, char * segment, char * buffer, int size, ... ) { char * tmp_top = db->cache.top; char * tmp_buffer = db->cache.buffer; int tmp_size = db->cache.size; char * tmp_segment = db->cache.segment; int i; va_list argptr; selectInfo_t * select = (selectInfo_t*)sql_prepare( db, src ); ASSERT( select->stmt.type == SQL_SELECT ); va_start(argptr, size); for ( i=0; istmt.params[ i ].format != INVALID; i++ ) { if ( select->stmt.params[ i ].format == STRING ) { select->stmt.params[ i ].payload.string = va_arg( argptr, char* ); } else { select->stmt.params[ i ].payload.integer= va_arg( argptr, int ); } } va_end(argptr); db->cache.buffer = db->cache.top = buffer; db->cache.size = size; db->cache.segment = segment; sql_select_work( db, select ); sql_done( db ); db->cache.top = tmp_top; db->cache.buffer = tmp_buffer; db->cache.size = tmp_size; db->cache.segment = tmp_segment; return select->cache.row_count; } static const unsigned int primes[] = { 3, 7, 11, 0x11, 0x17, 0x1D, 0x25, 0x2F, 0x3B, 0x47, 0x59, 0x6B, 0x83, 0xA3, 0xC5, 0xEF, 0x125, 0x161, 0x1AF, 0x209, 0x277, 0x2F9, 0x397, 0x44F, 0x52F, 0x63D, 0x78B, 0x91D, 0xAF1, 0xD2B, 0xFD1, 0x12FD, 0x16CF, 0x1B65, 0x20E3, 0x2777, 0x2F6F, 0x38FF, 0x446F, 0x521F, 0x628D, 0x7655, 0x8E01, 0xAA6B, 0xCC89, 0xF583, 0x126A7, 0x1619B, 0x1A857, 0x1FD3B, 0x26315, 0x2DD67, 0x3701B, 0x42023, 0x4F361, 0x5F0ED, 0x72125, 0x88E31, 0xA443B, 0xC51EB, 0xEC8C1, 0x11BDBF, 0x154A3F, 0x198C4F, 0x1EA867, 0x24CA19, 0x2C25C1, 0x34FA1B, 0x3F928F, 0x4C4987, 0x5B8B6F, 0x6DDA89, //that last one's a bit over 7 million...god help us if we blow it }; static unsigned int try_to_get_a_prime( unsigned int min ) { int i; for( i = 0; i < lengthof( primes ); i++ ) if( primes[i] >= min ) return primes[i]; return min; } // comes from common.c set by the 'outside' via SE_RANDSEED event extern int com_randseed; void sql_reset( sqlInfo_t * db, int memory_tag ) { Z_FreeTags( memory_tag ); memset( db, 0, sizeof(sqlInfo_t) ); db->memory_tag = memory_tag; db->cache.segment = db->cache.top = db->cache.buffer = sql_alloc( db, SQL_CACHE_SIZE ); db->cache.size = SQL_CACHE_SIZE; db->strings = sql_str_init_strings( try_to_get_a_prime( 512 ), memory_tag ); Rand_Initialize( &db->rand, com_randseed ); } void sql_assign_gs( sqlInfo_t * db, int * gs ) { db->gs = gs; } void sql_assign_triggers( sqlInfo_t * db, sqltrigger_t insert, sqltrigger_t update, sqltrigger_t delete ) { db->delete_trigger = delete; db->insert_trigger = insert; db->update_trigger = update; } // // memory // void * sql_alloc( sqlInfo_t * db, size_t size ) { return Z_TagMalloc(size,db->memory_tag); } void * sql_calloc( sqlInfo_t * db, size_t size ) { void * p = Z_TagMalloc(size,db->memory_tag); memset( p, 0, size ); return p; } void * sql_realloc( sqlInfo_t * db, void * p, size_t size1, size_t size2 ) { void * pp = sql_alloc( db, size2 ); if ( p ) { Com_Memcpy( pp,p,size1 ); sql_free( db, p ); } return pp; } void sql_free( sqlInfo_t * db, void * p ) { if ( p ) { Z_Free( p ); } } void * sql_cache_push ( sqlInfo_t * db, int n ) { void * p = db->cache.top; db->cache.top += n; ASSERT( db->cache.top - db->cache.buffer < db->cache.size ); memset(p,0,n); return p; } void sql_cache_pop ( sqlInfo_t * db, void * p ) { if ( p ) { db->cache.top = p; } } const char* sql_alloc_string( sqlInfo_t * db, const char * s ) { if ( s == NULL || (int)s == -1 ) { return "NULL"; } else { return sql_str_intern( db->strings, s ); } } const char* sql_alloc_stringn( sqlInfo_t * db, const char * s, int n ) { if ( s == NULL || (int)s == -1 ) { return "NULL"; } else { return sql_str_intern_n( db->strings, s, n ); } } void * sql_alloc_stmt( sqlInfo_t * db, int n ) { void * m; if ( db->stmt_buffer.c == db->stmt_buffer.p ) { if ( !db->stmt_buffer.p || db->stmt_buffer.p->top + n > SQL_STMT_ALLOC ) { db->stmt_buffer.c = db->stmt_buffer.p = sql_alloc( db, SQL_STMT_ALLOC ); db->stmt_buffer.p->top = sizeof( sqlStack_t ); } } else { ASSERT( db->stmt_buffer.c && db->stmt_buffer.c->top + n <= SQL_STMT_ALLOC ); } m = ((char*)db->stmt_buffer.c) + db->stmt_buffer.c->top; db->stmt_buffer.c->top += n; return m; } void * sql_calloc_stmt( sqlInfo_t * db, int n ) { return Com_Memset( sql_alloc_stmt( db, n ), 0, n ); } int parse( const char ** data_p, char * buffer, int size, int * string_literal ) { const char *data; int len = 0; char c; data = *data_p; // skip white space for ( ;; data++ ) { c = *data; if ( c == '\0' || c == ';' ) { buffer[ 0 ] = '\0'; return 0; } if ( c <= ' ' || c == '\n' ) continue; break; } // handle quoted strings if ( c == '\"' || c == '\'' ) { int quote = c; data++; for( ; ; ) { c = *data++; if( !c || c == quote ) { if ( *data != quote ) break; c = *data++; } if( len < size - 1 ) { buffer[len++] = (char)c; } } if ( string_literal ) *string_literal = 1; buffer[ len++ ] = '\0'; *data_p = ( char * ) data; return len; } // parse a regular word len = 0; for ( ;; data++ ) { c = *data; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '?') || (c == '_') || (c == '$') ) { buffer[ len++ ] = c; } else break; } // parse an operator if ( len == 0 ) { switch( CS(data[0],data[1],0,0) ) { case CS('<','=',0,0): case CS('>','=',0,0): case CS('=','=',0,0): case CS('&','&',0,0): case CS('|','|',0,0): buffer[ len++ ] = *data++; buffer[ len++ ] = *data++; break; default: buffer[ len++ ] = *data++; break; } } buffer[ len++ ] = '\0'; *data_p = data; return len; } int parse_tofirstparam( const char ** data_p ) { const char *data = *data_p; int c; for ( ;; data++ ) { c = *data; if ( c == ' ' || c == '\t' || c == '\n' ) continue; break; } if ( c == '(' ) { *data_p = data + 1; return 1; } *data_p = data; return 0; } int parse_tonextparam( const char ** data_p ) { const char *data = *data_p; int c; for ( ;; data++ ) { c = *data; if ( c == ',' || c == ')' || c == '\0' ) break; } *data_p = data+1; return c == ','; } int parse_tonextstatement( const char ** data_p ) { const char *data = *data_p; int c; for ( ;; data++ ) { c = *data; // handle quoted strings if ( c == '\"' || c == '\'' ) { int quote = c; data++; for( ; ; ) { c = *data++; if( !c || c == quote ) { if ( *data != quote ) break; c = *data++; } } } if ( c == ';' || c == '\0' ) break; } *data_p = data+1; return c == ';'; } char * parse_temp( const char ** s ) { static char buffer[ 64 ]; parse( s, buffer, sizeof(buffer), 0 ); return buffer; } static void sql_export_text( fileHandle_t f, const char * fmt, ... ) { int len; char dest[ 8192 ]; va_list argptr; va_start (argptr,fmt); len = vsnprintf( dest, sizeof(dest), fmt, argptr ); va_end (argptr); FS_Write( dest, len, f ); } static int sql_export_escape( char * dst, int size, const char * src, char quote ) { int i,j=0; dst[ j++ ] = quote; for ( i=0; src[i] && jflags&SQL_TABLE_CLIENTONLY ) { prefix = "cl_"; } else if ( table->flags&SQL_TABLE_SERVERONLY ) { prefix = "sv_"; #ifdef DEVELOPER } else if ( table->flags&SQL_TABLE_BOTH ) { prefix="bg_"; #endif } else { prefix = ""; } // // CREATE TABLE // sql_export_text( f, "CREATE TABLE %s%s (\n", prefix, table->name ); for ( i=0; icolumn_count; i++ ) { const char * type = (table->columns[ i ].format==STRING)?"string":"integer"; if ( table->primary_column == (i+1) ) { type = "unique"; } sql_export_text( f, "\t%s %s%s\n", table->columns[ i ].name, type, (i==table->column_count-1)?"":"," ); } sql_export_text( f, ");\n" ); // // INSERT INTO // if ( table->row_count>0 ) { sql_export_text( f, "INSERT INTO %s%s ROWS (\n", prefix, table->name ); for ( i=0; irow_count; i++ ) { int j; char buff[8192]; sql_export_text( f, "\t" ); for ( j=0; jcolumn_count; j++ ) { char * fmt; if ( j == table->column_count-1 ) { if ( i == table->row_count-1 ) { fmt = (table->columns[ j ].format == STRING )?"%s\n":"%d\n"; } else { fmt = (table->columns[ j ].format == STRING )?"%s,\n":"%d,\n"; } } else { fmt = (table->columns[ j ].format == STRING )?"%s,\t":"%d,\t"; } if ( table->columns[ j ].format == STRING ) { if ( table->rows[ i*table->column_count+j ].string ) { sql_export_escape( buff, sizeof(buff), table->rows[ i*table->column_count+j ].string, '\''); sql_export_text( f, fmt, buff ); } else { sql_export_text( f, fmt, "''" ); } } else { sql_export_text( f, fmt, table->rows[ i*table->column_count+j ].integer ); } } } sql_export_text( f, ");\n" ); } } static void sql_export_db( fileHandle_t f, sqlInfo_t * db, int server ) { int i; for ( i=0; itable_count; i++ ) { tableInfo_t * table = db->tables[ i ]; if ( table->flags&SQL_TABLE_TEMPORARY ) { continue; } if ( !server && !(table->flags&SQL_TABLE_CLIENTONLY) ) { continue; } sql_export_table( f, table ); } } void sql_export( const char * filename ) { char clientdb[MAX_OSPATH]; fileHandle_t f; if ( FS_FOpenFileByMode( filename, &f, FS_WRITE ) == -1 ) { Com_Printf( "cannot open %s for sql export\n", filename ); return; } sql_export_db( f, sql_getserverdb(), 1 ); FS_FCloseFile( f ); if ( Cvar_VariableIntegerValue( "sql_export_text" ) == 1 ) { COM_StripExtension(filename, clientdb, sizeof(clientdb)); Q_strcat( clientdb, sizeof(clientdb), "_en-US.txt"); if ( FS_FOpenFileByMode( clientdb, &f, FS_WRITE ) == -1 ) { Com_Printf( "cannot open %s for sql export\n", filename ); return; } sql_export_db( f, sql_getclientdb(), 0 ); FS_FCloseFile( f ); } } #ifdef DEVELOPER static void sql_save_db( fileHandle_t f, sqlInfo_t * db, int server ) { int i; for ( i=0; itable_count; i++ ) { tableInfo_t * table = db->tables[ i ]; if ( table->flags&SQL_TABLE_TEMPORARY ) { // no temp tables continue; } if ( !server && !(table->flags&SQL_TABLE_CLIENTONLY) ) { // if it's not the server, and not client only, skip continue; } if ( server && (table->flags & SQL_TABLE_CLIENTONLY) ) { // if it is server, and not SERVERONLY or BOTH skip continue; } sql_export_table( f, table ); } } void sql_save( const char * filename ) { char clientdb[MAX_OSPATH]; fileHandle_t f; if ( FS_FOpenFileByMode( filename, &f, FS_WRITE ) == -1 ) { Com_Printf( "cannot open %s for sql export\n", filename ); return; } // prepare db for export sql_exec( sql_getserverdb(), "UPDATE missions SET value = 0 SEARCH key 'hidden';"); // these are re-generated on db load DELETE FROM contacts; DELETE FROM todaysprices; DELETE FROM todaysnews; sql_exec( sql_getserverdb(), "DELETE FROM prices;"); sql_exec( sql_getserverdb(), "DELETE FROM contacts;"); sql_exec( sql_getserverdb(), "DELETE FROM todaysprices;"); sql_exec( sql_getserverdb(), "DELETE FROM todaysnews;"); sql_exec( sql_getserverdb(), "DELETE FROM purchases;"); sql_exec( sql_getserverdb(), "DELETE FROM inventory;"); sql_exec( sql_getserverdb(), "DELETE FROM flags;"); sql_exec( sql_getserverdb(), "DELETE FROM travels;"); sql_exec( sql_getserverdb(), "DELETE FROM history;"); sql_exec( sql_getserverdb(), "DELETE FROM players;"); sql_exec( sql_getserverdb(), "DELETE FROM stash;"); // these are cached on the client side, so zero them out on export sql_exec( sql_getclientdb(), "UPDATE commodities_text SET icon=0, chart=0;" ); sql_save_db( f, sql_getserverdb(), 1 ); FS_FCloseFile( f ); COM_StripExtension(filename, clientdb, sizeof(clientdb)); Q_strcat( clientdb, sizeof(clientdb), "_en-US.txt"); if ( FS_FOpenFileByMode( clientdb, &f, FS_WRITE ) == -1 ) { Com_Printf( "cannot open %s for sql export\n", filename ); return; } sql_save_db( f, sql_getclientdb(), 0 ); FS_FCloseFile( f ); } #endif static void FS_WriteString( const char * s, fileHandle_t f ) { FS_Write( s, strlen(s), f ); } void sql_graph( sqlInfo_t * db, fileHandle_t f ) { int i,j; FS_WriteString( "digraph tables {\n", f ); for ( i=0; itable_count; i++ ) { tableInfo_t * table = db->tables[ i ]; FS_WriteString ( va( "%s [shape=record,label=\"%s|{", table->name, table->name ), f ); for ( j=0; jcolumn_count; j++ ) { if ( j>0 ) FS_WriteString( "|", f ); FS_WriteString( va( "<%s> %s", table->columns[ j ].name, table->columns[ j ].name ), f ); } FS_WriteString ( "}\"];\n", f ); } FS_WriteString( "}\n", f ); FS_FCloseFile( f ); }