diff options
Diffstat (limited to 'gcsdk/sqlaccess/schema.cpp')
| -rw-r--r-- | gcsdk/sqlaccess/schema.cpp | 1505 |
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 |