Skip to content

Commit adbda4d

Browse files
committed
refactor(plugin-duckdb): detect cast-needed columns via render allowlist
1 parent 7cfa85f commit adbda4d

1 file changed

Lines changed: 26 additions & 15 deletions

File tree

Plugins/DuckDBDriverPlugin/DuckDBConnection.swift

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ actor DuckDBConnectionActor {
9191
}
9292

9393
var raw = Self.extractResult(from: &result, startTime: startTime)
94-
Self.patchTzColumns(&raw, query: query, connection: conn)
94+
Self.patchCastedColumns(&raw, query: query, connection: conn)
9595
return raw
9696
}
9797

@@ -163,7 +163,7 @@ actor DuckDBConnectionActor {
163163
}
164164

165165
var raw = Self.extractResult(from: &result, startTime: startTime)
166-
Self.patchTzColumns(&raw, query: query, connection: conn)
166+
Self.patchCastedColumns(&raw, query: query, connection: conn)
167167
return raw
168168
}
169169

@@ -204,7 +204,7 @@ actor DuckDBConnectionActor {
204204
columnTypeNames.append(Self.typeName(for: colType))
205205
}
206206

207-
if columnTypes.contains(where: Self.isUnrenderable) {
207+
if columnTypes.contains(where: Self.requiresTextCast) {
208208
duckdb_destroy_result(&result)
209209
try Self.streamWrappedQuery(
210210
query: query,
@@ -235,7 +235,7 @@ actor DuckDBConnectionActor {
235235
continuation: AsyncThrowingStream<PluginStreamElement, Error>.Continuation
236236
) throws {
237237
let castExprs = columns.enumerated().map { i, name in
238-
castExpression(for: columnTypes[i], column: name)
238+
projection(for: columnTypes[i], column: name)
239239
}
240240
let wrappedQuery = buildWrappedQuery(originalQuery: query, castExprs: castExprs)
241241

@@ -484,16 +484,16 @@ actor DuckDBConnectionActor {
484484
}
485485
}
486486

487-
static func patchTzColumns(
487+
static func patchCastedColumns(
488488
_ raw: inout DuckDBRawResult, query: String, connection: duckdb_connection
489489
) {
490490
let patchedColIndices = raw.columnTypes.enumerated().compactMap { idx, type in
491-
isUnrenderable(type) ? idx : nil
491+
requiresTextCast(type) ? idx : nil
492492
}
493493
guard !patchedColIndices.isEmpty, !raw.rows.isEmpty else { return }
494494

495495
let castExprs = raw.columns.enumerated().map { i, name in
496-
castExpression(for: raw.columnTypes[i], column: name)
496+
projection(for: raw.columnTypes[i], column: name)
497497
}
498498
let wrappedQuery = buildWrappedQuery(originalQuery: query, castExprs: castExprs)
499499

@@ -514,25 +514,36 @@ actor DuckDBConnectionActor {
514514
}
515515
}
516516

517-
static func isUnrenderable(_ type: duckdb_type) -> Bool {
517+
static func isNativelyRenderable(_ type: duckdb_type) -> Bool {
518518
switch type {
519-
case DUCKDB_TYPE_INVALID, DUCKDB_TYPE_TIMESTAMP_TZ, DUCKDB_TYPE_TIME_TZ, DUCKDB_TYPE_GEOMETRY:
519+
case DUCKDB_TYPE_BOOLEAN,
520+
DUCKDB_TYPE_TINYINT, DUCKDB_TYPE_SMALLINT, DUCKDB_TYPE_INTEGER, DUCKDB_TYPE_BIGINT, DUCKDB_TYPE_HUGEINT,
521+
DUCKDB_TYPE_UTINYINT, DUCKDB_TYPE_USMALLINT, DUCKDB_TYPE_UINTEGER, DUCKDB_TYPE_UBIGINT, DUCKDB_TYPE_UHUGEINT,
522+
DUCKDB_TYPE_FLOAT, DUCKDB_TYPE_DOUBLE, DUCKDB_TYPE_DECIMAL,
523+
DUCKDB_TYPE_VARCHAR, DUCKDB_TYPE_BLOB, DUCKDB_TYPE_UUID, DUCKDB_TYPE_BIT, DUCKDB_TYPE_ENUM,
524+
DUCKDB_TYPE_DATE, DUCKDB_TYPE_TIME, DUCKDB_TYPE_TIME_NS, DUCKDB_TYPE_INTERVAL,
525+
DUCKDB_TYPE_TIMESTAMP, DUCKDB_TYPE_TIMESTAMP_S, DUCKDB_TYPE_TIMESTAMP_MS, DUCKDB_TYPE_TIMESTAMP_NS,
526+
DUCKDB_TYPE_LIST, DUCKDB_TYPE_STRUCT, DUCKDB_TYPE_MAP, DUCKDB_TYPE_ARRAY, DUCKDB_TYPE_UNION:
520527
return true
521528
default:
522529
return false
523530
}
524531
}
525532

533+
static func requiresTextCast(_ type: duckdb_type) -> Bool {
534+
!isNativelyRenderable(type)
535+
}
536+
526537
static func castExpression(for type: duckdb_type, column: String) -> String {
527538
let quoted = quoteIdentifier(column)
528-
switch type {
529-
case DUCKDB_TYPE_GEOMETRY:
539+
if type == DUCKDB_TYPE_GEOMETRY {
530540
return "CASE WHEN \(quoted) IS NULL THEN NULL ELSE ST_AsText(\(quoted)) END AS \(quoted)"
531-
case DUCKDB_TYPE_INVALID, DUCKDB_TYPE_TIMESTAMP_TZ, DUCKDB_TYPE_TIME_TZ:
532-
return "CASE WHEN \(quoted) IS NULL THEN NULL ELSE CAST(\(quoted) AS VARCHAR) END AS \(quoted)"
533-
default:
534-
return quoted
535541
}
542+
return "CASE WHEN \(quoted) IS NULL THEN NULL ELSE CAST(\(quoted) AS VARCHAR) END AS \(quoted)"
543+
}
544+
545+
static func projection(for type: duckdb_type, column: String) -> String {
546+
requiresTextCast(type) ? castExpression(for: type, column: column) : quoteIdentifier(column)
536547
}
537548

538549
static func buildWrappedQuery(originalQuery: String, castExprs: [String]) -> String {

0 commit comments

Comments
 (0)