diff --git a/.github/workflows/ios-tests.yml b/.github/workflows/ios-tests.yml index 563b7cb6c..6c9cf0d7b 100644 --- a/.github/workflows/ios-tests.yml +++ b/.github/workflows/ios-tests.yml @@ -43,16 +43,36 @@ jobs: - name: Install xcbeautify run: brew list xcbeautify &>/dev/null || brew install xcbeautify - # macos-26 lazy-loads simulator runtimes; -downloadPlatform pulls the runtime - # matching Xcode's SDK and is a no-op when it is already present. - - name: Install iOS simulator runtime - run: sudo xcodebuild -downloadPlatform iOS - # Secrets.xcconfig is gitignored. Tests do not need analytics keys, so the # checked-in example template is enough for the project to resolve. - name: Stub Secrets.xcconfig run: cp TableProMobile/Secrets.xcconfig.example TableProMobile/Secrets.xcconfig + # macos-26 lazy-loads simulator runtimes; -downloadPlatform pulls the runtime + # matching Xcode's SDK and is a no-op when it is already present. + - name: Install iOS simulator runtime + run: | + if sudo xcodebuild -downloadPlatform iOS; then + exit 0 + fi + + echo "::warning::xcodebuild -downloadPlatform iOS failed; checking existing simulator destinations" + destinations="$( + xcodebuild -showdestinations \ + -project "$XCODE_PROJECT" \ + -scheme "$XCODE_SCHEME" \ + -skipPackagePluginValidation 2>&1 || true + )" + echo "$destinations" + + if echo "$destinations" | grep -q "platform:iOS Simulator.*name:iPhone 17 Pro"; then + echo "iPhone 17 Pro simulator destination is already available; continuing." + exit 0 + fi + + echo "::error::No iPhone 17 Pro simulator destination is available after downloadPlatform failed." + exit 70 + - name: Cache static libraries uses: actions/cache@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 841ed7bfd..442878b39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- SQL Server table browsing and edits now use the active schema when opening objects outside `dbo`. (#1774) - Quick Switcher opens again on macOS Sequoia. Since 0.51.0 the panel came up invisible on macOS 15, and the toolbar button and keyboard shortcut appeared to do nothing. (#1806) - Quick Switcher keyboard navigation (Ctrl-J/K and arrow shortcuts) no longer goes dead after the switcher has been opened and closed repeatedly. (#1806) - Restored table tabs no longer reload all at once or flood failure dialogs on launch. Only the frontmost tab loads immediately; other restored tabs load when you switch to them, and a load failure now shows inline in the tab instead of a dialog. (#1796) diff --git a/Packages/TableProCore/Sources/TableProMSSQLCore/MSSQLSchemaQueries.swift b/Packages/TableProCore/Sources/TableProMSSQLCore/MSSQLSchemaQueries.swift index 2224683bd..bb4fed6ba 100644 --- a/Packages/TableProCore/Sources/TableProMSSQLCore/MSSQLSchemaQueries.swift +++ b/Packages/TableProCore/Sources/TableProMSSQLCore/MSSQLSchemaQueries.swift @@ -20,6 +20,23 @@ public enum MSSQLSchemaQueries { return bracketed(schema: schema, table: table) } + public static func resolvedObjectSchema( + _ schema: String?, + currentSchema: String?, + allowCurrentSchemaFallback: Bool = true + ) -> String? { + if let schema, !schema.isEmpty { + return schema + } + guard allowCurrentSchemaFallback else { + return nil + } + guard let currentSchema, !currentSchema.isEmpty else { + return nil + } + return currentSchema + } + public static func browse( schema: String?, table: String, diff --git a/Packages/TableProCore/Tests/TableProMSSQLCoreTests/MSSQLSchemaQueriesTests.swift b/Packages/TableProCore/Tests/TableProMSSQLCoreTests/MSSQLSchemaQueriesTests.swift index 43c552ccb..9d289eeb6 100644 --- a/Packages/TableProCore/Tests/TableProMSSQLCoreTests/MSSQLSchemaQueriesTests.swift +++ b/Packages/TableProCore/Tests/TableProMSSQLCoreTests/MSSQLSchemaQueriesTests.swift @@ -132,6 +132,30 @@ final class MSSQLSchemaQueriesTests: XCTestCase { XCTAssertEqual(MSSQLSchemaQueries.qualifiedName(schema: "", table: "routeCache"), "[routeCache]") } + func testResolvedObjectSchemaPrefersExplicitSchema() { + XCTAssertEqual(MSSQLSchemaQueries.resolvedObjectSchema("audit", currentSchema: "sales"), "audit") + } + + func testResolvedObjectSchemaFallsBackToCurrentSchema() { + XCTAssertEqual(MSSQLSchemaQueries.resolvedObjectSchema(nil, currentSchema: "sales"), "sales") + XCTAssertEqual(MSSQLSchemaQueries.resolvedObjectSchema("", currentSchema: "sales"), "sales") + } + + func testResolvedObjectSchemaCanDisableCurrentSchemaFallback() { + XCTAssertNil( + MSSQLSchemaQueries.resolvedObjectSchema( + nil, + currentSchema: "dbo", + allowCurrentSchemaFallback: false + ) + ) + } + + func testResolvedObjectSchemaReturnsNilWhenBothSchemasAreBlank() { + XCTAssertNil(MSSQLSchemaQueries.resolvedObjectSchema(nil, currentSchema: nil)) + XCTAssertNil(MSSQLSchemaQueries.resolvedObjectSchema("", currentSchema: "")) + } + func testBrowseQualifiesNonDefaultSchema() { let sql = MSSQLSchemaQueries.browse( schema: "sales", table: "routeCache", @@ -143,6 +167,18 @@ final class MSSQLSchemaQueriesTests: XCTestCase { ) } + func testBrowseUsesResolvedCurrentSchema() { + let schema = MSSQLSchemaQueries.resolvedObjectSchema(nil, currentSchema: "sales") + let sql = MSSQLSchemaQueries.browse( + schema: schema, table: "routeCache", + orderByClause: "ORDER BY (SELECT NULL)", offset: 0, limit: 200 + ) + XCTAssertEqual( + sql, + "SELECT * FROM [sales].[routeCache] ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH NEXT 200 ROWS ONLY" + ) + } + func testBrowseWithoutSchemaStaysUnqualified() { let sql = MSSQLSchemaQueries.browse( schema: nil, table: "routeCache", diff --git a/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift b/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift index 1fcdfdc5a..f07634a86 100644 --- a/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift +++ b/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift @@ -330,7 +330,7 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable { deletedRowIndices: Set, insertedRowIndices: Set ) -> [(statement: String, parameters: [PluginCellValue])]? { - let qualifiedTable = MSSQLSchemaQueries.qualifiedName(schema: schema, table: table) + let qualifiedTable = MSSQLSchemaQueries.qualifiedName(schema: resolvedObjectSchema(schema), table: table) var statements: [(statement: String, parameters: [PluginCellValue])] = [] var deleteChanges: [PluginRowChange] = [] @@ -598,7 +598,7 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable { sortColumns: sortColumns, columns: columns, quoteIdentifier: mssqlQuoteIdentifier ) ?? "ORDER BY (SELECT NULL)" return MSSQLSchemaQueries.browse( - schema: schema, table: table, orderByClause: orderBy, offset: offset, limit: limit + schema: resolvedObjectSchema(schema), table: table, orderByClause: orderBy, offset: offset, limit: limit ) } @@ -640,7 +640,7 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable { sortColumns: sortColumns, columns: columns, quoteIdentifier: mssqlQuoteIdentifier ) ?? "ORDER BY (SELECT NULL)" return MSSQLSchemaQueries.filtered( - schema: schema, table: table, whereClause: whereClause, + schema: resolvedObjectSchema(schema), table: table, whereClause: whereClause, orderByClause: orderBy, offset: offset, limit: limit ) } @@ -651,6 +651,14 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable { quoteIdentifier(identifier) } + private func resolvedObjectSchema(_ schema: String?) -> String? { + MSSQLSchemaQueries.resolvedObjectSchema( + schema, + currentSchema: _currentSchema, + allowCurrentSchemaFallback: freeTDSConn != nil + ) + } + private func mssqlEscapeValue(_ value: String) -> String { let trimmed = value.trimmingCharacters(in: .whitespaces) if trimmed.caseInsensitiveCompare("NULL") == .orderedSame { return "NULL" }