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.

1696 lines
64 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Contains the job that's responsible for updating the database schema
  4. //
  5. //=============================================================================
  6. #include "stdafx.h"
  7. #include "gcsdk/sqlaccess/schemaupdate.h"
  8. // memdbgon must be the last include file in a .cpp file!!!
  9. #include "tier0/memdbgon.h"
  10. namespace GCSDK
  11. {
  12. #ifndef SQL_SUCCESS
  13. #define SQL_SUCCESS 0
  14. #define SQL_SUCCESS_WITH_INFO 1
  15. #define SQL_NO_DATA 100
  16. #define SQL_ERROR (-1)
  17. #endif // SQLSUCCESS
  18. // this comes from sql.h. The GC really shouldn't depend on the MSSQL headers
  19. #define SQL_INDEX_CLUSTERED 1
  20. inline bool SQL_OK( SQLRETURN nRet )
  21. {
  22. return ( ( SQL_SUCCESS == nRet ) || (SQL_SUCCESS_WITH_INFO == nRet ) );
  23. }
  24. #define SQL_FAILED( ret ) ( !SQL_OK( ret ) )
  25. #define EXIT_ON_SQL_FAILURE( ret ) \
  26. { \
  27. if ( !SQL_OK( ret ) ) \
  28. { \
  29. goto Exit; \
  30. } \
  31. }
  32. #define EXIT_ON_BOOLSQL_FAILURE( ret ) \
  33. { \
  34. if ( !(ret) ) \
  35. { \
  36. nRet = SQL_ERROR; \
  37. goto Exit; \
  38. } \
  39. }
  40. #define RETURN_SQL_ERROR_ON_FALSE( ret ) \
  41. if( !(ret) ) \
  42. {\
  43. return SQL_ERROR;\
  44. }
  45. //-----------------------------------------------------------------------------
  46. // Purpose: Emits a message and appends it to a string
  47. //-----------------------------------------------------------------------------
  48. void EmitAndAppend( CFmtStr1024 & sTarget, const CGCEmitGroup& Group, int iLevel, int iLevelLog, PRINTF_FORMAT_STRING const char *pchMessage, ... )
  49. {
  50. va_list args;
  51. va_start( args, pchMessage );
  52. if( sTarget.Length() < 1024 )
  53. sTarget.AppendFormatV( pchMessage, args );
  54. EmitInfoV( Group, iLevel, iLevelLog, pchMessage, args );
  55. va_end( args );
  56. }
  57. //-----------------------------------------------------------------------------
  58. // builds a command of the form:
  59. // CREATE [CLUSTERED] [UNIQUE] INDEX <index_name> ON <table_name>
  60. // (col1, col2, ...)
  61. // [INCLUDE (icol1, icol2, ...)]
  62. // [WITH (FILLFACTOR = n)]
  63. //-----------------------------------------------------------------------------
  64. CUtlString GetAddIndexSQL( CRecordInfo *pRecordInfo, const FieldSet_t &refFields )
  65. {
  66. CFmtStrMax sCmd;
  67. sCmd.sprintf( "CREATE %s%sINDEX %s ON App%u.%s (",
  68. refFields.IsClustered() ? "CLUSTERED " : "",
  69. refFields.IsUnique() ? "UNIQUE " : "",
  70. refFields.GetIndexName(),
  71. GGCBase()->GetAppID(),
  72. pRecordInfo->GetName() );
  73. // add real columns
  74. for ( int n = 0; n < refFields.GetCount(); n++ )
  75. {
  76. int nField = refFields.GetField( n );
  77. const CColumnInfo &refInfo = pRecordInfo->GetColumnInfo( nField );
  78. sCmd.AppendFormat( "%s%s",
  79. (n > 0) ? "," : "",
  80. refInfo.GetName() );
  81. }
  82. sCmd += ")";
  83. // do we have any included columns?
  84. if ( refFields.GetIncludedCount() > 0 )
  85. {
  86. // yes, add those
  87. sCmd += "\nINCLUDE (";
  88. for ( int n = 0; n < refFields.GetIncludedCount(); n++ )
  89. {
  90. int nField = refFields.GetIncludedField( n );
  91. const CColumnInfo &refInfo = pRecordInfo->GetColumnInfo( nField );
  92. sCmd.AppendFormat( "%s%s",
  93. (n > 0) ? "," : "",
  94. refInfo.GetName() );
  95. }
  96. sCmd += ")";
  97. }
  98. // do we need a fill factor?
  99. if ( refFields.GetFillFactor() != 0)
  100. {
  101. sCmd.AppendFormat("\nWITH (FILLFACTOR = %d)",
  102. refFields.GetFillFactor() );
  103. }
  104. return CUtlString( sCmd.String() );
  105. }
  106. CUtlString GetAlterColumnText( CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfoDesired )
  107. {
  108. Assert( pRecordInfo );
  109. Assert( pColumnInfoDesired );
  110. char rgchTmp[128];
  111. CUtlString sCmd;
  112. sCmd.Format( "ALTER TABLE App%u.%s ALTER COLUMN %s %s %s", GGCBase()->GetAppID(), pRecordInfo->GetName(), pColumnInfoDesired->GetName(),
  113. SQLTypeFromField( *pColumnInfoDesired, rgchTmp, Q_ARRAYSIZE( rgchTmp ) ),
  114. pColumnInfoDesired->BIsPrimaryKey() ? "NOT NULL" : ""
  115. );
  116. return sCmd;
  117. }
  118. //-----------------------------------------------------------------------------
  119. // Purpose: Constructor
  120. //-----------------------------------------------------------------------------
  121. CSchemaUpdate::CSchemaUpdate()
  122. {
  123. m_mapPRecordInfoDesired.SetLessFunc( CaselessStringLessThan );
  124. m_eConversionMode = k_EConversionModeInspectOnly;
  125. m_bConversionNeeded = false;
  126. m_cTablesDesiredMissing = 0;
  127. m_cTablesActualDifferent = 0;
  128. m_cTablesActualUnknown = 0;
  129. m_cTablesNeedingChange = 0;
  130. m_cColumnsDesiredMissing = 0;
  131. m_cColumnsActualDifferent = 0;
  132. m_cColumnsActualUnknown = 0;
  133. m_bSkippedAChange = false;
  134. }
  135. //-----------------------------------------------------------------------------
  136. // Purpose: Destructor
  137. //-----------------------------------------------------------------------------
  138. CSchemaUpdate::~CSchemaUpdate()
  139. {
  140. // release all the record info's we're holding onto
  141. FOR_EACH_MAP_FAST( m_mapPRecordInfoDesired, iRecordInfo )
  142. {
  143. SAFE_RELEASE( m_mapPRecordInfoDesired[iRecordInfo] );
  144. }
  145. }
  146. //-----------------------------------------------------------------------------
  147. // Purpose: Adds a record info that describes a desired table that should
  148. // exist in the database
  149. // Input: pRecordInfo - pointer to record info
  150. //-----------------------------------------------------------------------------
  151. void CSchemaUpdate::AddRecordInfoDesired( CRecordInfo *pRecordInfo )
  152. {
  153. Assert( pRecordInfo );
  154. const char *pchName = pRecordInfo->GetName();
  155. Assert( pchName && pchName[0] );
  156. Assert( m_mapPRecordInfoDesired.InvalidIndex() == m_mapPRecordInfoDesired.Find( pchName ) );
  157. // addref the record info since we're hanging onto it
  158. pRecordInfo->AddRef();
  159. // insert it in our map, indexed by name
  160. m_mapPRecordInfoDesired.Insert( pchName, pRecordInfo );
  161. }
  162. //-----------------------------------------------------------------------------
  163. // Purpose: Adds a CFTSCatalogInfo that tells us about a FTS catalog that
  164. // should exist in the database
  165. //-----------------------------------------------------------------------------
  166. void CSchemaUpdate::AddFTSInfo( const CFTSCatalogInfo &refFTSInfo )
  167. {
  168. m_listFTSCatalogInfo.AddToTail( refFTSInfo );
  169. }
  170. //-----------------------------------------------------------------------------
  171. // Purpose: Adds a CFTSCatalogInfo that tells us about a FTS catalog that
  172. // should exist in the database
  173. //-----------------------------------------------------------------------------
  174. void CSchemaUpdate::AddTriggerInfos( const CUtlVector< CTriggerInfo > &refTriggerInfo )
  175. {
  176. m_vecTriggerInfo = refTriggerInfo;
  177. }
  178. //-----------------------------------------------------------------------------
  179. // Purpose: Validates and updates the database schema
  180. //-----------------------------------------------------------------------------
  181. bool CJobUpdateSchema::BYieldingRunJob( void * )
  182. {
  183. // update the main schema
  184. EmitInfo( SPEW_GC, 2, 2, "Updating main schema...\n" );
  185. if ( !BYieldingUpdateSchema( k_ESchemaCatalogMain ) )
  186. {
  187. m_pGC->SetStartupComplete( false );
  188. return false;
  189. }
  190. // Could fail, but we shouldn't stop from starting up if it does
  191. BYieldingUpdateSchema( k_ESchemaCatalogOGS );
  192. bool bSuccess = m_pGC->BYieldingFinishStartup();
  193. m_pGC->SetStartupComplete( bSuccess );
  194. if ( bSuccess )
  195. {
  196. bSuccess = m_pGC->BYieldingPostStartup();
  197. }
  198. return true;
  199. }
  200. //-----------------------------------------------------------------------------
  201. // Purpose:
  202. //-----------------------------------------------------------------------------
  203. bool CJobUpdateSchema::BYieldingUpdateSchema( ESchemaCatalog eSchemaCatalog )
  204. {
  205. if( !YieldingBuildTypeMap( eSchemaCatalog ) )
  206. return false;
  207. // make an object to communicate desired database schema & results
  208. CSchemaUpdate *pSchemaUpdate = new CSchemaUpdate();
  209. // do safe conversions only
  210. // TODO - do one round of inspection only first so we can send watchdog alert about what's about
  211. // to happen, then do conversions. Also force conversions in dev system.
  212. pSchemaUpdate->m_eConversionMode = k_EConversionModeConvertSafe;
  213. // Add all the tables to desired schema
  214. for ( int iTable = 0; iTable < m_iTableCount; iTable++ )
  215. {
  216. // MERGE COMMENT: "schema" cannot be used as a variable name because it is an empty #define resulting in error C2059
  217. CSchema &gc_schema = GSchemaFull().GetSchema( iTable );
  218. // is it in the schema we want?
  219. if ( gc_schema.GetESchemaCatalog() == eSchemaCatalog )
  220. pSchemaUpdate->AddRecordInfoDesired( gc_schema.GetRecordInfo() );
  221. }
  222. // add all the FTS catalogs to the desired schema
  223. for ( int n = 0; n < GSchemaFull().GetCFTSCatalogs(); n++ )
  224. {
  225. const CFTSCatalogInfo &refInfo = GSchemaFull().GetFTSCatalogInfo( n );
  226. pSchemaUpdate->AddFTSInfo( refInfo );
  227. }
  228. pSchemaUpdate->AddTriggerInfos( GSchemaFull().GetTriggerInfos() );
  229. SQLRETURN nRet = YieldingEnsureDatabaseSchemaCorrect( eSchemaCatalog, pSchemaUpdate );
  230. if( !SQL_OK( nRet ) )
  231. {
  232. AssertMsg( false, "SQL Schema Update failed" );
  233. return false;
  234. }
  235. SAFE_RELEASE( pSchemaUpdate );
  236. return true;
  237. }
  238. //-----------------------------------------------------------------------------
  239. // Purpose: Examines actual running schema of database, compares to specified desired
  240. // schema, and changes the actual schema to correspond to desired schema
  241. // Input: pSQLThread - SQL thread to execute on
  242. // pSchemaUpdate - pointer to object with desired schema
  243. // Output: SQL return code.
  244. //-----------------------------------------------------------------------------
  245. SQLRETURN CJobUpdateSchema::YieldingEnsureDatabaseSchemaCorrect( ESchemaCatalog eSchemaCatalog, CSchemaUpdate *pSchemaUpdate )
  246. {
  247. Assert( pSchemaUpdate );
  248. CMapPRecordInfo &mapPRecordInfoDesired = pSchemaUpdate->m_mapPRecordInfoDesired;
  249. const CUtlVector< CTriggerInfo > &vecTriggerInfoDesired = pSchemaUpdate->m_vecTriggerInfo;
  250. m_eConversionMode = pSchemaUpdate->m_eConversionMode;
  251. bool bDoConversion = true;
  252. // bool bDoConversion = ( ( k_EConversionModeConvertSafe == eConversionMode ) ||
  253. // ( k_EConversionModeConvertIrreversible == eConversionMode ) );
  254. CMapPRecordInfo mapPRecordInfoActual;
  255. mapPRecordInfoActual.SetLessFunc( CaselessStringLessThan );
  256. CUtlVector<CRecordInfo *> vecPRecordInfoDesiredMissing;
  257. CUtlVector<CRecordInfo *> vecPRecordInfoActualDifferent;
  258. CUtlVector<CRecordInfo *> vecPRecordInfoActualUnknown;
  259. CUtlVector< CTriggerInfo > vecTriggerInfoActual;
  260. CUtlVector< CTriggerInfo > vecTriggerInfoMissing;
  261. CUtlVector< CTriggerInfo > vecTriggerInfoDifferent;
  262. pSchemaUpdate->m_cTablesDesiredMissing = 0;
  263. pSchemaUpdate->m_cTablesActualDifferent = 0;
  264. pSchemaUpdate->m_cTablesActualUnknown = 0;
  265. pSchemaUpdate->m_cTablesNeedingChange = 0;
  266. pSchemaUpdate->m_cColumnsDesiredMissing = 0;
  267. pSchemaUpdate->m_cColumnsActualDifferent = 0;
  268. pSchemaUpdate->m_cColumnsActualUnknown = 0;
  269. pSchemaUpdate->m_sDetail.Clear();
  270. CFmtStr1024 &sDetail = pSchemaUpdate->m_sDetail;
  271. CFastTimer tickCounterOverall;
  272. tickCounterOverall.Start();
  273. //
  274. // Do some up-front bookkeeping to see how many tables need to be created and/or altered
  275. //
  276. int nSchemaID;
  277. SQLRETURN nRet = YieldingGetSchemaID( eSchemaCatalog, &nSchemaID );
  278. EXIT_ON_SQL_FAILURE( nRet );
  279. // Determine the actual running schema
  280. nRet = YieldingGetRecordInfoForAllTables( eSchemaCatalog, nSchemaID, mapPRecordInfoActual );
  281. EXIT_ON_SQL_FAILURE( nRet );
  282. nRet = YieldingGetTriggers( eSchemaCatalog, nSchemaID, vecTriggerInfoActual );
  283. EXIT_ON_SQL_FAILURE( nRet );
  284. // Look through the list of desired tables, find any that are missing or different from the actual schema
  285. FOR_EACH_MAP_FAST( mapPRecordInfoDesired, iRecordInfoDesired )
  286. {
  287. // is this desired table in the currently connected catalog?
  288. CRecordInfo *pRecordInfoDesired = mapPRecordInfoDesired[iRecordInfoDesired];
  289. if ( pRecordInfoDesired->GetESchemaCatalog() == eSchemaCatalog )
  290. {
  291. // yes. do something about it
  292. int iRecordInfoActual = mapPRecordInfoActual.Find( pRecordInfoDesired->GetName() );
  293. if ( mapPRecordInfoDesired.InvalidIndex() == iRecordInfoActual )
  294. {
  295. // This table is desired but does not exist
  296. vecPRecordInfoDesiredMissing.AddToTail( pRecordInfoDesired );
  297. }
  298. else
  299. {
  300. // Table with same name exists in desired & actual schemas; is it exactly the same?
  301. CRecordInfo *pRecordInfoActual = mapPRecordInfoActual[iRecordInfoActual];
  302. if ( !pRecordInfoDesired->EqualTo( pRecordInfoActual ) )
  303. {
  304. // This desired table exists but the actual table is different than desired
  305. vecPRecordInfoActualDifferent.AddToTail( pRecordInfoActual );
  306. }
  307. }
  308. }
  309. }
  310. // Now, look through the list of actual tables and find any that do not exist in the list of desired tables
  311. FOR_EACH_MAP_FAST( mapPRecordInfoActual, iRecordInfoActual )
  312. {
  313. CRecordInfo *pRecordInfoActual = mapPRecordInfoActual[iRecordInfoActual];
  314. int iRecordInfoDesired = mapPRecordInfoDesired.Find( pRecordInfoActual->GetName() );
  315. if ( !mapPRecordInfoDesired.IsValidIndex( iRecordInfoDesired ) )
  316. {
  317. // This table exists but is not in the list of desired tables
  318. // maybe it's an old table.
  319. vecPRecordInfoActualUnknown.AddToTail( pRecordInfoActual );
  320. }
  321. }
  322. // find a list of missing triggers
  323. FOR_EACH_VEC( vecTriggerInfoDesired, iDesired )
  324. {
  325. // not of this catalog? skip it
  326. if ( vecTriggerInfoDesired[ iDesired ].m_eSchemaCatalog != eSchemaCatalog )
  327. continue;
  328. // it is our catalog, so try and match
  329. bool bMatched = false;
  330. FOR_EACH_VEC( vecTriggerInfoActual, iActual )
  331. {
  332. // is it the same table and trigger name?
  333. if ( vecTriggerInfoActual[ iActual ] == vecTriggerInfoDesired[ iDesired ] )
  334. {
  335. // yes! test the text for differences
  336. if ( vecTriggerInfoActual[ iActual ].IsDifferent( vecTriggerInfoDesired[ iDesired ] ) )
  337. {
  338. vecTriggerInfoDifferent.AddToTail( vecTriggerInfoDesired[ iDesired ] );
  339. }
  340. else
  341. {
  342. // we have a match!
  343. vecTriggerInfoActual[ iActual ].m_bMatched = true;
  344. bMatched = true;
  345. }
  346. break;
  347. }
  348. }
  349. if ( !bMatched )
  350. {
  351. vecTriggerInfoMissing.AddToTail( vecTriggerInfoDesired[ iDesired ] );
  352. }
  353. }
  354. //
  355. // Now do the actual conversion
  356. //
  357. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "Database conversion: %s\n",
  358. bDoConversion ? "beginning" : "inspection only" );
  359. // find tables which need to be created
  360. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "# of specified tables that do not currently exist in database: %d\n",
  361. vecPRecordInfoDesiredMissing.Count() );
  362. if ( vecPRecordInfoDesiredMissing.Count() > 0 )
  363. pSchemaUpdate->m_bConversionNeeded = true;
  364. // Create any tables which need to be created
  365. for ( int iTable = 0; iTable < vecPRecordInfoDesiredMissing.Count(); iTable++ )
  366. {
  367. CRecordInfo *pRecordInfoDesired = vecPRecordInfoDesiredMissing[iTable];
  368. if ( bDoConversion )
  369. {
  370. CFastTimer tickCounter;
  371. tickCounter.Start();
  372. // Create the table
  373. nRet = YieldingCreateTable( eSchemaCatalog, pRecordInfoDesired );
  374. EXIT_ON_SQL_FAILURE( nRet );
  375. tickCounter.End();
  376. int nElapsedMilliseconds = tickCounter.GetDuration().GetMilliseconds();
  377. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tCreated table: %s: %d millisec\n", pRecordInfoDesired->GetName(), nElapsedMilliseconds );
  378. }
  379. else
  380. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t%s\n", pRecordInfoDesired->GetName() );
  381. }
  382. // find tables which are different
  383. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: # of specified tables that differ from schema in database: %d\n",
  384. vecPRecordInfoActualDifferent.Count() );
  385. #ifdef _DEBUG
  386. // are some different? if so, list their names only for now.
  387. // This is in _debug only because it's useful for debugging the below loop,
  388. // but spewey for everyday life (as long as the below loop is working).
  389. if ( vecPRecordInfoActualDifferent.Count() > 0 )
  390. {
  391. FOR_EACH_VEC( vecPRecordInfoActualDifferent, i )
  392. {
  393. CRecordInfo *pRecordInfoActual = vecPRecordInfoActualDifferent[i];
  394. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: table %s is different\n",
  395. pRecordInfoActual->GetName() );
  396. }
  397. }
  398. #endif
  399. pSchemaUpdate->m_bSkippedAChange = false;
  400. // Alter any table which needs to be altered
  401. for ( int iTable = 0; iTable < vecPRecordInfoActualDifferent.Count(); iTable++ )
  402. {
  403. CRecordInfo *pRecordInfoActual = vecPRecordInfoActualDifferent[iTable];
  404. int iRecordInfoDesired = mapPRecordInfoDesired.Find( pRecordInfoActual->GetName() );
  405. Assert( mapPRecordInfoDesired.InvalidIndex() != iRecordInfoDesired );
  406. CRecordInfo *pRecordInfoDesired = mapPRecordInfoDesired[iRecordInfoDesired];
  407. CUtlVector<const CColumnInfo *> vecPColumnInfoDesiredMissing;
  408. CUtlVector<const CColumnInfo *> vecPColumnInfoActualDifferent;
  409. CUtlVector<const CColumnInfo *> vecPColumnInfoActualUnknown;
  410. // We know something is different between the actual & desired schema for this table, but don't yet know what
  411. // Compare each column
  412. for ( int iColumnDesired = 0; iColumnDesired < pRecordInfoDesired->GetNumColumns(); iColumnDesired ++ )
  413. {
  414. const CColumnInfo &columnInfoDesired = pRecordInfoDesired->GetColumnInfo( iColumnDesired );
  415. int iColumnActual = -1;
  416. bool bRet = pRecordInfoActual->BFindColumnByName( columnInfoDesired.GetName(), &iColumnActual );
  417. if ( bRet )
  418. {
  419. const CColumnInfo &columnInfoActual = pRecordInfoActual->GetColumnInfo( iColumnActual );
  420. if ( columnInfoActual.GetChecksum() != columnInfoDesired.GetChecksum() )
  421. {
  422. // The actual column is different than the desired column
  423. vecPColumnInfoActualDifferent.AddToTail( &columnInfoActual );
  424. }
  425. }
  426. else
  427. {
  428. // The desired column is missing from the actual table
  429. vecPColumnInfoDesiredMissing.AddToTail( &columnInfoDesired );
  430. }
  431. }
  432. for ( int iColumnActual = 0; iColumnActual < pRecordInfoActual->GetNumColumns(); iColumnActual ++ )
  433. {
  434. const CColumnInfo &columnInfoActual = pRecordInfoActual->GetColumnInfo( iColumnActual );
  435. int iColumnDesired = -1;
  436. bool bRet = pRecordInfoDesired->BFindColumnByName( columnInfoActual.GetName(), &iColumnDesired );
  437. if ( !bRet )
  438. {
  439. // this column exists in the running schema, but not in the desired schema (e.g. old column)
  440. vecPColumnInfoActualUnknown.AddToTail( &columnInfoActual );
  441. }
  442. }
  443. if ( ( vecPColumnInfoDesiredMissing.Count() > 0 ) || ( vecPColumnInfoActualDifferent.Count() > 0 ) )
  444. {
  445. pSchemaUpdate->m_bConversionNeeded = true;
  446. pSchemaUpdate->m_cTablesNeedingChange++;
  447. }
  448. // Add any desired columns which are missing from the actual schema
  449. if ( vecPColumnInfoDesiredMissing.Count() > 0 )
  450. {
  451. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\tDesired columns missing in table %s:\n", pRecordInfoActual->GetName() );
  452. for ( int iColumn = 0; iColumn < vecPColumnInfoDesiredMissing.Count(); iColumn++ )
  453. {
  454. const CColumnInfo *pColumnInfoDesired = vecPColumnInfoDesiredMissing[iColumn];
  455. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t\t%s\n", pColumnInfoDesired->GetName() );
  456. if ( bDoConversion )
  457. {
  458. CFastTimer tickCounter;
  459. tickCounter.Start();
  460. // Add the column
  461. nRet = YieldingAlterTableAddColumn( eSchemaCatalog, pRecordInfoActual, pColumnInfoDesired );
  462. EXIT_ON_SQL_FAILURE( nRet );
  463. tickCounter.End();
  464. int nElapsedMilliseconds = tickCounter.GetDuration().GetMilliseconds();
  465. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t\t\tCreated column %s: %d millisec\n", pColumnInfoDesired->GetName(), nElapsedMilliseconds );
  466. }
  467. }
  468. }
  469. // Check for any stray indices that aren't found in the specification?
  470. bool bIndexMismatch = false;
  471. for ( int idx = 0 ; idx < pRecordInfoActual->GetIndexFieldCount() ; ++idx )
  472. {
  473. const FieldSet_t &fs = pRecordInfoActual->GetIndexFields()[idx];
  474. if ( pRecordInfoDesired->FindIndex( pRecordInfoActual, fs ) >= 0 )
  475. continue;
  476. if ( pRecordInfoDesired->FindIndexByName( fs.GetIndexName() ) >= 0 )
  477. continue; // we already handled this above
  478. bIndexMismatch = true;
  479. if ( idx == pRecordInfoActual->GetPKIndex() )
  480. {
  481. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tTable %s primary key in database differs from specification.\n",
  482. pRecordInfoDesired->GetName() );
  483. nRet = YieldingProcessUnsafeConversion( eSchemaCatalog,
  484. CFmtStr("ALTER TABLE App%u.%s DROP CONSTRAINT %s", GGCBase()->GetAppID(), pRecordInfoActual->GetName(), fs.GetIndexName() ).String(),
  485. CFmtStr("%s: remove old primary key.", pRecordInfoDesired->GetName() ).String() );
  486. EXIT_ON_SQL_FAILURE( nRet );
  487. }
  488. else
  489. {
  490. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tTable %s index %s exists in database but is not in specification. (Possible performance problem?).\n",
  491. pRecordInfoDesired->GetName(), fs.GetIndexName() );
  492. nRet = YieldingProcessUnsafeConversion( eSchemaCatalog,
  493. CFmtStr("DROP INDEX %s ON App%u.%s", fs.GetIndexName(), GGCBase()->GetAppID(), pRecordInfoActual->GetName() ).String(),
  494. CFmtStr("%s: cleanup stray index %s.", pRecordInfoDesired->GetName(), fs.GetIndexName() ).String() );
  495. EXIT_ON_SQL_FAILURE( nRet );
  496. }
  497. }
  498. // Change any columns which are different between desired and actual schema
  499. if ( vecPColumnInfoActualDifferent.Count() > 0 )
  500. {
  501. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tColumns that differ between specification and database in table %s:\n",
  502. pRecordInfoActual->GetName() );
  503. for ( int iColumn = 0; iColumn < vecPColumnInfoActualDifferent.Count(); iColumn++ )
  504. {
  505. const CColumnInfo *pColumnInfoActual = vecPColumnInfoActualDifferent[iColumn];
  506. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t%s", pColumnInfoActual->GetName() );
  507. int iColumnDesired = -1;
  508. DbgVerify( pRecordInfoDesired->BFindColumnByName( pColumnInfoActual->GetName(), &iColumnDesired ) );
  509. const CColumnInfo *pColumnInfoDesired = &pRecordInfoDesired->GetColumnInfo( iColumnDesired );
  510. // if type or size changed, alter the column
  511. if ( ( pColumnInfoDesired->GetType() != pColumnInfoActual->GetType() ) ||
  512. // fixed length field, and the sizes differ
  513. ( ! pColumnInfoDesired->BIsVariableLength() &&
  514. ( pColumnInfoDesired->GetFixedSize() != pColumnInfoActual->GetFixedSize() ) ) ||
  515. // variable length field, and the sizes differ
  516. // fixed length field, and the sizes differ
  517. ( pColumnInfoDesired->BIsVariableLength() &&
  518. ( pColumnInfoDesired->GetMaxSize() != pColumnInfoActual->GetMaxSize() ) ) )
  519. {
  520. if ( k_EConversionModeConvertIrreversible != m_eConversionMode )
  521. {
  522. pSchemaUpdate->m_bSkippedAChange = true;
  523. if ( pColumnInfoDesired->GetType() != pColumnInfoActual->GetType() )
  524. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t(data types differ: desired=%s, actual=%s)", PchNameFromEGCSQLType( pColumnInfoDesired->GetType() ), PchNameFromEGCSQLType( pColumnInfoActual->GetType() ) );
  525. if ( pColumnInfoDesired->GetFixedSize() != pColumnInfoActual->GetFixedSize() )
  526. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t(column sizes differ: desired=%d, actual=%d)", pColumnInfoDesired->GetFixedSize(), pColumnInfoActual->GetFixedSize() );
  527. if ( pColumnInfoDesired->GetMaxSize() != pColumnInfoActual->GetMaxSize() )
  528. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t(maximum column sizes differ: desired=%d, actual=%d)", pColumnInfoDesired->GetMaxSize(), pColumnInfoActual->GetMaxSize() );
  529. }
  530. nRet = YieldingChangeColumnTypeOrLength( eSchemaCatalog, pRecordInfoActual, pColumnInfoDesired );
  531. EXIT_ON_SQL_FAILURE( nRet );
  532. }
  533. // If column constraints/indexes are different, make appropriate adjustments
  534. // do this second so it has a chance to succeed - otherwise, we may create an index here that
  535. // prevents us from performing an alter table
  536. if ( pColumnInfoDesired->GetColFlags() != pColumnInfoActual->GetColFlags() )
  537. {
  538. if ( k_EConversionModeConvertIrreversible != m_eConversionMode )
  539. {
  540. char szDesiredFlags[k_nKiloByte];
  541. char szActualFlags[k_nKiloByte];
  542. pColumnInfoDesired->GetColFlagDescription( szDesiredFlags, Q_ARRAYSIZE( szDesiredFlags ) );
  543. pColumnInfoActual->GetColFlagDescription( szActualFlags, Q_ARRAYSIZE( szActualFlags ) );
  544. pSchemaUpdate->m_bSkippedAChange = true;
  545. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t(column flags differ: desired=\"%s\", actual=\"%s\")",
  546. szDesiredFlags, szActualFlags );
  547. }
  548. nRet = YieldingChangeColumnProperties( eSchemaCatalog, pRecordInfoActual, pColumnInfoActual, pColumnInfoDesired );
  549. EXIT_ON_SQL_FAILURE( nRet );
  550. }
  551. if ( pSchemaUpdate->m_bSkippedAChange )
  552. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t(Not attempting unsafe change).\n" );
  553. }
  554. }
  555. // Scan for any new / changed indices
  556. for ( int idx = 0 ; idx < pRecordInfoDesired->GetIndexFieldCount() ; ++idx )
  557. {
  558. const FieldSet_t &fs = pRecordInfoDesired->GetIndexFields()[idx];
  559. int iActualIdx = pRecordInfoActual->FindIndex( pRecordInfoDesired, fs );
  560. if ( iActualIdx >= 0 )
  561. continue;
  562. bIndexMismatch = true;
  563. // The exact index we want doesn't exist. Is it the primary key?
  564. CUtlString sCommand;
  565. CUtlString sComment;
  566. if ( idx == pRecordInfoDesired->GetPKIndex() )
  567. {
  568. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tTable %s primary key in specification differs from database.\n",
  569. pRecordInfoDesired->GetName() );
  570. sComment.Format( "%s: create new primary key constraint", pRecordInfoDesired->GetName() );
  571. TSQLCmdStr cmd;
  572. BuildTablePKConstraintText( &cmd, pRecordInfoDesired );
  573. for ( int i = 0 ; i < fs.GetCount() ; ++i ) // make sure they are non-NULL
  574. {
  575. int idxField = fs.GetField(i);
  576. const CColumnInfo *pColInfo = &pRecordInfoDesired->GetColumnInfo( idxField );
  577. Assert( pColInfo->BIsPrimaryKey() );
  578. sCommand += GetAlterColumnText( pRecordInfoDesired, pColInfo );
  579. sCommand += ";\n";
  580. }
  581. CUtlString sCreatePK;
  582. sCreatePK.Format( "GO\nALTER TABLE App%u.%s ADD %s\n", GGCBase()->GetAppID(), pRecordInfoDesired->GetName(), cmd.String() );
  583. sCommand += sCreatePK;
  584. }
  585. else
  586. {
  587. // Another common thing that could happen is that an index is changed.
  588. // Look for an existing index with the same name as a way to try to
  589. // detect this common case and provide a more specific message. (Otherwise,
  590. // we will report it as a missing index, and an extra unwanted index --- which
  591. // is correct but a bit more confusing.)
  592. iActualIdx = pRecordInfoActual->FindIndexByName( fs.GetIndexName() );
  593. if ( iActualIdx < 0 )
  594. {
  595. sComment.Format("%s: add index %s", pRecordInfoDesired->GetName(), fs.GetIndexName() );
  596. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tTable %s index %s is specified but not present in database.\n",
  597. pRecordInfoDesired->GetName(), fs.GetIndexName() );
  598. }
  599. else
  600. {
  601. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tTable %s index %s differs between specification and database.\n",
  602. pRecordInfoDesired->GetName(), fs.GetIndexName() );
  603. sComment.Format( "%s: fix index %s", pRecordInfoDesired->GetName(), fs.GetIndexName() );
  604. sCommand.Format( "DROP INDEX %s ON App%u.%s;\n", fs.GetIndexName(), GGCBase()->GetAppID(), pRecordInfoDesired->GetName() );
  605. }
  606. sCommand += GetAddIndexSQL( pRecordInfoDesired, fs );
  607. }
  608. nRet = YieldingProcessUnsafeConversion( eSchemaCatalog, sCommand, sComment );
  609. EXIT_ON_SQL_FAILURE( nRet );
  610. }
  611. // Just to be safe, let's run the old code, too.
  612. if ( !bIndexMismatch && !pRecordInfoActual->CompareIndexLists( pRecordInfoDesired ) )
  613. {
  614. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tIndex sets in table %s differ between specification and database [GC]:\n",
  615. pRecordInfoDesired->GetName() );
  616. CFmtStr1024 sTemp;
  617. pRecordInfoDesired->GetIndexFieldList( &sTemp, 3 );
  618. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\tDesired %s", sTemp.Access() );
  619. pRecordInfoActual->GetIndexFieldList( &sTemp, 3 );
  620. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\tActual %s", sTemp.Access() );
  621. }
  622. // what about foreign key constraints?
  623. if ( ! pRecordInfoActual->CompareFKs( pRecordInfoDesired ) )
  624. {
  625. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tForeign Key constraints in table %s differs between specification and database [GC]:\n",
  626. pRecordInfoDesired->GetName() );
  627. CFmtStr1024 sTemp;
  628. pRecordInfoDesired->GetFKListString( &sTemp, 3 );
  629. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\tDesired %s", sTemp.Access() );
  630. pRecordInfoActual->GetFKListString( &sTemp, 3 );
  631. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\tActual %s", sTemp.Access() );
  632. }
  633. if ( vecPColumnInfoActualUnknown.Count() > 0 )
  634. {
  635. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tColumns in database [GC] table %s that are not in specification (ignored):\n",
  636. pRecordInfoActual->GetName() );
  637. // Since this can actually destroy data, let's not ever, ever run it automatically.
  638. CUtlString sCommand;
  639. sCommand.Format( "ALTER TABLE App%u.%s DROP COLUMN ", GGCBase()->GetAppID(), pRecordInfoActual->GetName() );
  640. for ( int iColumn = 0; iColumn < vecPColumnInfoActualUnknown.Count(); iColumn++ )
  641. {
  642. const CColumnInfo *pColumnInfo = vecPColumnInfoActualUnknown[iColumn];
  643. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t%s\n", pColumnInfo->GetName() );
  644. if ( iColumn != 0 )
  645. sCommand += ", ";
  646. sCommand += pColumnInfo->GetName();
  647. }
  648. AddDataDestroyingConversion( sCommand,
  649. CFmtStr( "-- Drop extra column(s) in %s\n", pRecordInfoActual->GetName() ) );
  650. }
  651. pSchemaUpdate->m_cColumnsDesiredMissing += vecPColumnInfoDesiredMissing.Count();
  652. pSchemaUpdate->m_cColumnsActualDifferent += vecPColumnInfoActualDifferent.Count();
  653. pSchemaUpdate->m_cColumnsActualUnknown += vecPColumnInfoActualUnknown.Count();
  654. }
  655. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: # of tables that currently exist in database but were unspecified: %d\n",
  656. vecPRecordInfoActualUnknown.Count() );
  657. for ( int iTable = 0; iTable < vecPRecordInfoActualUnknown.Count(); iTable++ )
  658. {
  659. CRecordInfo *pRecordInfo = vecPRecordInfoActualUnknown[iTable];
  660. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t%s\n", pRecordInfo->GetName() );
  661. AddDataDestroyingConversion(
  662. CFmtStr( "DROP TABLE App%u.%s\n", GGCBase()->GetAppID(), pRecordInfo->GetName() ),
  663. CFmtStr( "-- Drop extra table %s\n", pRecordInfo->GetName() ) );
  664. }
  665. // then, the triggers
  666. if ( vecTriggerInfoMissing.Count() > 0 )
  667. {
  668. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: # of specified triggers that do not currently exist: %d\n",
  669. vecTriggerInfoMissing.Count() );
  670. FOR_EACH_VEC( vecTriggerInfoMissing, iMissing )
  671. {
  672. CFastTimer tickCounter;
  673. tickCounter.Start();
  674. // Create the trigger
  675. nRet = YieldingCreateTrigger( eSchemaCatalog, vecTriggerInfoMissing[ iMissing] );
  676. EXIT_ON_SQL_FAILURE( nRet );
  677. tickCounter.End();
  678. int nElapsedMilliseconds = tickCounter.GetDuration().GetMilliseconds();
  679. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: Created trigger %s on table %s: %d millisec\n",
  680. vecTriggerInfoMissing[ iMissing ].m_szTriggerName,
  681. vecTriggerInfoMissing[ iMissing ].m_szTriggerTableName, nElapsedMilliseconds );
  682. }
  683. }
  684. // different triggers
  685. FOR_EACH_VEC( vecTriggerInfoDifferent, iDifferent )
  686. {
  687. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: Trigger %s on table %s differs from the desired trigger\n", vecTriggerInfoMissing[ iDifferent ].m_szTriggerName,
  688. vecTriggerInfoMissing[ iDifferent ].m_szTriggerTableName);
  689. // a different trigger text is a forced failure.
  690. nRet = SQL_ERROR;
  691. }
  692. // extra triggers
  693. FOR_EACH_VEC( vecTriggerInfoActual, iActual )
  694. {
  695. // if it was never matched, it isn't in the schema anywhere
  696. if ( ! vecTriggerInfoActual[ iActual ].m_bMatched )
  697. {
  698. SQLRETURN nSQLReturn = YieldingDropTrigger( eSchemaCatalog, vecTriggerInfoActual[ iActual ] );
  699. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: Trigger %s on table %s is not in the declared schema ... Drop %s",
  700. vecTriggerInfoActual[ iActual ].m_szTriggerName,
  701. vecTriggerInfoActual[ iActual ].m_szTriggerTableName,
  702. SQL_OK( nSQLReturn ) ? "OK" : "FAILED!" ) ;
  703. if ( !SQL_OK ( nSQLReturn ) )
  704. {
  705. // it broke; latch the failure
  706. nRet = nSQLReturn;
  707. }
  708. }
  709. }
  710. tickCounterOverall.End();
  711. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "[GC]: Total time: %d milliseconds\n",
  712. tickCounterOverall.GetDuration().GetMilliseconds() );
  713. Exit:
  714. // Spew suggested SQL to clean up stuff
  715. if ( !m_sRecommendedSQL.IsEmpty() || !m_sDataDestroyingCleanupSQL.IsEmpty() )
  716. {
  717. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\tThe following SQL might work to fixup schema differences.\n" );
  718. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t** \n" );
  719. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t** DISCLAIMER ** This conversion code is not well tested. Review the SQL and use at your own risk.\n" );
  720. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t** \n" );
  721. {
  722. CUtlVectorAutoPurge<char*> vecLines;
  723. V_SplitString( m_sRecommendedSQL, "\n", vecLines );
  724. FOR_EACH_VEC( vecLines, i )
  725. {
  726. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t%s\n", vecLines[i] );
  727. }
  728. m_sRecommendedSQL.Clear();
  729. }
  730. if ( !m_sDataDestroyingCleanupSQL.IsEmpty() )
  731. {
  732. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t-- WARNING: The following operations will *destroy* data that is in the database but not in the specification.\n" );
  733. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t-- If you have manually created extra tables or columns in your database, it will appear here!\n" );
  734. CUtlVectorAutoPurge<char*> vecLines;
  735. V_SplitString( m_sDataDestroyingCleanupSQL, "\n", vecLines );
  736. FOR_EACH_VEC( vecLines, i )
  737. {
  738. EmitAndAppend( sDetail, SPEW_GC, 2, 2, "\t\t%s\n", vecLines[i] );
  739. }
  740. m_sDataDestroyingCleanupSQL.Clear();
  741. }
  742. }
  743. pSchemaUpdate->m_cTablesDesiredMissing = vecPRecordInfoDesiredMissing.Count();
  744. pSchemaUpdate->m_cTablesActualDifferent = vecPRecordInfoActualDifferent.Count();
  745. pSchemaUpdate->m_cTablesActualUnknown = vecPRecordInfoActualUnknown.Count();
  746. FOR_EACH_MAP_FAST( mapPRecordInfoActual, iRecordInfoActual )
  747. SAFE_RELEASE( mapPRecordInfoActual[iRecordInfoActual] );
  748. return nRet;
  749. }
  750. //-----------------------------------------------------------------------------
  751. // Purpose: Builds a record info for each table in database that describes the
  752. // columns in that table
  753. // Input: pSQLConnection - SQL connection to execute on
  754. // mapPRecordInfo - map to add the table record infos to
  755. // Output: SQL return code.
  756. //-----------------------------------------------------------------------------
  757. SQLRETURN CJobUpdateSchema::YieldingGetSchemaID( ESchemaCatalog eSchemaCatalog, int *pSchemaID )
  758. {
  759. CSQLAccess sqlAccess( eSchemaCatalog );
  760. CFmtStr1024 sDefaultSchema;
  761. if( !sqlAccess.BYieldingExecuteString( FILE_AND_LINE, "SELECT default_schema_name FROM sys.database_principals WHERE name=CURRENT_USER",
  762. &sDefaultSchema ) )
  763. return SQL_ERROR;
  764. CFmtStr sExpectedDefaultSchema( "App%u", GGCBase()->GetAppID() );
  765. if ( 0 != Q_stricmp( sDefaultSchema, sExpectedDefaultSchema ) )
  766. {
  767. AssertMsg2( false, "SQL connection has the wrong default schema. Expected: %s. Actual %s", sExpectedDefaultSchema.Get(), sDefaultSchema.Get() );
  768. return SQL_ERROR;
  769. }
  770. sqlAccess.AddBindParam( sDefaultSchema );
  771. if( !sqlAccess.BYieldingExecuteScalarInt( FILE_AND_LINE, "SELECT SCHEMA_ID(?)", pSchemaID ) )
  772. return SQL_ERROR;
  773. return SQL_SUCCESS;
  774. }
  775. //-----------------------------------------------------------------------------
  776. // Purpose: Builds a record info for each table in database that describes the
  777. // columns in that table
  778. // Input: pSQLConnection - SQL connection to execute on
  779. // mapPRecordInfo - map to add the table record infos to
  780. // Output: SQL return code.
  781. //-----------------------------------------------------------------------------
  782. SQLRETURN CJobUpdateSchema::YieldingGetRecordInfoForAllTables( ESchemaCatalog eSchemaCatalog, int nSchemaID, CMapPRecordInfo &mapPRecordInfo )
  783. {
  784. CSQLAccess sqlAccess( eSchemaCatalog );
  785. // create a query that returns all tables in the database
  786. sqlAccess.AddBindParam( nSchemaID );
  787. RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, "SELECT object_id, name FROM sys.objects WHERE type_desc = 'USER_TABLE' AND is_ms_shipped = 0 AND schema_id = ?" ) );
  788. FOR_EACH_SQL_RESULT( sqlAccess, 0, tableIDRecord )
  789. {
  790. int nTableID;
  791. RETURN_SQL_ERROR_ON_FALSE( tableIDRecord.BGetIntValue( 0, &nTableID ) );
  792. CFmtStr1024 sTableName;
  793. RETURN_SQL_ERROR_ON_FALSE( tableIDRecord.BGetStringValue( 1, &sTableName) );
  794. YieldingGetColumnInfoForTable( eSchemaCatalog, mapPRecordInfo, nTableID, sTableName );
  795. }
  796. // now, get the column indexes and constraints for each table
  797. FOR_EACH_MAP_FAST( mapPRecordInfo, iRecordInfo )
  798. {
  799. CRecordInfo *pRecordInfo = mapPRecordInfo[iRecordInfo];
  800. pRecordInfo->SetAllColumnsAdded();
  801. // determine indexes and constraints
  802. YieldingGetColumnIndexes( eSchemaCatalog, pRecordInfo );
  803. // determine FK constraints
  804. YieldingGetTableFKConstraints( eSchemaCatalog, pRecordInfo );
  805. // do final calculations then ready to use
  806. pRecordInfo->PrepareForUse();
  807. }
  808. return SQL_SUCCESS;
  809. }
  810. SQLRETURN CJobUpdateSchema::YieldingGetColumnInfoForTable( ESchemaCatalog eSchemaCatalog, CMapPRecordInfo &mapPRecordInfo, int nTableID, const char *pchTableName )
  811. {
  812. CSQLAccess sqlAccess( eSchemaCatalog );
  813. sqlAccess.AddBindParam( nTableID );
  814. RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, "SELECT name, column_id, user_type_id, max_length, is_identity FROM sys.columns WHERE object_id = ?") );
  815. CRecordInfo *pRecordInfo = CRecordInfo::Alloc();
  816. pRecordInfo->SetName( pchTableName );
  817. pRecordInfo->SetTableID( nTableID );
  818. FOR_EACH_SQL_RESULT( sqlAccess, 0, columnInfo )
  819. {
  820. CFmtStr1024 sColumnName;
  821. int nType;
  822. int nColumnID;
  823. int nMaxLength;
  824. bool bIdentity;
  825. RETURN_SQL_ERROR_ON_FALSE( columnInfo.BGetStringValue( 0, &sColumnName) );
  826. RETURN_SQL_ERROR_ON_FALSE( columnInfo.BGetIntValue( 1, &nColumnID ) );
  827. RETURN_SQL_ERROR_ON_FALSE( columnInfo.BGetIntValue( 2, &nType ) );
  828. RETURN_SQL_ERROR_ON_FALSE( columnInfo.BGetIntValue( 3, &nMaxLength ) );
  829. RETURN_SQL_ERROR_ON_FALSE( columnInfo.BGetBoolValue( 4, &bIdentity) );
  830. int nColFlags = 0;
  831. if( bIdentity )
  832. nColFlags |= k_nColFlagAutoIncrement;
  833. pRecordInfo->AddColumn( sColumnName, nColumnID, GetEGCSQLTypeForMSSQLType( nType ), nMaxLength, nColFlags, nMaxLength );
  834. }
  835. mapPRecordInfo.Insert( pRecordInfo->GetName(), pRecordInfo );
  836. return SQL_SUCCESS;
  837. }
  838. //-----------------------------------------------------------------------------
  839. // purpose: get a list of triggers from the database.
  840. //-----------------------------------------------------------------------------
  841. SQLRETURN CJobUpdateSchema::YieldingGetTriggers( ESchemaCatalog eSchemaCatalog, int nSchemaID, CUtlVector< CTriggerInfo > &vecTriggerInfo )
  842. {
  843. CSQLAccess sqlAccess( eSchemaCatalog );
  844. // get some description and the text of the triggers on this database.
  845. // Find the name of the trigger, the name of the table it servers, the type of
  846. // trigger, and its text. Doesn't bring back any disabled or MS-shipped triggers,
  847. // and gets only DML triggers and not DDL triggers.
  848. const char *pchStatement = "SELECT ST.name AS TriggerName, SOT.name AS TableName, ST.is_instead_of_trigger, SC.Text, SC.ColID"
  849. " FROM sys.triggers AS ST"
  850. " JOIN sys.syscomments AS SC ON SC.id = ST.object_id"
  851. " JOIN sys.objects AS SO ON SO.object_id = ST.object_id"
  852. " JOIN sys.objects AS SOT on SOT.object_id = ST.parent_id"
  853. " WHERE ST.type_desc = 'SQL_TRIGGER'"
  854. " AND ST.is_ms_shipped = 0 AND ST.is_disabled = 0"
  855. " AND ST.parent_class = 1"
  856. " AND SO.schema_id = ?"
  857. " ORDER BY TableName, TriggerName, SC.ColID";
  858. sqlAccess.AddBindParam( nSchemaID );
  859. RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, pchStatement ) );
  860. // should be one results set
  861. Assert( 1 == sqlAccess.GetResultSetCount() );
  862. FOR_EACH_SQL_RESULT( sqlAccess, 0, sqlRecord )
  863. {
  864. // get the text of the procedure
  865. const char *pchText;
  866. DbgVerify( sqlRecord.BGetStringValue( 3, &pchText ) );
  867. // is this instead of?
  868. bool bIsInsteadOf;
  869. DbgVerify( sqlRecord.BGetBoolValue( 2, &bIsInsteadOf ) );
  870. // get the table name
  871. const char *pchTableName;
  872. DbgVerify( sqlRecord.BGetStringValue( 1, &pchTableName ) );
  873. // and the trigger name
  874. const char *pchTriggerName;
  875. DbgVerify( sqlRecord.BGetStringValue( 0, &pchTriggerName ) );
  876. // finally, grab the collation id
  877. int16 nColID;
  878. DbgVerify( sqlRecord.BGetInt16Value( 4, &nColID ) );
  879. if ( nColID == 1 )
  880. {
  881. // the collation ID is one, so we know this is a new one
  882. CTriggerInfo info;
  883. info.m_strText = pchText;
  884. Q_strncpy( info.m_szTriggerName, pchTriggerName, Q_ARRAYSIZE( info.m_szTriggerName ) );
  885. Q_strncpy( info.m_szTriggerTableName, pchTableName, Q_ARRAYSIZE( info.m_szTriggerTableName ) );
  886. info.m_eSchemaCatalog = eSchemaCatalog;
  887. vecTriggerInfo.AddToTail( info );
  888. }
  889. else
  890. {
  891. // the collation ID is not one, so we're concatenating.
  892. Assert( vecTriggerInfo.Count() - 1 >= 0 );
  893. // the name could not have changed
  894. Assert( 0 == Q_strcmp( vecTriggerInfo[vecTriggerInfo.Count() - 1].m_szTriggerName, pchTriggerName ) );
  895. }
  896. }
  897. return SQL_SUCCESS;
  898. }
  899. //-----------------------------------------------------------------------------
  900. // Purpose: retrieves the index information for the specified table, then adds this
  901. // information to the record info. This is a SQL Server-specific implementation
  902. // which gets data describing index features not available through plain ODBC
  903. // queries.
  904. // Input: pSQLConnection - SQL connection to execute on
  905. // pRecordInfo - CRecordInfo to add the index info into
  906. // Output: SQL return code.
  907. //-----------------------------------------------------------------------------
  908. SQLRETURN CJobUpdateSchema::YieldingGetColumnIndexes( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo )
  909. {
  910. // query the system management views for all of the indexes on this table
  911. static const char *pstrStatement =
  912. "SELECT SI.Name AS INDEX_NAME, SC.Name AS COLUMN_NAME, SI.Type AS [TYPE], IS_INCLUDED_COLUMN, SIC.KEY_Ordinal AS [ORDINAL_POSITION], IS_UNIQUE, IS_PRIMARY_KEY, SI.INDEX_ID"
  913. " FROM sys.indexes SI "
  914. " JOIN sys.index_columns SIC"
  915. " ON SIC.Object_id = SI.Object_Id"
  916. " AND SIC.Index_ID = SI.Index_id"
  917. " JOIN sys.objects SO"
  918. " ON SIC.Object_ID = SO.Object_ID"
  919. " JOIN sys.columns SC"
  920. " ON SC.Object_ID = SO.Object_ID"
  921. " AND SC.column_id = SIC.column_id"
  922. " WHERE SO.Object_ID = ? "
  923. "ORDER BY SIC.Index_id, SIC.Key_Ordinal ";
  924. CSQLAccess sqlAccess( eSchemaCatalog );
  925. sqlAccess.AddBindParam( pRecordInfo->GetTableID() );
  926. RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, pstrStatement ) );
  927. // builds a list of columns in a particular index.
  928. CUtlVector<int> vecColumns;
  929. CUtlVector<int> vecIncluded;
  930. int nLastIndexID = -1;
  931. bool bIsClustered = false;
  932. bool bIsIndexUnique = false;
  933. bool bIsPrimaryKey = false;
  934. CFmtStr1024 sIndexName, sColumnName;
  935. FOR_EACH_SQL_RESULT( sqlAccess, 0, typeRecord )
  936. {
  937. // Starting a new index?
  938. int nIndexID;
  939. DbgVerify( typeRecord.BGetIntValue( 7, &nIndexID ) );
  940. if ( nIndexID != nLastIndexID )
  941. {
  942. // first column! is it our first time through?
  943. if ( vecColumns.Count() > 0 )
  944. {
  945. // yes. let's add what we had from the previous index
  946. // to the fieldset
  947. FieldSet_t fs( bIsIndexUnique, bIsClustered, vecColumns, sIndexName );
  948. fs.AddIncludedColumns( vecIncluded );
  949. int idx = pRecordInfo->AddIndex( fs );
  950. if ( bIsPrimaryKey )
  951. {
  952. Assert( bIsIndexUnique );
  953. Assert( pRecordInfo->GetPKIndex() < 0 );
  954. pRecordInfo->SetPKIndex( idx );
  955. }
  956. // reset the vector
  957. vecColumns.RemoveAll();
  958. vecIncluded.RemoveAll();
  959. bIsIndexUnique = false;
  960. bIsClustered = false;
  961. bIsPrimaryKey = false;
  962. }
  963. nLastIndexID = nIndexID;
  964. }
  965. int nTypeID, nOrdinalPosition;
  966. bool bIsIncluded, bIsColumnUnique;
  967. DbgVerify( typeRecord.BGetStringValue( 0, &sIndexName ) );
  968. DbgVerify( typeRecord.BGetStringValue( 1, &sColumnName ) );
  969. DbgVerify( typeRecord.BGetIntValue( 2, &nTypeID ) );
  970. DbgVerify( typeRecord.BGetBoolValue( 3, &bIsIncluded ) );
  971. DbgVerify( typeRecord.BGetIntValue( 4, &nOrdinalPosition ) );
  972. DbgVerify( typeRecord.BGetBoolValue( 5, &bIsColumnUnique ) );
  973. DbgVerify( typeRecord.BGetBoolValue( 6, &bIsPrimaryKey ) );
  974. RETURN_SQL_ERROR_ON_FALSE( sColumnName.Length() > 0 );
  975. int nColumnIndexed = -1;
  976. RETURN_SQL_ERROR_ON_FALSE( pRecordInfo->BFindColumnByName( sColumnName, &nColumnIndexed ) );
  977. CColumnInfo & columnInfo = pRecordInfo->GetColumnInfo( nColumnIndexed );
  978. int nColFlags = 0;
  979. if ( bIsIncluded )
  980. {
  981. Assert( nOrdinalPosition == 0 );
  982. // it's included; no flags
  983. vecIncluded.AddToTail( nColumnIndexed );
  984. }
  985. else
  986. {
  987. Assert( nOrdinalPosition != 0 );
  988. if ( bIsPrimaryKey )
  989. {
  990. // if we're working a primary key, mark those flags
  991. nColFlags = k_nColFlagPrimaryKey | k_nColFlagIndexed | k_nColFlagUnique;
  992. // PKs are always unique
  993. bIsIndexUnique = true;
  994. }
  995. else
  996. {
  997. // if we're working a "regular" index, we need to know the uniqueness of the index ...
  998. nColFlags = k_nColFlagIndexed;
  999. if ( bIsColumnUnique )
  1000. {
  1001. nColFlags |= k_nColFlagUnique;
  1002. bIsIndexUnique = true;
  1003. }
  1004. }
  1005. // clustering type
  1006. if ( nTypeID == SQL_INDEX_CLUSTERED )
  1007. {
  1008. nColFlags |= k_nColFlagClustered;
  1009. bIsClustered = true;
  1010. }
  1011. columnInfo.SetColFlagBits( nColFlags );
  1012. // add this column to our list for the index set
  1013. vecColumns.AddToTail( nColumnIndexed );
  1014. }
  1015. }
  1016. // anything left over?
  1017. if ( vecColumns.Count() > 0 )
  1018. {
  1019. // yep, add that, too
  1020. FieldSet_t fs( bIsIndexUnique, bIsClustered, vecColumns, sIndexName );
  1021. fs.AddIncludedColumns( vecIncluded );
  1022. int idx = pRecordInfo->AddIndex( fs );
  1023. if ( bIsPrimaryKey )
  1024. {
  1025. Assert( bIsIndexUnique );
  1026. Assert( pRecordInfo->GetPKIndex() < 0 );
  1027. pRecordInfo->SetPKIndex( idx );
  1028. }
  1029. }
  1030. return SQL_SUCCESS;
  1031. }
  1032. //-----------------------------------------------------------------------------
  1033. // Purpose: Finds the schema info on any FK constraints defined for the table
  1034. // Input: pRecordInfo - CRecordInfo to add the index info into (and lookup table name out of)
  1035. // Output: SQL return code.
  1036. //-----------------------------------------------------------------------------
  1037. SQLRETURN CJobUpdateSchema::YieldingGetTableFKConstraints( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo )
  1038. {
  1039. // Used below, declared up here because of goto
  1040. FKData_t fkData;
  1041. CSQLAccess sqlAccess( eSchemaCatalog );
  1042. const char *pchStatement = "SELECT fk.name AS FKName, current_table.name AS TableName, parent_table.name AS ParentTableName, "
  1043. "table_column.name AS ColName, parent_table_column.name AS ParentColName, "
  1044. "fk.delete_referential_action_desc AS OnDelete, "
  1045. "fk.update_referential_action_desc AS OnUpdate "
  1046. "FROM sys.objects AS current_table "
  1047. "JOIN sys.foreign_keys AS fk ON fk.parent_object_id=current_table.object_id AND fk.is_ms_shipped=0 "
  1048. "JOIN sys.foreign_key_columns AS fk_col ON fk_col.constraint_object_id=fk.object_id "
  1049. "JOIN sys.objects AS parent_table ON parent_table.object_id=fk_col.referenced_object_id "
  1050. "JOIN sys.columns AS table_column ON table_column.object_id=fk_col.parent_object_id AND table_column.column_id=fk_col.parent_column_id "
  1051. "JOIN sys.columns AS parent_table_column ON parent_table_column.object_id=fk_col.referenced_object_id AND parent_table_column.column_id=fk_col.referenced_column_id "
  1052. "WHERE current_table.object_id = ? ORDER BY fk.name";
  1053. sqlAccess.AddBindParam( pRecordInfo->GetTableID() );
  1054. RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, pchStatement ) );
  1055. FOR_EACH_SQL_RESULT( sqlAccess, 0, sqlRecord )
  1056. {
  1057. // get all the data for the FK
  1058. const char *pchFKName;
  1059. DbgVerify( sqlRecord.BGetStringValue( 0, &pchFKName ) );
  1060. const char *pchTableName;
  1061. DbgVerify( sqlRecord.BGetStringValue( 1, &pchTableName ) );
  1062. AssertMsg( Q_stricmp( pchTableName, pRecordInfo->GetName() ) == 0, "FOREIGN KEY schema conversion found FK for table not matching search!\n" );
  1063. const char *pchParentTable;
  1064. DbgVerify( sqlRecord.BGetStringValue( 2, &pchParentTable ) );
  1065. const char *pchColName;
  1066. DbgVerify( sqlRecord.BGetStringValue( 3, &pchColName ) );
  1067. const char *pchParentColName;
  1068. DbgVerify( sqlRecord.BGetStringValue( 4, &pchParentColName ) );
  1069. const char *pchOnDelete;
  1070. DbgVerify( sqlRecord.BGetStringValue( 5, &pchOnDelete ) );
  1071. const char *pchOnUpdate;
  1072. DbgVerify( sqlRecord.BGetStringValue( 6, &pchOnUpdate ) );
  1073. // Is this more data for the FK we are already tracking? If so just append the column data,
  1074. // otherwise, assuming some data exists, add the key to the record info
  1075. if ( Q_strcmp( fkData.m_rgchName, pchFKName ) == 0 )
  1076. {
  1077. int iColRelation = fkData.m_VecColumnRelations.AddToTail();
  1078. FKColumnRelation_t &colRelation = fkData.m_VecColumnRelations[iColRelation];
  1079. Q_strncpy( colRelation.m_rgchCol, pchColName, Q_ARRAYSIZE( colRelation.m_rgchCol ) );
  1080. Q_strncpy( colRelation.m_rgchParentCol, pchParentColName, Q_ARRAYSIZE( colRelation.m_rgchParentCol ) );
  1081. }
  1082. else
  1083. {
  1084. if ( Q_strlen( fkData.m_rgchName ) )
  1085. {
  1086. pRecordInfo->AddFK( fkData );
  1087. }
  1088. // Do initial setup of the new key
  1089. Q_strncpy( fkData.m_rgchName, pchFKName, Q_ARRAYSIZE( fkData.m_rgchName ) );
  1090. Q_strncpy( fkData.m_rgchParentTableName, pchParentTable, Q_ARRAYSIZE( fkData.m_rgchParentTableName ) );
  1091. fkData.m_eOnDeleteAction = EForeignKeyActionFromName( pchOnDelete );
  1092. fkData.m_eOnUpdateAction = EForeignKeyActionFromName( pchOnUpdate );
  1093. int iColRelation = fkData.m_VecColumnRelations.AddToTail();
  1094. FKColumnRelation_t &colRelation = fkData.m_VecColumnRelations[iColRelation];
  1095. Q_strncpy( colRelation.m_rgchCol, pchColName, Q_ARRAYSIZE( colRelation.m_rgchCol ) );
  1096. Q_strncpy( colRelation.m_rgchParentCol, pchParentColName, Q_ARRAYSIZE( colRelation.m_rgchParentCol ) );
  1097. }
  1098. }
  1099. // Add the last key we were building data for
  1100. if ( Q_strlen( fkData.m_rgchName ) )
  1101. {
  1102. pRecordInfo->AddFK( fkData );
  1103. }
  1104. return SQL_SUCCESS;
  1105. }
  1106. //-----------------------------------------------------------------------------
  1107. // Purpose: Creates a table in the DB to match the recordinfo
  1108. // Input: pRecordInfo - CRecordInfo to create a table for
  1109. // Output: SQL return code.
  1110. //-----------------------------------------------------------------------------
  1111. SQLRETURN CJobUpdateSchema::YieldingCreateTable( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo )
  1112. {
  1113. CFmtStrMax sCmd;
  1114. SQLRETURN nRet = 0;
  1115. const char *pchName = pRecordInfo->GetName();
  1116. Assert( pchName && pchName[0] );
  1117. CSQLAccess sqlAccess( eSchemaCatalog );
  1118. // build the create table command
  1119. sCmd.sprintf( "CREATE TABLE %s (", pchName );
  1120. // add all the columns
  1121. for ( int iColumn = 0; iColumn < pRecordInfo->GetNumColumns(); iColumn++ )
  1122. {
  1123. char rgchType[k_cSmallBuff];
  1124. const CColumnInfo &columnInfo = pRecordInfo->GetColumnInfo( iColumn );
  1125. char *pchType = SQLTypeFromField( columnInfo, rgchType, Q_ARRAYSIZE( rgchType ) );
  1126. Assert( pchType[0] );
  1127. // add the name and type of this column
  1128. sCmd.AppendFormat( "%s %s", columnInfo.GetName(), pchType );
  1129. // add any constraints
  1130. AppendConstraints( pRecordInfo, &columnInfo, true, sCmd );
  1131. if ( iColumn < ( pRecordInfo->GetNumColumns() - 1 ) )
  1132. sCmd += ", ";
  1133. }
  1134. AppendTableConstraints( pRecordInfo, sCmd );
  1135. sCmd += ")";
  1136. // create the table
  1137. // metadata operations aren't transactional, so we'll set bAutoTransaction = true
  1138. EXIT_ON_BOOLSQL_FAILURE( sqlAccess.BYieldingExecute( FILE_AND_LINE, sCmd ) );
  1139. // add any indexes
  1140. for ( int n = 0; n < pRecordInfo->GetIndexFields( ).Count(); n++ )
  1141. {
  1142. const FieldSet_t& refSet = pRecordInfo->GetIndexFields( )[ n ];
  1143. // already added the PK index, so skip it
  1144. if ( n == pRecordInfo->GetPKIndex() )
  1145. continue;
  1146. // call YieldingAddIndex to do the work
  1147. nRet = YieldingAddIndex( eSchemaCatalog, pRecordInfo, refSet );
  1148. EXIT_ON_SQL_FAILURE( nRet );
  1149. }
  1150. Exit:
  1151. return nRet;
  1152. }
  1153. //-----------------------------------------------------------------------------
  1154. // Purpose: Adds an index of multiple columns to a table.
  1155. // Input: pSQLConnection - connection to use for command
  1156. // pRecordInfo - record info describing table
  1157. // refFields - description of index to add
  1158. // Output: SQL return code.
  1159. //-----------------------------------------------------------------------------
  1160. SQLRETURN CJobUpdateSchema::YieldingAddIndex( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const FieldSet_t &refFields )
  1161. {
  1162. CSQLAccess sqlAccess( eSchemaCatalog );
  1163. CUtlString sCmd = GetAddIndexSQL( pRecordInfo, refFields );
  1164. RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, sCmd ) );
  1165. return SQL_SUCCESS;
  1166. }
  1167. //-----------------------------------------------------------------------------
  1168. // Purpose: Depending on the conversion mode, either really perfom the
  1169. // conversion, or just log the SQL text so a human being can review
  1170. // it and do it later.
  1171. //-----------------------------------------------------------------------------
  1172. SQLRETURN CJobUpdateSchema::YieldingProcessUnsafeConversion( ESchemaCatalog eSchemaCatalog, const char *pszSQL, const char *pszComment )
  1173. {
  1174. if ( k_EConversionModeConvertIrreversible != m_eConversionMode )
  1175. {
  1176. if ( pszComment )
  1177. {
  1178. m_sRecommendedSQL.Append( "-- " );
  1179. m_sRecommendedSQL.Append( pszComment );
  1180. m_sRecommendedSQL.Append( "\n" );
  1181. }
  1182. m_sRecommendedSQL.Append( pszSQL );
  1183. m_sRecommendedSQL.Append("\nGO\n \n"); // that space is a kludge, because we are using V_splitlines, which will ignore consecutive seperators
  1184. return SQL_SUCCESS;
  1185. }
  1186. CSQLAccess sqlAccess( eSchemaCatalog );
  1187. RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, pszSQL ) );
  1188. return SQL_SUCCESS;
  1189. }
  1190. //-----------------------------------------------------------------------------
  1191. // Purpose: Add a SQL command to cleanup the schema that will actually destroy (supposedly unused) data.
  1192. //-----------------------------------------------------------------------------
  1193. void CJobUpdateSchema::AddDataDestroyingConversion( const char *pszSQL, const char *pszComment )
  1194. {
  1195. if ( pszComment )
  1196. {
  1197. m_sDataDestroyingCleanupSQL.Append( "-- " );
  1198. m_sDataDestroyingCleanupSQL.Append( pszComment );
  1199. m_sDataDestroyingCleanupSQL.Append( "\n" );
  1200. }
  1201. m_sDataDestroyingCleanupSQL.Append( pszSQL );
  1202. m_sDataDestroyingCleanupSQL.Append("\nGO\n \n"); // that space is a kludge, because we are using V_splitlines, which will ignore consecutive seperators
  1203. }
  1204. //-----------------------------------------------------------------------------
  1205. // Purpose: Adds a column to a table
  1206. // Input: pRecordInfo - record info describing table to add a column to
  1207. // pColumnInfo - column info describing column to add
  1208. // Output: SQL return code.
  1209. //-----------------------------------------------------------------------------
  1210. SQLRETURN CJobUpdateSchema::YieldingAlterTableAddColumn( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo )
  1211. {
  1212. CSQLAccess sqlAccess( eSchemaCatalog );
  1213. CFmtStrMax sCmd;
  1214. char rgchType[k_cSmallBuff];
  1215. const char *pchTableName = pRecordInfo->GetName();
  1216. Assert( pchTableName );
  1217. const char *pchColumnName = pColumnInfo->GetName();
  1218. Assert( pchColumnName );
  1219. DbgVerify( SQLTypeFromField( *pColumnInfo, rgchType, Q_ARRAYSIZE( rgchType ) )[0] );
  1220. // build the alter table command
  1221. sCmd.sprintf( "ALTER TABLE %s ADD %s %s", pchTableName, pchColumnName, rgchType );
  1222. // add any constraints
  1223. AppendConstraints( pRecordInfo, pColumnInfo, true, sCmd );
  1224. // !KLUDGE! This is guaranteed to fail if it has "not null" in it.
  1225. if ( V_strstr( sCmd, "NOT NULL" ) )
  1226. {
  1227. EmitError( SPEW_SQL, "Cannot add column %s to table %s with NOT NULL constraint\n", pchColumnName, pchTableName );
  1228. EmitInfo( SPEW_SQL, SPEW_ALWAYS, LOG_ALWAYS, "Here is the SQL that should be run to add the column:\n" );
  1229. EmitInfo( SPEW_SQL, SPEW_ALWAYS, LOG_ALWAYS, " %s\n", sCmd.String() );
  1230. return SQL_ERROR;
  1231. }
  1232. // execute the command.
  1233. RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, sCmd ) );
  1234. return SQL_SUCCESS;
  1235. }
  1236. //-----------------------------------------------------------------------------
  1237. // Purpose: Adds a constraint to an existing column
  1238. // Input: hDBC - SQL connection to execute on
  1239. // pRecordInfo - record info describing table
  1240. // pColumnInfo - column info describing column to add contraint for
  1241. // nColFlagConstraint - constraint to add
  1242. // Output: SQL return code.
  1243. //-----------------------------------------------------------------------------
  1244. SQLRETURN CJobUpdateSchema::YieldingAddConstraint( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfo, int nColFlagConstraint )
  1245. {
  1246. Assert( pRecordInfo );
  1247. Assert( pColumnInfo );
  1248. CFmtStrMax sCmd;
  1249. sCmd.sprintf( "ALTER TABLE App%u.%s ADD", GGCBase()->GetAppID(), pRecordInfo->GetName() );
  1250. Assert( !pColumnInfo->BIsClustered() );
  1251. AppendConstraint( pRecordInfo->GetName(), pColumnInfo->GetName(), nColFlagConstraint, true, pColumnInfo->BIsClustered(), sCmd, 0 );
  1252. sCmd.AppendFormat( " (%s)", pColumnInfo->GetName() );
  1253. return YieldingProcessUnsafeConversion( eSchemaCatalog, sCmd );
  1254. }
  1255. //-----------------------------------------------------------------------------
  1256. // Purpose: Changes type or length of existing column
  1257. // Input: hDBC - SQL connection to execute on
  1258. // pRecordInfo - record info describing table
  1259. // pColumnInfoDesired - column info describing new column type or length
  1260. // Output: SQL return code.
  1261. //-----------------------------------------------------------------------------
  1262. SQLRETURN CJobUpdateSchema::YieldingChangeColumnTypeOrLength( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfoDesired )
  1263. {
  1264. CUtlString sCmd = GetAlterColumnText( pRecordInfo, pColumnInfoDesired );
  1265. return YieldingProcessUnsafeConversion( eSchemaCatalog, sCmd );
  1266. }
  1267. //-----------------------------------------------------------------------------
  1268. // Purpose: Changes constraints/indexes on a column
  1269. // Input: hDBC - SQL connection to execute on
  1270. // pRecordInfo - record info describing table
  1271. // pColumnInfoActual - column info describing existing column properties
  1272. // pColumnInfoActual - column info describing desired new column properties
  1273. // Output: SQL return code.
  1274. //-----------------------------------------------------------------------------
  1275. SQLRETURN CJobUpdateSchema::YieldingChangeColumnProperties( ESchemaCatalog eSchemaCatalog, CRecordInfo *pRecordInfo, const CColumnInfo *pColumnInfoActual,
  1276. const CColumnInfo *pColumnInfoDesired )
  1277. {
  1278. CSQLAccess sqlAccess( eSchemaCatalog );
  1279. Assert( pRecordInfo );
  1280. Assert( pColumnInfoActual );
  1281. Assert( pColumnInfoDesired );
  1282. SQLRETURN nRet = SQL_SUCCESS;
  1283. pColumnInfoActual->ValidateColFlags();
  1284. pColumnInfoDesired->ValidateColFlags();
  1285. Assert( pColumnInfoActual->GetColFlags() != pColumnInfoDesired->GetColFlags() );
  1286. // all the operations we might have to perform; note we have have to drop one
  1287. // thing and add another
  1288. bool bAddExplicitIndex = false, bRemoveExplicitIndex = false;
  1289. bool bAddUniqueConstraint = false, bRemoveUniqueConstraint = false;
  1290. bool bAddPrimaryKeyConstraint = false, bRemovePrimaryKeyConstraint = false;
  1291. // determine which operations need to be performed
  1292. if ( !pColumnInfoActual->BIsPrimaryKey() && pColumnInfoDesired->BIsPrimaryKey() )
  1293. {
  1294. bAddPrimaryKeyConstraint = true;
  1295. }
  1296. else if ( pColumnInfoActual->BIsPrimaryKey() && !pColumnInfoDesired->BIsPrimaryKey() )
  1297. {
  1298. bRemovePrimaryKeyConstraint = true;
  1299. }
  1300. if ( !pColumnInfoActual->BIsExplicitlyUnique() && pColumnInfoDesired->BIsExplicitlyUnique() )
  1301. {
  1302. bAddUniqueConstraint = true;
  1303. }
  1304. else if ( pColumnInfoActual->BIsExplicitlyUnique() && !pColumnInfoDesired->BIsExplicitlyUnique() )
  1305. {
  1306. bRemoveUniqueConstraint = true;
  1307. }
  1308. if ( !pColumnInfoActual->BIsExplicitlyIndexed() && pColumnInfoDesired->BIsExplicitlyIndexed() )
  1309. {
  1310. bAddExplicitIndex = true;
  1311. }
  1312. else if ( pColumnInfoActual->BIsExplicitlyIndexed() && !pColumnInfoDesired->BIsExplicitlyIndexed() )
  1313. {
  1314. bRemoveExplicitIndex = true;
  1315. }
  1316. // sanity check
  1317. Assert( !( bAddUniqueConstraint && bAddPrimaryKeyConstraint ) ); // primary key constraint adds implicit uniqueness constraint; it's a bug if we decide to do both
  1318. Assert( !( bAddUniqueConstraint && bAddExplicitIndex ) ); // uniqueness constraint adds implicit index; it's a bug if we decide to do both
  1319. Assert( !( bAddPrimaryKeyConstraint && bAddExplicitIndex ) ); // primary key constraint adds implicit index; it's a bug if we decide to do both
  1320. // The index conversion stuff already handles dropping of unexpected indices
  1321. // and primary key constraints. I'm not even sure that this works or is used anymore.
  1322. if ( bAddUniqueConstraint )
  1323. {
  1324. nRet = YieldingAddConstraint( eSchemaCatalog, pRecordInfo, pColumnInfoActual, k_nColFlagUnique );
  1325. EXIT_ON_SQL_FAILURE( nRet );
  1326. }
  1327. Exit:
  1328. return nRet;
  1329. }
  1330. //-----------------------------------------------------------------------------
  1331. // Purpose: Creates specified trigger
  1332. // Input: pSQLConnection - SQL connection to execute on
  1333. // refTriggerInfo - trigger to be created
  1334. // Output: SQL return code.
  1335. //-----------------------------------------------------------------------------
  1336. SQLRETURN CJobUpdateSchema::YieldingCreateTrigger( ESchemaCatalog eSchemaCatalog, CTriggerInfo &refTriggerInfo )
  1337. {
  1338. char rgchCmd[ k_cchSQLStatementTextMax];
  1339. CSQLAccess sqlAccess( eSchemaCatalog );
  1340. // build the create command
  1341. Q_snprintf( rgchCmd, Q_ARRAYSIZE( rgchCmd ),
  1342. "CREATE TRIGGER [%s] ON [%s] "
  1343. "%s "
  1344. "AS BEGIN"
  1345. "%s\n"
  1346. "END",
  1347. refTriggerInfo.m_szTriggerName,
  1348. refTriggerInfo.m_szTriggerTableName,
  1349. refTriggerInfo.GetTriggerTypeString(),
  1350. refTriggerInfo.m_strText.Get() );
  1351. RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, rgchCmd ) );
  1352. return SQL_SUCCESS;
  1353. }
  1354. //-----------------------------------------------------------------------------
  1355. // Purpose: drops specified trigger
  1356. // Input: pSQLConnection - SQL connection to execute on
  1357. // refTriggerInfo - trigger to be dropped
  1358. // Output: SQL return code.
  1359. //-----------------------------------------------------------------------------
  1360. SQLRETURN CJobUpdateSchema::YieldingDropTrigger( ESchemaCatalog eSchemaCatalog, CTriggerInfo &refTriggerInfo )
  1361. {
  1362. char rgchCmd[ k_cchSQLStatementTextMax];
  1363. CSQLAccess sqlAccess( eSchemaCatalog );
  1364. // build the create command
  1365. Q_snprintf( rgchCmd, Q_ARRAYSIZE( rgchCmd ),
  1366. "DROP TRIGGER [%s];",
  1367. refTriggerInfo.m_szTriggerName );
  1368. RETURN_SQL_ERROR_ON_FALSE( sqlAccess.BYieldingExecute( FILE_AND_LINE, rgchCmd ) );
  1369. return SQL_SUCCESS;
  1370. }
  1371. //-----------------------------------------------------------------------------
  1372. // Purpose: Used for SQL/EGCSQLType conversion
  1373. //-----------------------------------------------------------------------------
  1374. struct SQLTypeMapping_t
  1375. {
  1376. const char *m_pchTypeName;
  1377. EGCSQLType m_eType;
  1378. };
  1379. static SQLTypeMapping_t g_rSQLTypeMapping[] =
  1380. {
  1381. { "tinyint", k_EGCSQLType_int8 },
  1382. { "smallint", k_EGCSQLType_int16 },
  1383. { "int", k_EGCSQLType_int32 },
  1384. { "bigint", k_EGCSQLType_int64 },
  1385. { "real", k_EGCSQLType_float },
  1386. { "float", k_EGCSQLType_double },
  1387. { "text", k_EGCSQLType_String },
  1388. { "ntext", k_EGCSQLType_String },
  1389. { "char", k_EGCSQLType_String },
  1390. { "varchar", k_EGCSQLType_String },
  1391. { "nchar", k_EGCSQLType_String },
  1392. { "nvarchar", k_EGCSQLType_String },
  1393. { "sysname", k_EGCSQLType_String },
  1394. { "varbinary", k_EGCSQLType_Blob },
  1395. { "image", k_EGCSQLType_Image },
  1396. };
  1397. static uint32 g_cSQLTypeMapping = Q_ARRAYSIZE( g_rSQLTypeMapping );
  1398. //-----------------------------------------------------------------------------
  1399. // Purpose: Returns the EGCSQLType for the specified SQL type name (or Invalid
  1400. // for unsupported types.)
  1401. //-----------------------------------------------------------------------------
  1402. EGCSQLType ETypeFromMSSQLDataType( const char *pchType )
  1403. {
  1404. for( uint32 unMapping = 0; unMapping < g_cSQLTypeMapping; unMapping++ )
  1405. {
  1406. if( !Q_stricmp( pchType, g_rSQLTypeMapping[ unMapping ].m_pchTypeName ) )
  1407. return g_rSQLTypeMapping[ unMapping ].m_eType;
  1408. }
  1409. return k_EGCSQLTypeInvalid;
  1410. }
  1411. //-----------------------------------------------------------------------------
  1412. // Purpose: Prepares the type map for use in schema upgrades
  1413. //-----------------------------------------------------------------------------
  1414. bool CJobUpdateSchema::YieldingBuildTypeMap( ESchemaCatalog eSchemaCatalog )
  1415. {
  1416. CSQLAccess sqlAccess( eSchemaCatalog );
  1417. if( !sqlAccess.BYieldingExecute( FILE_AND_LINE, "select name, system_type_id from sys.types" ) )
  1418. return false;
  1419. FOR_EACH_SQL_RESULT( sqlAccess, 0, typeRecord )
  1420. {
  1421. CFmtStr1024 sTypeName;
  1422. byte nTypeID;
  1423. DbgVerify( typeRecord.BGetStringValue( 0, &sTypeName ) );
  1424. DbgVerify( typeRecord.BGetByteValue( 1, &nTypeID ) );
  1425. EGCSQLType eType = ETypeFromMSSQLDataType( sTypeName );
  1426. if( eType != k_EGCSQLTypeInvalid )
  1427. m_mapSQLTypeToEType.Insert( nTypeID, eType );
  1428. }
  1429. return true;
  1430. }
  1431. //-----------------------------------------------------------------------------
  1432. // Purpose: Returns the EGCSQLType for the specified type ID out of the database
  1433. //-----------------------------------------------------------------------------
  1434. EGCSQLType CJobUpdateSchema::GetEGCSQLTypeForMSSQLType( int nType )
  1435. {
  1436. int nIndex = m_mapSQLTypeToEType.Find( nType );
  1437. if( m_mapSQLTypeToEType.IsValidIndex( nIndex ) )
  1438. return m_mapSQLTypeToEType[ nIndex ];
  1439. else
  1440. return k_EGCSQLTypeInvalid;
  1441. }
  1442. } // namespace GCSDK