diff --git a/.gitignore b/.gitignore
index 8a26121..97dc336 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,23 @@ target/
adapters/file/data/files/2022CbpOceanEconomyTable__2022CbpIaOceanEconomy.json
adapters/file/data/files/2022CbpOceanEconomyTable__Notes.json
/grafana/tempo-data/
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+*.sln.ide/
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
diff --git a/calcite-rs-jni/odbc/.gitignore b/calcite-rs-jni/odbc/.gitignore
new file mode 100644
index 0000000..0ca9d2f
--- /dev/null
+++ b/calcite-rs-jni/odbc/.gitignore
@@ -0,0 +1,19 @@
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+*.sln.ide/
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
diff --git a/calcite-rs-jni/odbc/DDN-ODBC-Tester/DDN-ODBC-Tester.csproj b/calcite-rs-jni/odbc/DDN-ODBC-Tester/DDN-ODBC-Tester.csproj
new file mode 100644
index 0000000..b4bb47d
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC-Tester/DDN-ODBC-Tester.csproj
@@ -0,0 +1,12 @@
+
+
+ Exe
+ net8.0
+ DDN_ODBC_Tester
+ enable
+ enable
+
+
+
+
+
diff --git a/calcite-rs-jni/odbc/DDN-ODBC-Tester/Program.cs b/calcite-rs-jni/odbc/DDN-ODBC-Tester/Program.cs
new file mode 100644
index 0000000..439ea0c
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC-Tester/Program.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Data;
+using DDN_ODBC;
+
+namespace DDN_ODBC_Tester
+{
+ class OdbcMetadataExample
+ {
+ static void Main()
+ {
+ string connectionString = "jdbc:graphql:http://localhost:3280/graphql";
+
+ try
+ {
+ using (DDN_ODBC.DDN_ODBC connection = new DDN_ODBC.DDN_ODBC(connectionString))
+ {
+ connection.Open();
+ Console.WriteLine("Connected successfully.\n");
+
+ // Get Tables
+ Console.WriteLine("=== Tables ===");
+ using (IDbCommand command = connection.CreateCommand())
+ {
+ command.CommandText = "SQLTables";
+
+ using (IDataReader reader = command.ExecuteReader())
+ {
+ // Print column names
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader.GetName(i)}\t");
+ }
+ Console.WriteLine();
+
+ // Print data
+ while (reader.Read())
+ {
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader[i]}\t");
+ }
+ Console.WriteLine();
+ }
+ }
+ }
+
+ // Get Tables Parameterized
+ Console.WriteLine("=== Tables ===");
+ using (IDbCommand command = connection.CreateCommand())
+ {
+ command.CommandText = "{ CALL SQLTables(?, ?, ?, ?) }";
+
+ // Adding parameters (catalog, schema, table, table type)
+ // Create parameters
+ var param1 = new DDNDataParameter("@catalog", DbType.String) { Value = DBNull.Value };
+ var param2 = new DDNDataParameter("@schema", DbType.String) { Value = DBNull.Value };
+ var param3 = new DDNDataParameter("@table", DbType.String) { Value = DBNull.Value };
+ var param4 = new DDNDataParameter("@tableType", DbType.String) { Value = "TABLE" };
+
+ // Add parameters
+ command.Parameters.Add(param1);
+ command.Parameters.Add(param2);
+ command.Parameters.Add(param3);
+ command.Parameters.Add(param4);
+
+ using (IDataReader reader = command.ExecuteReader())
+ {
+ // Print column names
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader.GetName(i)}\t");
+ }
+ Console.WriteLine();
+
+ // Print data
+ while (reader.Read())
+ {
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader[i]}\t");
+ }
+ Console.WriteLine();
+ }
+ }
+ }
+
+ // Get Columns
+ Console.WriteLine("\n=== Columns for YourTable ===");
+ using (IDbCommand command = connection.CreateCommand())
+ {
+ command.CommandText = "SQLColumns";
+
+ using (IDataReader reader = command.ExecuteReader())
+ {
+ // Print column names
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader.GetName(i)}\t");
+ }
+ Console.WriteLine();
+
+ // Print data
+ while (reader.Read())
+ {
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader[i]}\t");
+ }
+ Console.WriteLine();
+ }
+ }
+ }
+
+ // Get Columns
+ Console.WriteLine("\n=== Columns for YourTable ===");
+ using (IDbCommand command = connection.CreateCommand())
+ {
+ command.CommandText = "{ CALL SQLColumns(?, ?, ?, ?) }";
+
+ // Adding parameters (catalog, schema, table, table type)
+ // Create parameters
+ var param1 = new DDNDataParameter("@catalog", DbType.String) { Value = DBNull.Value };
+ var param2 = new DDNDataParameter("@schema", DbType.String) { Value = DBNull.Value };
+ var param3 = new DDNDataParameter("@table", DbType.String) { Value = DBNull.Value };
+ var param4 = new DDNDataParameter("@column", DbType.String) { Value = "fi%" };
+
+ // Add parameters
+ command.Parameters.Add(param1);
+ command.Parameters.Add(param2);
+ command.Parameters.Add(param3);
+ command.Parameters.Add(param4);
+
+ using (IDataReader reader = command.ExecuteReader())
+ {
+ // Print column names
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader.GetName(i)}\t");
+ }
+ Console.WriteLine();
+
+ // Print data
+ while (reader.Read())
+ {
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader[i]}\t");
+ }
+ Console.WriteLine();
+ }
+ }
+ }
+
+ // Get Columns
+ Console.WriteLine("\n=== Plain old SQL for Customer Table ===");
+ using (IDbCommand command = connection.CreateCommand())
+ {
+ command.CommandText = "SELECT * FROM Customer";
+
+ using (IDataReader reader = command.ExecuteReader())
+ {
+ // Print column names
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader.GetName(i)}\t");
+ }
+ Console.WriteLine();
+
+ // Print data
+ while (reader.Read())
+ {
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader[i]}\t");
+ }
+ Console.WriteLine();
+ }
+ }
+ }
+
+ // Get Primary Keys
+ Console.WriteLine("\n=== Primary Keys ===");
+ using (IDbCommand command = connection.CreateCommand())
+ {
+ command.CommandText = "SQLPrimaryKeys";
+
+ using (IDataReader reader = command.ExecuteReader())
+ {
+ // Print column names
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader.GetName(i)}\t");
+ }
+ Console.WriteLine();
+
+ // Print data
+ while (reader.Read())
+ {
+ for (int i = 0; i < reader.FieldCount; i++)
+ {
+ Console.Write($"{reader[i]}\t");
+ }
+ Console.WriteLine();
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"\nError: {ex.Message}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/calcite-rs-jni/odbc/DDN-ODBC.sln b/calcite-rs-jni/odbc/DDN-ODBC.sln
new file mode 100644
index 0000000..d6de259
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DDN-ODBC-Tester", "DDN-ODBC-Tester\DDN-ODBC-Tester.csproj", "{BD626E9E-0270-49A1-8634-82513B607B30}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DDN-ODBC", "DDN-ODBC\DDN-ODBC.csproj", "{F0F0EDBF-19F1-4B16-B1CE-45F86526ABAD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {BD626E9E-0270-49A1-8634-82513B607B30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BD626E9E-0270-49A1-8634-82513B607B30}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BD626E9E-0270-49A1-8634-82513B607B30}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BD626E9E-0270-49A1-8634-82513B607B30}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F0F0EDBF-19F1-4B16-B1CE-45F86526ABAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F0F0EDBF-19F1-4B16-B1CE-45F86526ABAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F0F0EDBF-19F1-4B16-B1CE-45F86526ABAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F0F0EDBF-19F1-4B16-B1CE-45F86526ABAD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C87A2184-733D-4127-86EE-CDF175E7BDE5}
+ EndGlobalSection
+EndGlobal
diff --git a/calcite-rs-jni/odbc/DDN-ODBC/CommandParser.cs b/calcite-rs-jni/odbc/DDN-ODBC/CommandParser.cs
new file mode 100644
index 0000000..b115047
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC/CommandParser.cs
@@ -0,0 +1,74 @@
+using System.Text.RegularExpressions;
+
+namespace DDN_ODBC;
+
+public class CommandParser
+{
+ private static readonly Regex CallCommandRegex = new(@"{\s*CALL\s+(?\w+)\s*\((?[^)]*)\)\s*}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private static readonly Regex SimpleCommandRegex = new(@"^(?\w+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ private static readonly Regex ParameterRegex = new(@"\?", RegexOptions.Compiled);
+
+ private readonly HashSet _validCommands;
+
+ public CommandParser(IEnumerable validCommands)
+ {
+ _validCommands = new HashSet(validCommands, StringComparer.OrdinalIgnoreCase);
+ }
+
+ public CommandParser()
+ {
+ var validCommands = new List
+ {
+ "SQLTables",
+ "SQLColumns",
+ "SQLProcedures",
+ "SQLProcedureColumns",
+ "SQLPrimaryKeys",
+ "SQLForeignKeys",
+ "SQLStatistics",
+ "SQLSpecialColumns",
+ "SQLGetTypeInfo",
+ "SQLTablePrivileges",
+ "SQLColumnPrivileges",
+ "SQLGetFunctions"
+ };
+ _validCommands = new HashSet(validCommands, StringComparer.OrdinalIgnoreCase);
+ }
+
+ public bool TryParseCommand(string commandText, out string command, out int parameterCount)
+ {
+ command = null;
+ parameterCount = 0;
+
+ // Try to match the CALL command format
+ var match = CallCommandRegex.Match(commandText);
+ if (match.Success)
+ {
+ command = match.Groups["command"].Value;
+ if (!_validCommands.Contains(command))
+ {
+ return false;
+ }
+
+ string paramsGroup = match.Groups["params"].Value;
+ parameterCount = 0;
+ if (!string.IsNullOrEmpty(paramsGroup))
+ {
+ parameterCount = ParameterRegex.Matches(paramsGroup).Count;
+ }
+
+ return true;
+ }
+
+ // Try to match the simple command format
+ match = SimpleCommandRegex.Match(commandText);
+ if (match.Success)
+ {
+ command = match.Groups["command"].Value;
+ return _validCommands.Contains(command);
+ }
+
+ // No match
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/calcite-rs-jni/odbc/DDN-ODBC/DDN-ODBC.csproj b/calcite-rs-jni/odbc/DDN-ODBC/DDN-ODBC.csproj
new file mode 100644
index 0000000..f71683a
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC/DDN-ODBC.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net8.0
+ DDN_ODBC
+ enable
+ enable
+
+
+
+
+
+
+ ./Resources/sqlengine-1.0.0-jar-with-dependencies.jar
+
+
+
+
+
+
+
+
diff --git a/calcite-rs-jni/odbc/DDN-ODBC/DDNColumnMetadata.cs b/calcite-rs-jni/odbc/DDN-ODBC/DDNColumnMetadata.cs
new file mode 100644
index 0000000..d6bcd38
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC/DDNColumnMetadata.cs
@@ -0,0 +1,96 @@
+namespace DDN_ODBC;
+using Newtonsoft.Json;
+public class DDNColumnMetadata
+{
+ [JsonProperty("tableCat")]
+ public string? TableCatalog { get; set; }
+
+ [JsonProperty("tableSchem")]
+ public string? TableSchema { get; set; }
+
+ [JsonProperty("tableName")]
+ public string? TableName { get; set; }
+
+ [JsonProperty("columnName")]
+ public string? ColumnName { get; set; }
+
+ [JsonProperty("dataType")]
+ public int DataType { get; set; }
+
+ [JsonProperty("typeName")]
+ public string? TypeName { get; set; }
+
+ [JsonProperty("columnSize")]
+ public int ColumnSize { get; set; }
+
+ [JsonProperty("bufferLength")]
+ public int? BufferLength { get; set; }
+
+ [JsonProperty("decimalDigits")]
+ public int? DecimalDigits { get; set; }
+
+ [JsonProperty("numPrecRadix")]
+ public int NumPrecRadix { get; set; }
+
+ [JsonProperty("nullable")]
+ public int Nullable { get; set; }
+
+ [JsonProperty("remarks")]
+ public string? Remarks { get; set; }
+
+ [JsonProperty("columnDef")]
+ public string? ColumnDefault { get; set; }
+
+ [JsonProperty("sqlDataType")]
+ public int? SqlDataType { get; set; }
+
+ [JsonProperty("sqlDatetimeSub")]
+ public int? SqlDatetimeSub { get; set; }
+
+ [JsonProperty("charOctetLength")]
+ public int CharOctetLength { get; set; }
+
+ [JsonProperty("ordinalPosition")]
+ public int OrdinalPosition { get; set; }
+
+ [JsonProperty("isNullable")]
+ public string? IsNullable { get; set; }
+
+ [JsonProperty("scopeCatalog")]
+ public string? ScopeCatalog { get; set; }
+
+ [JsonProperty("scopeSchema")]
+ public string? ScopeSchema { get; set; }
+
+ [JsonProperty("scopeTable")]
+ public string? ScopeTable { get; set; }
+
+ [JsonProperty("sourceDataType")]
+ public short? SourceDataType { get; set; }
+
+ [JsonProperty("isAutoincrement")]
+ public string? IsAutoincrement { get; set; }
+
+ [JsonProperty("isGeneratedcolumn")]
+ public string? IsGeneratedColumn { get; set; }
+}
+
+// Usage example:
+/*
+using System.Text.Json;
+
+// Deserialize JSON
+var columnMetadata = JsonSerializer.Deserialize(jsonString);
+
+// Get ODBC type information
+short odbcType = columnMetadata.GetOdbcType();
+string odbcTypeName = columnMetadata.GetOdbcTypeName();
+
+// Check type categories
+bool isString = columnMetadata.IsStringType();
+bool isNumeric = columnMetadata.IsNumericType();
+bool isTemporal = columnMetadata.IsTemporalType();
+
+// Work with nullability
+bool isNullable = columnMetadata.Nullable == ColumnMetadata.SQL_NULLABLE;
+*/
\ No newline at end of file
diff --git a/calcite-rs-jni/odbc/DDN-ODBC/DDNDataParameter.cs b/calcite-rs-jni/odbc/DDN-ODBC/DDNDataParameter.cs
new file mode 100644
index 0000000..61bcb01
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC/DDNDataParameter.cs
@@ -0,0 +1,76 @@
+using System.Data;
+using System.Data.Common;
+
+namespace DDN_ODBC;
+
+public class DDNDataParameter : DbParameter
+{
+ private string _parameterName = string.Empty;
+ private string _sourceColumn = string.Empty;
+ private object? _value;
+ private DbType _dbType = DbType.String;
+ private ParameterDirection _direction = ParameterDirection.Input; // Since read-only
+ private int _size;
+
+ public DDNDataParameter(string parameterName, DbType dbType)
+ {
+ ParameterName = parameterName;
+ DbType = dbType;
+ }
+
+ public override DbType DbType
+ {
+ get => _dbType;
+ set => _dbType = value;
+ }
+
+ public override ParameterDirection Direction
+ {
+ get => _direction;
+ set
+ {
+ if (value != ParameterDirection.Input)
+ throw new NotSupportedException("Only input parameters are supported in read-only mode.");
+ _direction = value;
+ }
+ }
+
+ public override string ParameterName
+ {
+ get => _parameterName;
+ set => _parameterName = value ?? string.Empty;
+ }
+
+ public override string SourceColumn
+ {
+ get => _sourceColumn;
+ set => _sourceColumn = value ?? string.Empty;
+ }
+
+ public override bool SourceColumnNullMapping { get; set; }
+
+ public override DataRowVersion SourceVersion { get; set; } = DataRowVersion.Current;
+
+ public override object? Value
+ {
+ get => _value;
+ set => _value = value;
+ }
+
+ public override int Size
+ {
+ get => _size;
+ set
+ {
+ if (value < 0)
+ throw new ArgumentException("Size must be non-negative.", nameof(value));
+ _size = value;
+ }
+ }
+
+ public override bool IsNullable { get; set; }
+
+ public override void ResetDbType() => _dbType = DbType.String;
+
+ public override string ToString() => ParameterName;
+}
\ No newline at end of file
diff --git a/calcite-rs-jni/odbc/DDN-ODBC/DDNDataParameterCollection.cs b/calcite-rs-jni/odbc/DDN-ODBC/DDNDataParameterCollection.cs
new file mode 100644
index 0000000..0f4d7d1
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC/DDNDataParameterCollection.cs
@@ -0,0 +1,97 @@
+using System.Collections;
+using System.Data;
+
+namespace DDN_ODBC;
+
+public class DDNDataParameterCollection : IDataParameterCollection
+{
+ private readonly List _parameters = new();
+
+ public object this[string parameterName]
+ {
+ get => _parameters.Find(p => p.ParameterName == parameterName);
+ set
+ {
+ var index = _parameters.FindIndex(p => p.ParameterName == parameterName);
+ if (index != -1)
+ _parameters[index] = (DDNDataParameter)value;
+ else
+ _parameters.Add((DDNDataParameter)value);
+ }
+ }
+
+ public object this[int index]
+ {
+ get => _parameters[index];
+ set => _parameters[index] = (DDNDataParameter)value;
+ }
+
+ public bool Contains(string parameterName)
+ {
+ return _parameters.Exists(p => p.ParameterName == parameterName);
+ }
+
+ public int IndexOf(string parameterName)
+ {
+ return _parameters.FindIndex(p => p.ParameterName == parameterName);
+ }
+
+ public void RemoveAt(string parameterName)
+ {
+ var index = IndexOf(parameterName);
+ if (index != -1)
+ _parameters.RemoveAt(index);
+ }
+
+ public int Count => _parameters.Count;
+ public bool IsReadOnly => false;
+ public bool IsFixedSize => false;
+ public bool IsSynchronized => false;
+ public object SyncRoot => null;
+
+ public int Add(object value)
+ {
+ _parameters.Add((DDNDataParameter)value);
+ return _parameters.Count - 1;
+ }
+
+ public void Clear()
+ {
+ _parameters.Clear();
+ }
+
+ public bool Contains(object value)
+ {
+ return _parameters.Contains((DDNDataParameter)value);
+ }
+
+ public int IndexOf(object value)
+ {
+ return _parameters.IndexOf((DDNDataParameter)value);
+ }
+
+ public void Insert(int index, object value)
+ {
+ _parameters.Insert(index, (DDNDataParameter)value);
+ }
+
+ public void Remove(object value)
+ {
+ _parameters.Remove((DDNDataParameter)value);
+ }
+
+ public void RemoveAt(int index)
+ {
+ _parameters.RemoveAt(index);
+ }
+
+ public void CopyTo(Array array, int index)
+ {
+ _parameters.CopyTo((DDNDataParameter[])array, index);
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return _parameters.GetEnumerator();
+ }
+}
\ No newline at end of file
diff --git a/calcite-rs-jni/odbc/DDN-ODBC/DDNParameterCollection.cs b/calcite-rs-jni/odbc/DDN-ODBC/DDNParameterCollection.cs
new file mode 100644
index 0000000..5297907
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC/DDNParameterCollection.cs
@@ -0,0 +1,89 @@
+using System.Collections;
+using System.Data;
+using System.Data.Common;
+
+namespace DDN_ODBC;
+
+public class DDNParameterCollection : DbParameterCollection
+{
+ private readonly List _parameters = new();
+
+ public override int Count => _parameters.Count;
+
+ public override object SyncRoot => ((ICollection)_parameters).SyncRoot;
+
+ public override int Add(object value)
+ {
+ if (value is not DbParameter parameter)
+ throw new ArgumentException("Value must be a DbParameter");
+
+ _parameters.Add(parameter);
+ return _parameters.Count - 1;
+ }
+
+ public override void AddRange(Array values)
+ {
+ foreach (var value in values)
+ Add(value);
+ }
+
+ public override void Clear() => _parameters.Clear();
+
+ public override bool Contains(object value) =>
+ value is DbParameter parameter && _parameters.Contains(parameter);
+
+ public override bool Contains(string value) =>
+ _parameters.Any(p => string.Equals(p.ParameterName, value, StringComparison.OrdinalIgnoreCase));
+
+ public override void CopyTo(Array array, int index) =>
+ ((ICollection)_parameters).CopyTo(array, index);
+
+ public override IEnumerator GetEnumerator() => _parameters.GetEnumerator();
+
+ public override int IndexOf(object value) =>
+ value is DbParameter parameter ? _parameters.IndexOf(parameter) : -1;
+
+ public override int IndexOf(string parameterName) =>
+ _parameters.FindIndex(p => string.Equals(p.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase));
+
+ public override void Insert(int index, object value)
+ {
+ if (value is not DbParameter parameter)
+ throw new ArgumentException("Value must be a DbParameter");
+ _parameters.Insert(index, parameter);
+ }
+
+ public override void Remove(object value)
+ {
+ if (value is DbParameter parameter)
+ _parameters.Remove(parameter);
+ }
+
+ public override void RemoveAt(int index) => _parameters.RemoveAt(index);
+
+ public override void RemoveAt(string parameterName)
+ {
+ var index = IndexOf(parameterName);
+ if (index >= 0)
+ RemoveAt(index);
+ }
+
+ protected override DbParameter GetParameter(int index) => _parameters[index];
+
+ protected override DbParameter GetParameter(string parameterName) =>
+ _parameters.FirstOrDefault(p => string.Equals(p.ParameterName, parameterName, StringComparison.OrdinalIgnoreCase))
+ ?? throw new ArgumentException($"Parameter '{parameterName}' not found.", nameof(parameterName));
+
+ protected override void SetParameter(int index, DbParameter value) =>
+ _parameters[index] = value ?? throw new ArgumentNullException(nameof(value));
+
+ protected override void SetParameter(string parameterName, DbParameter value)
+ {
+ if (value == null) throw new ArgumentNullException(nameof(value));
+ var index = IndexOf(parameterName);
+ if (index >= 0)
+ _parameters[index] = value;
+ else
+ Add(value);
+ }
+}
\ No newline at end of file
diff --git a/calcite-rs-jni/odbc/DDN-ODBC/DDNTableMetadata.cs b/calcite-rs-jni/odbc/DDN-ODBC/DDNTableMetadata.cs
new file mode 100644
index 0000000..3e7d4d5
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC/DDNTableMetadata.cs
@@ -0,0 +1,16 @@
+using Newtonsoft.Json;
+
+namespace DDN_ODBC;
+
+public class DDNTableMetadata
+{
+ [JsonProperty("tableSchem")] public string TABLE_SCHEM { get; set; }
+
+ [JsonProperty("tableType")] public string TABLE_TYPE { get; set; }
+
+ [JsonProperty("tableName")] public string TABLE_NAME { get; set; }
+
+ [JsonProperty("tableCat")] public string TABLE_CAT { get; set; }
+
+ [JsonProperty("remarks")] public string REMARKS { get; set; }
+}
\ No newline at end of file
diff --git a/calcite-rs-jni/odbc/DDN-ODBC/DDN_ODBC.cs b/calcite-rs-jni/odbc/DDN-ODBC/DDN_ODBC.cs
new file mode 100644
index 0000000..885509b
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC/DDN_ODBC.cs
@@ -0,0 +1,499 @@
+using System.Data;
+using System.Data.Common;
+using System.Diagnostics;
+using System.Reflection;
+using System.Text;
+using Newtonsoft.Json;
+
+namespace DDN_ODBC;
+
+using System;
+public class DDN_ODBC : DbConnection
+{
+ private readonly CancellationTokenSource _cancellationTokenSource = new();
+ private readonly HttpClient _httpClient;
+ private readonly int _javaAppPort;
+ private readonly string _javaDDNSQLEngine;
+ private string _connectionString;
+ private Process _javaProcess;
+ private ConnectionState _state = ConnectionState.Closed;
+
+ public DDN_ODBC(string connectionString)
+ {
+ _connectionString = connectionString;
+ _javaDDNSQLEngine = ExtractEmbeddedJar();
+ _javaAppPort = GetRandomPort();
+ _httpClient = new HttpClient
+ {
+ Timeout = TimeSpan.FromSeconds(30)
+ };
+ }
+
+ private string ExtractEmbeddedJar()
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ var resourceName = assembly.GetManifestResourceNames()
+ .Single(str => str.EndsWith("sqlengine-1.0.0-jar-with-dependencies.jar"));
+
+ // Create temp directory if it doesn't exist
+ var tempPath = Path.Combine(Path.GetTempPath(), "DDN_ODBC");
+ Directory.CreateDirectory(tempPath);
+
+ var jarPath = Path.Combine(tempPath, "sqlengine-1.0.0-jar-with-dependencies.jar");
+
+ // Only extract if it doesn't exist
+ if (!File.Exists(jarPath))
+ {
+ using (var stream = assembly.GetManifestResourceStream(resourceName))
+ using (var file = new FileStream(jarPath, FileMode.Create, FileAccess.Write))
+ {
+ stream.CopyTo(file);
+ }
+ }
+
+ return jarPath;
+ }
+
+ #region IDisposable Implementation
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ try
+ {
+ // Cancel any pending operations
+ _cancellationTokenSource.Cancel();
+
+ // Close the connection if it's open
+ if (_state != ConnectionState.Closed)
+ Close();
+
+ // Dispose of managed resources
+ _cancellationTokenSource.Dispose();
+ _httpClient.Dispose();
+ _javaProcess?.Dispose();
+ }
+ catch
+ {
+ // Log disposal errors if you have logging
+ }
+
+ base.Dispose(disposing);
+ }
+
+ #endregion
+
+ #region Required DbConnection Implementation
+
+ public override string ConnectionString
+ {
+ get => _connectionString;
+ set
+ {
+ if (_state != ConnectionState.Closed)
+ throw new InvalidOperationException("Cannot change connection string while connection is open.");
+ _connectionString = value;
+ }
+ }
+
+ public override string Database => string.Empty; // Or return actual database name if applicable
+
+ public override string DataSource => $"http://localhost:{_javaAppPort}";
+
+ public override string ServerVersion => "1.0.0"; // Return your actual server version
+
+ public override ConnectionState State => _state;
+
+ protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
+ {
+ throw new NotSupportedException("Transactions are not supported in read-only mode.");
+ }
+
+ public override void ChangeDatabase(string databaseName)
+ {
+ throw new NotSupportedException("Changing database is not supported.");
+ }
+
+ protected override DbCommand CreateDbCommand()
+ {
+ if (_state != ConnectionState.Open)
+ throw new InvalidOperationException("Cannot create command on closed connection.");
+ return new DDN_OdbcCommand(this);
+ }
+
+ #endregion
+
+ #region Open/Close Implementation
+
+ public override void Open()
+ {
+ if (_state == ConnectionState.Open)
+ return;
+
+ try
+ {
+ StartJavaApplication();
+
+ // Wait for the server to start (with timeout)
+ var startTime = DateTime.Now;
+ var timeout = TimeSpan.FromSeconds(30);
+ var success = false;
+
+ while (DateTime.Now - startTime < timeout)
+ try
+ {
+ var response = GetRequest("/health");
+ if (response.Contains("OK")) // Adjust based on your health check response
+ {
+ success = true;
+ break;
+ }
+ }
+ catch
+ {
+ // Server might not be ready yet
+ Thread.Sleep(100);
+ }
+
+ if (!success)
+ throw new InvalidOperationException("Failed to start Java application server.");
+
+ _state = ConnectionState.Open;
+ }
+ catch (Exception ex)
+ {
+ _state = ConnectionState.Closed;
+ throw new InvalidOperationException("Failed to open connection.", ex);
+ }
+ }
+
+ public override async Task OpenAsync(CancellationToken cancellationToken)
+ {
+ if (_state == ConnectionState.Open)
+ return;
+
+ try
+ {
+ await StartJavaApplicationAsync(cancellationToken);
+
+ // Wait for the server to start (with timeout)
+ using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
+ cancellationToken,
+ _cancellationTokenSource.Token);
+
+ var startTime = DateTime.Now;
+ var timeout = TimeSpan.FromSeconds(30);
+ var success = false;
+
+ while (DateTime.Now - startTime < timeout)
+ try
+ {
+ linkedCts.Token.ThrowIfCancellationRequested();
+ var response = await GetRequestAsync("/health", linkedCts.Token);
+ if (response.Contains("OK")) // Adjust based on your health check response
+ {
+ success = true;
+ break;
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch
+ {
+ // Server might not be ready yet
+ await Task.Delay(100, linkedCts.Token);
+ }
+
+ if (!success)
+ throw new InvalidOperationException("Failed to start Java application server.");
+
+ _state = ConnectionState.Open;
+ }
+ catch (Exception ex)
+ {
+ _state = ConnectionState.Closed;
+ if (ex is OperationCanceledException)
+ throw;
+ throw new InvalidOperationException("Failed to open connection.", ex);
+ }
+ }
+
+ public override void Close()
+ {
+ if (_state == ConnectionState.Closed)
+ return;
+
+ try
+ {
+ _cancellationTokenSource.Cancel();
+ TerminateJavaApplication();
+ }
+ finally
+ {
+ _state = ConnectionState.Closed;
+ }
+ }
+
+ #endregion
+
+ #region HTTP Request Helpers
+
+ private async Task GetRequestAsync(string action, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var uri = new Uri($"http://localhost:{_javaAppPort}{action}");
+ var response = await _httpClient.GetAsync(uri, cancellationToken);
+ response.EnsureSuccessStatusCode();
+ return await response.Content.ReadAsStringAsync(cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException($"Failed to execute GET request to {action}", ex);
+ }
+ }
+
+ private string GetRequest(string action)
+ {
+ try
+ {
+ var uri = new Uri($"http://localhost:{_javaAppPort}{action}");
+ var response = _httpClient.GetAsync(uri).Result;
+ response.EnsureSuccessStatusCode();
+ return response.Content.ReadAsStringAsync().Result;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException($"Failed to execute GET request to {action}", ex);
+ }
+ }
+
+ public async Task PostRequestAsync(string action, string postData, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var query = new SqlQuery
+ {
+ Sql = postData,
+ DisallowMutations = true // Force read-only mode
+ };
+
+ var jsonString = JsonConvert.SerializeObject(query);
+ var content = new StringContent(jsonString, Encoding.UTF8, "application/json");
+ var uri = new Uri($"http://localhost:{_javaAppPort}{action}");
+
+ var response = await _httpClient.PostAsync(uri, content, cancellationToken);
+ response.EnsureSuccessStatusCode();
+ return await response.Content.ReadAsStringAsync(cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException($"Failed to execute POST request to {action}", ex);
+ }
+ }
+
+ public async Task GetTablesAsync(string wherePhrase = "*", CancellationToken cancellationToken = default)
+ {
+ return await PostRequestAsync("/v1/sql", $"SELECT * FROM \"metadata\".TABLES {wherePhrase}", cancellationToken);
+ }
+ public async Task GetColumnsAsync(string wherePhrase = "*", CancellationToken cancellationToken = default)
+ {
+ return await PostRequestAsync("/v1/sql", $"SELECT * FROM \"metadata\".COLUMNS {wherePhrase}", cancellationToken);
+ }
+
+ public string GetTables(DDNParameterCollection parameters)
+ {
+ string result = "";
+ if (parameters.Count == 0)
+ {
+ result = "";
+ }
+ else
+ {
+ List columns = new List();
+ foreach (DDNDataParameter parameter in parameters)
+ {
+ switch (parameter.ParameterName.Replace("@", "").ToUpper())
+ {
+ case "CATALOG":
+ if (parameter.Value != DBNull.Value)
+ {
+ columns.Add($"tableCat = '{parameter.Value}'");
+ }
+ break;
+ case "SCHEMA":
+ if (parameter.Value != DBNull.Value)
+ {
+ columns.Add($"tableSchem LIKE '{parameter.Value}'");
+ }
+ break;
+ case "TABLE":
+ if (parameter.Value != DBNull.Value)
+ {
+ columns.Add($"tableName LIKE '{parameter.Value}'");
+ }
+
+ break;
+ case "TABLETYPE":
+ if (parameter.Value != DBNull.Value)
+ {
+ columns.Add($"tableType LIKE '{parameter.Value}'");
+ }
+
+ break;
+ default:
+ throw new InvalidOperationException($"Invalid parameter name: {parameter.ParameterName}");
+ }
+ }
+ result = $"WHERE {string.Join(" AND ", columns)}";
+ }
+
+ var task = GetTablesAsync(result, _cancellationTokenSource.Token);
+ try
+ {
+ return task.Result;
+ }
+ catch (AggregateException ae)
+ {
+ throw ae.InnerException ?? ae;
+ }
+ }
+
+ public string GetColumns(DDNParameterCollection parameters)
+ {
+ string result = "";
+ if (parameters.Count == 0)
+ {
+ result = "";
+ }
+ else
+ {
+ List columns = new List();
+ foreach (DDNDataParameter parameter in parameters)
+ {
+ switch (parameter.ParameterName.Replace("@", "").ToUpper())
+ {
+ case "CATALOG":
+ if (parameter.Value != DBNull.Value)
+ {
+ columns.Add($"tableCat = '{parameter.Value}'");
+ }
+ break;
+ case "SCHEMA":
+ if (parameter.Value != DBNull.Value)
+ {
+ columns.Add($"tableSchem LIKE '{parameter.Value}'");
+ }
+ break;
+ case "TABLE":
+ if (parameter.Value != DBNull.Value)
+ {
+ columns.Add($"tableName LIKE '{parameter.Value}'");
+ }
+
+ break;
+ case "COLUMN":
+ if (parameter.Value != DBNull.Value)
+ {
+ columns.Add($"columnName LIKE '{parameter.Value}'");
+ }
+
+ break;
+ default:
+ throw new InvalidOperationException($"Invalid parameter name: {parameter.ParameterName}");
+ }
+ }
+ result = $"WHERE {string.Join(" AND ", columns)}";
+ }
+ var task = GetColumnsAsync(result, _cancellationTokenSource.Token);
+ try
+ {
+ return task.Result;
+ }
+ catch (AggregateException ae)
+ {
+ throw ae.InnerException ?? ae;
+ }
+ }
+ #endregion
+
+ #region Java Process Management
+
+ private void StartJavaApplication()
+ {
+ var javaHomePath = Environment.GetEnvironmentVariable("JAVA_HOME")
+ ?? throw new InvalidOperationException("JAVA_HOME environment variable is not set.");
+
+ var javaExecutablePath = Path.Combine(javaHomePath, "bin", "java");
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = javaExecutablePath,
+ Arguments = $"-classpath {_javaDDNSQLEngine} com.hasura.SQLHttpServer",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ };
+
+ startInfo.EnvironmentVariables["PORT"] = _javaAppPort.ToString();
+ startInfo.EnvironmentVariables["JDBC_URL"] = _connectionString;
+
+ _javaProcess = new Process { StartInfo = startInfo };
+
+ _javaProcess.OutputDataReceived += (sender, args) =>
+ {
+ if (args.Data != null)
+ Debug.WriteLine($"[Java Process Output]: {args.Data}");
+ };
+
+ _javaProcess.ErrorDataReceived += (sender, args) =>
+ {
+ if (args.Data != null)
+ Debug.WriteLine($"[Java Process Error]: {args.Data}");
+ };
+
+ _javaProcess.Start();
+ _javaProcess.BeginOutputReadLine();
+ _javaProcess.BeginErrorReadLine();
+ }
+
+ private async Task StartJavaApplicationAsync(CancellationToken cancellationToken)
+ {
+ await Task.Run(() => StartJavaApplication(), cancellationToken);
+ }
+
+ private void TerminateJavaApplication()
+ {
+ if (_javaProcess != null && !_javaProcess.HasExited)
+ try
+ {
+ _javaProcess.Kill(true);
+ _javaProcess.WaitForExit(5000);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Error terminating Java process: {ex.Message}");
+ }
+ }
+
+ private int GetRandomPort()
+ {
+ var random = new Random();
+ return random.Next(8081, 65535);
+ }
+
+ #endregion
+}
+
+// Parameter Collection Implementation
\ No newline at end of file
diff --git a/calcite-rs-jni/odbc/DDN-ODBC/DDN_OdbcCommand.cs b/calcite-rs-jni/odbc/DDN-ODBC/DDN_OdbcCommand.cs
new file mode 100644
index 0000000..be4a4ba
--- /dev/null
+++ b/calcite-rs-jni/odbc/DDN-ODBC/DDN_OdbcCommand.cs
@@ -0,0 +1,428 @@
+using System.Data;
+using System.Data.Common;
+using Newtonsoft.Json;
+
+namespace DDN_ODBC;
+
+public class DDN_OdbcCommand : DbCommand
+{
+ private readonly DDN_ODBC _connection;
+ private readonly DDNParameterCollection _parameters;
+ private string _commandText;
+ private int _commandTimeout = 30; // Default 30 seconds
+ private CommandType _commandType = CommandType.Text;
+ private bool _isDisposed;
+ private UpdateRowSource _updatedRowSource = UpdateRowSource.None;
+ private int _parametersCount;
+ private CommandParser parser = new CommandParser();
+
+ public DDN_OdbcCommand(DDN_ODBC connection)
+ {
+ _connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ _parameters = new DDNParameterCollection();
+ }
+
+ #region IDisposable Implementation
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!_isDisposed)
+ {
+ if (disposing)
+ // Clean up managed resources
+ _commandText = null;
+ _isDisposed = true;
+ }
+
+ base.Dispose(disposing);
+ }
+
+ #endregion
+
+ #region DbCommand Property Implementations
+
+ public override string CommandText
+ {
+ get => _commandText;
+ set
+ {
+ ValidateNotDisposed();
+ _commandText = value;
+ }
+ }
+
+ public override int CommandTimeout
+ {
+ get => _commandTimeout;
+ set
+ {
+ ValidateNotDisposed();
+ if (value < 0)
+ throw new ArgumentOutOfRangeException(nameof(value), "Timeout must be non-negative.");
+ _commandTimeout = value;
+ }
+ }
+
+ public override CommandType CommandType
+ {
+ get => _commandType;
+ set
+ {
+ ValidateNotDisposed();
+ if (!Enum.IsDefined(typeof(CommandType), value))
+ throw new ArgumentOutOfRangeException(nameof(value));
+ _commandType = value;
+ }
+ }
+
+ public override UpdateRowSource UpdatedRowSource
+ {
+ get => _updatedRowSource;
+ set
+ {
+ ValidateNotDisposed();
+ if (!Enum.IsDefined(typeof(UpdateRowSource), value))
+ throw new ArgumentOutOfRangeException(nameof(value));
+ _updatedRowSource = value;
+ }
+ }
+
+ protected override DbConnection DbConnection
+ {
+ get => _connection;
+ set => throw new NotSupportedException("Changing the connection is not supported.");
+ }
+
+ protected override DbParameterCollection DbParameterCollection => _parameters;
+
+ protected override DbTransaction DbTransaction
+ {
+ get => null;
+ set => throw new NotSupportedException("Transactions are not supported in read-only mode.");
+ }
+
+ public override bool DesignTimeVisible { get; set; }
+
+ #endregion
+
+ #region Command Execution Methods
+
+ public override void Cancel()
+ {
+ ValidateNotDisposed();
+ throw new NotSupportedException("Command cancellation is not supported.");
+ }
+
+ protected override DbParameter CreateDbParameter()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override int ExecuteNonQuery()
+ {
+ ValidateNotDisposed();
+ throw new NotSupportedException("This is a read-only driver.");
+ }
+
+ public override async Task ExecuteNonQueryAsync(CancellationToken cancellationToken)
+ {
+ ValidateNotDisposed();
+ throw new NotSupportedException("This is a read-only driver.");
+ }
+
+ public override object ExecuteScalar()
+ {
+ ValidateNotDisposed();
+ using var reader = ExecuteDbDataReader(CommandBehavior.SingleRow);
+ return reader.Read() ? reader.GetValue(0) : null;
+ }
+
+ public override async Task