345 lines
13 KiB
C#
345 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using TBF.Rig.Input.DataStorage.UniDataStorageReader.Searching;
|
|
|
|
namespace TBF.Rig.Input.DataStorage.UniDataStorageReader.Readers
|
|
{
|
|
/// <summary>
|
|
/// Reader implementation for CSV-based data source.
|
|
/// </summary>
|
|
public class CsvReader : IDataStorageReader
|
|
{
|
|
private string resolvedPath;
|
|
private string[] loadedLines;
|
|
private string[] loadedHeaders;
|
|
|
|
private readonly ReaderCfg cfg;
|
|
|
|
public CsvReader(ReaderCfg cfg)
|
|
{
|
|
this.cfg = cfg ?? throw new ArgumentNullException(nameof(cfg));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads data from CSV and returns matched row data.
|
|
/// Executes the same query template for each value in DataQuery.QueryParams.
|
|
/// </summary>
|
|
public object GetData(DataQuery query)
|
|
{
|
|
if (query == null)
|
|
throw new ArgumentNullException(nameof(query));
|
|
|
|
ReaderDiagnosticResult connectResult = ConnectToSource(true);
|
|
if (!connectResult.Success)
|
|
throw new InvalidOperationException(connectResult.Message);
|
|
|
|
ReaderDiagnosticResult searchResult = ExecuteQuery(query, true);
|
|
if (!searchResult.Success)
|
|
throw new InvalidOperationException(searchResult.Message);
|
|
|
|
return searchResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests whether the CSV source can be resolved, opened and read.
|
|
/// </summary>
|
|
public ReaderDiagnosticResult TestSource(bool enableDiagnostics)
|
|
{
|
|
return ConnectToSource(enableDiagnostics);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests whether the configured query columns exist in the CSV source.
|
|
/// </summary>
|
|
public ReaderDiagnosticResult TestQuery(bool enableDiagnostics)
|
|
{
|
|
ReaderDiagnosticResult connectResult = ConnectToSource(enableDiagnostics);
|
|
if (!connectResult.Success)
|
|
return connectResult;
|
|
|
|
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the CSV source, validates its existence and loads its content.
|
|
/// </summary>
|
|
public ReaderDiagnosticResult ConnectToSource(bool enableDiagnostics)
|
|
{
|
|
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the configured query for all provided parameter values.
|
|
/// </summary>
|
|
private ReaderDiagnosticResult ExecuteQuery(DataQuery query, bool enableDiagnostics)
|
|
{
|
|
ReaderDiagnosticResult result = new ReaderDiagnosticResult();
|
|
|
|
try
|
|
{
|
|
Log(result, enableDiagnostics, "Starting QueryTemplate execution.");
|
|
|
|
if (query == null)
|
|
throw new InvalidOperationException("DataQuery is null.");
|
|
|
|
if (query.QueryParams == null || query.QueryParams.Count == 0)
|
|
throw new InvalidOperationException("DataQuery.QueryParams 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);
|
|
|
|
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);
|
|
Log(result, enableDiagnostics, "Query parameter count: " + query.QueryParams.Count);
|
|
|
|
List<string> matchedValues = new List<string>();
|
|
int matchCount = 0;
|
|
|
|
for (int paramIndex = 0; paramIndex < query.QueryParams.Count; paramIndex++)
|
|
{
|
|
string queryValue = (query.QueryParams[paramIndex] ?? string.Empty).Trim();
|
|
|
|
Log(result, enableDiagnostics, string.Format("=== Query item {0} ===", paramIndex + 1));
|
|
Log(result, enableDiagnostics, "QueryParam: " + queryValue);
|
|
|
|
bool found = false;
|
|
|
|
for (int lineIndex = 1; lineIndex < loadedLines.Length; lineIndex++)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(loadedLines[lineIndex]))
|
|
continue;
|
|
|
|
string[] values = SplitCsvLine(loadedLines[lineIndex]);
|
|
|
|
if (whereIndex >= values.Length)
|
|
continue;
|
|
|
|
string currentValue = (values[whereIndex] ?? string.Empty).Trim();
|
|
|
|
if (string.Equals(currentValue, queryValue, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
string returnValue = selectIndex < values.Length
|
|
? values[selectIndex]
|
|
: string.Empty;
|
|
|
|
Log(result, enableDiagnostics, "Match found at line index: " + lineIndex);
|
|
Log(result, enableDiagnostics, "Returned value: " + returnValue);
|
|
|
|
matchedValues.Add(returnValue);
|
|
matchCount++;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
Log(result, enableDiagnostics, "No matching row found.");
|
|
}
|
|
}
|
|
|
|
if (query.QueryParams.Count == 1)
|
|
{
|
|
result.Data = matchedValues.Count > 0 ? (object)matchedValues[0] : null;
|
|
result.Message = matchedValues.Count > 0 ? "Value found." : "No match found.";
|
|
}
|
|
else
|
|
{
|
|
result.Data = matchedValues;
|
|
result.Message = string.Format(
|
|
"Batch query finished. Matches found: {0} of {1}.",
|
|
matchCount,
|
|
query.QueryParams.Count);
|
|
}
|
|
|
|
result.Success = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.Success = false;
|
|
result.Message = ex.Message;
|
|
result.Data = null;
|
|
|
|
Log(result, enableDiagnostics, "ERROR: " + ex.Message);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds zero-based index of the requested column in CSV header.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Splits CSV line by common separators.
|
|
/// Simple implementation without quoted-separator support.
|
|
/// </summary>
|
|
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[] { line };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends a diagnostic line if diagnostics are enabled.
|
|
/// </summary>
|
|
private void Log(ReaderDiagnosticResult 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.");
|
|
}
|
|
}
|
|
} |