Skip to content

Commit

Permalink
add StructType behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
David Coe committed Nov 23, 2024
1 parent e7ec53d commit 055fe84
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 56 deletions.
5 changes: 4 additions & 1 deletion csharp/src/Client/AdbcCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ internal AdbcCommand(AdbcStatement adbcStatement, AdbcConnection adbcConnection)
this._adbcStatement = adbcStatement;
this.DbConnection = adbcConnection;
this.DecimalBehavior = adbcConnection.DecimalBehavior;
this.StructBehavior = adbcConnection.StructBehavior;
}

/// <summary>
Expand All @@ -90,6 +91,8 @@ internal AdbcCommand(AdbcStatement adbcStatement, AdbcConnection adbcConnection)

public DecimalBehavior DecimalBehavior { get; set; }

public StructBehavior StructBehavior { get; set; }

public override string CommandText
{
get => AdbcStatement.SqlQuery ?? string.Empty;
Expand Down Expand Up @@ -204,7 +207,7 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
case CommandBehavior.SchemaOnly: // The schema is not known until a read happens
case CommandBehavior.Default:
QueryResult result = this.ExecuteQuery();
return new AdbcDataReader(this, result, this.DecimalBehavior, closeConnection);
return new AdbcDataReader(this, result, this.DecimalBehavior, this.StructBehavior, closeConnection);

default:
throw new InvalidOperationException($"{behavior} is not supported with this provider");
Expand Down
5 changes: 5 additions & 0 deletions csharp/src/Client/AdbcConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ internal AdbcStatement CreateStatement()
/// </summary>
public DecimalBehavior DecimalBehavior { get; set; }

/// <summary>
/// Indicates how structs should be treated.
/// </summary>
public StructBehavior StructBehavior { get; set; } = StructBehavior.JsonString;

protected override DbCommand CreateDbCommand()
{
EnsureConnectionOpen();
Expand Down
9 changes: 6 additions & 3 deletions csharp/src/Client/AdbcDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public sealed class AdbcDataReader : DbDataReader, IDbColumnSchemaGenerator
/// </remarks>
public event GetValueEventHandler? OnGetValue;

internal AdbcDataReader(AdbcCommand adbcCommand, QueryResult adbcQueryResult, DecimalBehavior decimalBehavior, bool closeConnection)
internal AdbcDataReader(AdbcCommand adbcCommand, QueryResult adbcQueryResult, DecimalBehavior decimalBehavior, StructBehavior structBehavior, bool closeConnection)
{
if (adbcCommand == null)
throw new ArgumentNullException(nameof(adbcCommand));
Expand All @@ -82,6 +82,7 @@ internal AdbcDataReader(AdbcCommand adbcCommand, QueryResult adbcQueryResult, De
this.closeConnection = closeConnection;
this.isClosed = false;
this.DecimalBehavior = decimalBehavior;
this.StructBehavior = structBehavior;
}

public override object this[int ordinal] => GetValue(ordinal);
Expand All @@ -103,6 +104,8 @@ internal AdbcDataReader(AdbcCommand adbcCommand, QueryResult adbcQueryResult, De

public DecimalBehavior DecimalBehavior { get; set; }

public StructBehavior StructBehavior { get; set; }

public override int RecordsAffected => this.recordsAffected;

/// <summary>
Expand Down Expand Up @@ -318,7 +321,7 @@ public override bool Read()

public override DataTable? GetSchemaTable()
{
return SchemaConverter.ConvertArrowSchema(this.schema, this.adbcCommand.AdbcStatement, this.DecimalBehavior);
return SchemaConverter.ConvertArrowSchema(this.schema, this.adbcCommand.AdbcStatement, this.DecimalBehavior, this.StructBehavior);
}

#if NET5_0_OR_GREATER
Expand All @@ -338,7 +341,7 @@ public ReadOnlyCollection<AdbcColumn> GetAdbcColumnSchema()

foreach (Field f in this.schema.FieldsList)
{
Type t = SchemaConverter.ConvertArrowType(f, this.DecimalBehavior);
Type t = SchemaConverter.ConvertArrowType(f, this.DecimalBehavior, this.StructBehavior);

if (f.HasMetadata &&
f.Metadata.ContainsKey("precision") &&
Expand Down
15 changes: 9 additions & 6 deletions csharp/src/Client/SchemaConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal class SchemaConverter
/// <param name="schema">The Arrow schema</param>
/// <param name="adbcStatement">The AdbcStatement to use</param>
/// <exception cref="ArgumentNullException"></exception>
public static DataTable ConvertArrowSchema(Schema schema, AdbcStatement adbcStatement, DecimalBehavior decimalBehavior)
public static DataTable ConvertArrowSchema(Schema schema, AdbcStatement adbcStatement, DecimalBehavior decimalBehavior, StructBehavior structBehavior)
{
if (schema == null)
throw new ArgumentNullException(nameof(schema));
Expand Down Expand Up @@ -61,7 +61,7 @@ public static DataTable ConvertArrowSchema(Schema schema, AdbcStatement adbcStat
row[SchemaTableColumn.ColumnOrdinal] = columnOrdinal;
row[SchemaTableColumn.AllowDBNull] = f.IsNullable;
row[SchemaTableColumn.ProviderType] = f.DataType;
Type t = ConvertArrowType(f, decimalBehavior);
Type t = ConvertArrowType(f, decimalBehavior, structBehavior);

row[SchemaTableColumn.DataType] = t;

Expand Down Expand Up @@ -111,7 +111,7 @@ public static DataTable ConvertArrowSchema(Schema schema, AdbcStatement adbcStat
/// </summary>
/// <param name="f"></param>
/// <returns></returns>
public static Type ConvertArrowType(Field f, DecimalBehavior decimalBehavior)
public static Type ConvertArrowType(Field f, DecimalBehavior decimalBehavior, StructBehavior structBehavior)
{
switch (f.DataType.TypeId)
{
Expand All @@ -120,11 +120,11 @@ public static Type ConvertArrowType(Field f, DecimalBehavior decimalBehavior)
IArrowType valueType = list.ValueDataType;
return GetArrowArrayType(valueType);
default:
return GetArrowType(f, decimalBehavior);
return GetArrowType(f, decimalBehavior, structBehavior);
}
}

public static Type GetArrowType(Field f, DecimalBehavior decimalBehavior)
public static Type GetArrowType(Field f, DecimalBehavior decimalBehavior, StructBehavior structBehavior)
{
switch (f.DataType.TypeId)
{
Expand Down Expand Up @@ -183,7 +183,10 @@ public static Type GetArrowType(Field f, DecimalBehavior decimalBehavior)
return typeof(string);

case ArrowTypeId.Struct:
goto default;
if (structBehavior == StructBehavior.JsonString)
return typeof(string);
else
goto default;

case ArrowTypeId.Timestamp:
return typeof(DateTimeOffset);
Expand Down
32 changes: 32 additions & 0 deletions csharp/src/Client/StructBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Apache.Arrow.Adbc.Client
{
public enum StructBehavior
{
/// <summary>
/// Serialized as a JSON string
/// </summary>
JsonString,

/// <summary>
/// Leave as native StructArray
/// </summary>
Strict
}
}
4 changes: 2 additions & 2 deletions csharp/test/Apache.Arrow.Adbc.Tests/Client/ClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private AdbcDataReader GetMoqDataReader(DecimalBehavior decimalBehavior, string

List<RecordBatch> records = new List<RecordBatch>()
{
new RecordBatch(schema, values, values.Count)
new RecordBatch(schema, values, array.Length)
};

MockArrayStream mockArrayStream = new MockArrayStream(schema, records);
Expand Down Expand Up @@ -150,7 +150,7 @@ private AdbcDataReader GetMoqDataReaderForIntegers()

List<RecordBatch> records = new List<RecordBatch>()
{
new RecordBatch(schema, values, numbersArray.Count())
new RecordBatch(schema, values, numbersArray.Length)
};

MockArrayStream mockArrayStream = new MockArrayStream(schema, records);
Expand Down
2 changes: 1 addition & 1 deletion csharp/test/Drivers/Interop/FlightSql/DriverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ public void CanGetObjectsAll()
{
foreach (FlightSqlTestEnvironment environment in _environments)
{
string databaseName = environment.Metadata.Catalog;
string? databaseName = environment.Metadata.Catalog;
string? schemaName = environment.Metadata.Schema;
string tableName = environment.Metadata.Table;
string? columnName = null;
Expand Down
141 changes: 100 additions & 41 deletions csharp/test/Drivers/Interop/FlightSql/FlightSqlData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,20 @@ FlightSqlTestEnvironmentType environmentType
{
switch (environmentType)
{
case FlightSqlTestEnvironmentType.Denodo:
return GetDenodoSampleData();
case FlightSqlTestEnvironmentType.Dremio:
return GetDremioSampleData();
case FlightSqlTestEnvironmentType.DuckDB:
return GetDuckDbSampleData();
case FlightSqlTestEnvironmentType.SQLite:
return GetSQLiteSampleData();
case FlightSqlTestEnvironmentType.Dremio:
return GetDremioSampleData();
default:
throw new InvalidOperationException("Unknown environment type.");
}
}

private static SampleDataBuilder GetDuckDbSampleData()
private static SampleDataBuilder GetDenodoSampleData()
{
SampleDataBuilder sampleDataBuilder = new SampleDataBuilder();

Expand Down Expand Up @@ -109,43 +111,6 @@ private static SampleDataBuilder GetDuckDbSampleData()
return sampleDataBuilder;
}

private static SampleDataBuilder GetSQLiteSampleData()
{
string tempTable = Guid.NewGuid().ToString().Replace("-", "");

SampleDataBuilder sampleDataBuilder = new SampleDataBuilder();

sampleDataBuilder.Samples.Add(
new SampleData()
{
// for SQLite, we can't just select data without a
// table because we get mixed schemas that are returned,
// resulting in an error. so create a temp table,
// insert data, select data, then remove the table.
PreQueryCommands = new List<string>()
{
$"CREATE TEMP TABLE [{tempTable}] (INTEGER_COLUMN INTEGER, TEXT_COLUMN TEXT, BLOB_COLUMN BLOB, REAL_COLUMN REAL, NULL_COLUMN NULL);",
$"INSERT INTO [{tempTable}] (INTEGER_COLUMN, TEXT_COLUMN, BLOB_COLUMN, REAL_COLUMN, NULL_COLUMN) VALUES (42, 'Hello, SQLite', X'426C6F62', 3.14159, NULL);"
},
Query = $"SELECT INTEGER_COLUMN, TEXT_COLUMN, BLOB_COLUMN, REAL_COLUMN, NULL_COLUMN FROM [{tempTable}];",
PostQueryCommands = new List<string>()
{
$"DROP TABLE [{tempTable}];"
},
ExpectedValues = new List<ColumnNetTypeArrowTypeValue>()
{
new ColumnNetTypeArrowTypeValue("INTEGER_COLUMN", typeof(long), typeof(Int64Type), 42L),
new ColumnNetTypeArrowTypeValue("TEXT_COLUMN", typeof(string), typeof(StringType), "Hello, SQLite"),
new ColumnNetTypeArrowTypeValue("BLOB_COLUMN", typeof(byte[]), typeof(BinaryType), Encoding.UTF8.GetBytes("Blob")),
new ColumnNetTypeArrowTypeValue("REAL_COLUMN", typeof(double), typeof(DoubleType), 3.14159d),
new ColumnNetTypeArrowTypeValue("NULL_COLUMN", typeof(UnionType), typeof(UnionType), null),
}
}
);

return sampleDataBuilder;
}

private static SampleDataBuilder GetDremioSampleData()
{
ListArray.Builder labuilder = new ListArray.Builder(Int32Type.Default);
Expand Down Expand Up @@ -190,11 +155,105 @@ private static SampleDataBuilder GetDremioSampleData()
#endif
new ColumnNetTypeArrowTypeValue("sample_timestamp", typeof(DateTimeOffset), typeof(TimestampType), new DateTimeOffset(new DateTime(2024, 1, 1, 12, 34, 56), TimeSpan.Zero)),
new ColumnNetTypeArrowTypeValue("sample_array", typeof(Int32Array), typeof(ListType), numbersArray),
new ColumnNetTypeArrowTypeValue("sample_struct", typeof(string), typeof(StringType), "{\"name\":\"Gnarly\", \"age\":7, \"car\":null}"),
new ColumnNetTypeArrowTypeValue("sample_struct", typeof(string), typeof(StructType), "{\"name\":\"Gnarly\",\"age\":7}"),
}
});

return sampleDataBuilder;
}

private static SampleDataBuilder GetDuckDbSampleData()
{
SampleDataBuilder sampleDataBuilder = new SampleDataBuilder();

sampleDataBuilder.Samples.Add(
new SampleData()
{
Query = "SELECT " +
"42 AS \"TinyInt\", " +
"12345 AS \"SmallInt\", " +
"987654321 AS \"Integer\", " +
"1234567890123 AS \"BigInt\", " +
"3.141592 AS \"Real\", " +
"123.456789123456 AS \"Double\", " +
"DECIMAL '12345.67' AS \"Decimal\", " +
"'DuckDB' AS \"Varchar\", " +
"BLOB 'abc' AS \"Blob\", " +
"TRUE AS \"Boolean\"," +
"DATE '2024-09-10' AS \"Date\", " +
"TIME '12:34:56' AS \"Time\", " +
"TIMESTAMP '2024-09-10 12:34:56' AS \"Timestamp\", " +
"INTERVAL '1 year' AS \"Interval\", " +
"'[1, 2, 3]'::JSON AS \"JSON\", " +
"'[{\"key\": \"value\"}]'::JSON AS \"JSON_Array\", " +
"to_json([true, false, null]) AS \"List_JSON\", " + // need to convert List values to json
"to_json(MAP {'key': 'value'}) AS \"Map_JSON\" ", // need to convert Map values to json
ExpectedValues = new List<ColumnNetTypeArrowTypeValue>()
{
new ColumnNetTypeArrowTypeValue("TinyInt", typeof(int), typeof(Int32Type), 42),
new ColumnNetTypeArrowTypeValue("SmallInt", typeof(int), typeof(Int32Type), 12345),
new ColumnNetTypeArrowTypeValue("Integer", typeof(int), typeof(Int32Type), 987654321),
new ColumnNetTypeArrowTypeValue("BigInt", typeof(Int64), typeof(Int64Type), 1234567890123),
new ColumnNetTypeArrowTypeValue("Real", typeof(SqlDecimal), typeof(Decimal128Type), new SqlDecimal(3.141592m)),
new ColumnNetTypeArrowTypeValue("Double", typeof(SqlDecimal), typeof(Decimal128Type), new SqlDecimal(123.456789123456m)),
new ColumnNetTypeArrowTypeValue("Decimal", typeof(SqlDecimal), typeof(Decimal128Type), new SqlDecimal(12345.67m)),
new ColumnNetTypeArrowTypeValue("Varchar", typeof(string), typeof(StringType), "DuckDB"),
new ColumnNetTypeArrowTypeValue("Blob", typeof(byte[]), typeof(BinaryType), Encoding.UTF8.GetBytes("abc")),
new ColumnNetTypeArrowTypeValue("Boolean", typeof(bool), typeof(BooleanType), true),
new ColumnNetTypeArrowTypeValue("Date", typeof(DateTime), typeof(Date32Type), new DateTime(2024, 09, 10)),
#if NET6_0_OR_GREATER
new ColumnNetTypeArrowTypeValue("Time", typeof(TimeOnly), typeof(Time64Type), new TimeOnly(12, 34, 56)),
#else
new ColumnNetTypeArrowTypeValue("Time", typeof(TimeSpan), typeof(Time64Type), new TimeSpan(12, 34, 56)),
#endif
new ColumnNetTypeArrowTypeValue("Timestamp", typeof(DateTimeOffset), typeof(TimestampType), new DateTimeOffset(new DateTime(2024, 9, 10, 12, 34, 56), TimeSpan.Zero)),
new ColumnNetTypeArrowTypeValue("Interval", typeof(MonthDayNanosecondInterval), typeof(IntervalType), new MonthDayNanosecondInterval(12, 0, 0)),
new ColumnNetTypeArrowTypeValue("JSON", typeof(string), typeof(StringType), "[1, 2, 3]"),
new ColumnNetTypeArrowTypeValue("JSON_Array", typeof(string), typeof(StringType), "[{\"key\": \"value\"}]"),
new ColumnNetTypeArrowTypeValue("List_JSON", typeof(string), typeof(StringType),"[true,false,null]"),
new ColumnNetTypeArrowTypeValue("Map_JSON", typeof(string), typeof(StringType), "{\"key\":\"value\"}"),
}
}
);

return sampleDataBuilder;
}

private static SampleDataBuilder GetSQLiteSampleData()
{
string tempTable = Guid.NewGuid().ToString().Replace("-", "");

SampleDataBuilder sampleDataBuilder = new SampleDataBuilder();

sampleDataBuilder.Samples.Add(
new SampleData()
{
// for SQLite, we can't just select data without a
// table because we get mixed schemas that are returned,
// resulting in an error. so create a temp table,
// insert data, select data, then remove the table.
PreQueryCommands = new List<string>()
{
$"CREATE TEMP TABLE [{tempTable}] (INTEGER_COLUMN INTEGER, TEXT_COLUMN TEXT, BLOB_COLUMN BLOB, REAL_COLUMN REAL, NULL_COLUMN NULL);",
$"INSERT INTO [{tempTable}] (INTEGER_COLUMN, TEXT_COLUMN, BLOB_COLUMN, REAL_COLUMN, NULL_COLUMN) VALUES (42, 'Hello, SQLite', X'426C6F62', 3.14159, NULL);"
},
Query = $"SELECT INTEGER_COLUMN, TEXT_COLUMN, BLOB_COLUMN, REAL_COLUMN, NULL_COLUMN FROM [{tempTable}];",
PostQueryCommands = new List<string>()
{
$"DROP TABLE [{tempTable}];"
},
ExpectedValues = new List<ColumnNetTypeArrowTypeValue>()
{
new ColumnNetTypeArrowTypeValue("INTEGER_COLUMN", typeof(long), typeof(Int64Type), 42L),
new ColumnNetTypeArrowTypeValue("TEXT_COLUMN", typeof(string), typeof(StringType), "Hello, SQLite"),
new ColumnNetTypeArrowTypeValue("BLOB_COLUMN", typeof(byte[]), typeof(BinaryType), Encoding.UTF8.GetBytes("Blob")),
new ColumnNetTypeArrowTypeValue("REAL_COLUMN", typeof(double), typeof(DoubleType), 3.14159d),
new ColumnNetTypeArrowTypeValue("NULL_COLUMN", typeof(UnionType), typeof(UnionType), null),
}
}
);

return sampleDataBuilder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ internal class FlightSqlTestConfiguration

internal enum FlightSqlTestEnvironmentType
{
Denodo,
Dremio,
DuckDB,
SQLite,
Dremio
SQLite
}

internal class FlightSqlTestEnvironment : TestConfiguration
Expand Down

0 comments on commit 055fe84

Please sign in to comment.