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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Cancelling a pending SSH connection now closes its tunnel instead of leaving the local forward port open (#1369)
- Importing connections from DBeaver now brings over the username (#1355)
- Copying rows now includes only the visible columns, in their current order, instead of every column (#1354)
- The query shown in the editor when you open a table now matches the query that actually runs, instead of showing `SELECT *` and then running a different one
- Large text columns are no longer truncated to 256 characters when browsing a table. The full value is loaded, the same way BLOB columns already worked, so editing such a cell no longer risks saving a shortened value

## [0.43.1] - 2026-05-20

Expand Down
19 changes: 8 additions & 11 deletions TablePro/Core/Coordinators/FilterCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,15 @@ final class FilterCoordinator {

let tab = parent.tabManager.tabs[capturedTabIndex]
let buffer = parent.tabSessionRegistry.tableRows(for: tab.id)
let exclusions = parent.columnExclusions(for: capturedTableName)
let newQuery = parent.queryBuilder.buildFilteredQuery(
tableName: capturedTableName,
schemaName: tab.tableContext.schemaName,
filters: capturedFilters,
logicMode: tab.filterState.filterLogicMode,
sortState: tab.sortState,
columns: buffer.columns,
limit: tab.pagination.pageSize,
offset: tab.pagination.currentOffset,
columnExclusions: exclusions
offset: tab.pagination.currentOffset
)

parent.tabManager.mutate(at: capturedTabIndex) { $0.content.query = newQuery }
Expand All @@ -68,14 +67,13 @@ final class FilterCoordinator {

let tab = parent.tabManager.tabs[capturedTabIndex]
let buffer = parent.tabSessionRegistry.tableRows(for: tab.id)
let exclusions = parent.columnExclusions(for: capturedTableName)
let newQuery = parent.queryBuilder.buildBaseQuery(
tableName: capturedTableName,
schemaName: tab.tableContext.schemaName,
sortState: tab.sortState,
columns: buffer.columns,
limit: tab.pagination.pageSize,
offset: tab.pagination.currentOffset,
columnExclusions: exclusions
offset: tab.pagination.currentOffset
)

parent.tabManager.mutate(at: capturedTabIndex) { $0.content.query = newQuery }
Expand All @@ -98,28 +96,27 @@ final class FilterCoordinator {
let tab = parent.tabManager.tabs[tabIndex]
let buffer = parent.tabSessionRegistry.tableRows(for: tab.id)
let hasFilters = tab.filterState.hasAppliedFilters
let exclusions = parent.columnExclusions(for: tableName)

let newQuery: String
if hasFilters {
newQuery = parent.queryBuilder.buildFilteredQuery(
tableName: tableName,
schemaName: tab.tableContext.schemaName,
filters: tab.filterState.appliedFilters,
logicMode: tab.filterState.filterLogicMode,
sortState: tab.sortState,
columns: buffer.columns,
limit: tab.pagination.pageSize,
offset: tab.pagination.currentOffset,
columnExclusions: exclusions
offset: tab.pagination.currentOffset
)
} else {
newQuery = parent.queryBuilder.buildBaseQuery(
tableName: tableName,
schemaName: tab.tableContext.schemaName,
sortState: tab.sortState,
columns: buffer.columns,
limit: tab.pagination.pageSize,
offset: tab.pagination.currentOffset,
columnExclusions: exclusions
offset: tab.pagination.currentOffset
)
}

Expand Down
20 changes: 0 additions & 20 deletions TablePro/Core/Coordinators/QueryExecutionCoordinator+Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,6 @@ extension QueryExecutionCoordinator {
}
parent.toolbarState.isResultsCollapsed = false

if let tbl = tableName, !tbl.isEmpty, hasSchema {
let cacheKey = "\(conn.id):\(parent.activeDatabaseName):\(tbl)"
parent.cachedTableColumnTypes[cacheKey] = columnTypes
parent.cachedTableColumnNames[cacheKey] = columns
}

let resolvedPKs: [String]
if let pks = metadata?.primaryKeyColumns, !pks.isEmpty {
resolvedPKs = pks
Expand Down Expand Up @@ -486,18 +480,4 @@ extension QueryExecutionCoordinator {
}
parent.runQuery()
}

func columnExclusions(for tableName: String) -> [ColumnExclusion] {
let cacheKey = "\(parent.connectionId):\(parent.activeDatabaseName):\(tableName)"
guard let cachedTypes = parent.cachedTableColumnTypes[cacheKey],
let cachedCols = parent.cachedTableColumnNames[cacheKey] else {
return []
}
return ColumnExclusionPolicy.exclusions(
columns: cachedCols,
columnTypes: cachedTypes,
databaseType: parent.connection.type,
quoteIdentifier: parent.queryBuilder.quoteIdentifier
)
}
}
70 changes: 0 additions & 70 deletions TablePro/Core/Database/LazyLoadColumnsService.swift

This file was deleted.

57 changes: 0 additions & 57 deletions TablePro/Core/Services/Query/ColumnExclusionPolicy.swift

This file was deleted.

37 changes: 14 additions & 23 deletions TablePro/Core/Services/Query/TableQueryBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ struct TableQueryBuilder {
sortState: SortState? = nil,
columns: [String] = [],
limit: Int = 200,
offset: Int = 0,
columnExclusions: [ColumnExclusion] = []
offset: Int = 0
) -> String {
if let pluginDriver {
let sortCols = sortColumnsAsTuples(sortState)
Expand All @@ -79,10 +78,9 @@ struct TableQueryBuilder {
}

let quotedTable = qualifiedTable(tableName, schema: schemaName)
let selectClause = buildSelectClause(columns: columns, exclusions: columnExclusions)
var query = "SELECT \(selectClause) FROM \(quotedTable)"
var query = "SELECT * FROM \(quotedTable)"

if let orderBy = buildOrderByClause(sortState: sortState, columns: columns) {
if let orderBy = orderByOrOffsetFetchDefault(sortState: sortState, columns: columns) {
query += " \(orderBy)"
}

Expand All @@ -98,8 +96,7 @@ struct TableQueryBuilder {
sortState: SortState? = nil,
columns: [String] = [],
limit: Int = 200,
offset: Int = 0,
columnExclusions: [ColumnExclusion] = []
offset: Int = 0
) -> String {
if let pluginDriver {
let sortCols = sortColumnsAsTuples(sortState)
Expand All @@ -124,8 +121,7 @@ struct TableQueryBuilder {
}

let quotedTable = qualifiedTable(tableName, schema: schemaName)
let selectClause = buildSelectClause(columns: columns, exclusions: columnExclusions)
var query = "SELECT \(selectClause) FROM \(quotedTable)"
var query = "SELECT * FROM \(quotedTable)"

if let dialect {
let activeFilters = filters.filter { $0.isEnabled }
Expand All @@ -136,7 +132,7 @@ struct TableQueryBuilder {
}
}

if let orderBy = buildOrderByClause(sortState: sortState, columns: columns) {
if let orderBy = orderByOrOffsetFetchDefault(sortState: sortState, columns: columns) {
query += " \(orderBy)"
}

Expand Down Expand Up @@ -205,19 +201,6 @@ struct TableQueryBuilder {

// MARK: - Private Helpers

private func buildSelectClause(columns: [String], exclusions: [ColumnExclusion]) -> String {
guard !exclusions.isEmpty, !columns.isEmpty else { return "*" }

let exclusionMap = Dictionary(exclusions.map { ($0.columnName, $0.placeholderExpression) }) { _, last in last }

return columns.map { col in
if let placeholder = exclusionMap[col] {
return "\(placeholder) AS \(quote(col))"
}
return quote(col)
}.joined(separator: ", ")
}

private func buildPaginationClause(limit: Int, offset: Int) -> String {
if let dialect, dialect.paginationStyle == .offsetFetch {
return "OFFSET \(offset) ROWS FETCH NEXT \(limit) ROWS ONLY"
Expand All @@ -232,6 +215,14 @@ struct TableQueryBuilder {
} ?? []
}

private func orderByOrOffsetFetchDefault(sortState: SortState?, columns: [String]) -> String? {
if let orderBy = buildOrderByClause(sortState: sortState, columns: columns) {
return orderBy
}
guard dialect?.paginationStyle == .offsetFetch else { return nil }
return dialect?.offsetFetchOrderBy ?? "ORDER BY (SELECT NULL)"
}

private func buildOrderByClause(sortState: SortState?, columns: [String]) -> String? {
guard let state = sortState, state.isSorting else { return nil }

Expand Down
26 changes: 12 additions & 14 deletions TablePro/Models/Query/QueryTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,20 +115,18 @@ struct QueryTab: Identifiable, Equatable {
return "SCAN 0 MATCH * COUNT \(pageSize)"
default:
let dialect = try resolveSQLDialect(for: databaseType)
let quote = quoteIdentifier ?? quoteIdentifierFromDialect(dialect)
let qualifiedName: String
if let schema = schemaName, !schema.isEmpty {
qualifiedName = "\(quote(schema)).\(quote(tableName))"
} else {
qualifiedName = quote(tableName)
}
switch PluginManager.shared.paginationStyle(for: databaseType) {
case .offsetFetch:
let orderBy = PluginManager.shared.offsetFetchOrderBy(for: databaseType)
return "SELECT * FROM \(qualifiedName) \(orderBy) OFFSET 0 ROWS FETCH NEXT \(pageSize) ROWS ONLY;"
case .limit:
return "SELECT * FROM \(qualifiedName) LIMIT \(pageSize);"
}
let builder = TableQueryBuilder(
databaseType: databaseType,
pluginDriver: nil,
dialect: dialect,
dialectQuote: quoteIdentifier ?? quoteIdentifierFromDialect(dialect)
)
return builder.buildBaseQuery(
tableName: tableName,
schemaName: schemaName,
limit: pageSize,
offset: 0
)
}
}

Expand Down
Loading
Loading