This is an automated email from the ASF dual-hosted git repository.
curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 01fe15ac4 feat(csharp/src/Drivers/Apache): enhance GetColumns with
BASE_TYPE_NAME column (#2695)
01fe15ac4 is described below
commit 01fe15ac4397c255ed4fc16104c2074304e06c67
Author: eric-wang-1990 <[email protected]>
AuthorDate: Mon Apr 21 19:18:16 2025 +0000
feat(csharp/src/Drivers/Apache): enhance GetColumns with BASE_TYPE_NAME
column (#2695)
## Summary
This PR enhances the C# ADBC driver's `GetColumns` functionality by
adding a new `BASE_TYPE_NAME` column to the result set. This column
provides the base type name without parameters or decorations, making it
easier for clients to identify the fundamental data type regardless of
its parameterization.
## Proposed Changes
- Added a new `BASE_TYPE_NAME` column to the `GetColumns` result schema
- Enhanced the `HiveServer2Statement` class to process and populate the
`BASE_TYPE_NAME` column
- Changed visibility modifiers for several methods from `protected` to
`public` or `internal` to enable the enhancement
- Added support for the `VARIANT` SQL type in the `SqlTypeNameParser`
- Created comprehensive unit tests to verify the functionality with
various data types
## Implementation Details
The implementation:
1. Intercepts `GetColumns` query results
2. Processes each row to extract the base type name using the existing
`SetPrecisionScaleAndTypeName` method
3. Adds the base type name as a new column in the result set
4. Preserves and potentially enhances precision and scale information
for parameterized types
## Benefits
- Simplifies client code that needs to identify base types (e.g., all
DECIMAL types regardless of precision/scale)
- Maintains compatibility with existing code as this is an additive
change
- Improves developer experience by providing clearer type information
## Testing
Added comprehensive unit tests that verify:
- The presence of the BASE_TYPE_NAME column in GetColumns results
- Correct base type extraction for simple types
- Correct base type extraction for complex types (DECIMAL, INTERVAL,
MAP, ARRAY, STRUCT)
- Precision and scale preservation for DECIMAL types
- Proper handling of type aliases (INT vs INTEGER)
---
.../Drivers/Apache/Hive2/HiveServer2Connection.cs | 11 +-
.../Apache/Hive2/HiveServer2HttpConnection.cs | 2 +-
.../Drivers/Apache/Hive2/HiveServer2Statement.cs | 128 +++++++++++-
.../src/Drivers/Apache/Hive2/SqlTypeNameParser.cs | 6 +-
.../src/Drivers/Apache/Impala/ImpalaConnection.cs | 2 +-
csharp/src/Drivers/Apache/Spark/SparkConnection.cs | 2 +-
csharp/test/Drivers/Databricks/StatementTests.cs | 216 +++++++++++++++++++++
7 files changed, 354 insertions(+), 13 deletions(-)
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2Connection.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2Connection.cs
index 215964284..9a17eb6c1 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2Connection.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2Connection.cs
@@ -768,7 +768,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
return baseAddress;
}
- protected IReadOnlyDictionary<string, int>
GetColumnIndexMap(List<TColumnDesc> columns) => columns
+ internal IReadOnlyDictionary<string, int>
GetColumnIndexMap(List<TColumnDesc> columns) => columns
.Select(t => new { Index = t.Position - ColumnMapIndexOffset,
t.ColumnName })
.ToDictionary(t => t.ColumnName, t => t.Index);
@@ -1242,12 +1242,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
nullBitmapBuffer.Build());
}
- protected abstract void SetPrecisionScaleAndTypeName(
- short colType,
- string typeName,
- TableInfo? tableInfo,
- int columnSize,
- int decimalDigits);
+ internal abstract void SetPrecisionScaleAndTypeName(short columnType,
string typeName, TableInfo? tableInfo, int columnSize, int decimalDigits);
public override Schema GetTableSchema(string? catalog, string?
dbSchema, string? tableName)
{
@@ -1364,7 +1359,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
}
}
- protected async Task<TRowSet> FetchResultsAsync(TOperationHandle
operationHandle, long batchSize = BatchSizeDefault, CancellationToken
cancellationToken = default)
+ internal async Task<TRowSet> FetchResultsAsync(TOperationHandle
operationHandle, long batchSize = BatchSizeDefault, CancellationToken
cancellationToken = default)
{
await PollForResponseAsync(operationHandle, Client,
PollTimeMillisecondsDefault, cancellationToken);
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
index aeee4b998..1c882b78c 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
@@ -224,7 +224,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
return req;
}
- protected override void SetPrecisionScaleAndTypeName(
+ internal override void SetPrecisionScaleAndTypeName(
short colType,
string typeName,
TableInfo? tableInfo,
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
index f43bad07a..607925333 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
@@ -21,6 +21,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Apache.Arrow.Ipc;
+using Apache.Arrow.Types;
using Apache.Hive.Service.Rpc.Thrift;
using Thrift.Transport;
@@ -406,7 +407,39 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
cancellationToken);
OperationHandle = resp.OperationHandle;
- return await GetQueryResult(resp.DirectResults, cancellationToken);
+ // Common variables declared upfront
+ TGetResultSetMetadataResp metadata;
+ Schema schema;
+ TRowSet rowSet;
+
+ // For GetColumns, we need to enhance the result with
BASE_TYPE_NAME
+ if (Connection.AreResultsAvailableDirectly() &&
resp.DirectResults?.ResultSet?.Results != null)
+ {
+ // Get data from direct results
+ metadata = resp.DirectResults.ResultSetMetadata;
+ schema =
Connection.SchemaParser.GetArrowSchema(metadata.Schema,
Connection.DataTypeConversion);
+ rowSet = resp.DirectResults.ResultSet.Results;
+ }
+ else
+ {
+ // Poll and fetch results
+ await
HiveServer2Connection.PollForResponseAsync(OperationHandle!, Connection.Client,
PollTimeMilliseconds, cancellationToken);
+
+ // Get metadata
+ metadata = await
HiveServer2Connection.GetResultSetMetadataAsync(OperationHandle!,
Connection.Client, cancellationToken);
+ schema =
Connection.SchemaParser.GetArrowSchema(metadata.Schema,
Connection.DataTypeConversion);
+
+ // Fetch the results
+ rowSet = await Connection.FetchResultsAsync(OperationHandle!,
BatchSize, cancellationToken);
+ }
+
+ // Common processing for both paths
+ int columnCount = HiveServer2Reader.GetColumnCount(rowSet);
+ int rowCount = HiveServer2Reader.GetRowCount(rowSet, columnCount);
+ IReadOnlyList<IArrowArray> data =
HiveServer2Reader.GetArrowArrayData(rowSet, columnCount, schema,
Connection.DataTypeConversion);
+
+ // Return the enhanced result with added BASE_TYPE_NAME column
+ return EnhanceGetColumnsResult(schema, data, rowCount, metadata,
rowSet);
}
private async Task<Schema> GetResultSetSchemaAsync(TOperationHandle
operationHandle, TCLIService.IAsync client, CancellationToken cancellationToken
= default)
@@ -426,12 +459,105 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
int columnCount = HiveServer2Reader.GetColumnCount(rowSet);
int rowCount = HiveServer2Reader.GetRowCount(rowSet,
columnCount);
IReadOnlyList<IArrowArray> data =
HiveServer2Reader.GetArrowArrayData(rowSet, columnCount, schema,
Connection.DataTypeConversion);
+
return new QueryResult(rowCount, new
HiveServer2Connection.HiveInfoArrowStream(schema, data));
}
await HiveServer2Connection.PollForResponseAsync(OperationHandle!,
Connection.Client, PollTimeMilliseconds, cancellationToken);
schema = await GetResultSetSchemaAsync(OperationHandle!,
Connection.Client, cancellationToken);
+
return new QueryResult(-1, Connection.NewReader(this, schema));
}
+
+ protected internal QueryResult EnhanceGetColumnsResult(Schema
originalSchema, IReadOnlyList<IArrowArray> originalData,
+ int rowCount, TGetResultSetMetadataResp metadata, TRowSet rowSet)
+ {
+ // Create a column map using Connection's GetColumnIndexMap method
+ var columnMap =
Connection.GetColumnIndexMap(metadata.Schema.Columns);
+
+ // Get column indices - we know these columns always exist
+ int typeNameIndex = columnMap["TYPE_NAME"];
+ int dataTypeIndex = columnMap["DATA_TYPE"];
+ int columnSizeIndex = columnMap["COLUMN_SIZE"];
+ int decimalDigitsIndex = columnMap["DECIMAL_DIGITS"];
+
+ // Extract the existing arrays
+ StringArray typeNames = (StringArray)originalData[typeNameIndex];
+ Int32Array originalColumnSizes =
(Int32Array)originalData[columnSizeIndex];
+ Int32Array originalDecimalDigits =
(Int32Array)originalData[decimalDigitsIndex];
+
+ // Create enhanced schema with BASE_TYPE_NAME column
+ var enhancedFields = originalSchema.FieldsList.ToList();
+ enhancedFields.Add(new Field("BASE_TYPE_NAME", StringType.Default,
true));
+ Schema enhancedSchema = new Schema(enhancedFields,
originalSchema.Metadata);
+
+ // Pre-allocate arrays to store our values
+ int length = typeNames.Length;
+ List<string> baseTypeNames = new List<string>(length);
+ List<int> columnSizeValues = new List<int>(length);
+ List<int> decimalDigitsValues = new List<int>(length);
+
+ // Process each row
+ for (int i = 0; i < length; i++)
+ {
+ string? typeName = typeNames.GetString(i);
+ short colType =
(short)rowSet.Columns[dataTypeIndex].I32Val.Values.Values[i];
+ int columnSize =
originalColumnSizes.GetValue(i).GetValueOrDefault();
+ int decimalDigits =
originalDecimalDigits.GetValue(i).GetValueOrDefault();
+
+ // Create a TableInfo for this row
+ var tableInfo = new
HiveServer2Connection.TableInfo(string.Empty);
+
+ // Process all types through SetPrecisionScaleAndTypeName
+ Connection.SetPrecisionScaleAndTypeName(colType, typeName ??
string.Empty, tableInfo, columnSize, decimalDigits);
+
+ // Get base type name
+ string baseTypeName;
+ if (tableInfo.BaseTypeName.Count > 0)
+ {
+ string? baseTypeNameValue = tableInfo.BaseTypeName[0];
+ baseTypeName = baseTypeNameValue ?? string.Empty;
+ }
+ else
+ {
+ baseTypeName = typeName ?? string.Empty;
+ }
+ baseTypeNames.Add(baseTypeName);
+
+ // Get precision/scale values
+ if (tableInfo.Precision.Count > 0)
+ {
+ int? precisionValue = tableInfo.Precision[0];
+
columnSizeValues.Add(precisionValue.GetValueOrDefault(columnSize));
+ }
+ else
+ {
+ columnSizeValues.Add(columnSize);
+ }
+
+ if (tableInfo.Scale.Count > 0)
+ {
+ int? scaleValue = tableInfo.Scale[0];
+
decimalDigitsValues.Add(scaleValue.GetValueOrDefault(decimalDigits));
+ }
+ else
+ {
+ decimalDigitsValues.Add(decimalDigits);
+ }
+ }
+
+ // Create the Arrow arrays directly from our data arrays
+ StringArray baseTypeNameArray = new
StringArray.Builder().AppendRange(baseTypeNames).Build();
+ Int32Array columnSizeArray = new
Int32Array.Builder().AppendRange(columnSizeValues).Build();
+ Int32Array decimalDigitsArray = new
Int32Array.Builder().AppendRange(decimalDigitsValues).Build();
+
+ // Create enhanced data with modified columns
+ var enhancedData = new List<IArrowArray>(originalData);
+ enhancedData[columnSizeIndex] = columnSizeArray;
+ enhancedData[decimalDigitsIndex] = decimalDigitsArray;
+ enhancedData.Add(baseTypeNameArray);
+
+ return new QueryResult(rowCount, new
HiveServer2Connection.HiveInfoArrowStream(enhancedSchema, enhancedData));
+ }
}
}
diff --git a/csharp/src/Drivers/Apache/Hive2/SqlTypeNameParser.cs
b/csharp/src/Drivers/Apache/Hive2/SqlTypeNameParser.cs
index bd7d285e2..4445334d0 100644
--- a/csharp/src/Drivers/Apache/Hive2/SqlTypeNameParser.cs
+++ b/csharp/src/Drivers/Apache/Hive2/SqlTypeNameParser.cs
@@ -89,7 +89,11 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
// Note: the INTERVAL sql type does not have an associated column type
id.
private static readonly HashSet<ISqlTypeNameParser> s_parsers = new
HashSet<ISqlTypeNameParser>(s_parserMap.Values
- .Concat([SqlIntervalTypeParser.Default,
SqlSimpleTypeParser.Default("VOID")]));
+ .Concat([
+ SqlIntervalTypeParser.Default,
+ SqlSimpleTypeParser.Default("VOID"),
+ SqlSimpleTypeParser.Default("VARIANT"),
+ ]));
/// <summary>
/// Gets the base SQL type name without decoration or sub clauses
diff --git a/csharp/src/Drivers/Apache/Impala/ImpalaConnection.cs
b/csharp/src/Drivers/Apache/Impala/ImpalaConnection.cs
index 3d51af521..c5d8e45c8 100644
--- a/csharp/src/Drivers/Apache/Impala/ImpalaConnection.cs
+++ b/csharp/src/Drivers/Apache/Impala/ImpalaConnection.cs
@@ -86,7 +86,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
protected internal override Task<TRowSet>
GetRowSetAsync(TGetPrimaryKeysResp response, CancellationToken
cancellationToken = default) =>
FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
- protected override void SetPrecisionScaleAndTypeName(
+ internal override void SetPrecisionScaleAndTypeName(
short colType,
string typeName,
TableInfo? tableInfo,
diff --git a/csharp/src/Drivers/Apache/Spark/SparkConnection.cs
b/csharp/src/Drivers/Apache/Spark/SparkConnection.cs
index 8b3014cc8..925073e35 100644
--- a/csharp/src/Drivers/Apache/Spark/SparkConnection.cs
+++ b/csharp/src/Drivers/Apache/Spark/SparkConnection.cs
@@ -63,7 +63,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Spark
protected internal override int PositionRequiredOffset => 1;
- protected override void SetPrecisionScaleAndTypeName(
+ internal override void SetPrecisionScaleAndTypeName(
short colType,
string typeName,
TableInfo? tableInfo,
diff --git a/csharp/test/Drivers/Databricks/StatementTests.cs
b/csharp/test/Drivers/Databricks/StatementTests.cs
index 16bc67bd4..f6f443807 100644
--- a/csharp/test/Drivers/Databricks/StatementTests.cs
+++ b/csharp/test/Drivers/Databricks/StatementTests.cs
@@ -18,10 +18,14 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Apache.Arrow.Adbc.Drivers.Apache;
using Apache.Arrow.Adbc.Drivers.Databricks;
using Apache.Arrow.Adbc.Tests.Drivers.Apache.Common;
+using Apache.Arrow.Adbc.Tests.Xunit;
+using Apache.Arrow.Types;
using Xunit;
using Xunit.Abstractions;
+using System.Linq;
namespace Apache.Arrow.Adbc.Tests.Drivers.Databricks
{
@@ -114,6 +118,218 @@ namespace Apache.Arrow.Adbc.Tests.Drivers.Databricks
await
base.CanGetCrossReferenceFromChildTable(TestConfiguration.Metadata.Catalog,
TestConfiguration.Metadata.Schema);
}
+ [SkippableFact]
+ public async Task CanGetColumnsWithBaseTypeName()
+ {
+ var statement = Connection.CreateStatement();
+ statement.SetOption(ApacheParameters.IsMetadataCommand, "true");
+ statement.SetOption(ApacheParameters.CatalogName,
TestConfiguration.Metadata.Catalog);
+ statement.SetOption(ApacheParameters.SchemaName,
TestConfiguration.Metadata.Schema);
+ statement.SetOption(ApacheParameters.TableName,
TestConfiguration.Metadata.Table);
+ statement.SqlQuery = "GetColumns";
+
+ QueryResult queryResult = await statement.ExecuteQueryAsync();
+ Assert.NotNull(queryResult.Stream);
+
+ // We should have 24 columns now (the original 23 + BASE_TYPE_NAME)
+ Assert.Equal(24, queryResult.Stream.Schema.FieldsList.Count);
+
+ // Verify the BASE_TYPE_NAME column is present
+ bool hasBaseTypeNameColumn = false;
+ int baseTypeNameIndex = -1;
+ int typeNameIndex = -1;
+
+ for (int i = 0; i < queryResult.Stream.Schema.FieldsList.Count;
i++)
+ {
+ if
(queryResult.Stream.Schema.FieldsList[i].Name.Equals("BASE_TYPE_NAME",
StringComparison.OrdinalIgnoreCase))
+ {
+ hasBaseTypeNameColumn = true;
+ baseTypeNameIndex = i;
+ }
+ else if
(queryResult.Stream.Schema.FieldsList[i].Name.Equals("TYPE_NAME",
StringComparison.OrdinalIgnoreCase))
+ {
+ typeNameIndex = i;
+ }
+ }
+
+ Assert.True(hasBaseTypeNameColumn, "BASE_TYPE_NAME column not
found in GetColumns result");
+ Assert.True(typeNameIndex >= 0, "TYPE_NAME column not found in
GetColumns result");
+
+ // Read batches and verify BASE_TYPE_NAME values
+ int actualBatchLength = 0;
+
+ // Track if we've seen specific complex types
+ bool foundDecimal = false;
+ bool foundInterval = false;
+ bool foundMap = false;
+ bool foundArray = false;
+ bool foundStruct = false;
+
+ Dictionary<string, string> typeNameToBaseTypeName = new
Dictionary<string, string>();
+
+ // For tracking decimal precision and scale
+ int columnSizeIndex = -1;
+ int decimalDigitsIndex = -1;
+ Dictionary<string, (int precision, short scale)> decimalTypeInfo =
new Dictionary<string, (int, short)>();
+
+ // Find COLUMN_SIZE and DECIMAL_DIGITS columns
+ for (int i = 0; i < queryResult.Stream.Schema.FieldsList.Count;
i++)
+ {
+ if
(queryResult.Stream.Schema.FieldsList[i].Name.Equals("COLUMN_SIZE",
StringComparison.OrdinalIgnoreCase))
+ {
+ columnSizeIndex = i;
+ }
+ else if
(queryResult.Stream.Schema.FieldsList[i].Name.Equals("DECIMAL_DIGITS",
StringComparison.OrdinalIgnoreCase))
+ {
+ decimalDigitsIndex = i;
+ }
+
+ if (columnSizeIndex >= 0 && decimalDigitsIndex >= 0)
+ break;
+ }
+
+ while (queryResult.Stream != null)
+ {
+ RecordBatch? batch = await
queryResult.Stream.ReadNextRecordBatchAsync();
+ if (batch == null)
+ {
+ break;
+ }
+
+ actualBatchLength += batch.Length;
+
+ // Verify relationships between TYPE_NAME and BASE_TYPE_NAME
for each row
+ for (int i = 0; i < batch.Length; i++)
+ {
+ string? typeName =
((StringArray)batch.Column(typeNameIndex)).GetString(i);
+ string? baseTypeName =
((StringArray)batch.Column(baseTypeNameIndex)).GetString(i);
+
+ // Store for later analysis
+ if (!string.IsNullOrEmpty(typeName) &&
!string.IsNullOrEmpty(baseTypeName))
+ {
+ typeNameToBaseTypeName[typeName] = baseTypeName;
+
+ // Collect precision and scale for DECIMAL types
+ if (typeName.StartsWith("DECIMAL(") && columnSizeIndex
>= 0 && decimalDigitsIndex >= 0)
+ {
+ int? precision =
((Int32Array)batch.Column(columnSizeIndex)).GetValue(i);
+ int? scale =
((Int32Array)batch.Column(decimalDigitsIndex)).GetValue(i);
+
+ if (precision.HasValue && scale.HasValue)
+ {
+ decimalTypeInfo[typeName] = (precision.Value,
(short)scale.Value);
+ }
+ }
+
+ // Track if we've found specific complex types
+ if (typeName.StartsWith("DECIMAL("))
+ foundDecimal = true;
+ else if (typeName.StartsWith("INTERVAL"))
+ foundInterval = true;
+ else if (typeName.StartsWith("MAP<"))
+ foundMap = true;
+ else if (typeName.StartsWith("ARRAY<"))
+ foundArray = true;
+ else if (typeName.StartsWith("STRUCT<"))
+ foundStruct = true;
+ }
+
+ // BASE_TYPE_NAME should not be null if TYPE_NAME is not
null
+ if (!string.IsNullOrEmpty(typeName))
+ {
+ Assert.NotNull(baseTypeName);
+
+ // BASE_TYPE_NAME should be contained within TYPE_NAME
or equal to it
+ // But we might have cases like "ARRAY<INT>" where
baseTypeName would be "ARRAY"
+ if (!typeName.Contains("<") && !typeName.Contains("(")
&& !typeName.Contains(" "))
+ {
+ // Simple types should match exactly, with special
handling for INT vs INTEGER
+ bool isEquivalentType =
+ typeName == baseTypeName ||
+ ((typeName == "INT" && baseTypeName ==
"INTEGER")) ||
+ ((typeName == "TIMESTAMP_NTZ" || typeName ==
"TIMESTAMP_LTZ") && baseTypeName == "TIMESTAMP");
+
+ Assert.True(isEquivalentType,
+ $"TypeName '{typeName}' should be equivalent
to BaseTypeName '{baseTypeName}'");
+ }
+ else
+ {
+ // Complex types should have BASE_TYPE_NAME as a
prefix (without parameters)
+ Assert.True(typeName.StartsWith(baseTypeName),
+ $"TypeName '{typeName}' should start with
BaseTypeName '{baseTypeName}'");
+
+ // The BASE_TYPE_NAME should not contain angle
brackets or parentheses
+ Assert.DoesNotContain("(", baseTypeName);
+ Assert.DoesNotContain("<", baseTypeName);
+ }
+
+ OutputHelper?.WriteLine($"TYPE_NAME: {typeName},
BASE_TYPE_NAME: {baseTypeName}");
+ }
+ }
+ }
+
+ // Specific tests for complex types - if we found them in the
results
+ if (foundDecimal)
+ {
+ string decimalTypeName = typeNameToBaseTypeName.Keys.First(k
=> k.StartsWith("DECIMAL("));
+ string decimalBaseTypeName =
typeNameToBaseTypeName[decimalTypeName];
+ Assert.Equal("DECIMAL", decimalBaseTypeName);
+ OutputHelper?.WriteLine($"Verified DECIMAL: {decimalTypeName}
-> {decimalBaseTypeName}");
+
+ // Extract precision and scale from the type name (e.g.,
"DECIMAL(38,10)" -> precision=38, scale=10)
+ string typePart =
decimalTypeName.Substring(decimalTypeName.IndexOf('(') + 1);
+ typePart = typePart.Remove(typePart.Length - 1); // Remove
closing parenthesis
+ string[] parts = typePart.Split(',');
+
+ int expectedPrecision = int.Parse(parts[0]);
+ int expectedScale = parts.Length > 1 ? int.Parse(parts[1]) : 0;
+
+ // Verify that the precision and scale from the data match
what's in the type name
+ Assert.True(decimalTypeInfo.ContainsKey(decimalTypeName),
+ $"Could not find precision and scale information for
{decimalTypeName}");
+
+ var (actualPrecision, actualScale) =
decimalTypeInfo[decimalTypeName];
+ Assert.Equal(expectedPrecision, actualPrecision);
+ Assert.Equal(expectedScale, actualScale);
+
+ OutputHelper?.WriteLine($"Verified DECIMAL precision/scale:
{decimalTypeName} -> precision={actualPrecision}, scale={actualScale}");
+ }
+
+ if (foundInterval)
+ {
+ string intervalTypeName = typeNameToBaseTypeName.Keys.First(k
=> k.StartsWith("INTERVAL"));
+ string intervalBaseTypeName =
typeNameToBaseTypeName[intervalTypeName];
+ Assert.Equal("INTERVAL", intervalBaseTypeName);
+ OutputHelper?.WriteLine($"Verified INTERVAL:
{intervalTypeName} -> {intervalBaseTypeName}");
+ }
+
+ if (foundMap)
+ {
+ string mapTypeName = typeNameToBaseTypeName.Keys.First(k =>
k.StartsWith("MAP<"));
+ string mapBaseTypeName = typeNameToBaseTypeName[mapTypeName];
+ Assert.Equal("MAP", mapBaseTypeName);
+ OutputHelper?.WriteLine($"Verified MAP: {mapTypeName} ->
{mapBaseTypeName}");
+ }
+
+ if (foundArray)
+ {
+ string arrayTypeName = typeNameToBaseTypeName.Keys.First(k =>
k.StartsWith("ARRAY<"));
+ string arrayBaseTypeName =
typeNameToBaseTypeName[arrayTypeName];
+ Assert.Equal("ARRAY", arrayBaseTypeName);
+ OutputHelper?.WriteLine($"Verified ARRAY: {arrayTypeName} ->
{arrayBaseTypeName}");
+ }
+
+ if (foundStruct)
+ {
+ string structTypeName = typeNameToBaseTypeName.Keys.First(k =>
k.StartsWith("STRUCT<"));
+ string structBaseTypeName =
typeNameToBaseTypeName[structTypeName];
+ Assert.Equal("STRUCT", structBaseTypeName);
+ OutputHelper?.WriteLine($"Verified STRUCT: {structTypeName} ->
{structBaseTypeName}");
+ }
+
+ Assert.Equal(TestConfiguration.Metadata.ExpectedColumnCount,
actualBatchLength);
+ }
+
protected override void PrepareCreateTableWithPrimaryKeys(out string
sqlUpdate, out string tableNameParent, out string fullTableNameParent, out
IReadOnlyList<string> primaryKeys)
{
CreateNewTableName(out tableNameParent, out fullTableNameParent);