summaryrefslogtreecommitdiff
path: root/gcsdk/sqlaccess/schema.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /gcsdk/sqlaccess/schema.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'gcsdk/sqlaccess/schema.cpp')
-rw-r--r--gcsdk/sqlaccess/schema.cpp1505
1 files changed, 1505 insertions, 0 deletions
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