//====== Copyright (c), Valve Corporation, All rights reserved. ======= // // Purpose: Provides access to SQL at a high level // //============================================================================= #ifndef SQLACCESS_H #define SQLACCESS_H #ifdef _WIN32 #pragma once #endif #include "gcsdk/gcsqlquery.h" #include "tier0/memdbgon.h" namespace GCSDK { class CGCSQLQuery; class CGCSQLQueryGroup; class CColumnSet; class CRecordType; //----------------------------------------------------------------------------- // Purpose: Provides access to SQL at a high level //----------------------------------------------------------------------------- class CSQLAccess { public: CSQLAccess( ESchemaCatalog eSchemaCatalog = k_ESchemaCatalogMain ); ~CSQLAccess( ); bool BBeginTransaction( const char *pchName ); bool BCommitTransaction( bool bAllowEmpty = false ); void RollbackTransaction(); bool BInTransaction( ) const { return m_bInTransaction; } bool BYieldingExecute( const char *pchName, const char *pchSQLCommand, uint32 *pcRowsAffected = NULL, bool bSpewOnError = true ); bool BYieldingExecuteString( const char *pchName, const char *pchSQLCommand, CFmtStr1024 *psResult, uint32 *pcRowsAffected = NULL ); bool BYieldingExecuteScalarInt( const char *pchName, const char *pchSQLCommand, int *pnResult, uint32 *pcRowsAffected = NULL ); bool BYieldingExecuteScalarIntWithDefault( const char *pchName, const char *pchSQLCommand, int *pnResult, int iDefaultValue, uint32 *pcRowsAffected = NULL ); bool BYieldingExecuteScalarUint32( const char *pchName, const char *pchSQLCommand, uint32 *punResult, uint32 *pcRowsAffected = NULL ); bool BYieldingExecuteScalarUint32WithDefault( const char *pchName, const char *pchSQLCommand, uint32 *punResult, uint32 unDefaultValue, uint32 *pcRowsAffected = NULL ); bool BYieldingWipeTable( int iTable ); template bool BYieldingExecuteSingleResult( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, TReturn *pResult, uint32 *pcRowsAffected ); template bool BYieldingExecuteSingleResultWithDefault( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, TReturn *pResult, TReturn defaultValue, uint32 *pcRowsAffected ); // manipulating CRecordBase (i.e. CSch...) objects in the database bool BYieldingInsertRecord( CRecordBase *pRecordBase ); bool BYieldingInsertWithIdentity( CRecordBase* pRecordBase ) ; bool BYieldingReadRecordWithWhereColumns( CRecordBase *pRecord, const CColumnSet & readSet, const CColumnSet & whereSet ); template< typename SchClass_t > bool BYieldingReadRecordFromPK( SchClass_t *pRecord ); template< typename SchClass_t> bool BYieldingReadMultipleRecordsWithWhereColumns( CUtlVector< SchClass_t > *pvecRecords, const CColumnSet & whereSet, CUtlVector< SchClass_t > *pvecUnmatchedRecords = NULL ); template< typename SchClass_t> bool BYieldingReadMultipleRecordsWithWhereColumns( CUtlVector< SchClass_t > *pvecRecords, const CColumnSet & readSet, const CColumnSet & whereSet, CUtlVector< SchClass_t > *pvecUnmatchedRecords = NULL ); template< typename SchClass_t> bool BYieldingReadRecordsWithWhereClause( CUtlVector< SchClass_t > *pvecRecords, const char *pchWhereClause, const CColumnSet & readSet, const char *pchTopClause = NULL ); template< typename SchClass_t> bool BYieldingReadRecordsWithQuery( CUtlVector< SchClass_t > *pvecRecords, const char *sQuery, const CColumnSet & readSet ); bool BYieldingUpdateRecord( const CRecordBase &record, const CColumnSet & whereColumns, const CColumnSet & updateColumns ); bool BYieldingDeleteRecord( const CRecordBase & record, const CColumnSet & whereColumns ); void AddRecordParameters( const CRecordBase &record, const CColumnSet & columnSet ); void AddBindParam( const char *pchValue ); void AddBindParam( const int16 nValue ); void AddBindParam( const uint16 uValue ); void AddBindParam( const int32 nValue ); void AddBindParam( const uint32 uValue ); void AddBindParam( const uint64 ulValue ); void AddBindParam( const uint8 *ubValue, const int cubValue ); void AddBindParam( const float fValue ); void AddBindParam( const double dValue ); void ClearParams(); IGCSQLResultSetList *GetResults(); uint32 GetResultSetCount(); uint32 GetResultSetRowCount( uint32 unResultSet ); CSQLRecord GetResultRecord( uint32 unResultSet, uint32 unRow ); private: enum EReadSingleResultResult { eReadSingle_Error, // something went wrong in the DB or the data was in a format we didn't expect eReadSingle_ResultFound, // we found a single result and copied the value -- all is well! eReadSingle_UseDefault, // we didn't find any results but we specified a value in advance for this case }; EReadSingleResultResult BYieldingExecuteSingleResultDataInternal( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, uint8 **pubData, uint32 *punSize, uint32 *pcRowsAffected, bool bHasDefaultValue ); private: CGCSQLQuery *CurrentQuery(); ESchemaCatalog m_eSchemaCatalog; CGCSQLQuery *m_pCurrentQuery; CGCSQLQueryGroup *m_pQueryGroup; bool m_bInTransaction; }; #define FOR_EACH_SQL_RESULT( sqlAccess, resultSet, record ) \ for( CSQLRecord record = (sqlAccess).GetResultRecord( resultSet, 0 ); record.IsValid(); record.NextRow() ) //----------------------------------------------------------------------------- // Purpose: templatized version of querying for a single value //----------------------------------------------------------------------------- template bool CSQLAccess::BYieldingExecuteSingleResult( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, TReturn *pResult, uint32 *pcRowsAffected ) { uint8 *pubData; uint32 cubData; if( CSQLAccess::BYieldingExecuteSingleResultDataInternal( pchName, pchSQLCommand, eType, &pubData, &cubData, pcRowsAffected, false ) != eReadSingle_ResultFound ) return false; *pResult = *( (TCast *)pubData ); return true; } //----------------------------------------------------------------------------- // Purpose: templatized version of querying for a single value //----------------------------------------------------------------------------- template bool CSQLAccess::BYieldingExecuteSingleResultWithDefault( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, TReturn *pResult, TReturn defaultValue, uint32 *pcRowsAffected ) { uint8 *pubData; uint32 cubData; EReadSingleResultResult eResult = CSQLAccess::BYieldingExecuteSingleResultDataInternal( pchName, pchSQLCommand, eType, &pubData, &cubData, pcRowsAffected, true ); if ( eResult == eReadSingle_Error ) return false; if ( eResult == eReadSingle_ResultFound ) { *pResult = *( (TCast *)pubData ); } else { Assert( eResult == eReadSingle_UseDefault ); *pResult = defaultValue; } return true; } //----------------------------------------------------------------------------- // Purpose: Reads the record with a given PK. // Input: pRecordBase - record to read // Output: true if successful, false otherwise //----------------------------------------------------------------------------- template < typename SchClass_t > bool CSQLAccess::BYieldingReadRecordFromPK( SchClass_t *pRecord ) { CColumnSet csetWhere = CColumnSet::PrimaryKey< SchClass_t >(); CColumnSet csetRead = CColumnSet::Inverse( csetWhere ); return BYieldingReadRecordWithWhereColumns( pRecord, csetRead, csetWhere ); } //----------------------------------------------------------------------------- // Purpose: Reads multiple records from the database based on the where columns // filled in for each record. If the record is not found in the database // it will be removed from pvecRecords. If pvecUnmatchedRecords is // provided, it will be populated with the unmatched records removed // from pvecRecords // Input: pvecRecords - The records to fill in from the database // whereSet - The set of columns to query on // (optional) pvecUnmatchedRecords - A vector to hold records which // are not found in the database // Output: true if successful, false otherwise //----------------------------------------------------------------------------- template< typename SchClass_t> bool CSQLAccess::BYieldingReadMultipleRecordsWithWhereColumns( CUtlVector< SchClass_t > *pvecRecords, const CColumnSet & whereSet, CUtlVector< SchClass_t > *pvecUnmatchedRecords /* = NULL */ ) { CColumnSet readSet( GSchemaFull().GetSchema( SchClass_t::k_iTable ).GetRecordInfo() ); readSet.MakeInverse( whereSet ); return BYieldingReadMultipleRecordsWithWhereColumns( pvecRecords, readSet, whereSet, pvecUnmatchedRecords ); } //----------------------------------------------------------------------------- // Purpose: Reads multiple records from the database based on the where columns // filled in for each record. If the record is not found in the database // it will be removed from pvecRecords. If pvecUnmatchedRecords is // provided, it will be populated with the unmatched records removed // from pvecRecords // Input: pvecRecords - The records to fill in from the database // readSet - The set of columns to fill in // whereSet - The set of columns to query on // (optional) pvecUnmatchedRecords - A vector to hold records which // are not found in the database // Output: true if successful, false otherwise //----------------------------------------------------------------------------- template< typename SchClass_t> bool CSQLAccess::BYieldingReadMultipleRecordsWithWhereColumns( CUtlVector< SchClass_t > *pvecRecords, const CColumnSet & readSet, const CColumnSet & whereSet, CUtlVector< SchClass_t > *pvecUnmatchedRecords /* = NULL */ ) { AssertMsg( !BInTransaction(), "BYieldingReadMultipleRecordsWithWhereColumns is not supported in a transaction" ); if( BInTransaction() ) return false; Assert( !readSet.IsEmpty() ); if ( readSet.IsEmpty() ) return false; if ( pvecUnmatchedRecords ) { pvecUnmatchedRecords->RemoveAll(); } // Build the query we'll use for each record CFmtStr1024 sStatement, sWhere; BuildSelectStatementText( &sStatement, readSet ); BuildWhereClauseText( &sWhere, whereSet ); sStatement.Append( " WHERE " ); sStatement.Append( sWhere ); BBeginTransaction( CFmtStr1024( "BYieldingReadMultipleRecordsWithWhereColumns() - %s", sStatement.Access() ) ); // Batch this query for each record FOR_EACH_VEC( *pvecRecords, i ) { AddRecordParameters( pvecRecords->Element( i ), whereSet ); if( !BYieldingExecute( NULL, sStatement ) ) return false; } // Actually run the query if ( !BCommitTransaction() ) return false; Assert( GetResultSetCount() == (uint32)pvecRecords->Count() ); if ( GetResultSetCount() != (uint32)pvecRecords->Count() ) return false; // Get the results. Reading backwards because if a record doesn't find a match we'll // remove it from the list FOR_EACH_VEC_BACK( *pvecRecords, i ) { // make sure the types are the same IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( i ); Assert( pResultSet->GetRowCount() <= 1 ); if ( pResultSet->GetRowCount() > 1 ) return false; if( pResultSet->GetRowCount() == 1 ) { // We have a record in this set, read it in FOR_EACH_COLUMN_IN_SET( readSet, nColumnIndex ) { EGCSQLType eRecordType = readSet.GetColumnInfo( nColumnIndex ).GetType(); EGCSQLType eResultType = pResultSet->GetColumnType( nColumnIndex ); Assert( eResultType == eRecordType ); if( eRecordType != eResultType ) return false; } CSQLRecord sqlRecord = GetResultRecord( i, 0 ); FOR_EACH_COLUMN_IN_SET( readSet, nColumnIndex ) { uint8 *pubData; uint32 cubData; DbgVerify( sqlRecord.BGetColumnData( nColumnIndex, &pubData, (int*)&cubData ) ); DbgVerify( pvecRecords->Element( i ).BSetField( readSet.GetColumn( nColumnIndex ), pubData, cubData ) ); } } else { // This record did not match, remove it and add it to pvecUnmatchedRecords if needed if ( pvecUnmatchedRecords ) { pvecUnmatchedRecords->AddToTail( pvecRecords->Element( i ) ); } pvecRecords->Remove( i ); } } return true; } //----------------------------------------------------------------------------- // Purpose: Reads a list of records from the DB according to the specified where // clause // Input: pRecordBase - record to insert // Output: true if successful, false otherwise //----------------------------------------------------------------------------- template< typename SchClass_t> bool CSQLAccess::BYieldingReadRecordsWithWhereClause( CUtlVector< SchClass_t > *pvecRecords, const char *pchWhereClause, const CColumnSet & readSet, const char *pchTopClause ) { AssertMsg( !BInTransaction(), "BYieldingReadRecordsWithWhereClause is not supported in a transaction" ); if( BInTransaction() ) return false; Assert( !readSet.IsEmpty() ); CFmtStr1024 sStatement; BuildSelectStatementText( &sStatement, readSet, pchTopClause ); Assert( pchWhereClause && *pchWhereClause ); if( !pchWhereClause || !(*pchWhereClause) ) return false; CUtlString sFullStatement = sStatement.Access(); sFullStatement += " WHERE "; sFullStatement += pchWhereClause; return BYieldingReadRecordsWithQuery< SchClass_t >( pvecRecords, sFullStatement, readSet ); } //----------------------------------------------------------------------------- // Purpose: Inserts a new record into the DB and reads non-insertable fields back // into the record. // Input: pRecordBase - record to insert // Output: true if successful, false otherwise //----------------------------------------------------------------------------- template< typename SchClass_t> bool CSQLAccess::BYieldingReadRecordsWithQuery( CUtlVector< SchClass_t > *pvecRecords, const char *sQuery, const CColumnSet & readSet ) { AssertMsg( !BInTransaction(), "BYieldingReadRecordsWithQuery is not supported in a transaction" ); if( BInTransaction() ) return false; Assert(!readSet.IsEmpty() ); if( !BYieldingExecute( NULL, sQuery ) ) return false; Assert( GetResultSetCount() == 1 ); if ( GetResultSetCount() != 1 ) return false; // make sure the types are the same IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( 0 ); return CopyResultToSchVector( pResultSet, readSet, pvecRecords ); } } // namespace GCSDK #include "tier0/memdbgoff.h" #endif // SQLACCESS_H