From 6eaf7a57b56e697d30f946f3c613ab761ca0b8d5 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 25 Apr 2014 07:41:58 -0700 Subject: [PATCH 1/2] fix crash when there's a "'" in model name --- xwords4/relay/dbmgr.cpp | 114 +++++++++++++++++++++++++++++++++------- xwords4/relay/dbmgr.h | 4 +- 2 files changed, 98 insertions(+), 20 deletions(-) diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 212454a65..4ec559c9c 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -365,6 +365,18 @@ DBMgr::AllDevsAckd( const char* const connName ) return full; } +static void +getPtrs( vector& paramValues, const char* base, vector& offsets ) +{ + size_t ii; + for ( ii = 0; ii < offsets.size(); ++ii ) { + const char* ptr = offsets[ii] + base; + paramValues.push_back( ptr ); + logf( XW_LOGINFO, "%s: str[%d] points at: %s", __func__, ii, + paramValues[ii] ); + } +} + // Return DevIDRelay for device, adding it to devices table IFF it's not // already there. DevIDRelay @@ -397,13 +409,31 @@ DBMgr::RegisterDevice( const DevID* host, int clientVersion, } while ( DEVID_NONE == devID ); StrWPF query; + StrWPF paramBuf; + vector paramIndices; query.catf( "INSERT INTO " DEVICES_TABLE " (id, devTypes[1]," " devids[1], clntVers, versdesc, model, osvers)" - " VALUES( %d, %d, '%s', %d, '%s', '%s', '%s' )", - devID, host->m_devIDType, devidStr, clientVersion, - desc, model, osVers ); - logf( XW_LOGINFO, "%s: %s", __func__, query.c_str() ); - success = execSql( query ); + " VALUES( $1, $2, $3, $4, $5, $6, $7 )" ); + + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%d%c", devID, '\0' ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%d%c", host->m_devIDType, '\0' ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%s%c", devidStr, '\0' ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%d%c", clientVersion, '\0' ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%s%c", desc, '\0' ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%s%c", model, '\0' ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%s%c", osVers, '\0' ); + + vector paramValues; + getPtrs( paramValues, paramBuf.c_str(), paramIndices ); + + success = execParams( query, paramValues ); } } return devID; @@ -420,15 +450,24 @@ DBMgr::ReregisterDevice( DevIDRelay relayID, const DevID* host, const char* const desc, int clientVersion, const char* const model, const char* const osVers ) { - // First update the existing StrWPF query; - query.catf( "UPDATE " DEVICES_TABLE " SET " - "devTypes = array_prepend( %d, devTypes), " - "devids = array_prepend('%s', devids), ", - host->m_devIDType, host->m_devIDString.c_str() ); + StrWPF paramBuf; + vector paramIndices; - formatUpdate( query, true, desc, clientVersion, model, osVers, relayID ); - execSql( query ); + query.catf( "UPDATE " DEVICES_TABLE " SET " + "devTypes = array_prepend( $1, devTypes), " + "devids = array_prepend($2, devids), " ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%d%c", host->m_devIDType, '\0' ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%s%c", host->m_devIDString.c_str(), '\0' ); + + formatUpdate( query, paramBuf, paramIndices, true, desc, clientVersion, + model, osVers, relayID ); + + vector paramValues; + getPtrs( paramValues, paramBuf.c_str(), paramIndices ); + execParams( query, paramValues ); } // Return true if the relayID exists in the DB already @@ -447,8 +486,15 @@ DBMgr::UpdateDevice( DevIDRelay relayID, const char* const desc, if ( exists ) { StrWPF query; query.catf( "UPDATE " DEVICES_TABLE " SET " ); - formatUpdate( query, false, desc, clientVersion, model, osVers, relayID ); - execSql( query ); + + StrWPF paramBuf; + vector paramIndices; + + formatUpdate( query, paramBuf, paramIndices, false, desc, + clientVersion, model, osVers, relayID ); + vector paramValues; + getPtrs( paramValues, paramBuf.c_str(), paramIndices ); + execParams( query, paramValues ); } return exists; } @@ -460,7 +506,8 @@ DBMgr::UpdateDevice( DevIDRelay relayID ) } void -DBMgr::formatUpdate( StrWPF& query, bool append, const char* const desc, +DBMgr::formatUpdate( StrWPF& query, StrWPF& paramBuf, vector& paramIndices, + bool append, const char* const desc, int clientVersion, const char* const model, const char* const osVers, DevIDRelay relayID ) { @@ -470,16 +517,29 @@ DBMgr::formatUpdate( StrWPF& query, bool append, const char* const desc, query.catf( "mtimes[1]='now'" ); } + int count = paramIndices.size(); if ( NULL != desc && '\0' != desc[0] ) { - query.catf( ", clntVers=%d, versDesc='%s'", clientVersion, desc ); + query.catf( ", clntVers=$%d, versDesc=$%d", 1 + count, 2 + count ); + count += 2; + + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%d%c", clientVersion, '\0' ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%s%c", desc, '\0' ); } if ( NULL != model && '\0' != model[0] ) { - query.catf( ", model='%s'", model ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%s%c", model, '\0' ); + query.catf( ", model=$%d", ++count ); } if ( NULL != osVers && '\0' != osVers[0] ) { - query.catf( ", osvers='%s'", osVers ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%s%c", osVers, '\0' ); + query.catf( ", osvers=$%d", ++count ); } - query.catf( " WHERE id = %d", relayID ); + paramIndices.push_back( paramBuf.size() ); + paramBuf.catf( "%d%c", relayID, '\0' ); + query.catf( " WHERE id = $%d", ++count ); } HostID @@ -831,6 +891,22 @@ DBMgr::execSql( const char* const query ) return ok; } +bool +DBMgr::execParams( const string& query, vector paramValues ) +{ + PGresult* result = PQexecParams( getThreadConn(), query.c_str(), + paramValues.size(), NULL, + ¶mValues[0], + NULL, NULL, 0 ); + bool success = PGRES_COMMAND_OK == PQresultStatus( result ); + if ( !success ) { + logf( XW_LOGERROR, "PQexecParams(%s)=>%s;%s", query.c_str(), + PQresStatus(PQresultStatus(result)), + PQresultErrorMessage(result) ); + } + return success; +} + void DBMgr::readArray( const char* const connName, const char* column, int arr[] ) /* len 4 */ { diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index 6e5bcbf0e..c4fa0b691 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -149,6 +149,7 @@ class DBMgr { DBMgr(); bool execSql( const string& query ); bool execSql( const char* const query ); /* no-results query */ + bool execParams( const string& query, vector params ); void readArray( const char* const connName, const char* column, int arr[] ); DevIDRelay getDevID( const char* connName, int hid ); DevIDRelay getDevID( const DevID* devID ); @@ -160,7 +161,8 @@ class DBMgr { bool nullConnnameOK ); int CountStoredMessages( const char* const connName, int hid ); bool UpdateDevice( DevIDRelay relayID ); - void formatUpdate( StrWPF& query, bool append, const char* const desc, + void formatUpdate( StrWPF& query, StrWPF& prmBuf, vector& prmIndices, + bool append, const char* const desc, int clientVersion, const char* const model, const char* const osVers, DevIDRelay relayID ); From 19573af533c8ac16f7021e1e9dd04b4336091b1d Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 25 Apr 2014 18:45:51 -0700 Subject: [PATCH 2/2] a more elegant way of building up params to pass to PQexecParams --- xwords4/relay/Makefile | 1 + xwords4/relay/dbmgr.cpp | 272 ++++++++++++++----------------------- xwords4/relay/dbmgr.h | 6 +- xwords4/relay/querybld.cpp | 78 +++++++++++ xwords4/relay/querybld.h | 47 +++++++ xwords4/relay/strwpf.cpp | 45 +++--- xwords4/relay/strwpf.h | 8 +- 7 files changed, 263 insertions(+), 194 deletions(-) create mode 100644 xwords4/relay/querybld.cpp create mode 100644 xwords4/relay/querybld.h diff --git a/xwords4/relay/Makefile b/xwords4/relay/Makefile index b85aeb906..b762735f0 100644 --- a/xwords4/relay/Makefile +++ b/xwords4/relay/Makefile @@ -38,6 +38,7 @@ SRC = \ udpager.cpp \ udpqueue.cpp \ xwrelay.cpp \ + querybld.cpp \ # STATIC ?= -static GITINFO = gitversion.txt diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 4ec559c9c..179b01dad 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -43,11 +43,8 @@ static DBMgr* s_instance = NULL; -#define DELIM "\1" #define MAX_NUM_PLAYERS 4 -static void formatParams( char* paramValues[], int nParams, const char* fmt, - char* buf, int bufLen, ... ); static int here_less_seed( const char* seeds, int perDeviceSum, unsigned short seed ); static void destr_function( void* conn ); @@ -105,20 +102,21 @@ DBMgr::AddNew( const char* cookie, const char* connName, CookieID cid, if ( !cookie ) cookie = ""; if ( !connName ) connName = ""; - const char* command = "INSERT INTO " GAMES_TABLE - " (cid, room, connName, nTotal, lang, pub)" - " VALUES( $1, $2, $3, $4, $5, $6 )"; - int nParams = 6; - char* paramValues[nParams]; - char buf[512]; - formatParams( paramValues, nParams, - "%d"DELIM"%s"DELIM"%s"DELIM"%d"DELIM"%d"DELIM"%s", - buf, sizeof(buf), cid, cookie, connName, nPlayersT, - langCode, isPublic?"TRUE":"FALSE" ); + QueryBuilder qb; + qb.appendQueryf( "INSERT INTO " GAMES_TABLE + " (cid, room, connName, nTotal, lang, pub)" + " VALUES( $$, $$, $$, $$, $$, $$ )" ) + .appendParam(cid) + .appendParam(cookie) + .appendParam(connName) + .appendParam(nPlayersT) + .appendParam(langCode) + .appendParam(isPublic?"TRUE":"FALSE" ) + .finish(); - PGresult* result = PQexecParams( getThreadConn(), command, - nParams, NULL, - paramValues, + PGresult* result = PQexecParams( getThreadConn(), qb.c_str(), + qb.paramCount(), NULL, + qb.paramValues(), NULL, NULL, 0 ); if ( PGRES_COMMAND_OK != PQresultStatus(result) ) { logf( XW_LOGERROR, "PQexec=>%s;%s", PQresStatus(PQresultStatus(result)), @@ -271,28 +269,27 @@ DBMgr::SeenSeed( const char* cookie, unsigned short seed, char* connNameBuf, int bufLen, int* nPlayersHP, CookieID* cid ) { - int nParams = 5; - char* paramValues[nParams]; - char buf[512]; - formatParams( paramValues, nParams, - "%s"DELIM"%d"DELIM"%d"DELIM"%d"DELIM"%s", buf, sizeof(buf), - cookie, langCode, nPlayersT, seed, - wantsPublic?"TRUE":"FALSE" ); + QueryBuilder qb; + qb.appendQueryf( "SELECT cid, connName, seeds, sum_array(nPerDevice) FROM " + GAMES_TABLE + " WHERE NOT dead" + " AND room ILIKE $$" + " AND lang = $$" + " AND nTotal = $$" + " AND $$ = ANY(seeds)" + " AND $$ = pub" + " ORDER BY ctime DESC" + " LIMIT 1") + .appendParam(cookie) + .appendParam(langCode) + .appendParam(nPlayersT) + .appendParam(seed) + .appendParam(wantsPublic?"TRUE":"FALSE" ) + .finish(); - const char* cmd = "SELECT cid, connName, seeds, sum_array(nPerDevice) FROM " - GAMES_TABLE - " WHERE NOT dead" - " AND room ILIKE $1" - " AND lang = $2" - " AND nTotal = $3" - " AND $4 = ANY(seeds)" - " AND $5 = pub" - " ORDER BY ctime DESC" - " LIMIT 1"; - - PGresult* result = PQexecParams( getThreadConn(), cmd, - nParams, NULL, - paramValues, + PGresult* result = PQexecParams( getThreadConn(), qb.c_str(), + qb.paramCount(), NULL, + qb.paramValues(), NULL, NULL, 0 ); bool found = 1 == PQntuples( result ); if ( found ) { @@ -312,31 +309,28 @@ DBMgr::FindOpen( const char* cookie, int lang, int nPlayersT, int nPlayersH, bool wantsPublic, char* connNameBuf, int bufLen, int* nPlayersHP ) { - CookieID cid = 0; + QueryBuilder qb; + qb.appendQueryf("SELECT cid, connName, sum_array(nPerDevice) FROM " + GAMES_TABLE + " WHERE NOT dead" + " AND room ILIKE $$" + " AND lang = $$" + " AND nTotal = $$" + " AND $$ <= nTotal-sum_array(nPerDevice)" + " AND $$ = pub" + " LIMIT 1") + .appendParam(cookie) + .appendParam(lang) + .appendParam(nPlayersT) + .appendParam(nPlayersH) + .appendParam(wantsPublic?"TRUE":"FALSE" ) + .finish(); - int nParams = 5; - char* paramValues[nParams]; - char buf[512]; - formatParams( paramValues, nParams, - "%s"DELIM"%d"DELIM"%d"DELIM"%d"DELIM"%s", buf, sizeof(buf), - cookie, lang, nPlayersT, nPlayersH, wantsPublic?"TRUE":"FALSE" ); - - /* NOTE: ILIKE, for case-insensitive comparison, is a postgres extension - to SQL. */ - const char* cmd = "SELECT cid, connName, sum_array(nPerDevice) FROM " - GAMES_TABLE - " WHERE NOT dead" - " AND room ILIKE $1" - " AND lang = $2" - " AND nTotal = $3" - " AND $4 <= nTotal-sum_array(nPerDevice)" - " AND $5 = pub" - " LIMIT 1"; - - PGresult* result = PQexecParams( getThreadConn(), cmd, - nParams, NULL, - paramValues, + PGresult* result = PQexecParams( getThreadConn(), qb.c_str(), + qb.paramCount(), NULL, + qb.paramValues(), NULL, NULL, 0 ); + CookieID cid = 0; if ( 1 == PQntuples( result ) ) { cid = atoi( PQgetvalue( result, 0, 0 ) ); snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) ); @@ -365,18 +359,6 @@ DBMgr::AllDevsAckd( const char* const connName ) return full; } -static void -getPtrs( vector& paramValues, const char* base, vector& offsets ) -{ - size_t ii; - for ( ii = 0; ii < offsets.size(); ++ii ) { - const char* ptr = offsets[ii] + base; - paramValues.push_back( ptr ); - logf( XW_LOGINFO, "%s: str[%d] points at: %s", __func__, ii, - paramValues[ii] ); - } -} - // Return DevIDRelay for device, adding it to devices table IFF it's not // already there. DevIDRelay @@ -408,32 +390,21 @@ DBMgr::RegisterDevice( const DevID* host, int clientVersion, devID = (DevIDRelay)random(); } while ( DEVID_NONE == devID ); - StrWPF query; - StrWPF paramBuf; - vector paramIndices; - query.catf( "INSERT INTO " DEVICES_TABLE " (id, devTypes[1]," - " devids[1], clntVers, versdesc, model, osvers)" - " VALUES( $1, $2, $3, $4, $5, $6, $7 )" ); + QueryBuilder qb; + qb.appendQueryf( "INSERT INTO " DEVICES_TABLE " (id, devTypes[1]," + " devids[1], clntVers, versdesc, model, osvers)" + " VALUES($$, $$, $$, $$, $$, $$, $$)" ); - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%d%c", devID, '\0' ); - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%d%c", host->m_devIDType, '\0' ); - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%s%c", devidStr, '\0' ); - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%d%c", clientVersion, '\0' ); - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%s%c", desc, '\0' ); - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%s%c", model, '\0' ); - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%s%c", osVers, '\0' ); + qb.appendParam( devID ) + .appendParam( host->m_devIDType ) + .appendParam( devidStr ) + .appendParam( clientVersion ) + .appendParam( desc ) + .appendParam( model ) + .appendParam( osVers ) + .finish(); - vector paramValues; - getPtrs( paramValues, paramBuf.c_str(), paramIndices ); - - success = execParams( query, paramValues ); + success = execParams( qb ); } } return devID; @@ -450,24 +421,17 @@ DBMgr::ReregisterDevice( DevIDRelay relayID, const DevID* host, const char* const desc, int clientVersion, const char* const model, const char* const osVers ) { - StrWPF query; - StrWPF paramBuf; - vector paramIndices; + QueryBuilder qb; + qb.appendQueryf( "UPDATE " DEVICES_TABLE " SET " + "devTypes = array_prepend($$, devTypes), " + "devids = array_prepend($$, devids), " ) - query.catf( "UPDATE " DEVICES_TABLE " SET " - "devTypes = array_prepend( $1, devTypes), " - "devids = array_prepend($2, devids), " ); - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%d%c", host->m_devIDType, '\0' ); - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%s%c", host->m_devIDString.c_str(), '\0' ); + .appendParam( host->m_devIDType ) + .appendParam( host->m_devIDString.c_str() ); - formatUpdate( query, paramBuf, paramIndices, true, desc, clientVersion, - model, osVers, relayID ); - - vector paramValues; - getPtrs( paramValues, paramBuf.c_str(), paramIndices ); - execParams( query, paramValues ); + formatUpdate( qb, true, desc, clientVersion, model, osVers, relayID ); + qb.finish(); + execParams( qb ); } // Return true if the relayID exists in the DB already @@ -484,17 +448,11 @@ DBMgr::UpdateDevice( DevIDRelay relayID, const char* const desc, } if ( exists ) { - StrWPF query; - query.catf( "UPDATE " DEVICES_TABLE " SET " ); - - StrWPF paramBuf; - vector paramIndices; - - formatUpdate( query, paramBuf, paramIndices, false, desc, - clientVersion, model, osVers, relayID ); - vector paramValues; - getPtrs( paramValues, paramBuf.c_str(), paramIndices ); - execParams( query, paramValues ); + QueryBuilder qb; + qb.appendQueryf( "UPDATE " DEVICES_TABLE " SET " ); + formatUpdate( qb, false, desc, clientVersion, model, osVers, relayID ); + qb.finish(); + execParams( qb ); } return exists; } @@ -506,40 +464,33 @@ DBMgr::UpdateDevice( DevIDRelay relayID ) } void -DBMgr::formatUpdate( StrWPF& query, StrWPF& paramBuf, vector& paramIndices, +DBMgr::formatUpdate( QueryBuilder& qb, bool append, const char* const desc, int clientVersion, const char* const model, const char* const osVers, DevIDRelay relayID ) { if ( append ) { - query.catf( "mtimes=array_prepend('now', mtimes)" ); // FIXME: too many + qb.appendQueryf( "mtimes=array_prepend('now', mtimes)" ); // FIXME: too many } else { - query.catf( "mtimes[1]='now'" ); + qb.appendQueryf( "mtimes[1]='now'" ); } - int count = paramIndices.size(); if ( NULL != desc && '\0' != desc[0] ) { - query.catf( ", clntVers=$%d, versDesc=$%d", 1 + count, 2 + count ); - count += 2; - - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%d%c", clientVersion, '\0' ); - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%s%c", desc, '\0' ); + qb.appendQueryf( ", clntVers=$$" ) + .appendParam( clientVersion ) + .appendQueryf( ", versDesc=$$" ) + .appendParam( desc ); } if ( NULL != model && '\0' != model[0] ) { - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%s%c", model, '\0' ); - query.catf( ", model=$%d", ++count ); + qb.appendQueryf( ", model=$$" ) + .appendParam( model ); } if ( NULL != osVers && '\0' != osVers[0] ) { - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%s%c", osVers, '\0' ); - query.catf( ", osvers=$%d", ++count ); + qb.appendQueryf( ", osvers=$$" ) + .appendParam( osVers ); } - paramIndices.push_back( paramBuf.size() ); - paramBuf.catf( "%d%c", relayID, '\0' ); - query.catf( " WHERE id = $%d", ++count ); + qb.appendQueryf( " WHERE id = $$" ) + .appendParam( relayID ); } HostID @@ -892,15 +843,15 @@ DBMgr::execSql( const char* const query ) } bool -DBMgr::execParams( const string& query, vector paramValues ) +DBMgr::execParams( QueryBuilder& qb ) { - PGresult* result = PQexecParams( getThreadConn(), query.c_str(), - paramValues.size(), NULL, - ¶mValues[0], + PGresult* result = PQexecParams( getThreadConn(), qb.c_str(), + qb.paramCount(), NULL, + qb.paramValues(), NULL, NULL, 0 ); bool success = PGRES_COMMAND_OK == PQresultStatus( result ); if ( !success ) { - logf( XW_LOGERROR, "PQexecParams(%s)=>%s;%s", query.c_str(), + logf( XW_LOGERROR, "PQexecParams(%s)=>%s;%s", qb.c_str(), PQresStatus(PQresultStatus(result)), PQresultErrorMessage(result) ); } @@ -1331,31 +1282,6 @@ void DBMgr::clearHasNoMessages( DevIDRelay devid ) assert( !hasNoMessages( devid ) ); } -static void -formatParams( char* paramValues[], int nParams, const char* fmt, char* buf, - int bufLen, ... ) -{ - va_list ap; - va_start( ap, bufLen ); - - int len = vsnprintf( buf, bufLen, fmt, ap ); - assert( buf[len] == '\0' ); - - int pnum; - char* ptr = buf; - for ( pnum = 0; pnum < nParams; ++pnum ) { - paramValues[pnum] = ptr; - for ( ; *ptr != '\0' && *ptr != DELIM[0]; ++ptr ) { - // do nothing - assert( ptr < &buf[bufLen] ); - } - // we've found an end - *ptr = '\0'; - ++ptr; - } - va_end(ap); -} - static int here_less_seed( const char* seeds, int sumPerDevice, unsigned short seed ) { diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index c4fa0b691..e0fafcaad 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -32,6 +32,7 @@ #include "xwrelay_priv.h" #include "devid.h" #include "strwpf.h" +#include "querybld.h" using namespace std; @@ -149,7 +150,7 @@ class DBMgr { DBMgr(); bool execSql( const string& query ); bool execSql( const char* const query ); /* no-results query */ - bool execParams( const string& query, vector params ); + bool execParams( QueryBuilder& qb ); void readArray( const char* const connName, const char* column, int arr[] ); DevIDRelay getDevID( const char* connName, int hid ); DevIDRelay getDevID( const DevID* devID ); @@ -161,8 +162,7 @@ class DBMgr { bool nullConnnameOK ); int CountStoredMessages( const char* const connName, int hid ); bool UpdateDevice( DevIDRelay relayID ); - void formatUpdate( StrWPF& query, StrWPF& prmBuf, vector& prmIndices, - bool append, const char* const desc, + void formatUpdate( QueryBuilder& qb, bool append, const char* const desc, int clientVersion, const char* const model, const char* const osVers, DevIDRelay relayID ); diff --git a/xwords4/relay/querybld.cpp b/xwords4/relay/querybld.cpp new file mode 100644 index 000000000..6334d1f9f --- /dev/null +++ b/xwords4/relay/querybld.cpp @@ -0,0 +1,78 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2014 by Eric House (xwords@eehouse.org). All rights reserved. + * + * 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. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include + +#include "querybld.h" +#include "xwrelay_priv.h" + +QueryBuilder& +QueryBuilder::appendQueryf( const char* fmt, ... ) +{ + bool done; + do { + va_list ap; + va_start( ap, fmt ); + done = m_query.catf( fmt, ap ); + va_end( ap ); + } while ( !done ); + return *this; +} + +QueryBuilder& +QueryBuilder::appendParam( const char* value ) +{ + m_paramIndices.push_back( m_paramBuf.size() ); + m_paramBuf.catf( "%s%c", value, '\0' ); + return *this; +} + +QueryBuilder& +QueryBuilder::appendParam( int value ) +{ + m_paramIndices.push_back( m_paramBuf.size() ); + m_paramBuf.catf( "%d%c", value, '\0' ); + return *this; +} + +/* When done adding params, some of which contain $$, turn these into an order + * progression of $1, $2 .. $9. Note assumption that we don't go above 9 since + */ +void +QueryBuilder::finish() +{ + assert( 0 == m_paramValues.size() ); + + size_t ii; + const char* base = m_paramBuf.c_str(); + for ( ii = 0; ii < m_paramIndices.size(); ++ii ) { + const char* ptr = m_paramIndices[ii] + base; + m_paramValues.push_back( ptr ); + } + + for ( size_t count = 0; ; ++count ) { + const char* str = m_query.c_str(); + const char* ptr = strstr( str, "$$" ); + if ( !ptr ) { + assert( count == m_paramIndices.size() ); + break; + } + assert( count < 9 ); + m_query[1 + ptr - str] = '1' + count; + } +} diff --git a/xwords4/relay/querybld.h b/xwords4/relay/querybld.h new file mode 100644 index 000000000..1def83271 --- /dev/null +++ b/xwords4/relay/querybld.h @@ -0,0 +1,47 @@ +/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ +/* + * Copyright 2014 by Eric House (xwords@eehouse.org). All rights reserved. + * + * 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. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _QUERYBLD_H_ +#define _QUERYBLD_H_ + +#include + +#include "strwpf.h" + +using namespace std; + +class QueryBuilder { + + public: + QueryBuilder& appendQueryf( const char* fmt, ... ); + QueryBuilder& appendParam( const char* value ); + QueryBuilder& appendParam( int value ); + void finish(); + int paramCount() const { return m_paramValues.size(); } + const char* const* paramValues() const { return &m_paramValues[0]; } + const char* const c_str() const { return m_query.c_str(); } + + private: + StrWPF m_query; + StrWPF m_paramBuf; + vector m_paramIndices; + vector m_paramValues; +}; + +#endif diff --git a/xwords4/relay/strwpf.cpp b/xwords4/relay/strwpf.cpp index 9a8ab0a83..d6085755a 100644 --- a/xwords4/relay/strwpf.cpp +++ b/xwords4/relay/strwpf.cpp @@ -27,26 +27,37 @@ /* From stack overflow: snprintf with an expanding buffer. */ +bool +StrWPF::catf( const char* fmt, va_list ap ) +{ + bool success = false; + const int origsiz = size(); + resize( origsiz + m_addsiz ); + + int len = vsnprintf( (char*)c_str() + origsiz, m_addsiz, fmt, ap ); + + if ( len >= m_addsiz ) { // needs more space + m_addsiz = len + 1; + resize( origsiz ); + } else if ( -1 == len ) { + assert(0); // should be impossible + } else { + resize( origsiz + len ); + m_addsiz = 100; + success = true; + } + + return success; +} + void StrWPF::catf( const char* fmt, ... ) { - const int origsiz = size(); - int addsiz = 100; - va_list ap; - for ( ; ; ) { - resize( origsiz + addsiz ); - + bool done; + do { + va_list ap; va_start( ap, fmt ); - int len = vsnprintf( (char *)c_str() + origsiz, addsiz, fmt, ap ); + done = catf( fmt, ap ); va_end( ap ); - - if ( len >= addsiz ) { // needs more space - addsiz = len + 1; - } else if ( -1 == len ) { - assert(0); // should be impossible - } else { - resize( origsiz + len ); - break; - } - } + } while ( !done ); } diff --git a/xwords4/relay/strwpf.h b/xwords4/relay/strwpf.h index 950a9e817..e3973d2dc 100644 --- a/xwords4/relay/strwpf.h +++ b/xwords4/relay/strwpf.h @@ -21,10 +21,16 @@ #define _STRWPF_H_ #include +#include -class StrWPF : public std::string { +class StrWPF : public std::string { public: + StrWPF() : m_addsiz(100){} + void catf( const char* fmt, ... ); + bool catf( const char* fmt, va_list ap ); + private: + int m_addsiz; }; #endif