From 1ea8faafc2902547d127edb742c0a45ce37a8bda Mon Sep 17 00:00:00 2001
From: Bence Szikora <1381651+benceszikora@users.noreply.github.com>
Date: Fri, 1 Nov 2024 09:41:28 -0500
Subject: [PATCH 1/5] display kind, status, instrumentation library, links,
 events and state data for traces

---
 .../configEditor/TracesConfig.test.tsx        | 432 ++++++++++++++++++
 src/components/configEditor/TracesConfig.tsx  |  88 +++-
 .../queryBuilder/views/TraceQueryBuilder.tsx  | 126 ++++-
 src/data/CHDatasource.ts                      |   8 +
 src/data/sqlGenerator.test.ts                 |  88 +++-
 src/data/sqlGenerator.ts                      |  52 ++-
 src/labels.ts                                 |  73 ++-
 src/otel.ts                                   |   7 +
 src/types/config.ts                           |   8 +
 src/types/queryBuilder.ts                     |   7 +
 src/views/CHConfigEditor.tsx                  |   8 +
 11 files changed, 873 insertions(+), 24 deletions(-)

diff --git a/src/components/configEditor/TracesConfig.test.tsx b/src/components/configEditor/TracesConfig.test.tsx
index 9f97c0c1..2f946367 100644
--- a/src/components/configEditor/TracesConfig.test.tsx
+++ b/src/components/configEditor/TracesConfig.test.tsx
@@ -24,6 +24,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -48,6 +56,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -79,6 +95,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -110,6 +134,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -140,6 +172,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -171,6 +211,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -202,6 +250,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -233,6 +289,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -264,6 +328,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -295,6 +367,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -326,6 +406,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -357,6 +445,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -388,6 +484,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={onStartTimeColumnChange}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -419,6 +523,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={onTagsColumnChange}
         onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -450,6 +562,14 @@ describe('TracesConfig', () => {
         onStartTimeColumnChange={() => {}}
         onTagsColumnChange={() => {}}
         onServiceTagsColumnChange={onServiceTagsColumnChange}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -461,4 +581,316 @@ describe('TracesConfig', () => {
     expect(onServiceTagsColumnChange).toHaveBeenCalledTimes(1);
     expect(onServiceTagsColumnChange).toHaveBeenCalledWith('changed');
   });
+
+  it('should call onKindColumnChange when changed', () => {
+    const onKindColumnChange = jest.fn();
+    const result = render(
+      <TracesConfig
+        tracesConfig={{}}
+        onDefaultDatabaseChange={() => {}}
+        onDefaultTableChange={() => {}}
+        onOtelEnabledChange={() => {}}
+        onOtelVersionChange={() => {}}
+        onTraceIdColumnChange={() => {}}
+        onSpanIdColumnChange={() => {}}
+        onOperationNameColumnChange={() => {}}
+        onParentSpanIdColumnChange={() => {}}
+        onServiceNameColumnChange={() => {}}
+        onDurationColumnChange={() => {}}
+        onDurationUnitChange={() => {}}
+        onStartTimeColumnChange={() => {}}
+        onTagsColumnChange={() => {}}
+        onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={onKindColumnChange}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
+      />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.kind.label));
+    expect(input).toBeInTheDocument();
+    fireEvent.change(input, { target: { value: 'changed' } });
+    fireEvent.blur(input);
+    expect(onKindColumnChange).toHaveBeenCalledTimes(1);
+    expect(onKindColumnChange).toHaveBeenCalledWith('changed');
+  });
+
+  it('should call onStatusCodeColumnChange when changed', () => {
+    const onStatusCodeColumnChange = jest.fn();
+    const result = render(
+      <TracesConfig
+        tracesConfig={{}}
+        onDefaultDatabaseChange={() => {}}
+        onDefaultTableChange={() => {}}
+        onOtelEnabledChange={() => {}}
+        onOtelVersionChange={() => {}}
+        onTraceIdColumnChange={() => {}}
+        onSpanIdColumnChange={() => {}}
+        onOperationNameColumnChange={() => {}}
+        onParentSpanIdColumnChange={() => {}}
+        onServiceNameColumnChange={() => {}}
+        onDurationColumnChange={() => {}}
+        onDurationUnitChange={() => {}}
+        onStartTimeColumnChange={() => {}}
+        onTagsColumnChange={() => {}}
+        onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={onStatusCodeColumnChange}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
+      />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.statusCode.label));
+    expect(input).toBeInTheDocument();
+    fireEvent.change(input, { target: { value: 'changed' } });
+    fireEvent.blur(input);
+    expect(onStatusCodeColumnChange).toHaveBeenCalledTimes(1);
+    expect(onStatusCodeColumnChange).toHaveBeenCalledWith('changed');
+  });
+
+  it('should call onStatusMessageColumnChange when changed', () => {
+    const onStatusMessageColumnChange = jest.fn();
+    const result = render(
+      <TracesConfig
+        tracesConfig={{}}
+        onDefaultDatabaseChange={() => {}}
+        onDefaultTableChange={() => {}}
+        onOtelEnabledChange={() => {}}
+        onOtelVersionChange={() => {}}
+        onTraceIdColumnChange={() => {}}
+        onSpanIdColumnChange={() => {}}
+        onOperationNameColumnChange={() => {}}
+        onParentSpanIdColumnChange={() => {}}
+        onServiceNameColumnChange={() => {}}
+        onDurationColumnChange={() => {}}
+        onDurationUnitChange={() => {}}
+        onStartTimeColumnChange={() => {}}
+        onTagsColumnChange={() => {}}
+        onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={onStatusMessageColumnChange}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
+      />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.statusMessage.label));
+    expect(input).toBeInTheDocument();
+    fireEvent.change(input, { target: { value: 'changed' } });
+    fireEvent.blur(input);
+    expect(onStatusMessageColumnChange).toHaveBeenCalledTimes(1);
+    expect(onStatusMessageColumnChange).toHaveBeenCalledWith('changed');
+  });
+
+  it('should call onInstrumentationLibraryNameColumnChange when changed', () => {
+    const onInstrumentationLibraryNameColumnChange = jest.fn();
+    const result = render(
+      <TracesConfig
+        tracesConfig={{}}
+        onDefaultDatabaseChange={() => {}}
+        onDefaultTableChange={() => {}}
+        onOtelEnabledChange={() => {}}
+        onOtelVersionChange={() => {}}
+        onTraceIdColumnChange={() => {}}
+        onSpanIdColumnChange={() => {}}
+        onOperationNameColumnChange={() => {}}
+        onParentSpanIdColumnChange={() => {}}
+        onServiceNameColumnChange={() => {}}
+        onDurationColumnChange={() => {}}
+        onDurationUnitChange={() => {}}
+        onStartTimeColumnChange={() => {}}
+        onTagsColumnChange={() => {}}
+        onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={onInstrumentationLibraryNameColumnChange}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
+      />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.instrumentationLibraryName.label));
+    expect(input).toBeInTheDocument();
+    fireEvent.change(input, { target: { value: 'changed' } });
+    fireEvent.blur(input);
+    expect(onInstrumentationLibraryNameColumnChange).toHaveBeenCalledTimes(1);
+    expect(onInstrumentationLibraryNameColumnChange).toHaveBeenCalledWith('changed');
+  });
+
+  it('should call onInstrumentationLibraryVersionColumnChange when changed', () => {
+    const onInstrumentationLibraryVersionColumnChange = jest.fn();
+    const result = render(
+      <TracesConfig
+        tracesConfig={{}}
+        onDefaultDatabaseChange={() => {}}
+        onDefaultTableChange={() => {}}
+        onOtelEnabledChange={() => {}}
+        onOtelVersionChange={() => {}}
+        onTraceIdColumnChange={() => {}}
+        onSpanIdColumnChange={() => {}}
+        onOperationNameColumnChange={() => {}}
+        onParentSpanIdColumnChange={() => {}}
+        onServiceNameColumnChange={() => {}}
+        onDurationColumnChange={() => {}}
+        onDurationUnitChange={() => {}}
+        onStartTimeColumnChange={() => {}}
+        onTagsColumnChange={() => {}}
+        onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={onInstrumentationLibraryVersionColumnChange}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
+      />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.instrumentationLibraryVersion.label));
+    expect(input).toBeInTheDocument();
+    fireEvent.change(input, { target: { value: 'changed' } });
+    fireEvent.blur(input);
+    expect(onInstrumentationLibraryVersionColumnChange).toHaveBeenCalledTimes(1);
+    expect(onInstrumentationLibraryVersionColumnChange).toHaveBeenCalledWith('changed');
+  });
+
+  it('should call onStateColumnChange when changed', () => {
+    const onStateColumnChange = jest.fn();
+    const result = render(
+      <TracesConfig
+        tracesConfig={{}}
+        onDefaultDatabaseChange={() => {}}
+        onDefaultTableChange={() => {}}
+        onOtelEnabledChange={() => {}}
+        onOtelVersionChange={() => {}}
+        onTraceIdColumnChange={() => {}}
+        onSpanIdColumnChange={() => {}}
+        onOperationNameColumnChange={() => {}}
+        onParentSpanIdColumnChange={() => {}}
+        onServiceNameColumnChange={() => {}}
+        onDurationColumnChange={() => {}}
+        onDurationUnitChange={() => {}}
+        onStartTimeColumnChange={() => {}}
+        onTagsColumnChange={() => {}}
+        onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={onStateColumnChange}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={() => {}}
+      />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.state.label));
+    expect(input).toBeInTheDocument();
+    fireEvent.change(input, { target: { value: 'changed' } });
+    fireEvent.blur(input);
+    expect(onStateColumnChange).toHaveBeenCalledTimes(1);
+    expect(onStateColumnChange).toHaveBeenCalledWith('changed');
+  });
+
+  it('should call onEventsColumnChange when changed', () => {
+    const onEventsColumnChange = jest.fn();
+    const result = render(
+      <TracesConfig
+        tracesConfig={{}}
+        onDefaultDatabaseChange={() => {}}
+        onDefaultTableChange={() => {}}
+        onOtelEnabledChange={() => {}}
+        onOtelVersionChange={() => {}}
+        onTraceIdColumnChange={() => {}}
+        onSpanIdColumnChange={() => {}}
+        onOperationNameColumnChange={() => {}}
+        onParentSpanIdColumnChange={() => {}}
+        onServiceNameColumnChange={() => {}}
+        onDurationColumnChange={() => {}}
+        onDurationUnitChange={() => {}}
+        onStartTimeColumnChange={() => {}}
+        onTagsColumnChange={() => {}}
+        onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={onEventsColumnChange}
+        onLinksColumnChange={() => {}}
+      />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.events.label));
+    expect(input).toBeInTheDocument();
+    fireEvent.change(input, { target: { value: 'changed' } });
+    fireEvent.blur(input);
+    expect(onEventsColumnChange).toHaveBeenCalledTimes(1);
+    expect(onEventsColumnChange).toHaveBeenCalledWith('changed');
+  });
+
+  it('should call onLinksColumnChange when changed', () => {
+    const onLinksColumnChange = jest.fn();
+    const result = render(
+      <TracesConfig
+        tracesConfig={{}}
+        onDefaultDatabaseChange={() => {}}
+        onDefaultTableChange={() => {}}
+        onOtelEnabledChange={() => {}}
+        onOtelVersionChange={() => {}}
+        onTraceIdColumnChange={() => {}}
+        onSpanIdColumnChange={() => {}}
+        onOperationNameColumnChange={() => {}}
+        onParentSpanIdColumnChange={() => {}}
+        onServiceNameColumnChange={() => {}}
+        onDurationColumnChange={() => {}}
+        onDurationUnitChange={() => {}}
+        onStartTimeColumnChange={() => {}}
+        onTagsColumnChange={() => {}}
+        onServiceTagsColumnChange={() => {}}
+        onKindColumnChange={() => {}}
+        onStatusCodeColumnChange={() => {}}
+        onStatusMessageColumnChange={() => {}}
+        onInstrumentationLibraryNameColumnChange={() => {}}
+        onInstrumentationLibraryVersionColumnChange={() => {}}
+        onStateColumnChange={() => {}}
+        onEventsColumnChange={() => {}}
+        onLinksColumnChange={onLinksColumnChange}
+      />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.links.label));
+    expect(input).toBeInTheDocument();
+    fireEvent.change(input, { target: { value: 'changed' } });
+    fireEvent.blur(input);
+    expect(onLinksColumnChange).toHaveBeenCalledTimes(1);
+    expect(onLinksColumnChange).toHaveBeenCalledWith('changed');
+  });
 });
diff --git a/src/components/configEditor/TracesConfig.tsx b/src/components/configEditor/TracesConfig.tsx
index 27288042..8cf563a1 100644
--- a/src/components/configEditor/TracesConfig.tsx
+++ b/src/components/configEditor/TracesConfig.tsx
@@ -27,6 +27,14 @@ interface TraceConfigProps {
   onStartTimeColumnChange: (v: string) => void;
   onTagsColumnChange: (v: string) => void;
   onServiceTagsColumnChange: (v: string) => void;
+  onKindColumnChange: (v: string) => void;
+  onStatusCodeColumnChange: (v: string) => void;
+  onStatusMessageColumnChange: (v: string) => void;
+  onInstrumentationLibraryNameColumnChange: (v: string) => void;
+  onInstrumentationLibraryVersionColumnChange: (v: string) => void;
+  onStateColumnChange: (v: string) => void;
+  onEventsColumnChange: (v: string) => void;
+  onLinksColumnChange: (v: string) => void;
 }
 
 export const TracesConfig = (props: TraceConfigProps) => {
@@ -35,13 +43,17 @@ export const TracesConfig = (props: TraceConfigProps) => {
     onOtelEnabledChange, onOtelVersionChange,
     onTraceIdColumnChange, onSpanIdColumnChange, onOperationNameColumnChange, onParentSpanIdColumnChange,
     onServiceNameColumnChange, onDurationColumnChange, onDurationUnitChange, onStartTimeColumnChange,
-    onTagsColumnChange, onServiceTagsColumnChange,
+    onTagsColumnChange, onServiceTagsColumnChange, onKindColumnChange, onStatusCodeColumnChange,
+    onStatusMessageColumnChange, onInstrumentationLibraryNameColumnChange, onInstrumentationLibraryVersionColumnChange,
+    onStateColumnChange, onEventsColumnChange, onLinksColumnChange
   } = props;
   let {
     defaultDatabase, defaultTable,
     otelEnabled, otelVersion,
     traceIdColumn, spanIdColumn, operationNameColumn, parentSpanIdColumn, serviceNameColumn,
-    durationColumn, durationUnit, startTimeColumn, tagsColumn, serviceTagsColumn
+    durationColumn, durationUnit, startTimeColumn, tagsColumn, serviceTagsColumn, kindColumn,
+    statusCodeColumn, statusMessageColumn, instrumentationLibraryNameColumn, instrumentationLibraryVersionColumn,
+    stateColumn, eventsColumn, linksColumn
   } = (props.tracesConfig || {}) as CHTracesConfig;
   const labels = allLabels.components.Config.TracesConfig;
 
@@ -56,6 +68,14 @@ export const TracesConfig = (props: TraceConfigProps) => {
     durationColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceDurationTime);
     tagsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceTags);
     serviceTagsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceServiceTags);
+    kindColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceKind);
+    statusCodeColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceStatusCode);
+    statusMessageColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceStatusMessage);
+    instrumentationLibraryNameColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceInstrumentationLibraryName);
+    instrumentationLibraryVersionColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceInstrumentationLibraryVersion);
+    stateColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceState);
+    eventsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceEvents);
+    linksColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceLinks);
     durationUnit = otelConfig.traceDurationUnit.toString();
   }
 
@@ -181,6 +201,70 @@ export const TracesConfig = (props: TraceConfigProps) => {
           value={serviceTagsColumn || ''}
           onChange={onServiceTagsColumnChange}
         />
+        <LabeledInput
+          disabled={otelEnabled}
+          label={labels.columns.kind.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.kind.label)}
+          tooltip={labels.columns.kind.tooltip}
+          value={kindColumn || ''}
+          onChange={onKindColumnChange}
+        />
+        <LabeledInput
+          disabled={otelEnabled}
+          label={labels.columns.statusCode.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.statusCode.label)}
+          tooltip={labels.columns.statusCode.tooltip}
+          value={statusCodeColumn || ''}
+          onChange={onStatusCodeColumnChange}
+        />
+        <LabeledInput
+          disabled={otelEnabled}
+          label={labels.columns.statusMessage.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.statusMessage.label)}
+          tooltip={labels.columns.statusMessage.tooltip}
+          value={statusMessageColumn || ''}
+          onChange={onStatusMessageColumnChange}
+        />
+        <LabeledInput
+          disabled={otelEnabled}
+          label={labels.columns.instrumentationLibraryName.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.instrumentationLibraryName.label)}
+          tooltip={labels.columns.instrumentationLibraryName.tooltip}
+          value={instrumentationLibraryNameColumn || ''}
+          onChange={onInstrumentationLibraryNameColumnChange}
+        />
+        <LabeledInput
+          disabled={otelEnabled}
+          label={labels.columns.instrumentationLibraryVersion.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.instrumentationLibraryVersion.label)}
+          tooltip={labels.columns.instrumentationLibraryVersion.tooltip}
+          value={instrumentationLibraryVersionColumn || ''}
+          onChange={onInstrumentationLibraryVersionColumnChange}
+        />
+        <LabeledInput
+          disabled={otelEnabled}
+          label={labels.columns.state.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.state.label)}
+          tooltip={labels.columns.state.tooltip}
+          value={stateColumn || ''}
+          onChange={onStateColumnChange}
+        />
+        <LabeledInput
+          disabled={otelEnabled}
+          label={labels.columns.events.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.events.label)}
+          tooltip={labels.columns.events.tooltip}
+          value={eventsColumn || ''}
+          onChange={onEventsColumnChange}
+        />
+        <LabeledInput
+          disabled={otelEnabled}
+          label={labels.columns.links.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.links.label)}
+          tooltip={labels.columns.links.tooltip}
+          value={linksColumn || ''}
+          onChange={onLinksColumnChange}
+        />
       </ConfigSubSection>
     </ConfigSection>
   );
diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx
index d0824aa4..1097db45 100644
--- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx
+++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx
@@ -38,6 +38,14 @@ interface TraceQueryBuilderState {
   durationUnit: TimeUnit;
   tagsColumn?: SelectedColumn;
   serviceTagsColumn?: SelectedColumn;
+  kindColumn?: SelectedColumn;
+  statusCodeColumn?: SelectedColumn;
+  statusMessageColumn?: SelectedColumn;
+  instrumentationLibraryNameColumn?: SelectedColumn;
+  instrumentationLibraryVersionColumn?: SelectedColumn;
+  stateColumn?: SelectedColumn;
+  eventsColumn?: SelectedColumn;
+  linksColumn?: SelectedColumn;
   traceId: string;
   orderBy: OrderBy[];
   limit: number;
@@ -66,6 +74,14 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
     durationUnit: builderOptions.meta?.traceDurationUnit || TimeUnit.Nanoseconds,
     tagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceTags),
     serviceTagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceServiceTags),
+    kindColumn: getColumnByHint(builderOptions, ColumnHint.TraceKind),
+    statusCodeColumn: getColumnByHint(builderOptions, ColumnHint.TraceStatusCode),
+    statusMessageColumn: getColumnByHint(builderOptions, ColumnHint.TraceStatusMessage),
+    instrumentationLibraryNameColumn: getColumnByHint(builderOptions, ColumnHint.TraceInstrumentationLibraryName),
+    instrumentationLibraryVersionColumn: getColumnByHint(builderOptions, ColumnHint.TraceInstrumentationLibraryVersion),
+    stateColumn: getColumnByHint(builderOptions, ColumnHint.TraceState),
+    eventsColumn: getColumnByHint(builderOptions, ColumnHint.TraceEvents),
+    linksColumn: getColumnByHint(builderOptions, ColumnHint.TraceLinks),
     traceId: builderOptions.meta?.traceId || '',
     orderBy: builderOptions.orderBy || [],
     limit: builderOptions.limit || 0,
@@ -82,7 +98,15 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
       next.startTimeColumn,
       next.durationTimeColumn,
       next.tagsColumn,
-      next.serviceTagsColumn
+      next.serviceTagsColumn,
+      next.kindColumn,
+      next.statusCodeColumn,
+      next.statusMessageColumn,
+      next.instrumentationLibraryNameColumn,
+      next.instrumentationLibraryVersionColumn,
+      next.stateColumn,
+      next.eventsColumn,
+      next.linksColumn,
     ].filter(c => c !== undefined) as SelectedColumn[];
 
     builderOptionsDispatch(setOptions({
@@ -256,6 +280,106 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
             inline
           />
         </div>
+        <div className="gf-form">
+          <ColumnSelect
+            disabled={builderState.otelEnabled}
+            allColumns={allColumns}
+            selectedColumn={builderState.kindColumn}
+            invalid={!builderState.kindColumn}
+            onColumnChange={onOptionChange('kindColumn')}
+            columnHint={ColumnHint.TraceKind}
+            label={labels.columns.kind.label}
+            tooltip={labels.columns.kind.tooltip}
+            wide
+          />
+          <ColumnSelect
+            disabled={builderState.otelEnabled}
+            allColumns={allColumns}
+            selectedColumn={builderState.statusCodeColumn}
+            invalid={!builderState.statusCodeColumn}
+            onColumnChange={onOptionChange('statusCodeColumn')}
+            columnHint={ColumnHint.TraceStatusCode}
+            label={labels.columns.statusCode.label}
+            tooltip={labels.columns.statusCode.tooltip}
+            wide
+            inline
+          />
+        </div>
+        <div className="gf-form">
+          <ColumnSelect
+            disabled={builderState.otelEnabled}
+            allColumns={allColumns}
+            selectedColumn={builderState.statusMessageColumn}
+            invalid={!builderState.statusMessageColumn}
+            onColumnChange={onOptionChange('statusMessageColumn')}
+            columnHint={ColumnHint.TraceStatusMessage}
+            label={labels.columns.statusMessage.label}
+            tooltip={labels.columns.statusMessage.tooltip}
+            wide
+          />
+          <ColumnSelect
+            disabled={builderState.otelEnabled}
+            allColumns={allColumns}
+            selectedColumn={builderState.instrumentationLibraryNameColumn}
+            invalid={!builderState.instrumentationLibraryNameColumn}
+            onColumnChange={onOptionChange('instrumentationLibraryNameColumn')}
+            columnHint={ColumnHint.TraceInstrumentationLibraryName}
+            label={labels.columns.instrumentationLibraryName.label}
+            tooltip={labels.columns.instrumentationLibraryName.tooltip}
+            wide
+            inline
+          />
+        </div>
+        <div className="gf-form">
+          <ColumnSelect
+            disabled={builderState.otelEnabled}
+            allColumns={allColumns}
+            selectedColumn={builderState.instrumentationLibraryVersionColumn}
+            invalid={!builderState.instrumentationLibraryVersionColumn}
+            onColumnChange={onOptionChange('instrumentationLibraryVersionColumn')}
+            columnHint={ColumnHint.TraceInstrumentationLibraryVersion}
+            label={labels.columns.instrumentationLibraryVersion.label}
+            tooltip={labels.columns.instrumentationLibraryVersion.tooltip}
+            wide
+          />
+          <ColumnSelect
+            disabled={builderState.otelEnabled}
+            allColumns={allColumns}
+            selectedColumn={builderState.stateColumn}
+            invalid={!builderState.stateColumn}
+            onColumnChange={onOptionChange('stateColumn')}
+            columnHint={ColumnHint.TraceState}
+            label={labels.columns.state.label}
+            tooltip={labels.columns.state.tooltip}
+            wide
+            inline
+          />
+        </div>
+        <div className="gf-form">
+          <ColumnSelect
+            disabled={builderState.otelEnabled}
+            allColumns={allColumns}
+            selectedColumn={builderState.eventsColumn}
+            invalid={!builderState.eventsColumn}
+            onColumnChange={onOptionChange('eventsColumn')}
+            columnHint={ColumnHint.TraceEvents}
+            label={labels.columns.events.label}
+            tooltip={labels.columns.events.tooltip}
+            wide
+          />
+          <ColumnSelect
+            disabled={builderState.otelEnabled}
+            allColumns={allColumns}
+            selectedColumn={builderState.linksColumn}
+            invalid={!builderState.linksColumn}
+            onColumnChange={onOptionChange('linksColumn')}
+            columnHint={ColumnHint.TraceLinks}
+            label={labels.columns.links.label}
+            tooltip={labels.columns.links.tooltip}
+            wide
+            inline
+          />
+        </div>
       </Collapse>
       <Collapse label={labels.filtersSection}
         collapsible
diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts
index ad41970b..396ad240 100644
--- a/src/data/CHDatasource.ts
+++ b/src/data/CHDatasource.ts
@@ -493,6 +493,14 @@ export class Datasource
     traceConfig.startTimeColumn && result.set(ColumnHint.Time, traceConfig.startTimeColumn);
     traceConfig.tagsColumn && result.set(ColumnHint.TraceTags, traceConfig.tagsColumn);
     traceConfig.serviceTagsColumn && result.set(ColumnHint.TraceServiceTags, traceConfig.serviceTagsColumn);
+    traceConfig.kindColumn && result.set(ColumnHint.TraceKind, traceConfig.kindColumn);
+    traceConfig.statusCodeColumn && result.set(ColumnHint.TraceStatusCode, traceConfig.statusCodeColumn);
+    traceConfig.statusMessageColumn && result.set(ColumnHint.TraceStatusMessage, traceConfig.statusMessageColumn);
+    traceConfig.instrumentationLibraryNameColumn && result.set(ColumnHint.TraceInstrumentationLibraryName, traceConfig.instrumentationLibraryNameColumn);
+    traceConfig.instrumentationLibraryVersionColumn && result.set(ColumnHint.TraceInstrumentationLibraryVersion, traceConfig.instrumentationLibraryVersionColumn);
+    traceConfig.stateColumn && result.set(ColumnHint.TraceState, traceConfig.stateColumn);
+    traceConfig.eventsColumn && result.set(ColumnHint.TraceEvents, traceConfig.eventsColumn);
+    traceConfig.linksColumn && result.set(ColumnHint.TraceLinks, traceConfig.linksColumn);
 
     return result;
   }
diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts
index fe27fcb3..f35fd0e6 100644
--- a/src/data/sqlGenerator.test.ts
+++ b/src/data/sqlGenerator.test.ts
@@ -8,9 +8,9 @@ describe('SQL Generator', () => {
       table: 'sample',
       queryType: QueryType.Table,
       columns: [
-          { name: 'a', type: 'UInt64' },
-          { name: 'b', type: 'String' },
-          { name: 'c', type: 'String' },
+        { name: 'a', type: 'UInt64' },
+        { name: 'b', type: 'String' },
+        { name: 'c', type: 'String' },
       ],
       limit: 1000,
       filters: [
@@ -41,9 +41,9 @@ describe('SQL Generator', () => {
       queryType: QueryType.Table,
       mode: BuilderMode.Aggregate,
       columns: [
-          { name: 'a', type: 'DateTime' },
-          { name: 'b', type: 'String' },
-          { name: 'c', type: 'String' },
+        { name: 'a', type: 'DateTime' },
+        { name: 'b', type: 'String' },
+        { name: 'c', type: 'String' },
       ],
       aggregates: [
         { aggregateType: AggregateType.Count, column: '*', alias: 'd' }
@@ -77,9 +77,9 @@ describe('SQL Generator', () => {
       table: 'logs',
       queryType: QueryType.Logs,
       columns: [
-          { name: 'log_ts', type: 'DateTime', hint: ColumnHint.Time },
-          { name: 'log_level', type: 'String', hint: ColumnHint.LogLevel },
-          { name: 'log_body', type: 'String', hint: ColumnHint.LogMessage },
+        { name: 'log_ts', type: 'DateTime', hint: ColumnHint.Time },
+        { name: 'log_level', type: 'String', hint: ColumnHint.LogLevel },
+        { name: 'log_body', type: 'String', hint: ColumnHint.LogMessage },
       ],
       limit: 1000,
       filters: [
@@ -122,8 +122,8 @@ describe('SQL Generator', () => {
       table: 'time_data',
       queryType: QueryType.TimeSeries,
       columns: [
-          { name: 'time_field', type: 'DateTime', hint: ColumnHint.Time },
-          { name: 'number_field', type: 'UInt64' },
+        { name: 'time_field', type: 'DateTime', hint: ColumnHint.Time },
+        { name: 'number_field', type: 'UInt64' },
       ],
       limit: 100,
       filters: [
@@ -154,8 +154,8 @@ describe('SQL Generator', () => {
       table: 'time_data',
       queryType: QueryType.TimeSeries,
       columns: [
-          { name: 'time_field', type: 'DateTime', hint: ColumnHint.Time },
-          { name: 'number_field', type: 'UInt64' },
+        { name: 'time_field', type: 'DateTime', hint: ColumnHint.Time },
+        { name: 'number_field', type: 'UInt64' },
       ],
       limit: 100,
       aggregates: [{ aggregateType: AggregateType.Sum, column: 'number_field', alias: 'total' }],
@@ -226,6 +226,68 @@ describe('SQL Generator', () => {
     expect(sql).toEqual(expectedSqlParts.join(' '));
   });
 
+  it('generates trace ID query with additional fields (kind, statusCode, statusMessage, instrumentationLibraryName, instrumentationLibraryVersion, traceState, logs, references) and OTel enabled', () => {
+    const opts: QueryBuilderOptions = {
+      database: 'default',
+      table: 'otel_traces',
+      queryType: QueryType.Traces,
+      columns: [
+        { name: 'TraceId', type: 'String', hint: ColumnHint.TraceId },
+        { name: 'SpanId', type: 'String', hint: ColumnHint.TraceSpanId },
+        { name: 'ParentSpanId', type: 'String', hint: ColumnHint.TraceParentSpanId },
+        { name: 'ServiceName', type: 'LowCardinality(String)', hint: ColumnHint.TraceServiceName },
+        { name: 'SpanName', type: 'LowCardinality(String)', hint: ColumnHint.TraceOperationName },
+        { name: 'Timestamp', type: 'DateTime64(9)', hint: ColumnHint.Time },
+        { name: 'Duration', type: 'Int64', hint: ColumnHint.TraceDurationTime },
+        { name: 'SpanAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceTags },
+        { name: 'ResourceAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceServiceTags },
+        { name: 'StatusCode', type: 'LowCardinality(String)', hint: ColumnHint.TraceStatusCode },
+        { name: 'Kind', type: 'String', hint: ColumnHint.TraceKind },
+        { name: 'StatusMessage', type: 'String', hint: ColumnHint.TraceStatusMessage },
+        { name: 'InstrumentationLibraryName', type: 'String', hint: ColumnHint.TraceInstrumentationLibraryName },
+        { name: 'InstrumentationLibraryVersion', type: 'String', hint: ColumnHint.TraceInstrumentationLibraryVersion },
+        { name: 'TraceState', type: 'String', hint: ColumnHint.TraceState },
+        { name: 'Events', type: 'Array(Tuple(name String, timestamp UInt64, attributes Map(String, String)))', hint: ColumnHint.TraceEvents },
+        { name: 'Links', type: 'Array(Tuple(traceID String, spanID String, traceState String, attributes Map(String, String)))', hint: ColumnHint.TraceLinks },
+      ],
+      filters: [],
+      meta: {
+        minimized: true,
+        otelEnabled: true,
+        otelVersion: 'latest',
+        traceDurationUnit: TimeUnit.Nanoseconds,
+        isTraceIdMode: true,
+        traceId: 'abcdefg'
+      },
+      limit: 1000,
+      orderBy: []
+    };
+
+    const expectedSqlParts = [
+      `WITH 'abcdefg' as trace_id, (SELECT min(Start) FROM "default"."otel_traces_trace_id_ts" WHERE TraceId = trace_id) as trace_start,`,
+      `(SELECT max(End) + 1 FROM "default"."otel_traces_trace_id_ts" WHERE TraceId = trace_id) as trace_end`,
+      'SELECT "TraceId" as traceID, "SpanId" as spanID, "ParentSpanId" as parentSpanID,',
+      '"ServiceName" as serviceName, "SpanName" as operationName, multiply(toUnixTimestamp64Nano("Timestamp"), 0.000001) as startTime,',
+      'multiply("Duration", 0.000001) as duration,',
+      `arrayMap(key -> map('key', key, 'value',"SpanAttributes"[key]),`,
+      `mapKeys("SpanAttributes")) as tags,`,
+      `arrayMap(key -> map('key', key, 'value',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags,`,
+      `if("StatusCode" IN ('Error', 'STATUS_CODE_ERROR'), 2, 0) as statusCode,`,
+      '"Kind" as kind,',
+      '"StatusMessage" as statusMessage,',
+      '"InstrumentationLibraryName" as instrumentationLibraryName,',
+      '"InstrumentationLibraryVersion" as instrumentationLibraryVersion,',
+      '"TraceState" as traceState,',
+      `arrayMap(event -> tuple(multiply(toFloat64(event.2), 0.000001), arrayConcat(arrayMap(key -> map('key', key, 'value', event.3[key]), mapKeys(event.3)), [map('key', 'message', 'value', event.1)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), "Events") as logs,`,
+      `arrayMap(link -> tuple(link.1, link.2, arrayMap(key -> map('key', key, 'value', link.4[key]), mapKeys(link.4)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links") AS references`,
+      `FROM "default"."otel_traces" WHERE traceID = trace_id AND "Timestamp" >= trace_start AND "Timestamp" <= trace_end`,
+      'LIMIT 1000'
+    ];
+
+    const sql = generateSql(opts);
+    expect(sql).toEqual(expectedSqlParts.join(' '));
+  });
+
   it('generates trace ID query with OTel enabled', () => {
     const opts: QueryBuilderOptions = {
       database: 'default',
diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts
index dfdab346..819c9ce5 100644
--- a/src/data/sqlGenerator.ts
+++ b/src/data/sqlGenerator.ts
@@ -149,6 +149,44 @@ const generateTraceIdQuery = (options: QueryBuilderOptions): string => {
   if (traceStatusCode !== undefined) {
     selectParts.push(`if(${escapeIdentifier(traceStatusCode.name)} IN ('Error', 'STATUS_CODE_ERROR'), 2, 0) as statusCode`);
   }
+
+  const traceKind = getColumnByHint(options, ColumnHint.TraceKind);
+  if (traceKind !== undefined) {
+    selectParts.push(`${escapeIdentifier(traceKind.name)} as kind`);
+  }
+
+  const traceStatusMessage = getColumnByHint(options, ColumnHint.TraceStatusMessage);
+  if (traceStatusMessage !== undefined) {
+    selectParts.push(`${escapeIdentifier(traceStatusMessage.name)} as statusMessage`);
+  }
+
+  const traceInstrumentationLibraryName = getColumnByHint(options, ColumnHint.TraceInstrumentationLibraryName);
+  if (traceInstrumentationLibraryName !== undefined) {
+    selectParts.push(`${escapeIdentifier(traceInstrumentationLibraryName.name)} as instrumentationLibraryName`);
+  }
+
+  const traceInstrumentationLibraryVersion = getColumnByHint(options, ColumnHint.TraceInstrumentationLibraryVersion);
+  if (traceInstrumentationLibraryVersion !== undefined) {
+    selectParts.push(`${escapeIdentifier(traceInstrumentationLibraryVersion.name)} as instrumentationLibraryVersion`);
+  }
+
+  const traceState = getColumnByHint(options, ColumnHint.TraceState);
+  if (traceState !== undefined) {
+    selectParts.push(`${escapeIdentifier(traceState.name)} as traceState`);
+  }
+
+  const traceEvents = getColumnByHint(options, ColumnHint.TraceEvents);
+  if (traceEvents !== undefined) {
+    // Assumes `events` is of type Array(Tuple(name String, timestamp UInt64, attributes Map(String, String)))
+    selectParts.push(`arrayMap(event -> tuple(multiply(toFloat64(event.2), 0.000001), arrayConcat(arrayMap(key -> map('key', key, 'value', event.3[key]), mapKeys(event.3)), [map('key', 'message', 'value', event.1)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), ${escapeIdentifier(traceEvents.name)}) as logs`);
+  }
+
+  const traceLinks = getColumnByHint(options, ColumnHint.TraceLinks);
+  if (traceLinks !== undefined) {
+    // Assumes `links` is of type Array(Tuple(traceID String, spanID String, traceState String, attributes Map(String, String)))
+    selectParts.push(`arrayMap(link -> tuple(link.1, link.2, arrayMap(key -> map('key', key, 'value', link.4[key]), mapKeys(link.4)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinks.name)}) AS references`);
+  }
+
   const selectPartsSql = selectParts.join(', ');
 
   // Optimize trace ID filtering for OTel enabled trace lookups
@@ -689,7 +727,7 @@ const getFilters = (options: QueryBuilderOptions): string => {
       operator = '';
       negate = true;
     } else if (filter.operator === FilterOperator.WithInGrafanaTimeRange) {
-        operator = '';
+      operator = '';
     }
 
     if (operator) {
@@ -758,11 +796,11 @@ const getFilters = (options: QueryBuilderOptions): string => {
 };
 
 const stripTypeModifiers = (type: string): string => {
-    return type.toLowerCase().
-      replace(/\(/g, '').
-      replace(/\)/g, '').
-      replace(/nullable/g, '').
-      replace(/lowcardinality/g, '');
+  return type.toLowerCase().
+    replace(/\(/g, '').
+    replace(/\)/g, '').
+    replace(/nullable/g, '').
+    replace(/lowcardinality/g, '');
 
 }
 const isBooleanType = (type: string): boolean => (type?.toLowerCase().startsWith('boolean'));
@@ -773,7 +811,7 @@ const isDateType = (type: string): boolean => type?.toLowerCase().startsWith('da
 const isStringType = (type: string): boolean => {
   type = stripTypeModifiers(type.toLowerCase());
   return (type === 'string' || type.startsWith('fixedstring'))
-  && !(isBooleanType(type) || isNumberType(type) || isDateType(type));
+    && !(isBooleanType(type) || isNumberType(type) || isDateType(type));
 }
 const isNullFilter = (operator: FilterOperator): boolean => operator === FilterOperator.IsNull || operator === FilterOperator.IsNotNull;
 const isBooleanFilter = (type: string): boolean => isBooleanType(type);
diff --git a/src/labels.ts b/src/labels.ts
index 02a7f405..9b6a7447 100644
--- a/src/labels.ts
+++ b/src/labels.ts
@@ -185,7 +185,39 @@ export default {
           serviceTags: {
             label: 'Service Tags column',
             tooltip: 'Column for the service tags'
-          }
+          },
+          kind: {
+            label: 'Kind column',
+            tooltip: 'Column for the trace kind'
+          },
+          statusCode: {
+            label: 'Status Code column',
+            tooltip: 'Column for the trace status code'
+          },
+          statusMessage: {
+            label: 'Status Message column',
+            tooltip: 'Column for the trace status message'
+          },
+          instrumentationLibraryName: {
+            label: 'Library Name column',
+            tooltip: 'Column for the instrumentation library name'
+          },
+          instrumentationLibraryVersion: {
+            label: 'Library Version column',
+            tooltip: 'Column for the instrumentation library version'
+          },
+          state: {
+            label: 'State column',
+            tooltip: 'Column for the trace state'
+          },
+          events: {
+            label: 'Events column',
+            tooltip: 'Column for the trace events'
+          },
+          links: {
+            label: 'Links column',
+            tooltip: 'Column for the trace references'
+          },
         }
       },
       LogsConfig: {
@@ -403,6 +435,38 @@ export default {
           label: 'Service Tags Column',
           tooltip: 'Column that contains the service tags'
         },
+        kind: {
+          label: 'Kind Column',
+          tooltip: 'Column that contains the trace kind'
+        },
+        statusCode: {
+          label: 'Status Code Column',
+          tooltip: 'Column that contains the trace status code'
+        },
+        statusMessage: {
+          label: 'Status Message Column',
+          tooltip: 'Column that contains the trace status message'
+        },
+        instrumentationLibraryName: {
+          label: 'Library Name Column',
+          tooltip: 'Column that contains the instrumentation library name'
+        },
+        instrumentationLibraryVersion: {
+          label: 'Library Version Column',
+          tooltip: 'Column that contains the instrumentation library version'
+        },
+        state: {
+          label: 'State Column',
+          tooltip: 'Column that contains the trace state'
+        },
+        events: {
+          label: 'Events Column',
+          tooltip: 'Column that contains the trace events'
+        },
+        links: {
+          label: 'Links Column',
+          tooltip: 'Column that contains the trace references'
+        },
         traceIdFilter: {
           label: 'Trace ID',
           tooltip: 'filter by a specific trace ID'
@@ -437,6 +501,13 @@ export default {
       [ColumnHint.TraceTags]: 'Tags',
       [ColumnHint.TraceServiceTags]: 'Service Tags',
       [ColumnHint.TraceStatusCode]: 'Status Code',
+      [ColumnHint.TraceKind]: 'Kind',
+      [ColumnHint.TraceStatusMessage]: 'Status Message',
+      [ColumnHint.TraceInstrumentationLibraryName]: 'Instrumentation Library Name',
+      [ColumnHint.TraceInstrumentationLibraryVersion]: 'Instrumentation Library Version',
+      [ColumnHint.TraceState]: 'State',
+      [ColumnHint.TraceEvents]: 'Events',
+      [ColumnHint.TraceLinks]: 'Links',
     }
   }
 }
diff --git a/src/otel.ts b/src/otel.ts
index 7af421ef..57f6d825 100644
--- a/src/otel.ts
+++ b/src/otel.ts
@@ -49,6 +49,13 @@ const otel129: OtelVersion = {
     [ColumnHint.TraceTags, 'SpanAttributes'],
     [ColumnHint.TraceServiceTags, 'ResourceAttributes'],
     [ColumnHint.TraceStatusCode, 'StatusCode'],
+    [ColumnHint.TraceKind, 'SpanKind'],
+    [ColumnHint.TraceStatusMessage, 'StatusMessage'],
+    [ColumnHint.TraceInstrumentationLibraryName, 'InstrumentationLibraryName'],
+    [ColumnHint.TraceInstrumentationLibraryVersion, 'InstrumentationLibraryVersion'],
+    [ColumnHint.TraceState, 'State'],
+    [ColumnHint.TraceEvents, 'Events'],
+    [ColumnHint.TraceLinks, 'Links'],
   ]),
   traceDurationUnit: TimeUnit.Nanoseconds,
 };
diff --git a/src/types/config.ts b/src/types/config.ts
index 3f976bb6..034e254e 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -90,6 +90,14 @@ export interface CHTracesConfig {
   startTimeColumn?: string;
   tagsColumn?: string;
   serviceTagsColumn?: string;
+  kindColumn?: string;
+  statusCodeColumn?: string;
+  statusMessageColumn?: string;
+  instrumentationLibraryNameColumn?: string;
+  instrumentationLibraryVersionColumn?: string;
+  stateColumn?: string;
+  eventsColumn?: string;
+  linksColumn?: string;
 }
 
 export interface AliasTableEntry {
diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts
index 54cdb335..756723a5 100644
--- a/src/types/queryBuilder.ts
+++ b/src/types/queryBuilder.ts
@@ -133,6 +133,13 @@ export enum ColumnHint {
   TraceTags = 'trace_tags',
   TraceServiceTags = 'trace_service_tags',
   TraceStatusCode = 'trace_status_code',
+  TraceKind = 'trace_kind',
+  TraceStatusMessage = 'trace_status_message',
+  TraceInstrumentationLibraryName = 'instrumentation_library_name',
+  TraceInstrumentationLibraryVersion = 'instrumentation_library_version',
+  TraceState = 'trace_state',
+  TraceEvents = 'trace_events',
+  TraceLinks = 'trace_links',
 }
 
 /**
diff --git a/src/views/CHConfigEditor.tsx b/src/views/CHConfigEditor.tsx
index f14cd0c1..d76dceff 100644
--- a/src/views/CHConfigEditor.tsx
+++ b/src/views/CHConfigEditor.tsx
@@ -444,6 +444,14 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => {
           onStartTimeColumnChange={c => onTracesConfigChange('startTimeColumn', c)}
           onTagsColumnChange={c => onTracesConfigChange('tagsColumn', c)}
           onServiceTagsColumnChange={c => onTracesConfigChange('serviceTagsColumn', c)}
+          onKindColumnChange={c => onTracesConfigChange('kindColumn', c)}
+          onStatusCodeColumnChange={c => onTracesConfigChange('statusCodeColumn', c)}
+          onStatusMessageColumnChange={c => onTracesConfigChange('statusMessageColumn', c)}
+          onInstrumentationLibraryNameColumnChange={c => onTracesConfigChange('instrumentationLibraryNameColumn', c)}
+          onInstrumentationLibraryVersionColumnChange={c => onTracesConfigChange('instrumentationLibraryVersionColumn', c)}
+          onStateColumnChange={c => onTracesConfigChange('stateColumn', c)}
+          onEventsColumnChange={c => onTracesConfigChange('eventsColumn', c)}
+          onLinksColumnChange={c => onTracesConfigChange('linksColumn', c)}
         />
 
         <Divider />

From e7f9977b300caf079bcf90ea54d78dea2d997626 Mon Sep 17 00:00:00 2001
From: Harry Phillips <harryjamesphillips@gmail.com>
Date: Mon, 3 Feb 2025 05:37:43 -0600
Subject: [PATCH 2/5] Match OTel Collector Schema

---
 src/data/sqlGenerator.test.ts | 4 ++--
 src/data/sqlGenerator.ts      | 8 ++++----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts
index f35fd0e6..e673ab5c 100644
--- a/src/data/sqlGenerator.test.ts
+++ b/src/data/sqlGenerator.test.ts
@@ -278,8 +278,8 @@ describe('SQL Generator', () => {
       '"InstrumentationLibraryName" as instrumentationLibraryName,',
       '"InstrumentationLibraryVersion" as instrumentationLibraryVersion,',
       '"TraceState" as traceState,',
-      `arrayMap(event -> tuple(multiply(toFloat64(event.2), 0.000001), arrayConcat(arrayMap(key -> map('key', key, 'value', event.3[key]), mapKeys(event.3)), [map('key', 'message', 'value', event.1)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), "Events") as logs,`,
-      `arrayMap(link -> tuple(link.1, link.2, arrayMap(key -> map('key', key, 'value', link.4[key]), mapKeys(link.4)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links") AS references`,
+      `arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000), arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]), mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), "Events") as logs,`,
+      `arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]), mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links") AS references`,
       `FROM "default"."otel_traces" WHERE traceID = trace_id AND "Timestamp" >= trace_start AND "Timestamp" <= trace_end`,
       'LIMIT 1000'
     ];
diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts
index 819c9ce5..e360aebb 100644
--- a/src/data/sqlGenerator.ts
+++ b/src/data/sqlGenerator.ts
@@ -177,14 +177,14 @@ const generateTraceIdQuery = (options: QueryBuilderOptions): string => {
 
   const traceEvents = getColumnByHint(options, ColumnHint.TraceEvents);
   if (traceEvents !== undefined) {
-    // Assumes `events` is of type Array(Tuple(name String, timestamp UInt64, attributes Map(String, String)))
-    selectParts.push(`arrayMap(event -> tuple(multiply(toFloat64(event.2), 0.000001), arrayConcat(arrayMap(key -> map('key', key, 'value', event.3[key]), mapKeys(event.3)), [map('key', 'message', 'value', event.1)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), ${escapeIdentifier(traceEvents.name)}) as logs`);
+    // Assumes `events` is of type Nested(Timestamp DateTime64(9), Name LowCardinality(String), Attributes Map(LowCardinality(String), String))
+    selectParts.push(`arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000), arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]), mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), ${escapeIdentifier(traceEvents.name)}) as logs`);
   }
 
   const traceLinks = getColumnByHint(options, ColumnHint.TraceLinks);
   if (traceLinks !== undefined) {
-    // Assumes `links` is of type Array(Tuple(traceID String, spanID String, traceState String, attributes Map(String, String)))
-    selectParts.push(`arrayMap(link -> tuple(link.1, link.2, arrayMap(key -> map('key', key, 'value', link.4[key]), mapKeys(link.4)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinks.name)}) AS references`);
+    // Assumes `links` is of type Nested(TraceId String, SpanId String, TraceState String, Attributes Map(LowCardinality(String), String))
+    selectParts.push(`arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]), mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinks.name)}) AS references`);
   }
 
   const selectPartsSql = selectParts.join(', ');

From 5d9779ba96ed4ca1b1e914989ba5054a10412566 Mon Sep 17 00:00:00 2001
From: Harry Phillips <harryjamesphillips@gmail.com>
Date: Mon, 3 Feb 2025 10:49:58 -0600
Subject: [PATCH 3/5] Fix events and links column entry fields

---
 .../queryBuilder/views/TraceQueryBuilder.tsx  | 46 ++++++++++++-------
 src/labels.ts                                 |  4 +-
 2 files changed, 32 insertions(+), 18 deletions(-)

diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx
index 5b2e4115..ff2318fa 100644
--- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx
+++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx
@@ -124,6 +124,18 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
     }));
   }, builderState);
 
+  // A function to assemble custom column definitions for the prefixed Events/Links columns
+  const onColumnPrefixChange = (name: keyof TraceQueryBuilderState, columnHint: ColumnHint) => {
+    const baseOptionChange = onOptionChange(name);
+    return (name: string) => {
+      baseOptionChange({
+        name: name,
+        hint: columnHint,
+        custom: true,
+      });
+    };
+  };
+
   useTraceDefaultsOnMount(datasource, isNewQuery, builderOptions, builderOptionsDispatch);
   useOtelColumns(builderState.otelEnabled, builderState.otelVersion, builderOptionsDispatch);
   useDefaultFilters(builderOptions.table, builderState.isTraceIdMode, isNewQuery, builderOptionsDispatch);
@@ -282,22 +294,6 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
             inline
           />
         </div>
-        <div className="gf-form">
-          <LabeledInput
-              disabled={builderState.otelEnabled}
-              label={labels.columns.eventsPrefix.label}
-              tooltip={labels.columns.eventsPrefix.tooltip}
-              value={builderState.eventsColumnPrefix?.name || ''}
-              onChange={onOptionChange('eventsColumnPrefix')}
-          />
-          <LabeledInput
-              disabled={builderState.otelEnabled}
-              label={labels.columns.linksPrefix.label}
-              tooltip={labels.columns.linksPrefix.tooltip}
-              value={builderState.linksColumnPrefix?.name || ''}
-              onChange={onOptionChange('linksColumnPrefix')}
-          />
-        </div>
         <div className="gf-form">
           <ColumnSelect
             disabled={builderState.otelEnabled}
@@ -373,6 +369,24 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
             inline
           />
         </div>
+        <div className="gf-form">
+          <LabeledInput
+              disabled={builderState.otelEnabled}
+              label={labels.columns.eventsPrefix.label}
+              tooltip={labels.columns.eventsPrefix.tooltip}
+              value={builderState.eventsColumnPrefix?.name || ''}
+              onChange={onColumnPrefixChange('eventsColumnPrefix', ColumnHint.TraceEventsPrefix)}
+          />
+        </div>
+        <div className="gf-form">
+          <LabeledInput
+              disabled={builderState.otelEnabled}
+              label={labels.columns.linksPrefix.label}
+              tooltip={labels.columns.linksPrefix.tooltip}
+              value={builderState.linksColumnPrefix?.name || ''}
+              onChange={onColumnPrefixChange('linksColumnPrefix', ColumnHint.TraceLinksPrefix)}
+          />
+        </div>
       </Collapse>
       <Collapse label={labels.filtersSection}
         collapsible
diff --git a/src/labels.ts b/src/labels.ts
index 6c57ab61..1189433c 100644
--- a/src/labels.ts
+++ b/src/labels.ts
@@ -436,11 +436,11 @@ export default {
           tooltip: 'Column that contains the service tags'
         },
         eventsPrefix: {
-          label: 'Events Prefix Column',
+          label: 'Events Prefix',
           tooltip: 'Prefix for the events column'
         },
         linksPrefix: {
-          label: 'Links Column',
+          label: 'Links Prefix',
           tooltip: 'Prefix for the trace references column'
         },
         kind: {

From c208504c0b4b6f29eff8b05dafb6045ae49fa300 Mon Sep 17 00:00:00 2001
From: Harry Phillips <harryjamesphillips@gmail.com>
Date: Mon, 3 Feb 2025 10:50:15 -0600
Subject: [PATCH 4/5] Fix events and links queries when using flatten_nested=0

---
 src/data/sqlGenerator.test.ts | 4 ++--
 src/data/sqlGenerator.ts      | 8 ++++++--
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts
index 01057070..ab809138 100644
--- a/src/data/sqlGenerator.test.ts
+++ b/src/data/sqlGenerator.test.ts
@@ -273,8 +273,8 @@ describe('SQL Generator', () => {
       `mapKeys("SpanAttributes")) as tags,`,
       `arrayMap(key -> map('key', key, 'value',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags,`,
       `if("StatusCode" IN ('Error', 'STATUS_CODE_ERROR'), 2, 0) as statusCode,`,
-      `arrayMap((name, timestamp, attributes) -> tuple(name, toString(toUnixTimestamp64Milli(timestamp)), arrayMap( key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(name String, timestamp String, fields Array(Map(String, String))),"Events".Name, "Events".Timestamp, "Events".Attributes) AS logs,`,
-      `arrayMap((traceID, spanID, attributes) -> tuple(traceID, spanID, arrayMap(key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links".TraceId, "Links".SpanId, "Links".Attributes) AS references,`,
+      `arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000), arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]), mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), "Events") as logs,`,
+      `arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]), mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links") AS references,`,
       '"Kind" as kind,',
       '"StatusMessage" as statusMessage,',
       '"InstrumentationLibraryName" as instrumentationLibraryName,',
diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts
index 9c45aab1..84e32be4 100644
--- a/src/data/sqlGenerator.ts
+++ b/src/data/sqlGenerator.ts
@@ -152,12 +152,16 @@ const generateTraceIdQuery = (options: QueryBuilderOptions): string => {
 
   const traceEventsPrefix = getColumnByHint(options, ColumnHint.TraceEventsPrefix);
   if (traceEventsPrefix !== undefined) {
-    selectParts.push(`arrayMap((name, timestamp, attributes) -> tuple(name, toString(toUnixTimestamp64Milli(timestamp)), arrayMap( key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(name String, timestamp String, fields Array(Map(String, String))),${escapeIdentifier(traceEventsPrefix.name)}.Name, ${escapeIdentifier(traceEventsPrefix.name)}.Timestamp, ${escapeIdentifier(traceEventsPrefix.name)}.Attributes) AS logs`);
+    // It is important to treat the prefixed columns as one nested column, as this
+    // ensures that the query will work when the column was created with flatten_nested.
+    selectParts.push(`arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000), arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]), mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), ${escapeIdentifier(traceEventsPrefix.name)}) as logs`);
   }
 
   const traceLinksPrefix = getColumnByHint(options, ColumnHint.TraceLinksPrefix);
   if (traceLinksPrefix !== undefined) {
-    selectParts.push(`arrayMap((traceID, spanID, attributes) -> tuple(traceID, spanID, arrayMap(key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinksPrefix.name)}.TraceId, ${escapeIdentifier(traceLinksPrefix.name)}.SpanId, ${escapeIdentifier(traceLinksPrefix.name)}.Attributes) AS references`);
+    // It is important to treat the prefixed columns as one nested column, as this
+    // ensures that the query will work when the column was created with flatten_nested.
+    selectParts.push(`arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]), mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinksPrefix.name)}) AS references`);
   }
 
   const traceKind = getColumnByHint(options, ColumnHint.TraceKind);

From 3947bccfd83259b57117131b56bab82d0844345c Mon Sep 17 00:00:00 2001
From: Spencer Torres <contact@spencertorres.com>
Date: Wed, 26 Mar 2025 01:38:17 -0400
Subject: [PATCH 5/5] code review edits

---
 .../configEditor/TracesConfig.test.tsx        | 709 +++---------------
 src/components/configEditor/TracesConfig.tsx  |  82 +-
 src/components/queryBuilder/Switch.tsx        |   4 +-
 .../queryBuilder/views/TraceQueryBuilder.tsx  |  84 ++-
 .../views/traceQueryBuilderHooks.test.ts      |  10 +-
 .../views/traceQueryBuilderHooks.ts           |  13 +-
 src/data/CHDatasource.ts                      |  14 +-
 src/data/sqlGenerator.test.ts                 |  72 +-
 src/data/sqlGenerator.ts                      |  50 +-
 src/labels.ts                                 |  22 +-
 src/otel.ts                                   |  12 +-
 src/types/config.ts                           |   8 +-
 src/types/queryBuilder.ts                     |  10 +-
 src/views/CHConfigEditor.tsx                  |  17 +-
 14 files changed, 376 insertions(+), 731 deletions(-)

diff --git a/src/components/configEditor/TracesConfig.test.tsx b/src/components/configEditor/TracesConfig.test.tsx
index 6e9f601a..8afb5224 100644
--- a/src/components/configEditor/TracesConfig.test.tsx
+++ b/src/components/configEditor/TracesConfig.test.tsx
@@ -1,38 +1,43 @@
 import React from 'react';
 import { render, fireEvent } from '@testing-library/react';
-import { TracesConfig } from './TracesConfig';
+import { TracesConfig, TraceConfigProps } from './TracesConfig';
 import allLabels from 'labels';
 import { columnLabelToPlaceholder } from 'data/utils';
 import { defaultTraceTable } from 'otel';
 
+function defaultTraceConfigProps(): TraceConfigProps {
+  return {
+        tracesConfig: {},
+        onDefaultDatabaseChange: () => {},
+        onDefaultTableChange: () => {},
+        onOtelEnabledChange: () => {},
+        onOtelVersionChange: () => {},
+        onTraceIdColumnChange: () => {},
+        onSpanIdColumnChange: () => {},
+        onOperationNameColumnChange: () => {},
+        onParentSpanIdColumnChange: () => {},
+        onServiceNameColumnChange: () => {},
+        onDurationColumnChange: () => {},
+        onDurationUnitChange: () => {},
+        onStartTimeColumnChange: () => {},
+        onTagsColumnChange: () => {},
+        onServiceTagsColumnChange: () => {},
+        onKindColumnChange: () => {},
+        onStatusCodeColumnChange: () => {},
+        onStatusMessageColumnChange: () => {},
+        onStateColumnChange: () => {},
+        onInstrumentationLibraryNameColumnChange: () => {},
+        onInstrumentationLibraryVersionColumnChange: () => {},
+        onFlattenNestedChange: () => {},
+        onEventsColumnPrefixChange: () => {},
+        onLinksColumnPrefixChange: () => {}
+  };
+}
+
 describe('TracesConfig', () => {
   it('should render', () => {
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} />
     );
     expect(result.container.firstChild).not.toBeNull();
   });
@@ -40,31 +45,7 @@ describe('TracesConfig', () => {
   it('should call onDefaultDatabase when changed', () => {
     const onDefaultDatabaseChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={onDefaultDatabaseChange}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onDefaultDatabaseChange={onDefaultDatabaseChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -79,31 +60,7 @@ describe('TracesConfig', () => {
   it('should call onDefaultTable when changed', () => {
     const onDefaultTableChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={onDefaultTableChange}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onDefaultTableChange={onDefaultTableChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -115,38 +72,14 @@ describe('TracesConfig', () => {
     expect(onDefaultTableChange).toHaveBeenCalledWith('changed');
   });
 
-  it('should call onOtelEnabled when changed', () => {
+  it('should call onOtelEnabled when changed', async () => {
     const onOtelEnabledChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={onOtelEnabledChange}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onOtelEnabledChange={onOtelEnabledChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
-    const input = result.getByRole('checkbox');
+    const input = (await result.findAllByRole('checkbox'))[0];
     expect(input).toBeInTheDocument();
     fireEvent.click(input);
     expect(onOtelEnabledChange).toHaveBeenCalledTimes(1);
@@ -157,29 +90,9 @@ describe('TracesConfig', () => {
     const onOtelVersionChange = jest.fn();
     const result = render(
       <TracesConfig
+        {...defaultTraceConfigProps()}
         tracesConfig={{ otelEnabled: true }}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
         onOtelVersionChange={onOtelVersionChange}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
       />
     );
     expect(result.container.firstChild).not.toBeNull();
@@ -195,31 +108,7 @@ describe('TracesConfig', () => {
   it('should call onTraceIdColumnChange when changed', () => {
     const onTraceIdColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={onTraceIdColumnChange}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onTraceIdColumnChange={onTraceIdColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -234,31 +123,7 @@ describe('TracesConfig', () => {
   it('should call onSpanIdColumnChange when changed', () => {
     const onSpanIdColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={onSpanIdColumnChange}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onSpanIdColumnChange={onSpanIdColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -273,31 +138,7 @@ describe('TracesConfig', () => {
   it('should call onOperationNameColumnChange when changed', () => {
     const onOperationNameColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={onOperationNameColumnChange}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onOperationNameColumnChange={onOperationNameColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -312,31 +153,7 @@ describe('TracesConfig', () => {
   it('should call onParentSpanIdColumnChange when changed', () => {
     const onParentSpanIdColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={onParentSpanIdColumnChange}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onParentSpanIdColumnChange={onParentSpanIdColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -351,31 +168,7 @@ describe('TracesConfig', () => {
   it('should call onServiceNameColumnChange when changed', () => {
     const onServiceNameColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={onServiceNameColumnChange}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onServiceNameColumnChange={onServiceNameColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -390,31 +183,7 @@ describe('TracesConfig', () => {
   it('should call onDurationColumnChange when changed', () => {
     const onDurationColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={onDurationColumnChange}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onDurationColumnChange={onDurationColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -429,31 +198,7 @@ describe('TracesConfig', () => {
   it('should call onDurationUnitChange when changed', () => {
     const onDurationUnitChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={onDurationUnitChange}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onDurationUnitChange={onDurationUnitChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -468,31 +213,7 @@ describe('TracesConfig', () => {
   it('should call onStartTimeColumnChange when changed', () => {
     const onStartTimeColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={onStartTimeColumnChange}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onStartTimeColumnChange={onStartTimeColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -507,31 +228,7 @@ describe('TracesConfig', () => {
   it('should call onTagsColumnChange when changed', () => {
     const onTagsColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={onTagsColumnChange}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onTagsColumnChange={onTagsColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -546,31 +243,7 @@ describe('TracesConfig', () => {
   it('should call onServiceTagsColumnChange when changed', () => {
     const onServiceTagsColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={onServiceTagsColumnChange}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onServiceTagsColumnChange={onServiceTagsColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -582,112 +255,10 @@ describe('TracesConfig', () => {
     expect(onServiceTagsColumnChange).toHaveBeenCalledWith('changed');
   });
 
-  it('should call onEventsColumnPrefixChange when changed', () => {
-    const onEventsColumnPrefixChange = jest.fn();
-    const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={onEventsColumnPrefixChange}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
-    );
-    expect(result.container.firstChild).not.toBeNull();
-
-    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.eventsPrefix.label));
-    expect(input).toBeInTheDocument();
-    fireEvent.change(input, { target: { value: 'changed' } });
-    fireEvent.blur(input);
-    expect(onEventsColumnPrefixChange).toHaveBeenCalledTimes(1);
-    expect(onEventsColumnPrefixChange).toHaveBeenCalledWith('changed');
-  });
-
-  it('should call onLinksColumnPrefixChange when changed', () => {
-    const onLinksColumnPrefixChange = jest.fn();
-    const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={onLinksColumnPrefixChange}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
-    );
-    expect(result.container.firstChild).not.toBeNull();
-
-    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.linksPrefix.label));
-    expect(input).toBeInTheDocument();
-    fireEvent.change(input, { target: { value: 'changed' } });
-    fireEvent.blur(input);
-    expect(onLinksColumnPrefixChange).toHaveBeenCalledTimes(1);
-    expect(onLinksColumnPrefixChange).toHaveBeenCalledWith('changed');
-  });
-
   it('should call onKindColumnChange when changed', () => {
     const onKindColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={onKindColumnChange}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onKindColumnChange={onKindColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -702,31 +273,7 @@ describe('TracesConfig', () => {
   it('should call onStatusCodeColumnChange when changed', () => {
     const onStatusCodeColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={onStatusCodeColumnChange}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onStatusCodeColumnChange={onStatusCodeColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -741,31 +288,7 @@ describe('TracesConfig', () => {
   it('should call onStatusMessageColumnChange when changed', () => {
     const onStatusMessageColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={onStatusMessageColumnChange}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onStatusMessageColumnChange={onStatusMessageColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -777,34 +300,25 @@ describe('TracesConfig', () => {
     expect(onStatusMessageColumnChange).toHaveBeenCalledWith('changed');
   });
 
+  it('should call onStateColumnChange when changed', () => {
+    const onStateColumnChange = jest.fn();
+    const result = render(
+      <TracesConfig {...defaultTraceConfigProps()} onStateColumnChange={onStateColumnChange} />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.state.label));
+    expect(input).toBeInTheDocument();
+    fireEvent.change(input, { target: { value: 'changed' } });
+    fireEvent.blur(input);
+    expect(onStateColumnChange).toHaveBeenCalledTimes(1);
+    expect(onStateColumnChange).toHaveBeenCalledWith('changed');
+  });
+
   it('should call onInstrumentationLibraryNameColumnChange when changed', () => {
     const onInstrumentationLibraryNameColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={onInstrumentationLibraryNameColumnChange}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onInstrumentationLibraryNameColumnChange={onInstrumentationLibraryNameColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -819,31 +333,7 @@ describe('TracesConfig', () => {
   it('should call onInstrumentationLibraryVersionColumnChange when changed', () => {
     const onInstrumentationLibraryVersionColumnChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={onInstrumentationLibraryVersionColumnChange}
-        onStateColumnChange={() => {}}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onInstrumentationLibraryVersionColumnChange={onInstrumentationLibraryVersionColumnChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
@@ -855,42 +345,47 @@ describe('TracesConfig', () => {
     expect(onInstrumentationLibraryVersionColumnChange).toHaveBeenCalledWith('changed');
   });
 
-  it('should call onStateColumnChange when changed', () => {
-    const onStateColumnChange = jest.fn();
+  it('should call onFlattenNestedChange when changed', async () => {
+    const onFlattenNestedChange = jest.fn();
     const result = render(
-      <TracesConfig
-        tracesConfig={{}}
-        onDefaultDatabaseChange={() => {}}
-        onDefaultTableChange={() => {}}
-        onOtelEnabledChange={() => {}}
-        onOtelVersionChange={() => {}}
-        onTraceIdColumnChange={() => {}}
-        onSpanIdColumnChange={() => {}}
-        onOperationNameColumnChange={() => {}}
-        onParentSpanIdColumnChange={() => {}}
-        onServiceNameColumnChange={() => {}}
-        onDurationColumnChange={() => {}}
-        onDurationUnitChange={() => {}}
-        onStartTimeColumnChange={() => {}}
-        onTagsColumnChange={() => {}}
-        onServiceTagsColumnChange={() => {}}
-        onEventsColumnPrefixChange={() => {}}
-        onLinksColumnPrefixChange={() => {}}
-        onKindColumnChange={() => {}}
-        onStatusCodeColumnChange={() => {}}
-        onStatusMessageColumnChange={() => {}}
-        onInstrumentationLibraryNameColumnChange={() => {}}
-        onInstrumentationLibraryVersionColumnChange={() => {}}
-        onStateColumnChange={onStateColumnChange}
-      />
+      <TracesConfig {...defaultTraceConfigProps()} onFlattenNestedChange={onFlattenNestedChange} />
     );
     expect(result.container.firstChild).not.toBeNull();
 
-    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.state.label));
+    const input = (await result.findAllByRole('checkbox'))[1];
+    expect(input).toBeInTheDocument();
+    fireEvent.click(input);
+    expect(onFlattenNestedChange).toHaveBeenCalledTimes(1);
+    expect(onFlattenNestedChange).toHaveBeenCalledWith(true);
+  });
+
+  it('should call onEventsColumnPrefixChange when changed', () => {
+    const onEventsColumnPrefixChange = jest.fn();
+    const result = render(
+      <TracesConfig {...defaultTraceConfigProps()} onEventsColumnPrefixChange={onEventsColumnPrefixChange} />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.eventsPrefix.label));
     expect(input).toBeInTheDocument();
     fireEvent.change(input, { target: { value: 'changed' } });
     fireEvent.blur(input);
-    expect(onStateColumnChange).toHaveBeenCalledTimes(1);
-    expect(onStateColumnChange).toHaveBeenCalledWith('changed');
+    expect(onEventsColumnPrefixChange).toHaveBeenCalledTimes(1);
+    expect(onEventsColumnPrefixChange).toHaveBeenCalledWith('changed');
+  });
+
+  it('should call onLinksColumnPrefixChange when changed', () => {
+    const onLinksColumnPrefixChange = jest.fn();
+    const result = render(
+      <TracesConfig {...defaultTraceConfigProps()} onLinksColumnPrefixChange={onLinksColumnPrefixChange} />
+    );
+    expect(result.container.firstChild).not.toBeNull();
+
+    const input = result.getByPlaceholderText(columnLabelToPlaceholder(allLabels.components.Config.TracesConfig.columns.linksPrefix.label));
+    expect(input).toBeInTheDocument();
+    fireEvent.change(input, { target: { value: 'changed' } });
+    fireEvent.blur(input);
+    expect(onLinksColumnPrefixChange).toHaveBeenCalledTimes(1);
+    expect(onLinksColumnPrefixChange).toHaveBeenCalledWith('changed');
   });
 });
diff --git a/src/components/configEditor/TracesConfig.tsx b/src/components/configEditor/TracesConfig.tsx
index 404d0b36..f4d05b03 100644
--- a/src/components/configEditor/TracesConfig.tsx
+++ b/src/components/configEditor/TracesConfig.tsx
@@ -10,8 +10,9 @@ import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect';
 import { CHTracesConfig } from 'types/config';
 import allLabels from 'labels';
 import { columnLabelToPlaceholder } from 'data/utils';
+import { Switch } from 'components/queryBuilder/Switch';
 
-interface TraceConfigProps {
+export interface TraceConfigProps {
   tracesConfig?: CHTracesConfig;
   onDefaultDatabaseChange: (v: string) => void;
   onDefaultTableChange: (v: string) => void;
@@ -27,14 +28,15 @@ interface TraceConfigProps {
   onStartTimeColumnChange: (v: string) => void;
   onTagsColumnChange: (v: string) => void;
   onServiceTagsColumnChange: (v: string) => void;
-  onEventsColumnPrefixChange: (v: string) => void;
-  onLinksColumnPrefixChange: (v: string) => void;
   onKindColumnChange: (v: string) => void;
   onStatusCodeColumnChange: (v: string) => void;
   onStatusMessageColumnChange: (v: string) => void;
+  onStateColumnChange: (v: string) => void;
   onInstrumentationLibraryNameColumnChange: (v: string) => void;
   onInstrumentationLibraryVersionColumnChange: (v: string) => void;
-  onStateColumnChange: (v: string) => void;
+  onFlattenNestedChange: (v: boolean) => void;
+  onEventsColumnPrefixChange: (v: string) => void;
+  onLinksColumnPrefixChange: (v: string) => void;
 }
 
 export const TracesConfig = (props: TraceConfigProps) => {
@@ -43,17 +45,20 @@ export const TracesConfig = (props: TraceConfigProps) => {
     onOtelEnabledChange, onOtelVersionChange,
     onTraceIdColumnChange, onSpanIdColumnChange, onOperationNameColumnChange, onParentSpanIdColumnChange,
     onServiceNameColumnChange, onDurationColumnChange, onDurationUnitChange, onStartTimeColumnChange,
-    onTagsColumnChange, onServiceTagsColumnChange, onEventsColumnPrefixChange, onLinksColumnPrefixChange,
-    onKindColumnChange, onStatusCodeColumnChange, onStatusMessageColumnChange, onInstrumentationLibraryNameColumnChange,
-    onInstrumentationLibraryVersionColumnChange, onStateColumnChange
+    onTagsColumnChange, onServiceTagsColumnChange,
+    onKindColumnChange, onStatusCodeColumnChange, onStatusMessageColumnChange,
+    onStateColumnChange,
+    onInstrumentationLibraryNameColumnChange, onInstrumentationLibraryVersionColumnChange,
+    onFlattenNestedChange, onEventsColumnPrefixChange, onLinksColumnPrefixChange,
   } = props;
   let {
     defaultDatabase, defaultTable,
     otelEnabled, otelVersion,
     traceIdColumn, spanIdColumn, operationNameColumn, parentSpanIdColumn, serviceNameColumn,
-    durationColumn, durationUnit, startTimeColumn, tagsColumn, serviceTagsColumn, eventsColumnPrefix,
-    linksColumnPrefix, kindColumn, statusCodeColumn, statusMessageColumn, instrumentationLibraryNameColumn,
-    instrumentationLibraryVersionColumn, stateColumn
+    durationColumn, durationUnit, startTimeColumn, tagsColumn, serviceTagsColumn,
+    kindColumn, statusCodeColumn, statusMessageColumn, stateColumn,
+    instrumentationLibraryNameColumn, instrumentationLibraryVersionColumn, 
+    flattenNested, traceEventsColumnPrefix, traceLinksColumnPrefix,
   } = (props.tracesConfig || {}) as CHTracesConfig;
   const labels = allLabels.components.Config.TracesConfig;
 
@@ -68,15 +73,16 @@ export const TracesConfig = (props: TraceConfigProps) => {
     durationColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceDurationTime);
     tagsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceTags);
     serviceTagsColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceServiceTags);
-    eventsColumnPrefix = otelConfig.traceColumnMap.get(ColumnHint.TraceEventsPrefix);
-    linksColumnPrefix = otelConfig.traceColumnMap.get(ColumnHint.TraceLinksPrefix);
     kindColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceKind);
     statusCodeColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceStatusCode);
     statusMessageColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceStatusMessage);
+    stateColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceState);
     instrumentationLibraryNameColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceInstrumentationLibraryName);
     instrumentationLibraryVersionColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceInstrumentationLibraryVersion);
-    stateColumn = otelConfig.traceColumnMap.get(ColumnHint.TraceState);
     durationUnit = otelConfig.traceDurationUnit.toString();
+    flattenNested = otelConfig.flattenNested;
+    traceEventsColumnPrefix = otelConfig.traceEventsColumnPrefix;
+    traceLinksColumnPrefix = otelConfig.traceLinksColumnPrefix;
   }
 
   return (
@@ -201,22 +207,6 @@ export const TracesConfig = (props: TraceConfigProps) => {
           value={serviceTagsColumn || ''}
           onChange={onServiceTagsColumnChange}
         />
-        <LabeledInput
-          disabled={otelEnabled}
-          label={labels.columns.eventsPrefix.label}
-          placeholder={columnLabelToPlaceholder(labels.columns.eventsPrefix.label)}
-          tooltip={labels.columns.eventsPrefix.tooltip}
-          value={eventsColumnPrefix || ''}
-          onChange={onEventsColumnPrefixChange}
-        />
-        <LabeledInput
-          disabled={otelEnabled}
-          label={labels.columns.linksPrefix.label}
-          placeholder={columnLabelToPlaceholder(labels.columns.linksPrefix.label)}
-          tooltip={labels.columns.linksPrefix.tooltip}
-          value={linksColumnPrefix || ''}
-          onChange={onLinksColumnPrefixChange}
-        />
         <LabeledInput
           disabled={otelEnabled}
           label={labels.columns.kind.label}
@@ -241,6 +231,14 @@ export const TracesConfig = (props: TraceConfigProps) => {
           value={statusMessageColumn || ''}
           onChange={onStatusMessageColumnChange}
         />
+        <LabeledInput
+          disabled={otelEnabled}
+          label={labels.columns.state.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.state.label)}
+          tooltip={labels.columns.state.tooltip}
+          value={stateColumn || ''}
+          onChange={onStateColumnChange}
+        />
         <LabeledInput
           disabled={otelEnabled}
           label={labels.columns.instrumentationLibraryName.label}
@@ -257,13 +255,29 @@ export const TracesConfig = (props: TraceConfigProps) => {
           value={instrumentationLibraryVersionColumn || ''}
           onChange={onInstrumentationLibraryVersionColumnChange}
         />
+        <Switch
+          disabled={otelEnabled}
+          label={labels.columns.flattenNested.label}
+          tooltip={labels.columns.flattenNested.tooltip}
+          value={flattenNested || false}
+          onChange={onFlattenNestedChange}
+          wide
+        />
         <LabeledInput
           disabled={otelEnabled}
-          label={labels.columns.state.label}
-          placeholder={columnLabelToPlaceholder(labels.columns.state.label)}
-          tooltip={labels.columns.state.tooltip}
-          value={stateColumn || ''}
-          onChange={onStateColumnChange}
+          label={labels.columns.eventsPrefix.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.eventsPrefix.label)}
+          tooltip={labels.columns.eventsPrefix.tooltip}
+          value={traceEventsColumnPrefix || ''}
+          onChange={onEventsColumnPrefixChange}
+        />
+        <LabeledInput
+          disabled={otelEnabled}
+          label={labels.columns.linksPrefix.label}
+          placeholder={columnLabelToPlaceholder(labels.columns.linksPrefix.label)}
+          tooltip={labels.columns.linksPrefix.tooltip}
+          value={traceLinksColumnPrefix || ''}
+          onChange={onLinksColumnPrefixChange}
         />
       </ConfigSubSection>
     </ConfigSection>
diff --git a/src/components/queryBuilder/Switch.tsx b/src/components/queryBuilder/Switch.tsx
index 076b2454..4fca682e 100644
--- a/src/components/queryBuilder/Switch.tsx
+++ b/src/components/queryBuilder/Switch.tsx
@@ -7,12 +7,13 @@ interface SwitchProps {
   onChange: (value: boolean) => void;
   label: string;
   tooltip: string;
+  disabled?: boolean;
   inline?: boolean;
   wide?: boolean;
 }
 
 export const Switch = (props: SwitchProps) => {
-  const { value, onChange, label, tooltip, inline, wide } = props;
+  const { value, onChange, label, tooltip, disabled, inline, wide } = props;
 
   const theme = useTheme();
   const switchContainerStyle: React.CSSProperties = {
@@ -31,6 +32,7 @@ export const Switch = (props: SwitchProps) => {
       </InlineFormLabel>
       <div style={switchContainerStyle}>
         <GrafanaSwitch
+          disabled={disabled}
           className="gf-form"
           value={value}
           onChange={e => onChange(e.currentTarget.checked)}
diff --git a/src/components/queryBuilder/views/TraceQueryBuilder.tsx b/src/components/queryBuilder/views/TraceQueryBuilder.tsx
index ff2318fa..3513fede 100644
--- a/src/components/queryBuilder/views/TraceQueryBuilder.tsx
+++ b/src/components/queryBuilder/views/TraceQueryBuilder.tsx
@@ -5,7 +5,7 @@ import { FiltersEditor } from '../FilterEditor';
 import allLabels from 'labels';
 import { ModeSwitch } from '../ModeSwitch';
 import { getColumnByHint } from 'data/sqlGenerator';
-import { Alert, Collapse, VerticalGroup } from '@grafana/ui';
+import { Alert, Collapse, Stack } from '@grafana/ui';
 import { DurationUnitSelect } from 'components/queryBuilder/DurationUnitSelect';
 import { Datasource } from 'data/CHDatasource';
 import { useBuilderOptionChanges } from 'hooks/useBuilderOptionChanges';
@@ -18,6 +18,7 @@ import TraceIdInput from '../TraceIdInput';
 import { OrderByEditor, getOrderByOptions } from '../OrderByEditor';
 import { LimitEditor } from '../LimitEditor';
 import { LabeledInput } from 'components/configEditor/LabeledInput';
+import { Switch } from '../Switch';
 
 interface TraceQueryBuilderProps {
   datasource: Datasource;
@@ -39,14 +40,15 @@ interface TraceQueryBuilderState {
   durationUnit: TimeUnit;
   tagsColumn?: SelectedColumn;
   serviceTagsColumn?: SelectedColumn;
-  eventsColumnPrefix?: SelectedColumn;
-  linksColumnPrefix?: SelectedColumn;
   kindColumn?: SelectedColumn;
   statusCodeColumn?: SelectedColumn;
   statusMessageColumn?: SelectedColumn;
+  stateColumn?: SelectedColumn;
   instrumentationLibraryNameColumn?: SelectedColumn;
   instrumentationLibraryVersionColumn?: SelectedColumn;
-  stateColumn?: SelectedColumn;
+  flattenNested?: boolean;
+  traceEventsColumnPrefix?: string;
+  traceLinksColumnPrefix?: string;
   traceId: string;
   orderBy: OrderBy[];
   limit: number;
@@ -61,7 +63,7 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
   const [isColumnsOpen, setColumnsOpen] = useState<boolean>(showConfigWarning); // Toggle Columns collapse section
   const [isFiltersOpen, setFiltersOpen] = useState<boolean>(!(builderOptions.meta?.isTraceIdMode && builderOptions.meta.traceId)); // Toggle Filters collapse section
   const labels = allLabels.components.TraceQueryBuilder;
-  const builderState: TraceQueryBuilderState = useMemo(() => ({
+  const builderState = useMemo<TraceQueryBuilderState>(() => ({
     isTraceIdMode: builderOptions.meta?.isTraceIdMode || false,
     otelEnabled: builderOptions.meta?.otelEnabled || false,
     otelVersion: builderOptions.meta?.otelVersion || '',
@@ -75,14 +77,15 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
     durationUnit: builderOptions.meta?.traceDurationUnit || TimeUnit.Nanoseconds,
     tagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceTags),
     serviceTagsColumn: getColumnByHint(builderOptions, ColumnHint.TraceServiceTags),
-    eventsColumnPrefix: getColumnByHint(builderOptions, ColumnHint.TraceEventsPrefix),
-    linksColumnPrefix: getColumnByHint(builderOptions, ColumnHint.TraceLinksPrefix),
     kindColumn: getColumnByHint(builderOptions, ColumnHint.TraceKind),
     statusCodeColumn: getColumnByHint(builderOptions, ColumnHint.TraceStatusCode),
     statusMessageColumn: getColumnByHint(builderOptions, ColumnHint.TraceStatusMessage),
+    stateColumn: getColumnByHint(builderOptions, ColumnHint.TraceState),
     instrumentationLibraryNameColumn: getColumnByHint(builderOptions, ColumnHint.TraceInstrumentationLibraryName),
     instrumentationLibraryVersionColumn: getColumnByHint(builderOptions, ColumnHint.TraceInstrumentationLibraryVersion),
-    stateColumn: getColumnByHint(builderOptions, ColumnHint.TraceState),
+    flattenNested: Boolean(builderOptions.meta?.flattenNested),
+    traceEventsColumnPrefix: builderOptions.meta?.traceEventsColumnPrefix || '',
+    traceLinksColumnPrefix: builderOptions.meta?.traceLinksColumnPrefix || '',
     traceId: builderOptions.meta?.traceId || '',
     orderBy: builderOptions.orderBy || [],
     limit: builderOptions.limit || 0,
@@ -100,15 +103,13 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
       next.durationTimeColumn,
       next.tagsColumn,
       next.serviceTagsColumn,
-      next.eventsColumnPrefix,
-      next.linksColumnPrefix,
       next.serviceTagsColumn,
       next.kindColumn,
       next.statusCodeColumn,
       next.statusMessageColumn,
+      next.stateColumn,
       next.instrumentationLibraryNameColumn,
       next.instrumentationLibraryVersionColumn,
-      next.stateColumn
     ].filter(c => c !== undefined) as SelectedColumn[];
 
     builderOptionsDispatch(setOptions({
@@ -120,34 +121,25 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
         isTraceIdMode: next.isTraceIdMode,
         traceDurationUnit: next.durationUnit,
         traceId: next.traceId,
+        flattenNested: next.flattenNested,
+        traceEventsColumnPrefix: next.traceEventsColumnPrefix,
+        traceLinksColumnPrefix: next.traceLinksColumnPrefix,
       }
     }));
   }, builderState);
 
-  // A function to assemble custom column definitions for the prefixed Events/Links columns
-  const onColumnPrefixChange = (name: keyof TraceQueryBuilderState, columnHint: ColumnHint) => {
-    const baseOptionChange = onOptionChange(name);
-    return (name: string) => {
-      baseOptionChange({
-        name: name,
-        hint: columnHint,
-        custom: true,
-      });
-    };
-  };
-
   useTraceDefaultsOnMount(datasource, isNewQuery, builderOptions, builderOptionsDispatch);
   useOtelColumns(builderState.otelEnabled, builderState.otelVersion, builderOptionsDispatch);
   useDefaultFilters(builderOptions.table, builderState.isTraceIdMode, isNewQuery, builderOptionsDispatch);
 
   const configWarning = showConfigWarning && (
     <Alert title="" severity="warning" buttonContent="Close" onRemove={() => setConfigWarningOpen(false)}>
-      <VerticalGroup>
+      <Stack>
         <div>
           {'To speed up your query building, enter your default trace configuration in your '}
           <a style={{ textDecoration: 'underline' }} href={`/connections/datasources/edit/${encodeURIComponent(datasource.uid)}#traces-config`}>ClickHouse Data Source settings</a>
         </div>
-      </VerticalGroup>
+      </Stack>
     </Alert>
   );
 
@@ -331,6 +323,20 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
             tooltip={labels.columns.statusMessage.tooltip}
             wide
           />
+          <ColumnSelect
+            disabled={builderState.otelEnabled}
+            allColumns={allColumns}
+            selectedColumn={builderState.stateColumn}
+            invalid={!builderState.stateColumn}
+            onColumnChange={onOptionChange('stateColumn')}
+            columnHint={ColumnHint.TraceState}
+            label={labels.columns.state.label}
+            tooltip={labels.columns.state.tooltip}
+            wide
+            inline
+          />
+        </div>
+        <div className="gf-form">
           <ColumnSelect
             disabled={builderState.otelEnabled}
             allColumns={allColumns}
@@ -341,10 +347,7 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
             label={labels.columns.instrumentationLibraryName.label}
             tooltip={labels.columns.instrumentationLibraryName.tooltip}
             wide
-            inline
           />
-        </div>
-        <div className="gf-form">
           <ColumnSelect
             disabled={builderState.otelEnabled}
             allColumns={allColumns}
@@ -355,18 +358,17 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
             label={labels.columns.instrumentationLibraryVersion.label}
             tooltip={labels.columns.instrumentationLibraryVersion.tooltip}
             wide
+            inline
           />
-          <ColumnSelect
+        </div>
+        <div className="gf-form">
+          <Switch
             disabled={builderState.otelEnabled}
-            allColumns={allColumns}
-            selectedColumn={builderState.stateColumn}
-            invalid={!builderState.stateColumn}
-            onColumnChange={onOptionChange('stateColumn')}
-            columnHint={ColumnHint.TraceState}
-            label={labels.columns.state.label}
-            tooltip={labels.columns.state.tooltip}
+            label={labels.columns.flattenNested.label}
+            tooltip={labels.columns.flattenNested.tooltip}
+            value={Boolean(builderState.flattenNested)}
+            onChange={onOptionChange('flattenNested')}
             wide
-            inline
           />
         </div>
         <div className="gf-form">
@@ -374,8 +376,8 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
               disabled={builderState.otelEnabled}
               label={labels.columns.eventsPrefix.label}
               tooltip={labels.columns.eventsPrefix.tooltip}
-              value={builderState.eventsColumnPrefix?.name || ''}
-              onChange={onColumnPrefixChange('eventsColumnPrefix', ColumnHint.TraceEventsPrefix)}
+              value={builderState.traceEventsColumnPrefix || ''}
+              onChange={onOptionChange('traceEventsColumnPrefix')}
           />
         </div>
         <div className="gf-form">
@@ -383,8 +385,8 @@ export const TraceQueryBuilder = (props: TraceQueryBuilderProps) => {
               disabled={builderState.otelEnabled}
               label={labels.columns.linksPrefix.label}
               tooltip={labels.columns.linksPrefix.tooltip}
-              value={builderState.linksColumnPrefix?.name || ''}
-              onChange={onColumnPrefixChange('linksColumnPrefix', ColumnHint.TraceLinksPrefix)}
+              value={builderState.traceLinksColumnPrefix || ''}
+              onChange={onOptionChange('traceLinksColumnPrefix')}
           />
         </div>
       </Collapse>
diff --git a/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts b/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts
index 63b7efaf..6b87b57f 100644
--- a/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts
+++ b/src/components/queryBuilder/views/traceQueryBuilderHooks.test.ts
@@ -20,7 +20,10 @@ describe('useTraceDefaultsOnMount', () => {
       meta: {
         otelEnabled: expect.anything(),
         otelVersion: undefined,
-        traceDurationUnit: expect.anything()
+        traceDurationUnit: expect.anything(),
+        flattenNested: expect.anything(),
+        traceEventsColumnPrefix: expect.anything(),
+        traceLinksColumnPrefix: expect.anything(),
       }
     };
 
@@ -77,7 +80,10 @@ describe('useOtelColumns', () => {
     const expectedOptions = {
       columns,
       meta: {
-        traceDurationUnit: expect.anything()
+        traceDurationUnit: expect.anything(),
+        flattenNested: expect.anything(),
+        traceEventsColumnPrefix: expect.anything(),
+        traceLinksColumnPrefix: expect.anything(),
       }
     };
 
diff --git a/src/components/queryBuilder/views/traceQueryBuilderHooks.ts b/src/components/queryBuilder/views/traceQueryBuilderHooks.ts
index f8747839..fecd3f14 100644
--- a/src/components/queryBuilder/views/traceQueryBuilderHooks.ts
+++ b/src/components/queryBuilder/views/traceQueryBuilderHooks.ts
@@ -19,6 +19,9 @@ export const useTraceDefaultsOnMount = (datasource: Datasource, isNewQuery: bool
     const defaultDurationUnit = datasource.getDefaultTraceDurationUnit();
     const otelVersion = datasource.getTraceOtelVersion();
     const defaultColumns = datasource.getDefaultTraceColumns();
+    const defaultFlattenNested = datasource.getDefaultTraceFlattenNested();
+    const defaultEventsColumnPrefix = datasource.getDefaultTraceEventsColumnPrefix();
+    const defaultLinksColumnPrefix = datasource.getDefaultTraceLinksColumnPrefix();
 
     const nextColumns: SelectedColumn[] = [];
     for (let [hint, colName] of defaultColumns) {
@@ -32,7 +35,10 @@ export const useTraceDefaultsOnMount = (datasource: Datasource, isNewQuery: bool
       meta: {
         otelEnabled: Boolean(otelVersion),
         otelVersion,
-        traceDurationUnit: defaultDurationUnit
+        traceDurationUnit: defaultDurationUnit,
+        flattenNested: defaultFlattenNested,
+        traceEventsColumnPrefix: defaultEventsColumnPrefix,
+        traceLinksColumnPrefix: defaultLinksColumnPrefix,
       }
     }));
     didSetDefaults.current = true;
@@ -68,7 +74,10 @@ export const useOtelColumns = (otelEnabled: boolean, otelVersion: string, builde
     builderOptionsDispatch(setOptions({
       columns,
       meta: {
-        traceDurationUnit: otelConfig.traceDurationUnit
+        traceDurationUnit: otelConfig.traceDurationUnit,
+        flattenNested: otelConfig.flattenNested,
+        traceEventsColumnPrefix: otelConfig.traceEventsColumnPrefix,
+        traceLinksColumnPrefix: otelConfig.traceLinksColumnPrefix,
       }
     }));
     didSetColumns.current = true;
diff --git a/src/data/CHDatasource.ts b/src/data/CHDatasource.ts
index a628ef33..33f58054 100644
--- a/src/data/CHDatasource.ts
+++ b/src/data/CHDatasource.ts
@@ -493,8 +493,6 @@ export class Datasource
     traceConfig.startTimeColumn && result.set(ColumnHint.Time, traceConfig.startTimeColumn);
     traceConfig.tagsColumn && result.set(ColumnHint.TraceTags, traceConfig.tagsColumn);
     traceConfig.serviceTagsColumn && result.set(ColumnHint.TraceServiceTags, traceConfig.serviceTagsColumn);
-    traceConfig.eventsColumnPrefix && result.set(ColumnHint.TraceEventsPrefix, traceConfig.eventsColumnPrefix);
-    traceConfig.linksColumnPrefix && result.set(ColumnHint.TraceLinksPrefix, traceConfig.linksColumnPrefix);
     traceConfig.kindColumn && result.set(ColumnHint.TraceKind, traceConfig.kindColumn);
     traceConfig.statusCodeColumn && result.set(ColumnHint.TraceStatusCode, traceConfig.statusCodeColumn);
     traceConfig.statusMessageColumn && result.set(ColumnHint.TraceStatusMessage, traceConfig.statusMessageColumn);
@@ -517,6 +515,18 @@ export class Datasource
     return this.settings.jsonData.traces?.durationUnit as TimeUnit || TimeUnit.Nanoseconds;
   }
 
+  getDefaultTraceFlattenNested(): boolean {
+    return this.settings.jsonData.traces?.flattenNested || false;
+  }
+
+  getDefaultTraceEventsColumnPrefix(): string {
+    return this.settings.jsonData.traces?.traceEventsColumnPrefix || 'Events';
+  }
+
+  getDefaultTraceLinksColumnPrefix(): string {
+    return this.settings.jsonData.traces?.traceLinksColumnPrefix || 'Links';
+  }
+
   async fetchDatabases(): Promise<string[]> {
     return this.fetchData('SHOW DATABASES');
   }
diff --git a/src/data/sqlGenerator.test.ts b/src/data/sqlGenerator.test.ts
index ab809138..d686808c 100644
--- a/src/data/sqlGenerator.test.ts
+++ b/src/data/sqlGenerator.test.ts
@@ -226,7 +226,7 @@ describe('SQL Generator', () => {
     expect(sql).toEqual(expectedSqlParts.join(' '));
   });
 
-  it('generates trace ID query with additional fields (kind, statusCode, statusMessage, instrumentationLibraryName, instrumentationLibraryVersion, traceState, logs, references) and OTel enabled', () => {
+  it('generates trace ID query with additional fields, flatten nested disabled', () => {
     const opts: QueryBuilderOptions = {
       database: 'default',
       table: 'otel_traces',
@@ -242,8 +242,6 @@ describe('SQL Generator', () => {
         { name: 'SpanAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceTags },
         { name: 'ResourceAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceServiceTags },
         { name: 'StatusCode', type: 'LowCardinality(String)', hint: ColumnHint.TraceStatusCode },
-        { name: 'Events', type: 'Nested(Timestamp DateTime64(9), Name LowCardinality(String), Attributes Map(LowCardinality(String), String))', hint: ColumnHint.TraceEventsPrefix },
-        { name: 'Links', type: 'Nested(TraceId String, SpanId String, TraceState String, Attributes Map(LowCardinality(String), String))', hint: ColumnHint.TraceLinksPrefix },
         { name: 'Kind', type: 'String', hint: ColumnHint.TraceKind },
         { name: 'StatusMessage', type: 'String', hint: ColumnHint.TraceStatusMessage },
         { name: 'InstrumentationLibraryName', type: 'String', hint: ColumnHint.TraceInstrumentationLibraryName },
@@ -257,7 +255,73 @@ describe('SQL Generator', () => {
         otelVersion: 'latest',
         traceDurationUnit: TimeUnit.Nanoseconds,
         isTraceIdMode: true,
-        traceId: 'abcdefg'
+        traceId: 'abcdefg',
+        flattenNested: false,
+        traceEventsColumnPrefix: 'Events',
+        traceLinksColumnPrefix: 'Links',
+      },
+      limit: 1000,
+      orderBy: []
+    };
+
+    const expectedSqlParts = [
+      `WITH 'abcdefg' as trace_id, (SELECT min(Start) FROM "default"."otel_traces_trace_id_ts" WHERE TraceId = trace_id) as trace_start,`,
+      `(SELECT max(End) + 1 FROM "default"."otel_traces_trace_id_ts" WHERE TraceId = trace_id) as trace_end`,
+      'SELECT "TraceId" as traceID, "SpanId" as spanID, "ParentSpanId" as parentSpanID,',
+      '"ServiceName" as serviceName, "SpanName" as operationName, multiply(toUnixTimestamp64Nano("Timestamp"), 0.000001) as startTime,',
+      'multiply("Duration", 0.000001) as duration,',
+      `arrayMap(key -> map('key', key, 'value',"SpanAttributes"[key]),`,
+      `mapKeys("SpanAttributes")) as tags,`,
+      `arrayMap(key -> map('key', key, 'value',"ResourceAttributes"[key]), mapKeys("ResourceAttributes")) as serviceTags,`,
+      `if("StatusCode" IN ('Error', 'STATUS_CODE_ERROR'), 2, 0) as statusCode,`,
+      `arrayMap((name, timestamp, attributes) -> tuple(name, toString(toUnixTimestamp64Milli(timestamp)), arrayMap( key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(name String, timestamp String, fields Array(Map(String, String))), "Events".Name, "Events".Timestamp, "Events".Attributes) AS logs,`,
+      `arrayMap((traceID, spanID, attributes) -> tuple(traceID, spanID, arrayMap(key -> map('key', key, 'value', attributes[key]), mapKeys(attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), "Links".TraceId, "Links".SpanId, "Links".Attributes) AS references,`,
+      '"Kind" as kind,',
+      '"StatusMessage" as statusMessage,',
+      '"InstrumentationLibraryName" as instrumentationLibraryName,',
+      '"InstrumentationLibraryVersion" as instrumentationLibraryVersion,',
+      '"TraceState" as traceState',
+      `FROM "default"."otel_traces" WHERE traceID = trace_id AND "Timestamp" >= trace_start AND "Timestamp" <= trace_end`,
+      'LIMIT 1000'
+    ];
+
+    const sql = generateSql(opts);
+    expect(sql).toEqual(expectedSqlParts.join(' '));
+  });
+
+  it('generates trace ID query with additional fields, flatten nested enabled', () => {
+    const opts: QueryBuilderOptions = {
+      database: 'default',
+      table: 'otel_traces',
+      queryType: QueryType.Traces,
+      columns: [
+        { name: 'TraceId', type: 'String', hint: ColumnHint.TraceId },
+        { name: 'SpanId', type: 'String', hint: ColumnHint.TraceSpanId },
+        { name: 'ParentSpanId', type: 'String', hint: ColumnHint.TraceParentSpanId },
+        { name: 'ServiceName', type: 'LowCardinality(String)', hint: ColumnHint.TraceServiceName },
+        { name: 'SpanName', type: 'LowCardinality(String)', hint: ColumnHint.TraceOperationName },
+        { name: 'Timestamp', type: 'DateTime64(9)', hint: ColumnHint.Time },
+        { name: 'Duration', type: 'Int64', hint: ColumnHint.TraceDurationTime },
+        { name: 'SpanAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceTags },
+        { name: 'ResourceAttributes', type: 'Map(LowCardinality(String), String)', hint: ColumnHint.TraceServiceTags },
+        { name: 'StatusCode', type: 'LowCardinality(String)', hint: ColumnHint.TraceStatusCode },
+        { name: 'Kind', type: 'String', hint: ColumnHint.TraceKind },
+        { name: 'StatusMessage', type: 'String', hint: ColumnHint.TraceStatusMessage },
+        { name: 'InstrumentationLibraryName', type: 'String', hint: ColumnHint.TraceInstrumentationLibraryName },
+        { name: 'InstrumentationLibraryVersion', type: 'String', hint: ColumnHint.TraceInstrumentationLibraryVersion },
+        { name: 'TraceState', type: 'String', hint: ColumnHint.TraceState },
+      ],
+      filters: [],
+      meta: {
+        minimized: true,
+        otelEnabled: true,
+        otelVersion: 'latest',
+        traceDurationUnit: TimeUnit.Nanoseconds,
+        isTraceIdMode: true,
+        traceId: 'abcdefg',
+        flattenNested: true,
+        traceEventsColumnPrefix: 'Events',
+        traceLinksColumnPrefix: 'Links',
       },
       limit: 1000,
       orderBy: []
diff --git a/src/data/sqlGenerator.ts b/src/data/sqlGenerator.ts
index 84e32be4..6817419d 100644
--- a/src/data/sqlGenerator.ts
+++ b/src/data/sqlGenerator.ts
@@ -150,18 +150,44 @@ const generateTraceIdQuery = (options: QueryBuilderOptions): string => {
     selectParts.push(`if(${escapeIdentifier(traceStatusCode.name)} IN ('Error', 'STATUS_CODE_ERROR'), 2, 0) as statusCode`);
   }
 
-  const traceEventsPrefix = getColumnByHint(options, ColumnHint.TraceEventsPrefix);
-  if (traceEventsPrefix !== undefined) {
-    // It is important to treat the prefixed columns as one nested column, as this
-    // ensures that the query will work when the column was created with flatten_nested.
-    selectParts.push(`arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000), arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]), mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))), ${escapeIdentifier(traceEventsPrefix.name)}) as logs`);
-  }
-
-  const traceLinksPrefix = getColumnByHint(options, ColumnHint.TraceLinksPrefix);
-  if (traceLinksPrefix !== undefined) {
-    // It is important to treat the prefixed columns as one nested column, as this
-    // ensures that the query will work when the column was created with flatten_nested.
-    selectParts.push(`arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]), mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))), ${escapeIdentifier(traceLinksPrefix.name)}) AS references`);
+  const flattenNested = Boolean(options.meta?.flattenNested);
+
+  const traceEventsPrefix = options.meta?.traceEventsColumnPrefix || '';
+  if (traceEventsPrefix !== '') {
+    if (flattenNested) {
+      selectParts.push([
+        `arrayMap(event -> tuple(multiply(toFloat64(event.Timestamp), 1000),`,
+        `arrayConcat(arrayMap(key -> map('key', key, 'value', event.Attributes[key]),`,
+        `mapKeys(event.Attributes)), [map('key', 'message', 'value', event.Name)]))::Tuple(timestamp Float64, fields Array(Map(String, String))),`,
+        `${escapeIdentifier(traceEventsPrefix)}) as logs`
+      ].join(' '));
+    } else {
+      selectParts.push([
+        `arrayMap((name, timestamp, attributes) -> tuple(name, toString(toUnixTimestamp64Milli(timestamp)),`,
+        `arrayMap( key -> map('key', key, 'value', attributes[key]),`,
+        `mapKeys(attributes)))::Tuple(name String, timestamp String, fields Array(Map(String, String))),`,
+        `${escapeIdentifier(traceEventsPrefix)}.Name, ${escapeIdentifier(traceEventsPrefix)}.Timestamp,`,
+        `${escapeIdentifier(traceEventsPrefix)}.Attributes) AS logs`
+      ].join(' '));
+    }
+  }
+
+  const traceLinksPrefix = options.meta?.traceLinksColumnPrefix || '';
+  if (traceLinksPrefix !== '') {
+    if (flattenNested) {
+      selectParts.push([
+        `arrayMap(link -> tuple(link.TraceId, link.SpanId, arrayMap(key -> map('key', key, 'value', link.Attributes[key]),`,
+        `mapKeys(link.Attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))),`,
+        `${escapeIdentifier(traceLinksPrefix)}) AS references`
+      ].join(' '));
+    } else {
+      selectParts.push([
+        `arrayMap((traceID, spanID, attributes) -> tuple(traceID, spanID, arrayMap(key -> map('key', key, 'value', attributes[key]),`,
+        `mapKeys(attributes)))::Tuple(traceID String, spanID String, tags Array(Map(String, String))),`,
+        `${escapeIdentifier(traceLinksPrefix)}.TraceId, ${escapeIdentifier(traceLinksPrefix)}.SpanId,`,
+        `${escapeIdentifier(traceLinksPrefix)}.Attributes) AS references`
+      ].join(' '));
+    }
   }
 
   const traceKind = getColumnByHint(options, ColumnHint.TraceKind);
diff --git a/src/labels.ts b/src/labels.ts
index 3ac184ae..c557556c 100644
--- a/src/labels.ts
+++ b/src/labels.ts
@@ -204,13 +204,17 @@ export default {
             label: 'Service Tags column',
             tooltip: 'Column for the service tags'
           },
+          flattenNested: {
+            label: 'Use Flatten Nested',
+            tooltip: 'Enable if your traces table was created with flatten_nested=1',
+          },
           eventsPrefix: {
-            label: 'Events column',
-            tooltip: 'Prefix for the events column'
+            label: 'Events prefix',
+            tooltip: 'Prefix for the events column (Events.Timestamp, Events.Name, etc.)'
           },
           linksPrefix: {
-            label: 'Links column',
-            tooltip: 'Prefix for the trace references column'
+            label: 'Links prefix',
+            tooltip: 'Prefix for the trace references column (Links.TraceId, Links.TraceState, etc.)'
           },
           kind: {
             label: 'Kind column',
@@ -453,6 +457,10 @@ export default {
           label: 'Service Tags Column',
           tooltip: 'Column that contains the service tags'
         },
+        flattenNested: {
+          label: 'Use Flatten Nested',
+          tooltip: 'Enable if your traces table was created with flatten_nested=1',
+        },
         eventsPrefix: {
           label: 'Events Prefix',
           tooltip: 'Prefix for the events column'
@@ -475,11 +483,11 @@ export default {
         },
         instrumentationLibraryName: {
           label: 'Library Name Column',
-          tooltip: 'Column that contains the instrumentation library name'
+          tooltip: 'Column that contains the instrumentation library name (Optional)'
         },
         instrumentationLibraryVersion: {
           label: 'Library Version Column',
-          tooltip: 'Column that contains the instrumentation library version'
+          tooltip: 'Column that contains the instrumentation library version (Optional)'
         },
         state: {
           label: 'State Column',
@@ -519,8 +527,6 @@ export default {
       [ColumnHint.TraceTags]: 'Tags',
       [ColumnHint.TraceServiceTags]: 'Service Tags',
       [ColumnHint.TraceStatusCode]: 'Status Code',
-      [ColumnHint.TraceEventsPrefix]: 'Events Prefix',
-      [ColumnHint.TraceLinksPrefix]: 'Links Prefix',
       [ColumnHint.TraceKind]: 'Kind',
       [ColumnHint.TraceStatusMessage]: 'Status Message',
       [ColumnHint.TraceInstrumentationLibraryName]: 'Instrumentation Library Name',
diff --git a/src/otel.ts b/src/otel.ts
index 24171ef0..dc0aa8ae 100644
--- a/src/otel.ts
+++ b/src/otel.ts
@@ -15,6 +15,9 @@ export interface OtelVersion {
   traceTable: string;
   traceColumnMap: Map<ColumnHint, string>;
   traceDurationUnit: TimeUnit.Nanoseconds;
+  flattenNested: boolean;
+  traceEventsColumnPrefix: string;
+  traceLinksColumnPrefix: string;
 }
 
 const otel129: OtelVersion = {
@@ -49,15 +52,14 @@ const otel129: OtelVersion = {
     [ColumnHint.TraceTags, 'SpanAttributes'],
     [ColumnHint.TraceServiceTags, 'ResourceAttributes'],
     [ColumnHint.TraceStatusCode, 'StatusCode'],
-    [ColumnHint.TraceEventsPrefix, 'Events'],
-    [ColumnHint.TraceLinksPrefix, 'Links'],
     [ColumnHint.TraceKind, 'SpanKind'],
     [ColumnHint.TraceStatusMessage, 'StatusMessage'],
-    [ColumnHint.TraceInstrumentationLibraryName, 'InstrumentationLibraryName'],
-    [ColumnHint.TraceInstrumentationLibraryVersion, 'InstrumentationLibraryVersion'],
-    [ColumnHint.TraceState, 'State'],
+    [ColumnHint.TraceState, 'TraceState'],
   ]),
+  flattenNested: false,
   traceDurationUnit: TimeUnit.Nanoseconds,
+  traceEventsColumnPrefix: 'Events',
+  traceLinksColumnPrefix: 'Links',
 };
 
 export const versions: readonly OtelVersion[] = [
diff --git a/src/types/config.ts b/src/types/config.ts
index ab8255d7..a8e19972 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -92,14 +92,16 @@ export interface CHTracesConfig {
   startTimeColumn?: string;
   tagsColumn?: string;
   serviceTagsColumn?: string;
-  eventsColumnPrefix?: string;
-  linksColumnPrefix?: string;
   kindColumn?: string;
   statusCodeColumn?: string;
   statusMessageColumn?: string;
+  stateColumn?: string;
   instrumentationLibraryNameColumn?: string;
   instrumentationLibraryVersionColumn?: string;
-  stateColumn?: string;
+
+  flattenNested?: boolean;
+  traceEventsColumnPrefix?: string;
+  traceLinksColumnPrefix?: string;
 }
 
 export interface AliasTableEntry {
diff --git a/src/types/queryBuilder.ts b/src/types/queryBuilder.ts
index f307446f..2e6a94b1 100644
--- a/src/types/queryBuilder.ts
+++ b/src/types/queryBuilder.ts
@@ -56,6 +56,14 @@ export interface QueryBuilderOptions {
     isTraceIdMode?: boolean;
     traceId?: string;
 
+    /**
+     * True if "Nested" column types should be treated as if they
+     * were created with flatten_nested=1. Applies to trace Events and Links columns.
+     */
+    flattenNested?: boolean;
+    traceEventsColumnPrefix?: string;
+    traceLinksColumnPrefix?: string;
+
     // Logs & Traces
     otelEnabled?: boolean;
     otelVersion?: string;
@@ -133,8 +141,6 @@ export enum ColumnHint {
   TraceTags = 'trace_tags',
   TraceServiceTags = 'trace_service_tags',
   TraceStatusCode = 'trace_status_code',
-  TraceEventsPrefix = 'trace_events_prefix',
-  TraceLinksPrefix = 'trace_links_prefix',
   TraceKind = 'trace_kind',
   TraceStatusMessage = 'trace_status_message',
   TraceInstrumentationLibraryName = 'instrumentation_library_name',
diff --git a/src/views/CHConfigEditor.tsx b/src/views/CHConfigEditor.tsx
index 72f88fd5..babc82df 100644
--- a/src/views/CHConfigEditor.tsx
+++ b/src/views/CHConfigEditor.tsx
@@ -4,7 +4,7 @@ import {
   onUpdateDatasourceJsonDataOption,
   onUpdateDatasourceSecureJsonDataOption,
 } from '@grafana/data';
-import { RadioButtonGroup, Switch, Input, SecretInput, Button, Field, HorizontalGroup, Alert, VerticalGroup } from '@grafana/ui';
+import { RadioButtonGroup, Switch, Input, SecretInput, Button, Field, Alert, Stack } from '@grafana/ui';
 import { CertificationKey } from '../components/ui/CertificationKey';
 import {
   CHConfig,
@@ -191,7 +191,7 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => {
 
   const uidWarning = (!options.uid) && (
     <Alert title="" severity="warning" buttonContent="Close">
-      <VerticalGroup>
+      <Stack>
         <div>
           {'This datasource is missing the'}
           <code>uid</code>
@@ -204,7 +204,7 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => {
           >provisioned via YAML</a>
           {', please verify the UID is set. This is required to enable data linking between logs and traces.'}
         </div>
-      </VerticalGroup>
+      </Stack>
     </Alert>
   );
 
@@ -450,14 +450,15 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => {
           onStartTimeColumnChange={c => onTracesConfigChange('startTimeColumn', c)}
           onTagsColumnChange={c => onTracesConfigChange('tagsColumn', c)}
           onServiceTagsColumnChange={c => onTracesConfigChange('serviceTagsColumn', c)}
-          onEventsColumnPrefixChange={c => onTracesConfigChange('eventsColumnPrefix', c)}
-          onLinksPrefixColumnChange={c => onTracesConfigChange('linksColumnPrefix', c)}
           onKindColumnChange={c => onTracesConfigChange('kindColumn', c)}
           onStatusCodeColumnChange={c => onTracesConfigChange('statusCodeColumn', c)}
           onStatusMessageColumnChange={c => onTracesConfigChange('statusMessageColumn', c)}
+          onStateColumnChange={c => onTracesConfigChange('stateColumn', c)}
           onInstrumentationLibraryNameColumnChange={c => onTracesConfigChange('instrumentationLibraryNameColumn', c)}
           onInstrumentationLibraryVersionColumnChange={c => onTracesConfigChange('instrumentationLibraryVersionColumn', c)}
-          onStateColumnChange={c => onTracesConfigChange('stateColumn', c)}
+          onFlattenNestedChange={c => onTracesConfigChange('flattenNested', c)}
+          onEventsColumnPrefixChange={c => onTracesConfigChange('traceEventsColumnPrefix', c)}
+          onLinksColumnPrefixChange={c => onTracesConfigChange('traceLinksColumnPrefix', c)}
         />
 
         <Divider />
@@ -478,7 +479,7 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => {
         <ConfigSubSection title="Custom Settings">
           {customSettings.map(({ setting, value }, i) => {
             return (
-              <HorizontalGroup key={i}>
+              <Stack key={i} direction='row'>
                 <Field label={`Setting`} aria-label={`Setting`}>
                   <Input
                     value={setting}
@@ -507,7 +508,7 @@ export const ConfigEditor: React.FC<ConfigEditorProps> = (props) => {
                     }}
                   ></Input>
                 </Field>
-              </HorizontalGroup>
+              </Stack>
             );
           })}
           <Button