//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //============================================================================= #ifndef GCSCHEMA_H #define GCSCHEMA_H #ifdef _WIN32 #pragma once #endif namespace GCSDK { const int k_nColFlagIndexed = 0x0001; // this column is indexed. const int k_nColFlagUnique = 0x0002; // this column has a uniqueness constraint - creates implicit index const int k_nColFlagPrimaryKey = 0x0004; // this column has a primary key constraint - creates implicit uniqueness constraint & implicit index const int k_nColFlagAutoIncrement = 0x0008; // this column can have it's values created implicitly by the sql counter const int k_nColFlagClustered = 0x0010; // this column is clustered const int k_nColFlagAllConstraints = k_nColFlagUnique | k_nColFlagPrimaryKey; class CRecordInfo; struct VarFieldBlockInfo_t; struct VarFieldBlockInfo_t; struct Field_t; struct VarField_t; // Function type for altering field types when converting schemas typedef void (* PfnAlterField_t )( void *pvDest, const void *pvSrc ); // EPrimaryKeyType // This shows if a table has a primary key and, if so, if it has multiple columns // or not. enum EPrimaryKeyType { k_EPrimaryKeyTypeNone = 0, // none at all k_EPrimaryKeyTypeSingle, // single-column primary key k_EPrimaryKeyTypeMulti, // multi-column primary key }; // EWipePolicy // This tells us if a table is supposed to be wiped before all tests, // before all tests except stress tests, or not to be wiped before tests. enum EWipePolicy { k_EWipePolicyPreserveAlways = 0, // don't wipe table k_EWipePolicyPreserveForStress = 1, // preserve for stress tests, wipe before regular tests k_EWipePolicyWipeForAllTests = 2, // wipe table prior to all tests }; //----------------------------------------------------------------------------- // Field // This defines the schema of a single field in one of our tables. //----------------------------------------------------------------------------- struct Field_t { bool BGetIntData( uint8 *pubRecord, uint64 *pulRet ) const; bool BGetFloatData( uint8 *pubRecord, float *fRet ) const; bool SetIntData( uint8 *pubRecord, uint64 ulValue ) const; // Not all fields are updated wholly at a time int CubFieldUpdateSize() const; // Handy helpers - complex fields like "rolling unique" are // still binary bool BIsStringType() const; bool BIsVariableLength() const; // Members that get serialized EGCSQLType m_EType; // Field type uint32 m_cubLength; // Length of the field in bytes uint32 m_cchMaxLength; // maximum length of the field in characters char m_rgchName[k_cSQLObjectNameMax]; // Human-readable name of this field char m_rgchSQLName[k_cSQLObjectNameMax]; // SQL name of this field // Members that don't get serialized uint32 m_nColFlags; // k_nColFlag* bits for this field uint32 m_dubOffset; // Offset of this field from beginning of record }; // -------------------------------------------------------------------------- // Information about a foreign key relationship defined on a table struct FKColumnRelation_t { char m_rgchCol[k_cSQLObjectNameMax+1]; char m_rgchParentCol[k_cSQLObjectNameMax+1]; FKColumnRelation_t() { Q_memset( m_rgchCol, 0, Q_ARRAYSIZE( m_rgchCol ) ); Q_memset( m_rgchParentCol, 0, Q_ARRAYSIZE( m_rgchParentCol ) ); } bool operator==( const FKColumnRelation_t &other ) const { if ( Q_stricmp( m_rgchCol, other.m_rgchCol ) ) return false; if ( Q_stricmp( m_rgchParentCol, other.m_rgchParentCol ) ) return false; return true; } }; struct FKData_t { char m_rgchName[k_cSQLObjectNameMax+1]; char m_rgchParentTableName[k_cSQLObjectNameMax+1]; CCopyableUtlVector m_VecColumnRelations; EForeignKeyAction m_eOnDeleteAction; EForeignKeyAction m_eOnUpdateAction; FKData_t() { Q_memset( m_rgchName, 0, Q_ARRAYSIZE( m_rgchName ) ); Q_memset( m_rgchParentTableName, 0, Q_ARRAYSIZE( m_rgchParentTableName ) ); m_eOnDeleteAction = k_EForeignKeyActionNoAction; m_eOnUpdateAction = k_EForeignKeyActionNoAction; } bool operator==( const FKData_t &other ) const { if ( Q_stricmp( m_rgchName, other.m_rgchName ) ) return false; if ( Q_stricmp( m_rgchParentTableName, other.m_rgchParentTableName ) ) return false; if ( m_eOnDeleteAction != other.m_eOnDeleteAction || m_eOnUpdateAction != other.m_eOnUpdateAction ) return false; FOR_EACH_VEC( m_VecColumnRelations, i ) { bool bFoundInOther = false; const FKColumnRelation_t &cols = m_VecColumnRelations[i]; FOR_EACH_VEC( other.m_VecColumnRelations, j ) { const FKColumnRelation_t &colsOther = other.m_VecColumnRelations[j]; if ( cols == colsOther ) { bFoundInOther = true; break; } } if ( !bFoundInOther ) return false; } return true; } #ifdef DBGFLAG_VALIDATE // Validate our internal structures void Validate( CValidator &validator, const char *pchName ) { VALIDATE_SCOPE(); ValidateObj( m_VecColumnRelations ); } #endif }; #pragma pack( push, 1 ) //----------------------------------------------------------------------------- // SerSchema // Defines the binary serialization format for a schema. //----------------------------------------------------------------------------- struct SerSchema_t { uint32 m_cub; // Size of the whole schema (including header and fields) int32 m_iTable; // Our table's iTable char m_rgchName[k_cSQLObjectNameMax]; // Human-readable name of this table int16 m_cField; // # of fields in the schema (int16 for backward-compatibility reasons) int16 m_ETableGroup; // Our table's TableGroup (int16 for backward-compatibility reasons) - OBSOLETE }; //----------------------------------------------------------------------------- // SerField // Defines the binary serialization format for a field in a schema // Note that certain fields are missing from this because we only use serialized // schemas for schema mapping. Fields that only affect runtime behavior (like // indexing) are always defined by the intrinsic schema. //----------------------------------------------------------------------------- struct SerField_t { int32 m_EType; // Field type uint32 m_cubLength; // Length of field data in bytes // For rolling fields, high 16 bits are the // size of each element char m_rgchName[k_cSQLObjectNameMax];// Human-readable name of this field char m_rgchSQLName[k_cSQLObjectNameMax]; // SQL name of this field }; #pragma pack( pop ) //----------------------------------------------------------------------------- // Schema conversion instructions // These specify various operations that can be performed when converting // from one Schema to another. //----------------------------------------------------------------------------- struct DeleteField_t { char m_rgchFieldName[k_cSQLObjectNameMax]; // Name of the field to delete }; struct RenameField_t { char m_rgchFieldNameOld[k_cSQLObjectNameMax]; // Rename a field with this name int m_iFieldDst; // to this field }; struct AlterField_t { char m_rgchFieldNameOld[k_cSQLObjectNameMax]; // Name of field in the old schema int m_iFieldDst; // iField of it in the new PfnAlterField_t m_pfnAlterFunc; // Function to translate the data }; //----------------------------------------------------------------------------- // FieldSet_t describes a collection of fields in an index, as well as // attributes of the index itself //----------------------------------------------------------------------------- class FieldSet_t { public: FieldSet_t( bool bUnique, bool bClustered, const CUtlVector& vecFields, const char* pstrIndexName ) : m_bClustered( bClustered ), m_bUnique( bUnique ) { m_VecFields = vecFields; // zero means to use the server default m_nFillFactor = 0; // null name is allowed for primary keys if ( pstrIndexName != NULL ) Q_strncpy( m_szIndexName, pstrIndexName, Q_ARRAYSIZE( m_szIndexName ) ); else m_szIndexName[0] = 0; } FieldSet_t( ) { } ~FieldSet_t( ) { } FieldSet_t( const FieldSet_t &refOther ) { DoAssignment( refOther ); } FieldSet_t& operator=( const FieldSet_t &refOther ) { DoAssignment( refOther ); return *this; } void DoAssignment( const FieldSet_t &refOther ) { m_VecFields = refOther.m_VecFields; m_VecIncluded = refOther.m_VecIncluded; m_bClustered = refOther.m_bClustered; m_bUnique = refOther.m_bUnique; m_nFillFactor = refOther.m_nFillFactor; Q_strncpy( m_szIndexName, refOther.m_szIndexName, Q_ARRAYSIZE( m_szIndexName ) ); } // get count of fields in this index int GetCount() const { return m_VecFields.Count(); } // get count of included fields in this index int GetIncludedCount() const { return m_VecIncluded.Count(); } void AddIncludedColumn( int nIndex ) { m_VecIncluded.AddToTail( nIndex ); } void AddIncludedColumns( const CUtlVector &refVec ) { m_VecIncluded.AddVectorToTail( refVec ); } // get a particular field ID // the returned index is into the VecFields of the associated schema int GetField( int nIndex ) const { return m_VecFields[ nIndex ]; } int GetIncludedField( int nIndex ) const { return m_VecIncluded[ nIndex ]; } // is this index clustered? bool IsClustered() const { return m_bClustered; } // is this index unique? bool IsUnique() const { return m_bUnique; } void SetClustered( bool bIsClustered ) { m_bClustered = bIsClustered; } void SetFillFactor( int nFactor ) { Assert( nFactor >= 0 && nFactor <= 100 ); m_nFillFactor = nFactor; } int GetFillFactor( ) const { return m_nFillFactor; } const char* GetIndexName() const { return m_szIndexName; } // determine if this fieldset is equal to the other one static bool CompareFieldSets( const FieldSet_t& refThis, CRecordInfo* pRecordInfoThis, const FieldSet_t& refOther, CRecordInfo* pRecordInfoOther ); #ifdef DBGFLAG_VALIDATE // Validate our internal structures void Validate( CValidator &validator, const char *pchName ) { VALIDATE_SCOPE(); m_VecFields.Validate( validator, "m_VecFields" ); m_VecIncluded.Validate( validator, "m_VecIncluded" ); } #endif private: CUtlVector m_VecFields; // ids of fields; indexes into m_VecFields of CSchema for a table CUtlVector m_VecIncluded; // ids of included fields int m_nFillFactor; // fill factor for the index; zero means to use the server's default char m_szIndexName[k_cSQLObjectNameMax]; // name of this index bool m_bClustered:1; // is this index clustered? bool m_bUnique:1; // is this index unique? }; //----------------------------------------------------------------------------- enum ESchemaCatalog { k_ESchemaCatalogInvalid = -1, k_ESchemaCatalogMain = 0, // main GC catalog k_ESchemaCatalogOGS = 4, // operational game stats }; extern const char* PchNameFromESchemaCatalog( ESchemaCatalog e ); //----------------------------------------------------------------------------- // CSchema // This defines the schema for a single table. The schema essentially defines // what's in the table (ie, field 0 is a 32 char string called "Name", etc.) // The schema is in charge of manipulating individual records within the table. //----------------------------------------------------------------------------- class CSchemaFull; class CSchema { public: // Constructors & destructors CSchema(); ~CSchema(); // Recalculates field offsets and maximum record size. // Must be called after changing schema. void CalcOffsets(); // called to make final calculations when all fields/indexes/etc have been added and the schema is ready to be used void PrepareForUse(); // Sizing information uint32 CubSerialSchema() const { return( sizeof( SerSchema_t ) + m_VecField.Count() * sizeof( SerField_t ) ); } // Size of total fixed-length portion of record uint32 CubRecordFixed() const { return( m_cubRecord ); } // Size of the total variable-length portion of record (zero if no var-length fields) uint32 CubRecordVariable( const void *pvRecord ) const; // Does this record have variable-length fields? bool BHasVariableFields() const { return m_bHasVarFields; } VarFieldBlockInfo_t* PVarFieldBlockInfoFromRecord( const void *pvRecord ) const; // Access field data - fixed or variable (may return NULL/0 for empty var field) bool BGetFieldData( const void *pvRecord, int iField, uint8 **ppubField, uint32 *pcubField ) const; bool BSetFieldData( void *pvRecord, int iField, uint8 *pubField, uint32 cubField, bool *pbVarBlockRealloced ); bool BGetVarField( const void *pvRecord, const VarField_t *pVarField, uint8 **ppubField, uint32 *pcubField ) const; bool BSetVarField( void *pvRecord, VarField_t *pVarField, const void *pvData, uint32 cubData, bool *pbRealloced, bool bFreeOnRealloc ); // Adjust var-block pointer, if present, to point just after the fixed part of the record void FixupDeserializedRecord( void *pvRecord ); // Render a record in text format void RenderRecord( uint8 *pubRecord ); void RenderField( uint8 *pubRecord, int iField, int cchBuffer, char *pchBuffer ); // Accessors void SetITable( int iTable ) { m_iTable = iTable; } int GetITable() const { return m_iTable; } int GetCField() const { return m_VecField.Count(); } void SetReportingInterval( int nInterval ) { m_nReportingInterval = nInterval; } int GetReportingInterval( ) const { return m_nReportingInterval; } Field_t &GetField( int iField ) { return m_VecField[iField]; } const Field_t &GetField( int iField ) const { return m_VecField[iField]; } VarField_t *GetPVarField( void *pvRecord, int iField ) { return ( VarField_t * )( ( uint8 * ) pvRecord + m_VecField[iField].m_dubOffset ); } void SetName( const char *pchName ) { Q_strncpy( m_rgchName, pchName, sizeof( m_rgchName ) ); } const char *GetPchName() const { return m_rgchName; } const FieldSet_t& GetPKFields() { Assert( m_iPKIndex != -1 ); return GetIndexes()[m_iPKIndex]; } int GetPKIndex() const { return m_iPKIndex; } const CUtlVector& GetIndexes() { return m_VecIndexes; } const CUtlVector& GetFTSColumns() { return m_VecFullTextIndexes; } int GetFTSIndexCatalog() const { return m_nFullTextIndexCatalog; } ESchemaCatalog GetESchemaCatalog() const { return m_eSchemaCatalog; } void SetESchemaCatalog( ESchemaCatalog eSchemaCatalog ) { m_eSchemaCatalog = eSchemaCatalog; } // If cRecordMax is non-zero, this is a rolling table that only // holds on to cRecordMax records at most. void SetCRecordMax( int cRecordMax ) { m_cRecordMax = cRecordMax; } int GetCRecordMax() const { return m_cRecordMax; } // Is this table for TESTs only? void SetBTestTable( bool bTestTable ) { m_bTestTable = bTestTable; } bool GetBTestTable() const { return m_bTestTable; } // Randomly init a record or field to random values void InitRecordRandom( uint8 *pubRecord, uint32 unPrimaryIndex, bool *pbVarBlockRealloced, bool bFreeVarBlockOnRealloc ); void SetFieldRandom( uint8 *pubRecord, int iField, bool *pbVarBlockRealloced, bool bFreeVarBlockOnRealloc ); // Checksum the schema uint32 CalcChecksum(); // pre-allocate space in the field array void EnsureFieldCount( int cFields ) { m_VecField.EnsureCapacity( cFields ); } // This adds a field from our intrinsic schema to us void AddField( char *pchName, char *pchSQLName, EGCSQLType eType, uint32 cubSize, int cchMaxLength ); void AddIntField( char *pchName, char *pchSQLName, EGCSQLType eType, int cubSize ); // We want to make a particular field the primary key int PrimaryKey( bool bClustered, int nFillFactor, const char *pchName ); // we want to make a particular list of fields the primary key int PrimaryKeys( bool bClustered, int nFillFactor, const char *pchNames ); // We want to index a particular field by name int IndexField( const char *pchName, const char *pchIndexName ); // We want to index a particular list of fields in a group int IndexFields( const char *pchIndexName, const char *pchNames ); // We want a certain index to additionally include a list of fields void AddIncludedFields( const char *pchIndexName, const char *pchNames ); // We want to unique index a particular list of fields in a group int UniqueFields( const char *pchIndexName, const char *pchNames ); // add a full-text index to the given column void AddFullTextIndex( CSchemaFull *pSchemaFull, const char *pchCatalogName, const char *pchColumnName ); // We want to index a particular field by field number // (field number is an index into the m_VecField array) int AddIndexToFieldNumber( int iField, const char *pchIndexName, bool bClustered ); // We want to index a particular set of fields // pchNames includes the names, separated by commas, of each field int AddIndexToFieldList( const char *pchNames, const char *pchIndexName, int nFlags, int nFillFactor ); // We want a unique index on a particular field int UniqueField( const char *pchName, const char *pchIndexName ); // We want to have a clustered index on a particular field by name int ClusteredIndexField( int nFillFactor, const char *pchName, const char *pchIndexName ); // We want to index a particular list of fields in a group int ClusteredIndexFields( int nFillFactor, const char *pchIndexName, const char *pchNames ); // We want an autoinc on a particular field void AutoIncrementField( char *pchName ); // catalog on which we'll enable FTS void EnableFTS( ESchemaCatalog eCatalog ); // adds a full text catalog with the given name on the identified fileset void AddFullTextCatalog( ESchemaCatalog eCatalog, const char *pstrCatalogName, const char *pstrFileGroupName ); // Adds a FK on the table void AddFK( const char* pchName, const char* pchColumn, const char* pchParentTable, const char* pchParentColumn, EForeignKeyAction eOnDeleteAction, EForeignKeyAction eOnUpdateAction ); // Access FK data int GetFKCount(); FKData_t &GetFKData( int iIndex ); void SetTestWipePolicy( EWipePolicy policy ) { m_wipePolicy = policy; } EWipePolicy GetTestWipePolicy() const { return m_wipePolicy; } void SetBAllowWipeTableInProd( bool bVal ) { m_bAllowWipeInProd = bVal; } bool BAllowWipeTableInProd() const { return m_bAllowWipeInProd; } void SetPrepopulatedTable( ) { m_bPrepopulatedTable = true; } bool BPrepopulatedTable( ) const { return m_bPrepopulatedTable; } // Find the field with a given name (returns k_iFieldNil if not found) int FindIField( const char *pchName ); int FindIFieldSQL( const char *pchName ); // Helper functions for recording schema conversion operations void AddDeleteField( const char *pchFieldName ); void AddRenameField( const char *pchFieldNameOld, const char *pchFieldNameNew ); void AddAlterField( const char *pchFieldNameOld, const char *pchFieldNameNew, PfnAlterField_t pfnAlterField ); // Schema conversion helper: figure out what field to map a field from a different schema to bool BCanConvertField( const char *pchFieldSrc, int *piFieldDst, PfnAlterField_t *ppfnAlterField ); CRecordInfo *GetRecordInfo() { return m_pRecordInfo; } const CRecordInfo *GetRecordInfo() const { return m_pRecordInfo; } void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures void ValidateRecord( uint8 *pubRecord ); // Validate a record that uses our schema // cached queries const char *GetInsertStatementText() const; const char *GetMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert(); const char *GetMergeStatementTextOnPKWhenNotMatchedInsert(); private: int m_iTable; // The index of our table int m_iPKIndex; // index into of m_VecIndexes of our PK index; k_iFieldNil if no PK char m_rgchName[k_cSQLObjectNameMax]; // Name of this table CUtlVector m_VecField; // All the fields that make up the schema CUtlVector m_VecIndexes; // vector of all fields in all indexes int m_cRecordMax; // Max # records in the table (for rolling tables) bool m_bTestTable; // Table exists only for tests bool m_bAllowWipeInProd; // should we allow WipeTable operations on this table in the beta/public universe? EWipePolicy m_wipePolicy; // should this table be wiped between all tests, no tests, or non-stress tests? bool m_bHasVarFields; // True if this table has variable-length fields bool m_bPrepopulatedTable; // true if this table is pre-populated EPrimaryKeyType m_nHasPrimaryKey; // what kind of PK do we have, if any? CRecordInfo *m_pRecordInfo; // The record description corresponding to this schema. (Similar info, record description is new form, have both for a while during DS->SQL switch) CUtlVector m_VecFullTextIndexes; // vector of indexes into m_VecField of fields covered by this table's full-text index. int m_nFullTextIndexCatalog; // index of catalog to use for creating full-text indexes CUtlVector m_VecFKData; // data on foreign keys for this schema object uint32 m_cubRecord; // Binary record length int m_nReportingInterval; // reporting interval of this table if stats; 0 if not stats ESchemaCatalog m_eSchemaCatalog; // what catalog does this table live in? // Schema conversion instructions CUtlVector m_VecDeleteField; CUtlVector m_VecRenameField; CUtlVector m_VecAlterField; // Cached queries mutable CUtlString m_sInsertStatementText; // Cached insert statement for the table CUtlString m_sMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert; // Cached insert or update via MERGE statement for the table CUtlString m_sMergeStatementTextOnPKWhenNotMatchedInsert; // Cached insert via MERGE statement for the table }; } // namespace GCSDK #endif // GCSCHEMA_H