You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
918 lines
29 KiB
918 lines
29 KiB
//========= 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
|
|
|