//============================================================================== /// <summary> /// Returns a list of nodes (a subset of 'candidates') that can be safely removed in a way /// that no other remaining node depends (directly or indirectly) on removed nodes. /// </summary> /// <param name="candidates">Nodes to be removed.</param> /// <param name="dependencies">Dependency: Item2 depends on Item1.</param> public static List <T> RemovableLeaves <T>(IEnumerable <T> candidates, IEnumerable <Tuple <T, T> > dependencies) { CsUtility.Materialize(ref candidates); CsUtility.Materialize(ref dependencies); dependencies = dependencies.Distinct().ToArray(); var all = candidates.Union(dependencies.Select(d => d.Item1)).Union(dependencies.Select(d => d.Item2)).ToArray(); var numberOfDependents = all.ToDictionary(node => node, node => 0); foreach (var relation in dependencies) { numberOfDependents[relation.Item1]++; } var dependsOn = all.ToDictionary(node => node, node => new List <T>()); foreach (var relation in dependencies) { dependsOn[relation.Item2].Add(relation.Item1); } var removed = new HashSet <T>(); var candidatesIndex = new HashSet <T>(candidates); foreach (var cand in candidates) { if (numberOfDependents[cand] == 0 && !removed.Contains(cand)) { RemoveLeaf(cand, removed, numberOfDependents, dependsOn, candidatesIndex); } } return(removed.ToList()); }
public static List <SqlTransactionBatch> GroupByTransaction(IEnumerable <string> sqlScripts) { sqlScripts = sqlScripts.Where(s => !string.IsNullOrWhiteSpace(s.Replace(SqlUtility.NoTransactionTag, ""))); var batches = CsUtility.GroupItemsKeepOrdering(sqlScripts, SqlUtility.ScriptSupportsTransaction); return(batches.Select(batch => new SqlTransactionBatch(batch.Items) { UseTransacion = batch.Key }).ToList()); }
/// <summary> /// 1. Splits the scripts by the SQL batch delimiter ("GO", for Microsoft SQL Server). See <see cref="SqlUtility.SplitBatches(string)"/>. /// 2. Detects and applies the transaction usage tag. See <see cref="SqlUtility.NoTransactionTag"/> and <see cref="SqlUtility.ScriptSupportsTransaction(string)"/>. /// 3. Reports progress (Info level) after each minute. /// 4. Prefixes each SQL script with a comment containing the script's name. /// </summary> public void Execute(IEnumerable <SqlScript> sqlScripts) { var scriptParts = sqlScripts .SelectMany(script => script.IsBatch ? SqlUtility.SplitBatches(script.Sql).Select(scriptPart => new SqlScript { Name = script.Name, Sql = scriptPart, IsBatch = false }) : new[] { script }) .Where(script => !string.IsNullOrWhiteSpace(script.Sql)); var sqlBatches = CsUtility.GroupItemsKeepOrdering(scriptParts, script => SqlUtility.ScriptSupportsTransaction(script.Sql)) .Select(group => new { UseTransaction = group.Key, // The empty NoTransactionTag script is used by the DatabaseGenerator to split transactions. // This is why there scrips are removed *after* grouping Scripts = group.Items.Where(s => !string.Equals(s.Sql, SqlUtility.NoTransactionTag, StringComparison.Ordinal)).ToList() }) .Where(group => group.Scripts.Count() > 0) // Cleanup after removing the empty NoTransactionTag scripts. .ToList(); _logger.Trace(() => "SqlBatches: " + string.Join(", ", sqlBatches.Select(b => $"{(b.UseTransaction ? "tran" : "notran")} {b.Scripts.Count()}"))); int totalCount = sqlBatches.Sum(b => b.Scripts.Count); int previousBatchesCount = 0; var startTime = DateTime.Now; var lastReportTime = startTime; foreach (var sqlBatch in sqlBatches) { Action <int> reportProgress = currentBatchCount => { var now = DateTime.Now; int executedCount = previousBatchesCount + currentBatchCount + 1; if (now.Subtract(lastReportTime).TotalMilliseconds > _reportDelayMs && executedCount < totalCount) // No need to report progress if the work is done. { double estimatedTotalMs = now.Subtract(startTime).TotalMilliseconds / executedCount * totalCount; var remainingTime = startTime.AddMilliseconds(estimatedTotalMs).Subtract(now); _logger.Info($"Executed {executedCount} / {totalCount} SQL scripts. {(remainingTime.TotalMinutes).ToString("f2")} minutes remaining."); lastReportTime = now; } }; var scriptsWithName = sqlBatch.Scripts .Select(script => string.IsNullOrEmpty(script.Name) ? script.Sql : "--Name: " + script.Name.Replace("\r", " ").Replace("\n", " ") + "\r\n" + script.Sql); _sqlExecuter.ExecuteSql(scriptsWithName, sqlBatch.UseTransaction, null, reportProgress); previousBatchesCount += sqlBatch.Scripts.Count; } }
public static string LimitIdentifierLength(string name) { const int MaxLength = 30; if (name.Length > MaxLength) { var hashErasedPart = CsUtility.GetStableHashCode(name.Substring(MaxLength - 9)).ToString("X").PadLeft(8, '0'); return(name.Substring(0, MaxLength - 9) + "_" + hashErasedPart); } return(name); }
public static string GuidToString(Guid guid) { if (DatabaseLanguageIsMsSql.Value) { return(guid.ToString().ToUpper()); } else if (DatabaseLanguageIsOracle.Value) { return(CsUtility.ByteArrayToHex(guid.ToByteArray())); } else { throw new FrameworkException(UnsupportedLanguageError); } }
public static Guid StringToGuid(string guid) { if (DatabaseLanguageIsMsSql.Value) { return(Guid.Parse(guid)); } else if (DatabaseLanguageIsOracle.Value) { return(new Guid(CsUtility.HexToByteArray(guid))); } else { throw new FrameworkException(UnsupportedLanguageError); } }
/// <summary> /// Throws an exception if 'name' is not a valid SQL database object name. /// Function returns given argument so it can be used as fluent interface. /// In some cases the function may change the identifier (limit identifier length to 30 on Oracle database, e.g.). /// </summary> public static string Identifier(string name) { string error = CsUtility.GetIdentifierError(name); if (error != null) { throw new FrameworkException("Invalid database object name: " + error); } if (DatabaseLanguageIsOracle.Value) { name = OracleSqlUtility.LimitIdentifierLength(name); } return(name); }
private ValueOrError <List <string> > ListCachedFiles(string fileGroupName, byte[] sourceHash, IEnumerable <string> requestedExtensions) { var cachedFilesByExt = ListCachedFiles() .GetValueOrDefault(fileGroupName) ?.ToDictionary(file => Path.GetExtension(file)); if (cachedFilesByExt == null) { return(ValueOrError.CreateError("File group not cached.")); } string cachedHashFile = cachedFilesByExt.GetValueOrDefault(".hash"); if (cachedHashFile == null) { return(ValueOrError.CreateError("Missing hash file.")); } byte[] cachedHash = CsUtility.HexToByteArray(File.ReadAllText(cachedHashFile, Encoding.Default)); if (cachedHash == null || cachedHash.Length == 0) { return(ValueOrError.CreateError("Missing hash value.")); } if (!sourceHash.SequenceEqual(cachedHash)) { return(ValueOrError.CreateError("Different hash value.")); } var requestedFiles = new List <string>(requestedExtensions.Count()); foreach (var extension in requestedExtensions) { string cachedFile = cachedFilesByExt.GetValueOrDefault(extension); if (cachedFile == null) { return(ValueOrError.CreateError($"Extension '{extension}' not in cache.")); } requestedFiles.Add(cachedFile); } return(requestedFiles); }
/// <summary> /// Copies the files from cache only if all of the extensions are found in the cache, /// and if the sourceContent matches the corresponding sourceFile in the cache. /// </summary> /// <param name="sampleSourceFile">Any file from the cached file group, extension will be ignored.</param> /// <returns>List of the restored files, if the files are copied from the cache, null otherwise.</returns> public List <string> RestoreCachedFiles(string sampleSourceFile, byte[] sourceHash, string targetFolder, IEnumerable <string> copyExtensions) { CsUtility.Materialize(ref copyExtensions); var cachedFiles = ListCachedFiles(Path.GetFileNameWithoutExtension(sampleSourceFile), sourceHash, copyExtensions); List <string> targetFiles; string report; if (!cachedFiles.IsError) { targetFiles = cachedFiles.Value.Select(source => _filesUtility.SafeCopyFileToFolder(source, targetFolder)).ToList(); report = "Restored " + string.Join(", ", copyExtensions) + "."; } else { targetFiles = null; report = cachedFiles.Error; } _logger.Trace(() => "RestoreCachedFiles for " + Path.GetFileName(sampleSourceFile) + ": " + report); return(targetFiles); }
/// <param name="sampleSourceFile">Any file from the cached file group, extension will be ignored.</param> public void SaveHash(string sampleSourceFile, byte[] hash) { string hashFile = Path.GetFullPath(Path.ChangeExtension(sampleSourceFile, ".hash")); File.WriteAllText(hashFile, CsUtility.ByteArrayToHex(hash), Encoding.ASCII); }
/// <param name="sampleSourceFile">Any file from the cached file group, extension will be ignored.</param> public byte[] LoadHash(string sampleSourceFile) { string hashFile = Path.GetFullPath(Path.ChangeExtension(sampleSourceFile, ".hash")); return(CsUtility.HexToByteArray(File.ReadAllText(hashFile, Encoding.Default))); }