Пример #1
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.");
                }
            }
        }
Пример #2
0
        public IEnumerable <ObjectState> GetDesiredState(SchemaConnection connection, IOutput output)
        {
            var schemaDriver  = connection.SchemaDriver;
            var sqlTranslator = new SqlTranslator(schemaDriver, null);

            HashSet <string> catalogs = new HashSet <string>(schemaDriver.DbDriver.DbStringComparer);

            foreach (var nrdoTable in codeBase.AllTables)
            {
                if (nrdoTable.ExistingName != null)
                {
                    if (nrdoTable.BeforeStatements.Any())
                    {
                        throw new ApplicationException("Table " + nrdoTable.Name + " is 'existing' but has before statements, which is no longer supported");
                    }
                    continue;
                }

                output.Verbose("Loading table " + nrdoTable.DatabaseName + " from " + nrdoTable.Type.Name);
                var table = TableType.Create(schemaName + "." + nrdoTable.DatabaseName);
                yield return(table);

                if (preserveColumnOrder)
                {
                    yield return(FieldOrderSensitivityType.Create(table.Identifier));
                }

                string sequencedPkeyFieldName = null;
                string sequenceName           = null;
                if (nrdoTable.IsPkeySequenced)
                {
                    sequencedPkeyFieldName = nrdoTable.PkeyGet.Fields.Single().Name;
                    var sequenceNameBase = getNameHashString(nrdoTable.Name) + "_" + sequencedPkeyFieldName;

                    if (schemaDriver.IsSequenceUsed)
                    {
                        sequenceName = "sq_" + sequenceNameBase;
                        yield return(SequenceType.Create(schemaName + "." + sequenceName));
                    }

                    if (schemaDriver.IsTriggerUsedForSequence)
                    {
                        var triggerName = "sqt_" + sequenceNameBase;
                        yield return(TriggerType.Create(table.Identifier, triggerName, schemaDriver.GetSequencedFieldTriggerTiming(), TriggerEvents.Insert,
                                                        schemaDriver.GetSequencedFieldTriggerBody(table.Name, sequencedPkeyFieldName, sequenceName)));
                    }
                }

                var fieldIndex = 0;
                foreach (var nrdoField in nrdoTable.Fields)
                {
                    if (nrdoField.Name == sequencedPkeyFieldName)
                    {
                        yield return(FieldType.CreateSequencedPkey(table.Identifier, nrdoField.Name, fieldIndex++, nrdoField.DbType, nrdoField.IsNullable, sequenceName));
                    }
                    else
                    {
                        yield return(FieldType.Create(table.Identifier, nrdoField.Name, fieldIndex++, nrdoField.DbType, nrdoField.IsNullable));
                    }
                }

                foreach (var nrdoIndex in nrdoTable.Indexes)
                {
                    if (nrdoIndex.IsPrimary)
                    {
                        yield return(UniqueIndexType.CreatePrimaryKey(table.Identifier, nrdoIndex.Name,
                                                                      from field in nrdoIndex.Fields select FieldType.Identifier(field), schemaDriver.DefaultPrimaryKeyCustomState));
                    }
                    else if (nrdoIndex.IsUnique)
                    {
                        yield return(UniqueIndexType.CreateUnique(table.Identifier, nrdoIndex.Name,
                                                                  from field in nrdoIndex.Fields select FieldType.Identifier(field), schemaDriver.DefaultUniqueConstraintCustomState));
                    }
                    else
                    {
                        yield return(NonUniqueIndexType.Create(table.Identifier, nrdoIndex.Name,
                                                               from field in nrdoIndex.Fields select FieldType.Identifier(field), schemaDriver.DefaultIndexCustomState));
                    }
                }

                if (nrdoTable.FulltextFields.Any() && connection.SchemaDriver.IsFulltextSupported(connection))
                {
                    var catalog = nrdoTable.FulltextCatalog ?? "NrdoFulltext";
                    if (!catalogs.Contains(catalog))
                    {
                        yield return(FulltextCatalogType.Create(catalog));

                        catalogs.Add(catalog);
                    }

                    var pkey = nrdoTable.Indexes.Where(index => index.IsPrimary).Single();
                    yield return(FulltextIndexType.Create(table.Identifier, FulltextCatalogType.Identifier(catalog), pkey.Name, nrdoTable.FulltextFields));
                }

                foreach (var nrdoFkey in nrdoTable.References)
                {
                    if (nrdoFkey.IsFkey)
                    {
                        yield return(FkeyType.Create(table.Identifier, TableType.Identifier(schemaName + "." + nrdoFkey.TargetTable.DatabaseName),
                                                     nrdoFkey.FkeyName, nrdoFkey.IsCascadingFkey,
                                                     from fjoin in nrdoFkey.Joins select new FieldPair(fjoin.From.Name, fjoin.To.Name)));
                    }
                }

                var beforeIndex = 0;
                foreach (var nrdoBefore in nrdoTable.BeforeStatements)
                {
                    yield return(BeforeStatementType.Create(table.Identifier, "0" + nrdoTable.Name, beforeIndex++, nrdoBefore.Name, nrdoBefore.Step,
                                                            nrdoBefore.Initial, nrdoBefore.Upgrade, sqlTranslator.Translate(nrdoBefore.Statement)));
                }

                foreach (var oldName in nrdoTable.RenamedFrom)
                {
                    var oldDbName = schemaName + "." + oldName.Replace(':', '_');
                    yield return(TableRenameType.Create(oldDbName, table.Name));
                }
            }

            foreach (var nrdoQuery in codeBase.AllQueries)
            {
                output.Verbose("Loading query " + nrdoQuery.DatabaseName + " from " + nrdoQuery.Type.Name);

                var queryName = schemaName + "." + nrdoQuery.DatabaseName;

                var parameters = from param in nrdoQuery.Params select new ProcParam(param.Name, param.DbType);

                if (nrdoQuery.IsStoredProc)
                {
                    yield return(QueryType.CreateProc(queryName, parameters, sqlTranslator.Translate(nrdoQuery.Sql)));

                    if (nrdoQuery.IsPreUpgradeHook)
                    {
                        yield return(PreUpgradeHookType.Create(queryName));
                    }
                }
                else if (nrdoQuery.IsStoredFunction)
                {
                    var returnType = nrdoQuery.Results.Single().DbType;
                    yield return(QueryType.CreateFunction(queryName, parameters, returnType, sqlTranslator.Translate(nrdoQuery.Sql)));
                }
                else
                {
                    yield return(QueryType.CreateUnstored(queryName));
                }

                var beforeIndex = 0;
                foreach (var nrdoBefore in nrdoQuery.BeforeStatements)
                {
                    yield return(BeforeStatementType.Create(QueryType.Identifier(queryName), "1" + nrdoQuery.Name, beforeIndex++, nrdoBefore.Name, nrdoBefore.Step,
                                                            nrdoBefore.Initial, nrdoBefore.Upgrade, sqlTranslator.Translate(nrdoBefore.Statement)));
                }
            }
        }