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 @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- DELETE and UPDATE queries using all columns in WHERE clause instead of just the primary key for PostgreSQL, Redshift, MSSQL, and ClickHouse
- SSL/TLS always being enabled for MongoDB, Redis, and ClickHouse connections due to case mismatch in SSL mode string comparison (#249)
- Redis sidebar click showing data briefly then going empty due to double-navigation race condition (#251)
- MongoDB showing "Invalid database name: ''" when connecting without a database name
Expand Down
21 changes: 20 additions & 1 deletion Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,25 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
}

func fetchAllColumns(schema: String?) async throws -> [String: [PluginColumnInfo]] {
// Pre-fetch PK columns for all tables. Falls back to sorting_key when
// primary_key is empty (MergeTree without explicit PRIMARY KEY clause).
// Note: expression-based keys like toDate(col) won't match bare column names.
let pkSql = """
SELECT name, primary_key, sorting_key FROM system.tables
WHERE database = currentDatabase()
"""
let pkResult = try await execute(query: pkSql)
var pkLookup: [String: Set<String>] = [:]
for row in pkResult.rows {
guard let tableName = row[safe: 0] ?? nil else { continue }
let primaryKey = (row[safe: 1] ?? nil) ?? ""
let sortingKey = (row[safe: 2] ?? nil) ?? ""
let keyString = primaryKey.isEmpty ? sortingKey : primaryKey
guard !keyString.isEmpty else { continue }
let cols = Set(keyString.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) })
pkLookup[tableName] = cols
}

let sql = """
SELECT table, name, type, default_kind, default_expression, comment
FROM system.columns
Expand Down Expand Up @@ -265,7 +284,7 @@ final class ClickHousePluginDriver: PluginDatabaseDriver, @unchecked Sendable {
name: colName,
dataType: dataType,
isNullable: isNullable,
isPrimaryKey: false,
isPrimaryKey: pkLookup[tableName]?.contains(colName) == true,
defaultValue: defaultValue,
extra: extra,
comment: (comment?.isEmpty == false) ? comment : nil
Expand Down
38 changes: 25 additions & 13 deletions Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -461,18 +461,29 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
let esc = effectiveSchemaEscaped(schema)
let sql = """
SELECT
COLUMN_NAME,
DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH,
NUMERIC_PRECISION,
NUMERIC_SCALE,
IS_NULLABLE,
COLUMN_DEFAULT,
COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + TABLE_NAME), COLUMN_NAME, 'IsIdentity') AS IS_IDENTITY
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '\(escapedTable)'
AND TABLE_SCHEMA = '\(esc)'
ORDER BY ORDINAL_POSITION
c.COLUMN_NAME,
c.DATA_TYPE,
c.CHARACTER_MAXIMUM_LENGTH,
c.NUMERIC_PRECISION,
c.NUMERIC_SCALE,
c.IS_NULLABLE,
c.COLUMN_DEFAULT,
COLUMNPROPERTY(OBJECT_ID(c.TABLE_SCHEMA + '.' + c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') AS IS_IDENTITY,
CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS IS_PK
FROM INFORMATION_SCHEMA.COLUMNS c
LEFT JOIN (
SELECT kcu.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
AND tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA
WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
AND tc.TABLE_SCHEMA = '\(esc)'
AND tc.TABLE_NAME = '\(escapedTable)'
) pk ON c.COLUMN_NAME = pk.COLUMN_NAME
WHERE c.TABLE_NAME = '\(escapedTable)'
AND c.TABLE_SCHEMA = '\(esc)'
ORDER BY c.ORDINAL_POSITION
"""
let result = try await execute(query: sql)
return result.rows.compactMap { row -> PluginColumnInfo? in
Expand All @@ -484,6 +495,7 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
let isNullable = (row[safe: 5] ?? nil) == "YES"
let defaultValue = row[safe: 6] ?? nil
let isIdentity = (row[safe: 7] ?? nil) == "1"
let isPk = (row[safe: 8] ?? nil) == "1"

let baseType = (dataType ?? "nvarchar").lowercased()
let fixedSizeTypes: Set<String> = [
Expand All @@ -509,7 +521,7 @@ final class MSSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
name: name,
dataType: fullType,
isNullable: isNullable,
isPrimaryKey: false,
isPrimaryKey: isPk,
defaultValue: defaultValue,
extra: isIdentity ? "IDENTITY" : nil
)
Expand Down
31 changes: 27 additions & 4 deletions Plugins/PostgreSQLDriverPlugin/PostgreSQLPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,25 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
c.column_default,
c.collation_name,
pgd.description,
c.udt_name
c.udt_name,
CASE WHEN pk.column_name IS NOT NULL THEN 'YES' ELSE 'NO' END AS is_pk
FROM information_schema.columns c
LEFT JOIN pg_catalog.pg_statio_all_tables st
ON st.schemaname = c.table_schema
AND st.relname = c.table_name
LEFT JOIN pg_catalog.pg_description pgd
ON pgd.objoid = st.relid
AND pgd.objsubid = c.ordinal_position
LEFT JOIN (
SELECT DISTINCT kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type = 'PRIMARY KEY'
AND tc.table_schema = '\(escapedSchema)'
AND tc.table_name = '\(escapeLiteral(table))'
) pk ON c.column_name = pk.column_name
WHERE c.table_schema = '\(escapedSchema)' AND c.table_name = '\(escapeLiteral(table))'
ORDER BY c.ordinal_position
"""
Expand All @@ -214,6 +225,7 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
let defaultValue = row[3]
let collation = row.count > 4 ? row[4] : nil
let comment = row.count > 5 ? row[5] : nil
let isPk = row.count > 7 && row[7] == "YES"

let charset: String? = {
guard let coll = collation else { return nil }
Expand All @@ -227,7 +239,7 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
name: name,
dataType: dataType,
isNullable: isNullable,
isPrimaryKey: false,
isPrimaryKey: isPk,
defaultValue: defaultValue,
charset: charset,
collation: collation,
Expand All @@ -246,14 +258,24 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
c.column_default,
c.collation_name,
pgd.description,
c.udt_name
c.udt_name,
CASE WHEN pk.column_name IS NOT NULL THEN 'YES' ELSE 'NO' END AS is_pk
FROM information_schema.columns c
LEFT JOIN pg_catalog.pg_statio_all_tables st
ON st.schemaname = c.table_schema
AND st.relname = c.table_name
LEFT JOIN pg_catalog.pg_description pgd
ON pgd.objoid = st.relid
AND pgd.objsubid = c.ordinal_position
LEFT JOIN (
SELECT DISTINCT kcu.table_name, kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type = 'PRIMARY KEY'
AND tc.table_schema = '\(escapedSchema)'
) pk ON c.table_name = pk.table_name AND c.column_name = pk.column_name
WHERE c.table_schema = '\(escapedSchema)'
ORDER BY c.table_name, c.ordinal_position
"""
Expand All @@ -278,6 +300,7 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
let defaultValue = row[4]
let collation = row.count > 5 ? row[5] : nil
let comment = row.count > 6 ? row[6] : nil
let isPk = row.count > 8 && row[8] == "YES"

let charset: String? = {
guard let coll = collation else { return nil }
Expand All @@ -291,7 +314,7 @@ final class PostgreSQLPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
name: name,
dataType: dataType,
isNullable: isNullable,
isPrimaryKey: false,
isPrimaryKey: isPk,
defaultValue: defaultValue,
charset: charset,
collation: collation,
Expand Down
31 changes: 27 additions & 4 deletions Plugins/PostgreSQLDriverPlugin/RedshiftPluginDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,25 @@ final class RedshiftPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
c.column_default,
c.collation_name,
pgd.description,
c.udt_name
c.udt_name,
CASE WHEN pk.column_name IS NOT NULL THEN 'YES' ELSE 'NO' END AS is_pk
FROM information_schema.columns c
LEFT JOIN pg_catalog.pg_class cls
ON cls.relname = c.table_name
AND cls.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = c.table_schema)
LEFT JOIN pg_catalog.pg_description pgd
ON pgd.objoid = cls.oid
AND pgd.objsubid = c.ordinal_position
LEFT JOIN (
SELECT DISTINCT kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type = 'PRIMARY KEY'
AND tc.table_schema = '\(escapedSchema)'
AND tc.table_name = '\(safeTable)'
) pk ON c.column_name = pk.column_name
WHERE c.table_schema = '\(escapedSchema)' AND c.table_name = '\(safeTable)'
ORDER BY c.ordinal_position
"""
Expand All @@ -214,6 +225,7 @@ final class RedshiftPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
let defaultValue = row[3]
let collation = row.count > 4 ? row[4] : nil
let comment = row.count > 5 ? row[5] : nil
let isPk = row.count > 7 && row[7] == "YES"

let charset: String? = {
guard let coll = collation else { return nil }
Expand All @@ -227,7 +239,7 @@ final class RedshiftPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
name: name,
dataType: dataType,
isNullable: isNullable,
isPrimaryKey: false,
isPrimaryKey: isPk,
defaultValue: defaultValue,
charset: charset,
collation: collation,
Expand All @@ -246,14 +258,24 @@ final class RedshiftPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
c.column_default,
c.collation_name,
pgd.description,
c.udt_name
c.udt_name,
CASE WHEN pk.column_name IS NOT NULL THEN 'YES' ELSE 'NO' END AS is_pk
FROM information_schema.columns c
LEFT JOIN pg_catalog.pg_class cls
ON cls.relname = c.table_name
AND cls.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = c.table_schema)
LEFT JOIN pg_catalog.pg_description pgd
ON pgd.objoid = cls.oid
AND pgd.objsubid = c.ordinal_position
LEFT JOIN (
SELECT DISTINCT kcu.table_name, kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
WHERE tc.constraint_type = 'PRIMARY KEY'
AND tc.table_schema = '\(escapedSchema)'
) pk ON c.table_name = pk.table_name AND c.column_name = pk.column_name
WHERE c.table_schema = '\(escapedSchema)'
ORDER BY c.table_name, c.ordinal_position
"""
Expand All @@ -278,6 +300,7 @@ final class RedshiftPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
let defaultValue = row[4]
let collation = row.count > 5 ? row[5] : nil
let comment = row.count > 6 ? row[6] : nil
let isPk = row.count > 8 && row[8] == "YES"

let charset: String? = {
guard let coll = collation else { return nil }
Expand All @@ -291,7 +314,7 @@ final class RedshiftPluginDriver: PluginDatabaseDriver, @unchecked Sendable {
name: name,
dataType: dataType,
isNullable: isNullable,
isPrimaryKey: false,
isPrimaryKey: isPk,
defaultValue: defaultValue,
charset: charset,
collation: collation,
Expand Down
Loading