diff options
Diffstat (limited to 'gcsdk/sqlaccess/sqlutil.cpp')
| -rw-r--r-- | gcsdk/sqlaccess/sqlutil.cpp | 918 |
1 files changed, 918 insertions, 0 deletions
diff --git a/gcsdk/sqlaccess/sqlutil.cpp b/gcsdk/sqlaccess/sqlutil.cpp new file mode 100644 index 0000000..6744d21 --- /dev/null +++ b/gcsdk/sqlaccess/sqlutil.cpp @@ -0,0 +1,918 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + + +#include "stdafx.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +namespace GCSDK +{ +const char *GetInsertArgString() +{ + static char s_str[1024]; + static bool s_bInit = false; + + if ( !s_bInit ) + { + for ( int i = 0; i < 1023; i++ ) + { + s_str[i] = i % 2 == 0 ? '?' : ','; + } + + s_str[1023] = NULL; + s_bInit = true; + } + + return s_str; +} + +uint32 GetInsertArgStringChars( uint32 nNumParams ) +{ + AssertMsg( nNumParams <= GetInsertArgStringMaxParams(), "Error: Requested more characters than are provided by the GetInsertArgString" ); + if( nNumParams == 0 ) + return 0; + + return nNumParams * 2 - 1; +} + +uint32 GetInsertArgStringMaxParams() +{ + return 512; +} + +//----------------------------------------------------------------------------- +// Purpose: Converts array of field data to text for SQL IN clause +// Input: columnInfo - schema of column being converted +// pubData - pointer to array of data to convert +// cubData - size of array of data +// rgchResult - pointer to output buffer +// cubResultLen - size of output buffer +// bForPreparedStatement - Should we prepare the text for a prepared statement or directly place the values? +//----------------------------------------------------------------------------- +void ConvertFieldArrayToInText( const CColumnInfo &columnInfo, byte *pubData, int cubData, char *rgchResult, int cubResultLen, bool bForPreparedStatement ) +{ + int32 cubLength = columnInfo.GetFixedSize(); + Assert( cubData % cubLength == 0 ); + int32 nArrayCount = cubData / cubLength; + + int32 len = 0; + rgchResult[len++] = '('; + for( int i = 0; i < nArrayCount; ++i ) + { + if ( bForPreparedStatement ) + { + if ( i < nArrayCount - 1 ) + { + rgchResult[len++] = '?'; + rgchResult[len++] = ','; + } + else + { + rgchResult[len++] = '?'; + rgchResult[len++] = ')'; + } + } + else + { + switch ( columnInfo.GetType() ) + { + case k_EGCSQLType_int8: + if ( i < nArrayCount - 1 ) + len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (byte *) pubData ) ); + else + len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (byte *) pubData ) ); + break; + case k_EGCSQLType_int16: + if ( i < nArrayCount - 1 ) + len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (short *) pubData ) ); + else + len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (short *) pubData ) ); + break; + case k_EGCSQLType_int32: + if ( i < nArrayCount - 1 ) + len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (int *) pubData ) ); + else + len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (int *) pubData ) ); + break; + case k_EGCSQLType_int64: + if ( i < nArrayCount - 1 ) + len += Q_snprintf( rgchResult + len, cubResultLen - len, "%lld,", *( (int64 *) pubData ) ); + else + len += Q_snprintf( rgchResult + len, cubResultLen - len, "%lld)", *( (int64 *) pubData ) ); + break; + default: + AssertMsg( false, "Unsupported data type for non prepares statement with IN clause\n" ); + rgchResult[0] = 0; + return; + } + } + + if( len >= cubResultLen - 1 ) + { + AssertMsg( false, "Generation of IN clause foverflowed\n" ); + rgchResult[0] = 0; + return; + } + pubData += cubLength; + } + + rgchResult[len] = 0; + return; +} + + +//----------------------------------------------------------------------------- +// Purpose: Converts field data to text equivalent for SQL statement +// Input: eFieldType - The type of the field to convert to text +// pubRecord - pointer to record data to convert +// cubRecord - size of record data +// rgchField - pointer to output buffer +// cchField - size of output buffer +//----------------------------------------------------------------------------- +void ConvertFieldToText( EGCSQLType eFieldType, uint8 *pubRecord, int cubRecord, char *rgchField, int cchField, bool bQuoteString ) +{ + char rgchTmp[k_cMedBuff]; + + switch ( eFieldType ) + { + case k_EGCSQLType_int8: + Q_snprintf( rgchField, cchField, "%d", *( (byte *) pubRecord ) ); + break; + case k_EGCSQLType_int16: + Q_snprintf( rgchField, cchField, "%d", *( (short *) pubRecord ) ); + break; + case k_EGCSQLType_int32: + Q_snprintf( rgchField, cchField, "%d", *( (int *) pubRecord ) ); + break; + case k_EGCSQLType_int64: + Q_snprintf( rgchField, cchField, "%lld", *( (int64 *) pubRecord ) ); + break; + case k_EGCSQLType_float: + Q_snprintf( rgchField, cchField, "%f", *((float*) pubRecord) ); + break; + case k_EGCSQLType_double: + Q_snprintf( rgchField, cchField, "%f", *((double*) pubRecord) ); + break; + case k_EGCSQLType_String: + if ( pubRecord && *pubRecord ) + { + Assert( cubRecord + 1 < Q_ARRAYSIZE( rgchTmp ) ); + + Q_memcpy( rgchTmp, (char *) pubRecord, cubRecord ); + rgchTmp[cubRecord] = 0; + + if ( bQuoteString ) + { + EscapeStringValue( rgchTmp, Q_ARRAYSIZE( rgchTmp ) ); + Q_snprintf( rgchField, cchField, "'%s'", rgchTmp ); + } + else + { + Q_strncpy( rgchField, rgchTmp, cchField ); + } + } + else + { + if ( bQuoteString ) + { + Q_strncpy( rgchField, "''", cchField ); + } + else + { + Q_strncpy( rgchField, "", cchField ); + } + } + break; + case k_EGCSQLType_Blob: + case k_EGCSQLType_Image: + Q_strncpy( rgchField, "0x", cchField ); + Q_binarytohex( pubRecord, cubRecord, rgchField + 2, cchField - 2 ); + break; + default: + Assert( false ); + break; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the text SQL type for a given field +// Input: field - field to determine type for +// pchBuf - pointer to output buffer +// cchBuf - size of output buffer +// Output: returns pchBuf for convenience of one-line usage +//----------------------------------------------------------------------------- +char *SQLTypeFromField( const CColumnInfo &colInfo, char *pchBuf, int cchBuf ) +{ + EGCSQLType eType = colInfo.GetType(); + *pchBuf = 0; + switch ( eType ) + { + case k_EGCSQLType_int8: + Q_strncpy( pchBuf, "TINYINT", cchBuf ); + break; + case k_EGCSQLType_int16: + Q_strncpy( pchBuf, "SMALLINT", cchBuf ); + break; + case k_EGCSQLType_int32: + Q_strncpy( pchBuf, "INT", cchBuf ); + break; + case k_EGCSQLType_int64: + Q_strncpy( pchBuf, "BIGINT", cchBuf ); + break; + case k_EGCSQLType_float: + Q_strncpy( pchBuf, "REAL", cchBuf ); + break; + case k_EGCSQLType_double: + Q_strncpy( pchBuf, "FLOAT", cchBuf ); + break; + case k_EGCSQLType_String: + Q_snprintf( pchBuf, cchBuf, "VARCHAR(%d)", colInfo.GetMaxSize() ); + break; + case k_EGCSQLType_Blob: + Q_snprintf( pchBuf, cchBuf, "VARBINARY(%d)", colInfo.GetMaxSize() ); + break; + case k_EGCSQLType_Image: + Q_strncpy( pchBuf, "IMAGE", cchBuf ); + break; + default: + Assert( false ); + break; + } + + return pchBuf; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Escapes any single quotes to a string value to double single quotes +// Input: rgchField - text to escape +// cchField - size of text buffer +// Notes: The text will be escaped and expanded in place in the buffer. +// In the worst case, the text may expand by 2x. (If the field is all +// single quotes.) So, you must pass in a buffer which is at least +// twice as long as the text length so we can guarantee to be able to +// escape the string. +//----------------------------------------------------------------------------- +void EscapeStringValue( char *rgchField, int cchField ) +{ + // TODO - what else do we need to escape? %() ... + char *pubCur = rgchField; + int nLen = 0; + int cSingleQuotes = 0; + + // This function gets called on every text field we write but most text fields + // don't need to be escaped, so try to be as fast as possible in the normal case. + + // first, walk through the string and count the string length and number of single quotes + while ( *pubCur ) + { + if ( '\'' == *pubCur ) + cSingleQuotes++; + nLen ++; + pubCur++; + } + + // if no single quotes, nothing to do + if ( !cSingleQuotes ) + return; + + // caller must pass in a buffer that's long enough for expansion + Assert( nLen + cSingleQuotes + 1 <= cchField ); + if ( !( nLen + cSingleQuotes + 1 <= cchField ) ) + return; + + // We know exactly how many characters the string will expand by (the # of single quotes). Walk backward + // and copy the characters into the right places. This touches each character only once. + pubCur = rgchField + nLen + cSingleQuotes; + *pubCur = 0; + pubCur--; + while ( pubCur > rgchField && cSingleQuotes > 0 ) + { + // read pointer is offset from write pointer by # of remaining single quotes + char *pubRead = pubCur - cSingleQuotes; + Assert( pubRead >= rgchField ); + // copy each character + *pubCur = *pubRead; + if ( '\'' == *pubRead ) + { + // if the character is a single quote, back up one more and insert another single quote to escape it + pubCur --; + *pubCur = '\''; + // decrement # of single quotes remaining + cSingleQuotes --; + Assert( cSingleQuotes >= 0 ); + } + pubCur--; + } +} +//----------------------------------------------------------------------------- +// Purpose: Adds constraint information to a SQL command to add or remove constraint +// Input: pchTableName - name of table +// pchColumnName - name of column +// nColFlagConstraint - flag with which constraint to +// bForAdd - whether constraint is being added or removed +// pchCmd - buffer to append SQL command to +// cchCmd - size of buffer +//----------------------------------------------------------------------------- +void AppendConstraint( const char *pchTableName, const char *pchColumnName, int nColFlagConstraint, bool bForAdd, + bool bClustered, CFmtStrMax & sCmd, int nFillFactor ) +{ + Assert( pchTableName && pchTableName[0] ); + Assert( pchColumnName && pchColumnName[0] ); + + switch ( nColFlagConstraint ) + { + case k_nColFlagPrimaryKey: + sCmd.AppendFormat( " CONSTRAINT %s_%s_PrimaryKey", pchTableName, pchColumnName); + if ( bForAdd ) + { + sCmd += " PRIMARY KEY "; + if ( bClustered ) + { + sCmd.AppendFormat( " CLUSTERED WITH (FILLFACTOR = %d) ", nFillFactor ); + } + else + { + sCmd += "NONCLUSTERED"; + } + } + break; + case k_nColFlagUnique: + /* do nothing - the uniqueness will be handled by creation of an index */ + break; + case k_nColFlagAutoIncrement: + sCmd += " IDENTITY"; + break; + default: + AssertMsg( false, "CSQLThread::AppendContraint: invalid constraint type" ); + break; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Adds constraint information to a SQL command to add or remove constraint +// Input: pRecordInfo - record info describing table +// pColumnInfo - record info describing column +// bForAdd - whether constraint is being added or removed +// pchCmd - buffer to append SQL command to +// cchCmd - size of buffer +//----------------------------------------------------------------------------- +void AppendConstraints( const CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo, bool bForAdd, CFmtStrMax & sCmd ) +{ + Assert( pRecordInfo != NULL ); + Assert( pColumnInfo != NULL ); + + if ( pColumnInfo->BIsPrimaryKey() ) + { + // any column in a PK can't be NULL. + if ( bForAdd ) + { + sCmd += " NOT NULL"; + } + + // only add primary key constraint here if it is a single-column PK + if ( pRecordInfo->GetPrimaryKeyType() == k_EPrimaryKeyTypeSingle ) + { + // get the fields on the primary key + const CUtlVector< FieldSet_t > &refFields = pRecordInfo->GetIndexFields( ); + int nFillFactor = refFields.Element( pRecordInfo->GetPKIndex() ).GetFillFactor(); + AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagPrimaryKey, bForAdd, pColumnInfo->BIsClustered(), sCmd, nFillFactor ); + } + } + else if ( pColumnInfo->BIsUnique() ) + { + AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagUnique, bForAdd, pColumnInfo->BIsClustered(), sCmd, 0 ); + } + + if ( pColumnInfo->BIsAutoIncrement() ) + { + AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagAutoIncrement, bForAdd, pColumnInfo->BIsClustered(), sCmd, 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Generates the "CONSTRAINT ..." text for the table primary key +//----------------------------------------------------------------------------- +void BuildTablePKConstraintText( TSQLCmdStr *psStatement, CRecordInfo *pRecordInfo ) +{ + const FieldSet_t& vecFields = pRecordInfo->GetPKFields( ); + + psStatement->sprintf( "CONSTRAINT %s_PrimaryKey PRIMARY KEY %s ( ", + pRecordInfo->GetName(), + vecFields.IsClustered() ? "CLUSTERED" : "NONCLUSTERED" ); + + for ( int nField = 0; nField < vecFields.GetCount(); nField++ ) + { + // what field is the next column in our index? + int nThisField = vecFields.GetField( nField ); + const CColumnInfo& columnInfo = pRecordInfo->GetColumnInfo(nThisField); + + if (nField != 0) + { + *psStatement += ", "; + } + *psStatement += columnInfo.GetName(); + } + + // close our list + *psStatement += ") "; + + if ( vecFields.GetFillFactor() != 0 ) + { + // non-default fill factor, so specify it + psStatement->AppendFormat( " WITH FILLFACTOR = %d ", + vecFields.GetFillFactor() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds constraint information to a SQL command to add or remove table-level constraints +// Input: pRecordInfo - record info describing table +// pchCmd - buffer to append SQL command to +// cchCmd - size of buffer +//----------------------------------------------------------------------------- + +void AppendTableConstraints( CRecordInfo *pRecordInfo, CFmtStrMax & sCmd ) +{ + // the only supported table constraint is for PKs or FKs + if ( pRecordInfo->GetPrimaryKeyType() == k_EPrimaryKeyTypeMulti ) + { + TSQLCmdStr tmp; + BuildTablePKConstraintText( &tmp, pRecordInfo ); + sCmd += ", "; + sCmd += tmp; + } + + // Look for FKs required on this table + // the only supported table constraint is for PKs or FKs + int cFKs = pRecordInfo->GetFKCount(); + for( int i=0; i < cFKs; ++i ) + { + FKData_t &fkData = pRecordInfo->GetFKData( i ); + + CFmtStr sColumns, sParentColumns; + FOR_EACH_VEC( fkData.m_VecColumnRelations, nCol ) + { + FKColumnRelation_t &colRelation = fkData.m_VecColumnRelations[nCol]; + if ( nCol > 0) + { + sColumns += ","; + sParentColumns += ","; + } + sColumns += colRelation.m_rgchCol; + sParentColumns += colRelation.m_rgchParentCol; + } + + TSQLCmdStr sTmp; + sTmp.sprintf( ", CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s) ON DELETE %s ON UPDATE %s", + fkData.m_rgchName, sColumns.Access(), fkData.m_rgchParentTableName, sParentColumns.Access(), + PchNameFromEForeignKeyAction( fkData.m_eOnDeleteAction ), PchNameFromEForeignKeyAction( fkData.m_eOnUpdateAction ) ); + + // add to the command + sCmd += sTmp; + } +} + + + + +//----------------------------------------------------------------------------- +// Purpose: Builds a SQL INSERT statement +// Input: psStatement - The string to put the statement into +// pRecordInfo - record info describing table inserting into +//----------------------------------------------------------------------------- +void BuildInsertStatementText( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo ) +{ + psStatement->sprintf("INSERT INTO %s.%s (", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() ); + + // build a string of the field names + int cColumns = pRecordInfo->GetNumColumns(); + int nInsertable = 0; + bool bAddedBefore = false; + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ); + if ( !columnInfo.BIsInsertable() ) + continue; + + nInsertable++; + + if ( bAddedBefore ) + psStatement->Append( ',' ); + bAddedBefore = true; + psStatement->Append( columnInfo.GetName() ); + } + + psStatement->AppendFormat( ") VALUES (%.*s)", GetInsertArgStringChars( nInsertable ), GetInsertArgString() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Builds a SQL INSERT statement +// IMPORTANT NOTE - This Insert statement will use the Microsoft SQL Server +// specific clause 'OUTPUT Inserted.ColumnName' +// The result of that will be that the SQL statement will return to us +// the columns that could not be specified by the Insert. +// At the time of writing, that is primarily AutoIncrement columns, +// however in theory we should be able to recover any computed column +// from SQL server, with the caveats specified at : +// http://msdn.microsoft.com/en-us/library/ms177564.aspx +// +// Input: psStatement - The output statement string +// pRecordInfo - record info describing table inserting into +//----------------------------------------------------------------------------- + +void BuildInsertAndReadStatementText( TSQLCmdStr *psStatement, CUtlVector<int> *pvecOutputFields, const CRecordInfo *pRecordInfo ) +{ + psStatement->sprintf("INSERT INTO %s.%s (", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() ); + + // build a string of the field names + int nInsertable = 0; + int cColumns = pRecordInfo->GetNumColumns(); + bool bAddedBefore = false; + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ); + if ( !columnInfo.BIsInsertable() ) + continue; + + nInsertable++; + + if ( bAddedBefore ) + psStatement->Append( ',' ); + bAddedBefore = true; + psStatement->Append( columnInfo.GetName() ); + } + + bAddedBefore = false ; + int nOutputColumn = 0; + for( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ) ; + + // + // If we can't Insert it - we want SQL Server to tell us what value was stored + // in the column !! + // + if( !columnInfo.BIsInsertable() ) + { + if( bAddedBefore ) + psStatement->Append( ", INSERTED." ); + else + psStatement->Append( ") OUTPUT INSERTED." ); + bAddedBefore = true ; + psStatement->Append( columnInfo.GetName() ); + pvecOutputFields->AddToTail( iColumn ); + nOutputColumn++; + } + } + + // add field values to SQL statement + psStatement->AppendFormat( " VALUES (%.*s)", GetInsertArgStringChars( nInsertable ), GetInsertArgString() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Builds a SQL MERGE statement update or insert using in-flight values table +// Input: psStatement - The string to put the statement into +// pRecordInfo - record info describing table inserting into +//----------------------------------------------------------------------------- +void BuildMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo ) +{ + psStatement->sprintf( "MERGE INTO %s.%s WITH( HOLDLOCK, ROWLOCK ) T USING ( VALUES (%.*s) ) AS S(", + GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName(), + GetInsertArgStringChars( pRecordInfo->GetNumColumns() ), GetInsertArgString() ); + + { + int cColumns = pRecordInfo->GetNumColumns(); + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ); + if ( iColumn ) + psStatement->Append( ',' ); + psStatement->Append( columnInfo.GetName() ); + } + } + + psStatement->Append( ") ON " ); + + // build a string of the PK columns + const FieldSet_t &fsPK = pRecordInfo->GetIndexFields()[pRecordInfo->GetPKIndex()]; + { + int cColumns = fsPK.GetCount(); + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( fsPK.GetField( iColumn ) ); + if ( iColumn ) + psStatement->Append( " AND " ); + psStatement->Append( "T." ); + psStatement->Append( columnInfo.GetName() ); + psStatement->Append( "=S." ); + psStatement->Append( columnInfo.GetName() ); + } + } + + psStatement->Append( " WHEN MATCHED THEN UPDATE SET " ); + + // build the update string + { + int cColumns = pRecordInfo->GetNumColumns(); + bool bAddedBefore = false; + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + bool bThisColumnIsPartOfPK = false; + for ( int ipkCheck = 0; ipkCheck < fsPK.GetCount(); ++ipkCheck ) + { + if ( iColumn == fsPK.GetField( ipkCheck ) ) + { + bThisColumnIsPartOfPK = true; + break; + } + } + if ( bThisColumnIsPartOfPK ) + continue; + + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ); + if ( bAddedBefore ) + psStatement->Append( ',' ); + bAddedBefore = true; + psStatement->Append( columnInfo.GetName() ); + psStatement->Append( "=S." ); + psStatement->Append( columnInfo.GetName() ); + } + } + + psStatement->Append( " WHEN NOT MATCHED BY TARGET THEN INSERT (" ); + + // build a string of the field names + { + int cColumns = pRecordInfo->GetNumColumns(); + bool bAddedBefore = false; + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ); + if ( !columnInfo.BIsInsertable() ) + continue; + + if ( bAddedBefore ) + psStatement->Append( ',' ); + bAddedBefore = true; + psStatement->Append( columnInfo.GetName() ); + } + } + + psStatement->Append( ") VALUES (" ); + { + int cColumns = pRecordInfo->GetNumColumns(); + bool bAddedBefore = false; + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ); + if ( !columnInfo.BIsInsertable() ) + continue; + + if ( bAddedBefore ) + psStatement->Append( ',' ); + bAddedBefore = true; + psStatement->Append( "S." ); + psStatement->Append( columnInfo.GetName() ); + } + } + psStatement->Append( ");" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Builds a SQL MERGE statement using CTE_MergeParams as supplied table holding rows +// Input: psStatement - The string to put the statement into +// pRecordInfo - record info describing table inserting into +//----------------------------------------------------------------------------- +void BuildMergeStatementTextOnPKWhenNotMatchedInsert( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo ) +{ + psStatement->sprintf( "MERGE INTO %s.%s WITH( HOLDLOCK, ROWLOCK ) T USING ( VALUES (%.*s) ) AS S(", + GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName(), + GetInsertArgStringChars( pRecordInfo->GetNumColumns() ), GetInsertArgString() ); + + { + int cColumns = pRecordInfo->GetNumColumns(); + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ); + if ( iColumn ) + psStatement->Append( ',' ); + psStatement->Append( columnInfo.GetName() ); + } + } + + psStatement->Append( ") ON " ); + + // build a string of the PK columns + const FieldSet_t &fsPK = pRecordInfo->GetIndexFields()[pRecordInfo->GetPKIndex()]; + { + int cColumns = fsPK.GetCount(); + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( fsPK.GetField( iColumn ) ); + if ( iColumn ) + psStatement->Append( " AND " ); + psStatement->Append( "T." ); + psStatement->Append( columnInfo.GetName() ); + psStatement->Append( "=S." ); + psStatement->Append( columnInfo.GetName() ); + } + } + + psStatement->Append( " WHEN NOT MATCHED BY TARGET THEN INSERT (" ); + + // build a string of the field names + { + int cColumns = pRecordInfo->GetNumColumns(); + bool bAddedBefore = false; + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ); + if ( !columnInfo.BIsInsertable() ) + continue; + + if ( bAddedBefore ) + psStatement->Append( ',' ); + bAddedBefore = true; + psStatement->Append( columnInfo.GetName() ); + } + } + + psStatement->Append( ") VALUES (" ); + { + int cColumns = pRecordInfo->GetNumColumns(); + bool bAddedBefore = false; + for ( int iColumn = 0; iColumn < cColumns; iColumn++ ) + { + const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ); + if ( !columnInfo.BIsInsertable() ) + continue; + + if ( bAddedBefore ) + psStatement->Append( ',' ); + bAddedBefore = true; + psStatement->Append( "S." ); + psStatement->Append( columnInfo.GetName() ); + } + } + psStatement->Append( ");" ); +} + +void BuildSelectStatementText( TSQLCmdStr *psStatement, const CColumnSet & selectSet, const char *pchTopClause ) +{ + *psStatement = "SELECT "; + + if( pchTopClause ) + { + psStatement->Append( pchTopClause ); + psStatement->Append( ' ' ); + } + + // build a string of the field names + bool bAddedBefore = false; + FOR_EACH_COLUMN_IN_SET( selectSet, nColumnIndex ) + { + const CColumnInfo &columnInfo = selectSet.GetColumnInfo( nColumnIndex ); + if ( bAddedBefore ) + psStatement->Append( ',' ); + bAddedBefore = true; + psStatement->Append( columnInfo.GetName() ); + } + + psStatement->Append( " FROM "); + psStatement->Append( GSchemaFull().GetDefaultSchemaNameForCatalog( selectSet.GetRecordInfo()->GetESchemaCatalog() ) ); + psStatement->Append( '.' ); + psStatement->Append( selectSet.GetRecordInfo()->GetName() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Builds a SQL UPDATE statement +// Input: pRecordInfo - record info describing table inserting into +// bForPreparedStatement - if true, inserts values as '?' for later +// binding. If false, values are inserted in text. +// pchStatement - pointer to buffer to build statement in +// cchStatement - size of buffer +// pSQLRecord - pointer to record with data to update +// iColumnMatch - column to use for WHERE condition +// pvMatch - data value to use for WHERE condition +// cubMatch - size of pvMatch data +// rgiColumnUpdate - array of column #'s to update +// ciColumnUpdate - count of column #'s to update +//----------------------------------------------------------------------------- +void BuildUpdateStatementText( TSQLCmdStr *psStatement, const CColumnSet & updateColumns ) +{ + // build the UPDATE statement + psStatement->sprintf( "UPDATE %s.%s SET ", GSchemaFull().GetDefaultSchemaNameForCatalog( updateColumns.GetRecordInfo()->GetESchemaCatalog() ), updateColumns.GetRecordInfo()->GetName() ); + + // add each field we're updating to the UPDATE statement + FOR_EACH_COLUMN_IN_SET( updateColumns, nColumnIndex ) + { + const CColumnInfo &columnInfo = updateColumns.GetColumnInfo( nColumnIndex ); + + if( nColumnIndex > 0 ) + psStatement->Append( ',' ); + psStatement->Append( columnInfo.GetName() ); + psStatement->Append( "=?" ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Builds a SQL UPDATE statement +//----------------------------------------------------------------------------- +void BuildDeleteStatementText( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo ) +{ + psStatement->sprintf( "DELETE FROM %s.%s", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Builds a where clause for the provided fields +//----------------------------------------------------------------------------- +void AppendWhereClauseText( TSQLCmdStr *psClause, const CColumnSet & columnSet ) +{ + // add each field we're updating to the UPDATE statement + FOR_EACH_COLUMN_IN_SET( columnSet, nColumnIndex ) + { + const CColumnInfo &columnInfo = columnSet.GetColumnInfo( nColumnIndex ); + + if( nColumnIndex > 0 ) + psClause->Append( " AND "); + psClause->Append( columnInfo.GetName() ); + psClause->Append( "=?" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Builds an OUTPUT [fields] INTO [table] for the provided fields/data +//----------------------------------------------------------------------------- +void BuildOutputClauseText( TSQLCmdStr *psClause, const CColumnSet & columnSet ) +{ + *psClause = " OUTPUT "; + + FOR_EACH_COLUMN_IN_SET( columnSet, nColumnIndex ) + { + const CColumnInfo &columnInfo = columnSet.GetColumnInfo( nColumnIndex ); + + if( nColumnIndex > 0 ) + psClause->Append( ", "); + + psClause->Append( " ? AS " ); + psClause->Append( columnInfo.GetName() ); + } + + psClause->Append( " INTO " ); + psClause->Append( columnSet.GetRecordInfo()->GetName() ); +} + +////----------------------------------------------------------------------------- +//// Purpose: our own special "upsert" into a column with a uniqueness constraint +////----------------------------------------------------------------------------- +//EResult UpdateOrInsertUnique( CSQLAccess &sqlAccess, int iTable, int iField, CRecordBase *pRecordBase, int iIndexID ) +//{ +// // attempt an update - if it fails due to duplicate primary key, they can't use this +// // url (it's taken) - if it succeeds but affects 0 rows, they didn't have a vanity url +// // and we need to do an insert (which could again fail due to primary key constraints) +// int cRecordsUpdated = 0; +// bool bRet = sqlAccess.BYieldingUpdateFieldFromRecordWithIndex( iTable, &cRecordsUpdated, iField, pRecordBase, iIndexID ); +// if ( !bRet ) +// { +// // ODBC is the suck - give me Spring JDBC templates, please. +// if ( sqlAccess.GetLastError()->IsDuplicateInsertAttempt() ) +// { +// return k_EResultDuplicateName; +// } +// return k_EResultFail; +// } +// else if ( 0 == cRecordsUpdated ) +// { +// // the user didn't have an entry, so insert one. +// bRet = sqlAccess.BYieldingInsertRecord( iTable, pRecordBase ); +// if ( !bRet ) +// { +// // ODBC is the suck - give me Spring JDBC templates, please. +// if ( sqlAccess.GetLastError()->IsDuplicateInsertAttempt() ) +// { +// return k_EResultDuplicateName; +// } +// return k_EResultFail; +// } +// } +// return k_EResultOK; +//} +// + +} // namespace GCSDK |