summaryrefslogtreecommitdiff
path: root/gcsdk/sqlaccess
diff options
context:
space:
mode:
Diffstat (limited to 'gcsdk/sqlaccess')
-rw-r--r--gcsdk/sqlaccess/columnset.cpp368
-rw-r--r--gcsdk/sqlaccess/record.cpp856
-rw-r--r--gcsdk/sqlaccess/recordinfo.cpp915
-rw-r--r--gcsdk/sqlaccess/schema.cpp1505
-rw-r--r--gcsdk/sqlaccess/schemafull.cpp409
-rw-r--r--gcsdk/sqlaccess/schemaupdate.cpp1696
-rw-r--r--gcsdk/sqlaccess/sqlaccess.cpp953
-rw-r--r--gcsdk/sqlaccess/sqlrecord.cpp538
-rw-r--r--gcsdk/sqlaccess/sqlutil.cpp918
9 files changed, 8158 insertions, 0 deletions
diff --git a/gcsdk/sqlaccess/columnset.cpp b/gcsdk/sqlaccess/columnset.cpp
new file mode 100644
index 0000000..4798aa1
--- /dev/null
+++ b/gcsdk/sqlaccess/columnset.cpp
@@ -0,0 +1,368 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Sets of columns in SQL queries
+//
+// $NoKeywords: $
+//=============================================================================
+
+#include "stdafx.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+namespace GCSDK
+{
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructs a column set with no columns in it
+//-----------------------------------------------------------------------------
+CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo )
+: m_pRecordInfo( pRecordInfo )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructs a column set with a single column in it
+// Inputs: nColumn - the column to add
+//-----------------------------------------------------------------------------
+
+CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1 )
+ : m_pRecordInfo( pRecordInfo )
+{
+ m_vecColumns.AddToTail( col1 );
+}
+
+CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2 )
+ : m_pRecordInfo( pRecordInfo )
+{
+ m_vecColumns.EnsureCapacity( 2 );
+ m_vecColumns.AddToTail( col1 );
+ m_vecColumns.AddToTail( col2 );
+}
+
+CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3 )
+ : m_pRecordInfo( pRecordInfo )
+{
+ m_vecColumns.EnsureCapacity( 3 );
+ m_vecColumns.AddToTail( col1 );
+ m_vecColumns.AddToTail( col2 );
+ m_vecColumns.AddToTail( col3 );
+}
+
+CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4 )
+ : m_pRecordInfo( pRecordInfo )
+{
+ m_vecColumns.EnsureCapacity( 4 );
+ m_vecColumns.AddToTail( col1 );
+ m_vecColumns.AddToTail( col2 );
+ m_vecColumns.AddToTail( col3 );
+ m_vecColumns.AddToTail( col4 );
+}
+
+CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5 )
+ : m_pRecordInfo( pRecordInfo )
+{
+ m_vecColumns.EnsureCapacity( 5 );
+ m_vecColumns.AddToTail( col1 );
+ m_vecColumns.AddToTail( col2 );
+ m_vecColumns.AddToTail( col3 );
+ m_vecColumns.AddToTail( col4 );
+ m_vecColumns.AddToTail( col5 );
+}
+
+CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5, int col6 )
+ : m_pRecordInfo( pRecordInfo )
+{
+ m_vecColumns.EnsureCapacity( 6 );
+ m_vecColumns.AddToTail( col1 );
+ m_vecColumns.AddToTail( col2 );
+ m_vecColumns.AddToTail( col3 );
+ m_vecColumns.AddToTail( col4 );
+ m_vecColumns.AddToTail( col5 );
+ m_vecColumns.AddToTail( col6 );
+}
+
+CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5, int col6, int col7 )
+ : m_pRecordInfo( pRecordInfo )
+{
+ m_vecColumns.EnsureCapacity( 7 );
+ m_vecColumns.AddToTail( col1 );
+ m_vecColumns.AddToTail( col2 );
+ m_vecColumns.AddToTail( col3 );
+ m_vecColumns.AddToTail( col4 );
+ m_vecColumns.AddToTail( col5 );
+ m_vecColumns.AddToTail( col6 );
+ m_vecColumns.AddToTail( col7 );
+}
+
+CColumnSet::CColumnSet( const CRecordInfo *pRecordInfo, int col1, int col2, int col3, int col4, int col5, int col6, int col7, int col8 )
+: m_pRecordInfo( pRecordInfo )
+{
+ m_vecColumns.EnsureCapacity( 8 );
+ m_vecColumns.AddToTail( col1 );
+ m_vecColumns.AddToTail( col2 );
+ m_vecColumns.AddToTail( col3 );
+ m_vecColumns.AddToTail( col4 );
+ m_vecColumns.AddToTail( col5 );
+ m_vecColumns.AddToTail( col6 );
+ m_vecColumns.AddToTail( col7 );
+ m_vecColumns.AddToTail( col8 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Copy constructor
+//-----------------------------------------------------------------------------
+CColumnSet::CColumnSet( const CColumnSet & rhs )
+{
+ MEM_ALLOC_CREDIT_("CColumnSet");
+ m_vecColumns.CopyArray( rhs.m_vecColumns.Base(), rhs.m_vecColumns.Count() );
+ m_pRecordInfo = rhs.m_pRecordInfo;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Assignment operator
+//-----------------------------------------------------------------------------
+CColumnSet & CColumnSet::operator=( const CColumnSet & rhs )
+{
+ MEM_ALLOC_CREDIT_("CColumnSet");
+ m_vecColumns.CopyArray( rhs.m_vecColumns.Base(), rhs.m_vecColumns.Count() );
+ m_pRecordInfo = rhs.m_pRecordInfo;
+ return *this;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Addition operator. lhs ColumnSet will be a union of the two
+// ColumnSets
+//-----------------------------------------------------------------------------
+CColumnSet & CColumnSet::operator+=( const CColumnSet & rhs )
+{
+ Assert( this->GetRecordInfo() == rhs.GetRecordInfo() );
+ FOR_EACH_COLUMN_IN_SET( rhs, i )
+ {
+ BAddColumn( rhs.GetColumn( i ) );
+ }
+
+ return *this;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Addition operator. Returns a union of lhs and rhs
+//-----------------------------------------------------------------------------
+const CColumnSet CColumnSet::operator+( const CColumnSet & rhs ) const
+{
+ return CColumnSet( *this ) += rhs;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a column to the set if it is
+// Inputs: nColumn - THe column to add
+//-----------------------------------------------------------------------------
+void CColumnSet::BAddColumn( int nColumn )
+{
+ if( nColumn >= 0 && nColumn < m_pRecordInfo->GetNumColumns() )
+ {
+ //not sure best way to handle the 'is already set case'
+ if( !IsSet( nColumn ) )
+ m_vecColumns.AddToTail( nColumn );
+ }
+ else
+ {
+ AssertMsg3( false, "Attempting to set an out of range column on schema type %s, %d (of %d)", GetRecordInfo()->GetName(), nColumn, m_pRecordInfo->GetNumColumns() );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes a column from the set
+// Inputs: nColumn - THe column to remove
+//-----------------------------------------------------------------------------
+void CColumnSet::BRemoveColumn( int nColumn )
+{
+ m_vecColumns.FindAndRemove( nColumn );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if a column is in the set
+// Inputs: nColumn - THe column to test
+//-----------------------------------------------------------------------------
+bool CColumnSet::IsSet( int nColumn ) const
+{
+ int nIndex = m_vecColumns.Find( nColumn );
+ return m_vecColumns.IsValidIndex( nIndex );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the number of columns in the set
+//-----------------------------------------------------------------------------
+uint32 CColumnSet::GetColumnCount() const
+{
+ return m_vecColumns.Count();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the column index of the Nth column in the set
+// Inputs: nIndex - the position in the set to return a column index for.
+//-----------------------------------------------------------------------------
+int CColumnSet::GetColumn( int nIndex ) const
+{
+ return m_vecColumns[nIndex];
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a CColumnInfo object for the nth column in the set
+// Inputs: nIndex - the position in the set to return a column info for.
+//-----------------------------------------------------------------------------
+const CColumnInfo & CColumnSet::GetColumnInfo( int nIndex ) const
+{
+ return m_pRecordInfo->GetColumnInfo( GetColumn( nIndex ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Empties the column set
+//-----------------------------------------------------------------------------
+void CColumnSet::MakeEmpty()
+{
+ m_vecColumns.RemoveAll();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Makes the column set be the full set of all columns in the record info
+//-----------------------------------------------------------------------------
+void CColumnSet::MakeFull()
+{
+ MakeEmpty();
+ const int nNumColumns = m_pRecordInfo->GetNumColumns();
+ m_vecColumns.EnsureCapacity( nNumColumns );
+ for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
+ {
+ //do a direct add to avoid the exponential cost since we know we won't have conflicts
+ m_vecColumns.AddToTail( nColumn );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Makes the column set be the full set of all insertable columns in
+// the record info
+//-----------------------------------------------------------------------------
+void CColumnSet::MakeInsertable()
+{
+ MakeEmpty();
+ for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
+ {
+ const CColumnInfo & columnInfo = m_pRecordInfo->GetColumnInfo( nColumn );
+ if( columnInfo.BIsInsertable() )
+ {
+ //do a direct add to avoid the exponential cost since we know we won't have conflicts
+ m_vecColumns.AddToTail( nColumn );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Makes the column set be the full set of all noninsertable columns in
+// the record info
+//-----------------------------------------------------------------------------
+void CColumnSet::MakeNoninsertable()
+{
+ MakeEmpty();
+ for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
+ {
+ const CColumnInfo & columnInfo = m_pRecordInfo->GetColumnInfo( nColumn );
+ if( !columnInfo.BIsInsertable() )
+ {
+ //do a direct add to avoid the exponential cost since we know we won't have conflicts
+ m_vecColumns.AddToTail( nColumn );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Makes the column set be the full set of all primary key columns in
+// the record info
+//-----------------------------------------------------------------------------
+void CColumnSet::MakePrimaryKey()
+{
+ MakeEmpty();
+ for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
+ {
+ const CColumnInfo & columnInfo = m_pRecordInfo->GetColumnInfo( nColumn );
+ if( columnInfo.BIsPrimaryKey() )
+ {
+ //do a direct add to avoid the exponential cost since we know we won't have conflicts
+ m_vecColumns.AddToTail( nColumn );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Makes the column set be the full set of all primary key columns in
+// the record info
+//-----------------------------------------------------------------------------
+void CColumnSet::MakeInverse( const CColumnSet & columnSet )
+{
+ MakeEmpty();
+ for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
+ {
+ if( !columnSet.IsSet( nColumn ) )
+ {
+ //do a direct add to avoid the exponential cost since we know we won't have conflicts
+ m_vecColumns.AddToTail( nColumn );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// determines if the current column set has all fields set. Useful for detection of new columns being added to the schema
+//-----------------------------------------------------------------------------
+bool CColumnSet::BAreAllFieldsSet() const
+{
+ for( int nColumn = 0; nColumn < m_pRecordInfo->GetNumColumns(); nColumn++ )
+ {
+ if( !IsSet( nColumn ) )
+ return false;
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a Column Set which is the inverse of the given column set
+// STATIC - Difference from MakeInverse is that it has a return value
+//-----------------------------------------------------------------------------
+CColumnSet CColumnSet::Inverse( const CColumnSet & columnSet )
+{
+ CColumnSet set( columnSet.GetRecordInfo() );
+ set.MakeInverse( columnSet );
+ return set;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Claims the memory for CColumnSet
+//-----------------------------------------------------------------------------
+#ifdef DBGFLAG_VALIDATE
+void CColumnSet::Validate( CValidator &validator, const char *pchName )
+{
+ // these are INSIDE the function instead of outside so the interface
+ // doesn't change
+ VALIDATE_SCOPE();
+
+ ValidateObj( m_vecColumns );
+}
+#endif
+
+
+} // namespace GCSDK
diff --git a/gcsdk/sqlaccess/record.cpp b/gcsdk/sqlaccess/record.cpp
new file mode 100644
index 0000000..6812b50
--- /dev/null
+++ b/gcsdk/sqlaccess/record.cpp
@@ -0,0 +1,856 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+#include "stdafx.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+namespace GCSDK
+{
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CRecordBase::~CRecordBase()
+{
+ Cleanup();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Copy constructor
+// Input: that - CRecord to copy from
+//-----------------------------------------------------------------------------
+CRecordBase::CRecordBase( const CRecordBase &that )
+{
+ *this = that;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Assignment operator - COPIES the record data
+// Input: that - CRecord to copy from
+//-----------------------------------------------------------------------------
+CRecordBase& CRecordBase::operator = ( const CRecordBase & that )
+{
+ Assert( GetITable() == that.GetITable() );
+
+ // COPY that record
+ Copy( that );
+
+ return *this;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Copies the data in the record. This is overridden by CRecordVar and
+// CRecordExternal
+// Input: that - CRecord to copy from
+//-----------------------------------------------------------------------------
+void CRecordBase::Copy( const CRecordBase & that )
+{
+ Cleanup();
+ Q_memcpy( PubRecordFixed(), that.PubRecordFixed(), GetPSchema()->CubRecordFixed() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the record info for this record's schema
+//-----------------------------------------------------------------------------
+const CRecordInfo *CRecordBase::GetPRecordInfo() const
+{
+ return GetPSchema()->GetRecordInfo();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Copies the data in the var record.
+// Input: that - CRecord to copy from
+//-----------------------------------------------------------------------------
+void CRecordVar::Copy( const CRecordBase & baseThat )
+{
+ const CRecordVar & that = (const CRecordVar &)baseThat;
+
+ // COPY that record
+ Cleanup();
+ m_pSchema = that.m_pSchema;
+ Q_memcpy( PubRecordFixed(), that.PubRecordFixed(), GetPSchema()->CubRecordFixed() );
+
+ SetFlag( k_EAllocatedVarBlock, false );
+ if ( VarFieldBlockInfo_t *pVarBlockInfo = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() ) )
+ {
+ if ( pVarBlockInfo->m_cubBlock )
+ {
+ void *pvNewBlock = malloc( pVarBlockInfo->m_cubBlock );
+ Q_memcpy( pvNewBlock, pVarBlockInfo->m_pubBlock, pVarBlockInfo->m_cubBlock );
+ pVarBlockInfo->m_pubBlock = ( uint8 * )pvNewBlock;
+ SetFlag( k_EAllocatedVarBlock, true );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Copies the data in the var record.
+// Input: that - CRecord to copy from
+//-----------------------------------------------------------------------------
+void CRecordExternal::Copy( const CRecordBase & baseThat )
+{
+ const CRecordExternal & that = (const CRecordExternal &)baseThat;
+
+ Cleanup();
+ m_pSchema = that.m_pSchema;
+
+ m_pubRecordFixedExternal = ( uint8 * )malloc( m_pSchema->CubRecordFixed() );
+ Q_memcpy( m_pubRecordFixedExternal, that.PubRecordFixed(), m_pSchema->CubRecordFixed() );
+ SetFlag( k_EAllocatedFixed, true );
+
+ SetFlag( k_EAllocatedVarBlock, false );
+ if ( VarFieldBlockInfo_t *pVarBlockInfo = m_pSchema->PVarFieldBlockInfoFromRecord( PubRecordFixed() ) )
+ {
+ if ( pVarBlockInfo->m_cubBlock )
+ {
+ void *pvNewBlock = malloc( pVarBlockInfo->m_cubBlock );
+ Q_memcpy( pvNewBlock, pVarBlockInfo->m_pubBlock, pVarBlockInfo->m_cubBlock );
+ pVarBlockInfo->m_pubBlock = ( uint8 * )pvNewBlock;
+ SetFlag( k_EAllocatedVarBlock, true );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize to an empty record
+// Input: pSchema - Schema for the record this will hold
+//-----------------------------------------------------------------------------
+void CRecordExternal::Init( CSchema *pSchema )
+{
+ Cleanup();
+ m_pSchema = pSchema;
+ m_pubRecordFixedExternal = ( uint8 * )malloc( m_pSchema->CubRecordFixed() );
+ Q_memset( m_pubRecordFixedExternal, 0, m_pSchema->CubRecordFixed() );
+ SetFlag( k_EAllocatedFixed, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize pointing to a record expanded in memory
+// Input: pSchema - Schema for the record this will hold
+// pubRecord - Pointer to fixed record data
+// bTakeOwnership - Should we delete the record when destroyed
+// Output: Size of the record's data
+//-----------------------------------------------------------------------------
+int CRecordBase::InitFromBytes( uint8 *pubRecord )
+{
+ Cleanup();
+
+ Q_memcpy( PubRecordFixed(), pubRecord, GetPSchema()->CubRecordFixed() );
+ int cubRead = GetPSchema()->CubRecordFixed();
+ return cubRead;
+}
+
+int CRecordVar::InitFromBytes( uint8 *pubRecord )
+{
+ Cleanup();
+
+ Q_memcpy( PubRecordFixed(), pubRecord, GetPSchema()->CubRecordFixed() );
+ int cubRead = GetPSchema()->CubRecordFixed();
+ if ( VarFieldBlockInfo_t *pVarBlockInfo = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() ) )
+ {
+ if ( pVarBlockInfo->m_cubBlock )
+ {
+ void *pvNewBlock = malloc( pVarBlockInfo->m_cubBlock );
+ Q_memcpy( pvNewBlock, pVarBlockInfo->m_pubBlock, pVarBlockInfo->m_cubBlock );
+ pVarBlockInfo->m_pubBlock = ( uint8 * )pvNewBlock;
+ SetFlag( k_EAllocatedVarBlock, true );
+ cubRead += pVarBlockInfo->m_cubBlock;
+ }
+ }
+
+ return cubRead;
+}
+
+
+int CRecordExternal::Init( CSchema *pSchema, uint8 *pubRecord, bool bTakeOwnership )
+{
+ m_pSchema = pSchema;
+ m_pubRecordFixedExternal = pubRecord;
+ SetFlag( k_EAllocatedFixed, bTakeOwnership );
+ SetFlag( k_EAllocatedVarBlock, bTakeOwnership );
+ int cubRead = m_pSchema->CubRecordFixed() + CubRecordVarBlock();
+
+ return cubRead;
+}
+
+CSchema *CRecordBase::GetPSchema()
+{
+ return GetPSchemaImpl();
+}
+
+CSchema *CRecordBase::GetPSchemaImpl()
+{
+ CSchema *pSchema = NULL;
+ int i = GetITable();
+ if ( i != -1 )
+ pSchema = &GSchemaFull().GetSchema( i );
+ return pSchema;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Render a field to a buffer
+// Input: unColumn - field to render
+// cchBuffer - size of render buffer
+// pchBuffer - buffer to render into
+//-----------------------------------------------------------------------------
+void CRecordBase::RenderField( uint32 unColumn, int cchBuffer, char *pchBuffer ) const
+{
+ Q_strncpy( pchBuffer, "", cchBuffer );
+
+ uint8 *pubData;
+ uint32 cubData;
+ if ( !BGetField( unColumn, &pubData, &cubData ) )
+ return;
+
+ // Get the column info and figure out how to interpret the data
+ ConvertFieldToText( GetPRecordInfo()->GetColumnInfo( unColumn ).GetType(), pubData, cubData, pchBuffer, cchBuffer, false );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Reset to base state, freeing any memory we are responsible for
+//-----------------------------------------------------------------------------
+void CRecordBase::Cleanup()
+{
+}
+
+void CRecordVar::Cleanup()
+{
+ // Must do this before freeing memory that encloses it
+ // (eg releasing the net packet)
+ if ( BFlagSet( k_EAllocatedVarBlock ) )
+ {
+ void *pvVarBlock = m_pSchema->PVarFieldBlockInfoFromRecord( PubRecordFixed() )->m_pubBlock;
+ free( pvVarBlock );
+ m_pSchema->PVarFieldBlockInfoFromRecord( PubRecordFixed() )->m_pubBlock = NULL;
+ SetFlag( k_EAllocatedVarBlock, false );
+ }
+}
+
+void CRecordExternal::Cleanup()
+{
+ // clean up the variable-length memory we might have allocated
+ if ( BFlagSet( k_EAllocatedVarBlock ) )
+ {
+ void *pvVarBlock = m_pSchema->PVarFieldBlockInfoFromRecord( PubRecordFixed() )->m_pubBlock;
+ free( pvVarBlock );
+ m_pSchema->PVarFieldBlockInfoFromRecord( PubRecordFixed() )->m_pubBlock = NULL;
+ SetFlag( k_EAllocatedVarBlock, false );
+ }
+
+ // clean up the external memory we might have allocated
+ if ( BFlagSet( k_EAllocatedFixed ) )
+ free( m_pubRecordFixedExternal );
+ SetFlag( k_EAllocatedFixed, false );
+ m_pubRecordFixedExternal = NULL;
+
+ // clean up the lowest layer, not calling CRecordVar
+ CRecordBase::Cleanup();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Deserializes a block of memory into this record
+// Input: pubData - Memory block to deserialize from
+//-----------------------------------------------------------------------------
+void CRecordExternal::DeSerialize( uint8 *pubData )
+{
+ InitFromBytes( pubData );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculates the size of this record when serialized
+// Output: Size of serialized message
+//-----------------------------------------------------------------------------
+uint32 CRecordBase::CubSerialized()
+{
+ return CubRecordFixed() + CubRecordVarBlock();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get pointer to fixed part of record
+// Output: pubRecordFixed
+//-----------------------------------------------------------------------------
+uint8* CRecordBase::PubRecordFixed()
+{
+ return ( uint8 * )( this + 1 );
+}
+
+uint8* CRecordExternal::PubRecordFixed()
+{
+ Assert( m_pubRecordFixedExternal );
+ return m_pubRecordFixedExternal;
+}
+
+uint8* CRecordVar::PubRecordFixed()
+{
+ return ( uint8 * )( this + 1 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get pointer to fixed part of record
+// Output: pubRecordFixed
+//-----------------------------------------------------------------------------
+const uint8* CRecordBase::PubRecordFixed() const
+{
+ return const_cast<CRecordBase *>( this )->PubRecordFixed();
+}
+
+const uint8* CRecordVar::PubRecordFixed() const
+{
+ return const_cast<CRecordVar *>( this )->PubRecordFixed();
+}
+
+const uint8* CRecordExternal::PubRecordFixed() const
+{
+ return const_cast<CRecordExternal *>( this )->PubRecordFixed();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get size of fixed part of record
+// Output: size in bytes of fixed part
+//-----------------------------------------------------------------------------
+uint32 CRecordBase::CubRecordFixed() const
+{
+ return GetPSchema()->CubRecordFixed();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get pointer to variable part of record
+// Output: Pointer to variable-length block -- may be NULL if this record
+// has no var-length fields or they are all empty
+//-----------------------------------------------------------------------------
+uint8* CRecordBase::PubRecordVarBlock()
+{
+ VarFieldBlockInfo_t *pVarFieldBlockInfo = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() );
+ if ( pVarFieldBlockInfo )
+ {
+ return pVarFieldBlockInfo->m_pubBlock;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get pointer to variable part of record
+// Output: Pointer to variable-length block -- may be NULL if this record
+// has no var-length fields or they are all empty
+//-----------------------------------------------------------------------------
+const uint8* CRecordBase::PubRecordVarBlock() const
+{
+ return const_cast<CRecordBase *>( this )->PubRecordVarBlock();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get size of variable part of record
+// Output: Size in bytes of var-length block - may be zero if this record
+// has no var-length fields or they are all empty
+//-----------------------------------------------------------------------------
+uint32 CRecordBase::CubRecordVarBlock() const
+{
+ VarFieldBlockInfo_t *pVarFieldBlockInfo = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() );
+ if ( pVarFieldBlockInfo )
+ {
+ return pVarFieldBlockInfo->m_cubBlock;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get size of variable part of record
+// Output: Size in bytes of var-length block - may be zero if this record
+// has no var-length fields or they are all empty
+//-----------------------------------------------------------------------------
+bool CRecordBase::BAssureRecordVarStorage( uint32 cVariableBytes )
+{
+ // get the variable field block
+ VarFieldBlockInfo_t *pVarFieldBlockInfo = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() );
+ if ( pVarFieldBlockInfo )
+ {
+ // if we have it, see if it's got enough storage
+ if ( pVarFieldBlockInfo->m_cubBlock >= cVariableBytes )
+ {
+ // already there
+ return true;
+ }
+
+ // allocate it
+ uint8* pubData = (uint8*) malloc( cVariableBytes );
+ if ( pubData == NULL )
+ return false;
+
+ // do we have something right now?
+ if ( pVarFieldBlockInfo->m_cubBlock != 0 )
+ {
+ // sure do. copy it over.
+ Q_memcpy( pubData, pVarFieldBlockInfo->m_pubBlock, pVarFieldBlockInfo->m_cubBlock );
+
+ // free what was there
+ free( pVarFieldBlockInfo->m_pubBlock );
+ }
+
+ // hook up our buffer
+ pVarFieldBlockInfo->m_cubBlockFree = cVariableBytes - pVarFieldBlockInfo->m_cubBlock;
+ pVarFieldBlockInfo->m_cubBlock = cVariableBytes;
+ pVarFieldBlockInfo->m_pubBlock = pubData;
+
+ return true;
+ }
+ else
+ {
+ // we don't have one;
+ // we've got no variable length fields, and so can't preallocate for them!
+ return false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize this whole record to random data
+// Input: unPrimaryIndex - Primary index to set
+//-----------------------------------------------------------------------------
+void CRecordExternal::InitRecordRandom( uint32 unPrimaryIndex )
+{
+ bool bRealloced = false;
+ GetPSchema()->InitRecordRandom( PubRecordFixed(), unPrimaryIndex, &bRealloced, BFlagSet( k_EAllocatedVarBlock ) );
+
+ if ( bRealloced )
+ SetFlag( k_EAllocatedVarBlock, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set a field in this record to random bits
+// Input: iField - Field to set
+//-----------------------------------------------------------------------------
+void CRecordExternal::SetFieldRandom( int iField )
+{
+ bool bRealloced = false;
+ GetPSchema()->SetFieldRandom( PubRecordFixed(), iField, &bRealloced, BFlagSet( k_EAllocatedVarBlock ) );
+
+ if ( bRealloced )
+ SetFlag( k_EAllocatedVarBlock, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get a field (var or fixed) from this record
+// Input: iField - Field to set
+// ppubData - Receives pointer to fields data
+// pcubField - Receives count of bytes of data (will count the null for strings)
+// Output: true if succeeds
+//-----------------------------------------------------------------------------
+bool CRecordBase::BGetField( int iField, uint8 **ppubData, uint32 *pcubField ) const
+{
+ return GetPSchema()->BGetFieldData( PubRecordFixed(), iField, ppubData, pcubField );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the data for a field, whether fixed or variable length
+// Input: iField - index of field to set
+// pubData - pointer to field data to copy from
+// cubData - size in bytes of that data
+// Output: true if successful
+//-----------------------------------------------------------------------------
+bool CRecordBase::BSetField( int iField, void *pvData, uint32 cubData )
+{
+ bool bRealloced = false;
+ bool bResult = BSetField( iField, pvData, cubData, &bRealloced );
+ Assert( !bRealloced );
+
+ return bResult;
+}
+
+bool CRecordBase::BSetField( int iField, void *pvData, uint32 cubData, bool *pbRealloced )
+{
+ uint8 *pubData = reinterpret_cast<uint8 *>( pvData );
+
+ if ( !GetPSchema()->BSetFieldData( PubRecordFixed(), iField, pubData, cubData, pbRealloced ) )
+ return false;
+
+ return true;
+}
+
+bool CRecordVar::BSetField( int iField, void *pvData, uint32 cubData )
+{
+ bool bRealloced = false;
+ bool bResult = CRecordBase::BSetField( iField, pvData, cubData, &bRealloced );
+
+ if ( bRealloced )
+ SetFlag( k_EAllocatedVarBlock, true );
+ return bResult;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Erases a field, setting it to 0 length (if possible) and filling with nulls
+// Input: iField - index of field to wipe
+// NOTE: This relies on CSchema::BSetFieldData nulling out the rest of a field when it is set to 0 length!
+//-----------------------------------------------------------------------------
+void CRecordBase::WipeField( int iField )
+{
+ bool bRealloced = false;
+
+ // Empty Data
+ uint32 un = 0;
+
+ // Length should be 0, except for non-variable length strings where length should be 1 (for an empty string "")
+ int cub = 0;
+ Field_t &field = GetPSchema()->GetField( iField );
+ Assert( !field.BIsVariableLength() );
+ if ( field.BIsStringType() )
+ cub = 1;
+
+ GetPSchema()->BSetFieldData( PubRecordFixed(), iField, ( uint8 * ) &un, cub, &bRealloced );
+
+ Assert( !bRealloced );
+}
+
+void CRecordVar::WipeField( int iField )
+{
+ bool bRealloced = false;
+
+ // Empty Data
+ uint32 un = 0;
+
+ // Length should be 0, except for non-variable length strings where length should be 1 (for an empty string "")
+ int cub = 0;
+ Field_t &field = GetPSchema()->GetField( iField );
+ if ( field.BIsStringType() && !field.BIsVariableLength() )
+ cub = 1;
+
+ GetPSchema()->BSetFieldData( PubRecordFixed(), iField, ( uint8 * ) &un, cub, &bRealloced );
+
+ if ( bRealloced )
+ SetFlag( k_EAllocatedVarBlock, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get a string field - will return empty string instead of NULL if field has no datas
+// Input: iField - Field to get
+// pcubField - Receives count of bytes of data (will count the null for strings)
+// Output: const pointer to string data (to an empty string if no data)
+//-----------------------------------------------------------------------------
+const char * CRecordBase::GetStringField( int iField, uint32 *pcubField )
+{
+ uint8 * pubData = NULL;
+ *pcubField = 0;
+
+ if ( BGetField( iField, &pubData, pcubField ) && *pcubField > 0 )
+ return ( const char * ) pubData;
+ else
+ return "";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get an int field
+// Input: iField - Field to get
+// Output: Int (0 if no data)
+//-----------------------------------------------------------------------------
+int CRecordBase::GetInt( int iField )
+{
+ return ( int ) GetUint32( iField );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get a uint16 field
+// Input: iField - Field to get
+// Output: uint16 (0 if no data)
+//-----------------------------------------------------------------------------
+uint16 CRecordBase::GetUint16( int iField )
+{
+ uint8 * pubData = NULL;
+ uint32 cubField = 0;
+
+ DbgVerify( BGetField( iField, &pubData, &cubField ) );
+ Assert( 0 < cubField );
+
+ if ( NULL != pubData )
+ return *( uint16 * ) pubData;
+ else
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get a uint32 field
+// Input: iField - Field to get
+// Output: uint32 (0 if no data)
+//-----------------------------------------------------------------------------
+uint32 CRecordBase::GetUint32( int iField )
+{
+ uint8 * pubData = NULL;
+ uint32 cubField = 0;
+
+ DbgVerify( BGetField( iField, &pubData, &cubField ) );
+ Assert( 0 < cubField );
+
+ if ( NULL != pubData )
+ return *( uint32 * ) pubData;
+ else
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get a uint64 field
+// Input: iField - Field to get
+// Output: uint64 (0 if no data)
+//-----------------------------------------------------------------------------
+uint64 CRecordBase::GetUint64( int iField )
+{
+ uint8 * pubData = NULL;
+ uint32 cubField = 0;
+
+ DbgVerify( BGetField( iField, &pubData, &cubField ) );
+ Assert( 0 < cubField );
+
+ if ( NULL != pubData )
+ return *( uint64 * ) pubData;
+ else
+ return 0;
+}
+
+
+
+const char * CRecordBase::ReadVarCharField( const CVarCharField &field ) const
+{
+ Assert( false );
+ return NULL;
+}
+
+const uint8 * CRecordBase::ReadVarDataField( const CVarField &field, uint32 *pcubField ) const
+{
+ Assert( false );
+ return NULL;
+}
+
+// These may cause a realloc
+bool CRecordBase::SetVarCharField( CVarCharField &field, const char *pchString, bool bTruncate, int32 iField )
+{
+ Assert( false );
+ return false ;
+}
+
+void CRecordBase::SetVarDataField( CVarField &field, const void *pvData, uint32 cubData )
+{
+ Assert( false );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Read data from a varchar field
+// Input: field - opaque field object to read from
+// Output: pointer to data - may be NULL if that field is empty.
+//-----------------------------------------------------------------------------
+const char * CRecordVar::ReadVarCharField( const CVarCharField &field ) const
+{
+ Assert ( GetPSchema()->BHasVariableFields() );
+
+ uint8 *pubData;
+ uint32 cubData;
+ if ( GetPSchema()->BGetVarField( PubRecordFixed(), &field, &pubData, &cubData ) )
+ return (const char *)pubData;
+ else
+ return "";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Read data from a vardata field
+// Input: field - opaque field object to read from
+// Output: pointer to data - may be NULL if that field is empty.
+//-----------------------------------------------------------------------------
+const uint8 *CRecordVar::ReadVarDataField( const CVarField &field, uint32 *pcubField ) const
+{
+ Assert ( GetPSchema()->BHasVariableFields() );
+
+ uint8 *pubData;
+ *pcubField = 0;
+ if ( GetPSchema()->BGetVarField( PubRecordFixed(), &field, &pubData, pcubField ) )
+ return pubData;
+ else
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Update (in memory) a varchar field
+// Input: field - opaque field object to update
+// pchString - string data to set
+//-----------------------------------------------------------------------------
+bool CRecordVar::SetVarCharField( CVarCharField &field, const char *pchString, bool bTruncate, int32 iField )
+{
+ Assert ( GetPSchema()->BHasVariableFields() );
+ if( iField < 0 )
+ {
+ AssertMsg1( false, "Encountered a bad call to SetVarCharField with an invalid field specified: %d", iField );
+ return false;
+ }
+
+ bool bTruncated = false;
+ int cchLen = Q_strlen( pchString ) + 1;
+
+ // since we're a VARCHAR field, cbMaxLength is the length in characters
+ const int cchMaxLength = m_pSchema->GetField( iField ).m_cchMaxLength;
+ if ( ( cchMaxLength > 0 ) && ( cchLen > cchMaxLength ) )
+ {
+ if( bTruncate )
+ {
+ bTruncated = true;
+ cchLen = cchMaxLength;
+ }
+ else
+ {
+ // caller should check his data and not pass stuff that wont fit
+ AssertMsg4( false, "Overflow in SetVarCharField (%u > %u) for column %s in table %s", cchLen, cchMaxLength, m_pSchema->GetField( iField ).m_rgchName, m_pSchema->GetPchName() );
+ return false;
+ }
+ }
+
+ bool bRealloced = false;
+ bool fSuccess = GetPSchema()->BSetVarField( PubRecordFixed(), &field, pchString, cchLen, &bRealloced, BFlagSet( k_EAllocatedVarBlock ) );
+ if( fSuccess && bTruncated )
+ {
+ //make sure the last character is NULL if we truncated
+ VarFieldBlockInfo_t *pBlock = GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() );
+ uint8 *pubVarBlock = pBlock->m_pubBlock;
+ char* pField = ( char* )( pubVarBlock + field.m_dubOffset );
+ pField[ cchLen - 1 ] = '\0';
+ }
+
+ if ( bRealloced )
+ SetFlag( k_EAllocatedVarBlock, true );
+ return fSuccess;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Update (in memory) a vardata field
+// Input: field - opaque field object to update
+// pvData - pointer to data to put there
+// cubData - size in bytes of the data
+//-----------------------------------------------------------------------------
+void CRecordVar::SetVarDataField( CVarField &field, const void *pvData, uint32 cubData )
+{
+ Assert ( GetPSchema()->BHasVariableFields() );
+
+ bool bRealloced = false;
+ GetPSchema()->BSetVarField( PubRecordFixed(), &field, pvData, cubData, &bRealloced, BFlagSet( k_EAllocatedVarBlock ) );
+
+ if ( bRealloced )
+ SetFlag( k_EAllocatedVarBlock, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set or clear the specified flag in m_nFlags
+// Input: eFlag - flag (single bit) to change
+// bSet - Set it, else clear it
+//-----------------------------------------------------------------------------
+void CRecordVar::SetFlag( int eFlag, bool bSet )
+{
+ if ( bSet )
+ m_nFlags |= eFlag;
+ else
+ m_nFlags &= ~eFlag;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the state of the specified flag
+// Input: eFlag - flag (single bit) to check
+//-----------------------------------------------------------------------------
+bool CRecordVar::BFlagSet( int eFlag ) const
+{
+ return 0 != ( m_nFlags & eFlag );
+}
+
+
+#ifdef DBGFLAG_VALIDATE
+//-----------------------------------------------------------------------------
+// Purpose: Run a global validation pass on all of our data structures and memory
+// allocations.
+// Input: validator - Our global validator object
+// pchName - Our name (typically a member var in our container)
+//-----------------------------------------------------------------------------
+void CRecordBase::Validate( CValidator &validator, const char *pchName )
+{
+ VALIDATE_SCOPE();
+}
+
+void CRecordVar::Validate( CValidator &validator, const char *pchName )
+{
+ VALIDATE_SCOPE();
+
+ if ( BFlagSet( k_EAllocatedVarBlock ) )
+ {
+ validator.ClaimMemory( GetPSchema()->PVarFieldBlockInfoFromRecord( PubRecordFixed() )->m_pubBlock );
+ }
+
+}
+
+void CRecordExternal::Validate( CValidator &validator, const char *pchName )
+{
+ if ( BFlagSet( k_EAllocatedFixed ) )
+ {
+ validator.ClaimMemory( m_pubRecordFixedExternal );
+ }
+
+ CRecordBase::Validate( validator, pchName );
+}
+
+void CRecordBase::ValidateStatics( CValidator &validator, const char *pchName )
+{
+ VALIDATE_SCOPE_STATIC( "CRecordBase class statics" );
+}
+
+#endif // DBGFLAG_VALIDATE
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the schema for this record type
+//-----------------------------------------------------------------------------
+CSchema *CRecordType::GetSchema() const
+{
+ return &GSchemaFull().GetSchema( GetITable() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the CRecordInfo for this record type
+//-----------------------------------------------------------------------------
+CRecordInfo *CRecordType::GetRecordInfo() const
+{
+ return GetSchema()->GetRecordInfo();
+}
+
+} // namespace GCSDK
diff --git a/gcsdk/sqlaccess/recordinfo.cpp b/gcsdk/sqlaccess/recordinfo.cpp
new file mode 100644
index 0000000..57fb435
--- /dev/null
+++ b/gcsdk/sqlaccess/recordinfo.cpp
@@ -0,0 +1,915 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+
+#include "stdafx.h"
+
+//#include "sqlaccess.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+namespace GCSDK
+{
+// Memory pool for CRecordInfo
+CThreadSafeClassMemoryPool<CRecordInfo> CRecordInfo::sm_MemPoolRecordInfo( 10, UTLMEMORYPOOL_GROW_FAST );
+
+#ifdef _DEBUG
+// validation tracking
+CUtlRBTree<CRecordInfo *, int > CRecordInfo::sm_mapPMemPoolRecordInfo( DefLessFunc( CRecordInfo *) );
+CThreadMutex CRecordInfo::sm_mutexMemPoolRecordInfo;
+#endif
+
+
+//-----------------------------------------------------------------------------
+// determine if this fieldset is equal to the other one
+//-----------------------------------------------------------------------------
+/* static */
+bool FieldSet_t::CompareFieldSets( const FieldSet_t& refThis, CRecordInfo* pRecordInfoThis,
+ const FieldSet_t& refOther, CRecordInfo* pRecordInfoOther )
+{
+ // same number of columns?
+ int cColumns = refThis.GetCount();
+ if ( refOther.GetCount() != cColumns )
+ return false;
+
+ int cIncludedColumns = refThis.GetIncludedCount();
+ if ( refOther.GetIncludedCount() != cIncludedColumns )
+ return false;
+
+ // do the regular columns first; this is order-dependent
+ for ( int m = 0; m < cColumns; m++ )
+ {
+ int nThisField = refThis.GetField( m );
+ const CColumnInfo& refThisColumn = pRecordInfoThis->GetColumnInfo( nThisField );
+
+ int nOtherField = refOther.GetField( m );
+ const CColumnInfo& refOtherColumn = pRecordInfoOther->GetColumnInfo( nOtherField );
+
+ if ( refOtherColumn != refThisColumn )
+ {
+ return false;
+ }
+ }
+
+ // do the included columns now; order independent
+ for ( int m = 0; m < cIncludedColumns; m++ )
+ {
+ int nThisField = refThis.GetIncludedField( m );
+ const CColumnInfo& refThisColumn = pRecordInfoThis->GetColumnInfo( nThisField );
+ bool bFoundMatch = false;
+
+ for ( int n = 0; n < cIncludedColumns; n++ )
+ {
+ int nOtherField = refOther.GetIncludedField( n );
+ const CColumnInfo& refOtherColumn = pRecordInfoOther->GetColumnInfo( nOtherField );
+
+ if ( refOtherColumn == refThisColumn )
+ {
+ bFoundMatch = true;
+ break;
+ }
+ }
+
+ if ( !bFoundMatch )
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CRecordInfo::CRecordInfo()
+: m_MapIColumnInfo( 0, 0, CaselessStringLessThan )
+{
+ m_rgchName[0] = 0;
+ m_bPreparedForUse = false;
+ m_bAllColumnsAdded = false;
+ m_bHaveChecksum = false;
+ m_bHaveColumnNameIndex = false;
+ m_nHasPrimaryKey = k_EPrimaryKeyTypeNone;
+ m_iPKIndex = -1;
+ m_cubFixedSize = 0;
+ m_nChecksum = 0;
+ m_eSchemaCatalog = k_ESchemaCatalogInvalid;
+ m_nTableID = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes this record info from DS equivalent information
+//-----------------------------------------------------------------------------
+void CRecordInfo::InitFromDSSchema( CSchema *pSchema )
+{
+ // copy the name over
+ SetName( pSchema->GetPchName() );
+
+ // copy each of the fields, preallocating capacity
+ int cFields = pSchema->GetCField();
+ m_VecColumnInfo.EnsureCapacity( cFields );
+ for ( int iField = 0; iField < cFields; iField++ )
+ {
+ Field_t &field = pSchema->GetField( iField );
+ AddColumn( field.m_rgchSQLName, iField+1, field.m_EType, field.m_cubLength, field.m_nColFlags, field.m_cchMaxLength );
+ }
+
+ m_nTableID = pSchema->GetITable();
+
+ // copy the list of PK index fields
+ m_iPKIndex = pSchema->GetPKIndex( );
+
+ // copy the list of Indexes
+ m_VecIndexes = pSchema->GetIndexes( );
+
+ // which schema?
+ m_eSchemaCatalog = pSchema->GetESchemaCatalog();
+
+ // copy full-text column list
+ // and the index of the catalog it will create on
+ m_vecFTSFields = pSchema->GetFTSColumns();
+ m_nFullTextCatalogIndex = pSchema->GetFTSIndexCatalog();
+
+ // Copy over the FK data
+ int cFKs = pSchema->GetFKCount();
+ for ( int i = 0; i < cFKs; ++i )
+ {
+ FKData_t &fkData = pSchema->GetFKData( i );
+ AddFK( fkData );
+ }
+
+ // prepare for use
+ PrepareForUse( );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a new column to this record info
+// Input: pchName - column name
+// nSQLColumn - column index in SQL to bind to (1-based)
+// eType - data type of column
+// cubFixedSize - for fixed-size fields, the size
+// nColFlags - attributes
+//-----------------------------------------------------------------------------
+void CRecordInfo::AddColumn( const char *pchName, int nSQLColumn, EGCSQLType eType, int cubFixedSize, int nColFlags, int cchMaxSize )
+{
+ Assert( !m_bPreparedForUse );
+ if ( m_bPreparedForUse )
+ return;
+ uint32 unColumn = m_VecColumnInfo.AddToTail();
+ CColumnInfo &columnInfo = m_VecColumnInfo[unColumn];
+
+ columnInfo.Set( pchName, nSQLColumn, eType, cubFixedSize, nColFlags, cchMaxSize );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a new FK to this record info
+//-----------------------------------------------------------------------------
+void CRecordInfo::AddFK( const FKData_t &fkData )
+{
+ m_VecFKData.AddToTail( fkData );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: compare function to sort by column name
+//-----------------------------------------------------------------------------
+int __cdecl CompareColumnInfo( const CColumnInfo *pColumnInfoLeft, const CColumnInfo *pColumnInfoRight )
+{
+ const char *pchLeft = ( (CColumnInfo *) pColumnInfoLeft )->GetName();
+ const char *pchRight = ( (CColumnInfo *) pColumnInfoRight )->GetName();
+ Assert( pchLeft && pchLeft[0] );
+ Assert( pchRight && pchRight[0] );
+ return Q_stricmp( pchLeft, pchRight );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: compares this record info to another record info
+//-----------------------------------------------------------------------------
+bool CRecordInfo::EqualTo( CRecordInfo* pOther )
+{
+ int nOurs = GetChecksum();
+ int nTheirs = pOther->GetChecksum();
+
+ // if this much isn't equal, we're no good
+ if (nOurs != nTheirs)
+ return false;
+
+ if ( !CompareIndexLists( pOther ) )
+ return false;
+
+ if ( !CompareFKs( pOther ) )
+ return false;
+
+ return CompareFTSIndexLists( pOther );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: format the index list into a string
+//-----------------------------------------------------------------------------
+void CRecordInfo::GetIndexFieldList( CFmtStr1024 *pstr, int nIndents ) const
+{
+ // table name at first
+ pstr->sprintf( "Table %s:\n", this->GetName() );
+
+ // for each of the indexes ...
+ for ( int n = 0; n < m_VecIndexes.Count(); n++ )
+ {
+ const FieldSet_t& fs = m_VecIndexes[n];
+
+ // indent enough
+ for ( int x = 0; x < nIndents; x++ )
+ {
+ pstr->Append( "\t" );
+ }
+
+ // show if it is clustered or not
+ pstr->AppendFormat( "Index %d (%s): %sclustered, %sunique {", n,
+ fs.GetIndexName(),
+ fs.IsClustered() ? "" : "non-",
+ fs.IsUnique() ? "" : "non-" );
+
+ // then show all the columns
+ for (int m = 0; m < fs.GetCount(); m++ )
+ {
+ int x = fs.GetField( m );
+ const char* pstrName = m_VecColumnInfo[x].GetName();
+ pstr->AppendFormat( "%s %s", ( m == 0 ) ? "" : ",", pstrName );
+ }
+
+ // then the included columns, too
+ for ( int m = 0; m < fs.GetIncludedCount(); m++ )
+ {
+ int x = fs.GetIncludedField( m );
+ const char* pstrName = m_VecColumnInfo[x].GetName();
+ pstr->AppendFormat( ", *%s", pstrName );
+ }
+ pstr->Append( " }\n" );
+ }
+
+ return;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the number of foreign key constraints defined for the table
+//-----------------------------------------------------------------------------
+int CRecordInfo::GetFKCount()
+{
+ return m_VecFKData.Count();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get data for a foreign key by index (valid for 0...GetFKCount()-1)
+//-----------------------------------------------------------------------------
+FKData_t &CRecordInfo::GetFKData( int iIndex )
+{
+ return m_VecFKData[iIndex];
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: format the FK list into a string
+//-----------------------------------------------------------------------------
+void CRecordInfo::GetFKListString( CFmtStr1024 *pstr, int nIndents )
+{
+ // table name at first
+ pstr->sprintf( "Table %s Foreign Keys: \n", this->GetName() );
+
+
+
+ if ( m_VecFKData.Count() == 0 )
+ {
+ // indent enough
+ pstr->AppendIndent( nIndents );
+ pstr->Append( "No foreign keys for table\n" );
+ }
+ else
+ {
+ for ( int n = 0; n < m_VecFKData.Count(); n++ )
+ {
+ // indent enough
+ pstr->AppendIndent( nIndents );
+
+ FKData_t &fkData = m_VecFKData[n];
+ CFmtStr sColumns, sParentColumns;
+ FOR_EACH_VEC( fkData.m_VecColumnRelations, i )
+ {
+ FKColumnRelation_t &colRelation = fkData.m_VecColumnRelations[i];
+ if ( i > 0)
+ {
+ sColumns += ",";
+ sParentColumns += ",";
+ }
+ sColumns += colRelation.m_rgchCol;
+ sParentColumns += colRelation.m_rgchParentCol;
+ }
+
+ pstr->AppendFormat( "CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s) ON DELETE %s ON UPDATE %s\n",
+ fkData.m_rgchName, sColumns.Access(), fkData.m_rgchParentTableName, sParentColumns.Access(),
+ PchNameFromEForeignKeyAction( fkData.m_eOnDeleteAction ), PchNameFromEForeignKeyAction( fkData.m_eOnUpdateAction ) );
+ }
+ }
+
+ return;
+}
+
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CRecordInfo::AddFTSFields( CUtlVector< int > &vecFields )
+{
+ AssertMsg( m_vecFTSFields.Count() == 0, "Only one FTS index per table" );
+ FOR_EACH_VEC( vecFields, n )
+ {
+ int nField = vecFields[n];
+ m_vecFTSFields.AddToTail( nField );
+ }
+
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: compares FK lists in this record with those of another
+//-----------------------------------------------------------------------------
+bool CRecordInfo::CompareFKs( CRecordInfo *pOther )
+{
+ if ( pOther->m_VecFKData.Count() != m_VecFKData.Count() )
+ return false;
+
+ for( int i=0; i < m_VecFKData.Count(); ++i )
+ {
+ FKData_t &fkDataMine = m_VecFKData[i];
+
+ bool bFoundInOther = false;
+ for ( int j=0; j < pOther->m_VecFKData.Count(); ++j )
+ {
+ FKData_t &fkDataOther = pOther->m_VecFKData[j];
+
+ if ( fkDataMine == fkDataOther )
+ {
+ bFoundInOther = true;
+ break;
+ }
+ }
+
+ if ( !bFoundInOther )
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Locate an index by its properties (ignoring the name).
+// Returns position of index in the index array, or -1 if not found.
+//-----------------------------------------------------------------------------
+int CRecordInfo::FindIndex( CRecordInfo *pRec, const FieldSet_t& fieldSet )
+{
+ for ( int i = 0; i < m_VecIndexes.Count(); i++ )
+ {
+ if ( FieldSet_t::CompareFieldSets( m_VecIndexes[i], this, fieldSet, pRec ) )
+ return i;
+ }
+
+ // Not found
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Locate an index with the given name.
+// Returns position of index in the index array, or -1 if not found.
+//-----------------------------------------------------------------------------
+int CRecordInfo::FindIndexByName( const char *pszName ) const
+{
+ for ( int i = 0; i < m_VecIndexes.Count(); i++ )
+ {
+ if ( V_stricmp( m_VecIndexes[i].GetIndexName(), pszName )== 0 )
+ return i;
+ }
+
+ // Not found
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: compares index lists in this record with those of another
+//-----------------------------------------------------------------------------
+bool CRecordInfo::CompareIndexLists( CRecordInfo* pOther )
+{
+ // compare the index lists (but don't use CRCs)
+
+ // different size? can't be the same
+ if ( pOther->GetIndexFieldCount() != GetIndexFieldCount() )
+ {
+ return false;
+ }
+
+ // We have to loop through both lists of indexes and try to find a match.
+ // We also must make sure the match is exact, and that no previous match
+ // can alias another attempt at a match. Pretty messy, but with no available
+ // identity over index objects, we're forced to a suboptimal solution.
+
+ int nIndexes = GetIndexFieldCount();
+
+ // get a copy of the other index vector, which we'll remove items from as
+ // matches are found.
+
+ CUtlVector<FieldSet_t> vecOtherIndexes;
+ vecOtherIndexes.CopyArray( pOther->GetIndexFields().Base(), nIndexes );
+
+ for ( int nOurs = 0; nOurs < nIndexes; nOurs++ )
+ {
+ int nOtherMatchIndex = -1;
+ const FieldSet_t& refOurs = GetIndexFields()[nOurs];
+
+ // rip through copy of other to find one that matches
+ for ( int nOther = 0; nOther < vecOtherIndexes.Count(); nOther++ )
+ {
+ const FieldSet_t& refOther = vecOtherIndexes[nOther];
+ if ( FieldSet_t::CompareFieldSets( refOurs, this, refOther, pOther ) )
+ {
+ nOtherMatchIndex = nOther;
+ break;
+ }
+ }
+
+ if ( nOtherMatchIndex >= 0 )
+ {
+ // this works! remove it from other copy
+ vecOtherIndexes.Remove( nOtherMatchIndex );
+ }
+ else
+ {
+ // something didn't match, so bail out early
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: compares full-text indexes for this record with those of another
+// column order in an FTS is irrelevant, so this is a simple match
+//-----------------------------------------------------------------------------
+bool CRecordInfo::CompareFTSIndexLists( CRecordInfo* pOther ) const
+{
+ // compare full-text index columns
+ if ( m_vecFTSFields.Count() != pOther->m_vecFTSFields.Count() )
+ {
+ // counts don't match, so obviously no good
+ return false;
+ }
+ for ( int nColumnIndex = 0; nColumnIndex < m_vecFTSFields.Count(); nColumnIndex++ )
+ {
+ bool bFound = false;
+ for ( int nInnerIndex = 0; nInnerIndex < pOther->m_vecFTSFields.Count(); nInnerIndex++ )
+ {
+ if ( m_vecFTSFields[nInnerIndex] == pOther->m_vecFTSFields[nColumnIndex] )
+ {
+ bFound = true;
+ break;
+ }
+ }
+
+ if ( !bFound )
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the checksum for this record info
+//-----------------------------------------------------------------------------
+int CRecordInfo::GetChecksum()
+{
+ Assert( m_bPreparedForUse );
+
+ // calculate it now if we haven't already
+ if ( !m_bHaveChecksum )
+ CalculateChecksum();
+
+ return m_nChecksum;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Prepares this object for use after all columns have been added
+//-----------------------------------------------------------------------------
+void CRecordInfo::PrepareForUse()
+{
+ Assert( !m_bPreparedForUse );
+ Assert( 0 == m_cubFixedSize );
+ Assert( 0 == m_nChecksum );
+
+ SetAllColumnsAdded();
+
+ FOR_EACH_VEC( m_VecColumnInfo, nColumn )
+ {
+ CColumnInfo &columnInfo = m_VecColumnInfo[nColumn];
+
+ // keep track of total fixed size of all columns
+ if ( !columnInfo.BIsVariableLength() )
+ m_cubFixedSize += columnInfo.GetFixedSize();
+
+ if ( columnInfo.BIsPrimaryKey() )
+ {
+ // a PK column! if we have seen one before,
+ // know we have a-column PK; otherwise, a single column PK
+ if (m_nHasPrimaryKey == k_EPrimaryKeyTypeNone)
+ m_nHasPrimaryKey = k_EPrimaryKeyTypeSingle;
+ else
+ m_nHasPrimaryKey = k_EPrimaryKeyTypeMulti;
+ }
+ }
+
+ // make sure count matches the enum
+ /*
+ Assert( ( m_nHasPrimaryKey == k_EPrimaryKeyTypeNone && m_VecPKFields.Count() == 0 ) ||
+ ( m_nHasPrimaryKey == k_EPrimaryKeyTypeMulti && m_VecPKFields.Count() > 1) ||
+ ( m_nHasPrimaryKey == k_EPrimaryKeyTypeSingle && m_VecPKFields.Count() == 1) );
+ */
+
+ m_bPreparedForUse = true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns index of column with specified name
+// Input: pchName - column name
+// punColumn - pointer to fill in with index
+// Output: return true if found, false otherwise
+//-----------------------------------------------------------------------------
+bool CRecordInfo::BFindColumnByName( const char *pchName, int *punColumn )
+{
+ Assert( m_bAllColumnsAdded );
+ Assert( pchName && *pchName );
+ Assert( punColumn );
+
+ *punColumn = -1;
+
+ // if we haven't already built the name index, build it now
+ if ( !m_bHaveColumnNameIndex )
+ BuildColumnNameIndex();
+
+ *punColumn = m_MapIColumnInfo.Find( pchName );
+ return ( m_MapIColumnInfo.InvalidIndex() != *punColumn );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the name of this record info
+// Input: pchName - name
+// Notes: record info that describes a table will have a name (the table name);
+// record info that describes a result set will not
+//-----------------------------------------------------------------------------
+void CRecordInfo::SetName( const char *pchName )
+{
+ Assert( pchName && *pchName );
+ Assert( !m_bPreparedForUse ); // don't change this after prepared for use
+ Q_strncpy( m_rgchName, pchName, Q_ARRAYSIZE( m_rgchName ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds the column name index for fast lookup by name
+//-----------------------------------------------------------------------------
+void CRecordInfo::BuildColumnNameIndex()
+{
+ AUTO_LOCK( m_Mutex );
+
+ if ( m_bHaveColumnNameIndex )
+ return;
+
+ Assert( m_bAllColumnsAdded );
+
+ Assert( 0 == m_MapIColumnInfo.Count() );
+
+ FOR_EACH_VEC( m_VecColumnInfo, nColumn )
+ {
+ // build name->column index map
+ CColumnInfo &columnInfo = m_VecColumnInfo[nColumn];
+ m_MapIColumnInfo.Insert( columnInfo.GetName(), nColumn );
+ }
+ m_bHaveColumnNameIndex = true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculates the checksum for this record info
+//-----------------------------------------------------------------------------
+void CRecordInfo::CalculateChecksum()
+{
+ AUTO_LOCK( m_Mutex );
+
+ if ( m_bHaveChecksum )
+ return;
+
+ // build the column name index if necessary
+ if ( !m_bHaveColumnNameIndex )
+ BuildColumnNameIndex();
+
+ CRC32_t crc32;
+ CRC32_Init( &crc32 );
+
+ FOR_EACH_MAP( m_MapIColumnInfo, iMapItem )
+ {
+ uint32 unColumn = m_MapIColumnInfo[iMapItem];
+ CColumnInfo &columnInfo = m_VecColumnInfo[unColumn];
+ // calculate checksum of all of our columns
+ columnInfo.CalculateChecksum();
+ int nChecksum = columnInfo.GetChecksum();
+ CRC32_ProcessBuffer( &crc32, (void*) &nChecksum, sizeof( nChecksum ) );
+ }
+
+ // keep checksum for entire record info
+ CRC32_Final( &crc32 );
+ m_nChecksum = crc32;
+ m_bHaveChecksum = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: add another index disallowing duplicates. If a duplicate item is
+// found, we'll set the flags on the new item from the existing one.
+//-----------------------------------------------------------------------------
+int CRecordInfo::AddIndex( const FieldSet_t& fieldSet )
+{
+ for ( int n = 0; n < m_VecIndexes.Count(); n++ )
+ {
+ FieldSet_t& fs = m_VecIndexes[n];
+ if ( FieldSet_t::CompareFieldSets( fieldSet, this, fs, this ) )
+ {
+ fs.SetClustered( fs.IsClustered() );
+ return -1;
+ }
+ }
+
+ int nRet = m_VecIndexes.AddToTail( fieldSet );
+ return nRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if there is an IDENTITY column in the record info
+//-----------------------------------------------------------------------------
+bool CRecordInfo::BHasIdentity() const
+{
+ FOR_EACH_VEC( m_VecColumnInfo, nColumn)
+ {
+ if( m_VecColumnInfo[nColumn].BIsAutoIncrement() )
+ return true;
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CColumnInfo::CColumnInfo()
+{
+ m_rgchName[0] = 0;
+ m_nSQLColumn = 0;
+ m_eType = k_EGCSQLTypeInvalid;
+ m_nColFlags = 0;
+ m_cubFixedSize = 0;
+ m_cchMaxSize = 0;
+ m_nChecksum = 0;
+ m_bHaveChecksum = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets column info for this column
+// Input: pchName - column name
+// nSQLColumn - column index in SQL to bind to (1-based)
+// eType - data type of column
+// cubFixedSize - for fixed-size fields, the size
+// nColFlags - attributes
+//-----------------------------------------------------------------------------
+void CColumnInfo::Set( const char *pchName, int nSQLColumn, EGCSQLType eType, int cubFixedSize, int nColFlags, int cchMaxSize )
+{
+ Assert( !m_rgchName[0] );
+ Q_strncpy( m_rgchName, pchName, Q_ARRAYSIZE( m_rgchName ) );
+ m_nSQLColumn = nSQLColumn;
+ m_eType = eType;
+
+ m_nColFlags = nColFlags;
+ ValidateColFlags();
+
+ if ( !BIsVariableLength() )
+ {
+ Assert( cubFixedSize > 0 );
+ m_cubFixedSize = cubFixedSize;
+ m_cchMaxSize = 0;
+ }
+ else
+ {
+ // it's variable length, so we need a max length
+ m_cchMaxSize = cchMaxSize;
+ m_cubFixedSize = 0;
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: returns whether this column is variable length
+//-----------------------------------------------------------------------------
+bool CColumnInfo::BIsVariableLength() const
+{
+ return m_eType == k_EGCSQLType_Blob || m_eType == k_EGCSQLType_String || m_eType == k_EGCSQLType_Image;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: convert column flags to a visible representation
+//-----------------------------------------------------------------------------
+void CColumnInfo::GetColFlagDescription( char* pstrOut, int cubOutLength ) const
+{
+ if ( m_nColFlags == 0 )
+ Q_strncpy( pstrOut, "(none)", cubOutLength );
+ else
+ {
+ pstrOut[0] = 0;
+ if ( m_nColFlags & k_nColFlagIndexed )
+ Q_strncat( pstrOut, "(Indexed)", cubOutLength );
+ if ( m_nColFlags & k_nColFlagUnique )
+ Q_strncat( pstrOut, "(Unique)", cubOutLength );
+ if ( m_nColFlags & k_nColFlagPrimaryKey )
+ Q_strncat( pstrOut, "(PrimaryKey)", cubOutLength );
+ if ( m_nColFlags & k_nColFlagAutoIncrement )
+ Q_strncat( pstrOut, "(AutoIncrement)", cubOutLength );
+ if ( m_nColFlags & k_nColFlagClustered )
+ Q_strncat( pstrOut, "(Clustered)", cubOutLength );
+ }
+
+ return;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: sets column flag bits
+// Input: nColFlag - bits to set. (Other bits are not cleared.)
+//-----------------------------------------------------------------------------
+void CColumnInfo::SetColFlagBits( int nColFlag )
+{
+ ValidateColFlags();
+ m_nColFlags |= nColFlag; // set these bits
+ ValidateColFlags();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculates the checksum for this column
+//-----------------------------------------------------------------------------
+void CColumnInfo::CalculateChecksum()
+{
+ if ( m_bHaveChecksum )
+ return;
+
+ // calculate checksum of this column for easy comparsion
+ CRC32_t crc32;
+ CRC32_Init( &crc32 );
+ CRC32_ProcessBuffer( &crc32, (void*) m_rgchName, Q_strlen( m_rgchName ) );
+ CRC32_ProcessBuffer( &crc32, (void*) &m_nColFlags, sizeof( m_nColFlags ) );
+ CRC32_ProcessBuffer( &crc32, (void*) &m_eType, sizeof( m_eType ) );
+ CRC32_ProcessBuffer( &crc32, (void*) &m_cubFixedSize, sizeof( m_cubFixedSize ) );
+ CRC32_ProcessBuffer( &crc32, (void*) &m_cchMaxSize, sizeof( m_cchMaxSize ) );
+ CRC32_Final( &crc32 );
+
+ m_nChecksum = crc32;
+ m_bHaveChecksum = true;
+}
+
+//-----------------------------------------------------------------------------
+// determine if this CColumnInfo is the same as the referenced
+//-----------------------------------------------------------------------------
+bool CColumnInfo::operator==( const CColumnInfo& refOther ) const
+{
+ if ( m_eType != refOther.m_eType )
+ return false;
+ if ( m_cubFixedSize != refOther.m_cubFixedSize )
+ return false;
+ if ( m_cchMaxSize != refOther.m_cchMaxSize )
+ return false;
+ if ( m_nColFlags != refOther.m_nColFlags )
+ return false;
+ if ( 0 != Q_strcmp( m_rgchName, refOther.m_rgchName ) )
+ return false;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Validates that column flags are set in valid combinations
+//-----------------------------------------------------------------------------
+void CColumnInfo::ValidateColFlags() const
+{
+ // Check that column flags follow rules about how columns get expressed in SQL
+
+ if ( m_nColFlags & k_nColFlagPrimaryKey )
+ {
+ // a primary key must also be unique and indexed
+ Assert( m_nColFlags & k_nColFlagUnique );
+ Assert( m_nColFlags & k_nColFlagIndexed );
+ }
+
+ // a column with uniqueness constraint must also be indexed
+ if ( m_nColFlags & k_nColFlagUnique )
+ Assert( m_nColFlags & k_nColFlagIndexed );
+}
+
+
+CRecordInfo *CRecordInfo::Alloc()
+{
+ CRecordInfo *pRecordInfo = sm_MemPoolRecordInfo.Alloc();
+
+#ifdef _DEBUG
+ AUTO_LOCK( sm_mutexMemPoolRecordInfo );
+ sm_mapPMemPoolRecordInfo.Insert( pRecordInfo );
+#endif
+
+ return pRecordInfo;
+}
+
+
+void CRecordInfo::DestroyThis()
+{
+#ifdef _DEBUG
+ AUTO_LOCK( sm_mutexMemPoolRecordInfo );
+ sm_mapPMemPoolRecordInfo.Remove( this );
+#endif
+
+ sm_MemPoolRecordInfo.Free( this );
+}
+
+
+#ifdef DBGFLAG_VALIDATE
+
+
+void CRecordInfo::ValidateStatics( CValidator &validator, const char *pchName )
+{
+ VALIDATE_SCOPE_STATIC( "CRecordInfo class statics" );
+
+ ValidateObj( sm_MemPoolRecordInfo );
+
+#ifdef _DEBUG
+ AUTO_LOCK( sm_mutexMemPoolRecordInfo );
+ ValidateObj( sm_mapPMemPoolRecordInfo );
+ FOR_EACH_MAP_FAST( sm_mapPMemPoolRecordInfo, i )
+ {
+ sm_mapPMemPoolRecordInfo[i]->Validate( validator, "sm_mapPMemPoolRecordInfo[i]" );
+ }
+#endif
+}
+
+
+void CRecordInfo::Validate( CValidator &validator, const char *pchName )
+{
+ VALIDATE_SCOPE();
+
+ m_VecIndexes.Validate( validator, "m_VecIndexes" );
+
+ ValidateObj( m_VecFKData );
+ FOR_EACH_VEC( m_VecFKData, i )
+ {
+ ValidateObj( m_VecFKData[i] );
+ }
+
+ for ( int iIndex = 0; iIndex < m_VecIndexes.Count(); iIndex++ )
+ {
+ ValidateObj( m_VecIndexes[iIndex] );
+ }
+ ValidateObj( m_vecFTSFields );
+
+ ValidateObj( m_VecColumnInfo );
+ FOR_EACH_VEC( m_VecColumnInfo, nColumn )
+ {
+ CColumnInfo &columnInfo = GetColumnInfo( nColumn );
+ ValidateObj( columnInfo );
+ }
+ ValidateObj( m_MapIColumnInfo );
+}
+
+void CColumnInfo::Validate( CValidator &validator, const char *pchName )
+{
+ VALIDATE_SCOPE();
+
+}
+
+#endif // DBGFLAG_VALIDATE
+
+} // namespace GCSDK
diff --git a/gcsdk/sqlaccess/schema.cpp b/gcsdk/sqlaccess/schema.cpp
new file mode 100644
index 0000000..e92be8e
--- /dev/null
+++ b/gcsdk/sqlaccess/schema.cpp
@@ -0,0 +1,1505 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+
+#include "stdafx.h"
+//#include "sqlaccess/sqlaccess.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+namespace GCSDK
+{
+#ifndef STEAM
+bool isspace( char ch )
+{
+ return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
+}
+
+int Q_strnlen( const char *str, int count )
+{
+ // can't make more meaningful checks, because this routine is used itself
+ // to check the NUL-terminatedness of strings
+ if ( !str || count < 0 )
+ return -1;
+
+ for ( const char *pch = str; pch < str + count; pch++ )
+ {
+ if ( *pch == '\0' )
+ return pch - str;
+ }
+
+ return -1;
+}
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose: convert an ESchemaCatalog into a string for diagnostics and logging.
+// this can't be in enum_names because of data type dependencies.
+//-----------------------------------------------------------------------------
+const char* PchNameFromESchemaCatalog( ESchemaCatalog e )
+{
+ switch (e)
+ {
+ case k_ESchemaCatalogInvalid:
+ return "k_ESchemaCatalogInvalid";
+ break;
+
+ case k_ESchemaCatalogMain:
+ return "k_ESchemaCatalogMain";
+ break;
+ }
+
+ AssertMsg1( false, "unknown ESchemaCatalog (%d)", e );
+ return "Unknown";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CSchema::CSchema()
+{
+ m_iTable = -1;
+ m_rgchName[0] = 0;
+ m_cubRecord = 0;
+ m_bTestTable = false;
+ m_cRecordMax = 0;
+ m_bHasVarFields = false;
+ m_nHasPrimaryKey = k_EPrimaryKeyTypeNone;
+ m_iPKIndex = -1;
+ m_pRecordInfo = NULL;
+ m_wipePolicy = k_EWipePolicyPreserveAlways;
+ m_bAllowWipeInProd = false;
+ m_bPrepopulatedTable = false;
+ m_nFullTextIndexCatalog = -1;
+ m_eSchemaCatalog = k_ESchemaCatalogInvalid;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CSchema::~CSchema()
+{
+ SAFE_RELEASE( m_pRecordInfo );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculates offset of each field within a record structure, and the
+// maximum length of a record structure.
+//-----------------------------------------------------------------------------
+void CSchema::CalcOffsets()
+{
+ int dubOffsetCur = 0;
+
+ for ( int iField = 0; iField < m_VecField.Count(); iField++ )
+ {
+ m_VecField[iField].m_dubOffset = dubOffsetCur;
+ dubOffsetCur += m_VecField[iField].m_cubLength;
+
+ if ( m_VecField[iField].BIsVariableLength() )
+ m_bHasVarFields = true;
+ }
+
+ m_cubRecord = dubOffsetCur;
+
+ if ( m_bHasVarFields )
+ m_cubRecord += sizeof( VarFieldBlockInfo_t );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: called to make final calculations when all fields/indexes/etc have
+// been added and the schema is ready to be used
+//-----------------------------------------------------------------------------
+void CSchema::PrepareForUse()
+{
+ // Create a record description (new form of schema information, for SQL) that corresponds to this schema object.
+ // This contains essentially the information as the CSchema object, we keep both for the moment to bridge the DS and SQL worlds.
+ Assert( !m_pRecordInfo );
+ SAFE_RELEASE( m_pRecordInfo );
+ m_pRecordInfo = CRecordInfo::Alloc();
+ m_pRecordInfo->InitFromDSSchema( this );
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: For a record that has variable-length fields, gets the info block from
+// the tail end
+// Input : pvRecord - Record data
+//-----------------------------------------------------------------------------
+VarFieldBlockInfo_t* CSchema::PVarFieldBlockInfoFromRecord( const void *pvRecord ) const
+{
+ if ( !m_bHasVarFields )
+ return NULL;
+
+ uint8 *pubRecord = ( uint8* )pvRecord;
+ return ( VarFieldBlockInfo_t * )( pubRecord + m_cubRecord ) - 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the total size of the variable-length block for this record
+// For records that have no variable-length fields, it will return zero
+// Input : pvRecord - Record data
+//-----------------------------------------------------------------------------
+uint32 CSchema::CubRecordVariable( const void *pvRecord ) const
+{
+ VarFieldBlockInfo_t *pVarFieldsBlockInfo = PVarFieldBlockInfoFromRecord( pvRecord );
+ if ( pVarFieldsBlockInfo )
+ return pVarFieldsBlockInfo->m_cubBlock;
+ else
+ return 0;
+}
+
+
+
+void CSchema::RenderField( uint8 *pubRecord, int iField, int cchBuffer, char *pchBuffer )
+{
+ Field_t *pField = &m_VecField[iField];
+
+ uint8 *pubData;
+ uint32 cubData;
+ char chEmpty = 0;
+
+ if ( pField->BIsVariableLength() )
+ {
+ if ( !BGetVarField( pubRecord, ( VarField_t * )( pubRecord + pField->m_dubOffset ), &pubData, &cubData ) )
+ {
+ // just render a single byte
+ pubData = ( uint8* )&chEmpty;
+ cubData = 1;
+ }
+ }
+ else
+ {
+ pubData = pubRecord + pField->m_dubOffset;
+ cubData = m_VecField[iField].m_cubLength;
+ }
+
+ ConvertFieldToText( pField->m_EType, pubData, cubData, pchBuffer, cchBuffer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Renders a text version of a record to the console.
+// Input : pubRecord - Location of the record data
+//-----------------------------------------------------------------------------
+void CSchema::RenderRecord( uint8 *pubRecord )
+{
+ char rgchT[k_cMedBuff];
+
+ // First the header
+ EmitInfo( SPEW_CONSOLE, 2, 2, "%d\t*** Record header: # of lines ***\n", 1 + m_VecField.Count() );
+
+ // Render each field in turn
+ for ( int iField = 0; iField < m_VecField.Count(); iField++ )
+ {
+ Field_t *pField = &m_VecField[iField];
+
+ RenderField( pubRecord, iField, Q_ARRAYSIZE(rgchT), rgchT );
+
+ EmitInfo( SPEW_CONSOLE, 2, 2, "\t%s\t\t// Field %d (%s)\n", rgchT, iField, pField->m_rgchName );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get data and size of a field, whether fixed or variable length
+// Input: pvRecord - Fixed-length part of record
+// iField - index of field to get
+// ppubField - receives pointer to field data
+// pcubField - receives size in bytes of that data
+// Output: true if successful
+//-----------------------------------------------------------------------------
+bool CSchema::BGetFieldData( const void *pvRecord, int iField, uint8 **ppubField, uint32 *pcubField ) const
+{
+ *ppubField = NULL;
+ *pcubField = 0;
+ const Field_t &field = m_VecField[iField];
+ if ( field.BIsVariableLength() )
+ {
+ return BGetVarField( pvRecord, ( VarField_t * )( ( uint8 * ) pvRecord + field.m_dubOffset ), ppubField, pcubField );
+ }
+ else
+ {
+ *ppubField = ( ( uint8 * ) pvRecord + field.m_dubOffset );
+ *pcubField = field.CubFieldUpdateSize();
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set data and size of a field, whether fixed or variable length
+// Input: pvRecord - Fixed-length part of record
+// iField - index of field to set
+// pubField - pointer to field data to copy from
+// cubField - size in bytes of that data
+// Output: true if successful
+//-----------------------------------------------------------------------------
+bool CSchema::BSetFieldData( void *pvRecord, int iField, uint8 *pubField, uint32 cubField, bool *pbVarBlockRealloced )
+{
+ *pbVarBlockRealloced = false;
+
+ Field_t &field = m_VecField[iField];
+
+ if ( field.BIsVariableLength() )
+ {
+ return BSetVarField( pvRecord, ( VarField_t * )( ( uint8 * ) pvRecord + field.m_dubOffset ), pubField, cubField, pbVarBlockRealloced, /*bFreeOnRealloc*/ true );
+ }
+ else // fixed length
+ {
+ uint8 *pubFieldWrite = ( ( uint8 * ) pvRecord + field.m_dubOffset );
+
+ // Must fit in field and last byte for strings MUST be NULL
+ if ( cubField > field.m_cubLength ||
+ ( k_EGCSQLType_String == field.m_EType && Q_strnlen( reinterpret_cast< char* >( pubField ), cubField ) == -1 ) )
+ {
+ Assert( false );
+ return false;
+ }
+
+ if ( k_EGCSQLType_Blob != field.m_EType && k_EGCSQLType_Image != field.m_EType )
+ {
+ // Copy the data (string or binary, doesn't matter)
+ if ( cubField > 0 )
+ Q_memcpy( pubFieldWrite, pubField, cubField );
+
+ // Null termination and overwrite any old data
+ if ( cubField < field.m_cubLength )
+ Q_memset( pubFieldWrite + cubField, 0, field.m_cubLength - cubField );
+ }
+ else
+ {
+ // Only support fixed char or binary
+ Assert( false );
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get data from a variable-length field
+// Input: pvRecord - Fixed-length part of record
+// pVarField - fixed part of field in that record
+// ppubField - receives pointer to field data
+// pcubField - receives size in bytes of that data
+//-----------------------------------------------------------------------------
+bool CSchema::BGetVarField( const void *pvRecord, const VarField_t *pVarField, uint8 **ppubField, uint32 *pcubField ) const
+{
+ Assert( m_bHasVarFields );
+
+ *ppubField = 0;
+ *pcubField = 0;
+
+ VarFieldBlockInfo_t *pVarFieldBlockInfo = PVarFieldBlockInfoFromRecord( pvRecord );
+
+ if ( !pVarField->m_cubField )
+ {
+ *pcubField = 0;
+ *ppubField = NULL;
+ return true;
+ }
+
+ if ( pVarFieldBlockInfo->m_cubBlock )
+ {
+ uint8 *pubVarBlock = pVarFieldBlockInfo->m_pubBlock;
+
+ *ppubField = pubVarBlock + pVarField->m_dubOffset;
+ *pcubField = pVarField->m_cubField;
+
+ // Sanity check
+ Assert( *pcubField <= k_cubVarFieldMax );
+
+ return true;
+ }
+ else
+ {
+ // Should never happen
+ Assert( false );
+ return false;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Update a variable-length field in a record (may realloc the var block)
+// Input: pvRecord - Fixed-length part of record
+// pVarField - fixed part of field in that record
+// pvData - data to place in the field
+// cubData - size of that data
+// pbRealloced - set to indicate if the var block was realloced
+// bFreeOnRealloc - If we have to grow or shrink the var block, should we free the old memory?
+// Usually true, unless it lives in a NetPacket or something like that
+//-----------------------------------------------------------------------------
+bool CSchema::BSetVarField( void *pvRecord, VarField_t *pVarField, const void *pvData, uint32 cubData, bool *pbRealloced, bool bFreeOnRealloc )
+{
+ VarFieldBlockInfo_t *pVarFieldBlockInfo = PVarFieldBlockInfoFromRecord( pvRecord );
+ *pbRealloced = false;
+
+
+ if ( cubData > k_cubVarFieldMax )
+ {
+ // field size is too big
+ Assert( false );
+ return false;
+ }
+
+ // if no block exists, allocate and copy into it
+ if ( pVarFieldBlockInfo->m_cubBlock == 0 )
+ {
+ // Nothing to do?
+ if ( !cubData )
+ return true;
+
+ // create it
+ void *pvBlock = PvAlloc( cubData );
+ *pbRealloced = true;
+
+ // copy the data
+ Q_memcpy( pvBlock, pvData, cubData );
+
+ // set the record's structure to point at our new data
+ pVarFieldBlockInfo->m_cubBlock = cubData;
+ pVarFieldBlockInfo->m_pubBlock = ( uint8 * )pvBlock;
+
+ // set the field to point at its landing place
+ pVarField->m_cubField = cubData;
+ pVarField->m_dubOffset = 0;
+ }
+ else
+ {
+ // there is some block available.
+
+ // is this field changing size?
+ if ( cubData != 0 && ( cubData == pVarField->m_cubField ) )
+ {
+ // no size change - no need to reallocate anything
+ Q_memcpy( pVarFieldBlockInfo->m_pubBlock + pVarField->m_dubOffset, pvData, cubData );
+ }
+ else
+ {
+ // size change - realloc
+ *pbRealloced = true;
+ uint32 cubFieldOld = pVarField->m_cubField;
+ uint32 dubOffsetOld = pVarField->m_dubOffset;
+ uint32 cubBlockNew = pVarFieldBlockInfo->m_cubBlock - pVarField->m_cubField + cubData;
+
+ if ( ( cubBlockNew + m_cubRecord ) > k_cubRecordMax )
+ {
+ // total record size is too big
+ Assert( false );
+ return false;
+ }
+
+ void *pvBlockNew = NULL;
+
+ if ( cubBlockNew )
+ {
+ // if this field has never been placed in the block (that is, it's not changing an old value)
+ // and we have enough space, fastest to put it at the end in the free space.
+ if ( pVarField->m_cubField == 0 && pVarField->m_dubOffset == 0 && cubData <= pVarFieldBlockInfo->m_cubBlockFree )
+ {
+ uint8 *pubLastUsed = pVarFieldBlockInfo->m_pubBlock;
+ for ( int iField = 0; iField < m_VecField.Count(); ++iField )
+ {
+ Field_t &field = m_VecField[iField];
+ if ( field.BIsVariableLength() )
+ {
+ // Needs updating
+ VarField_t *pVarFieldCur = ( VarField_t * )( ( uint8 * )pvRecord + field.m_dubOffset );
+ pubLastUsed += pVarFieldCur->m_cubField;
+ }
+ }
+
+ // copy it there
+ Q_memcpy( pubLastUsed, pvData, cubData );
+
+ // set up the field
+ pVarField->m_cubField = cubData;
+ pVarField->m_dubOffset = static_cast<int>( (pubLastUsed - pVarFieldBlockInfo->m_pubBlock) );
+
+ // note that we used some up
+ pVarFieldBlockInfo->m_cubBlockFree -= cubData;
+ }
+ else
+ {
+ // yes ... rellocate
+ pvBlockNew = PvAlloc( cubBlockNew );
+
+ uint8 *pubBlockOldCursor = pVarFieldBlockInfo->m_pubBlock;
+ uint8 *pubBlockNewCursor = ( uint8* )pvBlockNew;
+
+ // copy data, skipping over this field (will put at end)
+ while ( pubBlockOldCursor < ( pVarFieldBlockInfo->m_pubBlock + pVarFieldBlockInfo->m_cubBlock ) )
+ {
+ if ( pVarField->m_cubField && ( (int)pVarField->m_dubOffset == ( pubBlockOldCursor - pVarFieldBlockInfo->m_pubBlock ) ) )
+ {
+ pubBlockOldCursor += pVarField->m_cubField;
+ }
+ else
+ {
+ *pubBlockNewCursor++ = *pubBlockOldCursor++;
+ }
+ }
+
+ // put this field data at the end
+ Q_memcpy( pubBlockNewCursor, pvData, cubData );
+
+ // free the old block
+ if ( bFreeOnRealloc )
+ FreePv( pVarFieldBlockInfo->m_pubBlock );
+
+ // Update the block info
+ pVarFieldBlockInfo->m_cubBlock = cubBlockNew;
+ pVarFieldBlockInfo->m_pubBlock = ( uint8 * )pvBlockNew;
+ pVarFieldBlockInfo->m_cubBlockFree = 0;
+
+ // update this field
+ pVarField->m_cubField = cubData;
+ if ( cubData > 0 )
+ pVarField->m_dubOffset = static_cast<int>( pubBlockNewCursor - pVarFieldBlockInfo->m_pubBlock );
+ else
+ pVarField->m_dubOffset = 0;
+
+ // update other fields
+ for ( int iField = 0; iField < m_VecField.Count(); ++iField )
+ {
+ Field_t &field = m_VecField[iField];
+ if ( field.BIsVariableLength() )
+ {
+ // Needs updating
+ VarField_t *pVarFieldCur = ( VarField_t * )( ( uint8 * )pvRecord + field.m_dubOffset );
+
+ // Except the one we just changed
+ if ( pVarFieldCur == pVarField )
+ continue;
+
+ // And empty fields
+ if ( !pVarFieldCur->m_cubField )
+ continue;
+
+ if ( pVarFieldCur->m_dubOffset > dubOffsetOld )
+ pVarFieldCur->m_dubOffset -= cubFieldOld;
+ }
+ }
+ }
+ }
+ else
+ {
+ // all of the variable data is gone, so
+ // free the old block
+ if ( bFreeOnRealloc )
+ FreePv( pVarFieldBlockInfo->m_pubBlock );
+
+ // ... update the block info
+ pVarFieldBlockInfo->m_cubBlock = 0;
+ pVarFieldBlockInfo->m_pubBlock = NULL;
+ pVarFieldBlockInfo->m_cubBlockFree = 0;
+
+ // ... and update this field
+ pVarField->m_dubOffset = 0;
+ pVarField->m_cubField = cubData;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: If this is a variable-length record, and we just read it from
+// a stream, then the var block is after the fixed-length part of the
+// record. So, we need to update the record's pointer to reflect that
+// Input: pvRecord - Beginning of the serialized record
+//-----------------------------------------------------------------------------
+void CSchema::FixupDeserializedRecord( void *pvRecord )
+{
+ // Nothing to do if not a variable record
+ if ( !BHasVariableFields() )
+ return;
+
+ uint8 *pubRecord = ( uint8 * )pvRecord;
+ VarFieldBlockInfo_t *pVarBlockInfo = PVarFieldBlockInfoFromRecord( pvRecord );
+ pVarBlockInfo->m_pubBlock = pubRecord + m_cubRecord;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes a new record with random data.
+// Input: pubRecord - The record's in-memory data that we'll fill out
+// unPrimaryIndex - Primary index of the record
+// pbRealloced - Set to 'true' if the var-fields block for this record was reallocated
+// (or allocated for the first time)
+// bFreeOnRealloc - If true, we should Free() the var block after reallocating a new block
+// (should be false if that block lives inside a NetPacket)
+//-----------------------------------------------------------------------------
+void CSchema::InitRecordRandom( uint8 *pubRecord, uint32 unPrimaryIndex, bool *pbVarBlockRealloced, bool bFreeVarBlockOnRealloc )
+{
+ // Fill out each field in turn
+ for ( int iField = 0; iField < m_VecField.Count(); iField++ )
+ {
+ bool bRealloced = false;
+ SetFieldRandom( pubRecord, iField, &bRealloced, bFreeVarBlockOnRealloc );
+ if ( bRealloced )
+ {
+ *pbVarBlockRealloced = true;
+ // if we just allocated it, we can free it next time we need to
+ bFreeVarBlockOnRealloc = true;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets a single field of a record to a random value.
+// Input: pubRecord - Where the record lives in memory
+// iField - Which field to set
+// pbRealloced - Set to 'true' if the var-fields block for this record was reallocated
+// (or allocated for the first time)
+// bFreeOnRealloc - If true, we should Free() the var block after reallocating a new block
+// (should be false if that block lives inside a NetPacket)
+//-----------------------------------------------------------------------------
+void CSchema::SetFieldRandom( uint8 *pubRecord, int iField, bool *pbVarBlockRealloced, bool bFreeVarBlockOnRealloc )
+{
+ Field_t &field = m_VecField[iField];
+ *pbVarBlockRealloced = false;
+
+ // Strings get random text
+ if ( k_EGCSQLType_String == field.m_EType )
+ {
+ // Generate up to cubLength - 1 chars
+ uint32 cch = UNRandFast() % field.m_cubLength;
+ uint32 ich = 0;
+ for ( ; ich < cch; ich++ )
+ *( ( char * ) pubRecord + field.m_dubOffset + ich ) = CHRandFast();
+
+ // Null termination
+ for ( ; ich < field.m_cubLength; ich++ )
+ *( ( char * ) pubRecord + field.m_dubOffset + ich ) = 0;
+ }
+ else if ( field.BIsVariableLength() )
+ {
+ // Need temp buffer to randomize before setting
+ // kept reasonably small to prevent spamming the console
+ uint8 rgubBuff[512];
+ uint32 cubData = UNRandFast() % sizeof(rgubBuff);
+
+ // For strings, put in (cubData-1) random characters (each randomly in the range [32,126])
+ // then a trailing NULL
+ if ( field.BIsStringType() )
+ {
+ uint32 ich = 0;
+ for ( ; ich < (cubData-1); ich++ )
+ rgubBuff[ich] = CHRandFast();
+
+ rgubBuff[ich] = 0;
+ }
+ else
+ {
+ // binary - just fill in random bytes
+ for ( uint32 iub = 0; iub < cubData; iub++ )
+ {
+ rgubBuff[iub] = ( uint8 )( UNRandFast() % 256 );
+ }
+ }
+
+ // Set the variable field in the record
+ BSetVarField( pubRecord, ( VarField_t * ) ( pubRecord + field.m_dubOffset ), rgubBuff, cubData, pbVarBlockRealloced, bFreeVarBlockOnRealloc );
+ }
+ // Binaries are filled with completely random bytes
+ else
+ {
+ for ( uint32 iub = 0; iub < field.m_cubLength; iub++ )
+ {
+ *( pubRecord + field.m_dubOffset + iub ) = ( uint8 ) ( UNRandFast() % 256 );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns checksum of our contents
+// Output : checksum
+//-----------------------------------------------------------------------------
+uint32 CSchema::CalcChecksum()
+{
+ CRC32_t crc32;
+ CRC32_Init( &crc32 );
+
+ FOR_EACH_VEC( m_VecField, nField )
+ {
+ CRC32_ProcessBuffer( &crc32, &m_VecField[nField], sizeof( m_VecField[nField] ) );
+ }
+
+ // keep checksum for entire record info
+ CRC32_Final( &crc32 );
+
+ return (uint32)crc32;
+}
+
+
+void CSchema::AddIntField( char *pchName, char *pchSQLName, EGCSQLType eType, int cubSize )
+{
+ int nExpectedSize = -1;
+ switch ( eType )
+ {
+ case k_EGCSQLType_int8:
+ nExpectedSize = 1;
+ break;
+
+ case k_EGCSQLType_int16:
+ nExpectedSize = 2;
+ break;
+
+ case k_EGCSQLType_int32:
+ case k_EGCSQLType_float:
+ nExpectedSize = 4;
+ break;
+
+ case k_EGCSQLType_int64:
+ case k_EGCSQLType_double:
+ nExpectedSize = 8;
+ break;
+ }
+
+ AssertMsg2( nExpectedSize != -1, "Unexpected EType in AddIntField: %d for column %s", eType, pchSQLName );
+ AssertMsg3( nExpectedSize == cubSize, "Unexpected size for in AddIntField for column %s: %d doesn't match %d ", pchSQLName, nExpectedSize, cubSize );
+
+ AddField( pchName, pchSQLName, eType, cubSize, 0 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a field from our intrinsic schema to this schema.
+// Input: pchName - Name of the field
+// pchSQLName - Name of the field in SQL database
+// eType - Type of the field
+// cubSize - Size of the field
+// pfnCompare - Function used to compare fields (NULL if none specified)
+//-----------------------------------------------------------------------------
+void CSchema::AddField( char *pchName, char *pchSQLName, EGCSQLType eType, uint32 cubSize, int cchMaxLength )
+{
+ int iFieldNew = m_VecField.AddToTail();
+ Field_t &field = m_VecField[iFieldNew];
+
+ field.m_EType = eType;
+ field.m_cubLength = cubSize;
+ field.m_nColFlags = 0;
+ Q_strncpy( field.m_rgchName, pchName, Q_ARRAYSIZE( field.m_rgchName ) );
+ Q_strncpy( field.m_rgchSQLName, pchSQLName, Q_ARRAYSIZE( field.m_rgchSQLName ) );
+ field.m_cchMaxLength = cchMaxLength;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a given field as the primary index
+// Input:
+// bClustered - true to create a clustered index
+// pchName - Name of the field to make primary index
+//-----------------------------------------------------------------------------
+int CSchema::PrimaryKey( bool bClustered, int nFillFactor, const char *pchName )
+{
+ int iField = FindIField( pchName );
+ AssertFatalMsg2( k_iFieldNil != iField, "Could not find index column \"%s\" on table \"%s\"", pchName, m_rgchName );
+
+ Assert( m_nHasPrimaryKey == k_EPrimaryKeyTypeNone ); // may not already have a primary key defined
+ Assert( m_iPKIndex == k_iFieldNil );
+
+ // must not have been indexed in any way before
+ Assert( 0 == ( m_VecField[ iField ].m_nColFlags & ( k_nColFlagPrimaryKey | k_nColFlagIndexed | k_nColFlagUnique | k_nColFlagClustered ) ) );
+
+ // note that we have a single-column PK
+ m_nHasPrimaryKey = k_EPrimaryKeyTypeSingle;
+
+ // set our flags
+ m_VecField[ iField ].m_nColFlags |= ( k_nColFlagPrimaryKey | k_nColFlagIndexed | k_nColFlagUnique );
+ if ( bClustered )
+ m_VecField[ iField ].m_nColFlags |= k_nColFlagClustered;
+
+ // add to list of primary keys and remember the indexID
+ CUtlVector<int> vecColumns;
+ vecColumns.AddToTail( iField );
+
+ FieldSet_t vecIndex( true /* unique */ , bClustered, vecColumns, NULL );
+ vecIndex.SetFillFactor( nFillFactor );
+ m_iPKIndex = m_VecIndexes.AddToTail( vecIndex );
+
+ return m_iPKIndex;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a set of fields as the primary index
+// Input:
+// bClustered - clustered index created if true
+// nFillFactor - fill facto to use; 0 is database default
+// pchName - Name of the field to make primary index
+//-----------------------------------------------------------------------------
+int CSchema::PrimaryKeys( bool bClustered, int nFillFactor, const char *pchNames )
+{
+ Assert( pchNames != NULL ); // no bogus parameters, please
+ Assert( m_nHasPrimaryKey == k_EPrimaryKeyTypeNone ); // may not already have a primary key defined
+ Assert( m_iPKIndex == k_iFieldNil ); // no primary key defined
+
+ int nFlags = k_nColFlagPrimaryKey | k_nColFlagIndexed | k_nColFlagUnique;
+ if ( bClustered )
+ nFlags |= k_nColFlagClustered;
+
+ // go add all those fields as Indexed
+ int nNewIndex = AddIndexToFieldList( pchNames, NULL, nFlags, nFillFactor );
+ if ( nNewIndex != k_iFieldNil )
+ {
+ m_nHasPrimaryKey = k_EPrimaryKeyTypeMulti; // remember that we have multiple keys
+ m_iPKIndex = nNewIndex;
+ }
+
+ return nNewIndex;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a set of fields as the primary index
+// Input:
+// pchName - Names of the fields for the primary index; comma-separated if multiple
+// pchIndexName - Name of the index object (not in SQL)
+// nFlags - flags for CColumnInfo on this object
+// nFillFactor - fill facto to use; 0 is database default
+//-----------------------------------------------------------------------------
+int CSchema::AddIndexToFieldList( const char *pchNames, const char *pchIndexName, int nFlags, int nFillFactor )
+{
+ // need a copy of string list since the argument is const and read-only
+ int cNamesLen = Q_strlen( pchNames ) + 1;
+ char* pchNamesCopy = (char*) PvAlloc( cNamesLen );
+ Q_strncpy( pchNamesCopy, pchNames, cNamesLen );
+
+ if (pchNames == NULL)
+ {
+ // not enough memory!
+ AssertFatal( false );
+ return k_iFieldNil;
+ }
+
+ CUtlVector<int> vecKeys;
+
+ char* pchCurrent = pchNamesCopy;
+ do
+ {
+ // find next token
+ char* pchEnd = strchr(pchCurrent, ',');
+ if ( pchEnd != NULL )
+ {
+ *pchEnd++ = 0;
+ }
+
+ // skip whitespace
+ while (isspace(*pchCurrent))
+ pchCurrent++;
+
+ // find the name; we expect a C++ name, not a SQL name
+ int iField = FindIField( pchCurrent );
+ AssertFatalMsg2( k_iFieldNil != iField, "Could not find index column \"%s\" on table \"%s\"", pchCurrent, m_rgchName );
+
+ // mark as a primary key
+ m_VecField[iField].m_nColFlags |= nFlags ;
+
+ // add it to our collection
+ vecKeys.AddToTail( iField );
+
+ // move past the end of this token in the string
+ pchCurrent = pchEnd;
+ } while ( pchCurrent != NULL );
+
+ // release our copy
+ FreePv(pchNamesCopy);
+
+ // create a fieldset with our list of indexes
+ // and add it to our indexes collection
+ bool bUnique = 0 != (nFlags & k_nColFlagUnique);
+ bool bClustered = 0 != (nFlags & k_nColFlagClustered);
+ FieldSet_t vecIndex( bUnique, bClustered, vecKeys, pchIndexName );
+ vecIndex.SetFillFactor( nFillFactor );
+ int iReturn = m_VecIndexes.AddToTail( vecIndex );
+ return iReturn;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a given field as indexed.
+// Input: pchName - Name of the field to index
+// pchIndexName - Name of the index object (not in SQL)
+//-----------------------------------------------------------------------------
+int CSchema::IndexField( const char *pchIndexName, const char *pchName )
+{
+ Assert( pchName != NULL );
+ Assert( pchIndexName != NULL );
+
+ // find the field by name
+ int iField = FindIField( pchName );
+ AssertFatalMsg2( k_iFieldNil != iField, "Could not find index column \"%s\" on table \"%s\"", pchName, m_rgchName );
+
+ // add an index to it
+ return AddIndexToFieldNumber( iField, pchIndexName, false /* Not clustered */ );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a given field as indexed.
+// Input: pchIndexName - Name of the index object (not in SQL)
+// pchName - Name of the fields to index; comma separated if multiple
+//-----------------------------------------------------------------------------
+int CSchema::IndexFields( const char *pchIndexName, const char *pchNames )
+{
+ Assert( pchNames != NULL );
+ Assert( pchIndexName != NULL );
+
+ // go add all those fields as Indexed
+ int nNewIndex = AddIndexToFieldList( pchNames, pchIndexName, k_nColFlagIndexed, 0 );
+ return nNewIndex;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Includes a column (or columns) in an index for the INCLUDE clause
+// Input: pchIndexName - Name of the index object (not in SQL)
+// pchName - Name of the fields to index; comma separated if multiple
+//-----------------------------------------------------------------------------
+void CSchema::AddIncludedFields( const char *pchIndexName, const char *pchNames )
+{
+ Assert( pchNames != NULL );
+ Assert( pchIndexName != NULL );
+
+ // find that index by name
+ int nIndexIndex = -1;
+ FOR_EACH_VEC( m_VecIndexes, i )
+ {
+ FieldSet_t &refSet = m_VecIndexes.Element(i);
+ const char *pstrMatch = refSet.GetIndexName();
+ if ( Q_stricmp( pstrMatch, pchIndexName ) == 0 )
+ {
+ nIndexIndex = i;
+ break;
+ }
+ }
+
+ // must have found it
+ AssertFatalMsg1( nIndexIndex != -1, "Index \"%s\" not found", pchIndexName );
+ FieldSet_t &refSet = m_VecIndexes.Element( nIndexIndex );
+
+ // need a copy of string list since the argument is const and read-only
+ int cNamesLen = Q_strlen( pchNames ) + 1;
+ char* pchNamesCopy = (char*) PvAlloc( cNamesLen );
+ Q_strncpy( pchNamesCopy, pchNames, cNamesLen );
+
+ char* pchCurrent = pchNamesCopy;
+ do
+ {
+ // find next token
+ char* pchEnd = strchr(pchCurrent, ',');
+ if ( pchEnd != NULL )
+ {
+ *pchEnd++ = 0;
+ }
+
+ // skip whitespace
+ while (isspace(*pchCurrent))
+ pchCurrent++;
+
+ // find the name; we expect a C++ name, not a SQL name
+ int iField = FindIField( pchCurrent );
+ AssertFatalMsg2( k_iFieldNil != iField, "Could not find index column \"%s\" on table \"%s\"", pchCurrent, m_rgchName );
+
+ // add it to our collection
+ refSet.AddIncludedColumn( iField );
+
+ // move past the end of this token in the string
+ pchCurrent = pchEnd;
+ } while ( pchCurrent != NULL );
+
+ // release our copy
+ FreePv( pchNamesCopy );
+
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a given field as indexed.
+// Input: pchIndexName - Name of the index object (not in SQL)
+// pchName - Name of the fields to index; comma separated if multiple
+//-----------------------------------------------------------------------------
+int CSchema::UniqueFields( const char *pchIndexName, const char *pchNames )
+{
+ Assert( pchNames != NULL );
+ Assert( pchIndexName != NULL );
+
+ // go add all those fields as Indexed
+ int nNewIndex = AddIndexToFieldList( pchNames, pchIndexName, k_nColFlagIndexed | k_nColFlagUnique, 0 );
+ return nNewIndex;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a given field a having a clustered index.
+// Input: pchName - Name of the field to index
+// pchIndexName - Name of the index object (not in SQL)
+//-----------------------------------------------------------------------------
+int CSchema::ClusteredIndexField( int nFillFactor, const char *pchIndexName, const char *pchName )
+{
+ Assert( pchName != NULL );
+ Assert( pchIndexName != NULL );
+
+ // can't previously have some other clustered index
+ FOR_EACH_VEC( m_VecIndexes, iIndex )
+ {
+ if ( m_VecIndexes[iIndex].IsClustered() )
+ {
+ AssertFatalMsg3( false, "On table %s, can't make index %s clustered because %s is already the clustered index\n",
+ m_rgchName, pchIndexName, m_VecIndexes[iIndex].GetIndexName() );
+ return -1;
+ }
+ }
+
+ // find the field by name
+ int iField = FindIField( pchName );
+ AssertFatalMsg2( k_iFieldNil != iField, "Could not find index column \"%s\" on table \"%s\"", pchName, m_rgchName );
+
+ // add an index to it
+ int iIndex = AddIndexToFieldNumber( iField, pchIndexName, true /* clustered */ );
+ m_VecIndexes[iIndex].SetFillFactor( nFillFactor );
+ return iIndex;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a given set of fields as having a clustered index.
+// Input: pchIndexName - Name of the index object (not in SQL)
+// pchName - Name of the fields to index; comma separated if multiple
+//-----------------------------------------------------------------------------
+int CSchema::ClusteredIndexFields( int nFillFactor, const char *pchIndexName, const char *pchNames )
+{
+ Assert( pchNames != NULL );
+ Assert( pchIndexName != NULL );
+
+ // can't previously have some other clustered index
+ FOR_EACH_VEC( m_VecIndexes, iIndex )
+ {
+ if ( m_VecIndexes[iIndex].IsClustered() )
+ {
+ AssertFatalMsg3( false, "On table %s, can't make index %s clustered because %s is already the clustered index\n",
+ m_rgchName, pchIndexName, m_VecIndexes[iIndex].GetIndexName() );
+ return -1;
+ }
+ }
+
+ // go add all those fields as Indexed
+ int nNewIndex = AddIndexToFieldList( pchNames, pchIndexName, k_nColFlagClustered | k_nColFlagIndexed, nFillFactor );
+ return nNewIndex;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CSchema::AddFullTextIndex( CSchemaFull *pSchemaFull, const char *pchCatalogName, const char *pchColumnName )
+{
+ Assert( pchCatalogName != NULL );
+ Assert( pchColumnName != NULL );
+
+ // need a copy of string list since the argument is const and read-only
+ int cNamesLen = Q_strlen( pchColumnName ) + 1;
+ char* pchNamesCopy = (char*) PvAlloc( cNamesLen );
+ Q_strncpy( pchNamesCopy, pchColumnName, cNamesLen );
+
+ if ( pchNamesCopy == NULL )
+ {
+ // not enough memory!
+ AssertFatal( false );
+ return;
+ }
+
+ char* pchCurrent = pchNamesCopy;
+ do
+ {
+ // find next token
+ char* pchEnd = strchr(pchCurrent, ',');
+ if ( pchEnd != NULL )
+ {
+ *pchEnd++ = 0;
+ }
+
+ // skip whitespace
+ while (isspace(*pchCurrent))
+ pchCurrent++;
+
+ // find the name; we expect a C++ name, not a SQL name
+ int iField = FindIField( pchCurrent );
+ AssertFatalMsg2( k_iFieldNil != iField, "Could not find index column \"%s\" on table \"%s\"", pchCurrent, m_rgchName );
+
+ // add it to our collection
+ m_VecFullTextIndexes.AddToTail( iField );
+
+ // move past the end of this token in the string
+ pchCurrent = pchEnd;
+ } while ( pchCurrent != NULL );
+
+ // release our copy
+ FreePv(pchNamesCopy);
+
+ // make a note of the catalog we want
+ int nCatalogID = pSchemaFull->GetFTSCatalogByName( m_eSchemaCatalog, pchCatalogName );
+ AssertFatalMsg2( nCatalogID != -1, "Could not find fulltext catalog \"%s\" on table \"%s\"", pchCatalogName, m_rgchName );
+ m_nFullTextIndexCatalog = nCatalogID;
+
+ return;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a field as indexed given its field number
+//-----------------------------------------------------------------------------
+int CSchema::AddIndexToFieldNumber( int iField, const char *pchIndexName, bool bClustered )
+{
+ // mark the field as indexed
+ m_VecField[iField].m_nColFlags |= k_nColFlagIndexed;
+
+ // meant to be clustered?
+ if ( bClustered )
+ m_VecField[iField].m_nColFlags |= k_nColFlagClustered;
+
+ // add to list of indexes, and remember the indexID
+ CUtlVector<int> vecColumns;
+ vecColumns.AddToTail( iField );
+
+ // false: not unique
+ // false: not clustered
+ FieldSet_t vecIndex( false, bClustered, vecColumns, pchIndexName);
+ int iIndex = m_VecIndexes.AddToTail( vecIndex );
+
+ return iIndex;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a given field as unique.
+// Input: pchName - Name of the field to index
+// pchIndexName - Name of the index object
+//-----------------------------------------------------------------------------
+int CSchema::UniqueField( const char *pchIndexName, const char *pchName )
+{
+ Assert( pchName != NULL );
+ Assert( pchIndexName != NULL );
+
+ int iField = FindIField( pchName );
+ AssertFatalMsg2( k_iFieldNil != iField, "Could not find index column \"%s\" on table \"%s\"", pchName, m_rgchName );
+
+ m_VecField[iField].m_nColFlags |= ( k_nColFlagUnique | k_nColFlagIndexed );
+
+ // add to list of indexes, and remember the indexID
+ CUtlVector<int> vecColumns;
+ vecColumns.AddToTail( iField );
+
+ // true: unique
+ // false: not clustered
+ FieldSet_t vecIndex( true, false, vecColumns, pchIndexName );
+ int iIndex = m_VecIndexes.AddToTail( vecIndex );
+
+ return iIndex;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Marks a given field as auto increment.
+// Input: pchName - Name of the field
+//-----------------------------------------------------------------------------
+void CSchema::AutoIncrementField( char *pchName )
+{
+ int iField = FindIField( pchName );
+ AssertFatalMsg2( k_iFieldNil != iField, "Could not find index column \"%s\" on table \"%s\"", pchName, m_rgchName );
+ m_VecField[iField].m_nColFlags |= k_nColFlagAutoIncrement;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the field with a given name.
+// Input: pchName - Name of the field to search for
+// Output: Index of the matching field (k_iFieldNil if there isn't one)
+//-----------------------------------------------------------------------------
+int CSchema::FindIField( const char *pchName )
+{
+ for ( int iField = 0; iField < m_VecField.Count(); iField++ )
+ {
+ if ( 0 == Q_strncmp( pchName, m_VecField[iField].m_rgchName, k_cSQLObjectNameMax ) )
+ return iField;
+ }
+
+ return k_iFieldNil;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the field with a given SQL name.
+// Input: pchName - Name of the field to search for
+// Output: Index of the matching field (k_iFieldNil if there isn't one)
+//-----------------------------------------------------------------------------
+int CSchema::FindIFieldSQL( const char *pchName )
+{
+ for ( int iField = 0; iField < m_VecField.Count(); iField++ )
+ {
+ if ( 0 == Q_strncmp( pchName, m_VecField[iField].m_rgchSQLName, k_cSQLObjectNameMax ) )
+ return iField;
+ }
+
+ return k_iFieldNil;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a schema conversion instruction (for use in converting from
+// a different Schema to this one).
+//-----------------------------------------------------------------------------
+void CSchema::AddDeleteField( const char *pchFieldName )
+{
+ DeleteField_t &deleteField = m_VecDeleteField[m_VecDeleteField.AddToTail()];
+ Q_strncpy( deleteField.m_rgchFieldName, pchFieldName, sizeof( deleteField.m_rgchFieldName ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a schema conversion instruction (for use in converting from
+// a different Schema to this one).
+//-----------------------------------------------------------------------------
+void CSchema::AddRenameField( const char *pchFieldNameOld, const char *pchFieldNameNew )
+{
+ RenameField_t &renameField = m_VecRenameField[m_VecRenameField.AddToTail()];
+ Q_strncpy( renameField.m_rgchFieldNameOld, pchFieldNameOld, sizeof( renameField.m_rgchFieldNameOld ) );
+ renameField.m_iFieldDst = FindIField( pchFieldNameNew );
+ Assert( k_iFieldNil != renameField.m_iFieldDst );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a schema conversion instruction (for use in converting from
+// a different Schema to this one).
+// Input: pchFieldNameOld - Name of the field in the old schema
+// pchFieldNameMew - Name of the field in the new schema
+// pfnAlterField - Function to translate data from the old format to
+// the new
+//-----------------------------------------------------------------------------
+void CSchema::AddAlterField( const char *pchFieldNameOld, const char *pchFieldNameNew, PfnAlterField_t pfnAlterField )
+{
+ Assert( pfnAlterField );
+ AlterField_t &alterField = m_VecAlterField[m_VecAlterField.AddToTail()];
+ Q_strncpy( alterField.m_rgchFieldNameOld, pchFieldNameOld, sizeof( alterField.m_rgchFieldNameOld ) );
+ alterField.m_iFieldDst = FindIField( pchFieldNameNew );
+ Assert( k_iFieldNil != alterField.m_iFieldDst );
+ alterField.m_pfnAlterFunc = pfnAlterField;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Figures out how to map a field from another Schema into us.
+// First we check our conversion instructions to see if any apply,
+// and then we look for a straightforward match.
+// Input: pchFieldName - Name of the field we're trying to map
+// piFieldDst - [Return] Index of the field to map it to
+// ppfnAlterField - [Return] Optional function to convert data
+// Output: true if we know what to do with this field (if false, the conversion
+// is undefined and dangerous).
+//-----------------------------------------------------------------------------
+bool CSchema::BCanConvertField( const char *pchFieldName, int *piFieldDst, PfnAlterField_t *ppfnAlterField )
+{
+ *ppfnAlterField = NULL;
+
+ // Should this field be deleted?
+ for ( int iDeleteField = 0; iDeleteField < m_VecDeleteField.Count(); iDeleteField++ )
+ {
+ if ( 0 == Q_strcmp( pchFieldName, m_VecDeleteField[iDeleteField].m_rgchFieldName ) )
+ {
+ *piFieldDst = k_iFieldNil;
+ return true;
+ }
+ }
+
+ // Should this field be renamed?
+ for ( int iRenameField = 0; iRenameField < m_VecRenameField.Count(); iRenameField++ )
+ {
+ if ( 0 == Q_strcmp( pchFieldName, m_VecRenameField[iRenameField].m_rgchFieldNameOld ) )
+ {
+ *piFieldDst = m_VecRenameField[iRenameField].m_iFieldDst;
+ return true;
+ }
+ }
+
+ // Was this field altered?
+ for ( int iAlterField = 0; iAlterField < m_VecAlterField.Count(); iAlterField++ )
+ {
+ if ( 0 == Q_strcmp( pchFieldName, m_VecAlterField[iAlterField].m_rgchFieldNameOld ) )
+ {
+ *piFieldDst = m_VecAlterField[iAlterField].m_iFieldDst;
+ *ppfnAlterField = m_VecAlterField[iAlterField].m_pfnAlterFunc;
+ return true;
+ }
+ }
+
+ // Find out which of our fields this field maps to (if it doesn't map
+ // to any of them, we don't know what to do with it).
+ *piFieldDst = FindIField( pchFieldName );
+ return ( k_iFieldNil != *piFieldDst );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the size to use when writing to a field.
+//-----------------------------------------------------------------------------
+int Field_t::CubFieldUpdateSize() const
+{
+ switch ( m_EType )
+ {
+ case k_EGCSQLType_String:
+ case k_EGCSQLType_Blob:
+ case k_EGCSQLType_Image:
+ // Nobody should call this function for
+ // var-length fields
+ Assert( false );
+ return m_cubLength;
+
+ case k_EGCSQLType_int8:
+ case k_EGCSQLType_int16:
+ case k_EGCSQLType_int32:
+ case k_EGCSQLType_int64:
+ case k_EGCSQLType_float:
+ case k_EGCSQLType_double:
+ return m_cubLength;
+
+ default:
+ Assert(false);
+ return 0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell whether or not a field is of string type.
+//-----------------------------------------------------------------------------
+bool Field_t::BIsStringType() const
+{
+ return ( k_EGCSQLType_String == m_EType );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell whether or not the field is of variable-length type.
+//-----------------------------------------------------------------------------
+bool Field_t::BIsVariableLength() const
+{
+ return ( k_EGCSQLType_String == m_EType ) || ( k_EGCSQLType_Blob == m_EType ) || ( k_EGCSQLType_Image == m_EType );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a foreign key
+//-----------------------------------------------------------------------------
+void CSchema::AddFK( const char* pchName, const char* pchColumn, const char* pchParentTable, const char* pchParentColumn, EForeignKeyAction eOnDeleteAction, EForeignKeyAction eOnUpdateAction )
+{
+ int iTail = m_VecFKData.AddToTail();
+ FKData_t &fkData = m_VecFKData[iTail];
+
+ Q_snprintf( fkData.m_rgchName, Q_ARRAYSIZE( fkData.m_rgchName ), "%s_%s", GetPchName(), pchName );
+ Q_strncpy( fkData.m_rgchParentTableName, pchParentTable, Q_ARRAYSIZE( fkData.m_rgchParentTableName ) );
+ fkData.m_eOnDeleteAction = eOnDeleteAction;
+ fkData.m_eOnUpdateAction = eOnUpdateAction;
+
+ // Now we need to split up the column name strings and add their data
+ FKColumnRelation_t colRelation;
+ Q_memset( &colRelation, 0, sizeof( FKColumnRelation_t ) );
+ uint iMyColumn = 0;
+ uint iParentColumn = 0;
+ uint iParentString = 0;
+ for( uint i=0; i<(uint)Q_strlen( pchColumn )+1; ++i )
+ {
+ if ( pchColumn[i] != ',' && pchColumn[i] != 0 )
+ {
+ colRelation.m_rgchCol[ iMyColumn++ ] = pchColumn[i];
+ }
+ else
+ {
+ Assert( Q_strlen( colRelation.m_rgchCol ) );
+ // Should have a matching column name in the parent string
+ while( iParentString < (uint)Q_strlen( pchParentColumn ) )
+ {
+ if ( pchParentColumn[iParentString] != ',' )
+ {
+ colRelation.m_rgchParentCol[ iParentColumn++ ] = pchParentColumn[iParentString];
+ ++iParentString;
+ }
+ else
+ {
+ ++iParentString;
+ break;
+ }
+ }
+
+ AssertMsg( Q_strlen( colRelation.m_rgchParentCol ), "Column counts for FK do not match between child/parent\n" );
+ fkData.m_VecColumnRelations.AddToTail( colRelation );
+ Q_memset( &colRelation, 0, sizeof( FKColumnRelation_t ) );
+ // Reset positions
+ iMyColumn = 0;
+ iParentColumn = 0;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Caches the insert statement for each table so we don't need to
+// generate it for each row we insert.
+//-----------------------------------------------------------------------------
+const char *CSchema::GetInsertStatementText() const
+{
+ if ( m_sInsertStatementText.IsEmpty() )
+ {
+ TSQLCmdStr sBuilder;
+ BuildInsertStatementText( &sBuilder, GetRecordInfo() );
+ m_sInsertStatementText.Set( sBuilder );
+ }
+
+ return m_sInsertStatementText;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Caches the insert via MERGE statement for each table so we don't need to
+// generate it for each row we insert.
+//-----------------------------------------------------------------------------
+const char *CSchema::GetMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert()
+{
+ if ( m_sMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert.IsEmpty() )
+ {
+ TSQLCmdStr sBuilder;
+ BuildMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert( &sBuilder, GetRecordInfo() );
+ m_sMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert.Set( sBuilder );
+ }
+
+ return m_sMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Caches the insert via MERGE statement for each table so we don't need to
+// generate it for each row we insert.
+//-----------------------------------------------------------------------------
+const char *CSchema::GetMergeStatementTextOnPKWhenNotMatchedInsert()
+{
+ if ( m_sMergeStatementTextOnPKWhenNotMatchedInsert.IsEmpty() )
+ {
+ TSQLCmdStr sBuilder;
+ BuildMergeStatementTextOnPKWhenNotMatchedInsert( &sBuilder, GetRecordInfo() );
+ m_sMergeStatementTextOnPKWhenNotMatchedInsert.Set( sBuilder );
+ }
+
+ return m_sMergeStatementTextOnPKWhenNotMatchedInsert;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the number of foreign key constraints defined for the table
+//-----------------------------------------------------------------------------
+int CSchema::GetFKCount()
+{
+ return m_VecFKData.Count();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get data for a foreign key by index (valid for 0...GetFKCount()-1)
+//-----------------------------------------------------------------------------
+FKData_t &CSchema::GetFKData( int iIndex )
+{
+ return m_VecFKData[iIndex];
+}
+
+
+#ifdef DBGFLAG_VALIDATE
+//-----------------------------------------------------------------------------
+// Purpose: Run a global validation pass on all of our data structures and memory
+// allocations.
+// Input: validator - Our global validator object
+// pchName - Our name (typically a member var in our container)
+//-----------------------------------------------------------------------------
+void CSchema::Validate( CValidator &validator, const char *pchName )
+{
+ // 1.
+ // Claim our memory
+ VALIDATE_SCOPE();
+
+ m_VecField.Validate( validator, "m_VecField" );
+ m_VecDeleteField.Validate( validator, "m_VecDeleteField" );
+ m_VecRenameField.Validate( validator, "m_VecRenameField" );
+ m_VecIndexes.Validate( validator, "m_VecIndexes" );
+ m_VecFullTextIndexes.Validate( validator, "m_VecFullTextIndexes" );
+ ValidateObj( m_VecFKData );
+ FOR_EACH_VEC( m_VecFKData, i )
+ {
+ ValidateObj( m_VecFKData[i] );
+ }
+
+ for ( int iIndex = 0; iIndex < m_VecIndexes.Count(); iIndex++ )
+ {
+ ValidateObj( m_VecIndexes[iIndex] );
+ }
+
+ ValidateObj( m_sInsertStatementText );
+
+ // 2.
+ // Validate that our fields make sense
+#if defined(_DEBUG)
+ uint32 dubOffset = 0;
+ for ( int iField = 0; iField < m_VecField.Count(); iField++ )
+ {
+ Assert( dubOffset == m_VecField[iField].m_dubOffset );
+ dubOffset += m_VecField[iField].m_cubLength;
+ }
+#endif // defined(_DEBUG)
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Validates that a given record from our table is in a good state.
+// Input: pubRecord - Record to validate
+//-----------------------------------------------------------------------------
+void CSchema::ValidateRecord( uint8 *pubRecord )
+{
+ // Make sure each record is in a consistent state
+ for ( int iField = 0; iField < m_VecField.Count(); iField++ )
+ {
+ Field_t &field = m_VecField[iField];
+
+ // Ensure that strings are null-terminated, and that everything after the terminator is 0
+ if ( k_EGCSQLType_String == field.m_EType )
+ {
+ char *pchField = ( char * ) pubRecord + field.m_dubOffset;
+ Assert( 0 == pchField[field.m_cubLength - 1] );
+ for ( uint32 ich = 0; ich < field.m_cubLength; ich++ )
+ {
+ if ( 0 == pchField[ich] )
+ {
+ while ( ich < field.m_cubLength )
+ {
+ Assert( 0 == pchField[ich] );
+ ich++;
+ }
+ }
+ }
+ }
+ }
+}
+#endif // DBGFLAG_VALIDATE
+} // namespace GCSDK
diff --git a/gcsdk/sqlaccess/schemafull.cpp b/gcsdk/sqlaccess/schemafull.cpp
new file mode 100644
index 0000000..d8e9490
--- /dev/null
+++ b/gcsdk/sqlaccess/schemafull.cpp
@@ -0,0 +1,409 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+
+#include "stdafx.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+namespace GCSDK
+{
+CSchemaFull g_SchemaFull;
+CSchemaFull & GSchemaFull()
+{
+ return g_SchemaFull;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CSchemaFull::CSchemaFull()
+{
+ m_pubScratchBuffer = NULL;
+ m_cubScratchBuffer = 0;
+ m_unCheckSum = 0;
+
+ m_mapFTSEnabled.SetLessFunc( DefLessFunc( enum ESchemaCatalog ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CSchemaFull::~CSchemaFull()
+{
+ Uninit();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Call this after you've finished setting up the SchemaFull (either
+// by loading it or by in GenerateIntrinsic). It calculates our checksum
+// and allocates our scratch buffer.
+//-----------------------------------------------------------------------------
+void CSchemaFull::FinishInit()
+{
+ // Calculate our checksum
+ m_unCheckSum = 0;
+
+ for ( int iSchema = 0; iSchema < m_VecSchema.Count(); iSchema++ )
+ m_unCheckSum += m_VecSchema[iSchema].CalcChecksum();
+
+ // Allocate our scratch buffer
+ Assert( NULL == m_pubScratchBuffer );
+ // Include some slop for field IDs and sizes in a sparse record
+ // 2k is way overkill but still no big deal
+ m_cubScratchBuffer = k_cubRecordMax + 2048;
+ m_pubScratchBuffer = ( uint8 * ) malloc( m_cubScratchBuffer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Call this after you've finished setting up the SchemaFull (either
+// by loading it or by in GenerateIntrinsic). It calculates our checksum
+// and allocates our scratch buffer.
+//-----------------------------------------------------------------------------
+
+void CSchemaFull::SetITable( CSchema* pSchema, int iTable )
+{
+ // make sure we don't have this schema anywhere already
+ for ( int iSchema = 0; iSchema < m_VecSchema.Count(); iSchema++ )
+ {
+ if ( pSchema != &m_VecSchema[iSchema] )
+ AssertFatalMsg( m_VecSchema[iSchema].GetITable() != iTable, "Duplicate iTable in schema definition.\n" );
+ }
+
+ // set the pSchema object
+ pSchema->SetITable( iTable );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Uninits the schema. Need to call this explicitly before app shutdown
+// on static instances of this object, as the CSchema objects
+// point to memory in static memory pools which may destruct
+// before static instances of this object.
+//-----------------------------------------------------------------------------
+void CSchemaFull::Uninit()
+{
+ m_VecSchema.RemoveAll();
+ if ( NULL != m_pubScratchBuffer )
+ {
+ free( m_pubScratchBuffer );
+ m_pubScratchBuffer = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the scratch buffer. It is large enough to handle any
+// record, sparse or otherwise
+//
+//-----------------------------------------------------------------------------
+uint8* CSchemaFull::GetPubScratchBuffer( )
+{
+ return m_pubScratchBuffer;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: This is used during the generation of our intrinsic schema. We've
+// added a new schema to ourselves, and we need to make sure that it
+// matches the corresponding C class.
+// Input: pSchema - Schema to check
+// cField - Number of fields the schema should contain.
+// cubRecord - Size of a record in the schema
+//-----------------------------------------------------------------------------
+void CSchemaFull::CheckSchema( CSchema *pSchema, int cField, uint32 cubRecord )
+{
+ // We generate our structures and our schema using macros that operate on the
+ // same source. We check a couple of things to make sure that they're properly in sync.
+
+ // This will fail if the schema's definition specifies the wrong iTable
+ if ( pSchema != &m_VecSchema[pSchema->GetITable()] )
+ {
+ EmitError( SPEW_SQL, "Table %s has a bad iTable\n", pSchema->GetPchName() );
+ }
+
+ // This will fail if there are missing lines in the schema definition
+ if ( pSchema->GetCField() != cField )
+ {
+ EmitError( SPEW_SQL, "Badly formed table %s (blank line in schema def?)\n", pSchema->GetPchName() );
+ AssertFatal( false );
+ }
+
+ // This is unlikely to fail. It indicates some kind of size mismatch (maybe a packing problem?)
+ if ( pSchema->CubRecordFixed() != cubRecord )
+ {
+ // You may hit this if END_FIELDDATA_HAS_VAR_FIELDS is not used properly
+ EmitError( SPEW_SQL, "Table %s has an inconsistent size (class = %d, schema = %d)\n",
+ pSchema->GetPchName(), cubRecord, pSchema->CubRecordFixed() );
+ AssertFatal( false );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the table with a given name.
+// Input: pchName - Name of the table to search for
+// Output: Index of the matching table ( k_iTableNil if there isn't one)
+//-----------------------------------------------------------------------------
+int CSchemaFull::FindITable( const char *pchName )
+{
+ for ( int iSchema = 0; iSchema < m_VecSchema.Count(); iSchema++ )
+ {
+ if ( 0 == Q_strcmp( pchName, m_VecSchema[iSchema].GetPchName() ) )
+ return iSchema;
+ }
+
+ return k_iTableNil;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the table with a given iTable (iSchema)
+// Input: iTable -
+// Output: NULL or a const char * to the name (for temporary use only)
+//-----------------------------------------------------------------------------
+const char * CSchemaFull::PchTableFromITable( int iTable )
+{
+ if ( iTable < 0 || iTable >= m_VecSchema.Count() )
+ return NULL;
+ else
+ return m_VecSchema[ iTable ].GetPchName();
+}
+
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CSchemaFull::AddFullTextCatalog( enum ESchemaCatalog eCatalog, const char *pstrCatalogName, int nFileGroup )
+{
+ CFTSCatalogInfo info;
+ info.m_eCatalog = eCatalog;
+ info.m_nFileGroup = nFileGroup;
+ info.m_pstrName = strdup(pstrCatalogName);
+
+ m_vecFTSCatalogs.AddToTail( info );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CSchemaFull::GetFTSCatalogByName( enum ESchemaCatalog eCatalog, const char *pstrCatalogName )
+{
+ int nIndex = -1;
+ FOR_EACH_VEC( m_vecFTSCatalogs, i )
+ {
+ CFTSCatalogInfo &refInfo = m_vecFTSCatalogs[ i ];
+ if ( 0 == Q_stricmp( pstrCatalogName, refInfo.m_pstrName ) )
+ {
+ nIndex = i;
+ break;
+ }
+ }
+
+ return nIndex;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: turn on FTS for the named schema catalog. Called by the
+// InitIntrinsic() function.
+//-----------------------------------------------------------------------------
+void CSchemaFull::EnableFTS( enum ESchemaCatalog eCatalog )
+{
+ // mark it enabled in the map
+ m_mapFTSEnabled.Insert( eCatalog, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: is FTS enabled for the supplied schema catalog?
+//-----------------------------------------------------------------------------
+bool CSchemaFull::GetFTSEnabled( enum ESchemaCatalog eCatalog )
+{
+ int iEntry = m_mapFTSEnabled.Find( eCatalog );
+ if ( iEntry == m_mapFTSEnabled.InvalidIndex() )
+ return false;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a schema conversion instruction (for use in converting from
+// a different SchemaFull to this one).
+//-----------------------------------------------------------------------------
+void CSchemaFull::AddDeleteTable( const char *pchTableName )
+{
+ DeleteTable_t &deleteTable = m_VecDeleteTable[m_VecDeleteTable.AddToTail()];
+ Q_strncpy( deleteTable.m_rgchTableName, pchTableName, sizeof( deleteTable.m_rgchTableName ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a schema conversion instruction (for use in converting from
+// a different SchemaFull to this one).
+//-----------------------------------------------------------------------------
+void CSchemaFull::AddRenameTable( const char *pchTableNameOld, const char *pchTableNameNew )
+{
+ RenameTable_t &renameTable = m_VecRenameTable[m_VecRenameTable.AddToTail()];
+ Q_strncpy( renameTable.m_rgchTableNameOld, pchTableNameOld, sizeof( renameTable.m_rgchTableNameOld ) );
+ renameTable.m_iTableDst = FindITable( pchTableNameNew );
+ Assert( k_iTableNil != renameTable.m_iTableDst );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a schema conversion instruction (for use in converting from
+// a different SchemaFull to this one).
+//-----------------------------------------------------------------------------
+void CSchemaFull::AddDeleteField( const char *pchTableName, const char *pchFieldName )
+{
+ int iSchema = FindITable( pchTableName );
+ AssertFatal( k_iTableNil != iSchema );
+
+ m_VecSchema[iSchema].AddDeleteField( pchFieldName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a schema conversion instruction (for use in converting from
+// a different SchemaFull to this one).
+//-----------------------------------------------------------------------------
+void CSchemaFull::AddRenameField( const char *pchTableName, const char *pchFieldNameOld, const char *pchFieldNameNew )
+{
+ int iSchema = FindITable( pchTableName );
+ AssertFatal( k_iTableNil != iSchema );
+
+ m_VecSchema[iSchema].AddRenameField( pchFieldNameOld, pchFieldNameNew );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a schema conversion instruction (for use in converting from
+// a different SchemaFull to this one).
+//-----------------------------------------------------------------------------
+void CSchemaFull::AddAlterField( const char *pchTableName, const char *pchFieldNameOld, const char *pchFieldnameNew, PfnAlterField_t pfnAlterField )
+{
+ int iSchema = FindITable( pchTableName );
+ AssertFatal( k_iTableNil != iSchema );
+
+ m_VecSchema[iSchema].AddAlterField( pchFieldNameOld, pchFieldnameNew, pfnAlterField );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a trigger to the desired schema
+//-----------------------------------------------------------------------------
+void CSchemaFull::AddTrigger( ESchemaCatalog eCatalog, const char *pchTableName, const char *pchTriggerName, ETriggerType eTriggerType, const char *pchTriggerText )
+{
+ CTriggerInfo trigger;
+ trigger.m_eTriggerType = eTriggerType;
+ trigger.m_eSchemaCatalog = eCatalog;
+ Q_strncpy( trigger.m_szTriggerName, pchTriggerName, Q_ARRAYSIZE( trigger.m_szTriggerName ) );
+ Q_strncpy( trigger.m_szTriggerTableName, pchTableName, Q_ARRAYSIZE( trigger.m_szTriggerTableName ) );
+ trigger.m_strText = pchTriggerText;
+
+ // add it to our list
+ m_VecTriggers.AddToTail( trigger );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Figures out how to map a table from another SchemaFull into us.
+// First we check our conversion instructions to see if any apply,
+// and then we look for a straightforward match.
+// Input: pchTableName - Name of the table we're trying to map
+// piTableDst - [Return] Index of the table to map it to
+// Output: true if we know what to do with this table (if false, the conversion
+// is undefined and dangerous).
+//-----------------------------------------------------------------------------
+bool CSchemaFull::BCanConvertTable( const char *pchTableName, int *piTableDst )
+{
+ // Should this table be deleted?
+ for ( int iDeleteTable = 0; iDeleteTable < m_VecDeleteTable.Count(); iDeleteTable++ )
+ {
+ if ( 0 == Q_strcmp( pchTableName, m_VecDeleteTable[iDeleteTable].m_rgchTableName ) )
+ {
+ *piTableDst = k_iTableNil;
+ return true;
+ }
+ }
+
+ // Should this table be renamed?
+ for ( int iRenameTable = 0; iRenameTable < m_VecRenameTable.Count(); iRenameTable++ )
+ {
+ if ( 0 == Q_strcmp( pchTableName, m_VecRenameTable[iRenameTable].m_rgchTableNameOld ) )
+ {
+ *piTableDst = m_VecRenameTable[iRenameTable].m_iTableDst;
+ return true;
+ }
+ }
+
+ // Find out which of our tables this table maps to (if it doesn't map
+ // to any of them, we don't know what to do with it).
+ *piTableDst = FindITable( pchTableName );
+ return ( k_iTableNil != *piTableDst );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets the default SQL schema name for a catalog
+//-----------------------------------------------------------------------------
+const char *CSchemaFull::GetDefaultSchemaNameForCatalog( ESchemaCatalog eCatalog )
+{
+ // For all catalogs it's actually the same
+ if ( m_strDefaultSchemaName.IsEmpty() )
+ {
+ m_strDefaultSchemaName.Set( CFmtStr( "App%u", GGCBase()->GetAppID() ) );
+ }
+
+ return m_strDefaultSchemaName.Get();
+}
+
+
+#ifdef DBGFLAG_VALIDATE
+//-----------------------------------------------------------------------------
+// Purpose: Run a global validation pass on all of our data structures and memory
+// allocations.
+// Input: validator - Our global validator object
+// pchName - Our name (typically a member var in our container)
+//-----------------------------------------------------------------------------
+void CSchemaFull::Validate( CValidator &validator, const char *pchName )
+{
+ VALIDATE_SCOPE();
+
+ ValidateObj( m_VecSchema );
+ for ( int iSchema = 0; iSchema < m_VecSchema.Count(); iSchema++ )
+ {
+ ValidateObj( m_VecSchema[iSchema] );
+ }
+
+ ValidateObj( m_VecDeleteTable );
+ ValidateObj( m_VecRenameTable );
+
+ ValidateObj( m_mapFTSEnabled );
+
+ ValidateObj( m_vecFTSCatalogs );
+ FOR_EACH_VEC( m_vecFTSCatalogs, i )
+ {
+ ValidateObj( m_vecFTSCatalogs[i] );
+ }
+
+ ValidateObj( m_VecTriggers );
+ FOR_EACH_VEC( m_VecTriggers, i )
+ {
+ ValidateObj( m_VecTriggers[i] );
+ }
+
+ validator.ClaimMemory( m_pubScratchBuffer );
+}
+#endif // DBGFLAG_VALIDATE
+
+} // namespace GCSDK
diff --git a/gcsdk/sqlaccess/schemaupdate.cpp b/gcsdk/sqlaccess/schemaupdate.cpp
new file mode 100644
index 0000000..b15b5f0
--- /dev/null
+++ b/gcsdk/sqlaccess/schemaupdate.cpp
@@ -0,0 +1,1696 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Contains the job that's responsible for updating the database schema
+//
+//=============================================================================
+
+#include "stdafx.h"
+#include "gcsdk/sqlaccess/schemaupdate.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+namespace GCSDK
+{
+#ifndef SQL_SUCCESS
+#define SQL_SUCCESS 0
+#define SQL_SUCCESS_WITH_INFO 1
+#define SQL_NO_DATA 100
+#define SQL_ERROR (-1)
+#endif // SQLSUCCESS
+
+// this comes from sql.h. The GC really shouldn't depend on the MSSQL headers
+#define SQL_INDEX_CLUSTERED 1
+
+inline bool SQL_OK( SQLRETURN nRet )
+{
+ return ( ( SQL_SUCCESS == nRet ) || (SQL_SUCCESS_WITH_INFO == nRet ) );
+}
+
+#define SQL_FAILED( ret ) ( !SQL_OK( ret ) )
+
+#define EXIT_ON_SQL_FAILURE( ret ) \
+{ \
+ if ( !SQL_OK( ret ) ) \
+{ \
+ goto Exit; \
+} \
+}
+
+#define EXIT_ON_BOOLSQL_FAILURE( ret ) \
+{ \
+ if ( !(ret) ) \
+{ \
+ nRet = SQL_ERROR; \
+ goto Exit; \
+} \
+}
+
+#define RETURN_SQL_ERROR_ON_FALSE( ret ) \
+ if( !(ret) ) \
+ {\
+ return SQL_ERROR;\
+ }
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Emits a message and appends it to a string
+//-----------------------------------------------------------------------------
+void EmitAndAppend( CFmtStr1024 & sTarget, const CGCEmitGroup& Group, int iLevel, int iLevelLog, PRINTF_FORMAT_STRING const char *pchMessage, ... )
+{
+ va_list args;
+ va_start( args, pchMessage );
+
+ if( sTarget.Length() < 1024 )
+ sTarget.AppendFormatV( pchMessage, args );
+
+ EmitInfoV( Group, iLevel, iLevelLog, pchMessage, args );
+
+ va_end( args );
+}
+
+//-----------------------------------------------------------------------------
+// builds a command of the form:
+// CREATE [CLUSTERED] [UNIQUE] INDEX <index_name> ON <table_name>
+// (col1, col2, ...)
+// [INCLUDE (icol1, icol2, ...)]
+// [WITH (FILLFACTOR = n)]
+//-----------------------------------------------------------------------------
+CUtlString GetAddIndexSQL( CRecordInfo *pRecordInfo, const FieldSet_t &refFields )
+{
+ CFmtStrMax sCmd;
+ sCmd.sprintf( "CREATE %s%sINDEX %s ON App%u.%s (",
+ refFields.IsClustered() ? "CLUSTERED " : "",
+ refFields.IsUnique() ? "UNIQUE " : "",
+ refFields.GetIndexName(),
+ GGCBase()->GetAppID(),
+ pRecordInfo->GetName() );
+
+ // add real columns
+ for ( int n = 0; n < refFields.GetCount(); n++ )
+ {
+ int nField = refFields.GetField( n );
+ const CColumnInfo &refInfo = pRecordInfo->GetColumnInfo( nField );
+
+ sCmd.AppendFormat( "%s%s",
+ (n > 0) ? "," : "",
+ refInfo.GetName() );
+ }
+ sCmd += ")";
+
+ // do we have any included columns?
+ if ( refFields.GetIncludedCount() > 0 )
+ {
+ // yes, add those
+ sCmd += "\nINCLUDE (";
+
+ for ( int n = 0; n < refFields.GetIncludedCount(); n++ )
+ {
+ int nField = refFields.GetIncludedField( n );
+ const CColumnInfo &refInfo = pRecordInfo->GetColumnInfo( nField );
+
+ sCmd.AppendFormat( "%s%s",
+ (n > 0) ? "," : "",
+ refInfo.GetName() );
+ }
+ sCmd += ")";
+ }
+
+ // do we need a fill factor?
+ if ( refFields.GetFillFactor() != 0)
+ {
+ sCmd.AppendFormat("\nWITH (FILLFACTOR = %d)",
+ refFields.GetFillFactor() );
+ }
+
+ return CUtlString( sCmd.String() );
+}
+
+CUtlString GetAlterColumnText( CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfoDesired )
+{
+ Assert( pRecordInfo );
+ Assert( pColumnInfoDesired );
+
+ char rgchTmp[128];
+ CUtlString sCmd;
+ sCmd.Format( "ALTER TABLE App%u.%s ALTER COLUMN %s %s %s", GGCBase()->GetAppID(), pRecordInfo->GetName(), pColumnInfoDesired->GetName(),
+ SQLTypeFromField( *pColumnInfoDesired, rgchTmp, Q_ARRAYSIZE( rgchTmp ) ),
+ pColumnInfoDesired->BIsPrimaryKey() ? "NOT NULL" : ""
+ );
+ return sCmd;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CSchemaUpdate::CSchemaUpdate()
+{
+ m_mapPRecordInfoDesired.SetLessFunc( CaselessStringLessThan );
+ m_eConversionMode = k_EConversionModeInspectOnly;
+
+ m_bConversionNeeded = false;
+ m_cTablesDesiredMissing = 0;
+ m_cTablesActualDifferent = 0;
+ m_cTablesActualUnknown = 0;
+ m_cTablesNeedingChange = 0;
+ m_cColumnsDesiredMissing = 0;
+ m_cColumnsActualDifferent = 0;
+ m_cColumnsActualUnknown = 0;
+ m_bSkippedAChange = false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CSchemaUpdate::~CSchemaUpdate()
+{
+ // release all the record info's we're holding onto
+ FOR_EACH_MAP_FAST( m_mapPRecordInfoDesired, iRecordInfo )
+ {
+ SAFE_RELEASE( m_mapPRecordInfoDesired[iRecordInfo] );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a record info that describes a desired table that should
+// exist in the database
+// Input: pRecordInfo - pointer to record info
+//-----------------------------------------------------------------------------
+void CSchemaUpdate::AddRecordInfoDesired( CRecordInfo *pRecordInfo )
+{
+ Assert( pRecordInfo );
+ const char *pchName = pRecordInfo->GetName();
+ Assert( pchName && pchName[0] );
+ Assert( m_mapPRecordInfoDesired.InvalidIndex() == m_mapPRecordInfoDesired.Find( pchName ) );
+ // addref the record info since we're hanging onto it
+ pRecordInfo->AddRef();
+ // insert it in our map, indexed by name
+ m_mapPRecordInfoDesired.Insert( pchName, pRecordInfo );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a CFTSCatalogInfo that tells us about a FTS catalog that
+// should exist in the database
+//-----------------------------------------------------------------------------
+void CSchemaUpdate::AddFTSInfo( const CFTSCatalogInfo &refFTSInfo )
+{
+ m_listFTSCatalogInfo.AddToTail( refFTSInfo );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a CFTSCatalogInfo that tells us about a FTS catalog that
+// should exist in the database
+//-----------------------------------------------------------------------------
+void CSchemaUpdate::AddTriggerInfos( const CUtlVector< CTriggerInfo > &refTriggerInfo )
+{
+ m_vecTriggerInfo = refTriggerInfo;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Validates and updates the database schema
+//-----------------------------------------------------------------------------
+bool CJobUpdateSchema::BYieldingRunJob( void * )
+{
+ // update the main schema
+ EmitInfo( SPEW_GC, 2, 2, "Updating main schema...\n" );
+ if ( !BYieldingUpdateSchema( k_ESchemaCatalogMain ) )
+ {
+ m_pGC->SetStartupComplete( false );
+ return false;
+ }
+
+ // Could fail, but we shouldn't stop from starting up if it does
+ BYieldingUpdateSchema( k_ESchemaCatalogOGS );
+
+ bool bSuccess = m_pGC->BYieldingFinishStartup();
+ m_pGC->SetStartupComplete( bSuccess );
+ if ( bSuccess )
+ {
+ bSuccess = m_pGC->BYieldingPostStartup();
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CJobUpdateSchema::BYieldingUpdateSchema( ESchemaCatalog eSchemaCatalog )
+{
+ if( !YieldingBuildTypeMap( eSchemaCatalog ) )
+ return false;
+
+ // make an object to communicate desired database schema & results
+ CSchemaUpdate *pSchemaUpdate = new CSchemaUpdate();
+
+ // do safe conversions only
+ // TODO - do one round of inspection only first so we can send watchdog alert about what's about
+ // to happen, then do conversions. Also force conversions in dev system.
+ pSchemaUpdate->m_eConversionMode = k_EConversionModeConvertSafe;
+
+ // Add all the tables to desired schema
+ for ( int iTable = 0; iTable < m_iTableCount; iTable++ )
+ {
+ // MERGE COMMENT: "schema" cannot be used as a variable name because it is an empty #define resulting in error C2059
+ CSchema &gc_schema = GSchemaFull().GetSchema( iTable );
+
+ // is it in the schema we want?
+ if ( gc_schema.GetESchemaCatalog() == eSchemaCatalog )
+ pSchemaUpdate->AddRecordInfoDesired( gc_schema.GetRecordInfo() );
+ }
+
+ // add all the FTS catalogs to the desired schema
+ for ( int n = 0; n < GSchemaFull().GetCFTSCatalogs(); n++ )
+ {
+ const CFTSCatalogInfo &refInfo = GSchemaFull().GetFTSCatalogInfo( n );
+ pSchemaUpdate->AddFTSInfo( refInfo );
+ }
+
+ pSchemaUpdate->AddTriggerInfos( GSchemaFull().GetTriggerInfos() );
+
+ SQLRETURN nRet = YieldingEnsureDatabaseSchemaCorrect( eSchemaCatalog, pSchemaUpdate );
+ if( !SQL_OK( nRet ) )
+ {
+ AssertMsg( false, "SQL Schema Update failed" );
+ return false;
+ }
+
+ SAFE_RELEASE( pSchemaUpdate );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Examines actual running schema of database, compares to specified desired
+// schema, and changes the actual schema to correspond to desired schema
+// Input: pSQLThread - SQL thread to execute on
+// pSchemaUpdate - pointer to object with desired schema
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingEnsureDatabaseSchemaCorrect( ESchemaCatalog eSchemaCatalog, CSchemaUpdate *pSchemaUpdate )
+{
+ Assert( pSchemaUpdate );
+
+ CMapPRecordInfo &mapPRecordInfoDesired = pSchemaUpdate->m_mapPRecordInfoDesired;
+ const CUtlVector< CTriggerInfo > &vecTriggerInfoDesired = pSchemaUpdate->m_vecTriggerInfo;
+ m_eConversionMode = pSchemaUpdate->m_eConversionMode;
+
+ bool bDoConversion = true;
+// bool bDoConversion = ( ( k_EConversionModeConvertSafe == eConversionMode ) ||
+// ( k_EConversionModeConvertIrreversible == eConversionMode ) );
+
+ CMapPRecordInfo mapPRecordInfoActual;
+ mapPRecordInfoActual.SetLessFunc( CaselessStringLessThan );
+ CUtlVector<CRecordInfo *> vecPRecordInfoDesiredMissing;
+ CUtlVector<CRecordInfo *> vecPRecordInfoActualDifferent;
+ CUtlVector<CRecordInfo *> vecPRecordInfoActualUnknown;
+ CUtlVector< CTriggerInfo > vecTriggerInfoActual;
+ CUtlVector< CTriggerInfo > vecTriggerInfoMissing;
+ CUtlVector< CTriggerInfo > vecTriggerInfoDifferent;
+
+ pSchemaUpdate->m_cTablesDesiredMissing = 0;
+ pSchemaUpdate->m_cTablesActualDifferent = 0;
+ pSchemaUpdate->m_cTablesActualUnknown = 0;
+ pSchemaUpdate->m_cTablesNeedingChange = 0;
+ pSchemaUpdate->m_cColumnsDesiredMissing = 0;
+ pSchemaUpdate->m_cColumnsActualDifferent = 0;
+ pSchemaUpdate->m_cColumnsActualUnknown = 0;
+ pSchemaUpdate->m_sDetail.Clear();
+
+ CFmtStr1024 &sDetail = pSchemaUpdate->m_sDetail;
+
+ CFastTimer tickCounterOverall;
+ tickCounterOverall.Start();
+
+ //
+ // Do some up-front bookkeeping to see how many tables need to be created and/or altered
+ //
+
+ int nSchemaID;
+ SQLRETURN nRet = YieldingGetSchemaID( eSchemaCatalog, &nSchemaID );
+ EXIT_ON_SQL_FAILURE( nRet );
+
+ // Determine the actual running schema
+ nRet = YieldingGetRecordInfoForAllTables( eSchemaCatalog, nSchemaID, mapPRecordInfoActual );
+ EXIT_ON_SQL_FAILURE( nRet );
+
+ nRet = YieldingGetTriggers( eSchemaCatalog, nSchemaID, vecTriggerInfoActual );
+ EXIT_ON_SQL_FAILURE( nRet );
+
+ // Look through the list of desired tables, find any that are missing or different from the actual schema
+ FOR_EACH_MAP_FAST( mapPRecordInfoDesired, iRecordInfoDesired )
+ {
+ // is this desired table in the currently connected catalog?
+ CRecordInfo *pRecordInfoDesired = mapPRecordInfoDesired[iRecordInfoDesired];
+ if ( pRecordInfoDesired->GetESchemaCatalog() == eSchemaCatalog )
+ {
+ // yes. do something about it
+ int iRecordInfoActual = mapPRecordInfoActual.Find( pRecordInfoDesired->GetName() );
+ if ( mapPRecordInfoDesired.InvalidIndex() == iRecordInfoActual )
+ {
+ // This table is desired but does not exist
+ vecPRecordInfoDesiredMissing.AddToTail( pRecordInfoDesired );
+ }
+ else
+ {
+ // Table with same name exists in desired & actual schemas; is it exactly the same?
+ CRecordInfo *pRecordInfoActual = mapPRecordInfoActual[iRecordInfoActual];
+ if ( !pRecordInfoDesired->EqualTo( pRecordInfoActual ) )
+ {
+ // This desired table exists but the actual table is different than desired
+ vecPRecordInfoActualDifferent.AddToTail( pRecordInfoActual );
+ }
+ }
+ }
+ }
+
+ // Now, look through the list of actual tables and find any that do not exist in the list of desired tables
+ FOR_EACH_MAP_FAST( mapPRecordInfoActual, iRecordInfoActual )
+ {
+ CRecordInfo *pRecordInfoActual = mapPRecordInfoActual[iRecordInfoActual];
+ int iRecordInfoDesired = mapPRecordInfoDesired.Find( pRecordInfoActual->GetName() );
+ if ( !mapPRecordInfoDesired.IsValidIndex( iRecordInfoDesired ) )
+ {
+ // This table exists but is not in the list of desired tables
+ // maybe it's an old table.
+
+ vecPRecordInfoActualUnknown.AddToTail( pRecordInfoActual );
+ }
+ }
+
+ // find a list of missing triggers
+ FOR_EACH_VEC( vecTriggerInfoDesired, iDesired )
+ {
+ // not of this catalog? skip it
+ if ( vecTriggerInfoDesired[ iDesired ].m_eSchemaCatalog != eSchemaCatalog )
+ continue;
+
+ // it is our catalog, so try and match
+ bool bMatched = false;
+ FOR_EACH_VEC( vecTriggerInfoActual, iActual )
+ {
+ // is it the same table and trigger name?
+ if ( vecTriggerInfoActual[ iActual ] == vecTriggerInfoDesired[ iDesired ] )
+ {
+ // yes! test the text for differences
+ if ( vecTriggerInfoActual[ iActual ].IsDifferent( vecTriggerInfoDesired[ iDesired ] ) )
+ {
+ vecTriggerInfoDifferent.AddToTail( vecTriggerInfoDesired[ iDesired ] );
+ }
+ else
+ {
+ // we have a match!
+ vecTriggerInfoActual[ iActual ].m_bMatched = true;
+ bMatched = true;
+ }
+ break;
+ }
+ }
+
+ if ( !bMatched )
+ {
+ vecTriggerInfoMissing.AddToTail( vecTriggerInfoDesired[ iDesired ] );
+ }
+ }
+
+ //
+ // Now do the actual conversion
+ //
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "Database conversion: %s\n",
+ bDoConversion ? "beginning" : "inspection only" );
+
+
+ // find tables which need to be created
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "# of specified tables that do not currently exist in database: %d\n",
+ vecPRecordInfoDesiredMissing.Count() );
+ if ( vecPRecordInfoDesiredMissing.Count() > 0 )
+ pSchemaUpdate->m_bConversionNeeded = true;
+
+ // Create any tables which need to be created
+ for ( int iTable = 0; iTable < vecPRecordInfoDesiredMissing.Count(); iTable++ )
+ {
+ CRecordInfo *pRecordInfoDesired = vecPRecordInfoDesiredMissing[iTable];
+ if ( bDoConversion )
+ {
+ CFastTimer tickCounter;
+ tickCounter.Start();
+
+ // Create the table
+ nRet = YieldingCreateTable( eSchemaCatalog, pRecordInfoDesired );
+ EXIT_ON_SQL_FAILURE( nRet );
+ tickCounter.End();
+ int nElapsedMilliseconds = tickCounter.GetDuration().GetMilliseconds();
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tCreated table: %s: %d millisec\n", pRecordInfoDesired->GetName(), nElapsedMilliseconds );
+ }
+ else
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t%s\n", pRecordInfoDesired->GetName() );
+ }
+
+ // find tables which are different
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: # of specified tables that differ from schema in database: %d\n",
+ vecPRecordInfoActualDifferent.Count() );
+
+#ifdef _DEBUG
+ // are some different? if so, list their names only for now.
+ // This is in _debug only because it's useful for debugging the below loop,
+ // but spewey for everyday life (as long as the below loop is working).
+ if ( vecPRecordInfoActualDifferent.Count() > 0 )
+ {
+ FOR_EACH_VEC( vecPRecordInfoActualDifferent, i )
+ {
+ CRecordInfo *pRecordInfoActual = vecPRecordInfoActualDifferent[i];
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: table %s is different\n",
+ pRecordInfoActual->GetName() );
+ }
+ }
+#endif
+
+ pSchemaUpdate->m_bSkippedAChange = false;
+
+ // Alter any table which needs to be altered
+ for ( int iTable = 0; iTable < vecPRecordInfoActualDifferent.Count(); iTable++ )
+ {
+ CRecordInfo *pRecordInfoActual = vecPRecordInfoActualDifferent[iTable];
+ int iRecordInfoDesired = mapPRecordInfoDesired.Find( pRecordInfoActual->GetName() );
+ Assert( mapPRecordInfoDesired.InvalidIndex() != iRecordInfoDesired );
+ CRecordInfo *pRecordInfoDesired = mapPRecordInfoDesired[iRecordInfoDesired];
+
+ CUtlVector<const CColumnInfo *> vecPColumnInfoDesiredMissing;
+ CUtlVector<const CColumnInfo *> vecPColumnInfoActualDifferent;
+ CUtlVector<const CColumnInfo *> vecPColumnInfoActualUnknown;
+
+ // We know something is different between the actual & desired schema for this table, but don't yet know what
+
+ // Compare each column
+ for ( int iColumnDesired = 0; iColumnDesired < pRecordInfoDesired->GetNumColumns(); iColumnDesired ++ )
+ {
+ const CColumnInfo &columnInfoDesired = pRecordInfoDesired->GetColumnInfo( iColumnDesired );
+ int iColumnActual = -1;
+ bool bRet = pRecordInfoActual->BFindColumnByName( columnInfoDesired.GetName(), &iColumnActual );
+ if ( bRet )
+ {
+ const CColumnInfo &columnInfoActual = pRecordInfoActual->GetColumnInfo( iColumnActual );
+ if ( columnInfoActual.GetChecksum() != columnInfoDesired.GetChecksum() )
+ {
+ // The actual column is different than the desired column
+ vecPColumnInfoActualDifferent.AddToTail( &columnInfoActual );
+ }
+ }
+ else
+ {
+ // The desired column is missing from the actual table
+ vecPColumnInfoDesiredMissing.AddToTail( &columnInfoDesired );
+ }
+ }
+ for ( int iColumnActual = 0; iColumnActual < pRecordInfoActual->GetNumColumns(); iColumnActual ++ )
+ {
+ const CColumnInfo &columnInfoActual = pRecordInfoActual->GetColumnInfo( iColumnActual );
+ int iColumnDesired = -1;
+ bool bRet = pRecordInfoDesired->BFindColumnByName( columnInfoActual.GetName(), &iColumnDesired );
+ if ( !bRet )
+ {
+ // this column exists in the running schema, but not in the desired schema (e.g. old column)
+ vecPColumnInfoActualUnknown.AddToTail( &columnInfoActual );
+ }
+ }
+
+ if ( ( vecPColumnInfoDesiredMissing.Count() > 0 ) || ( vecPColumnInfoActualDifferent.Count() > 0 ) )
+ {
+ pSchemaUpdate->m_bConversionNeeded = true;
+ pSchemaUpdate->m_cTablesNeedingChange++;
+ }
+
+ // Add any desired columns which are missing from the actual schema
+ if ( vecPColumnInfoDesiredMissing.Count() > 0 )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\tDesired columns missing in table %s:\n", pRecordInfoActual->GetName() );
+
+ for ( int iColumn = 0; iColumn < vecPColumnInfoDesiredMissing.Count(); iColumn++ )
+ {
+ const CColumnInfo *pColumnInfoDesired = vecPColumnInfoDesiredMissing[iColumn];
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t\t%s\n", pColumnInfoDesired->GetName() );
+ if ( bDoConversion )
+ {
+ CFastTimer tickCounter;
+ tickCounter.Start();
+ // Add the column
+ nRet = YieldingAlterTableAddColumn( eSchemaCatalog, pRecordInfoActual, pColumnInfoDesired );
+ EXIT_ON_SQL_FAILURE( nRet );
+ tickCounter.End();
+ int nElapsedMilliseconds = tickCounter.GetDuration().GetMilliseconds();
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t\t\tCreated column %s: %d millisec\n", pColumnInfoDesired->GetName(), nElapsedMilliseconds );
+ }
+ }
+ }
+
+ // Check for any stray indices that aren't found in the specification?
+ bool bIndexMismatch = false;
+ for ( int idx = 0 ; idx < pRecordInfoActual->GetIndexFieldCount() ; ++idx )
+ {
+ const FieldSet_t &fs = pRecordInfoActual->GetIndexFields()[idx];
+ if ( pRecordInfoDesired->FindIndex( pRecordInfoActual, fs ) >= 0 )
+ continue;
+ if ( pRecordInfoDesired->FindIndexByName( fs.GetIndexName() ) >= 0 )
+ continue; // we already handled this above
+ bIndexMismatch = true;
+
+ if ( idx == pRecordInfoActual->GetPKIndex() )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tTable %s primary key in database differs from specification.\n",
+ pRecordInfoDesired->GetName() );
+ nRet = YieldingProcessUnsafeConversion( eSchemaCatalog,
+ CFmtStr("ALTER TABLE App%u.%s DROP CONSTRAINT %s", GGCBase()->GetAppID(), pRecordInfoActual->GetName(), fs.GetIndexName() ).String(),
+ CFmtStr("%s: remove old primary key.", pRecordInfoDesired->GetName() ).String() );
+ EXIT_ON_SQL_FAILURE( nRet );
+ }
+ else
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tTable %s index %s exists in database but is not in specification. (Possible performance problem?).\n",
+ pRecordInfoDesired->GetName(), fs.GetIndexName() );
+ nRet = YieldingProcessUnsafeConversion( eSchemaCatalog,
+ CFmtStr("DROP INDEX %s ON App%u.%s", fs.GetIndexName(), GGCBase()->GetAppID(), pRecordInfoActual->GetName() ).String(),
+ CFmtStr("%s: cleanup stray index %s.", pRecordInfoDesired->GetName(), fs.GetIndexName() ).String() );
+ EXIT_ON_SQL_FAILURE( nRet );
+ }
+ }
+
+ // Change any columns which are different between desired and actual schema
+ if ( vecPColumnInfoActualDifferent.Count() > 0 )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tColumns that differ between specification and database in table %s:\n",
+ pRecordInfoActual->GetName() );
+ for ( int iColumn = 0; iColumn < vecPColumnInfoActualDifferent.Count(); iColumn++ )
+ {
+ const CColumnInfo *pColumnInfoActual = vecPColumnInfoActualDifferent[iColumn];
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t%s", pColumnInfoActual->GetName() );
+ int iColumnDesired = -1;
+ DbgVerify( pRecordInfoDesired->BFindColumnByName( pColumnInfoActual->GetName(), &iColumnDesired ) );
+ const CColumnInfo *pColumnInfoDesired = &pRecordInfoDesired->GetColumnInfo( iColumnDesired );
+
+ // if type or size changed, alter the column
+ if ( ( pColumnInfoDesired->GetType() != pColumnInfoActual->GetType() ) ||
+ // fixed length field, and the sizes differ
+ ( ! pColumnInfoDesired->BIsVariableLength() &&
+ ( pColumnInfoDesired->GetFixedSize() != pColumnInfoActual->GetFixedSize() ) ) ||
+ // variable length field, and the sizes differ
+ // fixed length field, and the sizes differ
+ ( pColumnInfoDesired->BIsVariableLength() &&
+ ( pColumnInfoDesired->GetMaxSize() != pColumnInfoActual->GetMaxSize() ) ) )
+ {
+ if ( k_EConversionModeConvertIrreversible != m_eConversionMode )
+ {
+ pSchemaUpdate->m_bSkippedAChange = true;
+ if ( pColumnInfoDesired->GetType() != pColumnInfoActual->GetType() )
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t(data types differ: desired=%s, actual=%s)", PchNameFromEGCSQLType( pColumnInfoDesired->GetType() ), PchNameFromEGCSQLType( pColumnInfoActual->GetType() ) );
+ if ( pColumnInfoDesired->GetFixedSize() != pColumnInfoActual->GetFixedSize() )
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t(column sizes differ: desired=%d, actual=%d)", pColumnInfoDesired->GetFixedSize(), pColumnInfoActual->GetFixedSize() );
+ if ( pColumnInfoDesired->GetMaxSize() != pColumnInfoActual->GetMaxSize() )
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t(maximum column sizes differ: desired=%d, actual=%d)", pColumnInfoDesired->GetMaxSize(), pColumnInfoActual->GetMaxSize() );
+ }
+ nRet = YieldingChangeColumnTypeOrLength( eSchemaCatalog, pRecordInfoActual, pColumnInfoDesired );
+ EXIT_ON_SQL_FAILURE( nRet );
+ }
+
+ // If column constraints/indexes are different, make appropriate adjustments
+ // do this second so it has a chance to succeed - otherwise, we may create an index here that
+ // prevents us from performing an alter table
+ if ( pColumnInfoDesired->GetColFlags() != pColumnInfoActual->GetColFlags() )
+ {
+ if ( k_EConversionModeConvertIrreversible != m_eConversionMode )
+ {
+ char szDesiredFlags[k_nKiloByte];
+ char szActualFlags[k_nKiloByte];
+ pColumnInfoDesired->GetColFlagDescription( szDesiredFlags, Q_ARRAYSIZE( szDesiredFlags ) );
+ pColumnInfoActual->GetColFlagDescription( szActualFlags, Q_ARRAYSIZE( szActualFlags ) );
+
+ pSchemaUpdate->m_bSkippedAChange = true;
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t(column flags differ: desired=\"%s\", actual=\"%s\")",
+ szDesiredFlags, szActualFlags );
+ }
+ nRet = YieldingChangeColumnProperties( eSchemaCatalog, pRecordInfoActual, pColumnInfoActual, pColumnInfoDesired );
+ EXIT_ON_SQL_FAILURE( nRet );
+ }
+
+ if ( pSchemaUpdate->m_bSkippedAChange )
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t(Not attempting unsafe change).\n" );
+ }
+ }
+
+ // Scan for any new / changed indices
+ for ( int idx = 0 ; idx < pRecordInfoDesired->GetIndexFieldCount() ; ++idx )
+ {
+ const FieldSet_t &fs = pRecordInfoDesired->GetIndexFields()[idx];
+ int iActualIdx = pRecordInfoActual->FindIndex( pRecordInfoDesired, fs );
+ if ( iActualIdx >= 0 )
+ continue;
+ bIndexMismatch = true;
+
+ // The exact index we want doesn't exist. Is it the primary key?
+ CUtlString sCommand;
+ CUtlString sComment;
+ if ( idx == pRecordInfoDesired->GetPKIndex() )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tTable %s primary key in specification differs from database.\n",
+ pRecordInfoDesired->GetName() );
+ sComment.Format( "%s: create new primary key constraint", pRecordInfoDesired->GetName() );
+ TSQLCmdStr cmd;
+ BuildTablePKConstraintText( &cmd, pRecordInfoDesired );
+ for ( int i = 0 ; i < fs.GetCount() ; ++i ) // make sure they are non-NULL
+ {
+ int idxField = fs.GetField(i);
+ const CColumnInfo *pColInfo = &pRecordInfoDesired->GetColumnInfo( idxField );
+ Assert( pColInfo->BIsPrimaryKey() );
+ sCommand += GetAlterColumnText( pRecordInfoDesired, pColInfo );
+ sCommand += ";\n";
+ }
+
+ CUtlString sCreatePK;
+ sCreatePK.Format( "GO\nALTER TABLE App%u.%s ADD %s\n", GGCBase()->GetAppID(), pRecordInfoDesired->GetName(), cmd.String() );
+ sCommand += sCreatePK;
+ }
+ else
+ {
+
+ // Another common thing that could happen is that an index is changed.
+ // Look for an existing index with the same name as a way to try to
+ // detect this common case and provide a more specific message. (Otherwise,
+ // we will report it as a missing index, and an extra unwanted index --- which
+ // is correct but a bit more confusing.)
+
+ iActualIdx = pRecordInfoActual->FindIndexByName( fs.GetIndexName() );
+ if ( iActualIdx < 0 )
+ {
+ sComment.Format("%s: add index %s", pRecordInfoDesired->GetName(), fs.GetIndexName() );
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tTable %s index %s is specified but not present in database.\n",
+ pRecordInfoDesired->GetName(), fs.GetIndexName() );
+ }
+ else
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tTable %s index %s differs between specification and database.\n",
+ pRecordInfoDesired->GetName(), fs.GetIndexName() );
+ sComment.Format( "%s: fix index %s", pRecordInfoDesired->GetName(), fs.GetIndexName() );
+ sCommand.Format( "DROP INDEX %s ON App%u.%s;\n", fs.GetIndexName(), GGCBase()->GetAppID(), pRecordInfoDesired->GetName() );
+ }
+ sCommand += GetAddIndexSQL( pRecordInfoDesired, fs );
+ }
+ nRet = YieldingProcessUnsafeConversion( eSchemaCatalog, sCommand, sComment );
+ EXIT_ON_SQL_FAILURE( nRet );
+ }
+
+ // Just to be safe, let's run the old code, too.
+ if ( !bIndexMismatch && !pRecordInfoActual->CompareIndexLists( pRecordInfoDesired ) )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tIndex sets in table %s differ between specification and database [GC]:\n",
+ pRecordInfoDesired->GetName() );
+ CFmtStr1024 sTemp;
+
+ pRecordInfoDesired->GetIndexFieldList( &sTemp, 3 );
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\tDesired %s", sTemp.Access() );
+
+ pRecordInfoActual->GetIndexFieldList( &sTemp, 3 );
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\tActual %s", sTemp.Access() );
+ }
+
+ // what about foreign key constraints?
+ if ( ! pRecordInfoActual->CompareFKs( pRecordInfoDesired ) )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tForeign Key constraints in table %s differs between specification and database [GC]:\n",
+ pRecordInfoDesired->GetName() );
+
+ CFmtStr1024 sTemp;
+ pRecordInfoDesired->GetFKListString( &sTemp, 3 );
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\tDesired %s", sTemp.Access() );
+
+ pRecordInfoActual->GetFKListString( &sTemp, 3 );
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\tActual %s", sTemp.Access() );
+ }
+
+
+
+ if ( vecPColumnInfoActualUnknown.Count() > 0 )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tColumns in database [GC] table %s that are not in specification (ignored):\n",
+ pRecordInfoActual->GetName() );
+
+ // Since this can actually destroy data, let's not ever, ever run it automatically.
+ CUtlString sCommand;
+ sCommand.Format( "ALTER TABLE App%u.%s DROP COLUMN ", GGCBase()->GetAppID(), pRecordInfoActual->GetName() );
+
+ for ( int iColumn = 0; iColumn < vecPColumnInfoActualUnknown.Count(); iColumn++ )
+ {
+ const CColumnInfo *pColumnInfo = vecPColumnInfoActualUnknown[iColumn];
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t%s\n", pColumnInfo->GetName() );
+ if ( iColumn != 0 )
+ sCommand += ", ";
+ sCommand += pColumnInfo->GetName();
+ }
+ AddDataDestroyingConversion( sCommand,
+ CFmtStr( "-- Drop extra column(s) in %s\n", pRecordInfoActual->GetName() ) );
+ }
+
+ pSchemaUpdate->m_cColumnsDesiredMissing += vecPColumnInfoDesiredMissing.Count();
+ pSchemaUpdate->m_cColumnsActualDifferent += vecPColumnInfoActualDifferent.Count();
+ pSchemaUpdate->m_cColumnsActualUnknown += vecPColumnInfoActualUnknown.Count();
+ }
+
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: # of tables that currently exist in database but were unspecified: %d\n",
+ vecPRecordInfoActualUnknown.Count() );
+ for ( int iTable = 0; iTable < vecPRecordInfoActualUnknown.Count(); iTable++ )
+ {
+ CRecordInfo *pRecordInfo = vecPRecordInfoActualUnknown[iTable];
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t%s\n", pRecordInfo->GetName() );
+ AddDataDestroyingConversion(
+ CFmtStr( "DROP TABLE App%u.%s\n", GGCBase()->GetAppID(), pRecordInfo->GetName() ),
+ CFmtStr( "-- Drop extra table %s\n", pRecordInfo->GetName() ) );
+ }
+
+ // then, the triggers
+ if ( vecTriggerInfoMissing.Count() > 0 )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: # of specified triggers that do not currently exist: %d\n",
+ vecTriggerInfoMissing.Count() );
+
+ FOR_EACH_VEC( vecTriggerInfoMissing, iMissing )
+ {
+ CFastTimer tickCounter;
+ tickCounter.Start();
+
+ // Create the trigger
+ nRet = YieldingCreateTrigger( eSchemaCatalog, vecTriggerInfoMissing[ iMissing] );
+ EXIT_ON_SQL_FAILURE( nRet );
+ tickCounter.End();
+ int nElapsedMilliseconds = tickCounter.GetDuration().GetMilliseconds();
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: Created trigger %s on table %s: %d millisec\n",
+ vecTriggerInfoMissing[ iMissing ].m_szTriggerName,
+ vecTriggerInfoMissing[ iMissing ].m_szTriggerTableName, nElapsedMilliseconds );
+ }
+ }
+
+ // different triggers
+ FOR_EACH_VEC( vecTriggerInfoDifferent, iDifferent )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: Trigger %s on table %s differs from the desired trigger\n", vecTriggerInfoMissing[ iDifferent ].m_szTriggerName,
+ vecTriggerInfoMissing[ iDifferent ].m_szTriggerTableName);
+
+ // a different trigger text is a forced failure.
+ nRet = SQL_ERROR;
+ }
+
+ // extra triggers
+ FOR_EACH_VEC( vecTriggerInfoActual, iActual )
+ {
+ // if it was never matched, it isn't in the schema anywhere
+ if ( ! vecTriggerInfoActual[ iActual ].m_bMatched )
+ {
+ SQLRETURN nSQLReturn = YieldingDropTrigger( eSchemaCatalog, vecTriggerInfoActual[ iActual ] );
+
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: Trigger %s on table %s is not in the declared schema ... Drop %s",
+ vecTriggerInfoActual[ iActual ].m_szTriggerName,
+ vecTriggerInfoActual[ iActual ].m_szTriggerTableName,
+ SQL_OK( nSQLReturn ) ? "OK" : "FAILED!" ) ;
+
+ if ( !SQL_OK ( nSQLReturn ) )
+ {
+ // it broke; latch the failure
+ nRet = nSQLReturn;
+ }
+ }
+ }
+
+ tickCounterOverall.End();
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: Total time: %d milliseconds\n",
+ tickCounterOverall.GetDuration().GetMilliseconds() );
+
+Exit:
+
+ // Spew suggested SQL to clean up stuff
+ if ( !m_sRecommendedSQL.IsEmpty() || !m_sDataDestroyingCleanupSQL.IsEmpty() )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tThe following SQL might work to fixup schema differences.\n" );
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t** \n" );
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t** DISCLAIMER ** This conversion code is not well tested. Review the SQL and use at your own risk.\n" );
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t** \n" );
+ {
+ CUtlVectorAutoPurge<char*> vecLines;
+ V_SplitString( m_sRecommendedSQL, "\n", vecLines );
+ FOR_EACH_VEC( vecLines, i )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t%s\n", vecLines[i] );
+ }
+ m_sRecommendedSQL.Clear();
+ }
+ if ( !m_sDataDestroyingCleanupSQL.IsEmpty() )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t-- WARNING: The following operations will *destroy* data that is in the database but not in the specification.\n" );
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t-- If you have manually created extra tables or columns in your database, it will appear here!\n" );
+ CUtlVectorAutoPurge<char*> vecLines;
+ V_SplitString( m_sDataDestroyingCleanupSQL, "\n", vecLines );
+ FOR_EACH_VEC( vecLines, i )
+ {
+ EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t%s\n", vecLines[i] );
+ }
+ m_sDataDestroyingCleanupSQL.Clear();
+ }
+ }
+
+ pSchemaUpdate->m_cTablesDesiredMissing = vecPRecordInfoDesiredMissing.Count();
+ pSchemaUpdate->m_cTablesActualDifferent = vecPRecordInfoActualDifferent.Count();
+ pSchemaUpdate->m_cTablesActualUnknown = vecPRecordInfoActualUnknown.Count();
+
+ FOR_EACH_MAP_FAST( mapPRecordInfoActual, iRecordInfoActual )
+ SAFE_RELEASE( mapPRecordInfoActual[iRecordInfoActual] );
+
+ return nRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds a record info for each table in database that describes the
+// columns in that table
+// Input: pSQLConnection - SQL connection to execute on
+// mapPRecordInfo - map to add the table record infos to
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingGetSchemaID( ESchemaCatalog eSchemaCatalog, int *pSchemaID )
+{
+ CSQLAccess sqlAccess( eSchemaCatalog );
+ CFmtStr1024 sDefaultSchema;
+ if( !sqlAccess.BYieldingExecuteString( FILE_AND_LINE, "SELECT default_schema_name FROM sys.database_principals WHERE name=CURRENT_USER",
+ &sDefaultSchema ) )
+ return SQL_ERROR;
+
+ CFmtStr sExpectedDefaultSchema( "App%u", GGCBase()->GetAppID() );
+ if ( 0 != Q_stricmp( sDefaultSchema, sExpectedDefaultSchema ) )
+ {
+ AssertMsg2( false, "SQL connection has the wrong default schema. Expected: %s. Actual %s", sExpectedDefaultSchema.Get(), sDefaultSchema.Get() );
+ return SQL_ERROR;
+ }
+
+ sqlAccess.AddBindParam( sDefaultSchema );
+ if( !sqlAccess.BYieldingExecuteScalarInt( FILE_AND_LINE, "SELECT SCHEMA_ID(?)", pSchemaID ) )
+ return SQL_ERROR;
+
+ return SQL_SUCCESS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds a record info for each table in database that describes the
+// columns in that table
+// Input: pSQLConnection - SQL connection to execute on
+// mapPRecordInfo - map to add the table record infos to
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingGetRecordInfoForAllTables( ESchemaCatalog eSchemaCatalog, int nSchemaID, CMapPRecordInfo &mapPRecordInfo )
+{
+ CSQLAccess sqlAccess( eSchemaCatalog );
+
+ // create a query that returns all tables in the database
+ sqlAccess.AddBindParam( nSchemaID );
+ RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, "SELECT object_id, name FROM sys.objects WHERE type_desc = 'USER_TABLE' AND is_ms_shipped = 0 AND schema_id = ?" ) );
+
+ FOR_EACH_SQL_RESULT( sqlAccess, 0, tableIDRecord )
+ {
+ int nTableID;
+ RETURN_SQL_ERROR_ON_FALSE( tableIDRecord.BGetIntValue( 0, &nTableID ) );
+
+ CFmtStr1024 sTableName;
+ RETURN_SQL_ERROR_ON_FALSE( tableIDRecord.BGetStringValue( 1, &sTableName) );
+
+ YieldingGetColumnInfoForTable( eSchemaCatalog, mapPRecordInfo, nTableID, sTableName );
+ }
+
+
+ // now, get the column indexes and constraints for each table
+ FOR_EACH_MAP_FAST( mapPRecordInfo, iRecordInfo )
+ {
+ CRecordInfo *pRecordInfo = mapPRecordInfo[iRecordInfo];
+ pRecordInfo->SetAllColumnsAdded();
+
+ // determine indexes and constraints
+ YieldingGetColumnIndexes( eSchemaCatalog, pRecordInfo );
+
+ // determine FK constraints
+ YieldingGetTableFKConstraints( eSchemaCatalog, pRecordInfo );
+
+ // do final calculations then ready to use
+ pRecordInfo->PrepareForUse();
+ }
+
+ return SQL_SUCCESS;
+}
+
+SQLRETURN CJobUpdateSchema::YieldingGetColumnInfoForTable( ESchemaCatalog eSchemaCatalog, CMapPRecordInfo &mapPRecordInfo, int nTableID, const char *pchTableName )
+{
+ CSQLAccess sqlAccess( eSchemaCatalog );
+
+ sqlAccess.AddBindParam( nTableID );
+ RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, "SELECT name, column_id, user_type_id, max_length, is_identity FROM sys.columns WHERE object_id = ?") );
+
+ CRecordInfo *pRecordInfo = CRecordInfo::Alloc();
+ pRecordInfo->SetName( pchTableName );
+ pRecordInfo->SetTableID( nTableID );
+ FOR_EACH_SQL_RESULT( sqlAccess, 0, columnInfo )
+ {
+ CFmtStr1024 sColumnName;
+ int nType;
+ int nColumnID;
+ int nMaxLength;
+ bool bIdentity;
+ RETURN_SQL_ERROR_ON_FALSE( columnInfo.BGetStringValue( 0, &sColumnName) );
+ RETURN_SQL_ERROR_ON_FALSE( columnInfo.BGetIntValue( 1, &nColumnID ) );
+ RETURN_SQL_ERROR_ON_FALSE( columnInfo.BGetIntValue( 2, &nType ) );
+ RETURN_SQL_ERROR_ON_FALSE( columnInfo.BGetIntValue( 3, &nMaxLength ) );
+ RETURN_SQL_ERROR_ON_FALSE( columnInfo.BGetBoolValue( 4, &bIdentity) );
+
+ int nColFlags = 0;
+ if( bIdentity )
+ nColFlags |= k_nColFlagAutoIncrement;
+
+ pRecordInfo->AddColumn( sColumnName, nColumnID, GetEGCSQLTypeForMSSQLType( nType ), nMaxLength, nColFlags, nMaxLength );
+ }
+ mapPRecordInfo.Insert( pRecordInfo->GetName(), pRecordInfo );
+
+ return SQL_SUCCESS;
+}
+
+
+//-----------------------------------------------------------------------------
+// purpose: get a list of triggers from the database.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingGetTriggers( ESchemaCatalog eSchemaCatalog, int nSchemaID, CUtlVector< CTriggerInfo > &vecTriggerInfo )
+{
+ CSQLAccess sqlAccess( eSchemaCatalog );
+
+ // get some description and the text of the triggers on this database.
+ // Find the name of the trigger, the name of the table it servers, the type of
+ // trigger, and its text. Doesn't bring back any disabled or MS-shipped triggers,
+ // and gets only DML triggers and not DDL triggers.
+ const char *pchStatement = "SELECT ST.name AS TriggerName, SOT.name AS TableName, ST.is_instead_of_trigger, SC.Text, SC.ColID"
+ " FROM sys.triggers AS ST"
+ " JOIN sys.syscomments AS SC ON SC.id = ST.object_id"
+ " JOIN sys.objects AS SO ON SO.object_id = ST.object_id"
+ " JOIN sys.objects AS SOT on SOT.object_id = ST.parent_id"
+ " WHERE ST.type_desc = 'SQL_TRIGGER'"
+ " AND ST.is_ms_shipped = 0 AND ST.is_disabled = 0"
+ " AND ST.parent_class = 1"
+ " AND SO.schema_id = ?"
+ " ORDER BY TableName, TriggerName, SC.ColID";
+ sqlAccess.AddBindParam( nSchemaID );
+ RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, pchStatement ) );
+
+ // should be one results set
+ Assert( 1 == sqlAccess.GetResultSetCount() );
+
+ FOR_EACH_SQL_RESULT( sqlAccess, 0, sqlRecord )
+ {
+ // get the text of the procedure
+ const char *pchText;
+ DbgVerify( sqlRecord.BGetStringValue( 3, &pchText ) );
+
+ // is this instead of?
+ bool bIsInsteadOf;
+ DbgVerify( sqlRecord.BGetBoolValue( 2, &bIsInsteadOf ) );
+
+ // get the table name
+ const char *pchTableName;
+ DbgVerify( sqlRecord.BGetStringValue( 1, &pchTableName ) );
+
+ // and the trigger name
+ const char *pchTriggerName;
+ DbgVerify( sqlRecord.BGetStringValue( 0, &pchTriggerName ) );
+
+ // finally, grab the collation id
+ int16 nColID;
+ DbgVerify( sqlRecord.BGetInt16Value( 4, &nColID ) );
+
+ if ( nColID == 1 )
+ {
+ // the collation ID is one, so we know this is a new one
+ CTriggerInfo info;
+ info.m_strText = pchText;
+ Q_strncpy( info.m_szTriggerName, pchTriggerName, Q_ARRAYSIZE( info.m_szTriggerName ) );
+ Q_strncpy( info.m_szTriggerTableName, pchTableName, Q_ARRAYSIZE( info.m_szTriggerTableName ) );
+ info.m_eSchemaCatalog = eSchemaCatalog;
+ vecTriggerInfo.AddToTail( info );
+ }
+ else
+ {
+ // the collation ID is not one, so we're concatenating.
+ Assert( vecTriggerInfo.Count() - 1 >= 0 );
+
+ // the name could not have changed
+ Assert( 0 == Q_strcmp( vecTriggerInfo[vecTriggerInfo.Count() - 1].m_szTriggerName, pchTriggerName ) );
+ }
+ }
+
+ return SQL_SUCCESS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: retrieves the index information for the specified table, then adds this
+// information to the record info. This is a SQL Server-specific implementation
+// which gets data describing index features not available through plain ODBC
+// queries.
+// Input: pSQLConnection - SQL connection to execute on
+// pRecordInfo - CRecordInfo to add the index info into
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingGetColumnIndexes( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo )
+{
+ // query the system management views for all of the indexes on this table
+ static const char *pstrStatement =
+ "SELECT SI.Name AS INDEX_NAME, SC.Name AS COLUMN_NAME, SI.Type AS [TYPE], IS_INCLUDED_COLUMN, SIC.KEY_Ordinal AS [ORDINAL_POSITION], IS_UNIQUE, IS_PRIMARY_KEY, SI.INDEX_ID"
+ " FROM sys.indexes SI "
+ " JOIN sys.index_columns SIC"
+ " ON SIC.Object_id = SI.Object_Id"
+ " AND SIC.Index_ID = SI.Index_id"
+ " JOIN sys.objects SO"
+ " ON SIC.Object_ID = SO.Object_ID"
+ " JOIN sys.columns SC"
+ " ON SC.Object_ID = SO.Object_ID"
+ " AND SC.column_id = SIC.column_id"
+ " WHERE SO.Object_ID = ? "
+ "ORDER BY SIC.Index_id, SIC.Key_Ordinal ";
+ CSQLAccess sqlAccess( eSchemaCatalog );
+ sqlAccess.AddBindParam( pRecordInfo->GetTableID() );
+ RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, pstrStatement ) );
+
+ // builds a list of columns in a particular index.
+ CUtlVector<int> vecColumns;
+ CUtlVector<int> vecIncluded;
+ int nLastIndexID = -1;
+ bool bIsClustered = false;
+ bool bIsIndexUnique = false;
+ bool bIsPrimaryKey = false;
+ CFmtStr1024 sIndexName, sColumnName;
+
+ FOR_EACH_SQL_RESULT( sqlAccess, 0, typeRecord )
+ {
+ // Starting a new index?
+ int nIndexID;
+ DbgVerify( typeRecord.BGetIntValue( 7, &nIndexID ) );
+ if ( nIndexID != nLastIndexID )
+ {
+ // first column! is it our first time through?
+ if ( vecColumns.Count() > 0 )
+ {
+ // yes. let's add what we had from the previous index
+ // to the fieldset
+ FieldSet_t fs( bIsIndexUnique, bIsClustered, vecColumns, sIndexName );
+ fs.AddIncludedColumns( vecIncluded );
+ int idx = pRecordInfo->AddIndex( fs );
+ if ( bIsPrimaryKey )
+ {
+ Assert( bIsIndexUnique );
+ Assert( pRecordInfo->GetPKIndex() < 0 );
+ pRecordInfo->SetPKIndex( idx );
+ }
+
+ // reset the vector
+ vecColumns.RemoveAll();
+ vecIncluded.RemoveAll();
+ bIsIndexUnique = false;
+ bIsClustered = false;
+ bIsPrimaryKey = false;
+ }
+ nLastIndexID = nIndexID;
+ }
+
+ int nTypeID, nOrdinalPosition;
+ bool bIsIncluded, bIsColumnUnique;
+
+ DbgVerify( typeRecord.BGetStringValue( 0, &sIndexName ) );
+ DbgVerify( typeRecord.BGetStringValue( 1, &sColumnName ) );
+ DbgVerify( typeRecord.BGetIntValue( 2, &nTypeID ) );
+ DbgVerify( typeRecord.BGetBoolValue( 3, &bIsIncluded ) );
+ DbgVerify( typeRecord.BGetIntValue( 4, &nOrdinalPosition ) );
+ DbgVerify( typeRecord.BGetBoolValue( 5, &bIsColumnUnique ) );
+ DbgVerify( typeRecord.BGetBoolValue( 6, &bIsPrimaryKey ) );
+
+ RETURN_SQL_ERROR_ON_FALSE( sColumnName.Length() > 0 );
+
+ int nColumnIndexed = -1;
+ RETURN_SQL_ERROR_ON_FALSE( pRecordInfo->BFindColumnByName( sColumnName, &nColumnIndexed ) );
+
+ CColumnInfo & columnInfo = pRecordInfo->GetColumnInfo( nColumnIndexed );
+ int nColFlags = 0;
+
+ if ( bIsIncluded )
+ {
+ Assert( nOrdinalPosition == 0 );
+ // it's included; no flags
+ vecIncluded.AddToTail( nColumnIndexed );
+ }
+ else
+ {
+ Assert( nOrdinalPosition != 0 );
+ if ( bIsPrimaryKey )
+ {
+ // if we're working a primary key, mark those flags
+ nColFlags = k_nColFlagPrimaryKey | k_nColFlagIndexed | k_nColFlagUnique;
+ // PKs are always unique
+ bIsIndexUnique = true;
+ }
+ else
+ {
+ // if we're working a "regular" index, we need to know the uniqueness of the index ...
+
+ nColFlags = k_nColFlagIndexed;
+ if ( bIsColumnUnique )
+ {
+ nColFlags |= k_nColFlagUnique;
+ bIsIndexUnique = true;
+ }
+ }
+
+ // clustering type
+ if ( nTypeID == SQL_INDEX_CLUSTERED )
+ {
+ nColFlags |= k_nColFlagClustered;
+ bIsClustered = true;
+ }
+ columnInfo.SetColFlagBits( nColFlags );
+
+ // add this column to our list for the index set
+ vecColumns.AddToTail( nColumnIndexed );
+ }
+ }
+
+ // anything left over?
+ if ( vecColumns.Count() > 0 )
+ {
+ // yep, add that, too
+ FieldSet_t fs( bIsIndexUnique, bIsClustered, vecColumns, sIndexName );
+ fs.AddIncludedColumns( vecIncluded );
+ int idx = pRecordInfo->AddIndex( fs );
+ if ( bIsPrimaryKey )
+ {
+ Assert( bIsIndexUnique );
+ Assert( pRecordInfo->GetPKIndex() < 0 );
+ pRecordInfo->SetPKIndex( idx );
+ }
+ }
+
+ return SQL_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the schema info on any FK constraints defined for the table
+// Input: pRecordInfo - CRecordInfo to add the index info into (and lookup table name out of)
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingGetTableFKConstraints( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo )
+{
+ // Used below, declared up here because of goto
+ FKData_t fkData;
+
+ CSQLAccess sqlAccess( eSchemaCatalog );
+ const char *pchStatement = "SELECT fk.name AS FKName, current_table.name AS TableName, parent_table.name AS ParentTableName, "
+ "table_column.name AS ColName, parent_table_column.name AS ParentColName, "
+ "fk.delete_referential_action_desc AS OnDelete, "
+ "fk.update_referential_action_desc AS OnUpdate "
+ "FROM sys.objects AS current_table "
+ "JOIN sys.foreign_keys AS fk ON fk.parent_object_id=current_table.object_id AND fk.is_ms_shipped=0 "
+ "JOIN sys.foreign_key_columns AS fk_col ON fk_col.constraint_object_id=fk.object_id "
+ "JOIN sys.objects AS parent_table ON parent_table.object_id=fk_col.referenced_object_id "
+ "JOIN sys.columns AS table_column ON table_column.object_id=fk_col.parent_object_id AND table_column.column_id=fk_col.parent_column_id "
+ "JOIN sys.columns AS parent_table_column ON parent_table_column.object_id=fk_col.referenced_object_id AND parent_table_column.column_id=fk_col.referenced_column_id "
+ "WHERE current_table.object_id = ? ORDER BY fk.name";
+ sqlAccess.AddBindParam( pRecordInfo->GetTableID() );
+ RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, pchStatement ) );
+
+ FOR_EACH_SQL_RESULT( sqlAccess, 0, sqlRecord )
+ {
+ // get all the data for the FK
+ const char *pchFKName;
+ DbgVerify( sqlRecord.BGetStringValue( 0, &pchFKName ) );
+
+ const char *pchTableName;
+ DbgVerify( sqlRecord.BGetStringValue( 1, &pchTableName ) );
+
+ AssertMsg( Q_stricmp( pchTableName, pRecordInfo->GetName() ) == 0, "FOREIGN KEY schema conversion found FK for table not matching search!\n" );
+
+ const char *pchParentTable;
+ DbgVerify( sqlRecord.BGetStringValue( 2, &pchParentTable ) );
+
+ const char *pchColName;
+ DbgVerify( sqlRecord.BGetStringValue( 3, &pchColName ) );
+
+ const char *pchParentColName;
+ DbgVerify( sqlRecord.BGetStringValue( 4, &pchParentColName ) );
+
+ const char *pchOnDelete;
+ DbgVerify( sqlRecord.BGetStringValue( 5, &pchOnDelete ) );
+
+ const char *pchOnUpdate;
+ DbgVerify( sqlRecord.BGetStringValue( 6, &pchOnUpdate ) );
+
+ // Is this more data for the FK we are already tracking? If so just append the column data,
+ // otherwise, assuming some data exists, add the key to the record info
+ if ( Q_strcmp( fkData.m_rgchName, pchFKName ) == 0 )
+ {
+ int iColRelation = fkData.m_VecColumnRelations.AddToTail();
+ FKColumnRelation_t &colRelation = fkData.m_VecColumnRelations[iColRelation];
+ Q_strncpy( colRelation.m_rgchCol, pchColName, Q_ARRAYSIZE( colRelation.m_rgchCol ) );
+ Q_strncpy( colRelation.m_rgchParentCol, pchParentColName, Q_ARRAYSIZE( colRelation.m_rgchParentCol ) );
+ }
+ else
+ {
+ if ( Q_strlen( fkData.m_rgchName ) )
+ {
+ pRecordInfo->AddFK( fkData );
+ }
+
+ // Do initial setup of the new key
+ Q_strncpy( fkData.m_rgchName, pchFKName, Q_ARRAYSIZE( fkData.m_rgchName ) );
+ Q_strncpy( fkData.m_rgchParentTableName, pchParentTable, Q_ARRAYSIZE( fkData.m_rgchParentTableName ) );
+ fkData.m_eOnDeleteAction = EForeignKeyActionFromName( pchOnDelete );
+ fkData.m_eOnUpdateAction = EForeignKeyActionFromName( pchOnUpdate );
+ int iColRelation = fkData.m_VecColumnRelations.AddToTail();
+ FKColumnRelation_t &colRelation = fkData.m_VecColumnRelations[iColRelation];
+ Q_strncpy( colRelation.m_rgchCol, pchColName, Q_ARRAYSIZE( colRelation.m_rgchCol ) );
+ Q_strncpy( colRelation.m_rgchParentCol, pchParentColName, Q_ARRAYSIZE( colRelation.m_rgchParentCol ) );
+ }
+ }
+
+
+ // Add the last key we were building data for
+ if ( Q_strlen( fkData.m_rgchName ) )
+ {
+ pRecordInfo->AddFK( fkData );
+ }
+
+ return SQL_SUCCESS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a table in the DB to match the recordinfo
+// Input: pRecordInfo - CRecordInfo to create a table for
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingCreateTable( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo )
+{
+ CFmtStrMax sCmd;
+ SQLRETURN nRet = 0;
+ const char *pchName = pRecordInfo->GetName();
+ Assert( pchName && pchName[0] );
+ CSQLAccess sqlAccess( eSchemaCatalog );
+
+ // build the create table command
+ sCmd.sprintf( "CREATE TABLE %s (", pchName );
+
+ // add all the columns
+ for ( int iColumn = 0; iColumn < pRecordInfo->GetNumColumns(); iColumn++ )
+ {
+ char rgchType[k_cSmallBuff];
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
+ char *pchType = SQLTypeFromField( columnInfo, rgchType, Q_ARRAYSIZE( rgchType ) );
+ Assert( pchType[0] );
+
+ // add the name and type of this column
+ sCmd.AppendFormat( "%s %s", columnInfo.GetName(), pchType );
+
+ // add any constraints
+ AppendConstraints( pRecordInfo, &columnInfo, true, sCmd );
+
+ if ( iColumn < ( pRecordInfo->GetNumColumns() - 1 ) )
+ sCmd += ", ";
+ }
+
+ AppendTableConstraints( pRecordInfo, sCmd );
+ sCmd += ")";
+
+
+ // create the table
+ // metadata operations aren't transactional, so we'll set bAutoTransaction = true
+ EXIT_ON_BOOLSQL_FAILURE( sqlAccess.BYieldingExecute( FILE_AND_LINE, sCmd ) );
+
+ // add any indexes
+ for ( int n = 0; n < pRecordInfo->GetIndexFields( ).Count(); n++ )
+ {
+ const FieldSet_t& refSet = pRecordInfo->GetIndexFields( )[ n ];
+
+ // already added the PK index, so skip it
+ if ( n == pRecordInfo->GetPKIndex() )
+ continue;
+
+ // call YieldingAddIndex to do the work
+ nRet = YieldingAddIndex( eSchemaCatalog, pRecordInfo, refSet );
+ EXIT_ON_SQL_FAILURE( nRet );
+ }
+
+Exit:
+ return nRet;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds an index of multiple columns to a table.
+// Input: pSQLConnection - connection to use for command
+// pRecordInfo - record info describing table
+// refFields - description of index to add
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingAddIndex( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const FieldSet_t &refFields )
+{
+ CSQLAccess sqlAccess( eSchemaCatalog );
+ CUtlString sCmd = GetAddIndexSQL( pRecordInfo, refFields );
+ RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, sCmd ) );
+ return SQL_SUCCESS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Depending on the conversion mode, either really perfom the
+// conversion, or just log the SQL text so a human being can review
+// it and do it later.
+//-----------------------------------------------------------------------------
+
+SQLRETURN CJobUpdateSchema::YieldingProcessUnsafeConversion( ESchemaCatalog eSchemaCatalog, const char *pszSQL, const char *pszComment )
+{
+ if ( k_EConversionModeConvertIrreversible != m_eConversionMode )
+ {
+ if ( pszComment )
+ {
+ m_sRecommendedSQL.Append( "-- " );
+ m_sRecommendedSQL.Append( pszComment );
+ m_sRecommendedSQL.Append( "\n" );
+ }
+ m_sRecommendedSQL.Append( pszSQL );
+ m_sRecommendedSQL.Append("\nGO\n \n"); // that space is a kludge, because we are using V_splitlines, which will ignore consecutive seperators
+ return SQL_SUCCESS;
+ }
+ CSQLAccess sqlAccess( eSchemaCatalog );
+ RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, pszSQL ) );
+ return SQL_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a SQL command to cleanup the schema that will actually destroy (supposedly unused) data.
+//-----------------------------------------------------------------------------
+
+void CJobUpdateSchema::AddDataDestroyingConversion( const char *pszSQL, const char *pszComment )
+{
+ if ( pszComment )
+ {
+ m_sDataDestroyingCleanupSQL.Append( "-- " );
+ m_sDataDestroyingCleanupSQL.Append( pszComment );
+ m_sDataDestroyingCleanupSQL.Append( "\n" );
+ }
+ m_sDataDestroyingCleanupSQL.Append( pszSQL );
+ m_sDataDestroyingCleanupSQL.Append("\nGO\n \n"); // that space is a kludge, because we are using V_splitlines, which will ignore consecutive seperators
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a column to a table
+// Input: pRecordInfo - record info describing table to add a column to
+// pColumnInfo - column info describing column to add
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingAlterTableAddColumn( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo )
+{
+ CSQLAccess sqlAccess( eSchemaCatalog );
+ CFmtStrMax sCmd;
+ char rgchType[k_cSmallBuff];
+ const char *pchTableName = pRecordInfo->GetName();
+ Assert( pchTableName );
+ const char *pchColumnName = pColumnInfo->GetName();
+ Assert( pchColumnName );
+ DbgVerify( SQLTypeFromField( *pColumnInfo, rgchType, Q_ARRAYSIZE( rgchType ) )[0] );
+
+ // build the alter table command
+ sCmd.sprintf( "ALTER TABLE %s ADD %s %s", pchTableName, pchColumnName, rgchType );
+
+ // add any constraints
+ AppendConstraints( pRecordInfo, pColumnInfo, true, sCmd );
+
+ // !KLUDGE! This is guaranteed to fail if it has "not null" in it.
+ if ( V_strstr( sCmd, "NOT NULL" ) )
+ {
+ EmitError( SPEW_SQL, "Cannot add column %s to table %s with NOT NULL constraint\n", pchColumnName, pchTableName );
+ EmitInfo( SPEW_SQL, SPEW_ALWAYS, LOG_ALWAYS, "Here is the SQL that should be run to add the column:\n" );
+ EmitInfo( SPEW_SQL, SPEW_ALWAYS, LOG_ALWAYS, " %s\n", sCmd.String() );
+ return SQL_ERROR;
+ }
+
+ // execute the command.
+ RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, sCmd ) );
+ return SQL_SUCCESS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a constraint to an existing column
+// Input: hDBC - SQL connection to execute on
+// pRecordInfo - record info describing table
+// pColumnInfo - column info describing column to add contraint for
+// nColFlagConstraint - constraint to add
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingAddConstraint( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo, int nColFlagConstraint )
+{
+ Assert( pRecordInfo );
+ Assert( pColumnInfo );
+
+ CFmtStrMax sCmd;
+ sCmd.sprintf( "ALTER TABLE App%u.%s ADD", GGCBase()->GetAppID(), pRecordInfo->GetName() );
+
+ Assert( !pColumnInfo->BIsClustered() );
+ AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), nColFlagConstraint, true, pColumnInfo->BIsClustered(), sCmd, 0 );
+
+ sCmd.AppendFormat( " (%s)", pColumnInfo->GetName() );
+
+ return YieldingProcessUnsafeConversion( eSchemaCatalog, sCmd );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Changes type or length of existing column
+// Input: hDBC - SQL connection to execute on
+// pRecordInfo - record info describing table
+// pColumnInfoDesired - column info describing new column type or length
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingChangeColumnTypeOrLength( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfoDesired )
+{
+ CUtlString sCmd = GetAlterColumnText( pRecordInfo, pColumnInfoDesired );
+ return YieldingProcessUnsafeConversion( eSchemaCatalog, sCmd );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Changes constraints/indexes on a column
+// Input: hDBC - SQL connection to execute on
+// pRecordInfo - record info describing table
+// pColumnInfoActual - column info describing existing column properties
+// pColumnInfoActual - column info describing desired new column properties
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingChangeColumnProperties( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfoActual,
+ const CColumnInfo *pColumnInfoDesired )
+{
+ CSQLAccess sqlAccess( eSchemaCatalog );
+ Assert( pRecordInfo );
+ Assert( pColumnInfoActual );
+ Assert( pColumnInfoDesired );
+
+ SQLRETURN nRet = SQL_SUCCESS;
+
+ pColumnInfoActual->ValidateColFlags();
+ pColumnInfoDesired->ValidateColFlags();
+
+ Assert( pColumnInfoActual->GetColFlags() != pColumnInfoDesired->GetColFlags() );
+
+ // all the operations we might have to perform; note we have have to drop one
+ // thing and add another
+ bool bAddExplicitIndex = false, bRemoveExplicitIndex = false;
+ bool bAddUniqueConstraint = false, bRemoveUniqueConstraint = false;
+ bool bAddPrimaryKeyConstraint = false, bRemovePrimaryKeyConstraint = false;
+
+ // determine which operations need to be performed
+ if ( !pColumnInfoActual->BIsPrimaryKey() && pColumnInfoDesired->BIsPrimaryKey() )
+ {
+ bAddPrimaryKeyConstraint = true;
+ }
+ else if ( pColumnInfoActual->BIsPrimaryKey() && !pColumnInfoDesired->BIsPrimaryKey() )
+ {
+ bRemovePrimaryKeyConstraint = true;
+ }
+
+ if ( !pColumnInfoActual->BIsExplicitlyUnique() && pColumnInfoDesired->BIsExplicitlyUnique() )
+ {
+ bAddUniqueConstraint = true;
+ }
+ else if ( pColumnInfoActual->BIsExplicitlyUnique() && !pColumnInfoDesired->BIsExplicitlyUnique() )
+ {
+ bRemoveUniqueConstraint = true;
+ }
+
+ if ( !pColumnInfoActual->BIsExplicitlyIndexed() && pColumnInfoDesired->BIsExplicitlyIndexed() )
+ {
+ bAddExplicitIndex = true;
+ }
+ else if ( pColumnInfoActual->BIsExplicitlyIndexed() && !pColumnInfoDesired->BIsExplicitlyIndexed() )
+ {
+ bRemoveExplicitIndex = true;
+ }
+
+ // sanity check
+ Assert( !( bAddUniqueConstraint && bAddPrimaryKeyConstraint ) ); // primary key constraint adds implicit uniqueness constraint; it's a bug if we decide to do both
+ Assert( !( bAddUniqueConstraint && bAddExplicitIndex ) ); // uniqueness constraint adds implicit index; it's a bug if we decide to do both
+ Assert( !( bAddPrimaryKeyConstraint && bAddExplicitIndex ) ); // primary key constraint adds implicit index; it's a bug if we decide to do both
+
+ // The index conversion stuff already handles dropping of unexpected indices
+ // and primary key constraints. I'm not even sure that this works or is used anymore.
+ if ( bAddUniqueConstraint )
+ {
+ nRet = YieldingAddConstraint( eSchemaCatalog, pRecordInfo, pColumnInfoActual, k_nColFlagUnique );
+ EXIT_ON_SQL_FAILURE( nRet );
+ }
+
+Exit:
+ return nRet;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates specified trigger
+// Input: pSQLConnection - SQL connection to execute on
+// refTriggerInfo - trigger to be created
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingCreateTrigger( ESchemaCatalog eSchemaCatalog, CTriggerInfo &refTriggerInfo )
+{
+ char rgchCmd[ k_cchSQLStatementTextMax];
+ CSQLAccess sqlAccess( eSchemaCatalog );
+
+ // build the create command
+ Q_snprintf( rgchCmd, Q_ARRAYSIZE( rgchCmd ),
+ "CREATE TRIGGER [%s] ON [%s] "
+ "%s "
+ "AS BEGIN"
+ "%s\n"
+ "END",
+ refTriggerInfo.m_szTriggerName,
+ refTriggerInfo.m_szTriggerTableName,
+ refTriggerInfo.GetTriggerTypeString(),
+ refTriggerInfo.m_strText.Get() );
+
+ RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, rgchCmd ) );
+ return SQL_SUCCESS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: drops specified trigger
+// Input: pSQLConnection - SQL connection to execute on
+// refTriggerInfo - trigger to be dropped
+// Output: SQL return code.
+//-----------------------------------------------------------------------------
+SQLRETURN CJobUpdateSchema::YieldingDropTrigger( ESchemaCatalog eSchemaCatalog, CTriggerInfo &refTriggerInfo )
+{
+ char rgchCmd[ k_cchSQLStatementTextMax];
+ CSQLAccess sqlAccess( eSchemaCatalog );
+
+ // build the create command
+ Q_snprintf( rgchCmd, Q_ARRAYSIZE( rgchCmd ),
+ "DROP TRIGGER [%s];",
+ refTriggerInfo.m_szTriggerName );
+
+ RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, rgchCmd ) );
+ return SQL_SUCCESS;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Used for SQL/EGCSQLType conversion
+//-----------------------------------------------------------------------------
+struct SQLTypeMapping_t
+{
+ const char *m_pchTypeName;
+ EGCSQLType m_eType;
+};
+
+static SQLTypeMapping_t g_rSQLTypeMapping[] =
+{
+ { "tinyint", k_EGCSQLType_int8 },
+ { "smallint", k_EGCSQLType_int16 },
+ { "int", k_EGCSQLType_int32 },
+ { "bigint", k_EGCSQLType_int64 },
+ { "real", k_EGCSQLType_float },
+ { "float", k_EGCSQLType_double },
+ { "text", k_EGCSQLType_String },
+ { "ntext", k_EGCSQLType_String },
+ { "char", k_EGCSQLType_String },
+ { "varchar", k_EGCSQLType_String },
+ { "nchar", k_EGCSQLType_String },
+ { "nvarchar", k_EGCSQLType_String },
+ { "sysname", k_EGCSQLType_String },
+ { "varbinary", k_EGCSQLType_Blob },
+ { "image", k_EGCSQLType_Image },
+};
+static uint32 g_cSQLTypeMapping = Q_ARRAYSIZE( g_rSQLTypeMapping );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the EGCSQLType for the specified SQL type name (or Invalid
+// for unsupported types.)
+//-----------------------------------------------------------------------------
+EGCSQLType ETypeFromMSSQLDataType( const char *pchType )
+{
+ for( uint32 unMapping = 0; unMapping < g_cSQLTypeMapping; unMapping++ )
+ {
+ if( !Q_stricmp( pchType, g_rSQLTypeMapping[ unMapping ].m_pchTypeName ) )
+ return g_rSQLTypeMapping[ unMapping ].m_eType;
+ }
+ return k_EGCSQLTypeInvalid;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Prepares the type map for use in schema upgrades
+//-----------------------------------------------------------------------------
+bool CJobUpdateSchema::YieldingBuildTypeMap( ESchemaCatalog eSchemaCatalog )
+{
+ CSQLAccess sqlAccess( eSchemaCatalog );
+ if( !sqlAccess.BYieldingExecute( FILE_AND_LINE, "select name, system_type_id from sys.types" ) )
+ return false;
+
+ FOR_EACH_SQL_RESULT( sqlAccess, 0, typeRecord )
+ {
+ CFmtStr1024 sTypeName;
+ byte nTypeID;
+ DbgVerify( typeRecord.BGetStringValue( 0, &sTypeName ) );
+ DbgVerify( typeRecord.BGetByteValue( 1, &nTypeID ) );
+
+ EGCSQLType eType = ETypeFromMSSQLDataType( sTypeName );
+ if( eType != k_EGCSQLTypeInvalid )
+ m_mapSQLTypeToEType.Insert( nTypeID, eType );
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the EGCSQLType for the specified type ID out of the database
+//-----------------------------------------------------------------------------
+EGCSQLType CJobUpdateSchema::GetEGCSQLTypeForMSSQLType( int nType )
+{
+ int nIndex = m_mapSQLTypeToEType.Find( nType );
+ if( m_mapSQLTypeToEType.IsValidIndex( nIndex ) )
+ return m_mapSQLTypeToEType[ nIndex ];
+ else
+ return k_EGCSQLTypeInvalid;
+}
+
+} // namespace GCSDK
diff --git a/gcsdk/sqlaccess/sqlaccess.cpp b/gcsdk/sqlaccess/sqlaccess.cpp
new file mode 100644
index 0000000..130a446
--- /dev/null
+++ b/gcsdk/sqlaccess/sqlaccess.cpp
@@ -0,0 +1,953 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Provides access to SQL at a high level
+//
+//=============================================================================
+
+#include "stdafx.h"
+#include "gcsdk/sqlaccess/sqlaccess.h"
+#include "gcsdk/gcsqlquery.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+template< typename LISTENER_FUNC >
+static void RunAndClearListenerList( std::vector< LISTENER_FUNC > &vecListeners )
+{
+ // Let us not underestimate the ability of random listeners to re-enter everything.
+ std::vector< LISTENER_FUNC > listenerCopy;
+ listenerCopy.swap( vecListeners );
+ vecListeners.clear();
+
+ // Why would you consider such a thing
+ DO_NOT_YIELD_THIS_SCOPE();
+
+ for ( const auto &listener : listenerCopy )
+ {
+ listener();
+ }
+}
+
+
+namespace GCSDK
+{
+//------------------------------------------------------------------------------------
+// Purpose: Constructor
+//------------------------------------------------------------------------------------
+CSQLAccess::CSQLAccess( ESchemaCatalog eSchemaCatalog )
+ : m_eSchemaCatalog( eSchemaCatalog)
+ , m_pCurrentQuery( NULL )
+ , m_bInTransaction( false )
+{
+ m_pQueryGroup = CGCSQLQueryGroup::Alloc();
+}
+
+
+//------------------------------------------------------------------------------------
+// Purpose: Destructor
+//------------------------------------------------------------------------------------
+CSQLAccess::~CSQLAccess( )
+{
+ SAFE_RELEASE( m_pQueryGroup );
+ Assert( !m_pCurrentQuery );
+ SAFE_DELETE( m_pCurrentQuery );
+ AssertMsg( !m_bInTransaction, "GCSDK::CSQLAccess object being destroyed with a transaction pending. Use BCommitTransaction or RollbackTransaction to match your BBeginTransaction call." );
+}
+
+
+//------------------------------------------------------------------------------------
+// Purpose: Perform a query
+//------------------------------------------------------------------------------------
+bool CSQLAccess::BYieldingExecute( const char *pchName, const char *pchSQLCommand, uint32 *pcRowsAffected, bool bSpewOnError )
+{
+ if ( NULL == pchName )
+ {
+ pchName = pchSQLCommand;
+ }
+
+ bool bStandalone = !BInTransaction();
+ if( bStandalone )
+ {
+ BBeginTransaction( pchName );
+ }
+
+ CurrentQuery()->SetCommand( pchSQLCommand );
+ m_pQueryGroup->AddQuery( m_pCurrentQuery );
+ m_pCurrentQuery = NULL;
+
+ bool bSuccess = true;
+ if( bStandalone )
+ {
+ bSuccess = BCommitTransaction();
+ if( bSuccess && pcRowsAffected )
+ {
+ *pcRowsAffected = m_pQueryGroup->GetResults()->GetRowsAffected( 0 );
+ }
+ }
+ return bSuccess;
+}
+
+
+//------------------------------------------------------------------------------------
+// Purpose: Starts a transaction
+//------------------------------------------------------------------------------------
+bool CSQLAccess::BBeginTransaction( const char *pchName )
+{
+ Assert( !m_bInTransaction );
+ if( m_bInTransaction )
+ return false;
+ m_pQueryGroup->Clear();
+ m_pQueryGroup->SetName( pchName );
+ m_bInTransaction = true;
+ return true;
+}
+
+//------------------------------------------------------------------------------------
+// Purpose: Returns the string last passed to BBeginTransaction
+//------------------------------------------------------------------------------------
+const char *CSQLAccess::PchTransactionName( ) const
+{
+ return m_pQueryGroup->PchName();
+}
+
+
+//------------------------------------------------------------------------------------
+// Purpose: Commits a transaction to the database
+//------------------------------------------------------------------------------------
+bool CSQLAccess::BCommitTransaction( bool bAllowEmpty )
+{
+ Assert( BInTransaction() );
+ if( !BInTransaction() )
+ return false;
+
+ if( !m_pCurrentQuery && !m_pQueryGroup->GetStatementCount() )
+ {
+ if( bAllowEmpty )
+ {
+ // No-op success
+ m_bInTransaction = false;
+ RunListeners_Commit();
+ return true;
+ }
+ else
+ {
+ AssertMsg1( false, "BCommitTransaction with empty transaction at %s", m_pQueryGroup->PchName() );
+ return false;
+ }
+ }
+
+ AssertMsg1( !m_pCurrentQuery, "Unexecuted query present in BCommitTransaction: %s", m_pCurrentQuery->PchCommand() );
+ if( m_pCurrentQuery )
+ return false;
+
+ m_bInTransaction = false;
+
+ if( !GJobCur().BYieldingRunQuery( m_pQueryGroup, m_eSchemaCatalog ) )
+ {
+ // Notify listeners that the transaction did not succeed
+ RunListeners_Rollback();
+ return false;
+ }
+
+ // The transaction presumably did make the database, so we do not notify rollback listeners beyond here.
+ RunListeners_Commit();
+
+ if( !m_pQueryGroup->GetResults() )
+ return false;
+
+ return true;
+}
+
+
+//------------------------------------------------------------------------------------
+// Purpose: Rolls back a transaction and clears any queries
+//------------------------------------------------------------------------------------
+void CSQLAccess::RollbackTransaction()
+{
+ bool bWasTransaction = BInTransaction();
+
+ Assert( bWasTransaction );
+ SAFE_DELETE( m_pCurrentQuery );
+ m_bInTransaction = false;
+
+ if ( bWasTransaction )
+ {
+ RunListeners_Rollback();
+ }
+ else
+ {
+ m_vecCommitListeners.clear();
+ m_vecRollbackListeners.clear();
+ }
+}
+
+//------------------------------------------------------------------------------------
+// Purpose: Adds a listener to be called synchronously should the transaction successfully commit
+//------------------------------------------------------------------------------------
+void CSQLAccess::AddCommitListener( std::function<void (void)> &&listener )
+{
+ if ( !BInTransaction() )
+ {
+ AssertMsg( BInTransaction(), "Adding a listener to a non-transaction access, will never fire" );
+ return;
+ }
+
+ m_vecCommitListeners.push_back( std::move( listener ) );
+}
+
+//------------------------------------------------------------------------------------
+// Purpose: Adds a listener to be called synchronously should the transaction fail or explicitly rollback
+//------------------------------------------------------------------------------------
+void CSQLAccess::AddRollbackListener( std::function<void (void)> &&listener )
+{
+ if ( !BInTransaction() )
+ {
+ AssertMsg( BInTransaction(), "Adding a listener to a non-transaction access, will never fire" );
+ return;
+ }
+
+ m_vecRollbackListeners.push_back( std::move( listener ) );
+}
+
+//------------------------------------------------------------------------------------
+// Purpose: Notifies listeners of successful commit.
+//------------------------------------------------------------------------------------
+void CSQLAccess::RunListeners_Commit()
+{
+ RunAndClearListenerList( m_vecCommitListeners );
+ // Clear the unused set
+ m_vecRollbackListeners.clear();
+}
+
+//------------------------------------------------------------------------------------
+// Purpose: Notifies listeners of a implicitly or explicitly rolled back transactions and clears the listener list.
+//------------------------------------------------------------------------------------
+void CSQLAccess::RunListeners_Rollback()
+{
+ RunAndClearListenerList( m_vecRollbackListeners );
+ // Clear the unused set
+ m_vecCommitListeners.clear();
+}
+
+//------------------------------------------------------------------------------------
+// Purpose: Perform a query that returns a single string
+//------------------------------------------------------------------------------------
+CSQLAccess::EReadSingleResultResult CSQLAccess::BYieldingExecuteSingleResultDataInternal( const char *pchName, const char *pchSQLCommand, EGCSQLType eType, uint8 **ppubData, uint32 *punSize, uint32 *pcRowsAffected, bool bHasDefaultValue )
+{
+ AssertMsg( !BInTransaction(), "BYieldingExecuteSingleResultData is not supported in a transaction" );
+ if( BInTransaction() )
+ return eReadSingle_Error;
+
+ bool bRet = BYieldingExecute( pchName, pchSQLCommand, pcRowsAffected );
+ if ( !bRet )
+ return eReadSingle_Error;
+
+ if( m_pQueryGroup->GetResults()->GetResultSetCount() != 1 )
+ {
+ AssertMsg1( false, "Expected single result set, found %d", m_pQueryGroup->GetResults()->GetResultSetCount() );
+ return eReadSingle_Error;
+ }
+
+ IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( 0 );
+
+ // If we have a default value, getting back zero rows is acceptable.
+ if( pResultSet->GetRowCount() == 0 && bHasDefaultValue )
+ {
+ return eReadSingle_UseDefault;
+ }
+
+ // If we either have more than one row or no default value specified, that's an error.
+ if( pResultSet->GetRowCount() != 1 )
+ {
+ AssertMsg1( false, "Expected single result, found %d", pResultSet->GetRowCount() );
+ return eReadSingle_Error;
+ }
+
+ if( pResultSet->GetColumnCount() != 1 )
+ {
+ AssertMsg1( false, "Expected single column, found %d", pResultSet->GetColumnCount() );
+ return eReadSingle_Error;
+ }
+ if( pResultSet->GetColumnType( 0 ) != eType )
+ {
+ AssertMsg2( false, "Expected column of type %s, found %s", PchNameFromEGCSQLType( eType ), PchNameFromEGCSQLType( pResultSet->GetColumnType( 0 ) ) );
+ return eReadSingle_Error;
+ }
+
+ return pResultSet->GetData( 0, 0, ppubData, punSize )
+ ? eReadSingle_ResultFound
+ : eReadSingle_Error;
+}
+
+
+
+
+//------------------------------------------------------------------------------------
+// Purpose: Perform a query that returns a single string
+//------------------------------------------------------------------------------------
+bool CSQLAccess::BYieldingExecuteString( const char *pchName, const char *pchSQLCommand, CFmtStr1024 *psResult, uint32 *pcRowsAffected )
+{
+ uint8 *pubData;
+ uint32 cubData;
+ if( CSQLAccess::BYieldingExecuteSingleResultDataInternal( pchName, pchSQLCommand, k_EGCSQLType_String, &pubData, &cubData, pcRowsAffected, false ) != eReadSingle_ResultFound )
+ return false;
+
+ *psResult = (char *)pubData;
+
+ return true;
+}
+
+//------------------------------------------------------------------------------------
+// Purpose: Perform a query that returns a single int
+//------------------------------------------------------------------------------------
+bool CSQLAccess::BYieldingExecuteScalarInt( const char *pchName, const char *pchSQLCommand, int *pnResult, uint32 *pcRowsAffected )
+{
+ return BYieldingExecuteSingleResult<int32, uint32>( pchName, pchSQLCommand, k_EGCSQLType_int32, pnResult, pcRowsAffected );
+}
+
+bool CSQLAccess::BYieldingExecuteScalarIntWithDefault( const char *pchName, const char *pchSQLCommand, int *pnResult, int iDefaultValue, uint32 *pcRowsAffected )
+{
+ return BYieldingExecuteSingleResultWithDefault<int32, uint32>( pchName, pchSQLCommand, k_EGCSQLType_int32, pnResult, iDefaultValue, pcRowsAffected );
+}
+
+//------------------------------------------------------------------------------------
+// Purpose: Perform a query that returns a single uint32
+//------------------------------------------------------------------------------------
+bool CSQLAccess::BYieldingExecuteScalarUint32( const char *pchName, const char *pchSQLCommand, uint32 *punResult, uint32 *pcRowsAffected )
+{
+ return BYieldingExecuteSingleResult<uint32, uint32>( pchName, pchSQLCommand, k_EGCSQLType_int32, punResult, pcRowsAffected );
+}
+
+bool CSQLAccess::BYieldingExecuteScalarUint32WithDefault( const char *pchName, const char *pchSQLCommand, uint32 *punResult, uint32 unDefaultValue, uint32 *pcRowsAffected )
+{
+ return BYieldingExecuteSingleResultWithDefault<uint32, uint32>( pchName, pchSQLCommand, k_EGCSQLType_int32, punResult, unDefaultValue, pcRowsAffected );
+}
+
+//------------------------------------------------------------------------------------
+// Purpose: A bunch of pass throughs to the query itself
+//------------------------------------------------------------------------------------
+void CSQLAccess::AddBindParam( const char *pchValue )
+{
+ CurrentQuery()->AddBindParam( pchValue );
+}
+
+void CSQLAccess::AddBindParam( const int16 nValue )
+{
+ CurrentQuery()->AddBindParam( nValue );
+}
+
+void CSQLAccess::AddBindParam( const uint16 uValue )
+{
+ CurrentQuery()->AddBindParam( uValue );
+}
+
+void CSQLAccess::AddBindParam( const int32 nValue )
+{
+ CurrentQuery()->AddBindParam( nValue );
+}
+
+void CSQLAccess::AddBindParam( const uint32 uValue )
+{
+ CurrentQuery()->AddBindParam( uValue );
+}
+
+void CSQLAccess::AddBindParam( const uint64 ulValue )
+{
+ CurrentQuery()->AddBindParam( ulValue );
+}
+
+void CSQLAccess::AddBindParam( const uint8 *ubValue, const int cubValue )
+{
+ CurrentQuery()->AddBindParam( ubValue, cubValue );
+}
+
+void CSQLAccess::AddBindParam( const float fValue )
+{
+ CurrentQuery()->AddBindParam( fValue );
+}
+
+void CSQLAccess::AddBindParam( const double dValue )
+{
+ CurrentQuery()->AddBindParam( dValue );
+}
+
+void CSQLAccess::AddBindParamRaw( EGCSQLType eType, const byte *pubData, uint32 cubData )
+{
+ CurrentQuery()->AddBindParamRaw( eType, pubData, cubData );
+}
+
+void CSQLAccess::ClearParams()
+{
+ if( m_pCurrentQuery )
+ {
+ delete m_pCurrentQuery;
+ m_pCurrentQuery = NULL;
+ }
+}
+
+
+IGCSQLResultSetList *CSQLAccess::GetResults()
+{
+ return m_pQueryGroup->GetResults();
+}
+
+
+//------------------------------------------------------------------------------------
+// Purpose: Returns the number of result sets
+//------------------------------------------------------------------------------------
+uint32 CSQLAccess::GetResultSetCount()
+{
+ if( m_pQueryGroup->GetResults() )
+ return m_pQueryGroup->GetResults()->GetResultSetCount();
+ else
+ return 0;
+}
+
+
+//------------------------------------------------------------------------------------
+// Purpose: Returns the number of rows in a result set
+//------------------------------------------------------------------------------------
+uint32 CSQLAccess::GetResultSetRowCount( uint32 unResultSet )
+{
+ if( m_pQueryGroup->GetResults() && unResultSet < m_pQueryGroup->GetResults()->GetResultSetCount() )
+ return m_pQueryGroup->GetResults()->GetResultSet( unResultSet )->GetRowCount();
+ else
+ return 0;
+}
+
+
+//------------------------------------------------------------------------------------
+// Purpose: Returns a CSQLRecord object that represents a row in a result set
+//------------------------------------------------------------------------------------
+CSQLRecord CSQLAccess::GetResultRecord( uint32 unResultSet, uint32 unRow )
+{
+ if( m_pQueryGroup->GetResults() && unResultSet < m_pQueryGroup->GetResults()->GetResultSetCount() )
+ {
+ IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( unResultSet );
+ if( unRow < pResultSet->GetRowCount() )
+ return CSQLRecord( unRow, pResultSet );
+ }
+ return CSQLRecord(); // if there was a problem return an empty record
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Inserts a new record into the DS
+// Input: pRecordBase - record to insert
+// Output: true if successful, false otherwise
+//-----------------------------------------------------------------------------
+bool CSQLAccess::BYieldingInsertRecord( const CRecordBase *pRecordBase )
+{
+ ClearParams();
+
+ const CRecordInfo *pRecordInfo = pRecordBase->GetPSchema()->GetRecordInfo();
+ int cColumns = pRecordInfo->GetNumColumns();
+ for ( int nColumn = 0; nColumn < cColumns; nColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( nColumn );
+ if ( !columnInfo.BIsInsertable() )
+ continue;
+
+ uint8 *pubData;
+ uint32 cubData;
+ DbgVerify( pRecordBase->BGetField( nColumn, &pubData, &cubData ) );
+
+ CurrentQuery()->AddBindParamRaw( columnInfo.GetType(), pubData, cubData );
+ }
+
+ uint32 nRows;
+ const char *pchStatement = pRecordBase->GetPSchema()->GetInsertStatementText();
+
+ bool bRet = BYieldingExecute( pchStatement, pchStatement, &nRows );
+ return ( nRows == 1 || BInTransaction() ) && bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Inserts a new record into the DS if such row doesn't exist
+// Input: pRecordBase - record to insert
+// Output: true if successful, false otherwise
+//-----------------------------------------------------------------------------
+bool CSQLAccess::BYieldingInsertWhenNotMatchedOnPK( CRecordBase *pRecordBase )
+{
+ ClearParams();
+
+ const CRecordInfo *pRecordInfo = pRecordBase->GetPSchema()->GetRecordInfo();
+ int cColumns = pRecordInfo->GetNumColumns();
+ for ( int nColumn = 0; nColumn < cColumns; nColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( nColumn );
+ if ( !columnInfo.BIsInsertable() )
+ {
+ Assert( columnInfo.BIsInsertable() );
+ return false;
+ }
+
+ uint8 *pubData;
+ uint32 cubData;
+ DbgVerify( pRecordBase->BGetField( nColumn, &pubData, &cubData ) );
+
+ CurrentQuery()->AddBindParamRaw( columnInfo.GetType(), pubData, cubData );
+ }
+
+ uint32 nRows;
+ const char *pchStatement = pRecordBase->GetPSchema()->GetMergeStatementTextOnPKWhenNotMatchedInsert();
+
+ bool bRet = BYieldingExecute( pchStatement, pchStatement, &nRows );
+ return ( nRows == 1 || nRows == 0 || BInTransaction() ) && bRet;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Inserts a new record into the DS if such row doesn't exist
+// updates an existing row if such row is matched by PK
+// Input: pRecordBase - record to insert
+// Output: true if successful, false otherwise
+//-----------------------------------------------------------------------------
+bool CSQLAccess::BYieldingInsertOrUpdateOnPK( CRecordBase *pRecordBase )
+{
+ ClearParams();
+
+ const CRecordInfo *pRecordInfo = pRecordBase->GetPSchema()->GetRecordInfo();
+ int cColumns = pRecordInfo->GetNumColumns();
+ for ( int nColumn = 0; nColumn < cColumns; nColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( nColumn );
+ if ( !columnInfo.BIsInsertable() )
+ {
+ Assert( columnInfo.BIsInsertable() );
+ return false;
+ }
+
+ uint8 *pubData;
+ uint32 cubData;
+ DbgVerify( pRecordBase->BGetField( nColumn, &pubData, &cubData ) );
+
+ CurrentQuery()->AddBindParamRaw( columnInfo.GetType(), pubData, cubData );
+ }
+
+ uint32 nRows;
+ const char *pchStatement = pRecordBase->GetPSchema()->GetMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert();
+
+ bool bRet = BYieldingExecute( pchStatement, pchStatement, &nRows );
+ return ( nRows == 1 || BInTransaction() ) && bRet;
+}
+
+//-----------------------------------------------------------------------------
+// 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
+//-----------------------------------------------------------------------------
+bool CSQLAccess::BYieldingInsertWithIdentity( CRecordBase* pRecordBase )
+{
+ AssertMsg( !BInTransaction(), "BYieldingInsertWithIdentity is not supported in a transaction" );
+ if( BInTransaction() )
+ return false;
+ ClearParams();
+
+ TSQLCmdStr sStatement;
+ CUtlVector<int> vecOutputFields;
+ CRecordInfo *pRecordInfo = pRecordBase->GetPSchema()->GetRecordInfo();
+ BuildInsertAndReadStatementText( &sStatement, &vecOutputFields, pRecordInfo );
+
+ AssertMsg( vecOutputFields.Count() > 0, "BYieldingInsertAndReadRecord called for a record type with no non-insertable columns" );
+ if ( vecOutputFields.Count() == 0 )
+ return false;
+
+ int cColumns = pRecordInfo->GetNumColumns();
+ for ( int nColumn = 0; nColumn < cColumns; nColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( nColumn );
+ if ( !columnInfo.BIsInsertable() )
+ {
+ continue;
+ }
+
+ uint8 *pubData;
+ uint32 cubData;
+ DbgVerify( pRecordBase->BGetField( nColumn, &pubData, &cubData ) );
+
+ CurrentQuery()->AddBindParamRaw( columnInfo.GetType(), pubData, cubData );
+ }
+
+ bool bRet = BYieldingExecute( sStatement, sStatement );
+ if( !bRet )
+ return false;
+
+ Assert( 1 == GetResultSetCount() );
+ if ( 1 != GetResultSetCount() )
+ return false;
+
+ IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( 0 );
+ Assert( 1 == pResultSet->GetRowCount() );
+ if ( 1 != pResultSet->GetRowCount() )
+ return false;
+
+ Assert( (uint32)vecOutputFields.Count() == pResultSet->GetColumnCount() );
+ if ( (uint32)vecOutputFields.Count() != pResultSet->GetColumnCount() )
+ return false;
+
+ for( uint32 nColumn = 0; nColumn < pResultSet->GetColumnCount(); nColumn++ )
+ {
+ uint8 *pubData;
+ uint32 cubData;
+ DbgVerify( pResultSet->GetData( 0, nColumn, &pubData, &cubData ) );
+
+ int nSchColumn = vecOutputFields[nColumn];
+ Assert( pResultSet->GetColumnType( nColumn ) == pRecordInfo->GetColumnInfo( nSchColumn ).GetType() );
+ DbgVerify( pRecordBase->BSetField( nSchColumn, pubData, cubData ) );
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Reads a list of records from the DB according to the specified where
+// clause
+// Input: pRecordBase - record to read
+// readSet - The set of columns to read
+// whereSet - The set of columns to query on
+// Output: true if successful, false otherwise
+//-----------------------------------------------------------------------------
+EResult CSQLAccess::YieldingReadRecordWithWhereColumns( CRecordBase *pRecord, const CColumnSet & readSet, const CColumnSet & whereSet, const char* pchOrderClause )
+{
+ AssertMsg( !BInTransaction(), "BYieldingReadRecordWithWhereColumns is not supported in a transaction" );
+ if( BInTransaction() )
+ return k_EResultInvalidState;
+
+ //if there is an order by clause, only take the top one, if there isn't, then validate that we have a single instance
+ const char* pszTopClause = ( pchOrderClause ) ? "TOP (1)" : "TOP (2)";
+
+ TSQLCmdStr sStatement;
+ BuildSelectStatementText( &sStatement, readSet, pszTopClause );
+
+ // if we actually have some columns for the where clause,
+ // append a where clause.
+ if( whereSet.GetColumnCount() )
+ {
+ sStatement.Append( " WHERE " );
+ AppendWhereClauseText( &sStatement, whereSet );
+ AddRecordParameters( *pRecord, whereSet );
+ }
+ //append the order by if they added one
+ if( pchOrderClause )
+ {
+ sStatement.Append( " ORDER BY " );
+ sStatement.Append( pchOrderClause );
+ }
+
+ Assert(!readSet.IsEmpty() );
+ if( !BYieldingExecute( sStatement, sStatement ) )
+ return k_EResultFail;
+
+ if ( GetResultSetCount() != 1 )
+ {
+ AssertMsg( GetResultSetCount() == 1, "Unexpected number of result sets returned from select statement" );
+ return k_EResultFail;
+ }
+
+ // make sure the types are the same
+ IGCSQLResultSet *pResultSet = m_pQueryGroup->GetResults()->GetResultSet( 0 );
+ if ( pResultSet->GetRowCount() == 0 )
+ return k_EResultNoMatch;
+
+ //note that since we only take the top one when there is an order by clause, we don't need to handle that case down here, only if top 2 is selected
+ if( pResultSet->GetRowCount() != 1 )
+ {
+ // Make sure we aren't failing because there are multiple matching records.
+ // That is probably a misuse of the API or some unexpected condition.
+ AssertMsg1( false, "BYieldingReadRecordWithWhereColumns from %s failing because multiple records match WHERE clause", readSet.GetRecordInfo()->GetName() );
+ return k_EResultLimitExceeded;
+ }
+ FOR_EACH_COLUMN_IN_SET( readSet, nColumnIndex )
+ {
+ EGCSQLType eRecordType = readSet.GetColumnInfo( nColumnIndex ).GetType();
+ EGCSQLType eResultType = pResultSet->GetColumnType( nColumnIndex );
+
+ AssertMsg2( eResultType == eRecordType, "Column %d type mismatch in %s", nColumnIndex, readSet.GetRecordInfo()->GetName() );
+ if( eRecordType != eResultType )
+ return k_EResultInvalidParam;
+ }
+
+ CSQLRecord sqlRecord = GetResultRecord( 0, 0 );
+
+ FOR_EACH_COLUMN_IN_SET( readSet, nColumnIndex )
+ {
+ uint8 *pubData;
+ uint32 cubData;
+
+ DbgVerify( sqlRecord.BGetColumnData( nColumnIndex, &pubData, (int*)&cubData ) );
+ DbgVerify( pRecord->BSetField( readSet.GetColumn( nColumnIndex), pubData, cubData ) );
+ }
+
+ return k_EResultOK;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates a record in the DB
+// Input: record - data source for columns to match against (whereColumns) and
+// columns to assign (updateColumns)
+// whereColumns - columns to match against
+// updateColumns - columns to update
+// Output: true if successful, false otherwise
+//-----------------------------------------------------------------------------
+bool CSQLAccess::BYieldingUpdateRecord( const CRecordBase & record, const CColumnSet & whereColumns, const CColumnSet & updateColumns, const CSQLOutputParams *pOptionalOutputParams /* = NULL */ )
+{
+ return BYieldingUpdateRecords( record, whereColumns, record, updateColumns, pOptionalOutputParams );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CSQLAccess::BYieldingUpdateRecords( const CRecordBase & whereRecord, const CColumnSet & whereColumns, const CRecordBase & updateRecord, const CColumnSet & updateColumns, const CSQLOutputParams *pOptionalOutputParams /* = NULL */ )
+{
+ ClearParams();
+
+ Assert( whereColumns.GetRecordInfo() == updateColumns.GetRecordInfo() );
+ if ( whereColumns.GetRecordInfo() != updateColumns.GetRecordInfo() )
+ return false;
+ Assert( whereColumns.GetRecordInfo() == whereRecord.GetPSchema()->GetRecordInfo() );
+ if ( whereColumns.GetRecordInfo() != whereRecord.GetPSchema()->GetRecordInfo() )
+ return false;
+ Assert( whereColumns.GetRecordInfo() == updateRecord.GetPSchema()->GetRecordInfo() );
+ if ( whereColumns.GetRecordInfo() != updateRecord.GetPSchema()->GetRecordInfo() )
+ return false;
+
+ AssertMsg( !updateColumns.IsEmpty(), "Someone is calling BYieldingUpdateRecord with no columns to update." );
+ if ( updateColumns.IsEmpty() )
+ return false;
+
+ // add the columns we're updating as bound params
+ TSQLCmdStr sStatement;
+ BuildUpdateStatementText( &sStatement, updateColumns );
+
+ AddRecordParameters( updateRecord, updateColumns );
+
+ // did the users specify an OUTPUT block?
+ if ( pOptionalOutputParams )
+ {
+ TSQLCmdStr sOutput;
+ BuildOutputClauseText( &sOutput, pOptionalOutputParams->GetColumnSet() );
+ sStatement.Append( sOutput );
+
+ AddRecordParameters( pOptionalOutputParams->GetRecord(), pOptionalOutputParams->GetColumnSet() );
+ }
+
+ if ( !whereColumns.IsEmpty() )
+ {
+ sStatement.Append( " WHERE " );
+ AppendWhereClauseText( &sStatement, whereColumns );
+
+ // add the columns we're querying on as bound params
+ AddRecordParameters( whereRecord, whereColumns );
+ }
+
+ return BYieldingExecute( sStatement, sStatement );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Deletes this record's row in the table
+// Input: record - record to delete
+// whereColumns - columns to use when searching for this record
+//-----------------------------------------------------------------------------
+bool CSQLAccess::BYieldingDeleteRecords( const CRecordBase & record, const CColumnSet & whereColumns )
+{
+ Assert( whereColumns.GetRecordInfo() == record.GetPSchema()->GetRecordInfo() );
+ if ( whereColumns.GetRecordInfo() != record.GetPSchema()->GetRecordInfo() )
+ return false;
+
+ ClearParams();
+ AddRecordParameters( record, whereColumns );
+
+ TSQLCmdStr sStatement;
+ BuildDeleteStatementText( &sStatement, record.GetPRecordInfo() );
+ sStatement.Append( " WHERE " );
+ AppendWhereClauseText( &sStatement, whereColumns );
+
+ uint32 unRowsAffected;
+ if( !BYieldingExecute( sStatement, sStatement, &unRowsAffected ) )
+ return false;
+
+ return unRowsAffected > 0 || BInTransaction();
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------
+// CSQLUpdateOrInsert
+//--------------------------------------------------------------------------------------------------------------------------------
+
+CSQLUpdateOrInsert::CSQLUpdateOrInsert( const char* pszName, int nTable, const CColumnSet & whereColumns, const CColumnSet & updateColumns, const char* pszWhereClause, const char* pszUpdateClause )
+{
+ const CRecordInfo* pRecordInfo = GSchemaFull().GetSchema( nTable ).GetRecordInfo();
+
+ //how many columns do we have
+ const int nNumColumns = pRecordInfo->GetNumColumns();
+
+ TSQLCmdStr sStatement;
+ sStatement = "MERGE INTO ";
+ sStatement.Append( GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ) );
+ sStatement.Append( '.' );
+ sStatement.Append( pRecordInfo->GetName() );
+ sStatement.Append( " WITH(HOLDLOCK) AS D USING(VALUES(" );
+ sStatement.AppendFormat( "%.*s", GetInsertArgStringChars( nNumColumns ), GetInsertArgString() );
+ sStatement.Append( "))AS S(" );
+
+ //add each column that we are adding the values for, along with the parameter from the structure
+ for( int nCurrColumn = 0; nCurrColumn < nNumColumns; nCurrColumn++ )
+ {
+ const CColumnInfo& colInfo = pRecordInfo->GetColumnInfo( nCurrColumn );
+ if( nCurrColumn != 0 )
+ sStatement.Append( ',' );
+ sStatement.Append( colInfo.GetName() );
+ }
+
+ //our where clause
+ sStatement.Append( ")ON " );
+
+ if( pszWhereClause )
+ {
+ sStatement.Append( pszWhereClause );
+ }
+ else
+ {
+ FOR_EACH_COLUMN_IN_SET( whereColumns, nCurrColumn )
+ {
+ const char* pszColName = pRecordInfo->GetColumnInfo( whereColumns.GetColumn( nCurrColumn ) ).GetName();
+ if( nCurrColumn > 0 )
+ sStatement.Append( " AND " );
+ sStatement.AppendFormat( "D.%s=S.%s", pszColName, pszColName );
+ }
+ }
+
+ //our update clause (if they have provided fields that they want to update)
+ if( pszUpdateClause || !updateColumns.IsEmpty() )
+ {
+ sStatement.Append( " WHEN MATCHED THEN UPDATE SET " );
+ if( pszUpdateClause )
+ {
+ sStatement.Append( pszUpdateClause );
+ }
+ else
+ {
+ FOR_EACH_COLUMN_IN_SET( updateColumns, nCurrColumn )
+ {
+ const char* pszColName = pRecordInfo->GetColumnInfo( updateColumns.GetColumn( nCurrColumn ) ).GetName();
+ if( nCurrColumn > 0 )
+ sStatement.Append( ',' );
+ sStatement.AppendFormat( "%s=S.%s", pszColName, pszColName );
+ }
+ }
+ }
+
+ //our insert clause
+ sStatement.Append( " WHEN NOT MATCHED THEN INSERT(" );
+ bool bFirstColumn = true;
+ for( int nCurrColumn = 0; nCurrColumn < nNumColumns; nCurrColumn++ )
+ {
+ const CColumnInfo& colInfo = pRecordInfo->GetColumnInfo( nCurrColumn );
+ if( !colInfo.BIsInsertable() )
+ continue;
+
+ if( !bFirstColumn )
+ sStatement.Append( ',' );
+ bFirstColumn = false;
+ sStatement.Append( colInfo.GetName() );
+ }
+
+ sStatement.Append( ")VALUES(" );
+ bFirstColumn = true;
+ for( int nCurrColumn = 0; nCurrColumn < nNumColumns; nCurrColumn++ )
+ {
+ const CColumnInfo& colInfo = pRecordInfo->GetColumnInfo( nCurrColumn );
+ if( !colInfo.BIsInsertable() )
+ continue;
+
+ if( !bFirstColumn )
+ sStatement.Append( ',' );
+ bFirstColumn = false;
+ sStatement.AppendFormat( "S.%s", colInfo.GetName() );
+ }
+ sStatement.Append( ");" );
+
+ //save our results so we can execute it in the future
+ m_nTable = nTable;
+ m_sName = pszName;
+ m_sQuery = sStatement;
+}
+
+bool CSQLUpdateOrInsert::BYieldingExecute( CSQLAccess& sqlAccess, const CRecordBase& record, uint32 *out_punRowsAffected /* = NULL */ ) const
+{
+ AssertMsg2( record.GetITable() == m_nTable, "Error: Merge was compiled for table %s, but was attempted to be executed against %s", GSchemaFull().GetSchema( m_nTable ).GetRecordInfo()->GetName(), record.GetPRecordInfo()->GetName() );
+
+ const CRecordInfo* pRecordInfo = record.GetPRecordInfo();
+ //how many columns do we have
+ const int nNumColumns = pRecordInfo->GetNumColumns();
+
+ sqlAccess.ClearParams();
+ for( int nCurrColumn = 0; nCurrColumn < nNumColumns; nCurrColumn++ )
+ {
+ const CColumnInfo& colInfo = pRecordInfo->GetColumnInfo( nCurrColumn );
+ uint8 *pubData;
+ uint32 cubData;
+ DbgVerify( record.BGetField( nCurrColumn, &pubData, &cubData ) );
+ sqlAccess.AddBindParamRaw( colInfo.GetType(), pubData, cubData );
+ }
+
+ return sqlAccess.BYieldingExecute( m_sName, m_sQuery, out_punRowsAffected );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds bind parameters to the list based on a set of fields in a record
+// Input: record - record to insert
+// columnSet - The set of columns to add as params
+//-----------------------------------------------------------------------------
+void CSQLAccess::AddRecordParameters( const CRecordBase &record, const CColumnSet & columnSet )
+{
+ Assert( record.GetPSchema()->GetRecordInfo() == columnSet.GetRecordInfo() );
+ if ( record.GetPSchema()->GetRecordInfo() != columnSet.GetRecordInfo() )
+ return;
+
+ FOR_EACH_COLUMN_IN_SET( columnSet, nColumnIndex )
+ {
+ const CColumnInfo &columnInfo = columnSet.GetColumnInfo( nColumnIndex );
+ uint8 *pubData;
+ uint32 cubData;
+ DbgVerify( record.BGetField( columnSet.GetColumn( nColumnIndex ), &pubData, &cubData ) );
+ EGCSQLType eType = columnInfo.GetType();
+ CurrentQuery()->AddBindParamRaw( eType, pubData, cubData );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Deletes all records from a table
+// Input: iTable - table to wipe
+// Output: true if the operation was successful
+// Note: PERFORMANCE WARNING: this is slow on big tables, not intended for use
+// in production
+//-----------------------------------------------------------------------------
+bool CSQLAccess::BYieldingWipeTable( int iTable )
+{
+ // make a wipe operation
+ CRecordInfo *pRecordInfo = GSchemaFull().GetSchema( iTable ).GetRecordInfo();
+
+ CUtlString buf;
+ buf.Format( "DELETE FROM %s", pRecordInfo->GetName() );
+ return BYieldingExecute( buf.String(), buf.String() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the current query to add stuff to, creating it if there isn't
+// already a current query
+//-----------------------------------------------------------------------------
+CGCSQLQuery *CSQLAccess::CurrentQuery()
+{
+ if( m_pCurrentQuery )
+ return m_pCurrentQuery;
+
+ m_pCurrentQuery = new CGCSQLQuery();
+ return m_pCurrentQuery;
+}
+
+
+} // namespace GCSDK
diff --git a/gcsdk/sqlaccess/sqlrecord.cpp b/gcsdk/sqlaccess/sqlrecord.cpp
new file mode 100644
index 0000000..5b285b4
--- /dev/null
+++ b/gcsdk/sqlaccess/sqlrecord.cpp
@@ -0,0 +1,538 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+
+#include "stdafx.h"
+
+//#include "sqlaccess.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+namespace GCSDK
+{
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CSQLRecord::CSQLRecord( uint32 unRow, IGCSQLResultSet *pResultSet )
+{
+ Init( unRow, pResultSet );
+}
+CSQLRecord::CSQLRecord()
+: m_pResultSet( NULL ), m_unRow( 0 )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+CSQLRecord::~CSQLRecord()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes a blank record
+// Input: iTable - table that this record will belong to
+//-----------------------------------------------------------------------------
+void CSQLRecord::Init( uint32 unRow, IGCSQLResultSet *pResultSet )
+{
+ if( unRow >= pResultSet->GetRowCount() )
+ {
+ m_pResultSet = NULL;
+ m_unRow = 0;
+ }
+ else
+ {
+ m_pResultSet = pResultSet;
+ m_unRow = unRow;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets data for a field in this record
+// Input: unColumn - field to get
+// pubField - pointer to get filled in with pointer to data
+// cubField - pointer to get filled in with size of data
+// Output: true if successful, false if data not present
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetColumnData( uint32 unColumn, uint8 **ppubField, int *pcubField )
+{
+ size_t sz;
+ bool bRet = BGetColumnData( unColumn, ppubField, &sz );
+ *pcubField = static_cast< int >( sz );
+ return bRet;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets data for a field in this record
+// Input: unColumn - field to get
+// pubField - pointer to get filled in with pointer to data
+// cubField - pointer to get filled in with size of data
+// Output: true if successful, false if data not present
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetColumnData( uint32 unColumn, uint8 **ppubField, size_t *pcubField )
+{
+ Assert( ppubField );
+ Assert( pcubField );
+ *ppubField = NULL;
+ *pcubField = 0;
+
+ Assert( m_pResultSet );
+
+ if ( !BValidateColumnIndex( unColumn ) )
+ return false;
+
+ *pcubField = 0;
+ return m_pResultSet->GetData( m_unRow, unColumn, ppubField, (uint32*)pcubField );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets string data for a field in this record
+// Input: unColumn - field to get
+// ppchVal - pointer to pointer to fill in to string data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetStringValue( uint32 unColumn, const char **ppchVal )
+{
+ Assert( ppchVal );
+ *ppchVal = NULL;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ Assert( k_EGCSQLType_String == m_pResultSet->GetColumnType( unColumn ) );
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ *ppchVal = (const char *) pubData;
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets string data for a field in this record
+// Input: unColumn - field to get
+// ppchVal - pointer to pointer to fill in to string data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetStringValue( uint32 unColumn, CFmtStr1024 *psVal )
+{
+ Assert( psVal );
+ *psVal = "";
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ Assert( k_EGCSQLType_String == m_pResultSet->GetColumnType( unColumn ) );
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ *psVal = (const char *) pubData;
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets int data for a field in this record
+// Input: unColumn - field to get
+// pnVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetIntValue( uint32 unColumn, int *pnVal )
+{
+ Assert( pnVal );
+ *pnVal = 0;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ {
+ switch( m_pResultSet->GetColumnType( unColumn ) )
+ {
+ case k_EGCSQLType_int64:
+ {
+ int64 ul = *((int64 *)pubData);
+ if ( ul >= LONG_MIN && ul <= LONG_MAX )
+ {
+ *pnVal = (int)ul;
+ return true;
+ }
+ else
+ {
+ AssertMsg1(false, "GetIntValue tried to catch %lld in an int, which is too small", ul );
+ return false;
+ }
+ }
+ break;
+
+ case k_EGCSQLType_int32:
+ *pnVal = *((int32 *)pubData);
+ return true;
+
+ case k_EGCSQLType_int16:
+ *pnVal = *((int16 *)pubData);
+ return true;
+
+ case k_EGCSQLType_int8:
+ *pnVal = *((int8 *)pubData);
+ return true;
+
+ default:
+ AssertMsg1(false, "GetIntValue tried to catch a %s, which is the wrong type", PchNameFromEGCSQLType( m_pResultSet->GetColumnType( unColumn ) ) );
+ return false;
+ }
+ }
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets float data for a field in this record
+// Input: unColumn - field to get
+// pnVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetFloatValue( uint32 unColumn, float *pfVal )
+{
+ Assert( pfVal );
+ *pfVal = 0.0f;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ {
+ Assert( k_EGCSQLType_float == m_pResultSet->GetColumnType( unColumn ) );
+ AssertMsg2( sizeof( float ) == cubData, "GetValue expected %llu bytes, found %d", (uint64)sizeof( float ), cubData );
+ if ( sizeof( float ) != cubData )
+ return false;
+ *pfVal = *( (float *) pubData );
+ }
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets double data for a field in this record
+// Input: unColumn - field to get
+// pnVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetDoubleValue( uint32 unColumn, double *pdVal )
+{
+ Assert( pdVal );
+ *pdVal = 0.0f;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ {
+ Assert( k_EGCSQLType_double == m_pResultSet->GetColumnType( unColumn ) );
+ AssertMsg2( sizeof( double ) == cubData, "GetValue expected %llu bytes, found %d", (uint64)sizeof( double ), cubData );
+ if ( sizeof( double ) != cubData )
+ return false;
+ *pdVal = *( (double *) pubData );
+ }
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets int data for a field in this record
+// Input: unColumn - field to get
+// pVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetByteValue( uint32 unColumn, byte *pVal )
+{
+ Assert( pVal );
+ *pVal = 0;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ {
+ Assert( k_EGCSQLType_int8 == m_pResultSet->GetColumnType( unColumn ) );
+ AssertMsg1( 1 == cubData, "GetValue expected 1 bytes, found %d", cubData );
+ if ( 1 != cubData )
+ return false;
+ *pVal = *( (byte *) pubData );
+ }
+
+ return bRet;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets int data for a field in this record
+// Input: unColumn - field to get
+// pVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetBoolValue( uint32 unColumn, bool *pVal )
+{
+ int32 b;
+ if ( !BGetIntValue( unColumn, &b ) )
+ return false;
+
+ // convert to boolean
+ *pVal = ( b != 0 );
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets int16 data for a field in this record
+// Input: unColumn - field to get
+// pnVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetInt16Value( uint32 unColumn, int16 *pnVal )
+{
+ Assert( pnVal );
+ *pnVal = 0;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ {
+ Assert( k_EGCSQLType_int16 == m_pResultSet->GetColumnType( unColumn ) );
+ AssertMsg1( 2 == cubData, "GetValue expected 2 bytes, found %d", cubData );
+ if ( 2 != cubData )
+ return false;
+ *pnVal = *( (int16 *) pubData );
+ }
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets int64 data for a field in this record
+// Input: unColumn - field to get
+// puVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetInt64Value( uint32 unColumn, int64 *puVal )
+{
+ Assert( puVal );
+ *puVal = 0;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ {
+ Assert( k_EGCSQLType_int64 == m_pResultSet->GetColumnType( unColumn ) );
+ AssertMsg1( 8 == cubData, "GetValue expected 8 bytes, found %d", cubData );
+ if ( 8 != cubData )
+ return false;
+ *puVal = *( (int64 *) pubData );
+ }
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets uint64 data for a field in this record
+// Input: unColumn - field to get
+// puVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetUint64Value( uint32 unColumn, uint64 *puVal )
+{
+ Assert( puVal );
+ *puVal = 0;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ {
+ Assert( k_EGCSQLType_int64 == m_pResultSet->GetColumnType( unColumn ) );
+ AssertMsg1( 8 == cubData, "GetValue expected 8 bytes, found %d", cubData );
+ if ( 8 != cubData )
+ return false;
+ *puVal = *( (uint64 *) pubData );
+ }
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets uint32 data for a field in this record
+// Input: unColumn - field to get
+// puVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetUint32Value( uint32 unColumn, uint32 *puVal )
+{
+ Assert( puVal );
+ *puVal = 0;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ {
+ Assert( k_EGCSQLType_int32 == m_pResultSet->GetColumnType( unColumn ) );
+ AssertMsg1( 4 == cubData, "GetValue expected 4 bytes, found %d", cubData );
+ if ( 4 != cubData )
+ return false;
+ *puVal = *( (uint32 *) pubData );
+ }
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets uint16 data for a field in this record
+// Input: unColumn - field to get
+// puVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetUint16Value( uint32 unColumn, uint16 *puVal )
+{
+ Assert( puVal );
+ *puVal = 0;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ {
+ Assert( k_EGCSQLType_int16 == m_pResultSet->GetColumnType( unColumn ) );
+ AssertMsg1( 2 == cubData, "GetValue expected 2 bytes, found %d", cubData );
+ if ( 2 != cubData )
+ return false;
+ *puVal = *( (uint16 *) pubData );
+ }
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets uint8 data for a field in this record
+// Input: unColumn - field to get
+// puVal - pointer to fill in with data
+// Output: true if successful, false if data not present or not of correct type
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BGetUint8Value( uint32 unColumn, uint8 *puVal )
+{
+ Assert( puVal );
+ *puVal = 0;
+
+ uint8 *pubData = NULL;
+ int cubData = 0;
+ bool bRet = BGetColumnData( unColumn, &pubData, &cubData );
+ if ( bRet )
+ {
+ Assert( k_EGCSQLType_int8 == m_pResultSet->GetColumnType( unColumn ) );
+ AssertMsg1( 1 == cubData, "GetValue expected 1 byte, found %d", cubData );
+ if ( 1 != cubData )
+ return false;
+ *puVal = *( (uint8 *) pubData );
+ }
+
+ return bRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Validates column index
+// Input: unColumn - field to validate
+// Output: true if valid, false otherwise
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BValidateColumnIndex( uint32 unColumn )
+{
+ if ( unColumn >= m_pResultSet->GetColumnCount() )
+ {
+ AssertMsg2( false, "CSQLRecord::BValidateColumnIndex: invalid column index %d. # columns: %d", unColumn,
+ m_pResultSet->GetColumnCount() );
+ return false;
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Advances the CSQLRecord to the next row
+// Output: returns false if this call would advance the record past the last row.
+// And makes the record invalid.
+//-----------------------------------------------------------------------------
+bool CSQLRecord::NextRow()
+{
+ Assert( m_pResultSet );
+ m_unRow++;
+
+ if( m_unRow >= m_pResultSet->GetRowCount() )
+ m_pResultSet = NULL;
+ return IsValid();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Render a field to a buffer
+// Input: unColumn - field to render
+// cchBuffer - size of render buffer
+// pchBuffer - buffer to render into
+//-----------------------------------------------------------------------------
+void CSQLRecord::RenderField( uint32 unColumn, int cchBuffer, char *pchBuffer )
+{
+ Q_strncpy( pchBuffer, "", cchBuffer );
+
+ uint8 *pubData;
+ int cubData;
+ if ( !BGetColumnData( unColumn, &pubData, &cubData ) )
+ return;
+
+ // Get the column info and figure out how to interpret the data
+ ConvertFieldToText( m_pResultSet->GetColumnType( unColumn ), pubData, cubData, pchBuffer, cchBuffer, false );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Copies a CSQLRecord to CRecordBase
+//-----------------------------------------------------------------------------
+bool CSQLRecord::BWriteToRecord( CRecordBase *pRecord, const CColumnSet & csWriteFields )
+{
+ bool bSuccess = true;
+ FOR_EACH_COLUMN_IN_SET( csWriteFields, unSQLColumn )
+ {
+ uint32 unRecordColumn = csWriteFields.GetColumn( unSQLColumn );
+
+ uint8 *pubData;
+ size_t cubData;
+ if( !BGetColumnData( unSQLColumn, &pubData, &cubData ) )
+ {
+ bSuccess = false;
+ }
+ else
+ {
+ bSuccess = pRecord->BSetField( unRecordColumn, pubData, cubData ) && bSuccess;
+ }
+
+ }
+ return bSuccess;
+}
+
+
+
+} // namespace GCSDK
diff --git a/gcsdk/sqlaccess/sqlutil.cpp b/gcsdk/sqlaccess/sqlutil.cpp
new file mode 100644
index 0000000..6744d21
--- /dev/null
+++ b/gcsdk/sqlaccess/sqlutil.cpp
@@ -0,0 +1,918 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================
+
+
+#include "stdafx.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+namespace GCSDK
+{
+const char *GetInsertArgString()
+{
+ static char s_str[1024];
+ static bool s_bInit = false;
+
+ if ( !s_bInit )
+ {
+ for ( int i = 0; i < 1023; i++ )
+ {
+ s_str[i] = i % 2 == 0 ? '?' : ',';
+ }
+
+ s_str[1023] = NULL;
+ s_bInit = true;
+ }
+
+ return s_str;
+}
+
+uint32 GetInsertArgStringChars( uint32 nNumParams )
+{
+ AssertMsg( nNumParams <= GetInsertArgStringMaxParams(), "Error: Requested more characters than are provided by the GetInsertArgString" );
+ if( nNumParams == 0 )
+ return 0;
+
+ return nNumParams * 2 - 1;
+}
+
+uint32 GetInsertArgStringMaxParams()
+{
+ return 512;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Converts array of field data to text for SQL IN clause
+// Input: columnInfo - schema of column being converted
+// pubData - pointer to array of data to convert
+// cubData - size of array of data
+// rgchResult - pointer to output buffer
+// cubResultLen - size of output buffer
+// bForPreparedStatement - Should we prepare the text for a prepared statement or directly place the values?
+//-----------------------------------------------------------------------------
+void ConvertFieldArrayToInText( const CColumnInfo &columnInfo, byte *pubData, int cubData, char *rgchResult, int cubResultLen, bool bForPreparedStatement )
+{
+ int32 cubLength = columnInfo.GetFixedSize();
+ Assert( cubData % cubLength == 0 );
+ int32 nArrayCount = cubData / cubLength;
+
+ int32 len = 0;
+ rgchResult[len++] = '(';
+ for( int i = 0; i < nArrayCount; ++i )
+ {
+ if ( bForPreparedStatement )
+ {
+ if ( i < nArrayCount - 1 )
+ {
+ rgchResult[len++] = '?';
+ rgchResult[len++] = ',';
+ }
+ else
+ {
+ rgchResult[len++] = '?';
+ rgchResult[len++] = ')';
+ }
+ }
+ else
+ {
+ switch ( columnInfo.GetType() )
+ {
+ case k_EGCSQLType_int8:
+ if ( i < nArrayCount - 1 )
+ len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (byte *) pubData ) );
+ else
+ len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (byte *) pubData ) );
+ break;
+ case k_EGCSQLType_int16:
+ if ( i < nArrayCount - 1 )
+ len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (short *) pubData ) );
+ else
+ len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (short *) pubData ) );
+ break;
+ case k_EGCSQLType_int32:
+ if ( i < nArrayCount - 1 )
+ len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (int *) pubData ) );
+ else
+ len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (int *) pubData ) );
+ break;
+ case k_EGCSQLType_int64:
+ if ( i < nArrayCount - 1 )
+ len += Q_snprintf( rgchResult + len, cubResultLen - len, "%lld,", *( (int64 *) pubData ) );
+ else
+ len += Q_snprintf( rgchResult + len, cubResultLen - len, "%lld)", *( (int64 *) pubData ) );
+ break;
+ default:
+ AssertMsg( false, "Unsupported data type for non prepares statement with IN clause\n" );
+ rgchResult[0] = 0;
+ return;
+ }
+ }
+
+ if( len >= cubResultLen - 1 )
+ {
+ AssertMsg( false, "Generation of IN clause foverflowed\n" );
+ rgchResult[0] = 0;
+ return;
+ }
+ pubData += cubLength;
+ }
+
+ rgchResult[len] = 0;
+ return;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Converts field data to text equivalent for SQL statement
+// Input: eFieldType - The type of the field to convert to text
+// pubRecord - pointer to record data to convert
+// cubRecord - size of record data
+// rgchField - pointer to output buffer
+// cchField - size of output buffer
+//-----------------------------------------------------------------------------
+void ConvertFieldToText( EGCSQLType eFieldType, uint8 *pubRecord, int cubRecord, char *rgchField, int cchField, bool bQuoteString )
+{
+ char rgchTmp[k_cMedBuff];
+
+ switch ( eFieldType )
+ {
+ case k_EGCSQLType_int8:
+ Q_snprintf( rgchField, cchField, "%d", *( (byte *) pubRecord ) );
+ break;
+ case k_EGCSQLType_int16:
+ Q_snprintf( rgchField, cchField, "%d", *( (short *) pubRecord ) );
+ break;
+ case k_EGCSQLType_int32:
+ Q_snprintf( rgchField, cchField, "%d", *( (int *) pubRecord ) );
+ break;
+ case k_EGCSQLType_int64:
+ Q_snprintf( rgchField, cchField, "%lld", *( (int64 *) pubRecord ) );
+ break;
+ case k_EGCSQLType_float:
+ Q_snprintf( rgchField, cchField, "%f", *((float*) pubRecord) );
+ break;
+ case k_EGCSQLType_double:
+ Q_snprintf( rgchField, cchField, "%f", *((double*) pubRecord) );
+ break;
+ case k_EGCSQLType_String:
+ if ( pubRecord && *pubRecord )
+ {
+ Assert( cubRecord + 1 < Q_ARRAYSIZE( rgchTmp ) );
+
+ Q_memcpy( rgchTmp, (char *) pubRecord, cubRecord );
+ rgchTmp[cubRecord] = 0;
+
+ if ( bQuoteString )
+ {
+ EscapeStringValue( rgchTmp, Q_ARRAYSIZE( rgchTmp ) );
+ Q_snprintf( rgchField, cchField, "'%s'", rgchTmp );
+ }
+ else
+ {
+ Q_strncpy( rgchField, rgchTmp, cchField );
+ }
+ }
+ else
+ {
+ if ( bQuoteString )
+ {
+ Q_strncpy( rgchField, "''", cchField );
+ }
+ else
+ {
+ Q_strncpy( rgchField, "", cchField );
+ }
+ }
+ break;
+ case k_EGCSQLType_Blob:
+ case k_EGCSQLType_Image:
+ Q_strncpy( rgchField, "0x", cchField );
+ Q_binarytohex( pubRecord, cubRecord, rgchField + 2, cchField - 2 );
+ break;
+ default:
+ Assert( false );
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the text SQL type for a given field
+// Input: field - field to determine type for
+// pchBuf - pointer to output buffer
+// cchBuf - size of output buffer
+// Output: returns pchBuf for convenience of one-line usage
+//-----------------------------------------------------------------------------
+char *SQLTypeFromField( const CColumnInfo &colInfo, char *pchBuf, int cchBuf )
+{
+ EGCSQLType eType = colInfo.GetType();
+ *pchBuf = 0;
+ switch ( eType )
+ {
+ case k_EGCSQLType_int8:
+ Q_strncpy( pchBuf, "TINYINT", cchBuf );
+ break;
+ case k_EGCSQLType_int16:
+ Q_strncpy( pchBuf, "SMALLINT", cchBuf );
+ break;
+ case k_EGCSQLType_int32:
+ Q_strncpy( pchBuf, "INT", cchBuf );
+ break;
+ case k_EGCSQLType_int64:
+ Q_strncpy( pchBuf, "BIGINT", cchBuf );
+ break;
+ case k_EGCSQLType_float:
+ Q_strncpy( pchBuf, "REAL", cchBuf );
+ break;
+ case k_EGCSQLType_double:
+ Q_strncpy( pchBuf, "FLOAT", cchBuf );
+ break;
+ case k_EGCSQLType_String:
+ Q_snprintf( pchBuf, cchBuf, "VARCHAR(%d)", colInfo.GetMaxSize() );
+ break;
+ case k_EGCSQLType_Blob:
+ Q_snprintf( pchBuf, cchBuf, "VARBINARY(%d)", colInfo.GetMaxSize() );
+ break;
+ case k_EGCSQLType_Image:
+ Q_strncpy( pchBuf, "IMAGE", cchBuf );
+ break;
+ default:
+ Assert( false );
+ break;
+ }
+
+ return pchBuf;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Escapes any single quotes to a string value to double single quotes
+// Input: rgchField - text to escape
+// cchField - size of text buffer
+// Notes: The text will be escaped and expanded in place in the buffer.
+// In the worst case, the text may expand by 2x. (If the field is all
+// single quotes.) So, you must pass in a buffer which is at least
+// twice as long as the text length so we can guarantee to be able to
+// escape the string.
+//-----------------------------------------------------------------------------
+void EscapeStringValue( char *rgchField, int cchField )
+{
+ // TODO - what else do we need to escape? %() ...
+ char *pubCur = rgchField;
+ int nLen = 0;
+ int cSingleQuotes = 0;
+
+ // This function gets called on every text field we write but most text fields
+ // don't need to be escaped, so try to be as fast as possible in the normal case.
+
+ // first, walk through the string and count the string length and number of single quotes
+ while ( *pubCur )
+ {
+ if ( '\'' == *pubCur )
+ cSingleQuotes++;
+ nLen ++;
+ pubCur++;
+ }
+
+ // if no single quotes, nothing to do
+ if ( !cSingleQuotes )
+ return;
+
+ // caller must pass in a buffer that's long enough for expansion
+ Assert( nLen + cSingleQuotes + 1 <= cchField );
+ if ( !( nLen + cSingleQuotes + 1 <= cchField ) )
+ return;
+
+ // We know exactly how many characters the string will expand by (the # of single quotes). Walk backward
+ // and copy the characters into the right places. This touches each character only once.
+ pubCur = rgchField + nLen + cSingleQuotes;
+ *pubCur = 0;
+ pubCur--;
+ while ( pubCur > rgchField && cSingleQuotes > 0 )
+ {
+ // read pointer is offset from write pointer by # of remaining single quotes
+ char *pubRead = pubCur - cSingleQuotes;
+ Assert( pubRead >= rgchField );
+ // copy each character
+ *pubCur = *pubRead;
+ if ( '\'' == *pubRead )
+ {
+ // if the character is a single quote, back up one more and insert another single quote to escape it
+ pubCur --;
+ *pubCur = '\'';
+ // decrement # of single quotes remaining
+ cSingleQuotes --;
+ Assert( cSingleQuotes >= 0 );
+ }
+ pubCur--;
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose: Adds constraint information to a SQL command to add or remove constraint
+// Input: pchTableName - name of table
+// pchColumnName - name of column
+// nColFlagConstraint - flag with which constraint to
+// bForAdd - whether constraint is being added or removed
+// pchCmd - buffer to append SQL command to
+// cchCmd - size of buffer
+//-----------------------------------------------------------------------------
+void AppendConstraint( const char *pchTableName, const char *pchColumnName, int nColFlagConstraint, bool bForAdd,
+ bool bClustered, CFmtStrMax & sCmd, int nFillFactor )
+{
+ Assert( pchTableName && pchTableName[0] );
+ Assert( pchColumnName && pchColumnName[0] );
+
+ switch ( nColFlagConstraint )
+ {
+ case k_nColFlagPrimaryKey:
+ sCmd.AppendFormat( " CONSTRAINT %s_%s_PrimaryKey", pchTableName, pchColumnName);
+ if ( bForAdd )
+ {
+ sCmd += " PRIMARY KEY ";
+ if ( bClustered )
+ {
+ sCmd.AppendFormat( " CLUSTERED WITH (FILLFACTOR = %d) ", nFillFactor );
+ }
+ else
+ {
+ sCmd += "NONCLUSTERED";
+ }
+ }
+ break;
+ case k_nColFlagUnique:
+ /* do nothing - the uniqueness will be handled by creation of an index */
+ break;
+ case k_nColFlagAutoIncrement:
+ sCmd += " IDENTITY";
+ break;
+ default:
+ AssertMsg( false, "CSQLThread::AppendContraint: invalid constraint type" );
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds constraint information to a SQL command to add or remove constraint
+// Input: pRecordInfo - record info describing table
+// pColumnInfo - record info describing column
+// bForAdd - whether constraint is being added or removed
+// pchCmd - buffer to append SQL command to
+// cchCmd - size of buffer
+//-----------------------------------------------------------------------------
+void AppendConstraints( const CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo, bool bForAdd, CFmtStrMax & sCmd )
+{
+ Assert( pRecordInfo != NULL );
+ Assert( pColumnInfo != NULL );
+
+ if ( pColumnInfo->BIsPrimaryKey() )
+ {
+ // any column in a PK can't be NULL.
+ if ( bForAdd )
+ {
+ sCmd += " NOT NULL";
+ }
+
+ // only add primary key constraint here if it is a single-column PK
+ if ( pRecordInfo->GetPrimaryKeyType() == k_EPrimaryKeyTypeSingle )
+ {
+ // get the fields on the primary key
+ const CUtlVector< FieldSet_t > &refFields = pRecordInfo->GetIndexFields( );
+ int nFillFactor = refFields.Element( pRecordInfo->GetPKIndex() ).GetFillFactor();
+ AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagPrimaryKey, bForAdd, pColumnInfo->BIsClustered(), sCmd, nFillFactor );
+ }
+ }
+ else if ( pColumnInfo->BIsUnique() )
+ {
+ AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagUnique, bForAdd, pColumnInfo->BIsClustered(), sCmd, 0 );
+ }
+
+ if ( pColumnInfo->BIsAutoIncrement() )
+ {
+ AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagAutoIncrement, bForAdd, pColumnInfo->BIsClustered(), sCmd, 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Generates the "CONSTRAINT ..." text for the table primary key
+//-----------------------------------------------------------------------------
+void BuildTablePKConstraintText( TSQLCmdStr *psStatement, CRecordInfo *pRecordInfo )
+{
+ const FieldSet_t& vecFields = pRecordInfo->GetPKFields( );
+
+ psStatement->sprintf( "CONSTRAINT %s_PrimaryKey PRIMARY KEY %s ( ",
+ pRecordInfo->GetName(),
+ vecFields.IsClustered() ? "CLUSTERED" : "NONCLUSTERED" );
+
+ for ( int nField = 0; nField < vecFields.GetCount(); nField++ )
+ {
+ // what field is the next column in our index?
+ int nThisField = vecFields.GetField( nField );
+ const CColumnInfo& columnInfo = pRecordInfo->GetColumnInfo(nThisField);
+
+ if (nField != 0)
+ {
+ *psStatement += ", ";
+ }
+ *psStatement += columnInfo.GetName();
+ }
+
+ // close our list
+ *psStatement += ") ";
+
+ if ( vecFields.GetFillFactor() != 0 )
+ {
+ // non-default fill factor, so specify it
+ psStatement->AppendFormat( " WITH FILLFACTOR = %d ",
+ vecFields.GetFillFactor() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds constraint information to a SQL command to add or remove table-level constraints
+// Input: pRecordInfo - record info describing table
+// pchCmd - buffer to append SQL command to
+// cchCmd - size of buffer
+//-----------------------------------------------------------------------------
+
+void AppendTableConstraints( CRecordInfo *pRecordInfo, CFmtStrMax & sCmd )
+{
+ // the only supported table constraint is for PKs or FKs
+ if ( pRecordInfo->GetPrimaryKeyType() == k_EPrimaryKeyTypeMulti )
+ {
+ TSQLCmdStr tmp;
+ BuildTablePKConstraintText( &tmp, pRecordInfo );
+ sCmd += ", ";
+ sCmd += tmp;
+ }
+
+ // Look for FKs required on this table
+ // the only supported table constraint is for PKs or FKs
+ int cFKs = pRecordInfo->GetFKCount();
+ for( int i=0; i < cFKs; ++i )
+ {
+ FKData_t &fkData = pRecordInfo->GetFKData( i );
+
+ CFmtStr sColumns, sParentColumns;
+ FOR_EACH_VEC( fkData.m_VecColumnRelations, nCol )
+ {
+ FKColumnRelation_t &colRelation = fkData.m_VecColumnRelations[nCol];
+ if ( nCol > 0)
+ {
+ sColumns += ",";
+ sParentColumns += ",";
+ }
+ sColumns += colRelation.m_rgchCol;
+ sParentColumns += colRelation.m_rgchParentCol;
+ }
+
+ TSQLCmdStr sTmp;
+ sTmp.sprintf( ", CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s) ON DELETE %s ON UPDATE %s",
+ fkData.m_rgchName, sColumns.Access(), fkData.m_rgchParentTableName, sParentColumns.Access(),
+ PchNameFromEForeignKeyAction( fkData.m_eOnDeleteAction ), PchNameFromEForeignKeyAction( fkData.m_eOnUpdateAction ) );
+
+ // add to the command
+ sCmd += sTmp;
+ }
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds a SQL INSERT statement
+// Input: psStatement - The string to put the statement into
+// pRecordInfo - record info describing table inserting into
+//-----------------------------------------------------------------------------
+void BuildInsertStatementText( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
+{
+ psStatement->sprintf("INSERT INTO %s.%s (", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() );
+
+ // build a string of the field names
+ int cColumns = pRecordInfo->GetNumColumns();
+ int nInsertable = 0;
+ bool bAddedBefore = false;
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
+ if ( !columnInfo.BIsInsertable() )
+ continue;
+
+ nInsertable++;
+
+ if ( bAddedBefore )
+ psStatement->Append( ',' );
+ bAddedBefore = true;
+ psStatement->Append( columnInfo.GetName() );
+ }
+
+ psStatement->AppendFormat( ") VALUES (%.*s)", GetInsertArgStringChars( nInsertable ), GetInsertArgString() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds a SQL INSERT statement
+// IMPORTANT NOTE - This Insert statement will use the Microsoft SQL Server
+// specific clause 'OUTPUT Inserted.ColumnName'
+// The result of that will be that the SQL statement will return to us
+// the columns that could not be specified by the Insert.
+// At the time of writing, that is primarily AutoIncrement columns,
+// however in theory we should be able to recover any computed column
+// from SQL server, with the caveats specified at :
+// http://msdn.microsoft.com/en-us/library/ms177564.aspx
+//
+// Input: psStatement - The output statement string
+// pRecordInfo - record info describing table inserting into
+//-----------------------------------------------------------------------------
+
+void BuildInsertAndReadStatementText( TSQLCmdStr *psStatement, CUtlVector<int> *pvecOutputFields, const CRecordInfo *pRecordInfo )
+{
+ psStatement->sprintf("INSERT INTO %s.%s (", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() );
+
+ // build a string of the field names
+ int nInsertable = 0;
+ int cColumns = pRecordInfo->GetNumColumns();
+ bool bAddedBefore = false;
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
+ if ( !columnInfo.BIsInsertable() )
+ continue;
+
+ nInsertable++;
+
+ if ( bAddedBefore )
+ psStatement->Append( ',' );
+ bAddedBefore = true;
+ psStatement->Append( columnInfo.GetName() );
+ }
+
+ bAddedBefore = false ;
+ int nOutputColumn = 0;
+ for( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ) ;
+
+ //
+ // If we can't Insert it - we want SQL Server to tell us what value was stored
+ // in the column !!
+ //
+ if( !columnInfo.BIsInsertable() )
+ {
+ if( bAddedBefore )
+ psStatement->Append( ", INSERTED." );
+ else
+ psStatement->Append( ") OUTPUT INSERTED." );
+ bAddedBefore = true ;
+ psStatement->Append( columnInfo.GetName() );
+ pvecOutputFields->AddToTail( iColumn );
+ nOutputColumn++;
+ }
+ }
+
+ // add field values to SQL statement
+ psStatement->AppendFormat( " VALUES (%.*s)", GetInsertArgStringChars( nInsertable ), GetInsertArgString() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds a SQL MERGE statement update or insert using in-flight values table
+// Input: psStatement - The string to put the statement into
+// pRecordInfo - record info describing table inserting into
+//-----------------------------------------------------------------------------
+void BuildMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
+{
+ psStatement->sprintf( "MERGE INTO %s.%s WITH( HOLDLOCK, ROWLOCK ) T USING ( VALUES (%.*s) ) AS S(",
+ GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName(),
+ GetInsertArgStringChars( pRecordInfo->GetNumColumns() ), GetInsertArgString() );
+
+ {
+ int cColumns = pRecordInfo->GetNumColumns();
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
+ if ( iColumn )
+ psStatement->Append( ',' );
+ psStatement->Append( columnInfo.GetName() );
+ }
+ }
+
+ psStatement->Append( ") ON " );
+
+ // build a string of the PK columns
+ const FieldSet_t &fsPK = pRecordInfo->GetIndexFields()[pRecordInfo->GetPKIndex()];
+ {
+ int cColumns = fsPK.GetCount();
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( fsPK.GetField( iColumn ) );
+ if ( iColumn )
+ psStatement->Append( " AND " );
+ psStatement->Append( "T." );
+ psStatement->Append( columnInfo.GetName() );
+ psStatement->Append( "=S." );
+ psStatement->Append( columnInfo.GetName() );
+ }
+ }
+
+ psStatement->Append( " WHEN MATCHED THEN UPDATE SET " );
+
+ // build the update string
+ {
+ int cColumns = pRecordInfo->GetNumColumns();
+ bool bAddedBefore = false;
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ bool bThisColumnIsPartOfPK = false;
+ for ( int ipkCheck = 0; ipkCheck < fsPK.GetCount(); ++ipkCheck )
+ {
+ if ( iColumn == fsPK.GetField( ipkCheck ) )
+ {
+ bThisColumnIsPartOfPK = true;
+ break;
+ }
+ }
+ if ( bThisColumnIsPartOfPK )
+ continue;
+
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
+ if ( bAddedBefore )
+ psStatement->Append( ',' );
+ bAddedBefore = true;
+ psStatement->Append( columnInfo.GetName() );
+ psStatement->Append( "=S." );
+ psStatement->Append( columnInfo.GetName() );
+ }
+ }
+
+ psStatement->Append( " WHEN NOT MATCHED BY TARGET THEN INSERT (" );
+
+ // build a string of the field names
+ {
+ int cColumns = pRecordInfo->GetNumColumns();
+ bool bAddedBefore = false;
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
+ if ( !columnInfo.BIsInsertable() )
+ continue;
+
+ if ( bAddedBefore )
+ psStatement->Append( ',' );
+ bAddedBefore = true;
+ psStatement->Append( columnInfo.GetName() );
+ }
+ }
+
+ psStatement->Append( ") VALUES (" );
+ {
+ int cColumns = pRecordInfo->GetNumColumns();
+ bool bAddedBefore = false;
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
+ if ( !columnInfo.BIsInsertable() )
+ continue;
+
+ if ( bAddedBefore )
+ psStatement->Append( ',' );
+ bAddedBefore = true;
+ psStatement->Append( "S." );
+ psStatement->Append( columnInfo.GetName() );
+ }
+ }
+ psStatement->Append( ");" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds a SQL MERGE statement using CTE_MergeParams as supplied table holding rows
+// Input: psStatement - The string to put the statement into
+// pRecordInfo - record info describing table inserting into
+//-----------------------------------------------------------------------------
+void BuildMergeStatementTextOnPKWhenNotMatchedInsert( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
+{
+ psStatement->sprintf( "MERGE INTO %s.%s WITH( HOLDLOCK, ROWLOCK ) T USING ( VALUES (%.*s) ) AS S(",
+ GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName(),
+ GetInsertArgStringChars( pRecordInfo->GetNumColumns() ), GetInsertArgString() );
+
+ {
+ int cColumns = pRecordInfo->GetNumColumns();
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
+ if ( iColumn )
+ psStatement->Append( ',' );
+ psStatement->Append( columnInfo.GetName() );
+ }
+ }
+
+ psStatement->Append( ") ON " );
+
+ // build a string of the PK columns
+ const FieldSet_t &fsPK = pRecordInfo->GetIndexFields()[pRecordInfo->GetPKIndex()];
+ {
+ int cColumns = fsPK.GetCount();
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( fsPK.GetField( iColumn ) );
+ if ( iColumn )
+ psStatement->Append( " AND " );
+ psStatement->Append( "T." );
+ psStatement->Append( columnInfo.GetName() );
+ psStatement->Append( "=S." );
+ psStatement->Append( columnInfo.GetName() );
+ }
+ }
+
+ psStatement->Append( " WHEN NOT MATCHED BY TARGET THEN INSERT (" );
+
+ // build a string of the field names
+ {
+ int cColumns = pRecordInfo->GetNumColumns();
+ bool bAddedBefore = false;
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
+ if ( !columnInfo.BIsInsertable() )
+ continue;
+
+ if ( bAddedBefore )
+ psStatement->Append( ',' );
+ bAddedBefore = true;
+ psStatement->Append( columnInfo.GetName() );
+ }
+ }
+
+ psStatement->Append( ") VALUES (" );
+ {
+ int cColumns = pRecordInfo->GetNumColumns();
+ bool bAddedBefore = false;
+ for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
+ {
+ const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
+ if ( !columnInfo.BIsInsertable() )
+ continue;
+
+ if ( bAddedBefore )
+ psStatement->Append( ',' );
+ bAddedBefore = true;
+ psStatement->Append( "S." );
+ psStatement->Append( columnInfo.GetName() );
+ }
+ }
+ psStatement->Append( ");" );
+}
+
+void BuildSelectStatementText( TSQLCmdStr *psStatement, const CColumnSet & selectSet, const char *pchTopClause )
+{
+ *psStatement = "SELECT ";
+
+ if( pchTopClause )
+ {
+ psStatement->Append( pchTopClause );
+ psStatement->Append( ' ' );
+ }
+
+ // build a string of the field names
+ bool bAddedBefore = false;
+ FOR_EACH_COLUMN_IN_SET( selectSet, nColumnIndex )
+ {
+ const CColumnInfo &columnInfo = selectSet.GetColumnInfo( nColumnIndex );
+ if ( bAddedBefore )
+ psStatement->Append( ',' );
+ bAddedBefore = true;
+ psStatement->Append( columnInfo.GetName() );
+ }
+
+ psStatement->Append( " FROM ");
+ psStatement->Append( GSchemaFull().GetDefaultSchemaNameForCatalog( selectSet.GetRecordInfo()->GetESchemaCatalog() ) );
+ psStatement->Append( '.' );
+ psStatement->Append( selectSet.GetRecordInfo()->GetName() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds a SQL UPDATE statement
+// Input: pRecordInfo - record info describing table inserting into
+// bForPreparedStatement - if true, inserts values as '?' for later
+// binding. If false, values are inserted in text.
+// pchStatement - pointer to buffer to build statement in
+// cchStatement - size of buffer
+// pSQLRecord - pointer to record with data to update
+// iColumnMatch - column to use for WHERE condition
+// pvMatch - data value to use for WHERE condition
+// cubMatch - size of pvMatch data
+// rgiColumnUpdate - array of column #'s to update
+// ciColumnUpdate - count of column #'s to update
+//-----------------------------------------------------------------------------
+void BuildUpdateStatementText( TSQLCmdStr *psStatement, const CColumnSet & updateColumns )
+{
+ // build the UPDATE statement
+ psStatement->sprintf( "UPDATE %s.%s SET ", GSchemaFull().GetDefaultSchemaNameForCatalog( updateColumns.GetRecordInfo()->GetESchemaCatalog() ), updateColumns.GetRecordInfo()->GetName() );
+
+ // add each field we're updating to the UPDATE statement
+ FOR_EACH_COLUMN_IN_SET( updateColumns, nColumnIndex )
+ {
+ const CColumnInfo &columnInfo = updateColumns.GetColumnInfo( nColumnIndex );
+
+ if( nColumnIndex > 0 )
+ psStatement->Append( ',' );
+ psStatement->Append( columnInfo.GetName() );
+ psStatement->Append( "=?" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds a SQL UPDATE statement
+//-----------------------------------------------------------------------------
+void BuildDeleteStatementText( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
+{
+ psStatement->sprintf( "DELETE FROM %s.%s", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds a where clause for the provided fields
+//-----------------------------------------------------------------------------
+void AppendWhereClauseText( TSQLCmdStr *psClause, const CColumnSet & columnSet )
+{
+ // add each field we're updating to the UPDATE statement
+ FOR_EACH_COLUMN_IN_SET( columnSet, nColumnIndex )
+ {
+ const CColumnInfo &columnInfo = columnSet.GetColumnInfo( nColumnIndex );
+
+ if( nColumnIndex > 0 )
+ psClause->Append( " AND ");
+ psClause->Append( columnInfo.GetName() );
+ psClause->Append( "=?" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds an OUTPUT [fields] INTO [table] for the provided fields/data
+//-----------------------------------------------------------------------------
+void BuildOutputClauseText( TSQLCmdStr *psClause, const CColumnSet & columnSet )
+{
+ *psClause = " OUTPUT ";
+
+ FOR_EACH_COLUMN_IN_SET( columnSet, nColumnIndex )
+ {
+ const CColumnInfo &columnInfo = columnSet.GetColumnInfo( nColumnIndex );
+
+ if( nColumnIndex > 0 )
+ psClause->Append( ", ");
+
+ psClause->Append( " ? AS " );
+ psClause->Append( columnInfo.GetName() );
+ }
+
+ psClause->Append( " INTO " );
+ psClause->Append( columnSet.GetRecordInfo()->GetName() );
+}
+
+////-----------------------------------------------------------------------------
+//// Purpose: our own special "upsert" into a column with a uniqueness constraint
+////-----------------------------------------------------------------------------
+//EResult UpdateOrInsertUnique( CSQLAccess &sqlAccess, int iTable, int iField, CRecordBase *pRecordBase, int iIndexID )
+//{
+// // attempt an update - if it fails due to duplicate primary key, they can't use this
+// // url (it's taken) - if it succeeds but affects 0 rows, they didn't have a vanity url
+// // and we need to do an insert (which could again fail due to primary key constraints)
+// int cRecordsUpdated = 0;
+// bool bRet = sqlAccess.BYieldingUpdateFieldFromRecordWithIndex( iTable, &cRecordsUpdated, iField, pRecordBase, iIndexID );
+// if ( !bRet )
+// {
+// // ODBC is the suck - give me Spring JDBC templates, please.
+// if ( sqlAccess.GetLastError()->IsDuplicateInsertAttempt() )
+// {
+// return k_EResultDuplicateName;
+// }
+// return k_EResultFail;
+// }
+// else if ( 0 == cRecordsUpdated )
+// {
+// // the user didn't have an entry, so insert one.
+// bRet = sqlAccess.BYieldingInsertRecord( iTable, pRecordBase );
+// if ( !bRet )
+// {
+// // ODBC is the suck - give me Spring JDBC templates, please.
+// if ( sqlAccess.GetLastError()->IsDuplicateInsertAttempt() )
+// {
+// return k_EResultDuplicateName;
+// }
+// return k_EResultFail;
+// }
+// }
+// return k_EResultOK;
+//}
+//
+
+} // namespace GCSDK