public override void Perform(SchemaChanges changes, IOutput output) { // This step covers both unique keys (including primary keys) and non-unique indexes foreach (var desired in UniqueIndexType.AllFrom(changes.Desired)) { if (changes.Current.Contains(desired)) { continue; } var sql = changes.SchemaDriver.GetCreateUniqueConstraintSql(desired.ParentName, desired.State.IsPrimaryKey, desired.Name, desired.State.IndexState.FieldNames, desired.State.IndexState.CustomState); changes.Put(sql, desired); } foreach (var desired in NonUniqueIndexType.AllFrom(changes.Desired)) { if (changes.Current.Contains(desired)) { continue; } var sql = changes.SchemaDriver.GetCreateIndexSql(desired.ParentName, desired.Name, desired.State.FieldNames, desired.State.CustomState); changes.Put(sql, desired); } }
public override void Perform(SchemaChanges changes, IOutput output) { foreach (var desired in QueryType.AllFrom(changes.Desired)) { var current = changes.Current.Get(desired); if (current != null && QueryType.IsEqual(current.State, desired.State, changes.DbDriver)) { continue; } if (desired.State != null) { var body = desired.State.Body; var parameters = desired.State.Parameters; string createSql; var functionState = desired.State as QueryType.FunctionState; if (functionState != null) { createSql = changes.SchemaDriver.GetCreateFunctionSql(desired.Name, parameters, functionState.ReturnType, body); } else { createSql = changes.SchemaDriver.GetCreateProcSql(desired.Name, parameters, body); } changes.Put(createSql, desired); } else { if (current == null) { changes.Put(null, desired); } } } // Restore all pre-upgrade-hooks corresponding to procedures that have been successfully updated. foreach (var upgradeHook in PreUpgradeHookType.AllFrom(changes.Desired)) { var desiredProc = QueryType.GetFrom(changes.Desired, upgradeHook.ParentIdentifier); var currentProc = changes.Current.Get(desiredProc); if (currentProc != null && QueryType.IsEqual(currentProc.State, desiredProc.State, changes.DbDriver)) { changes.Put(null, upgradeHook); } } }
public override void Perform(SchemaChanges changes, IOutput output) { foreach (var current in FieldType.AllFrom(changes.Current)) { var desired = FieldType.GetDesired(changes, current); if (desired == null) { continue; } // If we can't alter sequenced keys then no point trying! if (!changes.SchemaDriver.IsAlterSequencedFieldSupported && (current.State.IsSequencedPkey || desired.State.IsSequencedPkey)) { continue; } // If the fields are the same by datatype and sequenced-ness - or the sequenced-ness is irrelevant at the field level - // then there's nothing to change if (changes.SchemaDriver.DbDriver.StringEquals(current.State.DataType, desired.State.DataType) && (current.State.IsSequencedPkey == desired.State.IsSequencedPkey || !changes.SchemaDriver.IsSequencedPartOfFieldDeclaration)) { continue; } // Try the alter statement. If it doesn't work, the field will be dropped and added. var altered = current.With(s => s.WithTypeChange(desired.State.DataType, desired.State.IsSequencedPkey, desired.State.SequenceName)); var alterSql = changes.SchemaDriver.GetAlterFieldTypeSql(altered.ParentName, altered.Name, altered.State.DataType, altered.State.IsNullable, altered.State.IsSequencedPkey, altered.State.SequenceName); changes.Put(alterSql, ErrorResponse.Ignore, altered); } }
public override void Perform(SchemaChanges changes, IOutput output) { foreach (var current in FieldType.AllFrom(changes.Current)) { // Don't need to set null if it's already null! if (current.State.IsNullable) { continue; } // Don't need to set null if nullability is already correct var desired = FieldType.GetDesired(changes, current); if (desired != null && !desired.State.IsNullable) { continue; } // Sequenced pkey fields can't be made nullable if (current.State.IsSequencedPkey && !changes.SchemaDriver.IsAlterSequencedFieldSupported) { continue; } // The field is either being dropped (desired == null) or set to nullable // Either way we set it to null now // We know to specify "false, null" for sequenced key field because the desired state can't be nullable if it's sequenced var setNullSql = changes.SchemaDriver.GetSetFieldNullSql(current.ParentName, current.Name, current.State.DataType, false, null); changes.Put(setNullSql, current.With(state => state.WithNullable(true))); } }
public override void Perform(SchemaChanges changes, IOutput output) { var fieldsToBeAdded = from field in FieldType.AllFrom(changes.Desired) where !changes.Current.Contains(field) orderby field.State.OrdinalPosition select field; // FIXME It's a little weird to be ordering by OrdinalPosition independent of table, because it means we jump from table to table adding all // fifth columns, then all sixth columns, etc, but it doesn't hurt anything. foreach (var desired in fieldsToBeAdded) { // We create all fields as initially nullable... var fieldToCreate = desired.With(s => s.WithNullable(true)); // ... except for sequenced fields on databases that don't support altering sequenced fields to notnull later. if (desired.State.IsSequencedPkey && !changes.SchemaDriver.IsAlterSequencedFieldSupported) { fieldToCreate = desired; } // Add the field changes.Put(changes.SchemaDriver.GetAddFieldSql(desired.ParentName, desired.Name, desired.State.DataType, desired.State.IsSequencedPkey, desired.State.SequenceName), fieldToCreate); } }
public override void Perform(SchemaChanges changes, IOutput output) { // This isn't really anything to do with pre-upgrade-hooks but there's no other good place to give this warning! foreach (var query in QueryType.AllFrom(changes.Current)) { if (query.State != null && query.State.Body.StartsWith("<FAIL>")) { output.Warning(query.State.Body.Substring(6)); } } foreach (var hook in PreUpgradeHookType.AllFrom(changes.Current)) { var proc = QueryType.GetFrom(changes.Current, hook.ParentIdentifier); if (proc.State == null || proc.State is QueryType.FunctionState || proc.State.Parameters.Any()) { output.Warning("Cannot execute pre-upgrade hook " + proc.Name + ": stored procedure does not exist or has parameters."); } changes.Put(changes.SchemaDriver.GetExecuteProcSql(hook.ParentName)); if (changes.Desired.Get(hook) == null) { changes.Remove(null, hook); } } }
public override void Perform(SchemaChanges changes, IOutput output) { var completion = CompletionType.AllFrom(changes.Desired).Single(); if (!changes.Current.Contains(completion)) { changes.Put(null, completion); } }
public override void Perform(SchemaChanges changes, IOutput output) { foreach (var desired in SequenceType.AllFrom(changes.Desired)) { if (changes.Current.Contains(desired)) { continue; } changes.Put(changes.SchemaDriver.GetCreateSequenceSql(desired.Name), desired); } }
public override void Perform(SchemaChanges changes, IOutput output) { foreach (var desired in FkeyType.AllFrom(changes.Desired)) { if (changes.Current.Contains(desired)) { continue; } changes.Put(changes.SchemaDriver.GetAddFkeySql(desired.ParentName, desired.State.ToTableName, desired.Name, desired.State.IsCascadeDelete, desired.State.Joins), desired); } }
public override void Perform(SchemaChanges changes, IOutput output) { foreach (var desired in TriggerType.AllFrom(changes.Desired)) { if (changes.Current.Contains(desired)) { continue; } var table = desired.ParentIdentifier; changes.Put(changes.SchemaDriver.GetCreateTriggerSql(table.Name, desired.Name, desired.State.Timing, desired.State.Events, desired.State.Body), desired); } }
public override void Perform(SchemaChanges changes, IOutput output) { foreach (var current in QueryType.AllFrom(changes.Current)) { // Queries that are neither stored proc or stored function in the current schema don't need to be dropped! if (current.State == null) { continue; } var desired = changes.Desired.Get(current); if (desired != null) { if (changes.SchemaDriver.IsCreateOrReplaceProcSupported) { // If CREATE OR REPLACE PROCEDURE is supported in the current database, there's no need to drop it unless // it's changed from function to procedure or vice versa. if (QueryType.IsTypeEqual(current.State, desired.State)) { continue; } } else { // Even if CREATE OR REPLACE PROCEDURE is not supported, we don't need to drop it if it's // not changed at all. if (QueryType.IsEqual(current.State, desired.State, changes.DbDriver)) { continue; } } } var dropSql = current.State is QueryType.FunctionState ? changes.SchemaDriver.GetDropFunctionSql(current.Name) : changes.SchemaDriver.GetDropProcSql(current.Name); if (desired != null) { // The query isn't going away entirely so we don't want to lose its associated before statements changes.Put(dropSql, QueryType.CreateUnstored(current.Name)); } else { changes.Remove(dropSql, current); } } }
public override void Perform(SchemaChanges changes, IOutput output) { foreach (var current in FieldType.AllFrom(changes.Current)) { var desired = FieldType.GetDesired(changes, current); // At this point we are only dropping changed fields, not fields that are being // removed entirely, which we keep around til later to allow them to be used by // before statements for migration. So we only want to drop fields that conflict // with fields in the eventual state of the table (including the conflict // that would occur if two identity fields existed in the same table). if (desired == null && !isSequencedPkeyToBeDropped(changes, current)) { continue; } // If the type and sequenced-ness are unchanged then we don't need to do anything here. if (desired != null && FieldType.IsTypeEqual(current.State, desired.State, changes.SchemaDriver.IsSequencedPartOfFieldDeclaration)) { continue; } // Fields being dropped here need to be replaced later, so failing to drop them has to be fatal regardless of FieldDropBehavior if (!changes.AllowDropWithPossibleDataLoss(current, DropBehavior.Drop)) { continue; } // If we are about to drop the last field from a table, add a temporary field so that the drop will be allowed if (FieldType.ChildrenFrom(changes.Current, current.ParentIdentifier).Count() == 1) { var placeholder = FieldType.Create(current.ParentIdentifier, "nrdo_placeholder_field", 2, "int", true); changes.Put(changes.SchemaDriver.GetAddFieldSql(placeholder.ParentName, placeholder.Name, placeholder.State.DataType, false, null), placeholder); if (changes.HasFailed) { continue; } } // Drop the field changes.Remove(changes.SchemaDriver.GetDropFieldSql(current.ParentName, current.Name), current); } }
public override void Perform(SchemaChanges changes, IOutput output) { var pendingReorders = PendingReorderTableType.AllFrom(changes.Current); foreach (var desired in TableType.AllFrom(changes.Desired)) { if (changes.Current.Contains(desired)) { continue; } var fields = FieldType.ChildrenFrom(changes.Desired, desired.Identifier); var fieldCreation = from field in fields orderby field.State.OrdinalPosition select new FieldCreation(field.Name, field.State.DataType, field.State.IsNullable, field.State.IsSequencedPkey, field.State.SequenceName); changes.Put(changes.SchemaDriver.GetCreateTableSql(desired.Name, fieldCreation), new ObjectState[] { desired }.Concat(fields)); } }
public override void Perform(SchemaChanges changes, IOutput output) { var allDesired = ViewType.AllFrom(changes.Desired) .DependencySort((first, second) => second.State.Body.IndexOf(first.Name.Substring(first.Name.LastIndexOf('.') + 1), StringComparison.OrdinalIgnoreCase) >= 0) .ToImmutableList(); foreach (var desired in allDesired) { var current = changes.Current.Get(desired); if (current != null && ViewType.IsEqual(current.State, desired.State, changes.DbDriver)) { continue; } var body = desired.State.Body; var createSql = changes.SchemaDriver.GetCreateViewSql(desired.Name, body); changes.Put(createSql, desired); } }
public override void Perform(SchemaChanges changes, IOutput output) { foreach (var desired in FieldType.AllFrom(changes.Desired)) { // We don't need to set it notnull if it's supposed to be nullable if (desired.State.IsNullable) { continue; } var current = changes.Current.Get(desired); // This shouldn't happen because fields have been created by this point if (current == null) { changes.Fail("Trying to set " + desired + " not null but it hasn't been added yet!"); continue; } // This shouldn't happen either because field types have been altered by this point if (!FieldType.IsTypeEqual(desired.State, current.State, changes.SchemaDriver.IsSequencedPartOfFieldDeclaration)) { changes.Fail("Trying to set " + desired + " not null but its type is still wrong!"); continue; } // We don't need to set it notnull if it already is if (!current.State.IsNullable) { continue; } // Update the field to notnull changes.Put(changes.SchemaDriver.GetSetFieldNotNullSql(desired.ParentName, desired.Name, desired.State.DataType, desired.State.IsSequencedPkey, desired.State.SequenceName), desired); } }
public override void Perform(SchemaChanges changes, IOutput output) { using (var block = output.ProgressBlock()) { var desired = OldVersionCacheMigrationType.AllFrom(changes.Desired).Single(); // Because this is guaranteed to be last in the prerequisite run, we know the storage tables are fully created and we can start using them. changes.SetStorageAvailable(); var storage = changes.Storage; // If the migration has already run, OR the storage tables already contain Complete (implying they've already been populated by a full run // or migration) then we don't run it again. if (storage.ContainsRoot(desired.Identifier) || storage.ContainsRoot(CompletionType.Create().Identifier)) { return; } var chunks = block.GetChunks(10, 40).ToList(); // If we can't find a cache then there's no data to upgrade with. var nrdoCache = desired.State.FindOldVersionNrdoCache(chunks[0].Start()); if (nrdoCache == null) { return; } // This line serves no purpose except to cause changes to notice that this step did something and so needs to be included in the output. changes.Put(null); var total = nrdoCache.Entries.Count; using (var steps = chunks[1].ProgressBlock(total)) { output.Message("Migration: Processing " + total + " tables and queries..."); foreach (var entry in nrdoCache.Entries) { var cacheLine = SmeltFile.Parse(entry.Content).Lines.Single(); var obsoleteVersionSpec = cacheLine.GetString(0); var type = cacheLine.GetString(1); var name = cacheLine.GetString(2); if (obsoleteVersionSpec != "") { throw new ApplicationException("Unexpected value " + obsoleteVersionSpec + " found in obsolete version spec space"); } if (name != entry.Name) { throw new ApplicationException("Name in cache entry does not match file name: " + name + " vs " + entry.Name); } if (type == "tcache") { output.Verbose("Migration: Table " + name + "..."); // Table cache structure is either //[] tcache name existing { beforestatement beforestatement ... ; }; // OR //[] tcache name { // fieldname sqltype nullable|notnull identity? ; // ... //} { // pk|uk|ix indexname { fieldname; fieldname; ... } ; // ... //} { // fkeyname desttable { fromfield tofield; fromfield tofield; ... } cascade? ; // ... //} { // sequencename ; // triggername ; // beforestatement beforestatement ... ; //}; if (cacheLine.Words[3] is SmeltString) { if (cacheLine.GetString(3) != "existing") { throw new ApplicationException("Unexpected word '" + cacheLine.GetString(3) + "' in " + name); } var beforeStatements = cacheLine.GetBlock(4).Lines.Single(); if (beforeStatements.Words.Any()) { throw new ApplicationException("Table " + name + " was 'existing' but has before statements, which is no longer supported"); } // In the old world "existing" tables could have before statements, but we can't do that any more } else { var table = TableType.Identifier(name); storage.PutRoot(table); foreach (var fieldLine in cacheLine.GetBlock(3).Lines) { storage.PutSub(table, FieldType.Identifier(fieldLine.GetString(0))); // We don't care about type, nullability or identity any more because we can get all that from the database schema directly } foreach (var ixLine in cacheLine.GetBlock(4).Lines) { var ixTypeStr = ixLine.GetString(0); ObjectType ixType; switch (ixTypeStr) { case "ix": ixType = NonUniqueIndexType.Instance; break; case "pk": case "uk": ixType = UniqueIndexType.Instance; break; default: throw new ApplicationException("Unexpected index type " + ixTypeStr + " in " + name); } storage.PutSub(table, ixType.Identifier(ixLine.GetString(1))); } foreach (var fkLine in cacheLine.GetBlock(5).Lines) { storage.PutSub(table, FkeyType.Identifier(fkLine.GetString(0))); } var others = cacheLine.GetBlock(6); var seqInfo = others.Lines[0]; if (seqInfo.Words.Count > 1 && changes.SchemaDriver.IsSequenceUsed) { storage.PutSub(table, SequenceType.Identifier("dbo." + seqInfo.GetString(1))); } if (seqInfo.Words.Count > 2 && changes.SchemaDriver.IsTriggerUsedForSequence) { storage.PutSub(table, TriggerType.Identifier("dbo." + seqInfo.GetString(2))); } if (others.Lines.Count > 1) { foreach (var beforeStatement in others.Lines[1].Words.Cast <SmeltString>()) { storage.PutSub(table, BeforeStatementType.Identifier(beforeStatement.Text)); } } } } else { output.Verbose("Migration: Query " + name); // Query cache file structure is // [] spcache name [] { beforestatement beforestatement ... ; }; // OR // [] spcache|sfcache|spcache-preupgrade name [sql] { beforestatement beforestatement ... ; } var isPreUpgradeHook = false; switch (type) { case "spcache": case "sfcache": break; case "spcache-preupgrade": isPreUpgradeHook = true; break; default: throw new ApplicationException("Unknown type of cache entry '" + type + "' in " + name); } var query = QueryType.Identifier(name); storage.PutRoot(query); if (isPreUpgradeHook) { storage.PutSub(query, PreUpgradeHookType.Create(query).Identifier); } foreach (var beforeStatement in cacheLine.GetBlock(4).Lines.Single().Words.Cast <SmeltString>()) { storage.PutSub(query, BeforeStatementType.Identifier(beforeStatement.Text)); } } steps.Step++; } if (nrdoCache.IsComplete) { storage.PutRoot(CompletionType.Create().Identifier); } storage.PutRoot(desired.Identifier); output.Message("Migration: Complete."); } } }
private void createTempTables(SchemaChanges changes, IOutput output) { // For any tables with order-sensitivity and fields in different order between current and desired (ignoring fields that only exist in current), // Create a new table "_nrdo_reorder" using "select (columns in correct order followed by columns to be dropped) from original table into newname" // Put the table, the fields (with nullability, datatype and identityness as current but in new order) and a PendingReorderTable into current // For any tables that need their fields reordered, create a copy of the table with the fields in the right order. foreach (var origTable in TableType.AllFrom(changes.Current)) { // Tables that don't exist in desired state are to be dropped, they certainly don't need to be reordered! if (!changes.Desired.Contains(origTable)) { continue; } // Figure out whether a reorder is needed if (!FieldType.IsFieldReorderNeeded(changes, origTable.Identifier)) { continue; } // Put the fields in order. // At this point there may be extra fields that are to be dropped but haven't been yet. They need to stick around until // it's time to drop them, because before statements may rely on them. But we put those ones last (in the order they're currently in). var fieldsInOrder = from field in FieldType.ChildrenFrom(changes.Current, origTable.Identifier) let desired = changes.Desired.Get(field) orderby desired != null ? desired.State.OrdinalPosition : int.MaxValue, field.State.OrdinalPosition select field; // Construct the state representation that will be used for the new table. // It inherits all before statements from the original table so that when it's renamed they'll still be there. // FIXME: This hardcodes the assumption that CreateTableAsSelect preserves field type, nullability and identity-ness, which may or may not be true on other DBs // FIXME: any other aspects of field state that we add support for (Default values, check constraints...) need to get dropped here. var tempTable = TableType.Create(origTable.Name + "_nrdo_reorder"); var pendingReorder = PendingReorderTableType.Create(tempTable.Identifier, origTable.Name); var tempTableFields = from fi in fieldsInOrder.Select((field, index) => new { field, index }) select fi.field .WithParent(tempTable.Identifier) .With(s => s.WithOrdinalPosition(fi.index)); var tempTableBefores = from before in BeforeStatementType.ChildrenFrom(changes.Current, origTable.Identifier) select before.WithParent(tempTable.Identifier); // Gather all temporary objects into one list to put them into the database together var tempTableObjects = new ObjectState[] { tempTable, pendingReorder } .Concat(tempTableFields) .Concat(tempTableBefores); var sql = changes.SchemaDriver.GetCreateTableAsSelectSql(tempTable.Name, string.Join(", ", from field in fieldsInOrder select changes.DbDriver.QuoteIdentifier(field.Name)), changes.DbDriver.QuoteSchemaIdentifier(origTable.Name)); changes.Put(sql, tempTableObjects); } }