Skip to content

Commit 8ad29b7

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/tablepro-core-package
2 parents ee259b7 + bd75ea3 commit 8ad29b7

File tree

6 files changed

+70
-22
lines changed

6 files changed

+70
-22
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.27.3] - 2026-04-03
11+
1012
### Added
1113

1214
- Structure tab context menu with Copy Name, Copy Definition (SQL), Duplicate, and Delete for columns, indexes, and foreign keys
@@ -15,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1517
- Data grid: preview and navigate FK references from right-click context menu
1618
- Data grid: add row from right-click on empty space
1719

20+
### Fixed
21+
22+
- Oracle: crash when opening views caused by OracleNIO state-machine corruption from concurrent queries, LONG column types, and DBMS_METADATA errors
23+
1824
## [0.27.2] - 2026-04-02
1925

2026
### Added
@@ -1142,7 +1148,8 @@ TablePro is a native macOS database client built with SwiftUI and AppKit, design
11421148
- Custom SQL query templates
11431149
- Performance optimized for large datasets
11441150

1145-
[Unreleased]: https://github.com/TableProApp/TablePro/compare/v0.27.2...HEAD
1151+
[Unreleased]: https://github.com/TableProApp/TablePro/compare/v0.27.3...HEAD
1152+
[0.27.3]: https://github.com/TableProApp/TablePro/compare/v0.27.2...v0.27.3
11461153
[0.27.2]: https://github.com/TableProApp/TablePro/compare/v0.27.1...v0.27.2
11471154
[0.27.1]: https://github.com/TableProApp/TablePro/compare/v0.27.0...v0.27.1
11481155
[0.27.0]: https://github.com/TableProApp/TablePro/compare/v0.26.0...v0.27.0

Plugins/OracleDriverPlugin/OracleConnection.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,39 @@ struct OracleQueryResult {
3939
let isTruncated: Bool
4040
}
4141

42+
// MARK: - Query Serialization
43+
44+
/// OracleNIO does not support concurrent queries on a single connection.
45+
/// Sending a second statement while the first stream is active corrupts the
46+
/// state machine. This actor serializes all executeQuery calls.
47+
private actor QueryGate {
48+
private var busy = false
49+
private var waiters: [CheckedContinuation<Void, Never>] = []
50+
51+
func acquire() async {
52+
if !busy {
53+
busy = true
54+
return
55+
}
56+
await withCheckedContinuation { waiters.append($0) }
57+
}
58+
59+
func release() {
60+
if !waiters.isEmpty {
61+
waiters.removeFirst().resume()
62+
} else {
63+
busy = false
64+
}
65+
}
66+
}
67+
4268
// MARK: - Connection Class
4369

4470
final class OracleConnectionWrapper: @unchecked Sendable {
4571
// MARK: - Properties
4672

4773
private static let connectionCounter = OSAllocatedUnfairLock(initialState: 0)
74+
private let queryGate = QueryGate()
4875

4976
private let host: String
5077
private let port: Int
@@ -143,6 +170,10 @@ final class OracleConnectionWrapper: @unchecked Sendable {
143170
}
144171
lock.unlock()
145172

173+
// OracleNIO does not support concurrent queries on a single connection.
174+
// Serialize all queries to prevent state-machine corruption.
175+
await queryGate.acquire()
176+
146177
do {
147178
let statement = OracleStatement(stringLiteral: query)
148179
let stream = try await connection.execute(statement, logger: nioLogger)
@@ -183,6 +214,7 @@ final class OracleConnectionWrapper: @unchecked Sendable {
183214
columnTypeNames = Array(repeating: "unknown", count: columns.count)
184215
}
185216

217+
await queryGate.release()
186218
return OracleQueryResult(
187219
columns: columns,
188220
columnTypeNames: columnTypeNames,
@@ -192,12 +224,16 @@ final class OracleConnectionWrapper: @unchecked Sendable {
192224
)
193225
} catch let sqlError as OracleSQLError {
194226
let detail = sqlError.serverInfo?.message ?? sqlError.description
227+
await queryGate.release()
195228
throw OracleError(message: detail)
196229
} catch let error as OracleError {
230+
await queryGate.release()
197231
throw error
198232
} catch is CancellationError {
233+
await queryGate.release()
199234
throw CancellationError()
200235
} catch {
236+
await queryGate.release()
201237
throw OracleError(message: "Query execution failed: \(String(describing: error))")
202238
}
203239
}

Plugins/OracleDriverPlugin/OraclePlugin.swift

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,6 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
401401
c.DATA_PRECISION,
402402
c.DATA_SCALE,
403403
c.NULLABLE,
404-
c.DATA_DEFAULT,
405404
CASE WHEN cc.COLUMN_NAME IS NOT NULL THEN 'Y' ELSE 'N' END AS IS_PK
406405
FROM ALL_TAB_COLUMNS c
407406
LEFT JOIN (
@@ -424,8 +423,7 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
424423
let precision = row[safe: 4] ?? nil
425424
let scale = row[safe: 5] ?? nil
426425
let isNullable = (row[safe: 6] ?? nil) == "Y"
427-
let defaultValue = (row[safe: 7] ?? nil)?.trimmingCharacters(in: .whitespacesAndNewlines)
428-
let isPk = (row[safe: 8] ?? nil) == "Y"
426+
let isPk = (row[safe: 7] ?? nil) == "Y"
429427

430428
let fullType = buildOracleFullType(dataType: dataType, dataLength: dataLength, precision: precision, scale: scale)
431429

@@ -434,7 +432,7 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
434432
dataType: fullType,
435433
isNullable: isNullable,
436434
isPrimaryKey: isPk,
437-
defaultValue: defaultValue
435+
defaultValue: nil
438436
)
439437
columnsByTable[tableName, default: []].append(col)
440438
}
@@ -509,15 +507,10 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
509507
func fetchTableDDL(table: String, schema: String?) async throws -> String {
510508
let escapedTable = table.replacingOccurrences(of: "'", with: "''")
511509
let escaped = effectiveSchemaEscaped(schema)
512-
let sql = "SELECT DBMS_METADATA.GET_DDL('TABLE', '\(escapedTable)', '\(escaped)') FROM DUAL"
513-
do {
514-
let result = try await execute(query: sql)
515-
if let row = result.rows.first, let ddl = row.first ?? nil {
516-
return ddl
517-
}
518-
} catch {
519-
Self.logger.debug("DBMS_METADATA failed, building DDL manually: \(error.localizedDescription)")
520-
}
510+
511+
// Do NOT use DBMS_METADATA.GET_DDL — if the object type is wrong
512+
// (view, materialized view, etc.), Oracle returns ORA-31603 which
513+
// corrupts OracleNIO's connection state machine. Build DDL manually.
521514

522515
let cols = try await fetchColumns(table: table, schema: schema)
523516
var ddl = "CREATE TABLE \"\(escaped)\".\"\(escapedTable)\" (\n"
@@ -535,9 +528,10 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
535528
func fetchViewDefinition(view: String, schema: String?) async throws -> String {
536529
let escapedView = view.replacingOccurrences(of: "'", with: "''")
537530
let escaped = effectiveSchemaEscaped(schema)
538-
// Use DBMS_METADATA.GET_DDL instead of ALL_VIEWS.TEXT to avoid LONG column type
539-
// that crashes OracleNIO's decoder
540-
let sql = "SELECT DBMS_METADATA.GET_DDL('VIEW', '\(escapedView)', '\(escaped)') FROM DUAL"
531+
// ALL_VIEWS.TEXT is LONG (crashes OracleNIO). TEXT_VC is VARCHAR2(4000), safe.
532+
// Do NOT use DBMS_METADATA.GET_DDL — wrong object type triggers ORA-31603
533+
// which corrupts OracleNIO's connection state machine.
534+
let sql = "SELECT TEXT_VC FROM ALL_VIEWS WHERE VIEW_NAME = '\(escapedView)' AND OWNER = '\(escaped)'"
541535
let result = try await execute(query: sql)
542536
return result.rows.first?.first?.flatMap { $0 } ?? ""
543537
}

TablePro.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,7 +2093,7 @@
20932093
CODE_SIGN_IDENTITY = "Apple Development";
20942094
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
20952095
CODE_SIGN_STYLE = Automatic;
2096-
CURRENT_PROJECT_VERSION = 55;
2096+
CURRENT_PROJECT_VERSION = 56;
20972097
DEAD_CODE_STRIPPING = YES;
20982098
DEVELOPMENT_TEAM = D7HJ5TFYCU;
20992099
ENABLE_APP_SANDBOX = NO;
@@ -2118,7 +2118,7 @@
21182118
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
21192119
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
21202120
MACOSX_DEPLOYMENT_TARGET = 14.0;
2121-
MARKETING_VERSION = 0.27.2;
2121+
MARKETING_VERSION = 0.27.3;
21222122
OTHER_LDFLAGS = (
21232123
"-Wl,-w",
21242124
"-force_load",
@@ -2165,7 +2165,7 @@
21652165
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
21662166
CODE_SIGN_STYLE = Automatic;
21672167
COPY_PHASE_STRIP = YES;
2168-
CURRENT_PROJECT_VERSION = 55;
2168+
CURRENT_PROJECT_VERSION = 56;
21692169
DEAD_CODE_STRIPPING = YES;
21702170
DEPLOYMENT_POSTPROCESSING = YES;
21712171
DEVELOPMENT_TEAM = D7HJ5TFYCU;
@@ -2191,7 +2191,7 @@
21912191
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
21922192
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
21932193
MACOSX_DEPLOYMENT_TARGET = 14.0;
2194-
MARKETING_VERSION = 0.27.2;
2194+
MARKETING_VERSION = 0.27.3;
21952195
OTHER_LDFLAGS = (
21962196
"-Wl,-w",
21972197
"-force_load",

TablePro/Core/SchemaTracking/SchemaStatementGenerator.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,5 +237,4 @@ struct SchemaStatementGenerator {
237237
isDestructive: true
238238
)
239239
}
240-
241240
}

docs/changelog.mdx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ description: "Product updates and announcements for TablePro"
44
rss: true
55
---
66

7+
<Update label="April 3, 2026" description="v0.27.3">
8+
### New Features
9+
10+
- **Foreign Key Preview**: Cmd+Enter or right-click on FK cells to preview referenced rows
11+
- **Structure Tab Context Menu**: Copy Name, Copy Definition, Duplicate, and Delete for columns, indexes, and foreign keys
12+
- **Column Header Context Menu**: Sort and show/hide columns from header right-click
13+
14+
### Bug Fixes
15+
16+
- Fixed Oracle crash when opening views
17+
</Update>
18+
719
<Update label="April 2, 2026" description="v0.27.2">
820
### New Features
921

0 commit comments

Comments
 (0)