From 27de5b38ba6d7bdb0e9893ee6b4a7165ee73b747 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Sun, 30 Aug 2026 00:17:16 +0700 Subject: [PATCH] feat(datagrid): show a table's properties and let its comment be edited --- CHANGELOG.md | 2 + Plugins/MySQLDriverPlugin/MySQLPlugin.swift | 1 + .../MySQLDriverPlugin/MySQLPluginDriver.swift | 30 +- .../MySQLDriverPlugin/MySQLTableStatus.swift | 91 ++++++ .../PostgreSQLObjectQueries.swift | 17 ++ .../PostgreSQLPlugin.swift | 1 + .../PostgreSQLPluginDriver.swift | 28 +- .../PostgreSQLTableAttributes.swift | 67 +++++ Plugins/TableProPluginKit/DriverPlugin.swift | 6 + .../PluginDatabaseDriver.swift | 5 + .../PluginTableMetadata.swift | 68 +++++ .../Compare/SchemaSyncScriptBuilder.swift | 3 +- .../Core/Compare/SyncSafetyClassifier.swift | 3 +- .../Core/Plugins/PluginDriverAdapter.swift | 4 +- ...uginMetadataRegistry+CuratedDefaults.swift | 4 + ...ginMetadataRegistry+RegistryDefaults.swift | 220 +-------------- ...nMetadataRegistry+WideColumnDefaults.swift | 235 ++++++++++++++++ .../Core/Plugins/PluginMetadataRegistry.swift | 2 + .../SchemaStatementGenerator.swift | 15 + .../StructureChangeManager.swift | 63 ++++- .../Connection/DatabaseConnection.swift | 4 + TablePro/Models/Database/TableMetadata.swift | 10 +- TablePro/Models/Schema/SchemaChange.swift | 5 + TablePro/Models/Schema/StructureTab.swift | 2 + .../Main/Child/MainEditorContentView.swift | 3 +- ...MainContentCoordinator+SchemaLoading.swift | 30 ++ .../Structure/StructureEditingSession.swift | 14 +- .../Structure/StructureGridDelegate.swift | 18 +- .../StructureInspectorRowBuilder.swift | 2 +- .../Structure/StructureRowProvider.swift | 12 +- .../Views/Structure/TablePropertiesView.swift | 166 +++++++++++ .../Structure/TableStructureLoader.swift | 5 + .../TableStructureView+DataLoading.swift | 25 ++ .../Structure/TableStructureView+Schema.swift | 13 + .../Views/Structure/TableStructureView.swift | 34 ++- .../SchemaStatementGeneratorPluginTests.swift | 65 +++++ ...uctureChangeManagerTableCommentTests.swift | 183 ++++++++++++ .../Plugins/TablePropertyAttributeTests.swift | 266 ++++++++++++++++++ .../StructurePropertiesTabUITests.swift | 92 ++++++ docs/features/table-structure.mdx | 19 +- docs/images/structure-properties-dark.png | Bin 0 -> 97475 bytes docs/images/structure-properties.png | Bin 0 -> 94354 bytes project.yml | 2 + 43 files changed, 1579 insertions(+), 256 deletions(-) create mode 100644 Plugins/MySQLDriverPlugin/MySQLTableStatus.swift create mode 100644 Plugins/PostgreSQLDriverPlugin/PostgreSQLTableAttributes.swift create mode 100644 TablePro/Core/Plugins/PluginMetadataRegistry+WideColumnDefaults.swift create mode 100644 TablePro/Views/Structure/TablePropertiesView.swift create mode 100644 TableProTests/Core/SchemaTracking/StructureChangeManagerTableCommentTests.swift create mode 100644 TableProTests/Plugins/TablePropertyAttributeTests.swift create mode 100644 TableProUITests/StructurePropertiesTabUITests.swift create mode 100644 docs/images/structure-properties-dark.png create mode 100644 docs/images/structure-properties.png diff --git a/CHANGELOG.md b/CHANGELOG.md index fac3bf2fc7..70690b9d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Tab rows in Settings > General > Tabs, wrapping the strip instead of scrolling it. (#2438) - Autoscrolling while dragging a tab, so a tab can be moved past the run currently on screen. (#2438) - Move Tab to New Window on a tab's right-click menu, and by dragging a tab out of the strip. (#2438) +- Properties tab in the structure editor, with the table's owner, tablespace, storage and timestamps. (#2555) +- Editable table comment on the Properties tab for MySQL, MariaDB, PostgreSQL and PGlite. (#2555) ### Changed diff --git a/Plugins/MySQLDriverPlugin/MySQLPlugin.swift b/Plugins/MySQLDriverPlugin/MySQLPlugin.swift index 9ce414fc72..6e5380a80f 100644 --- a/Plugins/MySQLDriverPlugin/MySQLPlugin.swift +++ b/Plugins/MySQLDriverPlugin/MySQLPlugin.swift @@ -111,6 +111,7 @@ final class MySQLPlugin: NSObject, TableProPlugin, DriverPlugin { static let supportsCheckConstraints = true static let supportsCheckConstraintEditing = true static let supportsGeneratedColumns = true + static let supportsTableComment = true func createDriver(config: DriverConnectionConfig) -> any PluginDatabaseDriver { MySQLPluginDriver(config: config) diff --git a/Plugins/MySQLDriverPlugin/MySQLPluginDriver.swift b/Plugins/MySQLDriverPlugin/MySQLPluginDriver.swift index 297f2a5054..012deff036 100644 --- a/Plugins/MySQLDriverPlugin/MySQLPluginDriver.swift +++ b/Plugins/MySQLDriverPlugin/MySQLPluginDriver.swift @@ -607,25 +607,27 @@ final class MySQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable { return PluginTableMetadata(tableName: table) } - let engine = row[safe: 1]?.asText - let rowCount = (row[safe: 4]?.asText).flatMap { Int64($0) } - let dataSize = (row[safe: 6]?.asText).flatMap { Int64($0) } - let indexSize = (row[safe: 8]?.asText).flatMap { Int64($0) } - let comment = row[safe: 17]?.asText + let status = MySQLTableStatus(row: row) let totalSize: Int64? = { - guard let data = dataSize, let index = indexSize else { return nil } + guard let data = status.dataSize, let index = status.indexSize else { return nil } return data + index }() return PluginTableMetadata( tableName: table, - dataSize: dataSize, - indexSize: indexSize, + dataSize: status.dataSize, + indexSize: status.indexSize, totalSize: totalSize, - rowCount: rowCount, - comment: comment?.isEmpty == true ? nil : comment, - engine: engine + avgRowLength: status.avgRowLength, + rowCount: status.rowCount, + comment: status.comment, + engine: status.engine, + collation: status.collation, + createTime: status.createTime, + updateTime: status.updateTime, + attributes: status.attributes, + commentIsReadOnly: status.commentIsReadOnly ) } @@ -899,6 +901,12 @@ final class MySQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable { "ALTER TABLE \(quoteIdentifier(table)) DROP COLUMN \(quoteIdentifier(columnName))" } + /// MySQL has no way to unset a table comment, so clearing one writes the empty string, which is + /// what `information_schema` reports for a table that never had one. + func generateSetTableCommentSQL(table: String, comment: String?) -> String? { + "ALTER TABLE \(quoteIdentifier(table)) COMMENT = '\(escapeStringLiteral(comment ?? ""))'" + } + func generateAddIndexSQL(table: String, index: PluginIndexDefinition) -> String? { "ALTER TABLE \(quoteIdentifier(table)) ADD \(buildIndexDefinitionSQL(index))" } diff --git a/Plugins/MySQLDriverPlugin/MySQLTableStatus.swift b/Plugins/MySQLDriverPlugin/MySQLTableStatus.swift new file mode 100644 index 0000000000..08193410e3 --- /dev/null +++ b/Plugins/MySQLDriverPlugin/MySQLTableStatus.swift @@ -0,0 +1,91 @@ +// +// MySQLTableStatus.swift +// MySQLDriverPlugin +// +// Reads one `SHOW TABLE STATUS` row by its documented column order. +// + +import Foundation +import TableProPluginKit + +/// `SHOW TABLE STATUS` answers positionally, and the app used to read four of its eighteen columns +/// by hand-written index. Naming every column the Properties tab shows keeps those indexes in one +/// place instead of scattering more of them through the driver. +struct MySQLTableStatus { + let engine: String? + let rowFormat: String? + let rowCount: Int64? + let avgRowLength: Int64? + let dataSize: Int64? + let indexSize: Int64? + let autoIncrement: Int64? + let createTime: Date? + let updateTime: Date? + let collation: String? + let createOptions: String? + let comment: String? + + init(row: [PluginCellValue]) { + engine = Self.text(row, 1) + rowFormat = Self.text(row, 3) + rowCount = Self.number(row, 4) + avgRowLength = Self.number(row, 5) + dataSize = Self.number(row, 6) + indexSize = Self.number(row, 8) + autoIncrement = Self.number(row, 10) + createTime = Self.timestamp(row, 11) + updateTime = Self.timestamp(row, 12) + collation = Self.text(row, 14) + createOptions = Self.text(row, 16) + comment = Self.text(row, 17) + } + + /// `SHOW TABLE STATUS` answers for a view with every storage column NULL, `Engine` included, + /// and reports the literal `VIEW` where a table's comment would be. MySQL has no `COMMENT` form + /// for a view, so a row that names no engine is treated as one and its comment stays read-only. + var commentIsReadOnly: Bool { + engine == nil + } + + var attributes: [PluginObjectAttribute] { + var result: [PluginObjectAttribute] = [] + if let rowFormat { + result.append(PluginObjectAttribute(label: String(localized: "Row Format"), value: rowFormat)) + } + if let autoIncrement { + result.append( + PluginObjectAttribute(label: String(localized: "Auto Increment"), value: String(autoIncrement)) + ) + } + if let createOptions { + result.append(PluginObjectAttribute(label: String(localized: "Options"), value: createOptions)) + } + return result + } + + private static func text(_ row: [PluginCellValue], _ index: Int) -> String? { + guard let value = row[safe: index]?.asText, !value.isEmpty else { return nil } + return value + } + + private static func number(_ row: [PluginCellValue], _ index: Int) -> Int64? { + text(row, index).flatMap { Int64($0) } + } + + /// MySQL sends these as `YYYY-MM-DD HH:MM:SS` in the session time zone and carries no offset, + /// so the instant they name cannot be recovered from the string alone. Reading them in the + /// client's zone is deliberate: the app formats the `Date` back in that same zone, so what the + /// user reads is the wall clock the server reported. Fixing the formatter to UTC would be a + /// guess at the server's zone and would shift every displayed timestamp by that guess. + private static let timestampFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + return formatter + }() + + private static func timestamp(_ row: [PluginCellValue], _ index: Int) -> Date? { + guard let value = text(row, index) else { return nil } + return timestampFormatter.date(from: value) + } +} diff --git a/Plugins/PostgreSQLDriverPlugin/PostgreSQLObjectQueries.swift b/Plugins/PostgreSQLDriverPlugin/PostgreSQLObjectQueries.swift index c44b598e2a..f4fc2ff44e 100644 --- a/Plugins/PostgreSQLDriverPlugin/PostgreSQLObjectQueries.swift +++ b/Plugins/PostgreSQLDriverPlugin/PostgreSQLObjectQueries.swift @@ -12,6 +12,23 @@ public enum PostgreSQLObjectQueries { value.replacingOccurrences(of: "'", with: "''") } + /// A literal that means the same thing whatever `standard_conforming_strings` is set to. + /// + /// An ordinary `'...'` literal only needs its apostrophes doubled while that setting is on. + /// With it off, PostgreSQL reads backslash escapes inside one, so a backslash placed in front + /// of a doubled apostrophe consumes the first half and the second half closes the literal, and + /// whatever follows is parsed as SQL. A dollar-quoted body is not scanned for escapes at all, + /// so the setting cannot change what it means. The tag grows until it does not occur in the + /// value, which is the only way the body can end early. + public static func dollarQuoted(_ value: String) -> String { + let body = value.replacingOccurrences(of: "\0", with: "") + var tag = "tablepro" + while body.contains("$\(tag)$") { + tag += "_" + } + return "$\(tag)$\(body)$\(tag)$" + } + /// `prokind` arrived in PostgreSQL 11, which is also the first release with procedures. public static let prokindMinimumServerVersion: Int32 = 110_000 diff --git a/Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift b/Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift index 66c7a5d67e..e8fafb0a03 100644 --- a/Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift +++ b/Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift @@ -97,6 +97,7 @@ final class PostgreSQLPlugin: NSObject, TableProPlugin, DriverPlugin { static let supportsCheckConstraints = true static let supportsCheckConstraintEditing = true static let supportsGeneratedColumns = true + static let supportsTableComment = true static let sqlDialect: SQLDialectDescriptor? = PostgreSQLDialect.descriptor diff --git a/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift b/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift index a216a22f6b..1c0ee9917e 100644 --- a/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift +++ b/Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift @@ -626,9 +626,16 @@ class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { pg_table_size(c.oid) AS data_size, pg_indexes_size(c.oid) AS index_size, c.reltuples::bigint AS row_count, - obj_description(c.oid, 'pg_class') AS comment + obj_description(c.oid, 'pg_class') AS comment, + pg_get_userbyid(c.relowner) AS owner, + COALESCE(t.spcname, dt.spcname) AS tablespace, + c.relpersistence, + c.relkind FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace + LEFT JOIN pg_tablespace t ON t.oid = c.reltablespace + LEFT JOIN pg_database d ON d.datname = current_database() + LEFT JOIN pg_tablespace dt ON dt.oid = d.dattablespace WHERE c.relname = '\(escapeLiteral(table))' AND n.nspname = '\(schemaLiteral)' """ @@ -642,6 +649,7 @@ class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { let indexSize = row.count > 2 ? Int64(row[2].asText ?? "0") : nil let rowCount = row.count > 3 ? Int64(row[3].asText ?? "0") : nil let comment = row.count > 4 ? row[4].asText : nil + let relkind = row.count > 8 ? row[8].asText : nil return PluginTableMetadata( tableName: table, @@ -650,7 +658,14 @@ class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { totalSize: totalSize, rowCount: rowCount, comment: comment?.isEmpty == true ? nil : comment, - engine: "PostgreSQL" + engine: "PostgreSQL", + attributes: PostgreSQLTableAttributes.build( + owner: row.count > 5 ? row[5].asText : nil, + tablespace: row.count > 6 ? row[6].asText : nil, + persistence: row.count > 7 ? row[7].asText : nil, + relkind: relkind + ), + commentIsReadOnly: PostgreSQLTableAttributes.commentIsReadOnly(relkind: relkind) ) } @@ -1276,6 +1291,15 @@ class PostgreSQLPluginDriver: LibPQBackedDriver, @unchecked Sendable { "ALTER TABLE \(qualifiedTableName(table)) DROP COLUMN \(quoteIdentifier(columnName))" } + /// Dollar-quoted rather than `'...'`: a comment is arbitrary user text, and an ordinary literal + /// changes meaning with `standard_conforming_strings`. + func generateSetTableCommentSQL(table: String, comment: String?) -> String? { + guard let comment, !comment.isEmpty else { + return "COMMENT ON TABLE \(qualifiedTableName(table)) IS NULL" + } + return "COMMENT ON TABLE \(qualifiedTableName(table)) IS \(PostgreSQLObjectQueries.dollarQuoted(comment))" + } + func generateAddIndexSQL(table: String, index: PluginIndexDefinition) -> String? { pgIndexDefinition(index, qualifiedTable: qualifiedTableName(table)) } diff --git a/Plugins/PostgreSQLDriverPlugin/PostgreSQLTableAttributes.swift b/Plugins/PostgreSQLDriverPlugin/PostgreSQLTableAttributes.swift new file mode 100644 index 0000000000..a08dd38d84 --- /dev/null +++ b/Plugins/PostgreSQLDriverPlugin/PostgreSQLTableAttributes.swift @@ -0,0 +1,67 @@ +// +// PostgreSQLTableAttributes.swift +// PostgreSQLDriverPlugin +// +// The labelled properties the Properties tab shows for a PostgreSQL table. +// + +import Foundation +import TableProPluginKit + +/// The schema is deliberately absent: the app already knows which schema the tab is bound to and +/// labels it itself, so naming it here would print the same row twice. +enum PostgreSQLTableAttributes { + static func build( + owner: String?, + tablespace: String?, + persistence: String?, + relkind: String? + ) -> [PluginObjectAttribute] { + var attributes: [PluginObjectAttribute] = [] + if let owner, !owner.isEmpty { + attributes.append(PluginObjectAttribute(label: String(localized: "Owner"), value: owner)) + } + if let tablespace, !tablespace.isEmpty { + attributes.append(PluginObjectAttribute(label: String(localized: "Tablespace"), value: tablespace)) + } + if let label = persistenceLabel(persistence) { + attributes.append(PluginObjectAttribute(label: String(localized: "Persistence"), value: label)) + } + if let label = relkindLabel(relkind) { + attributes.append(PluginObjectAttribute(label: String(localized: "Kind"), value: label)) + } + return attributes + } + + /// `COMMENT ON TABLE` is refused on anything that is not an ordinary or partitioned table, and + /// PostgreSQL spells the rest with their own keywords (`VIEW`, `MATERIALIZED VIEW`, + /// `FOREIGN TABLE`). The app cannot tell those apart from a table, so the relation itself says + /// so here. An unreadable `relkind` is treated as read-only rather than guessed at. + static func commentIsReadOnly(relkind: String?) -> Bool { + guard let relkind else { return true } + return relkind != "r" && relkind != "p" + } + + /// `pg_class.relpersistence`, documented as p (permanent), u (unlogged) and t (temporary). + private static func persistenceLabel(_ value: String?) -> String? { + switch value { + case "p": String(localized: "Permanent") + case "u": String(localized: "Unlogged") + case "t": String(localized: "Temporary") + default: nil + } + } + + /// `pg_class.relkind`. An ordinary table is the assumption already, so only a relation that + /// differs from it is named, and an unfamiliar kind is left off rather than reported as a + /// single letter. + private static func relkindLabel(_ value: String?) -> String? { + switch value { + case "p": String(localized: "Partitioned table") + case "v": String(localized: "View") + case "m": String(localized: "Materialized view") + case "f": String(localized: "Foreign table") + default: nil + } + } +} diff --git a/Plugins/TableProPluginKit/DriverPlugin.swift b/Plugins/TableProPluginKit/DriverPlugin.swift index 7a18be51ef..737e270162 100644 --- a/Plugins/TableProPluginKit/DriverPlugin.swift +++ b/Plugins/TableProPluginKit/DriverPlugin.swift @@ -78,6 +78,11 @@ public protocol DriverPlugin: TableProPlugin { static var supportsAddIndex: Bool { get } static var supportsDropIndex: Bool { get } static var supportsModifyPrimaryKey: Bool { get } + + /// Whether the engine stores a comment on a table that can be written back. False by default so + /// a driver that has not implemented `generateSetTableCommentSQL` presents its comment read-only + /// instead of staging an edit no statement can carry. + static var supportsTableComment: Bool { get } } public extension DriverPlugin { @@ -170,4 +175,5 @@ public extension DriverPlugin { static var supportsAddIndex: Bool { true } static var supportsDropIndex: Bool { true } static var supportsModifyPrimaryKey: Bool { true } + static var supportsTableComment: Bool { false } } diff --git a/Plugins/TableProPluginKit/PluginDatabaseDriver.swift b/Plugins/TableProPluginKit/PluginDatabaseDriver.swift index 762816040c..b7fc1cb783 100644 --- a/Plugins/TableProPluginKit/PluginDatabaseDriver.swift +++ b/Plugins/TableProPluginKit/PluginDatabaseDriver.swift @@ -209,6 +209,10 @@ public protocol PluginDatabaseDriver: AnyObject, Sendable { func generateMoveColumnSQL(table: String, column: PluginColumnDefinition, afterColumn: String?) -> String? func generateCreateTableSQL(definition: PluginCreateTableDefinition) -> String? + /// A nil `comment` clears the table's comment. Returning nil means the engine has none, which + /// is what keeps the Properties tab's comment field read-only there. + func generateSetTableCommentSQL(table: String, comment: String?) -> String? + // Definition SQL for clipboard copy (optional — return nil if not supported) func generateColumnDefinitionSQL(column: PluginColumnDefinition) -> String? func generateIndexDefinitionSQL(index: PluginIndexDefinition, tableName: String?) -> String? @@ -534,6 +538,7 @@ public extension PluginDatabaseDriver { func generateModifyPrimaryKeySQL(table: String, oldColumns: [String], newColumns: [String], constraintName: String?) -> [String]? { nil } func generateMoveColumnSQL(table: String, column: PluginColumnDefinition, afterColumn: String?) -> String? { nil } func generateCreateTableSQL(definition: PluginCreateTableDefinition) -> String? { nil } + func generateSetTableCommentSQL(table: String, comment: String?) -> String? { nil } func generateColumnDefinitionSQL(column: PluginColumnDefinition) -> String? { nil } func generateIndexDefinitionSQL(index: PluginIndexDefinition, tableName: String?) -> String? { nil } diff --git a/Plugins/TableProPluginKit/PluginTableMetadata.swift b/Plugins/TableProPluginKit/PluginTableMetadata.swift index 0cd007e810..8bb642e82f 100644 --- a/Plugins/TableProPluginKit/PluginTableMetadata.swift +++ b/Plugins/TableProPluginKit/PluginTableMetadata.swift @@ -13,6 +13,53 @@ public struct PluginTableMetadata: Codable, Sendable { public let createTime: Date? public let updateTime: Date? + /// Per-engine properties the app does not model: owner, tablespace, persistence, row format. + /// The driver names and orders them, and they are rendered verbatim. + public let attributes: [PluginObjectAttribute] + + /// Whether the comment on *this* relation can be written back, as opposed to whether the engine + /// has table comments at all, which is the separate per-engine gate. + /// + /// True unless a driver says otherwise, so a driver that cannot establish the relation kind, and + /// an already-built plugin that predates this field, both present the comment read-only rather + /// than offering an edit that resolves to the wrong `COMMENT ON` keyword. PostgreSQL lowers it + /// from `pg_class.relkind`, which is the only thing separating an ordinary table from a view, a + /// materialized view or a foreign table once a tab has been opened on it. + public let commentIsReadOnly: Bool + + public init( + tableName: String, + dataSize: Int64? = nil, + indexSize: Int64? = nil, + totalSize: Int64? = nil, + avgRowLength: Int64? = nil, + rowCount: Int64? = nil, + comment: String? = nil, + engine: String? = nil, + collation: String? = nil, + createTime: Date? = nil, + updateTime: Date? = nil, + attributes: [PluginObjectAttribute], + commentIsReadOnly: Bool = true + ) { + self.tableName = tableName + self.dataSize = dataSize + self.indexSize = indexSize + self.totalSize = totalSize + self.avgRowLength = avgRowLength + self.rowCount = rowCount + self.comment = comment + self.engine = engine + self.collation = collation + self.createTime = createTime + self.updateTime = updateTime + self.attributes = attributes + self.commentIsReadOnly = commentIsReadOnly + } + + /// Kept at its exact original signature. Adding `attributes:` to it would replace the mangled + /// symbol and break every plugin already built against it. + @_disfavoredOverload public init( tableName: String, dataSize: Int64? = nil, @@ -37,5 +84,26 @@ public struct PluginTableMetadata: Codable, Sendable { self.collation = collation self.createTime = createTime self.updateTime = updateTime + self.attributes = [] + self.commentIsReadOnly = true + } + + /// Written out because Swift's synthesized `Decodable` does not fall back to an initializer's + /// default value: a payload encoded before `attributes` existed throws `keyNotFound` instead. + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + tableName = try container.decode(String.self, forKey: .tableName) + dataSize = try container.decodeIfPresent(Int64.self, forKey: .dataSize) + indexSize = try container.decodeIfPresent(Int64.self, forKey: .indexSize) + totalSize = try container.decodeIfPresent(Int64.self, forKey: .totalSize) + avgRowLength = try container.decodeIfPresent(Int64.self, forKey: .avgRowLength) + rowCount = try container.decodeIfPresent(Int64.self, forKey: .rowCount) + comment = try container.decodeIfPresent(String.self, forKey: .comment) + engine = try container.decodeIfPresent(String.self, forKey: .engine) + collation = try container.decodeIfPresent(String.self, forKey: .collation) + createTime = try container.decodeIfPresent(Date.self, forKey: .createTime) + updateTime = try container.decodeIfPresent(Date.self, forKey: .updateTime) + attributes = try container.decodeIfPresent([PluginObjectAttribute].self, forKey: .attributes) ?? [] + commentIsReadOnly = try container.decodeIfPresent(Bool.self, forKey: .commentIsReadOnly) ?? true } } diff --git a/TablePro/Core/Compare/SchemaSyncScriptBuilder.swift b/TablePro/Core/Compare/SchemaSyncScriptBuilder.swift index 25e83de1ee..a6bf4bf229 100644 --- a/TablePro/Core/Compare/SchemaSyncScriptBuilder.swift +++ b/TablePro/Core/Compare/SchemaSyncScriptBuilder.swift @@ -151,7 +151,7 @@ internal struct SchemaSyncScriptBuilder { internal enum SchemaChangeOrdering { internal static func sorted(_ changes: [SchemaChange]) -> [SchemaChange] { - var buckets: [[SchemaChange]] = Array(repeating: [], count: 10) + var buckets: [[SchemaChange]] = Array(repeating: [], count: 11) for change in changes { buckets[bucket(for: change)].append(change) } @@ -170,6 +170,7 @@ internal enum SchemaChangeOrdering { case .addIndex: return 7 case .addForeignKey: return 8 case .addCheckConstraint: return 9 + case .modifyTableComment: return 10 } } } diff --git a/TablePro/Core/Compare/SyncSafetyClassifier.swift b/TablePro/Core/Compare/SyncSafetyClassifier.swift index 9456768f9c..350bf17087 100644 --- a/TablePro/Core/Compare/SyncSafetyClassifier.swift +++ b/TablePro/Core/Compare/SyncSafetyClassifier.swift @@ -69,7 +69,8 @@ internal struct SyncSafetyClassifier { constraint.name ) )] - case .deleteForeignKey, .addForeignKey, .modifyForeignKey, .addColumn, .addIndex, .modifyIndex: + case .deleteForeignKey, .addForeignKey, .modifyForeignKey, .addColumn, .addIndex, .modifyIndex, + .modifyTableComment: return [] } } diff --git a/TablePro/Core/Plugins/PluginDriverAdapter.swift b/TablePro/Core/Plugins/PluginDriverAdapter.swift index df823305d9..87bf10adbe 100644 --- a/TablePro/Core/Plugins/PluginDriverAdapter.swift +++ b/TablePro/Core/Plugins/PluginDriverAdapter.swift @@ -410,7 +410,9 @@ final class PluginDriverAdapter: DatabaseDriver, SchemaSwitchable, DatabaseRepor engine: pluginMeta.engine, collation: pluginMeta.collation, createTime: pluginMeta.createTime, - updateTime: pluginMeta.updateTime + updateTime: pluginMeta.updateTime, + attributes: pluginMeta.attributes.map(ObjectAttribute.init), + commentIsReadOnly: pluginMeta.commentIsReadOnly ) } diff --git a/TablePro/Core/Plugins/PluginMetadataRegistry+CuratedDefaults.swift b/TablePro/Core/Plugins/PluginMetadataRegistry+CuratedDefaults.swift index fbb9ba7b55..5e9af04885 100644 --- a/TablePro/Core/Plugins/PluginMetadataRegistry+CuratedDefaults.swift +++ b/TablePro/Core/Plugins/PluginMetadataRegistry+CuratedDefaults.swift @@ -241,6 +241,7 @@ extension PluginMetadataRegistry { supportsRenameDatabase: false, supportsRenameSchema: false, supportsRenameColumn: true, + supportsTableComment: true, supportsTriggers: true, supportsTriggerEditing: true, supportsCheckConstraints: true, @@ -305,6 +306,7 @@ extension PluginMetadataRegistry { supportsRenameDatabase: false, supportsRenameSchema: false, supportsRenameColumn: true, + supportsTableComment: true, supportsTriggers: true, supportsTriggerEditing: true, supportsCheckConstraints: true, @@ -371,6 +373,7 @@ extension PluginMetadataRegistry { supportsRenameSchema: true, supportsDropSchema: true, supportsRenameColumn: true, + supportsTableComment: true, supportsTriggers: true, supportsTriggerEditing: true, supportsCheckConstraints: true, @@ -570,6 +573,7 @@ extension PluginMetadataRegistry { supportsRenameSchema: true, supportsDropSchema: true, supportsRenameColumn: true, + supportsTableComment: true, supportsTriggers: true, supportsTriggerEditing: true, supportsCheckConstraints: true, diff --git a/TablePro/Core/Plugins/PluginMetadataRegistry+RegistryDefaults.swift b/TablePro/Core/Plugins/PluginMetadataRegistry+RegistryDefaults.swift index 1c3b991e8b..0c9050a56a 100644 --- a/TablePro/Core/Plugins/PluginMetadataRegistry+RegistryDefaults.swift +++ b/TablePro/Core/Plugins/PluginMetadataRegistry+RegistryDefaults.swift @@ -839,219 +839,6 @@ extension PluginMetadataRegistry { tagline: String(localized: "Plain-text accounting ledgers") ) )), - ("Cassandra", PluginMetadataSnapshot( - displayName: "Cassandra / ScyllaDB", iconName: "cassandra-icon", defaultPort: 9_042, - requiresAuthentication: false, supportsForeignKeys: false, supportsSchemaEditing: true, - isDownloadable: true, primaryUrlScheme: "cassandra", parameterStyle: .questionMark, - navigationModel: .standard, explainVariants: [], pathFieldRole: .database, - supportsHealthMonitor: true, urlSchemes: ["cassandra", "cql", "scylladb", "scylla"], - postConnectActions: [], - brandColorHex: "#26A0D8", - queryLanguageName: "CQL", editorLanguage: .sql, - connectionMode: .network, supportsDatabaseSwitching: true, - supportsColumnReorder: false, - capabilities: PluginMetadataSnapshot.CapabilityFlags( - supportsSchemaSwitching: false, - supportsImport: false, - supportsExport: true, - supportsSSH: true, - supportsSSL: true, - supportsCascadeDrop: false, - supportsForeignKeyDisable: false, - supportsReadOnlyMode: true, - supportsQueryProgress: false, - requiresReconnectForDatabaseSwitch: false, - supportsDropDatabase: true, - supportsModifyColumn: false, - supportsAddIndex: false, - supportsDropIndex: false, - supportsModifyPrimaryKey: false, - supportsOpportunisticTLS: false, - supportsClientKeyPassphrase: true - ), - schema: PluginMetadataSnapshot.SchemaInfo( - defaultSchemaName: "public", - defaultGroupName: "default", - tableEntityName: "Tables", - containerEntityName: "Keyspace", - defaultPrimaryKeyColumn: nil, - immutableColumns: [], - systemDatabaseNames: [ - "system", "system_schema", "system_auth", - "system_distributed", "system_traces", "system_virtual_schema" - ], - systemSchemaNames: [], - fileExtensions: [], - databaseGroupingStrategy: .byDatabase, - structureColumnFields: [.name, .type, .nullable, .defaultValue, .autoIncrement, .comment] - ), - editor: PluginMetadataSnapshot.EditorConfig( - sqlDialect: cassandraDialect, - statementCompletions: [], - columnTypesByCategory: cassandraColumnTypes - ), - connection: PluginMetadataSnapshot.ConnectionConfig( - additionalConnectionFields: [ - ConnectionField( - id: "sslCaCertPath", - label: "CA Certificate", - placeholder: "/path/to/ca-cert.pem", - section: .advanced - ) - ], - category: .wideColumn, - tagline: String(localized: "Distributed wide-column store") - ) - )), - ("ScyllaDB", PluginMetadataSnapshot( - displayName: "ScyllaDB", iconName: "scylladb-icon", defaultPort: 9_042, - requiresAuthentication: false, supportsForeignKeys: false, supportsSchemaEditing: true, - isDownloadable: true, primaryUrlScheme: "scylladb", parameterStyle: .questionMark, - navigationModel: .standard, explainVariants: [], pathFieldRole: .database, - supportsHealthMonitor: true, urlSchemes: ["scylladb", "scylla"], - postConnectActions: [], - brandColorHex: "#6B2EE3", - queryLanguageName: "CQL", editorLanguage: .sql, - connectionMode: .network, supportsDatabaseSwitching: true, - supportsColumnReorder: false, - capabilities: PluginMetadataSnapshot.CapabilityFlags( - supportsSchemaSwitching: false, - supportsImport: false, - supportsExport: true, - supportsSSH: true, - supportsSSL: true, - supportsCascadeDrop: false, - supportsForeignKeyDisable: false, - supportsReadOnlyMode: true, - supportsQueryProgress: false, - requiresReconnectForDatabaseSwitch: false, - supportsDropDatabase: true, - supportsModifyColumn: false, - supportsAddIndex: false, - supportsDropIndex: false, - supportsModifyPrimaryKey: false, - supportsOpportunisticTLS: false, - supportsClientKeyPassphrase: true - ), - schema: PluginMetadataSnapshot.SchemaInfo( - defaultSchemaName: "public", - defaultGroupName: "default", - tableEntityName: "Tables", - containerEntityName: "Keyspace", - defaultPrimaryKeyColumn: nil, - immutableColumns: [], - systemDatabaseNames: [ - "system", "system_schema", "system_auth", - "system_distributed", "system_traces", "system_virtual_schema" - ], - systemSchemaNames: [], - fileExtensions: [], - databaseGroupingStrategy: .byDatabase, - structureColumnFields: [.name, .type, .nullable, .defaultValue, .autoIncrement, .comment] - ), - editor: PluginMetadataSnapshot.EditorConfig( - sqlDialect: cassandraDialect, - statementCompletions: [], - columnTypesByCategory: cassandraColumnTypes - ), - connection: PluginMetadataSnapshot.ConnectionConfig( - additionalConnectionFields: [ - ConnectionField( - id: "sslCaCertPath", - label: "CA Certificate", - placeholder: "/path/to/ca-cert.pem", - section: .advanced - ) - ], - category: .wideColumn, - tagline: String(localized: "C++ rewrite of Cassandra, faster") - ) - )), - ("etcd", PluginMetadataSnapshot( - displayName: "etcd", iconName: "etcd-icon", defaultPort: 2_379, - requiresAuthentication: false, supportsForeignKeys: false, supportsSchemaEditing: false, - isDownloadable: true, primaryUrlScheme: "etcd", parameterStyle: .questionMark, - navigationModel: .standard, explainVariants: [], pathFieldRole: .database, - supportsHealthMonitor: true, urlSchemes: ["etcd", "etcds"], postConnectActions: [], - brandColorHex: "#419EDA", - queryLanguageName: "etcdctl", editorLanguage: .bash, - connectionMode: .network, supportsDatabaseSwitching: false, - supportsColumnReorder: false, - capabilities: PluginMetadataSnapshot.CapabilityFlags( - supportsSchemaSwitching: false, - supportsImport: false, - supportsExport: true, - supportsSSH: true, - supportsSSL: true, - supportsCascadeDrop: false, - supportsForeignKeyDisable: false, - supportsReadOnlyMode: false, - supportsQueryProgress: false, - requiresReconnectForDatabaseSwitch: false, - supportsDropDatabase: false, - supportsOpportunisticTLS: false - ), - schema: PluginMetadataSnapshot.SchemaInfo( - defaultSchemaName: "public", - defaultGroupName: "main", - tableEntityName: "Keys", - containerEntityName: "Database", - defaultPrimaryKeyColumn: "Key", - immutableColumns: ["Version", "ModRevision", "CreateRevision"], - systemDatabaseNames: [], - systemSchemaNames: [], - fileExtensions: [], - databaseGroupingStrategy: .flat, - structureColumnFields: [.name, .type, .nullable] - ), - editor: PluginMetadataSnapshot.EditorConfig( - sqlDialect: nil, - statementCompletions: etcdCompletions, - columnTypesByCategory: ["String": ["string"]] - ), - connection: PluginMetadataSnapshot.ConnectionConfig( - additionalConnectionFields: [ - ConnectionField( - id: "etcdKeyPrefix", - label: String(localized: "Key Prefix Root"), - placeholder: "/", - section: .advanced - ), - ConnectionField( - id: "etcdTlsMode", - label: String(localized: "TLS Mode"), - fieldType: .dropdown(options: [ - .init(value: "Disabled", label: "Disabled"), - .init(value: "Required", label: String(localized: "Required (skip verify)")), - .init(value: "VerifyCA", label: String(localized: "Verify CA")), - .init(value: "VerifyIdentity", label: String(localized: "Verify Identity")), - ]), - section: .advanced - ), - ConnectionField( - id: "etcdCaCertPath", - label: String(localized: "CA Certificate"), - placeholder: "/path/to/ca.pem", - section: .advanced - ), - ConnectionField( - id: "etcdClientCertPath", - label: String(localized: "Client Certificate"), - placeholder: "/path/to/client.pem", - section: .advanced - ), - ConnectionField( - id: "etcdClientKeyPath", - label: String(localized: "Client Key"), - placeholder: "/path/to/client-key.pem", - section: .advanced - ), - ], - category: .coordination, - tagline: String(localized: "Distributed key-value store for service discovery"), - hidesBuiltInDatabase: true - ) - )), ("Cloudflare D1", PluginMetadataSnapshot( displayName: "Cloudflare D1", iconName: "cloudflare-d1-icon", defaultPort: 0, requiresAuthentication: true, supportsForeignKeys: true, supportsSchemaEditing: false, @@ -1201,7 +988,12 @@ extension PluginMetadataRegistry { tagline: String(localized: "Distributed SQLite by Turso") ) )), - ] + tursoPluginDefaults(dialect: d1Dialect, columnTypes: d1ColumnTypes) + ] + wideColumnPluginDefaults( + cassandraDialect: cassandraDialect, + cassandraColumnTypes: cassandraColumnTypes, + etcdCompletions: etcdCompletions + ) + + tursoPluginDefaults(dialect: d1Dialect, columnTypes: d1ColumnTypes) + cloudPluginDefaults() + elasticsearchPluginDefaults() + surrealDBPluginDefaults() + kafkaPluginDefaults() } diff --git a/TablePro/Core/Plugins/PluginMetadataRegistry+WideColumnDefaults.swift b/TablePro/Core/Plugins/PluginMetadataRegistry+WideColumnDefaults.swift new file mode 100644 index 0000000000..c8c1186437 --- /dev/null +++ b/TablePro/Core/Plugins/PluginMetadataRegistry+WideColumnDefaults.swift @@ -0,0 +1,235 @@ +// +// PluginMetadataRegistry+WideColumnDefaults.swift +// TablePro +// + +import Foundation +import TableProPluginKit + +/// Cassandra, ScyllaDB and etcd, split out of `registryPluginDefaults` so that file stays inside the +/// 1200-line limit. Cassandra and ScyllaDB are one driver behind two type ids, and etcd ships in the +/// same family of non-relational stores. +extension PluginMetadataRegistry { + // swiftlint:disable:next function_body_length + func wideColumnPluginDefaults( + cassandraDialect: SQLDialectDescriptor, + cassandraColumnTypes: [String: [String]], + etcdCompletions: [CompletionEntry] + ) -> [(typeId: String, snapshot: PluginMetadataSnapshot)] { + [ + ("Cassandra", PluginMetadataSnapshot( + displayName: "Cassandra / ScyllaDB", iconName: "cassandra-icon", defaultPort: 9_042, + requiresAuthentication: false, supportsForeignKeys: false, supportsSchemaEditing: true, + isDownloadable: true, primaryUrlScheme: "cassandra", parameterStyle: .questionMark, + navigationModel: .standard, explainVariants: [], pathFieldRole: .database, + supportsHealthMonitor: true, urlSchemes: ["cassandra", "cql", "scylladb", "scylla"], + postConnectActions: [], + brandColorHex: "#26A0D8", + queryLanguageName: "CQL", editorLanguage: .sql, + connectionMode: .network, supportsDatabaseSwitching: true, + supportsColumnReorder: false, + capabilities: PluginMetadataSnapshot.CapabilityFlags( + supportsSchemaSwitching: false, + supportsImport: false, + supportsExport: true, + supportsSSH: true, + supportsSSL: true, + supportsCascadeDrop: false, + supportsForeignKeyDisable: false, + supportsReadOnlyMode: true, + supportsQueryProgress: false, + requiresReconnectForDatabaseSwitch: false, + supportsDropDatabase: true, + supportsModifyColumn: false, + supportsAddIndex: false, + supportsDropIndex: false, + supportsModifyPrimaryKey: false, + supportsOpportunisticTLS: false, + supportsClientKeyPassphrase: true + ), + schema: PluginMetadataSnapshot.SchemaInfo( + defaultSchemaName: "public", + defaultGroupName: "default", + tableEntityName: "Tables", + containerEntityName: "Keyspace", + defaultPrimaryKeyColumn: nil, + immutableColumns: [], + systemDatabaseNames: [ + "system", "system_schema", "system_auth", + "system_distributed", "system_traces", "system_virtual_schema" + ], + systemSchemaNames: [], + fileExtensions: [], + databaseGroupingStrategy: .byDatabase, + structureColumnFields: [.name, .type, .nullable, .defaultValue, .autoIncrement, .comment] + ), + editor: PluginMetadataSnapshot.EditorConfig( + sqlDialect: cassandraDialect, + statementCompletions: [], + columnTypesByCategory: cassandraColumnTypes + ), + connection: PluginMetadataSnapshot.ConnectionConfig( + additionalConnectionFields: [ + ConnectionField( + id: "sslCaCertPath", + label: "CA Certificate", + placeholder: "/path/to/ca-cert.pem", + section: .advanced + ) + ], + category: .wideColumn, + tagline: String(localized: "Distributed wide-column store") + ) + )), + ("ScyllaDB", PluginMetadataSnapshot( + displayName: "ScyllaDB", iconName: "scylladb-icon", defaultPort: 9_042, + requiresAuthentication: false, supportsForeignKeys: false, supportsSchemaEditing: true, + isDownloadable: true, primaryUrlScheme: "scylladb", parameterStyle: .questionMark, + navigationModel: .standard, explainVariants: [], pathFieldRole: .database, + supportsHealthMonitor: true, urlSchemes: ["scylladb", "scylla"], + postConnectActions: [], + brandColorHex: "#6B2EE3", + queryLanguageName: "CQL", editorLanguage: .sql, + connectionMode: .network, supportsDatabaseSwitching: true, + supportsColumnReorder: false, + capabilities: PluginMetadataSnapshot.CapabilityFlags( + supportsSchemaSwitching: false, + supportsImport: false, + supportsExport: true, + supportsSSH: true, + supportsSSL: true, + supportsCascadeDrop: false, + supportsForeignKeyDisable: false, + supportsReadOnlyMode: true, + supportsQueryProgress: false, + requiresReconnectForDatabaseSwitch: false, + supportsDropDatabase: true, + supportsModifyColumn: false, + supportsAddIndex: false, + supportsDropIndex: false, + supportsModifyPrimaryKey: false, + supportsOpportunisticTLS: false, + supportsClientKeyPassphrase: true + ), + schema: PluginMetadataSnapshot.SchemaInfo( + defaultSchemaName: "public", + defaultGroupName: "default", + tableEntityName: "Tables", + containerEntityName: "Keyspace", + defaultPrimaryKeyColumn: nil, + immutableColumns: [], + systemDatabaseNames: [ + "system", "system_schema", "system_auth", + "system_distributed", "system_traces", "system_virtual_schema" + ], + systemSchemaNames: [], + fileExtensions: [], + databaseGroupingStrategy: .byDatabase, + structureColumnFields: [.name, .type, .nullable, .defaultValue, .autoIncrement, .comment] + ), + editor: PluginMetadataSnapshot.EditorConfig( + sqlDialect: cassandraDialect, + statementCompletions: [], + columnTypesByCategory: cassandraColumnTypes + ), + connection: PluginMetadataSnapshot.ConnectionConfig( + additionalConnectionFields: [ + ConnectionField( + id: "sslCaCertPath", + label: "CA Certificate", + placeholder: "/path/to/ca-cert.pem", + section: .advanced + ) + ], + category: .wideColumn, + tagline: String(localized: "C++ rewrite of Cassandra, faster") + ) + )), + ("etcd", PluginMetadataSnapshot( + displayName: "etcd", iconName: "etcd-icon", defaultPort: 2_379, + requiresAuthentication: false, supportsForeignKeys: false, supportsSchemaEditing: false, + isDownloadable: true, primaryUrlScheme: "etcd", parameterStyle: .questionMark, + navigationModel: .standard, explainVariants: [], pathFieldRole: .database, + supportsHealthMonitor: true, urlSchemes: ["etcd", "etcds"], postConnectActions: [], + brandColorHex: "#419EDA", + queryLanguageName: "etcdctl", editorLanguage: .bash, + connectionMode: .network, supportsDatabaseSwitching: false, + supportsColumnReorder: false, + capabilities: PluginMetadataSnapshot.CapabilityFlags( + supportsSchemaSwitching: false, + supportsImport: false, + supportsExport: true, + supportsSSH: true, + supportsSSL: true, + supportsCascadeDrop: false, + supportsForeignKeyDisable: false, + supportsReadOnlyMode: false, + supportsQueryProgress: false, + requiresReconnectForDatabaseSwitch: false, + supportsDropDatabase: false, + supportsOpportunisticTLS: false + ), + schema: PluginMetadataSnapshot.SchemaInfo( + defaultSchemaName: "public", + defaultGroupName: "main", + tableEntityName: "Keys", + containerEntityName: "Database", + defaultPrimaryKeyColumn: "Key", + immutableColumns: ["Version", "ModRevision", "CreateRevision"], + systemDatabaseNames: [], + systemSchemaNames: [], + fileExtensions: [], + databaseGroupingStrategy: .flat, + structureColumnFields: [.name, .type, .nullable] + ), + editor: PluginMetadataSnapshot.EditorConfig( + sqlDialect: nil, + statementCompletions: etcdCompletions, + columnTypesByCategory: ["String": ["string"]] + ), + connection: PluginMetadataSnapshot.ConnectionConfig( + additionalConnectionFields: [ + ConnectionField( + id: "etcdKeyPrefix", + label: String(localized: "Key Prefix Root"), + placeholder: "/", + section: .advanced + ), + ConnectionField( + id: "etcdTlsMode", + label: String(localized: "TLS Mode"), + fieldType: .dropdown(options: [ + .init(value: "Disabled", label: "Disabled"), + .init(value: "Required", label: String(localized: "Required (skip verify)")), + .init(value: "VerifyCA", label: String(localized: "Verify CA")), + .init(value: "VerifyIdentity", label: String(localized: "Verify Identity")), + ]), + section: .advanced + ), + ConnectionField( + id: "etcdCaCertPath", + label: String(localized: "CA Certificate"), + placeholder: "/path/to/ca.pem", + section: .advanced + ), + ConnectionField( + id: "etcdClientCertPath", + label: String(localized: "Client Certificate"), + placeholder: "/path/to/client.pem", + section: .advanced + ), + ConnectionField( + id: "etcdClientKeyPath", + label: String(localized: "Client Key"), + placeholder: "/path/to/client-key.pem", + section: .advanced + ), + ], + category: .coordination, + tagline: String(localized: "Distributed key-value store for service discovery"), + hidesBuiltInDatabase: true + ) + )), + ] + } +} diff --git a/TablePro/Core/Plugins/PluginMetadataRegistry.swift b/TablePro/Core/Plugins/PluginMetadataRegistry.swift index 01f6f10ab6..14a173597a 100644 --- a/TablePro/Core/Plugins/PluginMetadataRegistry.swift +++ b/TablePro/Core/Plugins/PluginMetadataRegistry.swift @@ -60,6 +60,7 @@ struct PluginMetadataSnapshot: Sendable { var supportsAddIndex: Bool = true var supportsDropIndex: Bool = true var supportsModifyPrimaryKey: Bool = true + var supportsTableComment: Bool = false var supportsTriggers: Bool = false var supportsTriggerEditing: Bool = false var supportsCheckConstraints: Bool = false @@ -581,6 +582,7 @@ final class PluginMetadataRegistry: @unchecked Sendable { supportsAddIndex: driverType.supportsAddIndex, supportsDropIndex: driverType.supportsDropIndex, supportsModifyPrimaryKey: driverType.supportsModifyPrimaryKey, + supportsTableComment: driverType.supportsTableComment, supportsTriggers: driverType.supportsTriggers, supportsTriggerEditing: driverType.supportsTriggerEditing, supportsCheckConstraints: driverType.supportsCheckConstraints, diff --git a/TablePro/Core/SchemaTracking/SchemaStatementGenerator.swift b/TablePro/Core/SchemaTracking/SchemaStatementGenerator.swift index 3c2528d2f7..0349187f20 100644 --- a/TablePro/Core/SchemaTracking/SchemaStatementGenerator.swift +++ b/TablePro/Core/SchemaTracking/SchemaStatementGenerator.swift @@ -87,6 +87,7 @@ struct SchemaStatementGenerator { var indexAdds: [SchemaChange] = [] var fkAdds: [SchemaChange] = [] var constraintAdds: [SchemaChange] = [] + var commentChanges: [SchemaChange] = [] for change in changes { switch change { @@ -121,11 +122,14 @@ struct SchemaStatementGenerator { indexAdds.append(change) case .addForeignKey: fkAdds.append(change) + case .modifyTableComment: + commentChanges.append(change) } } return constraintDeletes + constraintModifies + fkDeletes + indexDeletes + columnDeletes + columnModifies + columnAdds + pkChanges + indexAdds + fkAdds + constraintAdds + + commentChanges } // MARK: - Statement Generation @@ -158,9 +162,20 @@ struct SchemaStatementGenerator { return generateModifyCheckConstraint(old: old, new: new) case .deleteCheckConstraint(let constraint): return generateDeleteCheckConstraint(constraint).map { [$0] } ?? [] + case .modifyTableComment(_, let new): + return generateModifyTableComment(new).map { [$0] } ?? [] } } + // MARK: - Table Comment + + private func generateModifyTableComment(_ comment: String?) -> SchemaStatement? { + guard let sql = pluginDriver.generateSetTableCommentSQL(table: tableName, comment: comment) else { + return nil + } + return SchemaStatement(sql: sql, description: "Change table comment", isDestructive: false) + } + // MARK: - Column Operations private func generateAddColumn(_ column: EditableColumnDefinition) -> SchemaStatement? { diff --git a/TablePro/Core/SchemaTracking/StructureChangeManager.swift b/TablePro/Core/SchemaTracking/StructureChangeManager.swift index a64f38ba08..b72e338155 100644 --- a/TablePro/Core/SchemaTracking/StructureChangeManager.swift +++ b/TablePro/Core/SchemaTracking/StructureChangeManager.swift @@ -26,12 +26,18 @@ final class StructureChangeManager: ChangeManaging { private(set) var currentCheckConstraints: [EditableCheckConstraintDefinition] = [] private(set) var currentPrimaryKey: [String] = [] + /// Normalised to the empty string, never nil. A table with no comment and a table whose comment + /// is empty are the same table on every engine that has one, and keeping both spellings would + /// stage a change the moment the field is focused and left alone. + private(set) var currentTableComment: String = "" + // Working state (includes uncommitted changes + placeholders) var workingColumns: [EditableColumnDefinition] = [] var workingIndexes: [EditableIndexDefinition] = [] var workingForeignKeys: [EditableForeignKeyDefinition] = [] var workingCheckConstraints: [EditableCheckConstraintDefinition] = [] var workingPrimaryKey: [String] = [] + private(set) var workingTableComment: String = "" var tableName: String? @@ -141,6 +147,59 @@ final class StructureChangeManager: ChangeManaging { workingForeignKeys = currentForeignKeys workingCheckConstraints = currentCheckConstraints workingPrimaryKey = currentPrimaryKey + workingTableComment = currentTableComment + } + + /// Adopted separately from `loadSchema` because the table's comment arrives from a different + /// query than its columns. Folding it into `loadSchema` would mean re-baselining the whole + /// editor when the metadata lands, which discards every staged ALTER. + /// + /// What the user typed is left alone: they are mid-edit, and a refresh that means to replace it + /// goes through `discardChanges` first. The staged change is re-cut against the new baseline + /// even so, because it carries the value the server is being asked to move away from, and a + /// baseline that has caught up with the edit means there is nothing left to write. + func setTableCommentBaseline(_ comment: String?) { + currentTableComment = comment ?? "" + guard pendingChanges[.tableComment] != nil else { + workingTableComment = currentTableComment + return + } + restageTableComment() + } + + /// One undo step per editing episode, not per keystroke. The comment field writes on every + /// character, and a per-character entry would fill all 100 levels of undo from one paragraph and + /// evict the column edits staged beside it. The text view keeps its own undo while it has focus. + /// + /// Replaying an undo or a redo always registers, and registers the value being replaced rather + /// than the baseline. Coalescing there instead would skip the registration that becomes the + /// redo, because a staged change is exactly what an undo is undoing, and Cmd+Shift+Z would do + /// nothing after a comment edit. + func setTableComment(_ comment: String) { + guard comment != workingTableComment else { return } + let isReplaying = undoManager.isUndoing || undoManager.isRedoing + if isReplaying || pendingChanges[.tableComment] == nil { + let inverse = isReplaying ? workingTableComment : currentTableComment + registerUndo(String(localized: "Change Table Comment")) { target in + target.setTableComment(inverse) + } + } + workingTableComment = comment + restageTableComment() + } + + private func restageTableComment() { + if workingTableComment == currentTableComment { + pendingChanges.removeValue(forKey: .tableComment) + untrackChangeKey(.tableComment) + } else { + pendingChanges[.tableComment] = .modifyTableComment( + old: currentTableComment.isEmpty ? nil : currentTableComment, + new: workingTableComment.isEmpty ? nil : workingTableComment + ) + trackChangeKey(.tableComment) + } + validate() } private func trackChangeKey(_ key: SchemaChangeIdentifier) { @@ -397,7 +456,7 @@ final class StructureChangeManager: ChangeManaging { case .checkConstraints: guard row < workingCheckConstraints.count else { return } key = .checkConstraint(workingCheckConstraints[row].id) - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: return } guard pendingChanges[key]?.isDelete == true else { return } @@ -663,7 +722,7 @@ final class StructureChangeManager: ChangeManaging { return rowState(at: row, using: Self.foreignKeyOperations) case .checkConstraints: return rowState(at: row, using: Self.checkConstraintOperations) - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: return (false, false) } } diff --git a/TablePro/Models/Connection/DatabaseConnection.swift b/TablePro/Models/Connection/DatabaseConnection.swift index 92ecf4a7b4..91e92608a9 100644 --- a/TablePro/Models/Connection/DatabaseConnection.swift +++ b/TablePro/Models/Connection/DatabaseConnection.swift @@ -275,6 +275,10 @@ extension DatabaseType { var supportsModifyPrimaryKey: Bool { PluginMetadataRegistry.shared.snapshot(for: self)?.capabilities.supportsModifyPrimaryKey ?? true } + + var supportsTableComment: Bool { + PluginMetadataRegistry.shared.snapshot(for: self)?.capabilities.supportsTableComment ?? false + } } // MARK: - External Access diff --git a/TablePro/Models/Database/TableMetadata.swift b/TablePro/Models/Database/TableMetadata.swift index 288defb5d2..706a49d831 100644 --- a/TablePro/Models/Database/TableMetadata.swift +++ b/TablePro/Models/Database/TableMetadata.swift @@ -8,7 +8,7 @@ import Foundation /// Represents table-level metadata fetched from database -struct TableMetadata { +struct TableMetadata: Sendable { let tableName: String let dataSize: Int64? let indexSize: Int64? @@ -21,6 +21,14 @@ struct TableMetadata { let createTime: Date? let updateTime: Date? + /// Whatever else the driver wants to name: owner, tablespace, persistence, row format. The app + /// models none of it, so a new engine adds a property without a change here. + var attributes: [ObjectAttribute] = [] + + /// The driver's answer for this relation, not for the engine, and true unless a driver lowers + /// it: a driver that cannot establish the relation kind offers no edit at all. + var commentIsReadOnly: Bool = true + /// Format a size in bytes to human readable format static func formatSize(_ bytes: Int64?) -> String { guard let bytes = bytes else { return "—" } diff --git a/TablePro/Models/Schema/SchemaChange.swift b/TablePro/Models/Schema/SchemaChange.swift index 37d482b684..5e2207e6e2 100644 --- a/TablePro/Models/Schema/SchemaChange.swift +++ b/TablePro/Models/Schema/SchemaChange.swift @@ -28,6 +28,8 @@ enum SchemaChange: Hashable, Equatable { case modifyPrimaryKey(old: [String], new: [String]) + case modifyTableComment(old: String?, new: String?) + /// Whether this change is a deletion var isDelete: Bool { switch self { @@ -99,6 +101,8 @@ enum SchemaChange: Hashable, Equatable { return "Delete check constraint '\(constraint.name)'" case .modifyPrimaryKey(let old, let new): return "Change primary key from [\(old.joined(separator: ", "))] to [\(new.joined(separator: ", "))]" + case .modifyTableComment: + return "Change table comment" } } } @@ -110,4 +114,5 @@ enum SchemaChangeIdentifier: Hashable { case foreignKey(UUID) case checkConstraint(UUID) case primaryKey + case tableComment } diff --git a/TablePro/Models/Schema/StructureTab.swift b/TablePro/Models/Schema/StructureTab.swift index 53aba1a111..3315329030 100644 --- a/TablePro/Models/Schema/StructureTab.swift +++ b/TablePro/Models/Schema/StructureTab.swift @@ -9,6 +9,7 @@ import Foundation /// Tab selection for structure view enum StructureTab: String, CaseIterable, Hashable { + case properties case columns case indexes case foreignKeys @@ -19,6 +20,7 @@ enum StructureTab: String, CaseIterable, Hashable { var displayName: String { switch self { + case .properties: String(localized: "Properties") case .columns: String(localized: "Columns") case .indexes: String(localized: "Indexes") case .foreignKeys: String(localized: "Foreign Keys") diff --git a/TablePro/Views/Main/Child/MainEditorContentView.swift b/TablePro/Views/Main/Child/MainEditorContentView.swift index 62bad06d3a..74ee8719c9 100644 --- a/TablePro/Views/Main/Child/MainEditorContentView.swift +++ b/TablePro/Views/Main/Child/MainEditorContentView.swift @@ -664,7 +664,8 @@ struct MainEditorContentView: View { connection: connection, databaseName: scope?.database ?? "", schemaName: scope?.schema, - tableName: tableName + tableName: tableName, + isView: tab.tableContext.isView ) } } diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+SchemaLoading.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+SchemaLoading.swift index 80c6294247..ab725a4e62 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+SchemaLoading.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+SchemaLoading.swift @@ -140,6 +140,36 @@ extension MainContentCoordinator { return entry.tableName == tableName && entry.lastExecutedAt == tab.execution.lastExecutedAt } + /// Replaces what the Table Info panel holds for a table whose metadata changed without a query. + /// + /// `hasCurrentTableMetadata` stamps its cache with the tab's last execution, which is the right + /// test while only a query can move the numbers. Editing a table's comment moves one of them + /// without executing anything, so the panel would go on showing the comment the save replaced. + /// The fresh value is written in rather than the stale one dropped, because dropping it leaves + /// the panel empty until the tab is executed again. + /// + /// Matched on the whole scope, never on the bare name. The cache is keyed by tab and two tabs + /// can hold an `orders` in different schemas or different databases, so saving one would + /// otherwise write its owner, sizes, row count and comment over the other, under a stamp that + /// still reads as current. + func adoptTableMetadata(_ metadata: TableMetadata, in scope: DatabaseScope) { + for tab in tabManager.tabs { + guard tab.tableContext.tableName == metadata.tableName, + self.scope(for: tab) == scope, + let entry = tableMetadataCache[tab.id], + entry.tableName == metadata.tableName + else { continue } + tableMetadataCache[tab.id] = TableMetadataCacheEntry( + tableName: entry.tableName, + lastExecutedAt: entry.lastExecutedAt, + metadata: metadata + ) + if tabManager.selectedTabId == tab.id { + tableMetadata = metadata + } + } + } + func loadTableMetadata(tableName: String, for tab: QueryTab) async { if let entry = tableMetadataCache[tab.id], entry.tableName == tableName, diff --git a/TablePro/Views/Structure/StructureEditingSession.swift b/TablePro/Views/Structure/StructureEditingSession.swift index f8919440c4..9d1e935e06 100644 --- a/TablePro/Views/Structure/StructureEditingSession.swift +++ b/TablePro/Views/Structure/StructureEditingSession.swift @@ -43,6 +43,11 @@ internal final class StructureEditingSession { internal let schemaName: String? internal let tableName: String + /// Whether this tab was opened on a view rather than a table. The Properties tab reads it to + /// keep the comment read-only: clearing or setting one on a view is `COMMENT ON VIEW`, and the + /// tab carries no object type finer than this flag to tell a view from a materialized one. + internal let isView: Bool + internal let changeManager = StructureChangeManager() /// Built here, not seeded into the view's `@State`. `State(wrappedValue:)` runs only the first @@ -63,6 +68,11 @@ internal final class StructureEditingSession { internal var checkConstraints: [CheckConstraintInfo] = [] internal var triggers: [TriggerInfo] = [] internal var ddlStatement: String = "" + internal var tableMetadata: TableMetadata? + + /// Held apart from the view's `errorMessage`, which replaces every sub-tab. A catalog the + /// properties query cannot read stops that one tab, not the whole structure editor. + internal var tableMetadataError: String? internal var tabData = StructureTabDataState() /// Where the user was. Held here rather than in the view because two tabs on one table are two @@ -104,13 +114,15 @@ internal final class StructureEditingSession { connection: DatabaseConnection, databaseName: String, schemaName: String?, - tableName: String + tableName: String, + isView: Bool = false ) { self.identity = identity self.connection = connection self.databaseName = databaseName self.schemaName = schemaName self.tableName = tableName + self.isView = isView gridDelegate = StructureGridDelegate( structureChangeManager: changeManager, selectedTab: .columns, diff --git a/TablePro/Views/Structure/StructureGridDelegate.swift b/TablePro/Views/Structure/StructureGridDelegate.swift index 1d9539faf9..0551d6acd8 100644 --- a/TablePro/Views/Structure/StructureGridDelegate.swift +++ b/TablePro/Views/Structure/StructureGridDelegate.swift @@ -101,7 +101,7 @@ final class StructureGridDelegate: DataGridViewDelegate { StructureEditingSupport.updateCheckConstraint(&constraint, at: column, with: newValue ?? "") structureChangeManager.updateCheckConstraint(id: constraint.id, with: constraint) - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: break } @@ -170,7 +170,7 @@ final class StructureGridDelegate: DataGridViewDelegate { structureChangeManager.deleteCheckConstraint(id: constraint.id) } } - case .parts, .ddl, .triggers: + case .properties, .parts, .ddl, .triggers: onSelectedRowsChanged?([]) return } @@ -222,7 +222,7 @@ final class StructureGridDelegate: DataGridViewDelegate { guard row < structureChangeManager.workingCheckConstraints.count else { continue } copiedItems.append(structureChangeManager.workingCheckConstraints[row]) } - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: break } @@ -315,7 +315,7 @@ final class StructureGridDelegate: DataGridViewDelegate { structureChangeManager.addCheckConstraint(item.withNewIdentity()) } - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: break } } @@ -350,7 +350,7 @@ final class StructureGridDelegate: DataGridViewDelegate { case .checkConstraints: guard connection.type.supportsCheckConstraintEditing else { return } structureChangeManager.addNewCheckConstraint() - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: break } } @@ -404,7 +404,7 @@ final class StructureGridDelegate: DataGridViewDelegate { guard let original = structureChangeManager.currentCheckConstraints .first(where: { $0.id == working.id }) else { return [] } return StructureEditingSupport.checkConstraintModifiedIndices(old: original, new: working) - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: return [] } } @@ -498,7 +498,7 @@ final class StructureGridDelegate: DataGridViewDelegate { case .checkConstraints: guard connection.type.supportsCheckConstraintEditing else { return nil } label = String(localized: "Add Check Constraint") - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: return nil } @@ -551,7 +551,7 @@ final class StructureGridDelegate: DataGridViewDelegate { let constraint = structureChangeManager.workingCheckConstraints[row] let quoted = driver.quoteIdentifier(constraint.name) definitions.append("CONSTRAINT \(quoted) CHECK (\(constraint.expression))") - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: break } } @@ -640,7 +640,7 @@ final class StructureGridDelegate: DataGridViewDelegate { row < structureChangeManager.workingCheckConstraints.count else { continue } let copy = structureChangeManager.workingCheckConstraints[row] structureChangeManager.addCheckConstraint(copy.withNewIdentity()) - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: break } } diff --git a/TablePro/Views/Structure/StructureInspectorRowBuilder.swift b/TablePro/Views/Structure/StructureInspectorRowBuilder.swift index 77fd82254f..e9f9fb5900 100644 --- a/TablePro/Views/Structure/StructureInspectorRowBuilder.swift +++ b/TablePro/Views/Structure/StructureInspectorRowBuilder.swift @@ -20,7 +20,7 @@ internal enum StructureInspectorRowBuilder { switch tab { case .columns, .indexes, .foreignKeys, .checkConstraints: break - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: return nil } diff --git a/TablePro/Views/Structure/StructureRowProvider.swift b/TablePro/Views/Structure/StructureRowProvider.swift index e898c22e33..4092e1432b 100644 --- a/TablePro/Views/Structure/StructureRowProvider.swift +++ b/TablePro/Views/Structure/StructureRowProvider.swift @@ -72,7 +72,7 @@ final class StructureRowProvider { String(localized: "Expression"), String(localized: "Columns") ] - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: return [] } } @@ -94,7 +94,7 @@ final class StructureRowProvider { return [3] case .foreignKeys, .checkConstraints: return [] - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: return [] } } @@ -120,7 +120,7 @@ final class StructureRowProvider { result[index] = Self.generationOptions } return result - case .checkConstraints, .ddl, .parts, .triggers: + case .properties, .checkConstraints, .ddl, .parts, .triggers: return [:] } } @@ -138,7 +138,7 @@ final class StructureRowProvider { case .columns: if let i = orderedColumnFields.firstIndex(of: .type) { return [i] } return [] - case .indexes, .foreignKeys, .checkConstraints, .ddl, .parts, .triggers: + case .properties, .indexes, .foreignKeys, .checkConstraints, .ddl, .parts, .triggers: return [] } } @@ -234,7 +234,7 @@ final class StructureRowProvider { return nil } return Self.row(for: original) - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: return nil } } @@ -268,7 +268,7 @@ final class StructureRowProvider { return changeManager.workingCheckConstraints.enumerated().map { index, constraint in IndexedRow(sourceIndex: index, row: row(for: constraint)) } - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: return [] } } diff --git a/TablePro/Views/Structure/TablePropertiesView.swift b/TablePro/Views/Structure/TablePropertiesView.swift new file mode 100644 index 0000000000..4cfee3752a --- /dev/null +++ b/TablePro/Views/Structure/TablePropertiesView.swift @@ -0,0 +1,166 @@ +// +// TablePropertiesView.swift +// TablePro +// + +import SwiftUI + +/// The table's own identity, its driver-supplied properties, and its comment. +/// +/// The comment is the only editable field, and it stages into the same `StructureChangeManager` the +/// Columns grid writes to, so it saves, discards and undoes through the controls the rest of the +/// Structure tab already uses. +struct TablePropertiesView: View { + let tableName: String + let schemaName: String? + let databaseName: String + let metadata: TableMetadata? + let loadError: String? + let isLoading: Bool + let isView: Bool + let isCommentEditable: Bool + let comment: String + let onCommentChange: (String) -> Void + + private var themeEngine: ThemeEngine { ThemeEngine.shared } + + var body: some View { + if let loadError, metadata == nil { + ContentUnavailableView( + String(localized: "Properties Unavailable"), + systemImage: "exclamationmark.triangle", + description: Text(loadError) + ) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else if isLoading, metadata == nil { + ProgressView() + .controlSize(.small) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else { + form + } + } + + private var form: some View { + Form { + Section { + LabeledContent(String(localized: "Name"), value: tableName) + if let schemaName, !schemaName.isEmpty { + LabeledContent(String(localized: "Schema"), value: schemaName) + } + if !databaseName.isEmpty { + LabeledContent(String(localized: "Database"), value: databaseName) + } + ForEach(metadata?.attributes ?? []) { attribute in + LabeledContent(attribute.label, value: attribute.value) + } + } header: { + Text("GENERAL") + } + + commentSection + + if let metadata { + statisticsSection(metadata) + storageSection(metadata) + timestampsSection(metadata) + } + } + .formStyle(.grouped) + .textSelection(.enabled) + } + + /// A comment the engine cannot store, and an empty one, are the same absence, so the read-only + /// case falls back to a line of text rather than an empty box nothing can ever fill. + @ViewBuilder + private var commentSection: some View { + Section { + if !isCommentEditable, comment.isEmpty { + Text(unavailableCommentMessage) + .foregroundStyle(.secondary) + } else { + TextValueEditor( + text: Binding(get: { comment }, set: onCommentChange), + isEditable: isCommentEditable, + font: themeEngine.valueFont, + borderType: .bezelBorder + ) + .frame(minHeight: 140) + .accessibilityIdentifier("table-comment-editor") + .accessibilityLabel(Text("Table comment")) + } + } header: { + Text("COMMENT") + } + } + + /// Two different absences. The engine has no comment to store at all, or it has one for tables + /// and spells this relation's with a keyword the structure editor cannot ask for. + private var unavailableCommentMessage: String { + isView || metadata?.commentIsReadOnly == true + ? String(localized: "This object's comment is read-only. Run COMMENT ON in the editor to change it.") + : String(localized: "This database does not store a comment on a table.") + } + + @ViewBuilder + private func statisticsSection(_ metadata: TableMetadata) -> some View { + if metadata.rowCount != nil || metadata.avgRowLength != nil { + Section { + if let rows = metadata.rowCount { + LabeledContent(String(localized: "Rows"), value: rows.formatted()) + } + if let avgLength = metadata.avgRowLength { + LabeledContent( + String(localized: "Avg Row"), + value: TableMetadata.formatSize(avgLength)) + } + } header: { + Text("STATISTICS") + } + } + } + + @ViewBuilder + private func storageSection(_ metadata: TableMetadata) -> some View { + Section { + LabeledContent( + String(localized: "Data Size"), + value: TableMetadata.formatSize(metadata.dataSize)) + LabeledContent( + String(localized: "Index Size"), + value: TableMetadata.formatSize(metadata.indexSize)) + LabeledContent( + String(localized: "Total Size"), + value: TableMetadata.formatSize(metadata.totalSize)) + if let engine = metadata.engine { + LabeledContent(String(localized: "Engine"), value: engine) + } + if let collation = metadata.collation { + LabeledContent(String(localized: "Collation"), value: collation) + .help(collation) + } + } header: { + Text("STORAGE") + } + } + + @ViewBuilder + private func timestampsSection(_ metadata: TableMetadata) -> some View { + if metadata.createTime != nil || metadata.updateTime != nil { + Section { + if let created = metadata.createTime { + LabeledContent( + String(localized: "Created"), + value: created.formatted(date: .numeric, time: .shortened)) + } + if let updated = metadata.updateTime { + LabeledContent( + String(localized: "Updated"), + value: updated.formatted(date: .numeric, time: .shortened)) + } + } header: { + Text("TIMESTAMPS") + } + } + } +} diff --git a/TablePro/Views/Structure/TableStructureLoader.swift b/TablePro/Views/Structure/TableStructureLoader.swift index bb4469f9d8..ddd9d8cf14 100644 --- a/TablePro/Views/Structure/TableStructureLoader.swift +++ b/TablePro/Views/Structure/TableStructureLoader.swift @@ -59,6 +59,11 @@ struct TableStructureLoader { return try await perform { try await $0.fetchTriggers(table: table) } } + func metadata() async throws -> TableMetadata { + let table = tableName + return try await perform { try await $0.fetchTableMetadata(tableName: table) } + } + func coreTabs(includingForeignKeys: Bool) async throws -> CoreTabs { let table = tableName return try await perform { driver in diff --git a/TablePro/Views/Structure/TableStructureView+DataLoading.swift b/TablePro/Views/Structure/TableStructureView+DataLoading.swift index 8116aab9f7..c28760788c 100644 --- a/TablePro/Views/Structure/TableStructureView+DataLoading.swift +++ b/TablePro/Views/Structure/TableStructureView+DataLoading.swift @@ -59,6 +59,25 @@ extension TableStructureView { func fetchTabData(_ tab: StructureTab) async { do { switch tab { + /// Kept off `errorMessage`, which replaces the whole structure editor: a catalog this + /// one query cannot read must not take Columns, Indexes and DDL down with it. + /// + /// A failure keeps the last good snapshot and returns without marking the tab fetched, + /// so the next visit tries again instead of leaving a table that was reading correctly + /// stuck on an error, which is the rule the rest of the app's caches already follow. + case .properties: + do { + let metadata = try await structureLoader.metadata() + session.tableMetadata = metadata + session.tableMetadataError = nil + structureChangeManager.setTableCommentBaseline(metadata.comment) + } catch { + Self.logger.error( + "Failed to load table properties: \(error.localizedDescription, privacy: .public)" + ) + session.tableMetadataError = error.localizedDescription + return + } case .columns: columns = try await structureLoader.columns() case .indexes: @@ -189,6 +208,12 @@ extension TableStructureView { tabData.markAllStale() partsReloadToken += 1 await reloadCoreTabs() + /// Only for a tab that has already paid for it. Several drivers answer `fetchTableMetadata` + /// with a full scan, so refreshing Properties for a table nobody opened it on would put that + /// scan on every Cmd+R. + if tabData.hasData(.properties) { + await fetchTabData(.properties) + } if selectedTab == .ddl { await fetchTabData(.ddl) } diff --git a/TablePro/Views/Structure/TableStructureView+Schema.swift b/TablePro/Views/Structure/TableStructureView+Schema.swift index 74704b3c32..bbffaa98a6 100644 --- a/TablePro/Views/Structure/TableStructureView+Schema.swift +++ b/TablePro/Views/Structure/TableStructureView+Schema.swift @@ -56,6 +56,19 @@ extension TableStructureView { func refreshAfterApply() async { isReloadingAfterSave = true await reloadCoreTabs() + /// Ahead of `loadSchemaForEditing`, and unconditional: a comment change can be saved from + /// any sub-tab, and the baseline it was staged against is stale the moment it lands. Leaving + /// it to the next visit would show the old comment back in the field until then. + /// Only where Properties was opened, which is the only way a comment can have been staged. + /// The refetched value is handed to the inspector too: a comment moves without a query, and + /// the Table Info panel's cache is stamped with the tab's last execution, so nothing else + /// would ever tell it what the save changed. + if tabData.hasData(.properties) { + await fetchTabData(.properties) + if let metadata = session.tableMetadata { + coordinator?.adoptTableMetadata(metadata, in: scope) + } + } loadSchemaForEditing() await loadTabDataIfNeeded(selectedTab) diff --git a/TablePro/Views/Structure/TableStructureView.swift b/TablePro/Views/Structure/TableStructureView.swift index 20bdb121e1..cb5ba77e88 100644 --- a/TablePro/Views/Structure/TableStructureView.swift +++ b/TablePro/Views/Structure/TableStructureView.swift @@ -257,6 +257,19 @@ struct TableStructureView: View { // MARK: - Toolbar + /// A view's comment is `COMMENT ON VIEW` on the engines that have one, and a structure tab knows + /// only that its object is a view, not whether it is a materialized one, so views stay read-only + /// rather than being offered an edit that would run the wrong statement. + private var isTableCommentEditable: Bool { + guard connection.type.supportsSchemaEditing, connection.type.supportsTableComment else { return false } + /// Properties can be selected while the opening fetch is still working through the other + /// sub-tabs, and `loadSchemaForEditing` at the end of it re-baselines the change manager, + /// which drops every staged edit including a comment typed in the meantime. + guard !isInitialLoading, !isReloadingAfterSave else { return false } + guard let metadata = session.tableMetadata else { return false } + return !session.isView && !metadata.commentIsReadOnly + } + private var availableTabs: [StructureTab] { var tabs = StructureTab.allCases if !connection.type.supportsForeignKeys { @@ -318,7 +331,7 @@ struct TableStructureView: View { case .indexes: return connection.type.supportsAddIndex case .foreignKeys: return connection.type.supportsForeignKeys case .checkConstraints: return connection.type.supportsCheckConstraintEditing - case .ddl, .parts, .triggers: return false + case .properties, .ddl, .parts, .triggers: return false } } @@ -329,7 +342,7 @@ struct TableStructureView: View { case .indexes: return connection.type.supportsDropIndex case .foreignKeys: return connection.type.supportsForeignKeys case .checkConstraints: return connection.type.supportsCheckConstraintEditing - case .ddl, .parts, .triggers: return false + case .properties, .ddl, .parts, .triggers: return false } } @@ -343,7 +356,7 @@ struct TableStructureView: View { return (String(localized: "Add Foreign Key"), String(localized: "Remove Foreign Key")) case .checkConstraints: return (String(localized: "Add Check Constraint"), String(localized: "Remove Check Constraint")) - case .ddl, .parts, .triggers: + case .properties, .ddl, .parts, .triggers: return nil } } @@ -362,7 +375,7 @@ struct TableStructureView: View { case .foreignKeys: return foreignKeys.count case .triggers: return triggers.count case .checkConstraints: return checkConstraints.count - case .ddl, .parts: return nil + case .properties, .ddl, .parts: return nil } } @@ -380,6 +393,19 @@ struct TableStructureView: View { @ViewBuilder private var tabContent: some View { switch selectedTab { + case .properties: + TablePropertiesView( + tableName: tableName, + schemaName: schemaName, + databaseName: databaseName, + metadata: session.tableMetadata, + loadError: session.tableMetadataError, + isLoading: !tabData.hasData(.properties), + isView: session.isView, + isCommentEditable: isTableCommentEditable, + comment: structureChangeManager.workingTableComment, + onCommentChange: { structureChangeManager.setTableComment($0) } + ) case .columns: structureGrid case .indexes: diff --git a/TableProTests/Core/SchemaTracking/SchemaStatementGeneratorPluginTests.swift b/TableProTests/Core/SchemaTracking/SchemaStatementGeneratorPluginTests.swift index 6a1b120804..da608cbe82 100644 --- a/TableProTests/Core/SchemaTracking/SchemaStatementGeneratorPluginTests.swift +++ b/TableProTests/Core/SchemaTracking/SchemaStatementGeneratorPluginTests.swift @@ -21,6 +21,7 @@ private final class MockPluginDriver: PluginDatabaseDriver, @unchecked Sendable var addForeignKeyHandler: ((String, PluginForeignKeyDefinition) -> String?)? var dropForeignKeyHandler: ((String, String) -> String?)? var modifyPrimaryKeyHandler: ((String, [String], [String]) -> [String]?)? + var setTableCommentHandler: ((String, String?) -> String?)? // MARK: - DDL Schema Generation @@ -56,6 +57,10 @@ private final class MockPluginDriver: PluginDatabaseDriver, @unchecked Sendable modifyPrimaryKeyHandler?(table, oldColumns, newColumns) } + func generateSetTableCommentSQL(table: String, comment: String?) -> String? { + setTableCommentHandler?(table, comment) + } + // MARK: - Required Protocol Stubs func connect() async throws {} @@ -492,4 +497,64 @@ struct SchemaStatementGeneratorPluginTests { #expect(stmts.isEmpty) } + + // MARK: - Table Comment + + @Test("Table comment throws when the engine has none") + func tableCommentThrowsWhenNil() throws { + let mock = MockPluginDriver() + let generator = SchemaStatementGenerator(tableName: "users", pluginDriver: mock) + + #expect(throws: (any Error).self) { + _ = try generator.generate(changes: [.modifyTableComment(old: nil, new: "hello")]) + } + } + + @Test("Table comment uses plugin SQL and is not destructive") + func tableCommentPluginOverride() throws { + let mock = MockPluginDriver() + mock.setTableCommentHandler = { table, comment in + "COMMENT ON TABLE \(table) IS '\(comment ?? "")'" + } + + let generator = SchemaStatementGenerator(tableName: "users", pluginDriver: mock) + let stmts = try generator.generate(changes: [.modifyTableComment(old: nil, new: "hello")]) + + #expect(stmts.count == 1) + #expect(stmts[0].sql == "COMMENT ON TABLE users IS 'hello';") + #expect(stmts[0].isDestructive == false) + } + + @Test("Clearing a table comment passes nil to the plugin") + func tableCommentClearPassesNil() throws { + let mock = MockPluginDriver() + var received: String?? + mock.setTableCommentHandler = { _, comment in + received = .some(comment) + return "COMMENT ON TABLE users IS NULL" + } + + let generator = SchemaStatementGenerator(tableName: "users", pluginDriver: mock) + _ = try generator.generate(changes: [.modifyTableComment(old: "old", new: nil)]) + + #expect(received == .some(nil)) + } + + /// A comment cannot reference a column, so it is safe last and only last: run before a column + /// drop it would still succeed, but the ordering that matters is that nothing waits on it. + @Test("Table comment is ordered after every structural change") + func tableCommentOrderedLast() throws { + let mock = MockPluginDriver() + mock.setTableCommentHandler = { _, _ in "SET COMMENT" } + mock.addColumnHandler = { table, col in "ALTER TABLE \(table) ADD \(col.name)" } + + let generator = SchemaStatementGenerator(tableName: "users", pluginDriver: mock) + let stmts = try generator.generate(changes: [ + .modifyTableComment(old: nil, new: "hello"), + .addColumn(makeColumn()) + ]) + + #expect(stmts.count == 2) + #expect(stmts[1].sql == "SET COMMENT;") + } } diff --git a/TableProTests/Core/SchemaTracking/StructureChangeManagerTableCommentTests.swift b/TableProTests/Core/SchemaTracking/StructureChangeManagerTableCommentTests.swift new file mode 100644 index 0000000000..bb8df45155 --- /dev/null +++ b/TableProTests/Core/SchemaTracking/StructureChangeManagerTableCommentTests.swift @@ -0,0 +1,183 @@ +// +// StructureChangeManagerTableCommentTests.swift +// TableProTests +// +// Staging, undo and baselining for the table comment the Properties tab edits. +// + +import Foundation +import TableProPluginKit +import Testing +@testable import TablePro + +@Suite("Structure Change Manager Table Comment") +struct StructureChangeManagerTableCommentTests { + + @MainActor private func makeManager(baseline: String?) -> StructureChangeManager { + let manager = StructureChangeManager() + manager.loadSchema( + tableName: "users", + columns: [], + indexes: [], + foreignKeys: [], + primaryKey: [] + ) + manager.setTableCommentBaseline(baseline) + return manager + } + + @Test("A missing comment baselines to the empty string") + @MainActor func nilBaselineIsEmpty() { + let manager = makeManager(baseline: nil) + + #expect(manager.currentTableComment == "") + #expect(manager.workingTableComment == "") + #expect(manager.hasChanges == false) + } + + @Test("Editing the comment stages one change") + @MainActor func editStagesChange() { + let manager = makeManager(baseline: "old") + manager.setTableComment("new") + + #expect(manager.workingTableComment == "new") + #expect(manager.hasChanges) + #expect(manager.getChangesArray() == [.modifyTableComment(old: "old", new: "new")]) + } + + @Test("Clearing the comment stages a nil replacement") + @MainActor func clearStagesNil() { + let manager = makeManager(baseline: "old") + manager.setTableComment("") + + #expect(manager.getChangesArray() == [.modifyTableComment(old: "old", new: nil)]) + } + + @Test("Typing back to the baseline drops the staged change") + @MainActor func returningToBaselineClearsChange() { + let manager = makeManager(baseline: "old") + manager.setTableComment("new") + manager.setTableComment("old") + + #expect(manager.hasChanges == false) + #expect(manager.getChangesArray().isEmpty) + } + + @Test("Undo reverts the comment and its staged change") + @MainActor func undoRevertsComment() { + let manager = makeManager(baseline: "old") + manager.setTableComment("new") + + #expect(manager.canUndo) + manager.undo() + + #expect(manager.workingTableComment == "old") + #expect(manager.hasChanges == false) + } + + /// The field writes on every character, so a per-keystroke entry would fill all 100 levels of + /// undo from one paragraph and evict the column edits staged beside it. + @Test("Typing many characters leaves one undo step") + @MainActor func typingCoalescesIntoOneUndoStep() { + let manager = makeManager(baseline: "") + for text in ["h", "he", "hel", "hell", "hello"] { + manager.setTableComment(text) + } + + manager.undo() + + #expect(manager.workingTableComment == "") + #expect(manager.hasChanges == false) + #expect(manager.canUndo == false) + } + + @Test("Redo puts the comment back") + @MainActor func redoRestoresComment() { + let manager = makeManager(baseline: "old") + manager.setTableComment("new") + manager.undo() + + #expect(manager.canRedo) + manager.redo() + + #expect(manager.workingTableComment == "new") + #expect(manager.getChangesArray() == [.modifyTableComment(old: "old", new: "new")]) + } + + @Test("Undo and redo alternate more than once") + @MainActor func undoRedoAlternates() { + let manager = makeManager(baseline: "old") + manager.setTableComment("new") + + for _ in 0..<3 { + manager.undo() + #expect(manager.workingTableComment == "old") + manager.redo() + #expect(manager.workingTableComment == "new") + } + + #expect(manager.hasChanges) + } + + @Test("Discarding reverts the comment to its baseline") + @MainActor func discardRevertsComment() { + let manager = makeManager(baseline: "old") + manager.setTableComment("new") + manager.discardChanges() + + #expect(manager.workingTableComment == "old") + #expect(manager.hasChanges == false) + } + + /// A fresh baseline arrives after every save and every refresh, and it must not overwrite what + /// the user is typing. Only an unstaged field follows the database. + @Test("A new baseline leaves a staged edit alone") + @MainActor func baselineDoesNotClobberStagedEdit() { + let manager = makeManager(baseline: "old") + manager.setTableComment("mine") + manager.setTableCommentBaseline("theirs") + + #expect(manager.workingTableComment == "mine") + #expect(manager.getChangesArray() == [.modifyTableComment(old: "theirs", new: "mine")]) + } + + /// A refresh that finds the server already holding what the user typed leaves nothing to write, + /// so the staged change goes rather than saving a statement that changes nothing. + @Test("A baseline that catches up with the edit drops the staged change") + @MainActor func baselineMatchingEditDropsChange() { + let manager = makeManager(baseline: "old") + manager.setTableComment("mine") + manager.setTableCommentBaseline("mine") + + #expect(manager.workingTableComment == "mine") + #expect(manager.hasChanges == false) + } + + @Test("A new baseline moves an unstaged field") + @MainActor func baselineMovesUnstagedField() { + let manager = makeManager(baseline: "old") + manager.setTableCommentBaseline("fresh") + + #expect(manager.workingTableComment == "fresh") + #expect(manager.hasChanges == false) + } + + /// `loadSchema` is the "adopt a new baseline" entry point and clears every staged edit. The + /// comment has to travel with the rest rather than surviving as an orphan pending change. + @Test("Reloading the schema resets the comment with everything else") + @MainActor func loadSchemaResetsComment() { + let manager = makeManager(baseline: "old") + manager.setTableComment("new") + + manager.loadSchema( + tableName: "users", + columns: [], + indexes: [], + foreignKeys: [], + primaryKey: [] + ) + + #expect(manager.workingTableComment == "old") + #expect(manager.hasChanges == false) + } +} diff --git a/TableProTests/Plugins/TablePropertyAttributeTests.swift b/TableProTests/Plugins/TablePropertyAttributeTests.swift new file mode 100644 index 0000000000..36af18546e --- /dev/null +++ b/TableProTests/Plugins/TablePropertyAttributeTests.swift @@ -0,0 +1,266 @@ +// +// TablePropertyAttributeTests.swift +// TableProTests +// +// The driver-supplied properties the Properties tab renders verbatim. +// + +import Foundation +import TableProPluginKit +import Testing +@testable import TablePro + +@Suite("Plugin Table Metadata Coding") +struct PluginTableMetadataCodingTests { + + /// Swift's synthesized `Decodable` does not fall back to an initializer's default value, so a + /// payload written before `attributes` existed throws `keyNotFound` unless the decoder spells + /// the fallback out. Measured, not assumed. + @Test("Decodes a payload written before attributes existed") + func decodesWithoutAttributes() throws { + let json = Data(#"{"tableName":"orders","comment":"hi"}"#.utf8) + + let metadata = try JSONDecoder().decode(PluginTableMetadata.self, from: json) + + #expect(metadata.tableName == "orders") + #expect(metadata.comment == "hi") + #expect(metadata.attributes.isEmpty) + #expect(metadata.commentIsReadOnly) + } + + @Test("Round-trips the attributes it was given") + func roundTripsAttributes() throws { + let original = PluginTableMetadata( + tableName: "orders", + attributes: [PluginObjectAttribute(label: "Owner", value: "app")] + ) + + let decoded = try JSONDecoder().decode( + PluginTableMetadata.self, from: JSONEncoder().encode(original)) + + #expect(decoded.attributes == original.attributes) + } + + /// The pre-`attributes` initializer keeps its exact signature so already-built plugins keep + /// their symbol. It has to stay callable, and it has to produce an empty attribute list. + @Test("The original initializer still compiles and reports no attributes") + func originalInitializerStillWorks() { + let metadata = PluginTableMetadata(tableName: "orders", engine: "InnoDB") + + #expect(metadata.engine == "InnoDB") + #expect(metadata.attributes.isEmpty) + #expect(metadata.commentIsReadOnly) + } +} + +@Suite("PostgreSQL Comment Literal") +struct PostgreSQLDollarQuotedLiteralTests { + + @Test("Wraps the value in a dollar-quoted body") + func wrapsValue() { + #expect(PostgreSQLObjectQueries.dollarQuoted("hello") == "$tablepro$hello$tablepro$") + } + + /// The whole point of dollar quoting: neither an apostrophe nor a backslash is scanned inside + /// the body, so `standard_conforming_strings` cannot change where the literal ends. + @Test("Leaves quotes and backslashes exactly as typed") + func leavesEscapesAlone() { + let payload = #"\'; DROP TABLE users; --"# + + let literal = PostgreSQLObjectQueries.dollarQuoted(payload) + + #expect(literal == "$tablepro$\(payload)$tablepro$") + #expect(literal.hasSuffix("$tablepro$")) + } + + /// A body holding the tag would close the literal early, which is the one way dollar quoting + /// can be escaped from. + @Test("Grows the tag until the body cannot close it") + func growsTagOnCollision() { + let literal = PostgreSQLObjectQueries.dollarQuoted("a $tablepro$ b") + + #expect(literal == "$tablepro_$a $tablepro$ b$tablepro_$") + } + + @Test("Grows the tag again when the body holds the grown one too") + func growsTagRepeatedly() { + let literal = PostgreSQLObjectQueries.dollarQuoted("$tablepro$ $tablepro_$") + + #expect(literal.hasPrefix("$tablepro__$")) + #expect(literal.hasSuffix("$tablepro__$")) + } + + /// PostgreSQL rejects a NUL in any text value, dollar-quoted or not. + @Test("Drops a NUL the server would refuse") + func dropsNul() { + #expect(PostgreSQLObjectQueries.dollarQuoted("a\0b") == "$tablepro$ab$tablepro$") + } +} + +@Suite("PostgreSQL Table Attributes") +struct PostgreSQLTableAttributeTests { + + /// An ordinary table is the assumption, so naming its kind adds a row that changes nothing. + @Test("Names owner, tablespace and persistence, and leaves an ordinary table unlabelled") + func fullSet() { + let attributes = PostgreSQLTableAttributes.build( + owner: "app", + tablespace: "pg_default", + persistence: "p", + relkind: "r" + ) + + #expect(attributes.map(\.value) == ["app", "pg_default", "Permanent"]) + } + + @Test("Omits every property the catalog left blank") + func blanksOmitted() { + let attributes = PostgreSQLTableAttributes.build( + owner: nil, + tablespace: "", + persistence: nil, + relkind: nil + ) + + #expect(attributes.isEmpty) + } + + /// An unfamiliar `relkind` is left off rather than shown as a raw letter, so a future relation + /// kind reads as absent instead of as a one-character property. + @Test("Drops an unrecognised relkind and persistence") + func unknownCodesDropped() { + let attributes = PostgreSQLTableAttributes.build( + owner: "app", + tablespace: nil, + persistence: "z", + relkind: "I" + ) + + #expect(attributes.map(\.value) == ["app"]) + } + + /// The tab already labels the schema it was opened on, so a second copy under the driver's own + /// name would print the same value twice. + @Test("Never names the schema") + func schemaNeverNamed() { + let attributes = PostgreSQLTableAttributes.build( + owner: "app", + tablespace: "pg_default", + persistence: "p", + relkind: "p" + ) + + #expect(attributes.contains { $0.label == "Schema" } == false) + } + + /// `COMMENT ON TABLE` is refused on anything else, and each of the rest has its own keyword the + /// app has no way to ask for, so the relation itself has to say the comment is read-only. + @Test("Only an ordinary or partitioned table takes a writable comment", arguments: [ + ("r", false), ("p", false), ("v", true), ("m", true), ("f", true), ("I", true) + ]) + func commentWritability(relkind: String, readOnly: Bool) { + #expect(PostgreSQLTableAttributes.commentIsReadOnly(relkind: relkind) == readOnly) + } + + @Test("An unreadable relkind is treated as read-only") + func missingRelkindIsReadOnly() { + #expect(PostgreSQLTableAttributes.commentIsReadOnly(relkind: nil)) + } + + @Test("Names an unlogged partitioned table by both codes") + func unloggedPartitioned() { + let attributes = PostgreSQLTableAttributes.build( + owner: nil, + tablespace: nil, + persistence: "u", + relkind: "p" + ) + + #expect(attributes.map(\.value) == ["Unlogged", "Partitioned table"]) + } +} + +@Suite("MySQL Table Status") +struct MySQLTableStatusTests { + + /// `SHOW TABLE STATUS` in the documented column order: Name, Engine, Version, Row_format, Rows, + /// Avg_row_length, Data_length, Max_data_length, Index_length, Data_free, Auto_increment, + /// Create_time, Update_time, Check_time, Collation, Checksum, Create_options, Comment. + private func statusRow( + comment: String = "orders", + collation: String = "utf8mb4_unicode_ci" + ) -> [PluginCellValue] { + [ + .text("orders"), .text("InnoDB"), .text("10"), .text("Dynamic"), .text("42"), + .text("128"), .text("16384"), .text("0"), .text("32768"), .text("0"), .text("99"), + .text("2026-08-29 10:11:12"), .text("2026-08-29 11:12:13"), .null, + .text(collation), .null, .text("row_format=DYNAMIC"), .text(comment) + ] + } + + @Test("Reads every column the Properties tab shows") + func readsAllColumns() { + let status = MySQLTableStatus(row: statusRow()) + + #expect(status.engine == "InnoDB") + #expect(status.rowFormat == "Dynamic") + #expect(status.rowCount == 42) + #expect(status.avgRowLength == 128) + #expect(status.dataSize == 16_384) + #expect(status.indexSize == 32_768) + #expect(status.autoIncrement == 99) + #expect(status.collation == "utf8mb4_unicode_ci") + #expect(status.comment == "orders") + } + + @Test("Parses the create and update timestamps") + func parsesTimestamps() { + let status = MySQLTableStatus(row: statusRow()) + + var components = DateComponents() + components.year = 2_026 + components.month = 8 + components.day = 29 + components.hour = 10 + components.minute = 11 + components.second = 12 + let expected = Calendar.current.date(from: components) + + #expect(status.createTime == expected) + #expect(status.updateTime != nil) + } + + /// MySQL reports no comment as the empty string, and an empty string here would stage a change + /// against a table that never had one. + @Test("An empty comment reads as absent") + func emptyCommentIsNil() { + let status = MySQLTableStatus(row: statusRow(comment: "")) + + #expect(status.comment == nil) + } + + @Test("A short row reads every missing column as absent") + func shortRowIsSafe() { + let status = MySQLTableStatus(row: [.text("orders"), .text("InnoDB")]) + + #expect(status.engine == "InnoDB") + #expect(status.comment == nil) + #expect(status.rowCount == nil) + #expect(status.createTime == nil) + } + + /// `SHOW TABLE STATUS` answers for a view with every storage column NULL, so a row that names + /// no engine is a view and MySQL has no `COMMENT` form for one. + @Test("A row with no engine is a view and keeps its comment read-only") + func viewCommentIsReadOnly() { + #expect(MySQLTableStatus(row: [.text("orders"), .null]).commentIsReadOnly) + #expect(MySQLTableStatus(row: statusRow()).commentIsReadOnly == false) + } + + @Test("Publishes row format, auto increment and create options as attributes") + func attributes() { + let status = MySQLTableStatus(row: statusRow()) + + #expect(status.attributes.map(\.value) == ["Dynamic", "99", "row_format=DYNAMIC"]) + } +} diff --git a/TableProUITests/StructurePropertiesTabUITests.swift b/TableProUITests/StructurePropertiesTabUITests.swift new file mode 100644 index 0000000000..3565867c13 --- /dev/null +++ b/TableProUITests/StructurePropertiesTabUITests.swift @@ -0,0 +1,92 @@ +// +// StructurePropertiesTabUITests.swift +// TableProUITests +// + +import XCTest + +final class StructurePropertiesTabUITests: UITestCase { + /// Properties is the first segment of the picker, so the failure it guards against is the whole + /// tab missing or landing somewhere else in the run. + func testPropertiesTabIsFirstAndOpens() throws { + let app = try launchWithSampleDatabase() + let window = app.windows.firstMatch + + let row = objectBrowserRow("Album", in: window) + XCTAssertTrue(row.waitToExist(timeout: 20), "The object browser must list Album") + clickAtCenter(row) + + showStructure(in: window) + + let picker = window.radioGroups["structure-tab-picker"].firstMatch + XCTAssertTrue(picker.waitToExist(timeout: 20), "The structure editor must offer its sub-tabs") + + let properties = subTab(named: "Properties", in: window) + XCTAssertTrue(properties.waitToExist(timeout: 20), "Properties must reach the picker") + XCTAssertEqual( + picker.radioButtons.firstMatch.label, + properties.label, + "Properties is the leading segment" + ) + + properties.click() + XCTAssertTrue( + waitForPredicate(timeout: 10) { isSelected(properties) }, + "Selecting Properties moves the picker to it" + ) + + XCTAssertTrue( + window.staticTexts["Album"].waitToExist(timeout: 20), + "The Properties tab names the table it was opened on" + ) + } + + /// SQLite stores no comment on a table, which is what makes it the deterministic case: the field + /// must not offer an edit that no statement can carry. + func testCommentIsReadOnlyWhereTheEngineHasNone() throws { + let app = try launchWithSampleDatabase() + let window = app.windows.firstMatch + + let row = objectBrowserRow("Album", in: window) + XCTAssertTrue(row.waitToExist(timeout: 20), "The object browser must list Album") + clickAtCenter(row) + + showStructure(in: window) + + let properties = subTab(named: "Properties", in: window) + XCTAssertTrue(properties.waitToExist(timeout: 20), "Properties must reach the picker") + properties.click() + + XCTAssertTrue( + window.staticTexts["This database does not store a comment on a table."] + .waitToExist(timeout: 20), + "SQLite has no table comment, so the field reports that instead of offering an editor" + ) + XCTAssertFalse( + window.textViews["table-comment-editor"].exists, + "No comment editor is mounted where the engine has no comment to write" + ) + } + + /// A radio button in a hosted picker reports `AXSelected` as nil and answers with `AXValue` + /// instead, so `isSelected` reads as false however the picker is set. + private func isSelected(_ segment: XCUIElement) -> Bool { + (segment.value as? NSNumber)?.intValue == 1 + } + + private func showStructure(in window: XCUIElement) { + let modePicker = window.radioGroups["results-view-mode-picker"].firstMatch + XCTAssertTrue(modePicker.waitToExist(timeout: 20), "The result must expose its view modes") + let structure = modePicker.radioButtons["Structure"].firstMatch + XCTAssertTrue(structure.waitToExist(timeout: 20), "Structure must be one of them") + structure.click() + } + + /// The sub-tab labels carry item counts, so they are matched by prefix rather than exactly. + private func subTab(named name: String, in window: XCUIElement) -> XCUIElement { + window.radioGroups["structure-tab-picker"].firstMatch + .radioButtons + .matching(NSPredicate(format: "label BEGINSWITH %@", name)) + .firstMatch + } +} diff --git a/docs/features/table-structure.mdx b/docs/features/table-structure.mdx index 5a8c5a7f87..72cc1e75e9 100644 --- a/docs/features/table-structure.mdx +++ b/docs/features/table-structure.mdx @@ -14,7 +14,22 @@ This is a DDL editor with a grid in front of it. Rename a column, add an index, Open a table and switch the result view to **Structure**, or right-click it in the sidebar and choose **Show Structure**. -The tabs are **Columns**, **Indexes**, **Foreign Keys**, **Constraints**, **Triggers**, **DDL**, and **Parts** (ClickHouse only); the first five carry item counts. A tab the engine has no concept of is hidden: ClickHouse has no Foreign Keys, Redshift no Triggers, Redis no Constraints. Every grid has a filter field, and clicking a header sorts. +The tabs are **Properties**, **Columns**, **Indexes**, **Foreign Keys**, **Constraints**, **Triggers**, **DDL**, and **Parts** (ClickHouse only); Columns through Triggers carry item counts. A tab the engine has no concept of is hidden: ClickHouse has no Foreign Keys, Redshift no Triggers, Redis no Constraints. Every grid has a filter field, and clicking a header sorts. + +## Properties tab + +The table's own row in the catalog: name, schema, size on disk, row count, and the comment. What sits between them is the driver's choice, so PostgreSQL names an owner, a tablespace and a persistence where MySQL names a row format and the next auto-increment value. + + + A grouped form listing name, schema, owner and tablespace above a comment field holding several lines of JSON + A grouped form listing name, schema, owner and tablespace above a comment field holding several lines of JSON + + +**Comment** is the only editable field. It scrolls rather than clipping, so a long one reads and edits in place. An edit queues with the rest of the structure changes and reaches the server on **Save Changes**. + + +Comment editing is available for MySQL, MariaDB, PostgreSQL and PGlite, and on those engines for an ordinary or partitioned table. Everywhere else, and on a view, a materialized view or a foreign table, the comment is read-only. + ## Columns tab @@ -141,6 +156,8 @@ MongoDB structure is read-only, and inferred from the collection's first 200 doc - **Changing a primary key on an existing table** works on MySQL, MariaDB, PostgreSQL, PGlite, SQL Server, DuckDB, Snowflake, and Dameng. Elsewhere the dropdown accepts the edit and the save produces nothing for it: rebuild the table by hand. - **Check constraints and generated columns** have no field in the grid. Both show in the DDL tab; change them by running DDL yourself. +- **A view, a materialized view or a foreign table** keeps a read-only comment on the Properties tab. Each takes its own `COMMENT ON` keyword: run it in the editor. +- **Renaming a table** is not on the Properties tab. It happens in the sidebar row: see [Table Operations](/features/table-operations). - **SQLite**: a column can be added, dropped, and renamed. No other column change generates SQL. - **Cassandra / ScyllaDB**: add and drop column only, no index editing, no visual table creation. - **Redshift, CockroachDB, BigQuery, Elasticsearch, SurrealDB, Beancount**: structure is read-only. diff --git a/docs/images/structure-properties-dark.png b/docs/images/structure-properties-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..849cf2cd8b542e79e2a46624906a8c5be9a31b76 GIT binary patch literal 97475 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 IeCPgu1AFA~V*mgE literal 0 HcmV?d00001 diff --git a/docs/images/structure-properties.png b/docs/images/structure-properties.png new file mode 100644 index 0000000000000000000000000000000000000000..8b3ed4b757208c8a0b829483cdf26811829197e2 GIT binary patch literal 94354 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 literal 0 HcmV?d00001 diff --git a/project.yml b/project.yml index cd83991c4d..94ae28c023 100644 --- a/project.yml +++ b/project.yml @@ -422,6 +422,7 @@ targets: - Plugins/MySQLDriverPlugin/MySQLServerVersion.swift - Plugins/MySQLDriverPlugin/MySQLSocketTimeout.swift - Plugins/MySQLDriverPlugin/MySQLStatementClassification.swift + - Plugins/MySQLDriverPlugin/MySQLTableStatus.swift - Plugins/MySQLDriverPlugin/MySQLTransactionStatement.swift - Plugins/SQLiteDriverPlugin/SQLiteCheckConstraintParser.swift - Plugins/MSSQLDriverPlugin/MSSQLCheckConstraintDefinition.swift @@ -434,6 +435,7 @@ targets: - Plugins/PostgreSQLDriverPlugin/PostgreSQLObjectQueries.swift - Plugins/PostgreSQLDriverPlugin/PostgreSQLSchemaQueries.swift - Plugins/PostgreSQLDriverPlugin/PostgreSQLSystemDatabases.swift + - Plugins/PostgreSQLDriverPlugin/PostgreSQLTableAttributes.swift - Plugins/PostgreSQLDriverPlugin/PostgreSQLTableListingLadder.swift - Plugins/PostgreSQLDriverPlugin/PostgreSQLTransactionStatement.swift - Plugins/PostgreSQLDriverPlugin/RedshiftExternalSchemaQueries.swift