st/code/sql/sql.c
2008-04-04 00:00:00 +00:00

1059 lines
23 KiB
C

/*
===========================================================================
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; i<n; i++ ) {
Com_Printf( "%16s", sql_columnname( db, i ) );
}
Com_Printf( "\n" );
for ( i=0; i<n; i++ ) {
Com_Printf( "----------------" );
}
Com_Printf( "\n" );
}
for ( i=0; i<n; i++ ) {
Com_Printf( "%16s", sql_columnastext( db, i ) );
}
Com_Printf( "\n" );
}
sql_done( db );
// sql_stmt_free( db, stmt );
}
/*
===============
sql_exec
executes a statement without caching anything.
===============
*/
int sql_exec( sqlInfo_t * db, const char * src ) {
char tmp[ SQL_STMT_ALLOC ];
const char * s;
stmtInfo_t * stmt;
//
// allow statement to be re-directed to another db
//
switch( SWITCHSTRING( src ) )
{
case CS(':','s','e','r'): db = sql_getserverdb(); break;
case CS(':','c','l','i'): db = sql_getclientdb(); break;
case CS(':','c','o','m'): db = sql_getcommondb(); break;
case CS(':','b','o','t'):
{
COM_Parse( &src );
parse_tofirstparam( &src );
sql_exec( sql_getserverdb(), src);
sql_exec( sql_getclientdb(), src);
return 1;
} break;
default:
goto start;
}
COM_Parse( &src );
parse_tofirstparam( &src );
start:
s = src;
for ( ;s; )
{
stmt = sql_parse( db, &s, tmp );
if ( stmt ) {
sql_work( db, stmt );
}
}
return 1;
}
/*
===============
sql_prepare
pushes the statement in 'src' on to the execution stack. src may have been parsed
from a previous call to sql_prepare.
===============
*/
stmtInfo_t * sql_prepare( sqlInfo_t * db, const char * src ) {
stmtInfo_t * stmt = find_stmt( db, src );
if ( !stmt ) {
const char * s = src;
stmt = sql_parse( db, &s, 0 );
if ( stmt ) {
stmt->src = 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; i<MAX_COLUMNS_PER_TABLE && stmt->params[ 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; i<MAX_COLUMNS_PER_TABLE && select->stmt.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] && j<size; i++ ) {
dst[ j++ ] = src[ i ];
if ( src[ i ] == quote ) {
dst[ j++ ] = quote;
}
}
dst[ j++ ] = quote;
dst[ j ] = '\0';
return j;
}
static void sql_export_table( fileHandle_t f, tableInfo_t * table ) {
/*
CREATE TABLE commodities_npcs (
"commodity_id" integer,
"npc_id" integer
);
INSERT INTO "commodities_npcs" VALUES(13, 17);
*/
char * prefix;
int i;
if ( table->flags&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; i<table->column_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; i<table->row_count; i++ ) {
int j;
char buff[8192];
sql_export_text( f, "\t" );
for ( j=0; j<table->column_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; i<db->table_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; i<db->table_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; i<db->table_count; i++ ) {
tableInfo_t * table = db->tables[ i ];
FS_WriteString ( va( "%s [shape=record,label=\"%s|{", table->name, table->name ), f );
for ( j=0; j<table->column_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 );
}