Example #1
0
        public void BasicSyncTest()
        {
            // Arrange

            StringBuilder trace;

            var sqlMock = new Mock <ISql>();

            sqlMock.Setup(m => m.GetAllTables())
            .Returns(() => new List <(string fullName, int objectId)>
            {
                ("dbo.Dogs", 1),
                ("dbo.Cats", 2)
            });
            sqlMock.Setup(m => m.GetStoredProcsThatRefEntity(It.IsAny <string>()))
            .Returns(() => new List <(string fullName, int objectId)>
            {
                ("dbo.GetDogsById", 3),
                ("dbo.DogsSave", 4),
                ("dbo.DogsUpdate", 5)
            });
            sqlMock.Setup(m => m.GetColumnNames(It.IsAny <int>()))
            .Returns(() => new string[]
            {
                "id",
                "name",
                "thing"
            });
            sqlMock.Setup(m => m.GetTableObjectId(It.IsAny <string>()))
            .Returns(() => new int?(1));

            var sync = new HarpSynchronizer(sqlMock.Object, new StringBuilder());

            var harpFile = new HarpFile();

            harpFile.Entities.Add("Dogs", new Entity
            {
                Name       = "Dogs",
                Table      = string.Empty, // blank
                Properties = new Dictionary <string, string>
                {
                    { "ID", string.Empty }, // blank
                    { "Name", string.Empty } // blank
                },
                Behaviors = new Dictionary <string, string>
                {
                    { "Get by id", string.Empty } // blank
                }
            });

            // Act
            var results = sync.Synchronize(harpFile);

            // Assert
            Assert.IsTrue(harpFile.IsFullyMapped);
        }
Example #2
0
        static void Main(string[] args)
        {
            var trace = new StringBuilder();

            try
            {
                var harpFilePath = Path.Combine(Environment.CurrentDirectory, "DataLayer.harp");
                Console.WriteLine(harpFilePath);

                // read
                var inYaml = File.ReadAllText(harpFilePath);
                var result = HarpFile.FromYaml(inYaml);
                Console.WriteLine($"Parse: {result.code}");

                if (result.code != HarpFile.ParseResult.OK)
                {
                    return;
                }

                // synchronize
                var sync    = new HarpSynchronizer(new Sql(), trace);
                var sResult = sync.Synchronize(result.file);
                Console.WriteLine($"Sync: {sResult.Code}");

                if (sResult.Code != HarpSynchronizer.SynchronizeResultCode.OK)
                {
                    return;
                }

                if (sResult.WasUpdated)
                {
                    var outYaml = result.file.GenerateYaml();
                    File.WriteAllText(harpFilePath, outYaml);
                }
            }
            finally
            {
                Console.WriteLine($"----- TRACE -----");
                Console.WriteLine(trace.ToString());
                Console.WriteLine($"-----------------");

                Console.ReadLine();
            }
        }
Example #3
0
        HarpFile generateSampleHarpFile()
        {
            var file = new HarpFile
            {
                Config = new HarpFile.HarpConfig
                {
                    OutputDirectory     = "asdf",
                    SqlConnectionString = "asdf;"
                },
                Entities = new Dictionary <string, Entity>
                {
                    { "Dogs", new Entity
                      {
                          Name       = "Thing",
                          Table      = "dbo.Things",
                          Properties = new Dictionary <string, string>
                          {
                              { "PropA", "prop_a" }
                          },
                          Behaviors = new Dictionary <string, string>
                          {
                              { "Do thing A", "dbo.DoThingA" }
                          }
                      } },
                    { "Cats", new Entity
                      {
                          Name       = "Thing2",
                          Table      = "dbo.Things2",
                          Properties = new Dictionary <string, string>
                          {
                              { "PropA2", "prop_a2" }
                          },
                          Behaviors = new Dictionary <string, string>
                          {
                              { "Do thing A2", "dbo.DoThingA2" }
                          }
                      } },
                }
            };

            return(file);
        }
Example #4
0
        public SyncResults Synchronize(HarpFile mapFile)
        {
            var results = new SyncResults();

            var isValid = sql.ConfigureAndTest(mapFile.Config.SqlConnectionString);

            if (!isValid)
            {
                results.Code = SynchronizeResultCode.InvalidSqlConnectionString;
                return(results);
            }

            try
            {
                foreach (var entry in mapFile.Entities)
                {
                    int?tableId;
                    var entity = entry.Value;

                    // Table
                    if (string.IsNullOrWhiteSpace(entity.Table))
                    {
                        // Try match to an existing table
                        var tables = sql.GetAllTables();

                        var matches = tables.Where(
                            t => StringMatcher.IsAFuzzyMatch(
                                getObjectName(t.fullName), entity.Name
                                ));

                        if (matches.Count() > 1)
                        {
                            // Error: too many matches, can't decide.
                            results.Code = SynchronizeResultCode.EntityNameMatchesTooManyTables;
                            return(results);
                        }
                        else if (matches.Count() == 0)
                        {
                            // Error: no matches
                            results.Code = SynchronizeResultCode.EntityNameMatchesNoTables;
                            return(results);
                        }

                        var tableMatch = matches.Single();

                        // Capture table name on entity
                        entity.Table       = tableMatch.fullName;
                        results.WasUpdated = true;
                        trace.AppendLine($"Table match: {entity.Table}");
                    }

                    tableId = sql.GetTableObjectId(entity.Table);
                    if (tableId == null)
                    {
                        results.Code = SynchronizeResultCode.MatchedTableDoesNotExist;
                        return(results);
                    }

                    var columnsForEntity = sql.GetColumnNames(tableId.Value);

                    // Columns, unmapped
                    if (entity.Properties.Any(p => string.IsNullOrWhiteSpace(p.Value)))
                    {
                        for (int x = 0; x < entity.Properties.Count; x++)
                        {
                            var propEntry = entity.Properties.ElementAt(x);

                            // Excludes any already mapped
                            if (propEntry.Value != null)
                            {
                                continue;
                            }

                            var prop = propEntry.Value;

                            var availableMatches = columnsForEntity.Where(c => !entity.Properties.Any(p => p.Value == c));
                            var matches          = availableMatches.Where(c => StringMatcher.IsAFuzzyMatch(c, propEntry.Key));

                            if (matches.Any())
                            {
                                var match = matches.First();
                                entity.Properties[propEntry.Key] = match;
                                results.WasUpdated = true;
                                trace.AppendLine($"Property match: {propEntry.Key} = {match}");
                            }
                            else
                            {
                                // Error: No matches for column
                                results.Code = SynchronizeResultCode.ColumnMatchingError;
                                return(results);
                            }
                        }

                        // Track the unmapped
                        var unmappedCols = columnsForEntity.Except(entity.Properties.Select(p => p.Value));
                        results.UnmappedTableColumns.AddRange(unmappedCols);
                    }
                    else if (entity.Properties.Count() == 0)
                    {
                        // Autopopulate property list with all available columns
                        foreach (var column in columnsForEntity)
                        {
                            var humanName = column.Humanize();
                            entity.AddProperty(humanName, column);

                            results.WasUpdated = true;
                            trace.AppendLine($"Property match: {humanName} = {column}");
                        }
                    }

                    // Columns, mapped
                    var allExistingMappedColumnsExist = entity.Properties.All(p => columnsForEntity.Any(c => string.Equals(c, p.Value, StringComparison.OrdinalIgnoreCase)));
                    if (!allExistingMappedColumnsExist)
                    {
                        results.Code = SynchronizeResultCode.MatchedColumnDoesNotExist;
                        return(results);
                    }


                    var procsForEntity = sql.GetStoredProcsThatRefEntity(entity.Table);

                    // Behaviours, unmapped
                    if (entity.Behaviors.Any(b => string.IsNullOrWhiteSpace(b.Value)))
                    {
                        for (var x = 0; x < entity.Behaviors.Count; x++)
                        {
                            var behaveEntry = entity.Behaviors.ElementAt(x);

                            // ignore already mapped behaviors
                            if (behaveEntry.Value != null)
                            {
                                continue;
                            }

                            var behavior = behaveEntry.Value;

                            // Excludes any already mapped
                            var availableMatches = procsForEntity.Where(p => !entity.Behaviors.Any(b => b.Value == p.fullName));

                            var matches = availableMatches.Select(p => new ProcName(p.fullName))
                                          .OrderByDescending(p =>
                            {
                                // Remove entity name from proc's name, to make it more likely to get matched
                                // since there's less character changes required for a total match.
                                // e.g. 1 = get dogs by id = get by id (becomes the most closest match)
                                //      2 = get cats by id = get cats by id
                                var processed = p.HumanizedName.Replace((" " + entity.Name + " "), string.Empty);
                                return(stringCompareScore(behaveEntry.Key, processed));
                            }).ToArray();
                            if (matches.Any())
                            {
                                var match = matches.First().FullName;
                                entity.Behaviors[behaveEntry.Key] = match;
                                results.WasUpdated = true;
                                trace.AppendLine($"Behavior match: {behaveEntry.Key} = {match}");
                            }
                        }

                        // Track the unmapped
                        var unmappedCols = procsForEntity.Select(p => p.fullName)
                                           .Except(entity.Behaviors.Select(b => b.Value));
                        results.UnmappedStoredProcs.AddRange(unmappedCols);

                        // Ensure all stored procs that have been matched
                        // (either from this process or manually) exist.
                        var allExistingMappedProcsExist = entity.Behaviors.All(b => procsForEntity.Any(pr => string.Equals(pr.fullName, b.Value, StringComparison.OrdinalIgnoreCase)));
                        if (!allExistingMappedProcsExist)
                        {
                            results.Code = SynchronizeResultCode.MatchedProcDoesNotExist;
                            return(results);
                        }
                    }
                    else if (entity.Behaviors.Count() == 0)
                    {
                        // Autopopulate behavior list with all available procs
                        foreach (var proc in procsForEntity)
                        {
                            var humanName = removeWord(entity.Name, getObjectName(proc.fullName).Humanize()).Humanize();

                            if (string.IsNullOrWhiteSpace(humanName))
                            {
                                results.Code = SynchronizeResultCode.ProcMatchingError;
                                return(results);
                            }

                            entity.AddBehavior(humanName, proc.fullName);
                            results.WasUpdated = true;
                            trace.AppendLine($"Behavior match: {humanName} = {proc}");
                        }
                    }
                }

                // Validate
                foreach (var entity in mapFile.Entities.Select(e => e.Value))
                {
                    if (!entity.Behaviors.Any())
                    {
                        results.Code = SynchronizeResultCode.AtLeastOneBehaviorRequired;
                        return(results);
                    }
                }

                results.Code = mapFile.Entities.All(e => e.Value.IsFullyMapped)
                    ? SynchronizeResultCode.OK
                    : SynchronizeResultCode.NotAllMapped;
            }
            catch (Exception ex)
            {
                trace.AppendLine($"{ex.GetType().Name}: {ex.ToString()}");
                results.Code = SynchronizeResultCode.UnknownError;
            }

            return(results);
        }
Example #5
0
        static void Main(string[] args)
        {
            if (args == null || args.Count() < 2)
            {
                logAndTerminate("Both parameters must be specified: {command} {file}");
                return;
            }

            var cmd  = args[0];
            var file = args[1];

            if (string.IsNullOrWhiteSpace(cmd) || string.IsNullOrWhiteSpace(file))
            {
                logAndTerminate("Both parameters must be specified: {command} {file}");
                return;
            }

            var actionSync            = "sync";
            var actionSyncAndGenerate = "syncgen";
            var supportedActions      = new[]
            {
                actionSync,
                actionSyncAndGenerate
            };

            if (!supportedActions.Any(a => cmd.ToLower() == a.ToLower()))
            {
                logAndTerminate("Command not recognized.");
                return;
            }

            if (!File.Exists(file))
            {
                file = Path.Combine(Environment.CurrentDirectory, file);

                if (!File.Exists(file))
                {
                    logAndTerminate("Could not find the specified file");
                    return;
                }
            }

            if (file.EndsWith(".harp"))
            {
                logAndTerminate("File is not a .harp file");
                return;
            }

            var verboseOutput = (args.Length > 2);

            var sb   = new StringBuilder();
            var sync = new HarpSynchronizer(new Sql(), sb);

            // read file
            var harpYaml = File.ReadAllText(file);

            // load
            var loadResult = HarpFile.FromYaml(harpYaml);

            if (loadResult.code != HarpFile.ParseResult.OK)
            {
                logAndTerminate($"Could not load harp file: {loadResult.code}");
                return;
            }

            var syncSuccessful = false;

            var syncResult = sync.Synchronize(loadResult.file);

            if (syncResult.Code == HarpSynchronizer.SynchronizeResultCode.OK)
            {
                syncSuccessful = true;

                log("Sync was successful");

                if (syncResult.WasUpdated)
                {
                    log("Harp file was updated");
                }

                if (syncResult.UnmappedStoredProcs.Any())
                {
                    log("There are unmapped stored procedures");
                }

                if (syncResult.UnmappedTableColumns.Any())
                {
                    log("There are unmapped table columns");
                }
            }
            else
            {
                log($"Sync was NOT successful: {syncResult.Code}");

                if (syncResult.UnmappedStoredProcs.Any())
                {
                    log("There are also some unmapped stored procedures");
                }

                if (syncResult.UnmappedTableColumns.Any())
                {
                    log("There are also some unmapped table columns");
                }

                Environment.Exit(1);
            }

            if (cmd.ToLower() == actionSyncAndGenerate && syncSuccessful)
            {
                log("Not implemented");
                Environment.Exit(1);

                //var gen = new HarpGenerator();
                //gen.Generate(HarpFile,)
            }
        }
Example #6
0
 public GenerateResults Generate(HarpFile mapFile, out StringBuilder trace)
 {
     trace = new StringBuilder();
     return(new GenerateResults(GenerateResultCode.UnknownError));
 }