Skip to content

Commit 9f78f79

Browse files
committed
Fix
1 parent 836afc8 commit 9f78f79

2 files changed

Lines changed: 133 additions & 8 deletions

File tree

‎internal/connectors/trino.go‎

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,16 @@ func quoteTrinoIdentifier(name string) string {
893893
return name
894894
}
895895

896+
// unwrapMessageDataForColumns returns the map to use for column lookups.
897+
// If msgData is {"value": {...}, "_metadata": {...}}, returns the inner value map.
898+
// Otherwise returns msgData as-is (plain columnar format).
899+
func unwrapMessageDataForColumns(msgData map[string]interface{}) map[string]interface{} {
900+
if v, ok := msgData["value"].(map[string]interface{}); ok && len(msgData) <= 2 {
901+
return v
902+
}
903+
return msgData
904+
}
905+
896906
// extractDataAndMetadata extracts data and _metadata from a rawMode message.
897907
// If msg.Data is {"value": ..., "_metadata": ...}, uses those; otherwise uses whole data and msg.Metadata.
898908
func extractDataAndMetadata(msg *types.Message) (dataStr, metaStr string) {
@@ -1103,13 +1113,24 @@ func (t *TrinoSinkConnector) Write(ctx context.Context, messages <-chan *types.M
11031113
}
11041114
}
11051115

1116+
// hasRawModeColumns checks if the table has data and _metadata columns (required for raw mode).
1117+
func (t *TrinoSinkConnector) hasRawModeColumns(columns []TableColumnInfo) bool {
1118+
hasData, hasMeta := false, false
1119+
for _, col := range columns {
1120+
if col.Name == "data" {
1121+
hasData = true
1122+
}
1123+
if col.Name == "_metadata" {
1124+
hasMeta = true
1125+
}
1126+
}
1127+
return hasData && hasMeta
1128+
}
1129+
11061130
func (t *TrinoSinkConnector) executeBatch(ctx context.Context, batch []*types.Message) error {
11071131
if len(batch) == 0 {
11081132
return nil
11091133
}
1110-
if t.rawMode() {
1111-
return t.executeBatchRaw(ctx, batch)
1112-
}
11131134

11141135
// Get table columns (use cached if available, otherwise fetch)
11151136
t.columnsMu.RLock()
@@ -1128,6 +1149,16 @@ func (t *TrinoSinkConnector) executeBatch(ctx context.Context, batch []*types.Me
11281149
t.columnsMu.Unlock()
11291150
}
11301151

1152+
// Use raw mode only when configured AND table has data/_metadata columns.
1153+
// If rawMode is true but table has different schema (e.g. columnar), fall back to schema-based insert.
1154+
if t.rawMode() && t.hasRawModeColumns(tableColumns) {
1155+
return t.executeBatchRaw(ctx, batch)
1156+
}
1157+
if t.rawMode() && !t.hasRawModeColumns(tableColumns) {
1158+
t.logger.Info("rawMode is true but target table does not have data/_metadata columns, using schema-based insert",
1159+
"table", fmt.Sprintf("%s.%s.%s", t.config.Catalog, t.config.Schema, t.config.Table))
1160+
}
1161+
11311162
// Create a map for fast lookup of table columns by name
11321163
tableColumnsMap := make(map[string]TableColumnInfo)
11331164
for _, col := range tableColumns {
@@ -1146,8 +1177,9 @@ func (t *TrinoSinkConnector) executeBatch(ctx context.Context, batch []*types.Me
11461177
return fmt.Errorf("failed to parse message JSON at index %d: %w", i, err)
11471178
}
11481179

1149-
// Collect all keys from all messages
1150-
for k := range msgData {
1180+
// Collect all keys from all messages (unwrap {"value": {...}} format if present)
1181+
dataForColumns := unwrapMessageDataForColumns(msgData)
1182+
for k := range dataForColumns {
11511183
allMessageKeys[k] = true
11521184
}
11531185
}
@@ -1167,13 +1199,14 @@ func (t *TrinoSinkConnector) executeBatch(ctx context.Context, batch []*types.Me
11671199
var firstMsgData map[string]interface{}
11681200
if err := json.Unmarshal(batch[0].Data, &firstMsgData); err == nil {
11691201
// Log first message structure (limit size to avoid huge logs)
1170-
firstMsgJSON, _ := json.Marshal(firstMsgData)
1202+
dataForPreview := unwrapMessageDataForColumns(firstMsgData)
1203+
firstMsgJSON, _ := json.Marshal(dataForPreview)
11711204
msgPreview := string(firstMsgJSON)
11721205
if len(msgPreview) > 500 {
11731206
msgPreview = msgPreview[:500] + "..."
11741207
}
1175-
firstMsgKeys := make([]string, 0, len(firstMsgData))
1176-
for k := range firstMsgData {
1208+
firstMsgKeys := make([]string, 0, len(dataForPreview))
1209+
for k := range dataForPreview {
11771210
firstMsgKeys = append(firstMsgKeys, k)
11781211
}
11791212
t.logger.Info("First message in batch (preview)",
@@ -1244,6 +1277,9 @@ func (t *TrinoSinkConnector) executeBatch(ctx context.Context, batch []*types.Me
12441277
return fmt.Errorf("failed to parse message JSON: %w", err)
12451278
}
12461279

1280+
// Unwrap {"value": {...}} format if present (e.g. from Kafka with rawMode source)
1281+
data = unwrapMessageDataForColumns(data)
1282+
12471283
// Build values for this row - use values from message or NULL
12481284
values := make([]string, len(columnsToUse))
12491285
for j, col := range columnsToUse {

‎internal/connectors/trino_test.go‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package connectors
1818

1919
import (
20+
"encoding/json"
2021
"strings"
2122
"testing"
2223
"time"
@@ -402,6 +403,94 @@ func TestExtractDataAndMetadata(t *testing.T) {
402403
}
403404
}
404405

406+
func TestUnwrapMessageDataForColumns(t *testing.T) {
407+
tests := []struct {
408+
name string
409+
msgData string
410+
wantKeys []string
411+
}{
412+
{
413+
name: "wrapped_value_format",
414+
msgData: `{"value":{"id":1,"name":"foo"},"_metadata":{"offset":10}}`,
415+
wantKeys: []string{"id", "name"},
416+
},
417+
{
418+
name: "plain_columnar_format",
419+
msgData: `{"id":2,"name":"bar","amount":100}`,
420+
wantKeys: []string{"id", "name", "amount"},
421+
},
422+
{
423+
name: "value_not_map_returns_as_is",
424+
msgData: `{"value":"string","_metadata":{}}`,
425+
wantKeys: []string{"value", "_metadata"},
426+
},
427+
}
428+
for _, tt := range tests {
429+
t.Run(tt.name, func(t *testing.T) {
430+
var m map[string]interface{}
431+
require.NoError(t, json.Unmarshal([]byte(tt.msgData), &m))
432+
got := unwrapMessageDataForColumns(m)
433+
gotKeys := make([]string, 0, len(got))
434+
for k := range got {
435+
gotKeys = append(gotKeys, k)
436+
}
437+
assert.ElementsMatch(t, tt.wantKeys, gotKeys)
438+
})
439+
}
440+
}
441+
442+
func TestTrinoSinkConnector_hasRawModeColumns(t *testing.T) {
443+
connector := NewTrinoSinkConnector(&v1.TrinoSinkSpec{
444+
ServerURL: "http://localhost:8080",
445+
Catalog: "test",
446+
Schema: "test",
447+
Table: "test",
448+
})
449+
tests := []struct {
450+
name string
451+
columns []TableColumnInfo
452+
want bool
453+
}{
454+
{
455+
name: "has_both",
456+
columns: []TableColumnInfo{
457+
{Name: "data", Type: "varchar"},
458+
{Name: "_metadata", Type: "varchar"},
459+
},
460+
want: true,
461+
},
462+
{
463+
name: "missing_data",
464+
columns: []TableColumnInfo{
465+
{Name: "_metadata", Type: "varchar"},
466+
},
467+
want: false,
468+
},
469+
{
470+
name: "missing_metadata",
471+
columns: []TableColumnInfo{
472+
{Name: "data", Type: "varchar"},
473+
},
474+
want: false,
475+
},
476+
{
477+
name: "columnar_schema",
478+
columns: []TableColumnInfo{
479+
{Name: "id", Type: "bigint"},
480+
{Name: "name", Type: "varchar"},
481+
{Name: "amount", Type: "double"},
482+
},
483+
want: false,
484+
},
485+
}
486+
for _, tt := range tests {
487+
t.Run(tt.name, func(t *testing.T) {
488+
got := connector.hasRawModeColumns(tt.columns)
489+
assert.Equal(t, tt.want, got)
490+
})
491+
}
492+
}
493+
405494
func TestQuoteTrinoIdentifier(t *testing.T) {
406495
tests := []struct {
407496
name string

0 commit comments

Comments
 (0)