Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Raw filters in the data grid now apply on document and key-value databases; the typed text was being dropped before it reached the driver. (#1529)
- Connecting to Oracle no longer crashes the app while reading certain server values during the handshake; a bad packet now fails the connection with an error instead. (#1746)
- Following a foreign key into a table in another schema now opens the correct table for SQL Server and Oracle; the referenced schema was missing, so navigation fell back to the default schema. (#1754)
- Browsing and editing a SQL Server or Oracle table or view outside the default schema no longer fails with "Invalid object name" or writes to the wrong table; data, filter, and save queries now qualify the table with its schema. (#1754)

## [0.52.1] - 2026-06-22
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,16 @@ public enum MSSQLSchemaQueries {
fk.name AS constraint_name,
cp.name AS column_name,
tr.name AS ref_table,
cr.name AS ref_column
cr.name AS ref_column,
sr.name AS ref_schema
FROM sys.foreign_keys fk
JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
JOIN sys.tables tp ON fkc.parent_object_id = tp.object_id
JOIN sys.schemas s ON tp.schema_id = s.schema_id
JOIN sys.columns cp
ON fkc.parent_object_id = cp.object_id AND fkc.parent_column_id = cp.column_id
JOIN sys.tables tr ON fkc.referenced_object_id = tr.object_id
JOIN sys.schemas sr ON tr.schema_id = sr.schema_id
JOIN sys.columns cr
ON fkc.referenced_object_id = cr.object_id AND fkc.referenced_column_id = cr.column_id
WHERE tp.name = '\(t)' AND s.name = '\(s)'
Expand Down Expand Up @@ -233,12 +235,20 @@ public struct MSSQLForeignKeyRow: Sendable, Equatable {
public let columnName: String
public let referencedTable: String
public let referencedColumn: String
public let referencedSchema: String?

public init(constraintName: String, columnName: String, referencedTable: String, referencedColumn: String) {
public init(
constraintName: String,
columnName: String,
referencedTable: String,
referencedColumn: String,
referencedSchema: String? = nil
) {
self.constraintName = constraintName
self.columnName = columnName
self.referencedTable = referencedTable
self.referencedColumn = referencedColumn
self.referencedSchema = referencedSchema
}
}

Expand Down Expand Up @@ -286,7 +296,8 @@ public extension MSSQLSchemaQueries {
constraintName: name,
columnName: column,
referencedTable: refTable,
referencedColumn: refColumn
referencedColumn: refColumn,
referencedSchema: row[safe: 4] ?? nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass the parsed schema to mobile FK results

Because this shared parser is also used by TableProMobile/TableProMobile/Drivers/MSSQLDriver.swift, and that call still constructs ForeignKeyInfo without forwarding $0.referencedSchema, SQL Server mobile FK navigation continues to receive a nil schema even though this query now returns it. Please thread the parsed schema through that mapping too; otherwise the cross-schema FK fix only applies to the plugin path.

Useful? React with 👍 / 👎.

)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ final class MSSQLSchemaQueriesTests: XCTestCase {
XCTAssertTrue(sql.contains("'dbo'"))
}

func testForeignKeysQuerySelectsReferencedSchema() {
let sql = MSSQLSchemaQueries.foreignKeys(schema: "dbo", table: "Orders")
XCTAssertTrue(sql.contains("sr.name AS ref_schema"))
XCTAssertTrue(sql.contains("JOIN sys.schemas sr ON tr.schema_id = sr.schema_id"))
}

func testParseTableRowDetectsView() {
let row: [String?] = ["v_active_users", "VIEW"]
XCTAssertEqual(MSSQLSchemaQueries.parseTableRow(row), MSSQLTableRow(name: "v_active_users", isView: true))
Expand Down Expand Up @@ -103,12 +109,18 @@ final class MSSQLSchemaQueriesTests: XCTestCase {
}

func testParseForeignKeyRowExtractsAll() {
let row: [String?] = ["FK_orders_users", "user_id", "users", "id"]
let row: [String?] = ["FK_orders_users", "user_id", "users", "id", "sales"]
let parsed = MSSQLSchemaQueries.parseForeignKeyRow(row)
XCTAssertEqual(parsed?.constraintName, "FK_orders_users")
XCTAssertEqual(parsed?.columnName, "user_id")
XCTAssertEqual(parsed?.referencedTable, "users")
XCTAssertEqual(parsed?.referencedColumn, "id")
XCTAssertEqual(parsed?.referencedSchema, "sales")
}

func testParseForeignKeyRowWithoutReferencedSchema() {
let row: [String?] = ["FK_orders_users", "user_id", "users", "id"]
XCTAssertNil(MSSQLSchemaQueries.parseForeignKeyRow(row)?.referencedSchema)
}

func testQualifiedNamePrefixesSchema() {
Expand Down
42 changes: 12 additions & 30 deletions Plugins/MSSQLDriverPlugin/MSSQLPluginDriver+Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@
extra: isIdentity ? "IDENTITY" : nil
)
}
identityCacheLock.lock()

Check warning on line 107 in Plugins/MSSQLDriverPlugin/MSSQLPluginDriver+Schema.swift

View workflow job for this annotation

GitHub Actions / macOS App Tests

instance method 'lock' is unavailable from asynchronous contexts; Use async-safe scoped locking instead; this is an error in the Swift 6 language mode
identityColumnsByTable[table] = identityColumns
identityCacheLock.unlock()

Check warning on line 109 in Plugins/MSSQLDriverPlugin/MSSQLPluginDriver+Schema.swift

View workflow job for this annotation

GitHub Actions / macOS App Tests

instance method 'unlock' is unavailable from asynchronous contexts; Use async-safe scoped locking instead; this is an error in the Swift 6 language mode
return columns
}

Expand Down Expand Up @@ -166,37 +166,16 @@
}

func fetchForeignKeys(table: String, schema: String?) async throws -> [PluginForeignKeyInfo] {
let escapedTable = table.replacingOccurrences(of: "'", with: "''")
let esc = effectiveSchemaEscaped(schema)
let sql = """
SELECT
fk.name AS constraint_name,
cp.name AS column_name,
tr.name AS ref_table,
cr.name AS ref_column
FROM sys.foreign_keys fk
JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
JOIN sys.tables tp ON fkc.parent_object_id = tp.object_id
JOIN sys.schemas s ON tp.schema_id = s.schema_id
JOIN sys.columns cp
ON fkc.parent_object_id = cp.object_id AND fkc.parent_column_id = cp.column_id
JOIN sys.tables tr ON fkc.referenced_object_id = tr.object_id
JOIN sys.columns cr
ON fkc.referenced_object_id = cr.object_id AND fkc.referenced_column_id = cr.column_id
WHERE tp.name = '\(escapedTable)' AND s.name = '\(esc)'
ORDER BY fk.name
"""
let sql = MSSQLSchemaQueries.foreignKeys(schema: schema ?? _currentSchema, table: table)
let result = try await execute(query: sql)
return result.rows.compactMap { row -> PluginForeignKeyInfo? in
guard let constraintName = row[safe: 0]?.asText,
let columnName = row[safe: 1]?.asText,
let refTable = row[safe: 2]?.asText,
let refColumn = row[safe: 3]?.asText else { return nil }
guard let parsed = MSSQLSchemaQueries.parseForeignKeyRow(row.map { $0.asText }) else { return nil }
return PluginForeignKeyInfo(
name: constraintName,
column: columnName,
referencedTable: refTable,
referencedColumn: refColumn
name: parsed.constraintName,
column: parsed.columnName,
referencedTable: parsed.referencedTable,
referencedColumn: parsed.referencedColumn,
referencedSchema: parsed.referencedSchema
)
}
}
Expand Down Expand Up @@ -360,14 +339,16 @@
fk.name AS constraint_name,
cp.name AS column_name,
tr.name AS ref_table,
cr.name AS ref_column
cr.name AS ref_column,
sr.name AS ref_schema
FROM sys.foreign_keys fk
JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
JOIN sys.tables tp ON fkc.parent_object_id = tp.object_id
JOIN sys.schemas s ON tp.schema_id = s.schema_id
JOIN sys.columns cp
ON fkc.parent_object_id = cp.object_id AND fkc.parent_column_id = cp.column_id
JOIN sys.tables tr ON fkc.referenced_object_id = tr.object_id
JOIN sys.schemas sr ON tr.schema_id = sr.schema_id
JOIN sys.columns cr
ON fkc.referenced_object_id = cr.object_id AND fkc.referenced_column_id = cr.column_id
WHERE s.name = '\(esc)'
Expand All @@ -385,7 +366,8 @@
name: constraintName,
column: columnName,
referencedTable: refTable,
referencedColumn: refColumn
referencedColumn: refColumn,
referencedSchema: row[safe: 5]?.asText
)
fksByTable[tableName, default: []].append(fk)
}
Expand Down
8 changes: 6 additions & 2 deletions Plugins/OracleDriverPlugin/OraclePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,8 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
acc.COLUMN_NAME,
rc.TABLE_NAME AS REF_TABLE,
rcc.COLUMN_NAME AS REF_COLUMN,
ac.DELETE_RULE
ac.DELETE_RULE,
rc.OWNER AS REF_SCHEMA
FROM ALL_CONSTRAINTS ac
JOIN ALL_CONS_COLUMNS acc ON ac.CONSTRAINT_NAME = acc.CONSTRAINT_NAME
AND ac.OWNER = acc.OWNER
Expand All @@ -475,6 +476,7 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
column: columnName,
referencedTable: refTable,
referencedColumn: refColumn,
referencedSchema: row[safe: 5]?.asText,
onDelete: deleteRule,
onUpdate: "NO ACTION"
)
Expand Down Expand Up @@ -602,7 +604,8 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
acc.COLUMN_NAME,
rc.TABLE_NAME AS REF_TABLE,
rcc.COLUMN_NAME AS REF_COLUMN,
ac.DELETE_RULE
ac.DELETE_RULE,
rc.OWNER AS REF_SCHEMA
FROM ALL_CONSTRAINTS ac
JOIN ALL_CONS_COLUMNS acc ON ac.CONSTRAINT_NAME = acc.CONSTRAINT_NAME
AND ac.OWNER = acc.OWNER
Expand All @@ -627,6 +630,7 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
column: columnName,
referencedTable: refTable,
referencedColumn: refColumn,
referencedSchema: row[safe: 6]?.asText,
onDelete: deleteRule,
onUpdate: "NO ACTION"
)
Expand Down
Loading