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 212454a65..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 ) ); @@ -396,14 +390,21 @@ DBMgr::RegisterDevice( const DevID* host, int clientVersion, devID = (DevIDRelay)random(); } while ( DEVID_NONE == devID ); - StrWPF query; - 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 ); + QueryBuilder qb; + qb.appendQueryf( "INSERT INTO " DEVICES_TABLE " (id, devTypes[1]," + " devids[1], clntVers, versdesc, model, osvers)" + " VALUES($$, $$, $$, $$, $$, $$, $$)" ); + + qb.appendParam( devID ) + .appendParam( host->m_devIDType ) + .appendParam( devidStr ) + .appendParam( clientVersion ) + .appendParam( desc ) + .appendParam( model ) + .appendParam( osVers ) + .finish(); + + success = execParams( qb ); } } return devID; @@ -420,15 +421,17 @@ 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() ); + QueryBuilder qb; + qb.appendQueryf( "UPDATE " DEVICES_TABLE " SET " + "devTypes = array_prepend($$, devTypes), " + "devids = array_prepend($$, devids), " ) - formatUpdate( query, true, desc, clientVersion, model, osVers, relayID ); - execSql( query ); + .appendParam( host->m_devIDType ) + .appendParam( host->m_devIDString.c_str() ); + + formatUpdate( qb, true, desc, clientVersion, model, osVers, relayID ); + qb.finish(); + execParams( qb ); } // Return true if the relayID exists in the DB already @@ -445,10 +448,11 @@ 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 ); + QueryBuilder qb; + qb.appendQueryf( "UPDATE " DEVICES_TABLE " SET " ); + formatUpdate( qb, false, desc, clientVersion, model, osVers, relayID ); + qb.finish(); + execParams( qb ); } return exists; } @@ -460,26 +464,33 @@ DBMgr::UpdateDevice( DevIDRelay relayID ) } void -DBMgr::formatUpdate( StrWPF& query, bool append, const char* const desc, +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'" ); } if ( NULL != desc && '\0' != desc[0] ) { - query.catf( ", clntVers=%d, versDesc='%s'", clientVersion, desc ); + qb.appendQueryf( ", clntVers=$$" ) + .appendParam( clientVersion ) + .appendQueryf( ", versDesc=$$" ) + .appendParam( desc ); } if ( NULL != model && '\0' != model[0] ) { - query.catf( ", model='%s'", model ); + qb.appendQueryf( ", model=$$" ) + .appendParam( model ); } if ( NULL != osVers && '\0' != osVers[0] ) { - query.catf( ", osvers='%s'", osVers ); + qb.appendQueryf( ", osvers=$$" ) + .appendParam( osVers ); } - query.catf( " WHERE id = %d", relayID ); + qb.appendQueryf( " WHERE id = $$" ) + .appendParam( relayID ); } HostID @@ -831,6 +842,22 @@ DBMgr::execSql( const char* const query ) return ok; } +bool +DBMgr::execParams( QueryBuilder& qb ) +{ + 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", qb.c_str(), + PQresStatus(PQresultStatus(result)), + PQresultErrorMessage(result) ); + } + return success; +} + void DBMgr::readArray( const char* const connName, const char* column, int arr[] ) /* len 4 */ { @@ -1255,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 6e5bcbf0e..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,6 +150,7 @@ class DBMgr { DBMgr(); bool execSql( const string& query ); bool execSql( const char* const query ); /* no-results query */ + 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 ); @@ -160,7 +162,7 @@ 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( 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