Пример #1
0
        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);
            }
        }
Пример #2
0
        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);
                }
            }
        }
Пример #3
0
        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);
            }
        }
Пример #4
0
        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)));
            }
        }
Пример #5
0
        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);
            }
        }
Пример #6
0
        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);
                }
            }
        }
Пример #7
0
        public override void Perform(SchemaChanges changes, IOutput output)
        {
            var completion = CompletionType.AllFrom(changes.Desired).Single();

            if (!changes.Current.Contains(completion))
            {
                changes.Put(null, completion);
            }
        }
Пример #8
0
        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);
            }
        }
Пример #9
0
        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);
            }
        }
Пример #10
0
        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);
            }
        }
Пример #11
0
        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);
                }
            }
        }
Пример #12
0
        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);
            }
        }
Пример #13
0
        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));
            }
        }
Пример #14
0
        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);
            }
        }
Пример #15
0
        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);
            }
        }
Пример #16
0
        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.");
                }
            }
        }
Пример #17
0
        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);
            }
        }