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
|