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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- A line on the welcome window naming the version TablePro updated from, with a link to what changed.
- What's New window, from Help > What's New.
- Update install mode and check frequency in the anonymous usage heartbeat.
- SQL Server alias, table and CLR types in the sidebar's **Types** section, each with a rebuilt `CREATE TYPE` statement.
- OceanBase MySQL-mode connection type on the MySQL driver. (#1748)
- On the Server mode for a SQLite Remote Database File, editing a database on an SSH server in place with statements run on the server. (#2831)
- **System Databases and Schemas** for the sidebar tree, in View Options and Settings > General. (#2832)
Expand Down
2 changes: 1 addition & 1 deletion Plugins/BeancountDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesDatabaseTypeIds</key>
<array>
<string>Beancount</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/BigQueryDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
<key>TableProMinAppVersion</key>
<string>0.42.0</string>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/CSVExportPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesExportFormatIds</key>
<array>
<string>csv</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/CSVImportPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesImportFormatIds</key>
<array>
<string>csv</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/CassandraDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>NSPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).CassandraPlugin</string>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/ClickHouseDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesDatabaseTypeIds</key>
<array>
<string>ClickHouse</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/CloudflareD1DriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
<key>TableProMinAppVersion</key>
<string>0.42.0</string>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/CloudflareR2SQLDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesDatabaseTypeIds</key>
<array>
<string>Cloudflare R2 SQL</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/DamengDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesDatabaseTypeIds</key>
<array>
<string>Dameng</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/DuckDBDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/DynamoDBDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
<key>TableProMinAppVersion</key>
<string>0.42.0</string>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/ElasticsearchDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
<key>TableProMinAppVersion</key>
<string>0.53.0</string>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/EtcdDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
<key>TableProMinAppVersion</key>
<string>0.42.0</string>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/HTMLExportPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesExportFormatIds</key>
<array>
<string>html</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/JSONExportPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesExportFormatIds</key>
<array>
<string>json</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/JSONImportPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesImportFormatIds</key>
<array>
<string>json</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/KafkaDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesDatabaseTypeIds</key>
<array>
<string>Kafka</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/LibSQLDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
<key>TableProMinAppVersion</key>
<string>0.42.0</string>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Plugins/MQLExportPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
<key>TableProProvidesExportFormatIds</key>
<array>
<string>mql</string>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/MSSQLDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>TableProPluginKitVersion</key>
<integer>30</integer>
<integer>31</integer>
</dict>
</plist>
1 change: 1 addition & 0 deletions Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ final class MSSQLPlugin: NSObject, TableProPlugin, DriverPlugin {
static let supportsDropSchema = true
static let supportsTriggers = true
static let supportsRoutines = true
static let supportsUserDefinedTypeBrowse = true
static let supportsDatabaseTriggerBrowse = true
static let supportsTriggerEditing = true
static let supportsCheckConstraints = true
Expand Down
192 changes: 192 additions & 0 deletions Plugins/MSSQLDriverPlugin/MSSQLPluginDriver+Types.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//
// MSSQLPluginDriver+Types.swift
// MSSQLDriverPlugin
//

import Foundation
import TableProPluginKit

extension MSSQLPluginDriver {
/// The listing is one query and carries no definitions: a table type's statement needs two more
/// round trips per type, and a schema with a hundred types would pay for all of them to draw a
/// list of names. `fetchUserDefinedType` fills in the definition for the one the reader opens.
func fetchUserDefinedTypes(schema: String?) async throws -> [PluginUserDefinedTypeInfo] {
let resolvedSchema = effectiveSchema(schema)
let result = try await execute(query: MSSQLTypeQueries.userDefinedTypeList(schema: resolvedSchema))
return result.rows.compactMap { row in
listedType(from: row, schema: resolvedSchema)
}
}

/// Addressed by `user_type_id`, so a type renamed since the listing still resolves, and the kind
/// is never part of the lookup.
func fetchUserDefinedType(_ type: PluginUserDefinedTypeInfo) async throws -> PluginUserDefinedTypeInfo {
let resolvedSchema = effectiveSchema(type.schema)
let result = try await execute(query: MSSQLTypeQueries.userDefinedTypeList(schema: resolvedSchema))
let listed = result.rows.compactMap { listedType(from: $0, schema: resolvedSchema) }
let match = type.identity.flatMap { identity in listed.first { $0.identity == identity } }
?? listed.first { $0.name == type.name }
guard let match else { throw PluginObjectSourceError.notFound(type.name) }

switch match.kind {
case .tableType:
return try await tableType(match, schema: resolvedSchema)
case .clrType:
return match.withDefinition(MSSQLTypeDefinition.clrStatement(
schema: resolvedSchema,
name: match.name,
assembly: match.attributes.first { $0.label == "Assembly" }?.value,
assemblyClass: match.attributes.first { $0.label == "Class" }?.value
))
default:
return match.withDefinition(MSSQLTypeDefinition.aliasStatement(
schema: resolvedSchema,
name: match.name,
baseType: match.baseType,
isNullable: match.attributes.first { $0.label == "Nullable" }?.value != "NO"
))
}
}

private func tableType(
_ type: PluginUserDefinedTypeInfo,
schema: String
) async throws -> PluginUserDefinedTypeInfo {
let columnRows = try await execute(
query: MSSQLTypeQueries.tableTypeColumns(schema: schema, name: type.name)
).rows
let indexRows = try await execute(
query: MSSQLTypeQueries.tableTypeIndexes(schema: schema, name: type.name)
).rows
let checkRows = try await execute(
query: MSSQLTypeQueries.tableTypeCheckConstraints(schema: schema, name: type.name)
).rows
let collation = try? await execute(query: MSSQLTypeQueries.databaseCollation).rows.first?[safe: 0]?.asText

let columns = columnRows.compactMap { row -> MSSQLTypeDefinition.Column? in
guard let name = row[safe: 0]?.asText else { return nil }
return MSSQLTypeDefinition.Column(
name: name,
type: row[safe: 1]?.asText,
isNullable: row[safe: 2]?.asText == "1",
identitySpec: row[safe: 3]?.asText,
computedDefinition: row[safe: 4]?.asText,
defaultDefinition: row[safe: 5]?.asText,
collation: row[safe: 6]?.asText
)
}
var indexOrder: [String] = []
var indexesById: [String: MSSQLTypeDefinition.Index] = [:]
for row in indexRows {
guard let indexId = row[safe: 6]?.asText, let column = row[safe: 4]?.asText else { continue }
let key = MSSQLTypeDefinition.IndexKey(column: column, isDescending: row[safe: 5]?.asText == "1")
if let existing = indexesById[indexId] {
indexesById[indexId] = MSSQLTypeDefinition.Index(
name: existing.name,
isPrimaryKey: existing.isPrimaryKey,
isUnique: existing.isUnique,
typeDescription: existing.typeDescription,
keys: existing.keys + [key],
bucketCount: existing.bucketCount
)
continue
}
indexOrder.append(indexId)
indexesById[indexId] = MSSQLTypeDefinition.Index(
name: row[safe: 0]?.asText,
isPrimaryKey: row[safe: 1]?.asText == "1",
isUnique: row[safe: 2]?.asText == "1",
typeDescription: row[safe: 3]?.asText,
keys: [key],
bucketCount: row[safe: 7]?.asText.flatMap(Int.init) ?? 0
)
}
let indexes = indexOrder.compactMap { indexesById[$0] }
let checks = checkRows.compactMap { $0[safe: 0]?.asText }

return PluginUserDefinedTypeInfo(
name: type.name,
kind: .tableType,
schema: schema,
identity: type.identity,
fields: columns.map {
PluginUserDefinedTypeField(name: $0.name, type: $0.type ?? "", collation: $0.collation)
},
columnTypeSpelling: type.columnTypeSpelling,
definition: MSSQLTypeDefinition.tableStatement(
schema: schema,
name: type.name,
columns: columns,
indexes: indexes,
checkConstraints: checks,
isMemoryOptimized: type.attributes.contains { $0.label == "Memory Optimized" && $0.value == "YES" },
databaseCollation: collation ?? nil
),
attributes: type.attributes
)
}

private func listedType(from row: [PluginCellValue], schema: String) -> PluginUserDefinedTypeInfo? {
guard let name = row[safe: 0]?.asText else { return nil }
let kindCode = row[safe: 3]?.asText ?? ""
let kind: PluginUserDefinedTypeKind = switch MSSQLTypeQueries.Kind(rawValue: kindCode) {
case .table: .tableType
case .clr: .clrType
default: .aliasType
}

var attributes: [PluginObjectAttribute] = []
if kind == .aliasType {
attributes.append(PluginObjectAttribute(
label: "Nullable",
value: row[safe: 5]?.asText == "1" ? "YES" : "NO"
))
}
if let collation = row[safe: 6]?.asText, !collation.isEmpty {
attributes.append(PluginObjectAttribute(label: "Collation", value: collation))
}
if let assembly = row[safe: 7]?.asText, !assembly.isEmpty {
attributes.append(PluginObjectAttribute(label: "Assembly", value: assembly))
}
if let assemblyClass = row[safe: 8]?.asText, !assemblyClass.isEmpty {
attributes.append(PluginObjectAttribute(label: "Class", value: assemblyClass))
}
if kind == .tableType, row[safe: 9]?.asText == "1" {
attributes.append(PluginObjectAttribute(label: "Memory Optimized", value: "YES"))
}

return PluginUserDefinedTypeInfo(
name: name,
kind: kind,
schema: row[safe: 1]?.asText ?? schema,
identity: row[safe: 2]?.asText,
baseType: row[safe: 4]?.asText,
columnTypeSpelling: columnTypeSpelling(kind: kind, schema: row[safe: 1]?.asText ?? schema, name: name),
attributes: attributes
)
}

/// A table type is never a column type, so it gets no spelling at all rather than one the
/// column picker would offer and the server would reject.
private func columnTypeSpelling(kind: PluginUserDefinedTypeKind, schema: String, name: String) -> String? {
guard kind != .tableType else { return nil }
return "\(MSSQLTypeDefinition.bracketed(schema)).\(MSSQLTypeDefinition.bracketed(name))"
}
}

private extension PluginUserDefinedTypeInfo {
func withDefinition(_ definition: String) -> PluginUserDefinedTypeInfo {
PluginUserDefinedTypeInfo(
name: name,
kind: kind,
schema: schema,
identity: identity,
enumLabels: enumLabels,
fields: fields,
baseType: baseType,
columnTypeSpelling: columnTypeSpelling,
definition: definition,
attributes: attributes
)
}
}
Loading
Loading