Skip to content

Commit 9ca16dc

Browse files
authored
Merge branch 'main' into fix/inspector-edit-grid-sync-2851
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
2 parents f8dd02a + d4aff15 commit 9ca16dc

184 files changed

Lines changed: 12664 additions & 1481 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- A line on the welcome window naming the version TablePro updated from, with a link to what changed.
1414
- What's New window, from Help > What's New.
1515
- Update install mode and check frequency in the anonymous usage heartbeat.
16+
- SQL Server alias, table and CLR types in the sidebar's **Types** section, each with a rebuilt `CREATE TYPE` statement.
1617
- OceanBase MySQL-mode connection type on the MySQL driver. (#1748)
1718
- On the Server mode for a SQLite Remote Database File, editing a database on an SSH server in place with statements run on the server. (#2831)
1819
- **System Databases and Schemas** for the sidebar tree, in View Options and Settings > General. (#2832)
1920
- Favorites and Recent sections, sorting, drag and drop into groups, inline rename and tag search tokens in the welcome window.
2021
- **File > New Group…**, **File > Rename** and **View > Sort Connections By** for the welcome window.
2122
- Favorites, Recent, nested groups, sorting and tag search tokens in the iOS connection list.
23+
- **File > Import > Import from AWS…** for RDS instances and Aurora clusters, imported as connections. (#2852)
24+
- Per-table row filter, with an optional separate target filter, and row limit in data Compare & Sync. (#2537)
25+
- Row grid for data Compare & Sync with every column shown and each differing value marked. (#2537)
2226

2327
### Changed
2428

@@ -33,11 +37,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3337
- Welcome window list moved with the arrow keys instead of `Ctrl+J`, `Ctrl+K`, `Ctrl+H` and `Ctrl+L`.
3438
- Connection switcher lists Favorites, Recent and groups at every depth.
3539
- Connection rows without colored dots, on the Mac and on iOS.
40+
- SQL Server sessions open with the ANSI SET profile the server requires, matching every other client.
41+
- Compared columns in data Compare & Sync chosen per table, and saved with each table's key, filter and row limit. (#2537)
3642

3743
### Fixed
3844

3945
- Row inspector edits missing from the grid cell and gone from the inspector on reselect. (#2851)
46+
- Empty Procedures and Functions lists on every SQL Server connection.
47+
- SQL Server rows that could not be saved on a table with a filtered index or an index on a computed column.
48+
- SQL Server CLR and extended procedures and functions missing from the Procedures and Functions lists.
49+
- SQL Server routines labelled encrypted when the account simply cannot read their source.
50+
- SQL Server version detection on a patched server, which left `CREATE OR ALTER` unused since 2016.
4051
- SQL editor jumping back while scrolling sideways near the start of a long line. (#2841)
52+
- Data sync scripts missing every UPDATE and DELETE. (#2537)
53+
- Data sync statements written to the source schema instead of the target.
54+
- Numeric-looking text such as `007` written unquoted by data sync, and key matches that hit extra rows.
55+
- Data sync pairing arbitrary rows on a key that is not unique.
56+
- Data sync inserts failing on SQL Server identity and PostgreSQL `GENERATED ALWAYS` columns.
57+
- Text columns compared as timestamps, and keys that differ only in case never synced.
58+
- Data sync scripts including tables never compared, or rows that changed after comparing.
59+
- Apply unavailable for a second sync in the same Compare & Sync window.
60+
- Choosing a source, target, mode or option during Apply cancelling the running sync.
61+
- Apply offered for a target switched to Read-Only after it was picked.
62+
- Compare & Sync reporting nothing written after a sync had written to the target.
63+
- Rolled-back data sync on MyISAM tables reported as leaving the target unchanged.
64+
- Cancelling a repeated data comparison clearing the previous results.
65+
- Table whose data comparison failed stuck included with no way to exclude it.
4166
- SSH settings dropped from a Mac connection after it synced from the iPhone app, turning off its tunnel or remote database file.
4267
- Remote database file path and access mode dropped when a connection was exported, shared as a link, or imported.
4368
- Remote database file connection hanging for minutes when its SSH connection dropped silently, with Cancel doing nothing.
@@ -94,12 +119,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
94119
- Fit to Window on an ER diagram fitting empty space left behind by a dragged table.
95120
- Query plan mode, selected step, zoom and scroll lost on a mode or editor tab switch, or zoom carried over to another plan.
96121
- Zoom Out on a diagram at 5% jumping to 1%.
122+
- An AWS profile backed by IAM Identity Center, or an assume-role chain rooted on one, failing to authenticate.
123+
- AWS SSO sign-in leaving the `aws` CLI unable to refresh its own token.
124+
- AWS SSO, STS and RDS unreachable in the China, GovCloud and secret partitions.
97125

98126
### Security
99127

100128
- SQLite denies the `fts3_tokenizer` function, which could crash the app from a crafted query on any connection.
101129
- The AI assistant refuses statements that read or write files or run server-side code (ATTACH, LOAD, VACUUM INTO), matching the MCP server. (#2831)
102130
- Remote `VACUUM INTO` snapshot created world-readable beside a database with stricter permissions.
131+
- AWS SSO access token cached world-readable in `~/.aws/sso/cache`.
103132

104133
## [0.74.0] - 2026-09-13
105134

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Foundation
2+
3+
/// Reads the two things TablePro needs out of `@@VERSION`.
4+
///
5+
/// `@@VERSION` is a multi-line banner: a first line naming the product and its build, then the
6+
/// build date, the copyright and the host OS, each on its own tab-indented line. Only the first
7+
/// line carries the `major.minor.build.revision` the feature gates read, and only the first line is
8+
/// worth showing a user.
9+
///
10+
/// Truncating the banner to a fixed prefix does not work, and shipped as a silent regression: 50
11+
/// characters of a patched SQL Server 2022 is
12+
/// `Microsoft SQL Server 2022 (RTM-CU26-GDR) (KB512276`, which cuts the build off mid-KB-number and
13+
/// leaves no `major.minor.build` to match. The major version then read as unknown on every patched
14+
/// server, so `CREATE OR ALTER` was never used even on servers that have had it since 2016. The
15+
/// cumulative-update suffix grows with every patch, so the prefix that fitted when it was written
16+
/// stops fitting later.
17+
public enum MSSQLServerBanner {
18+
/// The product line, without the build date, copyright and host OS lines under it.
19+
public static func displayText(from banner: String) -> String {
20+
let firstLine = banner.split(separator: "\n", maxSplits: 1, omittingEmptySubsequences: false)[0]
21+
return firstLine.trimmingCharacters(in: .whitespaces)
22+
}
23+
24+
/// Matches against the whole banner rather than the first line alone, so a future layout that
25+
/// moves the build number cannot silently return nil the way the fixed prefix did.
26+
public static func majorVersion(from banner: String?) -> Int? {
27+
guard let banner else { return nil }
28+
guard let regex = try? NSRegularExpression(pattern: #"(\d+)\.\d+\.\d+"#),
29+
let match = regex.firstMatch(in: banner, range: NSRange(banner.startIndex..., in: banner)),
30+
let range = Range(match.range(at: 1), in: banner)
31+
else {
32+
return nil
33+
}
34+
return Int(banner[range])
35+
}
36+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import Foundation
2+
3+
/// The session state a SQL Server connection has to be put into before anything else runs.
4+
///
5+
/// FreeTDS db-lib inherits Sybase's 1990s defaults and hands back a session with every one of
6+
/// these off. Measured on SQL Server 2022 by reading `SESSIONPROPERTY` for all seven straight
7+
/// after `dbopen`: `0 0 0 0 0 0 0`. Every other client reaches the server with the six ON options
8+
/// already on, because ODBC, OLE DB, JDBC and .NET all set them in the login packet, and
9+
/// `sqlcmd -I` does the same; db-lib is the outlier.
10+
///
11+
/// Six on and `NUMERIC_ROUNDABORT` off is the SET profile SQL Server requires for an indexed view,
12+
/// an index on a computed column, a filtered index, an XML data type method and a spatial index
13+
/// operation. Miss any one and the server answers `Msg 1934`, naming the options rather than the
14+
/// real problem. Two of them reached users. The stored procedure and function lists were empty on
15+
/// every connection, because the catalog query aggregates a parameter list through `FOR XML PATH`
16+
/// and `.value()`. And no INSERT, UPDATE or DELETE could touch a table carrying a filtered index or
17+
/// an index on a computed column, so such a row edit could never be saved. Both were measured
18+
/// failing and then succeeding on one connection, either side of this establishment.
19+
///
20+
/// `NUMERIC_ROUNDABORT` already arrives off, so setting it changes nothing on a default server. It
21+
/// is set anyway because a database or a login can carry it on, and a session that inherits it
22+
/// fails those same writes while all six ON options read correctly. Measured: with the six on and
23+
/// `NUMERIC_ROUNDABORT` on, the INSERT fails with `Msg 1934` naming `NUMERIC_ROUNDABORT` alone.
24+
///
25+
/// `ANSI_NULLS` and `CONCAT_NULL_YIELDS_NULL` also decide how the user's own SQL reads: `col = NULL`
26+
/// stops matching, and concatenating a NULL yields NULL. That is the standard behaviour, the
27+
/// behaviour every other client gets, and the behaviour Microsoft documents these options as always
28+
/// having in a future version. A session that keeps db-lib's defaults is the surprising one.
29+
///
30+
/// Statements are sent one at a time rather than as a batch, so a server that refuses one still
31+
/// receives the rest.
32+
public enum MSSQLSessionOptions {
33+
/// Required on. `ARITHABORT` is implied by `ANSI_WARNINGS` at database compatibility level 90
34+
/// and above, which is every server TDS 7.4 can reach, so it is belt and braces rather than
35+
/// load-bearing; it is set because Microsoft documents it as part of the required profile.
36+
public static let optionsRequiredOn = [
37+
"ANSI_NULLS",
38+
"ANSI_PADDING",
39+
"ANSI_WARNINGS",
40+
"ARITHABORT",
41+
"CONCAT_NULL_YIELDS_NULL",
42+
"QUOTED_IDENTIFIER"
43+
]
44+
45+
/// Required off. The one member of the profile that is not an "ON".
46+
public static let optionsRequiredOff = ["NUMERIC_ROUNDABORT"]
47+
48+
/// Every option in the profile, paired with the value the server requires.
49+
public static var requiredValues: [(name: String, isOn: Bool)] {
50+
optionsRequiredOn.map { ($0, true) } + optionsRequiredOff.map { ($0, false) }
51+
}
52+
53+
/// `TEXTSIZE` defaults to 4096 bytes on db-lib, which truncates a large text or image column
54+
/// mid-value and reads as corrupted data rather than as a limit.
55+
public static let maxTextSize = "SET TEXTSIZE \(Int32.max)"
56+
57+
public static let ansiDefaults = requiredValues
58+
.map { "SET \($0.name) \($0.isOn ? "ON" : "OFF")" }
59+
.joined(separator: " ")
60+
61+
/// Run in this order: the profile first, because a server that drops the connection while
62+
/// applying `TEXTSIZE` should not leave the session in db-lib's defaults.
63+
public static let establishment = [ansiDefaults, maxTextSize]
64+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import Foundation
2+
import Testing
3+
4+
@testable import TableProMSSQLCore
5+
6+
@Suite("MSSQL Session Options")
7+
struct MSSQLSessionOptionsTests {
8+
/// Dropping any one leaves the server refusing every XML data type method and every write to a
9+
/// table with a filtered or computed-column index, which is what emptied the procedure and
10+
/// function lists.
11+
@Test("Every option SQL Server requires on is turned on")
12+
func requiredOnOptionsAreSet() {
13+
let sql = MSSQLSessionOptions.ansiDefaults
14+
for option in [
15+
"ANSI_NULLS", "ANSI_PADDING", "ANSI_WARNINGS",
16+
"ARITHABORT", "CONCAT_NULL_YIELDS_NULL", "QUOTED_IDENTIFIER"
17+
] {
18+
#expect(sql.contains("SET \(option) ON"))
19+
}
20+
#expect(MSSQLSessionOptions.optionsRequiredOn.count == 6)
21+
}
22+
23+
/// Measured on SQL Server 2022: with all six on and this one also on, the write still fails
24+
/// with Msg 1934 naming NUMERIC_ROUNDABORT alone. It arrives off, so this only matters when a
25+
/// database or a login carries it on, which is exactly the case nothing else would catch.
26+
@Test("NUMERIC_ROUNDABORT is turned off, not on")
27+
func roundabortIsTurnedOff() {
28+
#expect(MSSQLSessionOptions.ansiDefaults.contains("SET NUMERIC_ROUNDABORT OFF"))
29+
#expect(!MSSQLSessionOptions.ansiDefaults.contains("SET NUMERIC_ROUNDABORT ON"))
30+
#expect(MSSQLSessionOptions.optionsRequiredOff == ["NUMERIC_ROUNDABORT"])
31+
}
32+
33+
@Test("The profile is seven options and no option appears in both lists")
34+
func profileIsSevenDistinctOptions() {
35+
let values = MSSQLSessionOptions.requiredValues
36+
#expect(values.count == 7)
37+
#expect(Set(values.map(\.name)).count == 7)
38+
#expect(values.filter { !$0.isOn }.map(\.name) == ["NUMERIC_ROUNDABORT"])
39+
}
40+
41+
/// db-lib defaults TEXTSIZE to 4096 bytes, which truncates a large value mid-character and
42+
/// reads as damaged data rather than as a limit.
43+
@Test("Text size is raised to the protocol maximum")
44+
func textSizeIsRaised() {
45+
#expect(MSSQLSessionOptions.maxTextSize == "SET TEXTSIZE \(Int32.max)")
46+
}
47+
48+
/// One statement per element, so a server that refuses one still receives the rest.
49+
@Test("Establishment sends the ANSI options before the text size")
50+
func establishmentOrder() {
51+
#expect(MSSQLSessionOptions.establishment == [
52+
MSSQLSessionOptions.ansiDefaults,
53+
MSSQLSessionOptions.maxTextSize
54+
])
55+
}
56+
}
57+
58+
@Suite("MSSQL Server Banner")
59+
struct MSSQLServerBannerTests {
60+
/// Measured from a live SQL Server 2022 CU26. The 50-character prefix this replaced cut the
61+
/// build off mid-KB-number, so every version gate read the server as unknown.
62+
static let sqlServer2022 = """
63+
Microsoft SQL Server 2022 (RTM-CU26-GDR) (KB5122768) - 16.0.4275.2 (X64) \n\tAug 20 2026 00:33:45 \n\tCopyright (C) 2022 Microsoft Corporation\n\tDeveloper Edition (64-bit) on Linux (Ubuntu 22.04.5 LTS) <X64>
64+
"""
65+
66+
@Test("A patched server's major version survives the banner")
67+
func patchedServerParses() {
68+
#expect(MSSQLServerBanner.majorVersion(from: Self.sqlServer2022) == 16)
69+
}
70+
71+
@Test("A fixed 50-character prefix would have lost it")
72+
func fixedPrefixLosesTheBuild() {
73+
#expect(MSSQLServerBanner.majorVersion(from: String(Self.sqlServer2022.prefix(50))) == nil)
74+
}
75+
76+
@Test("Display text is the product line alone")
77+
func displayTextIsOneLine() {
78+
let text = MSSQLServerBanner.displayText(from: Self.sqlServer2022)
79+
#expect(text == "Microsoft SQL Server 2022 (RTM-CU26-GDR) (KB5122768) - 16.0.4275.2 (X64)")
80+
#expect(!text.contains("\n"))
81+
#expect(!text.contains("Copyright"))
82+
}
83+
84+
@Test("Azure SQL Database reports its version on the first line too")
85+
func azureParses() {
86+
let banner = "Microsoft SQL Azure (RTM) - 12.0.2000.8 \n\tOct 18 2025 12:00:00 \n\tCopyright (C) 2025 Microsoft Corporation"
87+
#expect(MSSQLServerBanner.majorVersion(from: banner) == 12)
88+
#expect(MSSQLServerBanner.displayText(from: banner) == "Microsoft SQL Azure (RTM) - 12.0.2000.8")
89+
}
90+
91+
@Test("A banner with no build number reports no version rather than a wrong one")
92+
func unparsableBanner() {
93+
#expect(MSSQLServerBanner.majorVersion(from: "Microsoft SQL Server") == nil)
94+
#expect(MSSQLServerBanner.majorVersion(from: nil) == nil)
95+
}
96+
97+
@Test("A single-line banner is returned whole")
98+
func singleLineBanner() {
99+
#expect(MSSQLServerBanner.displayText(from: "Microsoft SQL Server 2019 - 15.0.1.1") == "Microsoft SQL Server 2019 - 15.0.1.1")
100+
}
101+
}

‎Plugins/BeancountDriverPlugin/Info.plist‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<plist version="1.0">
44
<dict>
55
<key>TableProPluginKitVersion</key>
6-
<integer>30</integer>
6+
<integer>31</integer>
77
<key>TableProProvidesDatabaseTypeIds</key>
88
<array>
99
<string>Beancount</string>

‎Plugins/BigQueryDriverPlugin/Info.plist‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
<key>TableProMinAppVersion</key>
66
<string>0.42.0</string>
77
<key>TableProPluginKitVersion</key>
8-
<integer>30</integer>
8+
<integer>31</integer>
99
</dict>
1010
</plist>

‎Plugins/CSVExportPlugin/Info.plist‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<plist version="1.0">
44
<dict>
55
<key>TableProPluginKitVersion</key>
6-
<integer>30</integer>
6+
<integer>31</integer>
77
<key>TableProProvidesExportFormatIds</key>
88
<array>
99
<string>csv</string>

‎Plugins/CSVImportPlugin/Info.plist‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<plist version="1.0">
44
<dict>
55
<key>TableProPluginKitVersion</key>
6-
<integer>30</integer>
6+
<integer>31</integer>
77
<key>TableProProvidesImportFormatIds</key>
88
<array>
99
<string>csv</string>

‎Plugins/CassandraDriverPlugin/Info.plist‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@
2121
<key>NSPrincipalClass</key>
2222
<string>$(PRODUCT_MODULE_NAME).CassandraPlugin</string>
2323
<key>TableProPluginKitVersion</key>
24-
<integer>30</integer>
24+
<integer>31</integer>
2525
</dict>
2626
</plist>

‎Plugins/ClickHouseDriverPlugin/Info.plist‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<plist version="1.0">
44
<dict>
55
<key>TableProPluginKitVersion</key>
6-
<integer>30</integer>
6+
<integer>31</integer>
77
<key>TableProProvidesDatabaseTypeIds</key>
88
<array>
99
<string>ClickHouse</string>

0 commit comments

Comments
 (0)