From 545a747ebc87bdb38357d9d1c848e24d5c6603a7 Mon Sep 17 00:00:00 2001 From: Marek Frniak Date: Fri, 27 Mar 2026 12:47:56 +0100 Subject: [PATCH 1/3] Add UniDataStorageWriter component - initial commit --- .../Diagnostic/WriterDiagnosticResult.cs | 71 +++ .../DataStorage/UniDataStorageWriter/Enums.cs | 51 +++ .../UniDataStorageWriter/Factory.cs | 38 ++ .../Interfaces/DataQuery.cs | 16 + .../Interfaces/IDataStorageWriter.cs | 18 + .../Readers/CsvWriter .cs | 317 +++++++++++++ .../Readers/DatabaseWriter .cs | 38 ++ .../Readers/JsonWriter.cs | 44 ++ .../Readers/RestApiWriter.cs | 38 ++ .../Searching/SearchOrderDefinition.cs | 65 +++ .../Searching/SearchOrderParser.cs | 87 ++++ .../UI/ExamplesFrm.Designer.cs | 58 +++ .../UniDataStorageWriter/UI/ExamplesFrm.cs | 20 + .../UniDataStorageWriter/UI/ExamplesFrm.resx | 120 +++++ .../UniDataStorageWriter/UI/WriterCfgCtrl.cs | 325 ++++++++++++++ .../UI/WriterCfgCtrl.designer.cs | 418 ++++++++++++++++++ .../UI/WriterCfgCtrl.resx | 135 ++++++ .../UniDataStorageWriter/Writer.cs | 94 ++++ .../UniDataStorageWriter/WriterCfg.cs | 169 +++++++ TBF/Rig/TbfComponents.cs | 1 + TBF/TBF.csproj | 53 ++- 21 files changed, 2171 insertions(+), 5 deletions(-) create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Diagnostic/WriterDiagnosticResult.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Enums.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Factory.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Interfaces/DataQuery.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Interfaces/IDataStorageWriter.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/CsvWriter .cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/DatabaseWriter .cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/JsonWriter.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/RestApiWriter.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Searching/SearchOrderDefinition.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Searching/SearchOrderParser.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/UI/ExamplesFrm.Designer.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/UI/ExamplesFrm.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/UI/ExamplesFrm.resx create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/UI/WriterCfgCtrl.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/UI/WriterCfgCtrl.designer.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/UI/WriterCfgCtrl.resx create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/Writer.cs create mode 100644 TBF/Rig/Output/DataStorage/UniDataStorageWriter/WriterCfg.cs diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Diagnostic/WriterDiagnosticResult.cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Diagnostic/WriterDiagnosticResult.cs new file mode 100644 index 000000000..eff50d55e --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Diagnostic/WriterDiagnosticResult.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Text; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter +{ + /// + /// Represents result of source/query diagnostic. + /// + public class WriterDiagnosticResult + { + /// + /// Indicates whether the diagnostic operation was successful. + /// + public bool Success { get; set; } + + /// + /// Short summary message. + /// + public string Message { get; set; } + + /// + /// Optional detailed diagnostic log lines. + /// + public List Diagnostics { get; set; } + + /// + /// Optional payload returned by the diagnostic. + /// Example: header columns, file lines count, found index, etc. + /// + public object Data { get; set; } + + public WriterDiagnosticResult() + { + Diagnostics = new List(); + } + + public string ToDisplayDiag() + { + StringBuilder sb = new StringBuilder(); + + if (Diagnostics != null && Diagnostics.Count > 0) + { + foreach (string line in Diagnostics) + { + sb.AppendLine(line); + } + + if (!string.IsNullOrWhiteSpace(Message)) + sb.AppendLine(); + } + + if (!string.IsNullOrWhiteSpace(Message)) + sb.AppendLine(Message); + + return sb.ToString(); + } + } + public class CsvSearchResult + { + public bool Found { get; set; } + public string FilePath { get; set; } + public string HeaderLine { get; set; } + public string MatchedLine { get; set; } + public Dictionary Values { get; set; } + + public CsvSearchResult() + { + Values = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Enums.cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Enums.cs new file mode 100644 index 000000000..d8ce98f37 --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Enums.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter +{ + /// + /// Helper class to assign descriptions to enum values + /// + public class Description : Attribute + { + public string Text; + public Description(string t) + { + Text = t; + } + } + + public static class GetDescription + { + /// Extension method for enum-s + public static string ToDescription(this Enum en) + { + Type type = en.GetType(); + MemberInfo[] memInfo = type.GetMember(en.ToString()); + + if (memInfo != null && memInfo.Length > 0) + { + object[] attrs = memInfo[0].GetCustomAttributes(typeof(Description), false); + if (attrs != null && attrs.Length > 0) + return ((Description)attrs[0]).Text; + } + + return en.ToString(); /// Return ToString() value in case there is no description + } + } + + /// + /// Identifies the type of data storage + /// + public enum DataStorageType + { + RestApi, + Database, + Json, + Csv + } +} diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Factory.cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Factory.cs new file mode 100644 index 000000000..df0da7bc3 --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Factory.cs @@ -0,0 +1,38 @@ +/// +/// Copyright (c) 2018 Sensus Slovensko a.s. +/// + +using System; +using System.Collections.Generic; +using TBF.Rig.Generic; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter +{ + /// + /// Factory component 'UniDataStorageWriter' implements more storing modules + /// Modules: + /// + public class Factory : IComponentFactory + { + public string ClassName { get { return GetType().Namespace.Substring(8); } } /// For backward compatibility + public override string ToString() { return ClassName; } + + public IComponent DummyComponent() { return new Writer(new WriterCfg("UniDataStorageWriter", this)); } + + public IComponent GetComponent(IComponentCfg cfg, IList components) + { + WriterCfg WriterCfg = cfg as WriterCfg; + if (WriterCfg == null) + throw new ArgumentException("Invalid config for UniDataStorageWriter"); + + return new Writer(WriterCfg); + } + + public IComponentCfg DefaultConfig() { return new WriterCfg("UniDataStorageWriter", this); } + + public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component) + { + return ComponentCfgBase.CreateFromDbEntity(WriterCfg.Serializer, component, this); + } + } +} diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Interfaces/DataQuery.cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Interfaces/DataQuery.cs new file mode 100644 index 000000000..8b34f8ac3 --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Interfaces/DataQuery.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter.Interfaces +{ + public class DataQuery + { + /// + /// + /// + public string QueryParam { get; set; } + } +} diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Interfaces/IDataStorageWriter.cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Interfaces/IDataStorageWriter.cs new file mode 100644 index 000000000..2d9edd817 --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Interfaces/IDataStorageWriter.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TBF.Rig.Input.DataStorage.UniDataStorageWriter.Interfaces; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter +{ + public interface IDataStorageWriter + { + object GetData(DataQuery query); + + WriterDiagnosticResult TestSource(bool enableDiagnostics); + + WriterDiagnosticResult TestQuery(bool enableDiagnostics); + } +} diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/CsvWriter .cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/CsvWriter .cs new file mode 100644 index 000000000..e24d09990 --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/CsvWriter .cs @@ -0,0 +1,317 @@ +using System; +using System.Collections.Generic; +using System.IO; +using TBF.Rig.Input.DataStorage.UniDataStorageWriter.Interfaces; +using TBF.Rig.Input.DataStorage.UniDataStorageWriter.Searching; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter.Writers +{ + /// + /// Writer implementation for CSV-based data source. + /// + public class CsvWriter : IDataStorageWriter + { + private string resolvedPath; + private string[] loadedLines; + private string[] loadedHeaders; + + private readonly WriterCfg cfg; + + public CsvWriter(WriterCfg cfg) + { + this.cfg = cfg ?? throw new ArgumentNullException(nameof(cfg)); + } + + /// + /// Reads data from CSV and returns matched row data. + /// + public object GetData(DataQuery query) + { + if (query == null) + throw new ArgumentNullException(nameof(query)); + + WriterDiagnosticResult connectResult = ConnectToSource(true); + if (!connectResult.Success) + throw new InvalidOperationException(connectResult.Message); + + WriterDiagnosticResult searchResult = ExecuteQuery(query, true); + if (!searchResult.Success) + throw new InvalidOperationException(searchResult.Message); + + return searchResult; + } + + /// + /// Tests whether the CSV source can be resolved, opened and read. + /// + public WriterDiagnosticResult TestSource(bool enableDiagnostics) + { + return ConnectToSource(enableDiagnostics); + } + + /// + /// Tests whether the configured query column exists in the CSV source. + /// + public WriterDiagnosticResult TestQuery(bool enableDiagnostics) + { + WriterDiagnosticResult connectResult = ConnectToSource(enableDiagnostics); + if (!connectResult.Success) + return connectResult; + + WriterDiagnosticResult result = new WriterDiagnosticResult(); + + try + { + Log(result, enableDiagnostics, "Starting QueryTemplate validation."); + + SearchOrderDefinition definition = SearchOrderParser.Parse(cfg.QueryTemplate); + + Log(result, enableDiagnostics, "QueryTemplate parsed successfully."); + Log(result, enableDiagnostics, "Select column: " + definition.SelectColumn); + Log(result, enableDiagnostics, "Where column: " + definition.WhereColumn); + + int selectIndex = ResolveColumnIndex(loadedHeaders, definition.SelectColumn); + int whereIndex = ResolveColumnIndex(loadedHeaders, definition.WhereColumn); + + Log(result, enableDiagnostics, "Resolved select column index: " + selectIndex); + Log(result, enableDiagnostics, "Resolved where column index: " + whereIndex); + + result.Success = true; + result.Message = "QueryTemplate validation finished successfully."; + result.Data = null; + } + catch (Exception ex) + { + result.Success = false; + result.Message = ex.Message; + result.Data = null; + + Log(result, enableDiagnostics, "ERROR: " + ex.Message); + } + + return result; + } + + /// + /// Opens the CSV source, validates its existence and loads its content. + /// + public WriterDiagnosticResult ConnectToSource(bool enableDiagnostics) + { + WriterDiagnosticResult result = new WriterDiagnosticResult(); + + try + { + Log(result, enableDiagnostics, "Starting CSV source connection test."); + + if (string.IsNullOrWhiteSpace(cfg.DataSource)) + throw new InvalidOperationException("CSV data source is empty."); + + Log(result, enableDiagnostics, "Input data source: " + cfg.DataSource); + + resolvedPath = Path.GetFullPath(cfg.DataSource); + Log(result, enableDiagnostics, "Resolved full path: " + resolvedPath); + + Log(result, enableDiagnostics, "Checking whether the file exists."); + if (!File.Exists(resolvedPath)) + throw new FileNotFoundException("CSV file was not found.", resolvedPath); + + Log(result, enableDiagnostics, "CSV file exists."); + + Log(result, enableDiagnostics, "Reading CSV file."); + loadedLines = File.ReadAllLines(resolvedPath); + + if (loadedLines == null || loadedLines.Length == 0) + throw new InvalidOperationException("CSV file is empty."); + + Log(result, enableDiagnostics, "CSV line count: " + loadedLines.Length); + + loadedHeaders = SplitCsvLine(loadedLines[0]); + if (loadedHeaders == null || loadedHeaders.Length == 0) + throw new InvalidOperationException("CSV header is empty."); + + Log(result, enableDiagnostics, "CSV header loaded successfully."); + Log(result, enableDiagnostics, "Header column count: " + loadedHeaders.Length); + + result.Success = true; + result.Message = "CSV source connection finished successfully."; + result.Data = loadedLines; + } + catch (Exception ex) + { + result.Success = false; + result.Message = ex.Message; + result.Data = null; + + Log(result, enableDiagnostics, "ERROR: " + ex.Message); + } + + return result; + } + + private WriterDiagnosticResult ExecuteQuery(DataQuery query, bool enableDiagnostics) + { + WriterDiagnosticResult result = new WriterDiagnosticResult(); + + try + { + Log(result, enableDiagnostics, "Starting QueryTemplate execution."); + + if (query == null) + throw new InvalidOperationException("DataQuery is null."); + + if (string.IsNullOrWhiteSpace(query.QueryParam)) + throw new InvalidOperationException("DataQuery.QueryParam is empty."); + + if (loadedLines == null || loadedLines.Length == 0) + throw new InvalidOperationException("CSV source is not connected."); + + if (loadedHeaders == null || loadedHeaders.Length == 0) + throw new InvalidOperationException("CSV header is not loaded."); + + SearchOrderDefinition definition = SearchOrderParser.Parse(cfg.QueryTemplate); + + Log(result, enableDiagnostics, "QueryTemplate parsed successfully."); + Log(result, enableDiagnostics, "Select column: " + definition.SelectColumn); + Log(result, enableDiagnostics, "Where column: " + definition.WhereColumn); + Log(result, enableDiagnostics, "QueryParam: " + query.QueryParam); + + int selectIndex = ResolveColumnIndex(loadedHeaders, definition.SelectColumn); + int whereIndex = ResolveColumnIndex(loadedHeaders, definition.WhereColumn); + + Log(result, enableDiagnostics, "Resolved select column index: " + selectIndex); + Log(result, enableDiagnostics, "Resolved where column index: " + whereIndex); + + for (int i = 1; i < loadedLines.Length; i++) + { + if (string.IsNullOrWhiteSpace(loadedLines[i])) + continue; + + string[] values = SplitCsvLine(loadedLines[i]); + + if (whereIndex >= values.Length) + continue; + + string currentValue = (values[whereIndex] ?? string.Empty).Trim(); + + if (string.Equals( + currentValue, + query.QueryParam.Trim(), + StringComparison.OrdinalIgnoreCase)) + { + string returnValue = selectIndex < values.Length + ? values[selectIndex] + : string.Empty; + + Log(result, enableDiagnostics, "Match found at line index: " + i); + Log(result, enableDiagnostics, "Returned value: " + returnValue); + + result.Success = true; + result.Message = "Value found."; + result.Data = returnValue; + return result; + } + } + + Log(result, enableDiagnostics, "No matching row found."); + + result.Success = true; + result.Message = "No match found."; + result.Data = null; + } + catch (Exception ex) + { + result.Success = false; + result.Message = ex.Message; + result.Data = null; + + Log(result, enableDiagnostics, "ERROR: " + ex.Message); + } + + return result; + } + + /// + /// Finds zero-based index of the requested column in CSV header. + /// + private int FindColumnIndex(string[] headers, string columnName) + { + for (int i = 0; i < headers.Length; i++) + { + if (string.Equals( + (headers[i] ?? string.Empty).Trim(), + (columnName ?? string.Empty).Trim(), + StringComparison.OrdinalIgnoreCase)) + { + return i; + } + } + + return -1; + } + + /// + /// Splits CSV line by semicolon. + /// Simple implementation without quoted-separator support. + /// + private string[] SplitCsvLine(string line) + { + if (string.IsNullOrEmpty(line)) + return new string[0]; + + if (line.Contains(",")) + return line.Split(','); + + if (line.Contains(";")) + return line.Split(';'); + + if (line.Contains("\t")) + return line.Split('\t'); + + return new string[] { line }; + } + + /// + /// Appends a diagnostic line if diagnostics are enabled. + /// + private void Log(WriterDiagnosticResult result, bool enableDiagnostics, string message) + { + if (!enableDiagnostics || result == null) + return; + + result.Diagnostics.Add(message); + } + + private int ResolveColumnIndex(string[] headers, ColumnReference columnRef) + { + if (columnRef == null) + throw new InvalidOperationException("Column reference is null."); + + if (columnRef.HasIndex) + { + int index = columnRef.Index.Value; + + if (index < 0 || index >= headers.Length) + { + throw new InvalidOperationException( + string.Format("Column index {0} is out of range. Header column count: {1}.", index, headers.Length)); + } + + return index; + } + + if (columnRef.HasName) + { + int index = FindColumnIndex(headers, columnRef.Name); + if (index < 0) + { + throw new InvalidOperationException( + string.Format("Column '{0}' was not found in CSV header.", columnRef.Name)); + } + + return index; + } + + throw new InvalidOperationException("Column reference is not defined."); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/DatabaseWriter .cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/DatabaseWriter .cs new file mode 100644 index 000000000..fc94c0e88 --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/DatabaseWriter .cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TBF.Rig.Input.DataStorage.UniDataStorageWriter.Interfaces; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter.Writers +{ + public class DatabaseWriter : IDataStorageWriter + { + private readonly WriterCfg cfg; + + public DatabaseWriter(WriterCfg cfg) + { + this.cfg = cfg ?? throw new ArgumentNullException(nameof(cfg)); + } + + public object GetData(DataQuery query) + { + if (string.IsNullOrWhiteSpace(cfg.DataSource)) + throw new InvalidOperationException("Database data source is empty."); + + // TODO: DB connection + SQL query by searchOrder + return null; + } + + public WriterDiagnosticResult TestQuery(bool enableDiagnostics) + { + throw new NotImplementedException(); + } + + public WriterDiagnosticResult TestSource(bool enableDiagnostics) + { + throw new NotImplementedException(); + } + } +} diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/JsonWriter.cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/JsonWriter.cs new file mode 100644 index 000000000..bccf103ce --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/JsonWriter.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TBF.Rig.Input.DataStorage.UniDataStorageWriter.Interfaces; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter.Writers +{ + public class JsonWriter : IDataStorageWriter + { + private readonly WriterCfg cfg; + + public JsonWriter(WriterCfg cfg) + { + this.cfg = cfg ?? throw new ArgumentNullException(nameof(cfg)); + } + + public object GetData(DataQuery query) + { + if (string.IsNullOrWhiteSpace(cfg.DataSource)) + throw new InvalidOperationException("JSON data source is empty."); + + if (!File.Exists(cfg.DataSource)) + throw new FileNotFoundException("JSON file not found.", cfg.DataSource); + + string json = File.ReadAllText(cfg.DataSource); + + // TODO: deserialize + filter + return json; + } + + public WriterDiagnosticResult TestQuery(bool enableDiagnostics) + { + throw new NotImplementedException(); + } + + public WriterDiagnosticResult TestSource(bool enableDiagnostics) + { + throw new NotImplementedException(); + } + } +} diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/RestApiWriter.cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/RestApiWriter.cs new file mode 100644 index 000000000..7c4af2b21 --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Readers/RestApiWriter.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TBF.Rig.Input.DataStorage.UniDataStorageWriter.Interfaces; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter.Writers +{ + public class RestApiWriter : IDataStorageWriter + { + private readonly WriterCfg cfg; + + public RestApiWriter(WriterCfg cfg) + { + this.cfg = cfg ?? throw new ArgumentNullException(nameof(cfg)); + } + + public object GetData(DataQuery query) + { + if (string.IsNullOrWhiteSpace(cfg.DataSource)) + throw new InvalidOperationException("REST API data source is empty."); + + // TODO: HTTP request + return null; + } + + public WriterDiagnosticResult TestQuery(bool enableDiagnostics) + { + throw new NotImplementedException(); + } + + public WriterDiagnosticResult TestSource(bool enableDiagnostics) + { + throw new NotImplementedException(); + } + } +} diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Searching/SearchOrderDefinition.cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Searching/SearchOrderDefinition.cs new file mode 100644 index 000000000..e25ba9491 --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Searching/SearchOrderDefinition.cs @@ -0,0 +1,65 @@ +using System; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter.Searching +{ + /// + /// Parsed representation of QueryTemplate expression. + /// Example: + /// SELECT [Password] WHERE [PcbId] = QUERYPARAM + /// + public class SearchOrderDefinition + { + /// + /// Column to be returned. + /// + public ColumnReference SelectColumn { get; set; } + + /// + /// Column used for lookup. + /// + public ColumnReference WhereColumn { get; set; } + } + + /// + /// Represents a column reference either by name or by zero-based index. + /// + public class ColumnReference + { + /// + /// Column name if referenced by [ColumnName]. + /// + public string Name { get; set; } + + /// + /// Zero-based column index if referenced by COLUMN(n). + /// + public int? Index { get; set; } + + /// + /// Returns true if reference is by column name. + /// + public bool HasName + { + get { return !string.IsNullOrWhiteSpace(Name); } + } + + /// + /// Returns true if reference is by column index. + /// + public bool HasIndex + { + get { return Index.HasValue; } + } + + public override string ToString() + { + if (HasName) + return "[" + Name + "]"; + + if (HasIndex) + return "COLUMN(" + Index.Value + ")"; + + return ""; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Searching/SearchOrderParser.cs b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Searching/SearchOrderParser.cs new file mode 100644 index 000000000..8ba62f7d0 --- /dev/null +++ b/TBF/Rig/Output/DataStorage/UniDataStorageWriter/Searching/SearchOrderParser.cs @@ -0,0 +1,87 @@ +using System; +using System.Text.RegularExpressions; +using TBF.Rig.Input.DataStorage.UniDataStorageWriter.Searching; + +namespace TBF.Rig.Input.DataStorage.UniDataStorageWriter +{ + /// + /// Parses SQL-like QueryTemplate expressions. + /// Supported syntax: + /// SELECT [ReturnColumn] WHERE [MatchColumn] = QUERYPARAM + /// SELECT COLUMN(1) WHERE COLUMN(0) = QUERYPARAM + /// Mixed forms are also supported. + /// + public static class SearchOrderParser + { + private static readonly Regex FullPattern = new Regex( + @"^\s*SELECT\s+(?