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 tableDfns = new List(); 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; } } /// Processing statistics. public override List ProcessingStatistics { get { List processStats = new List(); 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 TableMap { get; } = new Dictionary(); public Dictionary ColumnMap { get; } = new Dictionary(); } //------------------------------------------------------------------- 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 cppColumnDataTypes = new Dictionary() { { 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; } //------------------------------------------------------------------- } //======================================================================= }