Add project files.
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Base.Procs;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Sarand.Net.Standard.Dbas.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sarand.Net.Standard.Dbas.Proc
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaDbProcess_CompareDatabaseSchemas : SacsProcess
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Configuration.
|
||||
public new class Config : SacsProcess.Config
|
||||
{
|
||||
public Config (SacsDatabase sourceDatabase, SacsDatabase targetDatabase)
|
||||
{
|
||||
SourceDatabase = sourceDatabase;
|
||||
TargetDatabase = targetDatabase;
|
||||
}
|
||||
public SacsDatabase SourceDatabase { get; }
|
||||
public SacsDatabase TargetDatabase { get; }
|
||||
public bool HideIdenticalObjects { get; set; }
|
||||
public bool IgnoreNullable { get; set; }
|
||||
public bool IgnorePosition { get; set; }
|
||||
public bool IgnoreSimilarCharTypes { get; set; }
|
||||
public bool IgnoreSimilarNumericTypes { get; set; }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
public interface IProcessNotifier : ISacsProcessNotifier
|
||||
{
|
||||
void OnSchemaComparisonItemAdded (SaDbProcess_CompareDatabaseSchemas proc);
|
||||
}
|
||||
public class SchemaComparisonItem
|
||||
{
|
||||
internal SchemaComparisonItem (String tableName, String columnName, int columnPosition, String indexName, String comment)
|
||||
{
|
||||
Id = ++SeedId;
|
||||
TableName = tableName;
|
||||
ColumnName = columnName;
|
||||
ColumnPosition = columnPosition;
|
||||
IndexName = indexName;
|
||||
Comment = comment;
|
||||
}
|
||||
public static int SeedId { get; private set; }
|
||||
public int Id { get; }
|
||||
public String TableName { get; }
|
||||
public String ColumnName { get; }
|
||||
public int ColumnPosition { get; }
|
||||
public String IndexName { get; }
|
||||
public String Comment { get; }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Config config;
|
||||
private IProcessNotifier processNotifier = null;
|
||||
private readonly List<SchemaComparisonItem> schemaComparisonItems = new List<SchemaComparisonItem>();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SaDbProcess_CompareDatabaseSchemas (Config config) : base(config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public override string ProcessDescription { get; } = "Database Schema Comparison";
|
||||
public new IProcessNotifier ProcessNotifier
|
||||
{
|
||||
get { return processNotifier; }
|
||||
set
|
||||
{
|
||||
processNotifier = value;
|
||||
base.ProcessNotifier = value;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// S c h e m a D i f f e r e n c e s
|
||||
//-------------------------------------------------------------------
|
||||
public List<SchemaComparisonItem> GetSchemaComparisonItems (int minId = 0)
|
||||
{
|
||||
return new List<SchemaComparisonItem>(schemaComparisonItems.FindAll(item => item.Id >= minId));
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// P r o c e s s i n g
|
||||
//-------------------------------------------------------------------
|
||||
protected override bool Run ()
|
||||
{
|
||||
schemaComparisonItems.Clear();
|
||||
if (config.SourceDatabase == null) return SetProcessError("Source database must be supplied.");
|
||||
|
||||
CompareTables();
|
||||
return OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private void CompareTables ()
|
||||
{
|
||||
List<String> sourceTables = config.SourceDatabase.GetTableNames().OrderBy(item => item).ToList();
|
||||
List<String> targetTables = config.TargetDatabase.GetTableNames().OrderBy(item => item).ToList();
|
||||
for (int sourceTableIdx = 0, targetTableIdx = 0; sourceTableIdx <= sourceTables.Count && targetTableIdx <= targetTables.Count;)
|
||||
{
|
||||
String sourceTableName = (sourceTableIdx < sourceTables.Count ? sourceTables[sourceTableIdx] : "");
|
||||
String targetTableName = (targetTableIdx < targetTables.Count ? targetTables[targetTableIdx] : "");
|
||||
if (StringUtil.IsEmpty(targetTableName) || targetTableName.CompareTo(sourceTableName) > 0)
|
||||
{
|
||||
// Target table missing.
|
||||
AddSchemaComparisonItem(sourceTableName, "", 0, "", "Table is missing");
|
||||
sourceTableIdx++;
|
||||
continue;
|
||||
}
|
||||
if (StringUtil.IsEmpty(sourceTableName) || sourceTableName.CompareTo(targetTableName) > 0)
|
||||
{
|
||||
// Source table missing.
|
||||
AddSchemaComparisonItem(targetTableName, "", 0, "", "Table is not required");
|
||||
targetTableIdx++;
|
||||
continue;
|
||||
}
|
||||
SetProgressInfo("Processing table: " + sourceTableName);
|
||||
// Table is present in both databases.
|
||||
if (!config.HideIdenticalObjects) AddSchemaComparisonItem(sourceTableName, "", 0, "", "Table OK");
|
||||
#if DEBUG_X
|
||||
if (sourceTableName.EqualsCI("address"))
|
||||
Debugger.Break();
|
||||
#endif
|
||||
// Retrieve table definition from each database.
|
||||
DbDfnTable sourceTableDfn = config.SourceDatabase.GetTableDfn (sourceTableName);
|
||||
DbDfnTable targetTableDfn = config.TargetDatabase.GetTableDfn (targetTableName);
|
||||
// Compare the columns and indices.
|
||||
CompareTableColumns(sourceTableDfn, targetTableDfn);
|
||||
CompareTableIndices(sourceTableDfn, targetTableDfn);
|
||||
// Move to next table in BOTH lists.
|
||||
sourceTableIdx++;
|
||||
targetTableIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private bool CompareTableColumns (DbDfnTable sourceTableDfn, DbDfnTable targetTableDfn)
|
||||
{
|
||||
String tableName = sourceTableDfn.Name;
|
||||
#if DEBUG_X
|
||||
if (tableName.EqualsCI("company"))
|
||||
Debugger.Break();
|
||||
#endif
|
||||
|
||||
// Columns:
|
||||
List<DbDfnColumn> sourceColumns = sourceTableDfn.Columns;
|
||||
List<DbDfnColumn> targetColumns = targetTableDfn.Columns;
|
||||
List<String> sourceColumnNames = sourceColumns.GetNames();
|
||||
List<String> targetColumnNames = targetColumns.GetNames();
|
||||
sourceColumnNames.Sort();
|
||||
targetColumnNames.Sort();
|
||||
for (int sourceColumnIdx = 0, targetColumnIdx = 0; sourceColumnIdx < sourceColumns.Count && targetColumnIdx < targetColumns.Count;)
|
||||
{
|
||||
// Retrieve the Column names:
|
||||
int iPosition = 0;
|
||||
String sourceColumnName = sourceColumnIdx < sourceColumnNames.Count() ? sourceColumnNames[sourceColumnIdx] : "";
|
||||
String targetColumnName = targetColumnIdx < targetColumnNames.Count() ? targetColumnNames[targetColumnIdx] : "";
|
||||
// Retrieve Column definition from each database.
|
||||
DbDfnColumn sourceColumn = sourceColumns.GetColumnByName (sourceColumnName);
|
||||
if (sourceColumn != null) iPosition = sourceColumn.Pos;
|
||||
DbDfnColumn targetColumn = targetColumns.GetColumnByName(targetColumnName);
|
||||
// Compare Columns.
|
||||
if (StringUtil.IsEmpty(targetColumnName) || targetColumnName.CompareTo(sourceColumnName) > 0)
|
||||
{
|
||||
// Column is only present in SOURCE list:
|
||||
AddSchemaComparisonItem(tableName, sourceColumnName, iPosition, "", "Column is missing");
|
||||
// Move to next Column in SOURCE list:
|
||||
sourceColumnIdx++;
|
||||
continue;
|
||||
}
|
||||
else if (StringUtil.IsEmpty(sourceColumnName) || sourceColumnName.CompareTo(targetColumnName) > 0)
|
||||
{
|
||||
// Column is only present in TARGET list:
|
||||
AddSchemaComparisonItem(tableName, targetColumnName, iPosition, "", "Column is not required");
|
||||
// Move to next Column in TARGET list:
|
||||
targetColumnIdx++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Column is present in both databases.
|
||||
List<String> lDifferences = new List<string>();
|
||||
// Verify column attributes:
|
||||
if (!config.IgnorePosition)
|
||||
if (sourceColumn.Pos != targetColumn.Pos) lDifferences.Add("pos");
|
||||
if (!sourceColumn.Name.EqualsCI(targetColumn.Name)) lDifferences.Add("name");
|
||||
if (ColumnNativeDataTypesDiffer(sourceColumn, targetColumn)) lDifferences.Add((String)"native type (" + sourceColumn.DataTypeNameNative + ":" + targetColumn.DataTypeNameNative + ")");
|
||||
else if (ColumnDataTypesDiffer(sourceColumn, targetColumn)) lDifferences.Add("data type (" + sourceColumn.DataTypeName + ":" + targetColumn.DataTypeName + ")");
|
||||
if (ColumnSizesDiffer(sourceColumn, targetColumn)) lDifferences.Add((String)"size (" + sourceColumn.Size + ":" + targetColumn.Size + ")");
|
||||
if (ColumnDecimalsDiffer(sourceColumn, targetColumn)) lDifferences.Add("decimals (" + sourceColumn.DecimalDigits + "," + targetColumn.DecimalDigits + ")");
|
||||
if (!config.IgnoreNullable)
|
||||
{
|
||||
if (sourceColumn.Nullable != targetColumn.Nullable) lDifferences.Add("nullable");
|
||||
if (sourceColumn.IsNullable != targetColumn.IsNullable) lDifferences.Add("is nullable");
|
||||
}
|
||||
// Display results:
|
||||
if (!config.HideIdenticalObjects || !lDifferences.IsEmpty())
|
||||
AddSchemaComparisonItem(tableName, sourceColumnName, iPosition, "", (lDifferences.IsEmpty() ? "Column OK" : "Different: " + lDifferences.Concat(", ")));
|
||||
// Move to next Column in BOTH lists:
|
||||
sourceColumnIdx++;
|
||||
targetColumnIdx++;
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
private static long iLargeSizeThreshold = 4000;
|
||||
private bool ColumnSizesDiffer (DbDfnColumn sourceColumn, DbDfnColumn targetColumn)
|
||||
{
|
||||
if (sourceColumn.Size == targetColumn.Size) return false;
|
||||
// If both columns are large, treat then as equal:
|
||||
if (sourceColumn.Size >= iLargeSizeThreshold && targetColumn.Size >= iLargeSizeThreshold) return false;
|
||||
// If one column is large (eg. varchar max) and the other zero (eg. Access Large Text):
|
||||
if (sourceColumn.Size >= iLargeSizeThreshold && targetColumn.Size == 0) return false;
|
||||
if (sourceColumn.Size == 0 && targetColumn.Size >= iLargeSizeThreshold) return false;
|
||||
// Check for specific data types:
|
||||
String sourceDataType = sourceColumn.DataTypeNameNative;
|
||||
String targetDataType = targetColumn.DataTypeNameNative;
|
||||
// Ignore length check for numeric types.
|
||||
if (config.IgnoreSimilarNumericTypes)
|
||||
{
|
||||
// Floating point types.
|
||||
List<String> numericTypes = StringListUtil.Split("Real,Float,Double,Decimal,Numeric",",");
|
||||
if (numericTypes.ContainsCI(sourceDataType) && numericTypes.ContainsCI(targetDataType)) return false;
|
||||
}
|
||||
// DateTimes:
|
||||
List<String> dateTimeTypes = StringListUtil.Split("DateTime",",");
|
||||
if (dateTimeTypes.ContainsCI(sourceDataType) && dateTimeTypes.ContainsCI(targetDataType)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ColumnDecimalsDiffer (DbDfnColumn sourceColumn, DbDfnColumn targetColumn)
|
||||
{
|
||||
if (sourceColumn.DecimalDigits == targetColumn.DecimalDigits) return false;
|
||||
// Check for specific data types:
|
||||
String sourceDataType = sourceColumn.DataTypeNameNative;
|
||||
String targetDataType = targetColumn.DataTypeNameNative;
|
||||
// Numeric types - float/double columns report decimals as 0.
|
||||
List<String> floatTypes = StringListUtil.Split("Real,Float,Double",",");
|
||||
List<String> decimalTypes = StringListUtil.Split("Decimal,Numeric",",");
|
||||
if (sourceColumn.DecimalDigits == 0 && floatTypes.ContainsCI(sourceDataType) && targetColumn.DecimalDigits > 0 && decimalTypes.ContainsCI(targetDataType)) return false;
|
||||
if (sourceColumn.DecimalDigits > 0 && decimalTypes.ContainsCI(sourceDataType) && targetColumn.DecimalDigits == 0 && floatTypes.ContainsCI(targetDataType)) return false;
|
||||
// DateTimes:
|
||||
List<String> dateTimeTypes = StringListUtil.Split("DateTime",",");
|
||||
if (dateTimeTypes.ContainsCI(sourceDataType) && dateTimeTypes.ContainsCI(targetDataType)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ColumnDataTypesDiffer (DbDfnColumn sourceColumn, DbDfnColumn targetColumn)
|
||||
{
|
||||
if (sourceColumn.DataType == targetColumn.DataType) return false;
|
||||
|
||||
// Check for specific data type matches:
|
||||
String sourceDataType = sourceColumn.DataTypeName;
|
||||
String targetDataType = targetColumn.DataTypeName;
|
||||
if (config.IgnoreSimilarCharTypes)
|
||||
{
|
||||
}
|
||||
if (config.IgnoreSimilarNumericTypes)
|
||||
{
|
||||
// Similar numeric type.
|
||||
List<String> numericTypes = StringListUtil.Split("Real,Float,Double,Decimal,Numeric",",");
|
||||
if (numericTypes.ContainsCI(sourceDataType) && numericTypes.ContainsCI(targetDataType)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ColumnNativeDataTypesDiffer (DbDfnColumn sourceColumn, DbDfnColumn targetColumn)
|
||||
{
|
||||
if (sourceColumn.DataTypeNative == targetColumn.DataTypeNative) return false;
|
||||
|
||||
// Check for specific data type matches:
|
||||
String sourceDataType = sourceColumn.DataTypeNameNative;
|
||||
String targetDataType = targetColumn.DataTypeNameNative;
|
||||
if (config.IgnoreSimilarCharTypes)
|
||||
{
|
||||
// Similar Char types.
|
||||
List<String> varCharTypes = StringListUtil.Split("VarChar,LongVarChar,LongChar,NVarChar,VarWChar",",");
|
||||
if (varCharTypes.ContainsCI(sourceDataType) && varCharTypes.ContainsCI(targetDataType)) return false;
|
||||
}
|
||||
if (config.IgnoreSimilarNumericTypes)
|
||||
{
|
||||
// Similar numeric types.
|
||||
List<String> numericTypes = StringListUtil.Split("Real,Float,Double,Numeric,Decimal",",");
|
||||
if (numericTypes.ContainsCI(sourceDataType) && numericTypes.ContainsCI(targetDataType)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private bool CompareTableIndices (DbDfnTable sourceTableDfn, DbDfnTable targetTableDfn)
|
||||
{
|
||||
String sTable = sourceTableDfn.Name;
|
||||
|
||||
// Indices:
|
||||
List<DbDfnIndex> sourceIndices = sourceTableDfn.Indices;
|
||||
List<DbDfnIndex> targetIndices = targetTableDfn.Indices;
|
||||
List<String> sourceIndexNames = sourceIndices.GetNames().OrderBy(item => item).ToList();
|
||||
List<String> targetIndexNames = targetIndices.GetNames().OrderBy(item => item).ToList();
|
||||
for (int sourceIndexIdx = 0, targetIndexIdx = 0; OK && sourceIndexIdx < sourceIndexNames.Count && targetIndexIdx < targetIndexNames.Count; )
|
||||
{
|
||||
// Retrieve the Index names.
|
||||
String sourceIndexName = sourceIndexIdx < sourceIndexNames.Count ? sourceIndexNames[sourceIndexIdx] : "";
|
||||
String targetIndexName = targetIndexIdx < targetIndexNames.Count ? targetIndexNames[targetIndexIdx] : "";
|
||||
// Compare Indices.
|
||||
if (StringUtil.IsEmpty(targetIndexName) || targetIndexName.CompareTo(sourceIndexName) > 0)
|
||||
{
|
||||
// Index is only present in SOURCE list.
|
||||
AddSchemaComparisonItem(sTable, "", 0, sourceIndexName, "Index is missing");
|
||||
// Move to next Index in SOURCE list.
|
||||
sourceIndexIdx++;
|
||||
}
|
||||
else if (StringUtil.IsEmpty(sourceIndexName) || sourceIndexName.CompareTo(targetIndexName) > 0)
|
||||
{
|
||||
// Index is only present in TARGET list.
|
||||
AddSchemaComparisonItem(sTable, "", 0, targetIndexName, "Index is not required");
|
||||
// Move to next Index in TARGET list.
|
||||
targetIndexIdx++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Index is present in both databases.
|
||||
// Retrieve Index definition from each database.
|
||||
DbDfnIndex sourceIndex = sourceIndices.GetIndexByName(sourceIndexName);
|
||||
DbDfnIndex targetIndex = targetIndices.GetIndexByName(targetIndexName);
|
||||
List<String> indexDifferences = new List<string>();
|
||||
// Verify Index attributes.
|
||||
if (!sourceIndex.Name.EqualsCI(targetIndex.Name)) indexDifferences.Add("name");
|
||||
if (sourceIndex.Primary != targetIndex.Primary) indexDifferences.Add("primary");
|
||||
if (sourceIndex.Unique != targetIndex.Unique) indexDifferences.Add("unique");
|
||||
if (sourceIndex.Nullable != targetIndex.Nullable) indexDifferences.Add("nullable");
|
||||
// Display results.
|
||||
if (!config.HideIdenticalObjects || indexDifferences.Count > 0)
|
||||
AddSchemaComparisonItem(sTable, "", 0, sourceIndexName, (indexDifferences.IsEmpty() ? "Index OK" : "Different: " + indexDifferences.Concat(",")));
|
||||
|
||||
// Compare index column lists:
|
||||
List<DbDfnIndexColumn> sourceIndexColumns = sourceIndex.Columns;
|
||||
List<DbDfnIndexColumn> targetIndexColumns = targetIndex.Columns;
|
||||
for (int sourceColumnIdx = 0; sourceColumnIdx < sourceIndexColumns.Count; sourceColumnIdx++)
|
||||
{
|
||||
String columnName = sourceIndexColumns[sourceColumnIdx].Name;
|
||||
int columnPos = sourceColumnIdx + 1;
|
||||
if (!targetIndexColumns.Exists(item => item.Name.EqualsCI(columnName)))
|
||||
AddSchemaComparisonItem(sTable, columnName, columnPos, sourceIndexName, "Index column is missing");
|
||||
else if (sourceColumnIdx != targetIndexColumns.GetPosForName(columnName) - 1)
|
||||
AddSchemaComparisonItem(sTable, columnName, columnPos, sourceIndexName, "Index column in different position");
|
||||
}
|
||||
for (int targetColumnIdx = 0; targetColumnIdx < targetIndexColumns.Count; targetColumnIdx++)
|
||||
{
|
||||
String columnName = targetIndexColumns[targetColumnIdx].Name;
|
||||
int columnPos = targetColumnIdx + 1;
|
||||
if (!sourceIndexColumns.Exists(item => item.Name.EqualsCI(columnName)))
|
||||
AddSchemaComparisonItem(sTable, columnName, columnPos, targetIndexName, "Index column is not required");
|
||||
}
|
||||
|
||||
// Move to next Index in BOTH lists.
|
||||
sourceIndexIdx++;
|
||||
targetIndexIdx++;
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private void AddSchemaComparisonItem (String tableName, String columnName, int position, String indexName, String comment)
|
||||
{
|
||||
schemaComparisonItems.Add(new SchemaComparisonItem(tableName, columnName, position, indexName, comment));
|
||||
if (ProcessNotifier != null) ProcessNotifier.OnSchemaComparisonItemAdded(this);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
public static class SchemaComparisonItemList
|
||||
{
|
||||
/// <summary>Identify the maximum Id value in the list.</summary>
|
||||
public static int GetMaxId (this IEnumerable<SaDbProcess_CompareDatabaseSchemas.SchemaComparisonItem> values)
|
||||
{
|
||||
int maxId = 0;
|
||||
foreach (SaDbProcess_CompareDatabaseSchemas.SchemaComparisonItem schemaComparisonItem in values)
|
||||
maxId = Math.Max(maxId, schemaComparisonItem.Id);
|
||||
return maxId;
|
||||
}
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Base.IO;
|
||||
using Sarand.Net.Standard.Base.Procs;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Sarand.Net.Standard.Dbas.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Sarand.Net.Standard.Dbas.Proc
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaDbProcess_ConvertDbDefinition : SacsProcess
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Configuration.
|
||||
public enum SourceDefinitionType
|
||||
{
|
||||
Unknown = 0,
|
||||
CppCode
|
||||
}
|
||||
public enum TargetDefinitionType
|
||||
{
|
||||
Unknown = 0,
|
||||
CsCode
|
||||
}
|
||||
public new class Config : SacsProcess.Config
|
||||
{
|
||||
public Config ()
|
||||
{
|
||||
}
|
||||
public SourceDefinitionType SourceDefinitionType { get; set; }
|
||||
public String SourceDefinitionFilePath { get; set; }
|
||||
public TargetDefinitionType TargetDefinitionType { get; set; }
|
||||
public String TargetDefinitionFilePath { get; set; }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
public interface IProcessNotifier : ISacsProcessNotifier
|
||||
{
|
||||
}
|
||||
private class Statistics
|
||||
{
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Config config;
|
||||
private IProcessNotifier processNotifier;
|
||||
private readonly List<DbDfnTable> tableDfns = new List<DbDfnTable>();
|
||||
private Statistics stats;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SaDbProcess_ConvertDbDefinition (Config config) : base(config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public override string ProcessDescription { get; } = "Database Definition Convertor";
|
||||
public new IProcessNotifier ProcessNotifier
|
||||
{
|
||||
get { return processNotifier; }
|
||||
set
|
||||
{
|
||||
processNotifier = value;
|
||||
base.ProcessNotifier = value;
|
||||
}
|
||||
}
|
||||
/// <summary>Processing statistics.</summary>
|
||||
public override List<StringPair> ProcessingStatistics
|
||||
{
|
||||
get
|
||||
{
|
||||
List<StringPair> processStats = new List<StringPair>();
|
||||
if (stats != null)
|
||||
{
|
||||
//if (tableDfns.Count > 0)
|
||||
processStats.Add("Tables found", tableDfns.Count.ToString());
|
||||
processStats.Add("Columns found", tableDfns.GetColumnCount().ToString());
|
||||
}
|
||||
return processStats;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// P r o c e s s i n g
|
||||
//-------------------------------------------------------------------
|
||||
protected override bool Run ()
|
||||
{
|
||||
// Initialise process.
|
||||
tableDfns.Clear();
|
||||
stats = new Statistics();
|
||||
|
||||
// Validate configuration.
|
||||
if (!ValidateConfiguration()) return false;
|
||||
|
||||
ConvertCppDatabaseDefinition();
|
||||
|
||||
return OK;
|
||||
}
|
||||
protected bool ValidateConfiguration ()
|
||||
{
|
||||
// Source file.
|
||||
if (config.SourceDefinitionType <= 0)
|
||||
return SetProcessError("Source definition type must be specified.");
|
||||
if (StringUtil.IsEmpty(config.SourceDefinitionFilePath))
|
||||
return SetProcessError("Source definition file must be specified.");
|
||||
if (!FileUtil.IsFile(config.SourceDefinitionFilePath))
|
||||
return SetProcessError("Source definition file not found.");
|
||||
|
||||
// Target file.
|
||||
if (config.TargetDefinitionType <= 0)
|
||||
return SetProcessError("Target definition type must be specified.");
|
||||
if (StringUtil.IsEmpty(config.TargetDefinitionFilePath))
|
||||
return SetProcessError("Target definition file must be specified.");
|
||||
if (!DirectoryUtil.DirectoryExists(DirectoryUtil.GetDirectoryName(config.TargetDefinitionFilePath)))
|
||||
return SetProcessError("Target folder not found.");
|
||||
|
||||
if (config.SourceDefinitionFilePath.EqualsCI(config.TargetDefinitionFilePath))
|
||||
return SetProcessError("Target file must not be the same as the source file.");
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private bool ConvertCppDatabaseDefinition ()
|
||||
{
|
||||
if (!OK) return false;
|
||||
|
||||
// Read source database definition.
|
||||
ExtractDatabaseDefinition();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// E x t r a c t S o u r c e D e f i n i t i o n
|
||||
//-------------------------------------------------------------------
|
||||
private class TableData
|
||||
{
|
||||
public String TableName { get; set; } = "";
|
||||
public int TableIdx { get; set; } = -1;
|
||||
}
|
||||
private class ColumnData
|
||||
{
|
||||
public String TableName { get; set; } = "";
|
||||
public String ColumnName { get; set; } = "";
|
||||
public int DataType { get; set; } = -1;
|
||||
public int ColumnLength { get; set; } = -1;
|
||||
public int TableIdx { get; set; } = -1;
|
||||
public int ColumnIdx { get; set; } = -1;
|
||||
}
|
||||
private class SourceDefinition_Params
|
||||
{
|
||||
protected SourceDefinition_Params () { }
|
||||
public int LineNumber { get; set; } = -1;
|
||||
public int TableIdx { get; set; } = -1;
|
||||
public int ColumnIdx { get; set; } = -1;
|
||||
}
|
||||
private class SourceDefinition_Params_CppCode : SourceDefinition_Params
|
||||
{
|
||||
public bool TablesEnumFound { get; set; }
|
||||
public bool InTablesEnum { get; set; }
|
||||
public bool ColumnsEnumFound { get; set; }
|
||||
public bool InColumnsEnum { get; set; }
|
||||
public bool ColumnsDfnsFound { get; set; }
|
||||
public bool InColumnsDfns { get; set; }
|
||||
public Dictionary<String, TableData> TableMap { get; } = new Dictionary<String, TableData>();
|
||||
public Dictionary<StringPair, ColumnData> ColumnMap { get; } = new Dictionary<StringPair, ColumnData>();
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
private bool ExtractDatabaseDefinition ()
|
||||
{
|
||||
if (!OK) return false;
|
||||
|
||||
// Source definition parameters.
|
||||
SourceDefinition_Params sourceDfnParams = null;
|
||||
SourceDefinition_Params_CppCode sourceDfnParams_CppCode = null;
|
||||
if (config.SourceDefinitionType == SourceDefinitionType.CppCode)
|
||||
{
|
||||
sourceDfnParams_CppCode = new SourceDefinition_Params_CppCode();
|
||||
sourceDfnParams = sourceDfnParams_CppCode;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Extract source database definition.
|
||||
using (StreamReader file = new StreamReader(config.SourceDefinitionFilePath))
|
||||
{
|
||||
while (OK)
|
||||
{
|
||||
String line = file.ReadLine();
|
||||
if (line == null) break;
|
||||
sourceDfnParams.LineNumber++;
|
||||
SetProgressInfo("Extracting source database definition.");
|
||||
line = line.Trim(new char[] { ' ', '\t', '\r', '\n' });
|
||||
if (config.SourceDefinitionType == SourceDefinitionType.CppCode)
|
||||
{
|
||||
ExtractDatabaseDefinitionLine_CppCode(sourceDfnParams_CppCode, line);
|
||||
}
|
||||
}
|
||||
file.Close();
|
||||
}
|
||||
|
||||
// Generate target database definition.
|
||||
String dbDfnClassname = "SaDbDfn";
|
||||
using (System.IO.StreamWriter file = new System.IO.StreamWriter(config.TargetDefinitionFilePath))
|
||||
{
|
||||
file.WriteLine("using Sarand.Net.Standard.Base.Core;");
|
||||
file.WriteLine("using Sarand.Net.Standard.Base.Dbas;");
|
||||
file.WriteLine("using System;");
|
||||
file.WriteLine("using System.Collections.Generic;");
|
||||
file.WriteLine("using System.Linq;");
|
||||
file.WriteLine("");
|
||||
file.WriteLine("//---------------------------------------------------------------------------");
|
||||
file.WriteLine("// Code generated by Sarand Database Utilities " + DateTime.Now.Format("ddd d-MMM-yyyy HH:mm:ss"));
|
||||
file.WriteLine("//---------------------------------------------------------------------------");
|
||||
file.WriteLine("namespace sarand.database.definition");
|
||||
file.WriteLine("{");
|
||||
file.WriteLine("\tpublic class " + dbDfnClassname);
|
||||
file.WriteLine("\t{");
|
||||
file.WriteLine("\t\t//-------------------------------------------------------------------");
|
||||
DatabaseDefinitionGenerator.GenerateDatabaseDefinitionTablesEnum(tableDfns, file);
|
||||
DatabaseDefinitionGenerator.GenerateDatabaseDefinitionColumnsEnum(tableDfns, file);
|
||||
DatabaseDefinitionGenerator.GenerateDatabaseDefinitionClass(tableDfns, dbDfnClassname, file);
|
||||
file.WriteLine("\t}");
|
||||
file.WriteLine("}");
|
||||
file.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception exc) { SaLogger.Warn(ExceptionUtil.GetLogMessage(exc)); SetProcessError(ExceptionUtil.GetErrorStateMessage(exc)); }
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// C + + C o d e S o u r c e
|
||||
//-------------------------------------------------------------------
|
||||
private readonly Dictionary<int,String> cppColumnDataTypes = new Dictionary<int, string>()
|
||||
{
|
||||
{ 1, "VarChar" },
|
||||
{ -7, "Bit" },
|
||||
{ -6, "TinyInt" },
|
||||
{ 5, "SmallInt" },
|
||||
{ -16, "Int" },
|
||||
{ -25, "BigInt" },
|
||||
{ 7, "Single" },
|
||||
{ 8, "Double" },
|
||||
{ 93, "DateTime" }
|
||||
};
|
||||
private bool ExtractDatabaseDefinitionLine_CppCode (SourceDefinition_Params_CppCode sourceDfnParams, String line)
|
||||
{
|
||||
if (!OK) return false;
|
||||
|
||||
// C++ Code definition.
|
||||
//---------------------------------------------------------------
|
||||
// Table enums.
|
||||
if (line.EqualsCI("enum eSaDbTable"))
|
||||
{
|
||||
// Table enum start.
|
||||
if (sourceDfnParams.TablesEnumFound)
|
||||
return SetProcessError("Found multiple table enums at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
sourceDfnParams.TablesEnumFound = true;
|
||||
sourceDfnParams.InTablesEnum = true;
|
||||
return OK;
|
||||
}
|
||||
if (sourceDfnParams.InTablesEnum)
|
||||
{
|
||||
// In Tables enum.
|
||||
if (line.Equals("};") || line.EqualsCI("// Database column identifiers:"))
|
||||
sourceDfnParams.InTablesEnum = false;
|
||||
else if (!line.ContainsCI("eSaDbTblUnknown"))
|
||||
{
|
||||
if (!line.ContainsCI("eSaDbTbl"))
|
||||
return SetProcessError("Invalid table definition on line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
// Extract table name.
|
||||
String tableName = line.ReplaceAll("eSaDbTbl","").ReplaceAll(",","");
|
||||
if (StringUtil.IsEmpty(tableName))
|
||||
return SetProcessError("Blank table name on line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
if (tableDfns.Exists(item => item.Name.EqualsCI(tableName)))
|
||||
return SetProcessError("Duplicate table name on line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
// Add this table to the list.
|
||||
sourceDfnParams.TableIdx++;
|
||||
//SaLogger.Debug("Table #" + sourceDfnParams.TableIdx + ": " + tableName);
|
||||
sourceDfnParams.TableMap.Add(tableName, new TableData() { TableName = tableName, TableIdx = sourceDfnParams.TableIdx });
|
||||
tableDfns.Add(new DbDfnTable("", tableName, ""));
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
// Column enums.
|
||||
if (line.EqualsCI("enum eSaDbColumn"))
|
||||
{
|
||||
// Column enum start.
|
||||
if (sourceDfnParams.ColumnsEnumFound)
|
||||
return SetProcessError("Found multiple columns enums at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
sourceDfnParams.ColumnsEnumFound = true;
|
||||
sourceDfnParams.InColumnsEnum = true;
|
||||
return OK;
|
||||
}
|
||||
if (sourceDfnParams.InColumnsEnum)
|
||||
{
|
||||
// In Columns enum.
|
||||
if (line.Equals("};") || line.StartsWithCI("//----"))
|
||||
sourceDfnParams.InColumnsEnum = false;
|
||||
else if (!line.ContainsCI("eSaDbColUnknown"))
|
||||
{
|
||||
if (!line.ContainsCI("eSaDbCol"))
|
||||
return SetProcessError("Invalid column definition on line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
// Column definitions:
|
||||
// eSaDbColUnknown = -1,
|
||||
// #define eSaDbColVendorType_ListID (eSaDbColumn)4101
|
||||
// #define eSaDbColSaCode_Notes (eSaDbColumn)101
|
||||
// Extract Table/Column name pair - value starts 'eSaDbCol'.
|
||||
int colDfnStartIdx = line.IndexOf("eSaDbCol");
|
||||
String tableColumnName = line.Substring(colDfnStartIdx + "eSaDbCol".Length);
|
||||
// Table/Column names end with space, equals or comma.
|
||||
int colDfnEndIdx = tableColumnName.IndexOf(' ');
|
||||
if (colDfnEndIdx > 0)
|
||||
tableColumnName = tableColumnName.Left(colDfnEndIdx + 1);
|
||||
colDfnEndIdx = tableColumnName.IndexOf('=');
|
||||
if (colDfnEndIdx > 0)
|
||||
tableColumnName = tableColumnName.Left(colDfnEndIdx + 1);
|
||||
colDfnEndIdx = tableColumnName.IndexOf(',');
|
||||
if (colDfnEndIdx > 0)
|
||||
tableColumnName = tableColumnName.Left(colDfnEndIdx + 1);
|
||||
//SaLogger.Debug("Table/Column name: " + tableColumnName);
|
||||
// Split table and column names on Underscore.
|
||||
String[] tableColumnNameParts = tableColumnName.Split('_');
|
||||
if (tableColumnNameParts.Length != 2)
|
||||
return SetProcessError("Invalid column definition on line (missing underscore?) " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
sourceDfnParams.ColumnIdx++;
|
||||
String tableName = tableColumnNameParts[0];
|
||||
String columnName = tableColumnNameParts[1];
|
||||
//SaLogger.Debug("Column #" + sourceDfnParams.ColumnIdx + ": " + tableName + "." + columnName);
|
||||
sourceDfnParams.ColumnMap.Add(new StringPair(tableName, columnName), new ColumnData() { TableName = tableName, ColumnName = columnName, ColumnIdx = sourceDfnParams.ColumnIdx });
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
// Column definitions.
|
||||
if (line.StartsWithCI("SaDbColumnDefinition dfnColumn []"))
|
||||
{
|
||||
// Column definitions start.
|
||||
if (sourceDfnParams.ColumnsDfnsFound)
|
||||
return SetProcessError("Found multiple column definitions at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
sourceDfnParams.ColumnsDfnsFound = true;
|
||||
sourceDfnParams.InColumnsDfns = true;
|
||||
sourceDfnParams.ColumnIdx = -1;
|
||||
return OK;
|
||||
}
|
||||
if (sourceDfnParams.InColumnsDfns)
|
||||
{
|
||||
if (line.Equals("{")) return OK;
|
||||
if (line.Equals("};"))
|
||||
{
|
||||
sourceDfnParams.InColumnsDfns = false;
|
||||
return OK;
|
||||
}
|
||||
// Column definition:
|
||||
// { TableIdx,"ColumnName",DataType,Length }, ... [multiple per line]
|
||||
// Column data types:
|
||||
// eString = 1 SQL_C_CHAR
|
||||
// eBool = -7 SQL_C_BIT
|
||||
// eByte = -6 SQL_C_TINYINT
|
||||
// eShortInt = 5 SQL_C_SHORT
|
||||
// eLongInt = -16 SQL_C_SLONG
|
||||
// eBigInt = -25 SQL_C_SBIGINT
|
||||
// eReal = 7 SQL_C_FLOAT
|
||||
// eDouble = 8 SQL_C_DOUBLE
|
||||
// eTime = 93 SQL_C_TYPE_TIMESTAMP
|
||||
foreach (String columnDfnDataRaw in line.Split(new String[] { "}," }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
String columnDfnData = columnDfnDataRaw.ReplaceAll("{","").TrimAll();
|
||||
//SaLogger.Debug("Column dfn: " + columnDfnData);
|
||||
String[] columnDfnParts = columnDfnData.Split(',');
|
||||
if (columnDfnParts.Length != 4) return SetProcessError("Invalid column definition (expecting 4 parts) at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
String tableIdxRaw = columnDfnParts[0].TrimAll();
|
||||
int tableIdx = StringUtil.ToInt(tableIdxRaw);
|
||||
String columnName = columnDfnParts[1].TrimAll().ReplaceAll("\"","");
|
||||
String dataTypeRaw = columnDfnParts[2].TrimAll();
|
||||
int dataType = StringUtil.ToInt(dataTypeRaw);
|
||||
String columnLengthRaw = columnDfnParts[3].TrimAll();
|
||||
int columnLength = StringUtil.ToInt(columnLengthRaw);
|
||||
if (!tableIdxRaw.IsNumeric()) return SetProcessError("Invalid table index in column definition (" + tableIdxRaw + ") at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
if (StringUtil.IsEmpty(columnName)) return SetProcessError("Blank name in column definition at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
if (!dataTypeRaw.IsNumeric(true) || !cppColumnDataTypes.ContainsKey(dataType)) return SetProcessError("Invalid data type in column definition (" + dataTypeRaw + ") at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
if (!columnLengthRaw.IsNumeric()) return SetProcessError("Invalid column length in column definition (" + columnLengthRaw + ") at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
// Table definition.
|
||||
String tableName = sourceDfnParams.TableMap.Values.ToList().Find(item => item.TableIdx == tableIdx)?.TableName ?? "";
|
||||
if (StringUtil.IsEmpty(tableName)) return SetProcessError("Unable to identify table name for Table idx " + tableIdx + " at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
DbDfnTable tableDfn = tableDfns.GetTableByName(tableName);
|
||||
if (tableDfn == null) return SetProcessError("Unable to retrieve table definition for Table name " + tableName + " at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
// Column data.
|
||||
sourceDfnParams.ColumnIdx++;
|
||||
ColumnData columnData = sourceDfnParams.ColumnMap.Values.ToList().Find(item => item.TableName.EqualsCI(tableName) && item.ColumnIdx == sourceDfnParams.ColumnIdx);
|
||||
if (columnData == null) return SetProcessError("Unable to column data for table " + tableName + " and column " + columnName + " at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
if (columnData.ColumnName.EqualsCI(columnName)) return SetProcessError("Inconsistent column name for table " + tableName + " and column " + columnName + " at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
// Column definition.
|
||||
DbDfnColumn columnDfn = new DbDfnColumn()
|
||||
{
|
||||
Table = tableName,
|
||||
Name = columnName,
|
||||
DataType = EnumUtil.ParseEnum(cppColumnDataTypes.GetValue(dataType,""),DbColumnDataType.Unknown),
|
||||
Size = columnLength
|
||||
};
|
||||
if (columnDfn.DataType < 0) return SetProcessError("Invalid data type for table " + tableName + " and column " + columnName + " at line " + sourceDfnParams.LineNumber + ": " + line + ".");
|
||||
tableDfn.AddColumn(columnDfn);
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
return OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
|
||||
//=======================================================================
|
||||
}
|
||||
Reference in New Issue
Block a user