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 b99a17949 feat(csharp): add statement-level trace parent support
(#3896)
b99a17949 is described below
commit b99a17949debc3d310ccbd3380bbf93dc90a5873
Author: eric-wang-1990 <[email protected]>
AuthorDate: Fri Jan 16 09:45:36 2026 -0800
feat(csharp): add statement-level trace parent support (#3896)
## Summary
Enable setting trace parent at the statement level for connection
pooling scenarios like Power BI, where multiple queries reuse the same
connection but need different trace IDs for distributed tracing.
## Changes
- **TracingStatement**: Add `_statementTraceParent` field and
`SetTraceParent()` method
- **TracingStatement**: `TraceParent` property now returns statement
override or falls back to connection's trace parent
- **HiveServer2Statement**: Add `SetOption` case for
`AdbcOptions.Telemetry.TraceParent`
- Add comprehensive test coverage for statement-level trace parent
functionality
## Motivation
This brings C# implementation to parity with Go drivers which already
support statement-level trace parent via `SetOption`.
### Power BI Use Case
Power BI uses connection pooling where multiple queries reuse the same
connection. Each Power BI query has its own trace ID for distributed
tracing correlation. Without statement-level trace parent support, all
queries from a pooled connection would share the same trace context,
making it impossible to correlate individual query traces.
With this change, Power BI can:
```csharp
var connection = pool.GetConnection();
// Query 1 with Trace ID A
var stmt1 = connection.CreateStatement();
stmt1.SetOption("adbc.telemetry.trace_parent", powerBiQueryTraceIdA);
stmt1.ExecuteQuery();
// Query 2 with Trace ID B (same connection!)
var stmt2 = connection.CreateStatement();
stmt2.SetOption("adbc.telemetry.trace_parent", powerBiQueryTraceIdB);
stmt2.ExecuteQuery();
```
## Test Plan
Added `CanSetTraceParentOnStatement` test that verifies:
1. Statement inherits connection's trace parent by default
2. Statement can override with its own trace parent via `SetOption`
3. Activities created by the statement use the statement's trace parent
4. Setting trace parent to null falls back to connection's trace parent
All existing tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Sonnet 4.5 <[email protected]>
---
.../Apache.Arrow.Adbc/Tracing/TracingStatement.cs | 8 ++-
.../Drivers/Apache/Hive2/HiveServer2Statement.cs | 3 +
.../Tracing/TracingTests.cs | 78 ++++++++++++++++++++++
3 files changed, 88 insertions(+), 1 deletion(-)
diff --git a/csharp/src/Apache.Arrow.Adbc/Tracing/TracingStatement.cs
b/csharp/src/Apache.Arrow.Adbc/Tracing/TracingStatement.cs
index b2d83aa33..486b15ced 100644
--- a/csharp/src/Apache.Arrow.Adbc/Tracing/TracingStatement.cs
+++ b/csharp/src/Apache.Arrow.Adbc/Tracing/TracingStatement.cs
@@ -22,6 +22,7 @@ namespace Apache.Arrow.Adbc.Tracing
public abstract class TracingStatement : AdbcStatement, ITracingStatement
{
private readonly ActivityTrace _trace;
+ private string? _statementTraceParent;
public TracingStatement(TracingConnection connection)
{
@@ -30,7 +31,12 @@ namespace Apache.Arrow.Adbc.Tracing
ActivityTrace IActivityTracer.Trace => _trace;
- string? IActivityTracer.TraceParent => _trace.TraceParent;
+ string? IActivityTracer.TraceParent => _statementTraceParent ??
_trace.TraceParent;
+
+ protected void SetTraceParent(string? traceParent)
+ {
+ _statementTraceParent = traceParent;
+ }
public abstract string AssemblyVersion { get; }
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
index 25e14f1cc..ea71ecf18 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2Statement.cs
@@ -319,6 +319,9 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
this.EscapePatternWildcards = escapePatternWildcards;
}
break;
+ case AdbcOptions.Telemetry.TraceParent:
+ SetTraceParent(string.IsNullOrWhiteSpace(value) ? null :
value);
+ break;
default:
throw AdbcException.NotImplemented($"Option '{key}' is not
implemented.");
}
diff --git a/csharp/test/Apache.Arrow.Adbc.Tests/Tracing/TracingTests.cs
b/csharp/test/Apache.Arrow.Adbc.Tests/Tracing/TracingTests.cs
index b1134c998..021eaed53 100644
--- a/csharp/test/Apache.Arrow.Adbc.Tests/Tracing/TracingTests.cs
+++ b/csharp/test/Apache.Arrow.Adbc.Tests/Tracing/TracingTests.cs
@@ -255,6 +255,57 @@ namespace Apache.Arrow.Adbc.Tests.Tracing
}
}
+ [Fact]
+ internal void CanSetTraceParentOnStatement()
+ {
+ string activitySourceName = NewName();
+ Queue<Activity> exportedActivities = new();
+ using TracerProvider provider = Sdk.CreateTracerProviderBuilder()
+ .AddSource(activitySourceName)
+ .AddTestActivityQueueExporter(exportedActivities)
+ .Build();
+
+ // Create a connection with a connection-level trace parent
+ const string connectionTraceParent =
"00-11111111111111111111111111111111-2222222222222222-01";
+ var connection = new MyTracingConnection(
+ new Dictionary<string, string> { {
AdbcOptions.Telemetry.TraceParent, connectionTraceParent } },
+ activitySourceName);
+
+ // Create a statement and verify it uses the connection's trace
parent
+ var statement = new MyTracingStatement(connection);
+ Assert.Equal(connectionTraceParent,
((IActivityTracer)statement).TraceParent);
+
+ // Test 1: Execute with connection's trace parent
+ string eventName1 = NewName();
+ statement.MethodWithActivity(eventName1);
+ Assert.Single(exportedActivities);
+ var activity1 = exportedActivities.First();
+ Assert.Equal(connectionTraceParent, activity1.ParentId);
+
+ // Test 2: Set statement-specific trace parent
+ exportedActivities.Clear();
+ const string statementTraceParent =
"00-33333333333333333333333333333333-4444444444444444-01";
+ statement.SetOption(AdbcOptions.Telemetry.TraceParent,
statementTraceParent);
+ Assert.Equal(statementTraceParent,
((IActivityTracer)statement).TraceParent);
+
+ string eventName2 = NewName();
+ statement.MethodWithActivity(eventName2);
+ Assert.Single(exportedActivities);
+ var activity2 = exportedActivities.First();
+ Assert.Equal(statementTraceParent, activity2.ParentId);
+
+ // Test 3: Set trace parent to null (should fall back to
connection's trace parent)
+ exportedActivities.Clear();
+ statement.SetOption(AdbcOptions.Telemetry.TraceParent, null);
+ Assert.Equal(connectionTraceParent,
((IActivityTracer)statement).TraceParent);
+
+ string eventName3 = NewName();
+ statement.MethodWithActivity(eventName3);
+ Assert.Single(exportedActivities);
+ var activity3 = exportedActivities.First();
+ Assert.Equal(connectionTraceParent, activity3.ParentId);
+ }
+
internal static string NewName() =>
Guid.NewGuid().ToString().Replace("-", "").ToLower();
protected virtual void Dispose(bool disposing)
@@ -451,6 +502,33 @@ namespace Apache.Arrow.Adbc.Tests.Tracing
public override IArrowArrayStream GetTableTypes() => throw new
NotImplementedException();
}
+ private class MyTracingStatement(TracingConnection connection) :
TracingStatement(connection)
+ {
+ public override string AssemblyVersion => "1.0.0";
+ public override string AssemblyName => "TestStatement";
+
+ public void MethodWithActivity(string activityName)
+ {
+ this.TraceActivity(activity =>
+ {
+ activity?.AddTag("testTag", "testValue");
+ }, activityName);
+ }
+
+ public override void SetOption(string key, string? value)
+ {
+ if (key == AdbcOptions.Telemetry.TraceParent)
+ {
+ SetTraceParent(string.IsNullOrWhiteSpace(value) ? null :
value);
+ return;
+ }
+ throw AdbcException.NotImplemented($"Option '{key}' is not
implemented.");
+ }
+
+ public override QueryResult ExecuteQuery() => throw new
NotImplementedException();
+ public override UpdateResult ExecuteUpdate() => throw new
NotImplementedException();
+ }
+
internal class ActivityQueueExporter(Queue<Activity>
exportedActivities) : BaseExporter<Activity>
{
private Queue<Activity> ExportedActivities { get; } =
exportedActivities;