CurtHagenlocher commented on code in PR #4911:
URL: https://github.com/apache/arrow-adbc/pull/4911#discussion_r4290684266
##########
csharp/test/Apache.Arrow.Adbc.Tests/ImportedDuckDbTests.cs:
##########
@@ -384,6 +388,267 @@ public async Task CancelConnection()
Assert.Contains("Interrupted", exception.Message);
}
+ [Fact]
+ public void DatabaseGetOption()
+ {
+ using var database = _duckDb.OpenDatabase("dbgetoption.db");
+
+ Assert.EndsWith("dbgetoption.db", database.GetOption("path"));
+ Assert.Equal(AdbcStatusCode.NotFound,
Assert.ThrowsAny<AdbcException>(() => database.GetOption("unknown")).Status);
+ }
+
+ [Fact]
+ public void TypedDatabaseOptionsAreAppliedAtOpen()
+ {
+ // A long goes through DatabaseSetOptionInt rather than
DatabaseSetOption
+ using var database = _duckDb.OpenDatabase("typedopen.db", new
Dictionary<string, object> { { "threads", 3L } });
+ using var connection = database.Connect(null);
+ using var statement = connection.CreateStatement();
+
+ Assert.Equal("3", GetScalarString(statement, "SELECT
current_setting('threads')::VARCHAR"));
+ Assert.Equal("3", database.GetOption("threads"));
+ }
+
+ [Fact]
+ public async Task OpenAndConnectAsync()
+ {
+ using var database = await
_duckDb.OpenDatabaseAsync("openasync.db");
+ using var connection = await database.ConnectAsync(null);
+ using var statement = connection.CreateStatement();
+
+ Assert.Equal(3, GetResultCount(statement, "SELECT * FROM
range(3)"));
+ }
+
+ [Fact]
+ public void StandardConnectionOptionsComeFromTheDriver()
+ {
+ using var database = _duckDb.OpenDatabase("standardoptions.db");
+ using var connection = database.Connect(null);
+
+ // Nothing has been set through this connection, so these are read
from DuckDB
+ Assert.True(connection.AutoCommit);
+ Assert.Equal(AdbcOptions.Enabled,
connection.GetOption(AdbcOptions.Connection.Autocommit));
+
+ connection.AutoCommit = false;
+ Assert.Equal(AdbcOptions.Disabled,
connection.GetOption(AdbcOptions.Connection.Autocommit));
+
+ // In DuckDB 1.5.6, reading the current catalog or schema causes a
later attempt
+ // to disable autocommit to be silently ignored, so these are
checked last.
+ Assert.Equal("standardoptions", connection.CurrentCatalog);
+ Assert.Equal("main", connection.CurrentDbSchema);
+ }
+
+ [Fact]
+ public async Task SetCurrentDbSchema()
+ {
+ using var database = _duckDb.OpenDatabase("currentschema.db");
+ using var connection = database.Connect(null);
+ using var statement = connection.CreateStatement();
+
+ statement.SqlQuery = "CREATE SCHEMA other";
+ statement.ExecuteUpdate();
+
+ await
connection.SetOptionAsync(AdbcOptions.Connection.CurrentDbSchema, "other");
+ Assert.Equal("other", await
connection.GetOptionAsync(AdbcOptions.Connection.CurrentDbSchema));
+ Assert.Equal("other", connection.CurrentDbSchema);
+
+ statement.SqlQuery = "CREATE TABLE unqualified(x INTEGER)";
+ statement.ExecuteUpdate();
+ Assert.Equal("other", GetScalarString(statement, "SELECT
schema_name FROM duckdb_tables() WHERE table_name = 'unqualified'"));
+
+ connection.CurrentDbSchema = "main";
+ Assert.Equal("main", GetScalarString(statement, "SELECT
current_schema()"));
+ }
+
+ [Fact]
+ public void ConnectWithTypedOptions()
+ {
+ using var database = _duckDb.OpenDatabase("connecttyped.db");
+
+ // The bool is sent to DuckDB as the string "false". (That it's
sent before the
+ // connection is initialized is covered by
ExportedDriverRoundTrip11Tests.)
+ using var connection = database.ConnectWithTypedOptions(new
Dictionary<string, object>
+ {
+ { AdbcOptions.Connection.Autocommit, false },
+ });
+
+ Assert.Equal(AdbcOptions.Disabled,
connection.GetOption(AdbcOptions.Connection.Autocommit));
+ Assert.False(connection.AutoCommit);
+ }
+
+ [Fact]
+ public async Task ConnectWithTypedOptionsAsync()
+ {
+ using var database = _duckDb.OpenDatabase("connecttypedasync.db");
+
+ using var connection = await
database.ConnectWithTypedOptionsAsync(new Dictionary<string, object>
+ {
+ { AdbcOptions.Connection.Autocommit, false },
+ });
+
+ Assert.Equal(AdbcOptions.Disabled,
connection.GetOption(AdbcOptions.Connection.Autocommit));
+ }
+
+ [Fact]
+ public void UnsupportedOptions()
+ {
+ using var database = _duckDb.OpenDatabase("unsupportedoptions.db");
+ using var connection = database.Connect(null);
+ using var statement = connection.CreateStatement();
+
+ Assert.Equal(AdbcStatusCode.NotFound,
Assert.ThrowsAny<AdbcException>(() => connection.GetOption("unknown")).Status);
+ Assert.Equal(AdbcStatusCode.NotFound,
Assert.ThrowsAny<AdbcException>(() =>
connection.GetOptionInt64("unknown")).Status);
+ Assert.Equal(AdbcStatusCode.NotFound,
Assert.ThrowsAny<AdbcException>(() =>
connection.GetOptionDouble("unknown")).Status);
+ Assert.Equal(AdbcStatusCode.NotFound,
Assert.ThrowsAny<AdbcException>(() =>
connection.GetOptionBytes("unknown")).Status);
+ Assert.Equal(AdbcStatusCode.NotImplemented,
Assert.ThrowsAny<AdbcException>(() => connection.SetOption("unknown",
"x")).Status);
+ Assert.Equal(AdbcStatusCode.NotImplemented,
Assert.ThrowsAny<AdbcException>(() => connection.SetOptionDouble("unknown",
1.5)).Status);
+ Assert.Equal(AdbcStatusCode.NotImplemented,
Assert.ThrowsAny<AdbcException>(() => connection.SetOptionBytes("unknown", new
byte[] { 1 })).Status);
+
+ Assert.Equal(AdbcStatusCode.NotFound,
Assert.ThrowsAny<AdbcException>(() => statement.GetOption("unknown")).Status);
+
+ // The spec says NotImplemented, but DuckDB 1.5.6 returns
InvalidArgument for
+ // unknown statement options. Accept either so a DuckDB fix
doesn't break this.
+ AdbcStatusCode status = Assert.ThrowsAny<AdbcException>(() =>
statement.SetOptionInt64("unknown", 1)).Status;
+ Assert.Contains(status, new[] { AdbcStatusCode.NotImplemented,
AdbcStatusCode.InvalidArgument });
+ }
+
+ [Fact]
+ public void StatementGetOption()
+ {
+ using var database = _duckDb.OpenDatabase("statementoption.db");
+ using var connection = database.Connect(null);
+ using var statement = connection.CreateStatement();
+
+ statement.SetOption(AdbcOptions.Ingest.TargetTable, "target");
+ Assert.Equal("target",
statement.GetOption(AdbcOptions.Ingest.TargetTable));
+ }
+
+ [Fact]
+ public void ErrorDetails()
+ {
+ using var database = _duckDb.OpenDatabase("errordetails.db");
+ using var connection = database.Connect(null);
+ using var statement = connection.CreateStatement();
+
+ statement.SqlQuery = "SELECT CAST('abc' AS INTEGER)";
+ AdbcException exception = Assert.ThrowsAny<AdbcException>(() =>
statement.ExecuteQuery());
+
+ Assert.Equal(AdbcStatusCode.InvalidArgument, exception.Status);
+ Assert.Contains("Conversion Error", exception.Message);
+ KeyValuePair<string, byte[]> detail =
Assert.Single(exception.Details, d => d.Key == "duckdb:error_type");
+ Assert.Equal("Conversion", Encoding.UTF8.GetString(detail.Value));
+
+ // The 1.1 error layout reuses the vendor code field, so it
mustn't leak through
+ Assert.Equal(0, exception.NativeError);
+ }
+
+ [Fact]
+ public async Task Statistics()
+ {
+ using var database = _duckDb.OpenDatabase("statistics.db");
+ using var connection = database.Connect(null);
+ using var statement = connection.CreateStatement();
+
+ using (IArrowArrayStream names = connection.GetStatisticsNames())
+ {
+ Assert.Equal(new[] { "statistic_name", "statistic_key" },
names.Schema.FieldsList.Select(f => f.Name));
+ Assert.Equal(ArrowTypeId.String,
names.Schema.FieldsList[0].DataType.TypeId);
+ Assert.Equal(ArrowTypeId.Int16,
names.Schema.FieldsList[1].DataType.TypeId);
+ }
+
+ statement.SqlQuery = "CREATE TABLE stats AS SELECT * FROM
range(10) r(x)";
+ statement.ExecuteUpdate();
+
+ using IArrowArrayStream statistics =
connection.GetStatistics(null, null, "stats", approximate: false);
+ Assert.Equal(new[] { "catalog_name", "catalog_db_schemas" },
statistics.Schema.FieldsList.Select(f => f.Name));
+
+ var catalogs = new List<string?>();
+ RecordBatch? batch;
+ while ((batch = await statistics.ReadNextRecordBatchAsync()) !=
null)
+ {
+ using (batch)
+ {
+ StringArray names = (StringArray)batch.Column(0);
+ for (int i = 0; i < names.Length; i++)
+ {
+ catalogs.Add(names.GetString(i));
+ }
+ }
+ }
+ Assert.Contains("statistics", catalogs);
+ }
+
+ [Fact]
+ public async Task ExecuteAsyncWithCancellationToken()
+ {
+ using var database = _duckDb.OpenDatabase("executeasync.db");
+ using var connection = database.Connect(null);
+ using var statement = connection.CreateStatement();
+ using var cts = new CancellationTokenSource();
+
+ statement.SqlQuery = "CREATE TABLE test(x INTEGER)";
+ await statement.ExecuteUpdateAsync(cts.Token);
+
+ statement.SqlQuery = "INSERT INTO test VALUES (1), (2)";
+ UpdateResult update = await
statement.ExecuteUpdateAsync(cts.Token);
+ Assert.Equal(2, update.AffectedRows);
+
+ statement.SqlQuery = "SELECT * FROM test";
+ QueryResult result = await statement.ExecuteQueryAsync(cts.Token);
+ Assert.Equal(2, GetResultCount(result));
+ }
+
+ [Fact]
+ public async Task CancellationTokenCancelsQuery()
+ {
+ using var database = _duckDb.OpenDatabase("canceltoken.db");
+ using var connection = database.Connect(null);
+ using var statement = connection.CreateStatement();
+ using var cts = new CancellationTokenSource();
+
+ statement.SqlQuery = "SELECT count(*) FROM range(1000000000000)";
+ Task<QueryResult> query =
statement.ExecuteQueryAsync(cts.Token).AsTask();
+
+ await Task.Delay(500);
+ cts.Cancel();
+
+ // The token calls Cancel once. Keep cancelling in case that
happened before
+ // DuckDB started the query; the token stays cancelled either way.
+ await CancelUntilCompleteAsync(statement.Cancel, query);
+ OperationCanceledException exception = await
Assert.ThrowsAnyAsync<OperationCanceledException>(() => query);
+ Assert.Equal(cts.Token, exception.CancellationToken);
+
+ // There's no inner exception if the token was cancelled before
the work started
+ if (exception.InnerException != null)
+ {
+ // DuckDB reports interruption as InvalidArgument rather than
Cancelled
+ Assert.Contains("Interrupted",
Assert.IsAssignableFrom<AdbcException>(exception.InnerException).Message);
+ }
+ }
+
+ [Fact]
+ public async Task TransactionsAsync()
+ {
+ using var database = _duckDb.OpenDatabase("transactionsasync.db");
+ using var connection = database.Connect(null);
+ using var statement = connection.CreateStatement();
+
+ statement.SqlQuery = "CREATE TABLE test(x INTEGER)";
+ statement.ExecuteUpdate();
+
+ connection.AutoCommit = false;
+
+ statement.SqlQuery = "INSERT INTO test VALUES (1)";
+ statement.ExecuteUpdate();
+ await connection.RollbackAsync();
+ Assert.Equal(0, GetResultCount(statement, "SELECT * FROM test"));
+
+ statement.SqlQuery = "INSERT INTO test VALUES (2)";
+ statement.ExecuteUpdate();
+ await connection.CommitAsync();
+ Assert.Equal(1, GetResultCount(statement, "SELECT * FROM test"));
Review Comment:
Agreed. Fixed in d441eae57. A second connection now checks that the row
isn't visible before `CommitAsync` and is visible after it. With the
`CommitAsync` call removed, the test now fails. The rollback check stays on the
inserting connection, because there a missing rollback would leave the row
visible.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]