blob: 7e0d11c3f3a3c0b3627a9f25a00500292aec620e [file] [log] [blame]
/*
* 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.
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Apache.Arrow.Ipc;
using Apache.Arrow.Types;
using GetObjectsDepth = Apache.Arrow.Adbc.AdbcConnection.GetObjectsDepth;
namespace Apache.Arrow.Adbc.Client
{
/// <summary>
/// Creates an ADO.NET connection over an Adbc driver.
/// </summary>
public sealed class AdbcConnection : DbConnection
{
private AdbcDatabase? adbcDatabase;
private Adbc.AdbcConnection? adbcConnectionInternal;
private TimeoutValue? connectionTimeoutValue;
private readonly Dictionary<string, string> adbcConnectionParameters;
private readonly Dictionary<string, string> adbcConnectionOptions;
private AdbcTransaction? currentTransaction;
/// <summary>
/// Overloaded. Initializes an <see cref="AdbcConnection"/>.
/// </summary>
public AdbcConnection()
{
this.AdbcDriver = null;
this.DecimalBehavior = DecimalBehavior.UseSqlDecimal;
this.adbcConnectionParameters = new Dictionary<string, string>();
this.adbcConnectionOptions = new Dictionary<string, string>();
}
/// <summary>
/// Overloaded. Initializes an <see cref="AdbcConnection"/>.
/// <param name="connectionString">The connection string to use.</param>
/// </summary>
public AdbcConnection(string connectionString) : this()
{
this.ConnectionString = connectionString;
}
/// <summary>
/// Overloaded. Initializes an <see cref="AdbcConnection"/>.
/// </summary>
/// <param name="adbcDriver">
/// The <see cref="AdbcDriver"/> to use for connecting. This value
/// must be set before a connection can be established.
/// </param>
public AdbcConnection(AdbcDriver adbcDriver) : this()
{
this.AdbcDriver = adbcDriver;
}
/// <summary>
/// Overloaded. Initializes an <see cref="AdbcConnection"/>.
/// </summary>
/// <param name="adbcDriver">
/// The <see cref="AdbcDriver"/> to use for connecting. This value
/// must be set before a connection can be established.
/// </param>
/// <param name="parameters">
/// The connection parameters to use (similar to connection string).
/// </param>
/// <param name="options">
/// Any additional options to apply when connection to the
/// <see cref="AdbcDatabase"/>.
/// </param>
public AdbcConnection(AdbcDriver adbcDriver, Dictionary<string, string> parameters, Dictionary<string, string> options)
{
this.AdbcDriver = adbcDriver;
this.adbcConnectionParameters = parameters;
this.adbcConnectionOptions = options;
}
// For testing
internal AdbcConnection(AdbcDriver driver, AdbcDatabase database, Adbc.AdbcConnection connection)
: this(driver)
{
this.adbcDatabase = database;
this.adbcConnectionInternal = connection;
}
/// <summary>
/// Creates a new <see cref="AdbcCommand"/>.
/// </summary>
/// <returns><see cref="AdbcCommand"/></returns>
public new AdbcCommand CreateCommand() => (AdbcCommand)CreateDbCommand();
/// <summary>
/// Gets or sets the <see cref="AdbcDriver"/> associated with this
/// connection.
/// </summary>
public AdbcDriver? AdbcDriver { get; set; }
/// <summary>
/// Creates an <see cref="AdbcStatement"/> for the connection.
/// </summary>
internal AdbcStatement CreateStatement()
{
EnsureConnectionOpen();
return this.adbcConnectionInternal!.CreateStatement();
}
internal TimeoutValue? CommandTimeoutValue { get; private set; }
#if NET5_0_OR_GREATER
[AllowNull]
#endif
public override string ConnectionString { get => GetConnectionString(); set => SetConnectionProperties(value!); }
/// <summary>
/// Gets or sets the behavior of decimals.
/// </summary>
public DecimalBehavior DecimalBehavior { get; set; }
/// <summary>
/// Indicates how structs should be treated.
/// </summary>
public StructBehavior StructBehavior { get; set; } = StructBehavior.JsonString;
public override int ConnectionTimeout
{
get
{
if (connectionTimeoutValue != null)
return connectionTimeoutValue.Value;
else
return base.ConnectionTimeout;
}
}
protected override DbCommand CreateDbCommand()
{
EnsureConnectionOpen();
AdbcCommand cmd = new AdbcCommand(this);
if (CommandTimeoutValue != null)
{
cmd.AdbcCommandTimeoutProperty = CommandTimeoutValue.DriverPropertyName!;
cmd.CommandTimeout = CommandTimeoutValue.Value;
}
return cmd;
}
/// <summary>
/// Ensures the connection is open.
/// </summary>
private void EnsureConnectionOpen()
{
if (this.State == ConnectionState.Closed)
this.Open();
}
protected override void Dispose(bool disposing)
{
this.adbcConnectionInternal?.Dispose();
this.adbcConnectionInternal = null;
base.Dispose(disposing);
}
public override void Open()
{
if (this.State == ConnectionState.Closed)
{
if (this.adbcConnectionParameters.Keys.Count == 0)
{
throw new InvalidOperationException("No connection values are present to connect with");
}
if (this.AdbcDriver == null)
{
throw new InvalidOperationException("The ADBC driver is not specified");
}
this.adbcDatabase = this.AdbcDriver.Open(this.adbcConnectionParameters);
this.adbcConnectionInternal = this.adbcDatabase.Connect(this.adbcConnectionOptions);
}
}
public override void Close()
{
if (this.currentTransaction != null)
{
this.currentTransaction.Rollback();
}
this.Dispose();
}
public override ConnectionState State
{
get
{
return this.adbcConnectionInternal != null ? ConnectionState.Open : ConnectionState.Closed;
}
}
private Adbc.AdbcConnection Connection => this.adbcConnectionInternal ?? throw new InvalidOperationException("Invalid operation. The connection is closed.");
/// <summary>
/// Builds a connection string based on the adbcConnectionParameters.
/// </summary>
/// <returns>connection string</returns>
private string GetConnectionString()
{
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
foreach (string key in this.adbcConnectionParameters.Keys)
{
builder.Add(key, this.adbcConnectionParameters[key]);
}
return builder.ConnectionString;
}
/// <summary>
/// Sets the adbcConnectionParameters based on a connection string.
/// </summary>
/// <param name="value"></param>
/// <exception cref="ArgumentNullException"></exception>
private void SetConnectionProperties(string value)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException(nameof(value));
}
DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
builder.ConnectionString = value;
this.adbcConnectionParameters.Clear();
foreach (string key in builder.Keys)
{
object? builderValue = builder[key];
if (builderValue != null)
{
string paramValue = Convert.ToString(builderValue)!;
switch (key)
{
case ConnectionStringKeywords.DecimalBehavior:
this.DecimalBehavior = (DecimalBehavior)Enum.Parse(typeof(DecimalBehavior), paramValue);
break;
case ConnectionStringKeywords.StructBehavior:
this.StructBehavior = (StructBehavior)Enum.Parse(typeof(StructBehavior), paramValue);
break;
case ConnectionStringKeywords.CommandTimeout:
CommandTimeoutValue = ConnectionStringParser.ParseTimeoutValue(paramValue);
break;
case ConnectionStringKeywords.ConnectionTimeout:
this.connectionTimeoutValue = ConnectionStringParser.ParseTimeoutValue(paramValue);
this.adbcConnectionParameters[connectionTimeoutValue.DriverPropertyName] = connectionTimeoutValue.Value.ToString();
break;
default:
this.adbcConnectionParameters.Add(key, paramValue);
break;
}
}
}
}
public override DataTable GetSchema()
{
return GetSchema("metadatacollections", null);
}
public override DataTable GetSchema(string collectionName)
{
return GetSchema(collectionName, null);
}
public override DataTable GetSchema(string collectionName, string?[]? restrictionValues)
{
SchemaCollection? collection;
if (!SchemaCollection.TryGetCollection(collectionName, out collection))
{
throw new ArgumentException(
$"The requested collection ('{collectionName}') is not defined",
nameof(collectionName));
}
if (restrictionValues != null && restrictionValues.Length > collection!.Restrictions.Length)
{
throw new ArgumentException(
$"More restrictions were provided than the requested schema ('{collectionName}') supports.",
nameof(restrictionValues));
}
return collection!.GetSchema(this.Connection, restrictionValues);
}
protected override DbTransaction BeginDbTransaction(System.Data.IsolationLevel isolationLevel)
{
if (this.currentTransaction != null) throw new InvalidOperationException("connection is already enlisted in a transaction");
this.Connection.AutoCommit = false;
if (isolationLevel != System.Data.IsolationLevel.Unspecified)
{
this.Connection.IsolationLevel = GetIsolationLevel(isolationLevel);
}
this.currentTransaction = new AdbcTransaction(this, isolationLevel);
return this.currentTransaction;
}
private static Adbc.IsolationLevel GetIsolationLevel(System.Data.IsolationLevel isolationLevel)
{
return isolationLevel switch
{
System.Data.IsolationLevel.Unspecified => Adbc.IsolationLevel.Default,
System.Data.IsolationLevel.ReadUncommitted => Adbc.IsolationLevel.ReadUncommitted,
System.Data.IsolationLevel.ReadCommitted => Adbc.IsolationLevel.ReadCommitted,
System.Data.IsolationLevel.RepeatableRead => Adbc.IsolationLevel.RepeatableRead,
System.Data.IsolationLevel.Snapshot => Adbc.IsolationLevel.Snapshot,
System.Data.IsolationLevel.Serializable => Adbc.IsolationLevel.Serializable,
_ => throw new NotSupportedException("unknown isolation level"),
};
}
private void Commit()
{
if (this.currentTransaction == null) throw new InvalidOperationException("connection is not enlisted in a transaction");
System.Data.IsolationLevel isolationLevel = this.currentTransaction.IsolationLevel;
this.Connection.Commit();
this.currentTransaction = null;
this.Connection.AutoCommit = true;
if (isolationLevel != System.Data.IsolationLevel.Unspecified)
{
this.adbcConnectionInternal!.IsolationLevel = IsolationLevel.Default;
}
}
private void Rollback()
{
if (this.currentTransaction == null) throw new InvalidOperationException("connection is not enlisted in a transaction");
System.Data.IsolationLevel isolationLevel = this.currentTransaction.IsolationLevel;
this.Connection.Rollback();
this.currentTransaction = null;
this.Connection.AutoCommit = true;
if (isolationLevel != System.Data.IsolationLevel.Unspecified)
{
this.adbcConnectionInternal!.IsolationLevel = IsolationLevel.Default;
}
}
#region NOT_IMPLEMENTED
public override string Database => throw new NotImplementedException();
public override string DataSource => throw new NotImplementedException();
public override string ServerVersion => throw new NotImplementedException();
public override void ChangeDatabase(string databaseName)
{
throw new NotImplementedException();
}
#endregion
sealed class AdbcTransaction : DbTransaction
{
readonly AdbcConnection connection;
readonly System.Data.IsolationLevel isolationLevel;
public AdbcTransaction(AdbcConnection connection, System.Data.IsolationLevel isolationLevel)
{
this.connection = connection;
this.isolationLevel = isolationLevel;
}
public override System.Data.IsolationLevel IsolationLevel => this.isolationLevel;
protected override DbConnection DbConnection => this.connection;
public override void Commit() => this.connection.Commit();
public override void Rollback() => this.connection.Rollback();
}
abstract class SchemaCollection
{
protected static readonly List<SchemaCollection> collections;
private static readonly SortedDictionary<string, SchemaCollection> schemaCollections;
static SchemaCollection()
{
collections = new List<SchemaCollection>();
schemaCollections = new SortedDictionary<string, SchemaCollection>(StringComparer.OrdinalIgnoreCase);
Add(new MetadataCollection());
Add(new RestrictionsCollection());
Add(new CatalogsCollection());
Add(new SchemasCollection());
Add(new TableTypesCollection());
Add(new TablesCollection());
Add(new ColumnsCollection());
}
private static void Add(SchemaCollection collection)
{
collections.Add(collection);
schemaCollections.Add(collection.Name, collection);
}
public static bool TryGetCollection(string name, out SchemaCollection? collection)
{
return schemaCollections.TryGetValue(name, out collection);
}
public abstract string Name { get; }
public abstract string[] Restrictions { get; }
public abstract DataTable GetSchema(Adbc.AdbcConnection adbcConnection, string?[]? restrictions);
}
private sealed class MetadataCollection : SchemaCollection
{
public override string Name => "MetaDataCollections";
public override string[] Restrictions => [];
public override DataTable GetSchema(Adbc.AdbcConnection adbcConnection, string?[]? restrictions)
{
DataTable result = new DataTable(Name);
result.Columns.Add("CollectionName", typeof(string));
result.Columns.Add("NumberOfRestrictions", typeof(int));
foreach (SchemaCollection collection in collections)
{
result.Rows.Add(collection.Name, collection.Restrictions.Length);
}
return result;
}
}
private sealed class RestrictionsCollection : SchemaCollection
{
public override string Name => "Restrictions";
public override string[] Restrictions => [];
public override DataTable GetSchema(Adbc.AdbcConnection adbcConnection, string?[]? restrictions)
{
var result = new DataTable(Name);
result.Columns.Add("CollectionName", typeof(string));
result.Columns.Add("RestrictionName", typeof(string));
result.Columns.Add("RestrictionNumber", typeof(int));
foreach (var collection in collections)
{
var collectionRestrictions = collection.Restrictions;
for (int i = 0; i < collectionRestrictions.Length; i++)
{
result.Rows.Add(collection.Name, collectionRestrictions[i], i + 1);
}
}
return result;
}
}
private abstract class ArrowCollection : SchemaCollection
{
protected abstract MapItem[] Map { get; }
protected abstract IArrowArrayStream Invoke(Adbc.AdbcConnection connection, string?[]? restrictions);
public override DataTable GetSchema(Adbc.AdbcConnection adbcConnection, string?[]? restrictions)
{
// Flattens the hierarchical ADBC schema into a DataTable
using (IArrowArrayStream stream = Invoke(adbcConnection, restrictions))
{
MapItem[] map = this.Map;
DataTable result = new DataTable(Name);
List<int> indices = new List<int>();
List<IRecordType> types = new List<IRecordType>();
List<string> path = new List<string>();
List<Action<State>> loaders = new List<Action<State>>();
types.Add(stream.Schema);
for (int targetIndex = 0; targetIndex < map.Length; targetIndex++)
{
MapItem item = map[targetIndex];
result.Columns.Add(item.AdoName, item.Type);
for (int i = 0; i < item.AdbcPath.Length - 1; i++)
{
string part = item.AdbcPath[i];
if (i == path.Count)
{
int index = types[i].GetFieldIndex(part, null);
if (index < 0)
{
throw new InvalidOperationException($"Unable to find '{part}'");
}
ListType? listType = types[i].GetFieldByIndex(index).DataType as ListType;
if (listType == null || listType.ValueDataType.TypeId != ArrowTypeId.Struct)
{
throw new InvalidOperationException($"Field '{part}' has unexpected type.");
}
path.Add(part);
indices.Add(index);
types.Add((IRecordType)listType.ValueDataType);
}
else if (!StringComparer.OrdinalIgnoreCase.Equals(path[i], part))
{
throw new InvalidOperationException($"expected '{path[i]}' found '{part}'");
}
}
int srcIndex = types[types.Count - 1].GetFieldIndex(item.AdbcPath[item.AdbcPath.Length - 1], null);
if (srcIndex < 0)
{
throw new InvalidOperationException($"Unable to find '{item.AdbcPath[item.AdbcPath.Length - 1]}'");
}
loaders.Add(State.CreateLoader(item.Type, item.AdbcPath.Length - 1, srcIndex, targetIndex));
}
State state = new State(result, indices.ToArray(), loaders.ToArray());
while (true)
{
using (RecordBatch? batch = stream.ReadNextRecordBatchAsync().Result)
{
if (batch == null) { return result; }
state.AddRecords(batch);
}
}
}
}
private class State
{
private readonly DataTable table;
private readonly int[] indices;
private readonly Action<State>[] loaders;
private readonly object?[] buffer;
private readonly int[] offsets;
private readonly IArrowRecord[] records;
public State(DataTable table, int[] indices, Action<State>[] loaders)
{
this.table = table;
this.indices = indices;
this.loaders = loaders;
this.buffer = new object[loaders.Length];
this.offsets = new int[indices.Length + 1];
this.records = new IArrowRecord[indices.Length + 1];
}
public void AddRecords(RecordBatch batch)
{
ListArray[] lists = new ListArray[this.indices.Length];
this.records[0] = batch;
this.offsets[0] = 0;
for (int i = 0; i < indices.Length; i++)
{
lists[i] = (ListArray)this.records[i].Column(indices[i]);
this.records[i + 1] = (StructArray)lists[i].Values;
this.offsets[i + 1] = 0;
}
Loop(lists, 0, batch.Length);
}
private void Loop(ListArray[] lists, int ptr, int count)
{
for (int i = 0; i < count; i++)
{
if (ptr == lists.Length)
{
AddRow();
}
else
{
Loop(lists, ptr + 1, lists[ptr].GetValueLength(this.offsets[ptr]));
}
this.offsets[ptr]++;
}
}
private void AddRow()
{
foreach (Action<State> loader in this.loaders)
{
loader(this);
}
this.table.Rows.Add(this.buffer);
}
public static Action<State> CreateLoader(Type type, int srcLevel, int srcIndex, int targetIndex)
{
return Type.GetTypeCode(type) switch
{
TypeCode.Boolean => state =>
state.buffer[targetIndex] = ((BooleanArray)state.records[srcLevel].Column(srcIndex)).GetValue(state.offsets[srcLevel]),
TypeCode.Int16 => state =>
state.buffer[targetIndex] = ((Int16Array)state.records[srcLevel].Column(srcIndex)).GetValue(state.offsets[srcLevel]),
TypeCode.Int32 => state =>
state.buffer[targetIndex] = ((Int32Array)state.records[srcLevel].Column(srcIndex)).GetValue(state.offsets[srcLevel]),
TypeCode.Int64 => state =>
state.buffer[targetIndex] = ((Int64Array)state.records[srcLevel].Column(srcIndex)).GetValue(state.offsets[srcLevel]),
TypeCode.String => state =>
state.buffer[targetIndex] = ((StringArray)state.records[srcLevel].Column(srcIndex)).GetString(state.offsets[srcLevel]),
_ => throw new NotSupportedException($"Type {type.FullName} is not supported."),
};
}
}
protected struct MapItem
{
public readonly string AdoName;
public readonly string[] AdbcPath;
public readonly Type Type;
public MapItem(string adoName, string[] adbcPath, Type type)
{
this.AdoName = adoName;
this.AdbcPath = adbcPath;
this.Type = type;
}
}
}
private sealed class CatalogsCollection : ArrowCollection
{
public override string Name => "Catalogs";
public override string[] Restrictions => new[] { "Catalog" };
protected override MapItem[] Map => new[]
{
new MapItem("TABLE_CATALOG", new[] { "catalog_name" }, typeof(string)),
};
protected override IArrowArrayStream Invoke(Adbc.AdbcConnection connection, string?[]? restrictions)
{
string? catalog = restrictions?.Length > 0 ? restrictions[0] : null;
return connection.GetObjects(GetObjectsDepth.Catalogs, catalog, null, null, null, null);
}
}
private class SchemasCollection : ArrowCollection
{
public override string Name => "Schemas";
public override string[] Restrictions => new[] { "Catalog", "Schema" };
protected override MapItem[] Map => new[]
{
new MapItem("TABLE_CATALOG", new [] { "catalog_name" }, typeof(string)),
new MapItem("TABLE_SCHEMA", new [] { "catalog_db_schemas", "db_schema_name" }, typeof(string)),
};
protected override IArrowArrayStream Invoke(Adbc.AdbcConnection connection, string?[]? restrictions)
{
string? catalog = restrictions?.Length > 0 ? restrictions[0] : null;
string? schema = restrictions?.Length > 1 ? restrictions[1] : null;
return connection.GetObjects(GetObjectsDepth.DbSchemas, catalog, schema, null, null, null);
}
}
private class TableTypesCollection : ArrowCollection
{
public override string Name => "TableTypes";
public override string[] Restrictions => [];
protected override MapItem[] Map => new[]
{
new MapItem("TABLE_TYPE", new [] { "table_type" }, typeof(string)),
};
protected override IArrowArrayStream Invoke(Adbc.AdbcConnection connection, string?[]? restrictions)
{
return connection.GetTableTypes();
}
}
private class TablesCollection : ArrowCollection
{
public override string Name => "Tables";
public override string[] Restrictions => new[] { "Catalog", "Schema", "Table", "TableType" };
protected override MapItem[] Map => new[]
{
new MapItem("TABLE_CATALOG", new [] { "catalog_name" }, typeof(string)),
new MapItem("TABLE_SCHEMA", new [] { "catalog_db_schemas", "db_schema_name" }, typeof(string)),
new MapItem("TABLE_NAME", new [] { "catalog_db_schemas", "db_schema_tables", "table_name" }, typeof(string)),
new MapItem("TABLE_TYPE", new [] { "catalog_db_schemas", "db_schema_tables", "table_type" }, typeof(string)),
};
protected override IArrowArrayStream Invoke(Adbc.AdbcConnection connection, string?[]? restrictions)
{
string? catalog = restrictions?.Length > 0 ? restrictions[0] : null;
string? schema = restrictions?.Length > 1 ? restrictions[1] : null;
string? table = restrictions?.Length > 2 ? restrictions[2] : null;
List<string>? tableTypes = restrictions?.Length > 3 ? restrictions[3]?.Split(',').ToList() : null;
return connection.GetObjects(GetObjectsDepth.Tables, catalog, schema, table, tableTypes, null);
}
}
private class ColumnsCollection : ArrowCollection
{
public override string Name => "Columns";
public override string[] Restrictions => new[] { "Catalog", "Schema", "Table", "Column" };
protected override MapItem[] Map => new[]
{
new MapItem("TABLE_CATALOG", new [] { "catalog_name" }, typeof(string)),
new MapItem("TABLE_SCHEMA", new [] { "catalog_db_schemas", "db_schema_name" }, typeof(string)),
new MapItem("TABLE_NAME", new [] { "catalog_db_schemas", "db_schema_tables", "table_name" }, typeof(string)),
new MapItem("COLUMN_NAME", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "column_name" }, typeof(string)),
new MapItem("ORDINAL_POSITION", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "ordinal_position" }, typeof(int)),
new MapItem("REMARKS", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "remarks" }, typeof(string)),
new MapItem("DATA_TYPE", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "xdbc_type_name" }, typeof(string)),
new MapItem("IS_NULLABLE", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "xdbc_is_nullable" }, typeof(string)),
new MapItem("COLUMN_DEFAULT", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "xdbc_column_def" }, typeof(string)),
new MapItem("IS_AUTOINCREMENT", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "xdbc_is_autoincrement" }, typeof(bool)),
new MapItem("IS_GENERATED", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "xdbc_is_generatedcolumn" }, typeof(bool)),
new MapItem("CHARACTER_OCTET_LENGTH", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "xdbc_char_octet_length" }, typeof(int)),
new MapItem("CHARACTER_MAXIMUM_LENGTH", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "xdbc_column_size" }, typeof(int)),
new MapItem("NUMERIC_PRECISION", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "xdbc_decimal_digits" }, typeof(short)),
new MapItem("NUMERIC_PRECISION_RADIX", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "xdbc_num_prec_radix" }, typeof(short)),
new MapItem("DATETIME_PRECISION", new [] { "catalog_db_schemas", "db_schema_tables", "table_columns", "xdbc_datetime_sub" }, typeof(short)),
};
protected override IArrowArrayStream Invoke(Adbc.AdbcConnection connection, string?[]? restrictions)
{
string? catalog = restrictions?.Length > 0 ? restrictions[0] : null;
string? schema = restrictions?.Length > 1 ? restrictions[1] : null;
string? table = restrictions?.Length > 2 ? restrictions[2] : null;
string? column = restrictions?.Length > 3 ? restrictions[3] : null;
return connection.GetObjects(GetObjectsDepth.All, catalog, schema, table, null, column);
}
}
}
}