/// <summary> Create a new <code>PatchTable</code>. /// /// </summary> /// <param name="migrationContext">the migration configuration and connection source /// </param> /// <param name="connection">the database connection to use; this will NOT be closed /// </param> //UPGRADE_NOTE: There are other database providers or managers under System.Data namespace which can be used optionally to better fit the application requirements. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1208'" public PatchTable(IAdoMigrationContext migrationContext) { this.context = migrationContext; if (context.DatabaseType == null) { throw new System.ArgumentException("The ADO.NET database type is required"); } }
public TestAdoMigrationLauncher(IAdoMigrationContext context) : base(context) { // does nothing }
/// <summary> /// Parses the SQL (DML/DDL) to execute and returns a list of individual statements. For database /// types that support multiple statements in a single <code>Statement.execute</code> call, /// this method will return a one-element <code>List</code> containing the entire SQL /// file. /// </summary> /// <param name="context"> /// the <code>IMigrationContext</code>, to figure out db type and if it can handle multiple /// statements at once /// </param> /// <returns> /// a list of SQL (DML/DDL) statements to execute /// </returns> public virtual IList<String> GetSqlStatements(IAdoMigrationContext context) { IList<String> statements = new List<String>(); if (context.DatabaseType.MultipleStatementsSupported) { statements.Add(sql); return statements; } StringBuilder currentStatement = new StringBuilder(); bool inQuotedString = false; bool inComment = false; char[] sqlChars = sql.ToCharArray(); for (int i = 0; i < sqlChars.Length; i++) { if (sqlChars[i] == '\n') { inComment = false; } if (!inComment) { switch (sqlChars[i]) { case '-': case '/': if (!inQuotedString && i + 1 < sqlChars.Length && sqlChars[i + 1] == sqlChars[i]) { inComment = true; } else { currentStatement.Append(sqlChars[i]); } break; case '\'': inQuotedString = !inQuotedString; currentStatement.Append(sqlChars[i]); break; case ';': if (!inQuotedString) { // If we're in a stored procedure, just keep rolling if (context.DatabaseType.getDatabaseType().Equals("oracle") && (currentStatement.ToString().Trim().ToLower().StartsWith("begin") || currentStatement.ToString().Trim().ToLower().StartsWith("create or replace method") || currentStatement.ToString().Trim().ToLower().StartsWith("create or replace function") || currentStatement.ToString().Trim().ToLower().StartsWith("create or replace procedure") || currentStatement.ToString().Trim().ToLower().StartsWith("create or replace package"))) { currentStatement.Append(sqlChars[i]); } else { statements.Add(currentStatement.ToString().Trim()); currentStatement = new StringBuilder(); } } else { currentStatement.Append(sqlChars[i]); } break; default: currentStatement.Append(sqlChars[i]); break; } } } if (currentStatement.ToString().Trim().Length > 0) { statements.Add(currentStatement.ToString().Trim()); } return statements; }
/// <seealso cref="AdoMigrationLauncher.AdoMigrationLauncher(IAdoMigrationContext)"/> public DistributedAdoMigrationLauncher(IAdoMigrationContext context) : base(context) { }
/// <summary> /// Lock the patch store. This is done safely, such that we safely handle the case where /// other migration launchers are patching at the same time. /// </summary> /// <param name="context"> /// the context to lock the store in /// </param> /// <exception cref="MigrationException"> /// if the reading or setting lock state fails /// </exception> private void LockPatchStore(IAdoMigrationContext context) { // Patch locks ensure that only one system sharing a patch store will patch // it at the same time. bool lockObtained = false; while (!lockObtained) { WaitForFreeLock(context); IPatchInfoStore piStore = contexts[context]; int patchLevel = piStore.PatchLevel; try { piStore.LockPatchStore(); lockObtained = true; } catch (Exception) { // this happens when someone woke up at the same time, // raced us to the lock and won. We re-sleep and try again. } } }
/// <summary> /// Pauses until the patch lock become available. /// </summary> /// <param name="context"> /// the context related to the store /// </param> /// <exception cref="MigrationException"> /// if an unrecoverable error occurs /// </exception> private void WaitForFreeLock(IAdoMigrationContext context) { IPatchInfoStore piStore = contexts[context]; while (piStore.PatchStoreLocked) { log.Info("Waiting for migration lock for system \"" + context.SystemName + "\""); try { Thread.Sleep(new TimeSpan(TimeSpan.TicksPerMillisecond * LockPollMillis)); } catch (ThreadInterruptedException e) { log.Error("Received ThreadInterruptedException while waiting for patch lock", e); } } }
/// <summary> /// Performs the application migration process in one go. /// </summary> /// <param name="context"> /// the context to run the patches in /// </param> /// <returns> /// the number of patches applied /// </returns> /// <exception cref="DbException"> /// if an unrecoverable database error occurs while working with the patches table /// </exception> /// <exception cref="MigrationException"> /// if an unrecoverable error occurs during the migration /// </exception> protected virtual int DoMigrations(IAdoMigrationContext context) { IPatchInfoStore patchTable = CreatePatchStore(context); LockPatchStore(context); // Now apply the patches int executedPatchCount = 0; try { int patchLevel = patchTable.PatchLevel; executedPatchCount = migrationProcess.DoMigrations(patchLevel, context); } catch (MigrationException me) { // If there was any kind of error, we don't want to eat it, but we do // want to unlock the patch store. So do that, then re-throw. patchTable.UnlockPatchStore(); throw me; } // Do any post-patch tasks try { migrationProcess.DoPostPatchMigrations(context); return executedPatchCount; } finally { try { patchTable.UnlockPatchStore(); } catch (MigrationException e) { log.Error("Error unlocking patch table: ", e); } } }
/// <summary> /// Create a patch table object for use in migrations. /// </summary> /// <param name="context"> /// the context to create the store in /// </param> /// <returns> /// a <code>PatchInfoStore</code> object for use in accessing patch state information /// </returns> /// <exception cref="MigrationException"> /// if unable to create the store /// </exception> protected virtual IPatchInfoStore CreatePatchStore(IAdoMigrationContext context) { //IPatchInfoStore piStore = new PatchTable(context); // Make sure the table is created before claiming it exists by returning IPatchInfoStore piStore = contexts[context]; int patchLevel = piStore.PatchLevel; return piStore; }
/// <summary> /// Get the patch level from the database. /// </summary> /// <param name="ctx"> /// the migration context to get the patch level for /// </param> /// <returns> /// the current database patch level /// </returns> /// <exception cref="MigrationException"> /// if there is a database connection error, or the patch level can't be determined /// </exception> public virtual int GetDatabasePatchLevel(IAdoMigrationContext ctx) { return contexts[ctx].PatchLevel; }
/// <summary> /// Adds an <code>IAdoMigrationContext</code> used for the migrations. /// </summary> /// <param name="context"> /// the <code>IAdoMigrationContext</code> used for the migrations /// </param> public virtual void AddContext(IAdoMigrationContext context) { IPatchInfoStore patchTable = new PatchTable(context); log.Debug("Adding context " + context + " with patch table " + patchTable + " in launcher " + this); contexts.Add(context, patchTable); }
/// <summary> /// Create a new <code>AdoMigrationLauncher</code>. /// </summary> /// <param name="context"> /// the <code>IAdoMigrationContext</code> to use /// </param> public AdoMigrationLauncher(IAdoMigrationContext context) : this() { AddContext(context); }