Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "stdafx.h"
  8. // memdbgon must be the last include file in a .cpp file!!!
  9. #include "tier0/memdbgon.h"
  10. namespace GCSDK
  11. {
  12. const char *GetInsertArgString()
  13. {
  14. static char s_str[1024];
  15. static bool s_bInit = false;
  16. if ( !s_bInit )
  17. {
  18. for ( int i = 0; i < 1023; i++ )
  19. {
  20. s_str[i] = i % 2 == 0 ? '?' : ',';
  21. }
  22. s_str[1023] = NULL;
  23. s_bInit = true;
  24. }
  25. return s_str;
  26. }
  27. uint32 GetInsertArgStringChars( uint32 nNumParams )
  28. {
  29. AssertMsg( nNumParams <= GetInsertArgStringMaxParams(), "Error: Requested more characters than are provided by the GetInsertArgString" );
  30. if( nNumParams == 0 )
  31. return 0;
  32. return nNumParams * 2 - 1;
  33. }
  34. uint32 GetInsertArgStringMaxParams()
  35. {
  36. return 512;
  37. }
  38. //-----------------------------------------------------------------------------
  39. // Purpose: Converts array of field data to text for SQL IN clause
  40. // Input: columnInfo - schema of column being converted
  41. // pubData - pointer to array of data to convert
  42. // cubData - size of array of data
  43. // rgchResult - pointer to output buffer
  44. // cubResultLen - size of output buffer
  45. // bForPreparedStatement - Should we prepare the text for a prepared statement or directly place the values?
  46. //-----------------------------------------------------------------------------
  47. void ConvertFieldArrayToInText( const CColumnInfo &columnInfo, byte *pubData, int cubData, char *rgchResult, int cubResultLen, bool bForPreparedStatement )
  48. {
  49. int32 cubLength = columnInfo.GetFixedSize();
  50. Assert( cubData % cubLength == 0 );
  51. int32 nArrayCount = cubData / cubLength;
  52. int32 len = 0;
  53. rgchResult[len++] = '(';
  54. for( int i = 0; i < nArrayCount; ++i )
  55. {
  56. if ( bForPreparedStatement )
  57. {
  58. if ( i < nArrayCount - 1 )
  59. {
  60. rgchResult[len++] = '?';
  61. rgchResult[len++] = ',';
  62. }
  63. else
  64. {
  65. rgchResult[len++] = '?';
  66. rgchResult[len++] = ')';
  67. }
  68. }
  69. else
  70. {
  71. switch ( columnInfo.GetType() )
  72. {
  73. case k_EGCSQLType_int8:
  74. if ( i < nArrayCount - 1 )
  75. len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (byte *) pubData ) );
  76. else
  77. len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (byte *) pubData ) );
  78. break;
  79. case k_EGCSQLType_int16:
  80. if ( i < nArrayCount - 1 )
  81. len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (short *) pubData ) );
  82. else
  83. len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (short *) pubData ) );
  84. break;
  85. case k_EGCSQLType_int32:
  86. if ( i < nArrayCount - 1 )
  87. len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d,", *( (int *) pubData ) );
  88. else
  89. len += Q_snprintf( rgchResult + len, cubResultLen - len, "%d)", *( (int *) pubData ) );
  90. break;
  91. case k_EGCSQLType_int64:
  92. if ( i < nArrayCount - 1 )
  93. len += Q_snprintf( rgchResult + len, cubResultLen - len, "%lld,", *( (int64 *) pubData ) );
  94. else
  95. len += Q_snprintf( rgchResult + len, cubResultLen - len, "%lld)", *( (int64 *) pubData ) );
  96. break;
  97. default:
  98. AssertMsg( false, "Unsupported data type for non prepares statement with IN clause\n" );
  99. rgchResult[0] = 0;
  100. return;
  101. }
  102. }
  103. if( len >= cubResultLen - 1 )
  104. {
  105. AssertMsg( false, "Generation of IN clause foverflowed\n" );
  106. rgchResult[0] = 0;
  107. return;
  108. }
  109. pubData += cubLength;
  110. }
  111. rgchResult[len] = 0;
  112. return;
  113. }
  114. //-----------------------------------------------------------------------------
  115. // Purpose: Converts field data to text equivalent for SQL statement
  116. // Input: eFieldType - The type of the field to convert to text
  117. // pubRecord - pointer to record data to convert
  118. // cubRecord - size of record data
  119. // rgchField - pointer to output buffer
  120. // cchField - size of output buffer
  121. //-----------------------------------------------------------------------------
  122. void ConvertFieldToText( EGCSQLType eFieldType, uint8 *pubRecord, int cubRecord, char *rgchField, int cchField, bool bQuoteString )
  123. {
  124. char rgchTmp[k_cMedBuff];
  125. switch ( eFieldType )
  126. {
  127. case k_EGCSQLType_int8:
  128. Q_snprintf( rgchField, cchField, "%d", *( (byte *) pubRecord ) );
  129. break;
  130. case k_EGCSQLType_int16:
  131. Q_snprintf( rgchField, cchField, "%d", *( (short *) pubRecord ) );
  132. break;
  133. case k_EGCSQLType_int32:
  134. Q_snprintf( rgchField, cchField, "%d", *( (int *) pubRecord ) );
  135. break;
  136. case k_EGCSQLType_int64:
  137. Q_snprintf( rgchField, cchField, "%lld", *( (int64 *) pubRecord ) );
  138. break;
  139. case k_EGCSQLType_float:
  140. Q_snprintf( rgchField, cchField, "%f", *((float*) pubRecord) );
  141. break;
  142. case k_EGCSQLType_double:
  143. Q_snprintf( rgchField, cchField, "%f", *((double*) pubRecord) );
  144. break;
  145. case k_EGCSQLType_String:
  146. if ( pubRecord && *pubRecord )
  147. {
  148. Assert( cubRecord + 1 < Q_ARRAYSIZE( rgchTmp ) );
  149. Q_memcpy( rgchTmp, (char *) pubRecord, cubRecord );
  150. rgchTmp[cubRecord] = 0;
  151. if ( bQuoteString )
  152. {
  153. EscapeStringValue( rgchTmp, Q_ARRAYSIZE( rgchTmp ) );
  154. Q_snprintf( rgchField, cchField, "'%s'", rgchTmp );
  155. }
  156. else
  157. {
  158. Q_strncpy( rgchField, rgchTmp, cchField );
  159. }
  160. }
  161. else
  162. {
  163. if ( bQuoteString )
  164. {
  165. Q_strncpy( rgchField, "''", cchField );
  166. }
  167. else
  168. {
  169. Q_strncpy( rgchField, "", cchField );
  170. }
  171. }
  172. break;
  173. case k_EGCSQLType_Blob:
  174. case k_EGCSQLType_Image:
  175. Q_strncpy( rgchField, "0x", cchField );
  176. Q_binarytohex( pubRecord, cubRecord, rgchField + 2, cchField - 2 );
  177. break;
  178. default:
  179. Assert( false );
  180. break;
  181. }
  182. }
  183. //-----------------------------------------------------------------------------
  184. // Purpose: Returns the text SQL type for a given field
  185. // Input: field - field to determine type for
  186. // pchBuf - pointer to output buffer
  187. // cchBuf - size of output buffer
  188. // Output: returns pchBuf for convenience of one-line usage
  189. //-----------------------------------------------------------------------------
  190. char *SQLTypeFromField( const CColumnInfo &colInfo, char *pchBuf, int cchBuf )
  191. {
  192. EGCSQLType eType = colInfo.GetType();
  193. *pchBuf = 0;
  194. switch ( eType )
  195. {
  196. case k_EGCSQLType_int8:
  197. Q_strncpy( pchBuf, "TINYINT", cchBuf );
  198. break;
  199. case k_EGCSQLType_int16:
  200. Q_strncpy( pchBuf, "SMALLINT", cchBuf );
  201. break;
  202. case k_EGCSQLType_int32:
  203. Q_strncpy( pchBuf, "INT", cchBuf );
  204. break;
  205. case k_EGCSQLType_int64:
  206. Q_strncpy( pchBuf, "BIGINT", cchBuf );
  207. break;
  208. case k_EGCSQLType_float:
  209. Q_strncpy( pchBuf, "REAL", cchBuf );
  210. break;
  211. case k_EGCSQLType_double:
  212. Q_strncpy( pchBuf, "FLOAT", cchBuf );
  213. break;
  214. case k_EGCSQLType_String:
  215. Q_snprintf( pchBuf, cchBuf, "VARCHAR(%d)", colInfo.GetMaxSize() );
  216. break;
  217. case k_EGCSQLType_Blob:
  218. Q_snprintf( pchBuf, cchBuf, "VARBINARY(%d)", colInfo.GetMaxSize() );
  219. break;
  220. case k_EGCSQLType_Image:
  221. Q_strncpy( pchBuf, "IMAGE", cchBuf );
  222. break;
  223. default:
  224. Assert( false );
  225. break;
  226. }
  227. return pchBuf;
  228. }
  229. //-----------------------------------------------------------------------------
  230. // Purpose: Escapes any single quotes to a string value to double single quotes
  231. // Input: rgchField - text to escape
  232. // cchField - size of text buffer
  233. // Notes: The text will be escaped and expanded in place in the buffer.
  234. // In the worst case, the text may expand by 2x. (If the field is all
  235. // single quotes.) So, you must pass in a buffer which is at least
  236. // twice as long as the text length so we can guarantee to be able to
  237. // escape the string.
  238. //-----------------------------------------------------------------------------
  239. void EscapeStringValue( char *rgchField, int cchField )
  240. {
  241. // TODO - what else do we need to escape? %() ...
  242. char *pubCur = rgchField;
  243. int nLen = 0;
  244. int cSingleQuotes = 0;
  245. // This function gets called on every text field we write but most text fields
  246. // don't need to be escaped, so try to be as fast as possible in the normal case.
  247. // first, walk through the string and count the string length and number of single quotes
  248. while ( *pubCur )
  249. {
  250. if ( '\'' == *pubCur )
  251. cSingleQuotes++;
  252. nLen ++;
  253. pubCur++;
  254. }
  255. // if no single quotes, nothing to do
  256. if ( !cSingleQuotes )
  257. return;
  258. // caller must pass in a buffer that's long enough for expansion
  259. Assert( nLen + cSingleQuotes + 1 <= cchField );
  260. if ( !( nLen + cSingleQuotes + 1 <= cchField ) )
  261. return;
  262. // We know exactly how many characters the string will expand by (the # of single quotes). Walk backward
  263. // and copy the characters into the right places. This touches each character only once.
  264. pubCur = rgchField + nLen + cSingleQuotes;
  265. *pubCur = 0;
  266. pubCur--;
  267. while ( pubCur > rgchField && cSingleQuotes > 0 )
  268. {
  269. // read pointer is offset from write pointer by # of remaining single quotes
  270. char *pubRead = pubCur - cSingleQuotes;
  271. Assert( pubRead >= rgchField );
  272. // copy each character
  273. *pubCur = *pubRead;
  274. if ( '\'' == *pubRead )
  275. {
  276. // if the character is a single quote, back up one more and insert another single quote to escape it
  277. pubCur --;
  278. *pubCur = '\'';
  279. // decrement # of single quotes remaining
  280. cSingleQuotes --;
  281. Assert( cSingleQuotes >= 0 );
  282. }
  283. pubCur--;
  284. }
  285. }
  286. //-----------------------------------------------------------------------------
  287. // Purpose: Adds constraint information to a SQL command to add or remove constraint
  288. // Input: pchTableName - name of table
  289. // pchColumnName - name of column
  290. // nColFlagConstraint - flag with which constraint to
  291. // bForAdd - whether constraint is being added or removed
  292. // pchCmd - buffer to append SQL command to
  293. // cchCmd - size of buffer
  294. //-----------------------------------------------------------------------------
  295. void AppendConstraint( const char *pchTableName, const char *pchColumnName, int nColFlagConstraint, bool bForAdd,
  296. bool bClustered, CFmtStrMax & sCmd, int nFillFactor )
  297. {
  298. Assert( pchTableName && pchTableName[0] );
  299. Assert( pchColumnName && pchColumnName[0] );
  300. switch ( nColFlagConstraint )
  301. {
  302. case k_nColFlagPrimaryKey:
  303. sCmd.AppendFormat( " CONSTRAINT %s_%s_PrimaryKey", pchTableName, pchColumnName);
  304. if ( bForAdd )
  305. {
  306. sCmd += " PRIMARY KEY ";
  307. if ( bClustered )
  308. {
  309. sCmd.AppendFormat( " CLUSTERED WITH (FILLFACTOR = %d) ", nFillFactor );
  310. }
  311. else
  312. {
  313. sCmd += "NONCLUSTERED";
  314. }
  315. }
  316. break;
  317. case k_nColFlagUnique:
  318. /* do nothing - the uniqueness will be handled by creation of an index */
  319. break;
  320. case k_nColFlagAutoIncrement:
  321. sCmd += " IDENTITY";
  322. break;
  323. default:
  324. AssertMsg( false, "CSQLThread::AppendContraint: invalid constraint type" );
  325. break;
  326. }
  327. }
  328. //-----------------------------------------------------------------------------
  329. // Purpose: Adds constraint information to a SQL command to add or remove constraint
  330. // Input: pRecordInfo - record info describing table
  331. // pColumnInfo - record info describing column
  332. // bForAdd - whether constraint is being added or removed
  333. // pchCmd - buffer to append SQL command to
  334. // cchCmd - size of buffer
  335. //-----------------------------------------------------------------------------
  336. void AppendConstraints( const CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo, bool bForAdd, CFmtStrMax & sCmd )
  337. {
  338. Assert( pRecordInfo != NULL );
  339. Assert( pColumnInfo != NULL );
  340. if ( pColumnInfo->BIsPrimaryKey() )
  341. {
  342. // any column in a PK can't be NULL.
  343. if ( bForAdd )
  344. {
  345. sCmd += " NOT NULL";
  346. }
  347. // only add primary key constraint here if it is a single-column PK
  348. if ( pRecordInfo->GetPrimaryKeyType() == k_EPrimaryKeyTypeSingle )
  349. {
  350. // get the fields on the primary key
  351. const CUtlVector< FieldSet_t > &refFields = pRecordInfo->GetIndexFields( );
  352. int nFillFactor = refFields.Element( pRecordInfo->GetPKIndex() ).GetFillFactor();
  353. AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagPrimaryKey, bForAdd, pColumnInfo->BIsClustered(), sCmd, nFillFactor );
  354. }
  355. }
  356. else if ( pColumnInfo->BIsUnique() )
  357. {
  358. AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagUnique, bForAdd, pColumnInfo->BIsClustered(), sCmd, 0 );
  359. }
  360. if ( pColumnInfo->BIsAutoIncrement() )
  361. {
  362. AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), k_nColFlagAutoIncrement, bForAdd, pColumnInfo->BIsClustered(), sCmd, 0 );
  363. }
  364. }
  365. //-----------------------------------------------------------------------------
  366. // Purpose: Generates the "CONSTRAINT ..." text for the table primary key
  367. //-----------------------------------------------------------------------------
  368. void BuildTablePKConstraintText( TSQLCmdStr *psStatement, CRecordInfo *pRecordInfo )
  369. {
  370. const FieldSet_t& vecFields = pRecordInfo->GetPKFields( );
  371. psStatement->sprintf( "CONSTRAINT %s_PrimaryKey PRIMARY KEY %s ( ",
  372. pRecordInfo->GetName(),
  373. vecFields.IsClustered() ? "CLUSTERED" : "NONCLUSTERED" );
  374. for ( int nField = 0; nField < vecFields.GetCount(); nField++ )
  375. {
  376. // what field is the next column in our index?
  377. int nThisField = vecFields.GetField( nField );
  378. const CColumnInfo& columnInfo = pRecordInfo->GetColumnInfo(nThisField);
  379. if (nField != 0)
  380. {
  381. *psStatement += ", ";
  382. }
  383. *psStatement += columnInfo.GetName();
  384. }
  385. // close our list
  386. *psStatement += ") ";
  387. if ( vecFields.GetFillFactor() != 0 )
  388. {
  389. // non-default fill factor, so specify it
  390. psStatement->AppendFormat( " WITH FILLFACTOR = %d ",
  391. vecFields.GetFillFactor() );
  392. }
  393. }
  394. //-----------------------------------------------------------------------------
  395. // Purpose: Adds constraint information to a SQL command to add or remove table-level constraints
  396. // Input: pRecordInfo - record info describing table
  397. // pchCmd - buffer to append SQL command to
  398. // cchCmd - size of buffer
  399. //-----------------------------------------------------------------------------
  400. void AppendTableConstraints( CRecordInfo *pRecordInfo, CFmtStrMax & sCmd )
  401. {
  402. // the only supported table constraint is for PKs or FKs
  403. if ( pRecordInfo->GetPrimaryKeyType() == k_EPrimaryKeyTypeMulti )
  404. {
  405. TSQLCmdStr tmp;
  406. BuildTablePKConstraintText( &tmp, pRecordInfo );
  407. sCmd += ", ";
  408. sCmd += tmp;
  409. }
  410. // Look for FKs required on this table
  411. // the only supported table constraint is for PKs or FKs
  412. int cFKs = pRecordInfo->GetFKCount();
  413. for( int i=0; i < cFKs; ++i )
  414. {
  415. FKData_t &fkData = pRecordInfo->GetFKData( i );
  416. CFmtStr sColumns, sParentColumns;
  417. FOR_EACH_VEC( fkData.m_VecColumnRelations, nCol )
  418. {
  419. FKColumnRelation_t &colRelation = fkData.m_VecColumnRelations[nCol];
  420. if ( nCol > 0)
  421. {
  422. sColumns += ",";
  423. sParentColumns += ",";
  424. }
  425. sColumns += colRelation.m_rgchCol;
  426. sParentColumns += colRelation.m_rgchParentCol;
  427. }
  428. TSQLCmdStr sTmp;
  429. sTmp.sprintf( ", CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s) ON DELETE %s ON UPDATE %s",
  430. fkData.m_rgchName, sColumns.Access(), fkData.m_rgchParentTableName, sParentColumns.Access(),
  431. PchNameFromEForeignKeyAction( fkData.m_eOnDeleteAction ), PchNameFromEForeignKeyAction( fkData.m_eOnUpdateAction ) );
  432. // add to the command
  433. sCmd += sTmp;
  434. }
  435. }
  436. //-----------------------------------------------------------------------------
  437. // Purpose: Builds a SQL INSERT statement
  438. // Input: psStatement - The string to put the statement into
  439. // pRecordInfo - record info describing table inserting into
  440. //-----------------------------------------------------------------------------
  441. void BuildInsertStatementText( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
  442. {
  443. psStatement->sprintf("INSERT INTO %s.%s (", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() );
  444. // build a string of the field names
  445. int cColumns = pRecordInfo->GetNumColumns();
  446. int nInsertable = 0;
  447. bool bAddedBefore = false;
  448. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  449. {
  450. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
  451. if ( !columnInfo.BIsInsertable() )
  452. continue;
  453. nInsertable++;
  454. if ( bAddedBefore )
  455. psStatement->Append( ',' );
  456. bAddedBefore = true;
  457. psStatement->Append( columnInfo.GetName() );
  458. }
  459. psStatement->AppendFormat( ") VALUES (%.*s)", GetInsertArgStringChars( nInsertable ), GetInsertArgString() );
  460. }
  461. //-----------------------------------------------------------------------------
  462. // Purpose: Builds a SQL INSERT statement
  463. // IMPORTANT NOTE - This Insert statement will use the Microsoft SQL Server
  464. // specific clause 'OUTPUT Inserted.ColumnName'
  465. // The result of that will be that the SQL statement will return to us
  466. // the columns that could not be specified by the Insert.
  467. // At the time of writing, that is primarily AutoIncrement columns,
  468. // however in theory we should be able to recover any computed column
  469. // from SQL server, with the caveats specified at :
  470. // http://msdn.microsoft.com/en-us/library/ms177564.aspx
  471. //
  472. // Input: psStatement - The output statement string
  473. // pRecordInfo - record info describing table inserting into
  474. //-----------------------------------------------------------------------------
  475. void BuildInsertAndReadStatementText( TSQLCmdStr *psStatement, CUtlVector<int> *pvecOutputFields, const CRecordInfo *pRecordInfo )
  476. {
  477. psStatement->sprintf("INSERT INTO %s.%s (", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() );
  478. // build a string of the field names
  479. int nInsertable = 0;
  480. int cColumns = pRecordInfo->GetNumColumns();
  481. bool bAddedBefore = false;
  482. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  483. {
  484. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
  485. if ( !columnInfo.BIsInsertable() )
  486. continue;
  487. nInsertable++;
  488. if ( bAddedBefore )
  489. psStatement->Append( ',' );
  490. bAddedBefore = true;
  491. psStatement->Append( columnInfo.GetName() );
  492. }
  493. bAddedBefore = false ;
  494. int nOutputColumn = 0;
  495. for( int iColumn = 0; iColumn < cColumns; iColumn++ )
  496. {
  497. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn ) ;
  498. //
  499. // If we can't Insert it - we want SQL Server to tell us what value was stored
  500. // in the column !!
  501. //
  502. if( !columnInfo.BIsInsertable() )
  503. {
  504. if( bAddedBefore )
  505. psStatement->Append( ", INSERTED." );
  506. else
  507. psStatement->Append( ") OUTPUT INSERTED." );
  508. bAddedBefore = true ;
  509. psStatement->Append( columnInfo.GetName() );
  510. pvecOutputFields->AddToTail( iColumn );
  511. nOutputColumn++;
  512. }
  513. }
  514. // add field values to SQL statement
  515. psStatement->AppendFormat( " VALUES (%.*s)", GetInsertArgStringChars( nInsertable ), GetInsertArgString() );
  516. }
  517. //-----------------------------------------------------------------------------
  518. // Purpose: Builds a SQL MERGE statement update or insert using in-flight values table
  519. // Input: psStatement - The string to put the statement into
  520. // pRecordInfo - record info describing table inserting into
  521. //-----------------------------------------------------------------------------
  522. void BuildMergeStatementTextOnPKWhenMatchedUpdateWhenNotMatchedInsert( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
  523. {
  524. psStatement->sprintf( "MERGE INTO %s.%s WITH( HOLDLOCK, ROWLOCK ) T USING ( VALUES (%.*s) ) AS S(",
  525. GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName(),
  526. GetInsertArgStringChars( pRecordInfo->GetNumColumns() ), GetInsertArgString() );
  527. {
  528. int cColumns = pRecordInfo->GetNumColumns();
  529. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  530. {
  531. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
  532. if ( iColumn )
  533. psStatement->Append( ',' );
  534. psStatement->Append( columnInfo.GetName() );
  535. }
  536. }
  537. psStatement->Append( ") ON " );
  538. // build a string of the PK columns
  539. const FieldSet_t &fsPK = pRecordInfo->GetIndexFields()[pRecordInfo->GetPKIndex()];
  540. {
  541. int cColumns = fsPK.GetCount();
  542. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  543. {
  544. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( fsPK.GetField( iColumn ) );
  545. if ( iColumn )
  546. psStatement->Append( " AND " );
  547. psStatement->Append( "T." );
  548. psStatement->Append( columnInfo.GetName() );
  549. psStatement->Append( "=S." );
  550. psStatement->Append( columnInfo.GetName() );
  551. }
  552. }
  553. psStatement->Append( " WHEN MATCHED THEN UPDATE SET " );
  554. // build the update string
  555. {
  556. int cColumns = pRecordInfo->GetNumColumns();
  557. bool bAddedBefore = false;
  558. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  559. {
  560. bool bThisColumnIsPartOfPK = false;
  561. for ( int ipkCheck = 0; ipkCheck < fsPK.GetCount(); ++ipkCheck )
  562. {
  563. if ( iColumn == fsPK.GetField( ipkCheck ) )
  564. {
  565. bThisColumnIsPartOfPK = true;
  566. break;
  567. }
  568. }
  569. if ( bThisColumnIsPartOfPK )
  570. continue;
  571. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
  572. if ( bAddedBefore )
  573. psStatement->Append( ',' );
  574. bAddedBefore = true;
  575. psStatement->Append( columnInfo.GetName() );
  576. psStatement->Append( "=S." );
  577. psStatement->Append( columnInfo.GetName() );
  578. }
  579. }
  580. psStatement->Append( " WHEN NOT MATCHED BY TARGET THEN INSERT (" );
  581. // build a string of the field names
  582. {
  583. int cColumns = pRecordInfo->GetNumColumns();
  584. bool bAddedBefore = false;
  585. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  586. {
  587. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
  588. if ( !columnInfo.BIsInsertable() )
  589. continue;
  590. if ( bAddedBefore )
  591. psStatement->Append( ',' );
  592. bAddedBefore = true;
  593. psStatement->Append( columnInfo.GetName() );
  594. }
  595. }
  596. psStatement->Append( ") VALUES (" );
  597. {
  598. int cColumns = pRecordInfo->GetNumColumns();
  599. bool bAddedBefore = false;
  600. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  601. {
  602. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
  603. if ( !columnInfo.BIsInsertable() )
  604. continue;
  605. if ( bAddedBefore )
  606. psStatement->Append( ',' );
  607. bAddedBefore = true;
  608. psStatement->Append( "S." );
  609. psStatement->Append( columnInfo.GetName() );
  610. }
  611. }
  612. psStatement->Append( ");" );
  613. }
  614. //-----------------------------------------------------------------------------
  615. // Purpose: Builds a SQL MERGE statement using CTE_MergeParams as supplied table holding rows
  616. // Input: psStatement - The string to put the statement into
  617. // pRecordInfo - record info describing table inserting into
  618. //-----------------------------------------------------------------------------
  619. void BuildMergeStatementTextOnPKWhenNotMatchedInsert( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
  620. {
  621. psStatement->sprintf( "MERGE INTO %s.%s WITH( HOLDLOCK, ROWLOCK ) T USING ( VALUES (%.*s) ) AS S(",
  622. GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName(),
  623. GetInsertArgStringChars( pRecordInfo->GetNumColumns() ), GetInsertArgString() );
  624. {
  625. int cColumns = pRecordInfo->GetNumColumns();
  626. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  627. {
  628. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
  629. if ( iColumn )
  630. psStatement->Append( ',' );
  631. psStatement->Append( columnInfo.GetName() );
  632. }
  633. }
  634. psStatement->Append( ") ON " );
  635. // build a string of the PK columns
  636. const FieldSet_t &fsPK = pRecordInfo->GetIndexFields()[pRecordInfo->GetPKIndex()];
  637. {
  638. int cColumns = fsPK.GetCount();
  639. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  640. {
  641. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( fsPK.GetField( iColumn ) );
  642. if ( iColumn )
  643. psStatement->Append( " AND " );
  644. psStatement->Append( "T." );
  645. psStatement->Append( columnInfo.GetName() );
  646. psStatement->Append( "=S." );
  647. psStatement->Append( columnInfo.GetName() );
  648. }
  649. }
  650. psStatement->Append( " WHEN NOT MATCHED BY TARGET THEN INSERT (" );
  651. // build a string of the field names
  652. {
  653. int cColumns = pRecordInfo->GetNumColumns();
  654. bool bAddedBefore = false;
  655. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  656. {
  657. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
  658. if ( !columnInfo.BIsInsertable() )
  659. continue;
  660. if ( bAddedBefore )
  661. psStatement->Append( ',' );
  662. bAddedBefore = true;
  663. psStatement->Append( columnInfo.GetName() );
  664. }
  665. }
  666. psStatement->Append( ") VALUES (" );
  667. {
  668. int cColumns = pRecordInfo->GetNumColumns();
  669. bool bAddedBefore = false;
  670. for ( int iColumn = 0; iColumn < cColumns; iColumn++ )
  671. {
  672. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
  673. if ( !columnInfo.BIsInsertable() )
  674. continue;
  675. if ( bAddedBefore )
  676. psStatement->Append( ',' );
  677. bAddedBefore = true;
  678. psStatement->Append( "S." );
  679. psStatement->Append( columnInfo.GetName() );
  680. }
  681. }
  682. psStatement->Append( ");" );
  683. }
  684. void BuildSelectStatementText( TSQLCmdStr *psStatement, const CColumnSet & selectSet, const char *pchTopClause )
  685. {
  686. *psStatement = "SELECT ";
  687. if( pchTopClause )
  688. {
  689. psStatement->Append( pchTopClause );
  690. psStatement->Append( ' ' );
  691. }
  692. // build a string of the field names
  693. bool bAddedBefore = false;
  694. FOR_EACH_COLUMN_IN_SET( selectSet, nColumnIndex )
  695. {
  696. const CColumnInfo &columnInfo = selectSet.GetColumnInfo( nColumnIndex );
  697. if ( bAddedBefore )
  698. psStatement->Append( ',' );
  699. bAddedBefore = true;
  700. psStatement->Append( columnInfo.GetName() );
  701. }
  702. psStatement->Append( " FROM ");
  703. psStatement->Append( GSchemaFull().GetDefaultSchemaNameForCatalog( selectSet.GetRecordInfo()->GetESchemaCatalog() ) );
  704. psStatement->Append( '.' );
  705. psStatement->Append( selectSet.GetRecordInfo()->GetName() );
  706. }
  707. //-----------------------------------------------------------------------------
  708. // Purpose: Builds a SQL UPDATE statement
  709. // Input: pRecordInfo - record info describing table inserting into
  710. // bForPreparedStatement - if true, inserts values as '?' for later
  711. // binding. If false, values are inserted in text.
  712. // pchStatement - pointer to buffer to build statement in
  713. // cchStatement - size of buffer
  714. // pSQLRecord - pointer to record with data to update
  715. // iColumnMatch - column to use for WHERE condition
  716. // pvMatch - data value to use for WHERE condition
  717. // cubMatch - size of pvMatch data
  718. // rgiColumnUpdate - array of column #'s to update
  719. // ciColumnUpdate - count of column #'s to update
  720. //-----------------------------------------------------------------------------
  721. void BuildUpdateStatementText( TSQLCmdStr *psStatement, const CColumnSet & updateColumns )
  722. {
  723. // build the UPDATE statement
  724. psStatement->sprintf( "UPDATE %s.%s SET ", GSchemaFull().GetDefaultSchemaNameForCatalog( updateColumns.GetRecordInfo()->GetESchemaCatalog() ), updateColumns.GetRecordInfo()->GetName() );
  725. // add each field we're updating to the UPDATE statement
  726. FOR_EACH_COLUMN_IN_SET( updateColumns, nColumnIndex )
  727. {
  728. const CColumnInfo &columnInfo = updateColumns.GetColumnInfo( nColumnIndex );
  729. if( nColumnIndex > 0 )
  730. psStatement->Append( ',' );
  731. psStatement->Append( columnInfo.GetName() );
  732. psStatement->Append( "=?" );
  733. }
  734. }
  735. //-----------------------------------------------------------------------------
  736. // Purpose: Builds a SQL UPDATE statement
  737. //-----------------------------------------------------------------------------
  738. void BuildDeleteStatementText( TSQLCmdStr *psStatement, const CRecordInfo *pRecordInfo )
  739. {
  740. psStatement->sprintf( "DELETE FROM %s.%s", GSchemaFull().GetDefaultSchemaNameForCatalog( pRecordInfo->GetESchemaCatalog() ), pRecordInfo->GetName() );
  741. }
  742. //-----------------------------------------------------------------------------
  743. // Purpose: Builds a where clause for the provided fields
  744. //-----------------------------------------------------------------------------
  745. void AppendWhereClauseText( TSQLCmdStr *psClause, const CColumnSet & columnSet )
  746. {
  747. // add each field we're updating to the UPDATE statement
  748. FOR_EACH_COLUMN_IN_SET( columnSet, nColumnIndex )
  749. {
  750. const CColumnInfo &columnInfo = columnSet.GetColumnInfo( nColumnIndex );
  751. if( nColumnIndex > 0 )
  752. psClause->Append( " AND ");
  753. psClause->Append( columnInfo.GetName() );
  754. psClause->Append( "=?" );
  755. }
  756. }
  757. //-----------------------------------------------------------------------------
  758. // Purpose: Builds an OUTPUT [fields] INTO [table] for the provided fields/data
  759. //-----------------------------------------------------------------------------
  760. void BuildOutputClauseText( TSQLCmdStr *psClause, const CColumnSet & columnSet )
  761. {
  762. *psClause = " OUTPUT ";
  763. FOR_EACH_COLUMN_IN_SET( columnSet, nColumnIndex )
  764. {
  765. const CColumnInfo &columnInfo = columnSet.GetColumnInfo( nColumnIndex );
  766. if( nColumnIndex > 0 )
  767. psClause->Append( ", ");
  768. psClause->Append( " ? AS " );
  769. psClause->Append( columnInfo.GetName() );
  770. }
  771. psClause->Append( " INTO " );
  772. psClause->Append( columnSet.GetRecordInfo()->GetName() );
  773. }
  774. ////-----------------------------------------------------------------------------
  775. //// Purpose: our own special "upsert" into a column with a uniqueness constraint
  776. ////-----------------------------------------------------------------------------
  777. //EResult UpdateOrInsertUnique( CSQLAccess &sqlAccess, int iTable, int iField, CRecordBase *pRecordBase, int iIndexID )
  778. //{
  779. // // attempt an update - if it fails due to duplicate primary key, they can't use this
  780. // // url (it's taken) - if it succeeds but affects 0 rows, they didn't have a vanity url
  781. // // and we need to do an insert (which could again fail due to primary key constraints)
  782. // int cRecordsUpdated = 0;
  783. // bool bRet = sqlAccess.BYieldingUpdateFieldFromRecordWithIndex( iTable, &cRecordsUpdated, iField, pRecordBase, iIndexID );
  784. // if ( !bRet )
  785. // {
  786. // // ODBC is the suck - give me Spring JDBC templates, please.
  787. // if ( sqlAccess.GetLastError()->IsDuplicateInsertAttempt() )
  788. // {
  789. // return k_EResultDuplicateName;
  790. // }
  791. // return k_EResultFail;
  792. // }
  793. // else if ( 0 == cRecordsUpdated )
  794. // {
  795. // // the user didn't have an entry, so insert one.
  796. // bRet = sqlAccess.BYieldingInsertRecord( iTable, pRecordBase );
  797. // if ( !bRet )
  798. // {
  799. // // ODBC is the suck - give me Spring JDBC templates, please.
  800. // if ( sqlAccess.GetLastError()->IsDuplicateInsertAttempt() )
  801. // {
  802. // return k_EResultDuplicateName;
  803. // }
  804. // return k_EResultFail;
  805. // }
  806. // }
  807. // return k_EResultOK;
  808. //}
  809. //
  810. } // namespace GCSDK