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

### Added

- Filter operator picker shows SQL symbols alongside names for quick visual recognition
- SQL autocomplete now suggests column names before a FROM clause is written, using all cached schema columns as fallback
- Eager column cache warming after schema load for faster autocomplete
- MCP query safety: three-tier classification with server-side confirmation for write and destructive queries
Expand Down
7 changes: 3 additions & 4 deletions TablePro/Core/Storage/FilterSettingsStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ enum FilterDefaultOperator: String, CaseIterable, Identifiable, Codable {
var id: String { rawValue }

var displayName: String {
switch self {
case .equal: return "Equal (=)"
case .contains: return "Contains"
}
let op = toFilterOperator()
if op.symbol.isEmpty { return op.displayName }
return "\(op.symbol) \(op.displayName)"
}

func toFilterOperator() -> FilterOperator {
Expand Down
21 changes: 21 additions & 0 deletions TablePro/Models/Database/TableFilter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,27 @@ enum FilterOperator: String, CaseIterable, Identifiable, Codable {
case .regex: return String(localized: "matches regex")
}
}

/// SQL operator symbol for visual recognition in menus
var symbol: String {
switch self {
case .equal: return "="
case .notEqual: return "!="
case .greaterThan: return ">"
case .greaterOrEqual: return ">="
case .lessThan: return "<"
case .lessOrEqual: return "<="
case .contains: return "LIKE %..%"
case .notContains: return "NOT LIKE %..%"
case .startsWith: return "LIKE ..%"
case .endsWith: return "LIKE %.."
case .inList: return "IN (..)"
case .notInList: return "NOT IN (..)"
case .between: return "BETWEEN"
case .regex: return "~"
case .isNull, .isNotNull, .isEmpty, .isNotEmpty: return ""
}
}
}

/// Represents a single table filter condition
Expand Down
15 changes: 13 additions & 2 deletions TablePro/Views/Filter/FilterRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ struct FilterRowView: View {
private var operatorPicker: some View {
Picker("", selection: $filter.filterOperator) {
ForEach(FilterOperator.allCases) { op in
Text(op.displayName).tag(op)
OperatorMenuLabel(op: op).tag(op)
}
}
.pickerStyle(.menu)
Expand Down Expand Up @@ -130,7 +130,7 @@ struct FilterRowView: View {
Text("—")
.font(.callout)
.foregroundStyle(.tertiary)
.frame(minWidth: 80, alignment: .leading)
.frame(maxWidth: .infinity, alignment: .leading)
}
}

Expand Down Expand Up @@ -182,4 +182,15 @@ struct FilterRowView: View {
Label(String(localized: "Remove Filter"), systemImage: "trash")
}
}

// MARK: - Operator Menu Label

private struct OperatorMenuLabel: View {
let op: FilterOperator

var body: some View {
Text(op.symbol.isEmpty ? op.displayName : "\(op.symbol) \(op.displayName)")
.accessibilityLabel(op.displayName)
}
}
}
2 changes: 1 addition & 1 deletion docs/features/filtering.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Filters are per-tab. Tables with active filters open in a new tab when you click

## Operators

18 operators: `=`, `!=`, contains, not contains, starts with, ends with, `>`, `>=`, `<`, `<=`, IS NULL, IS NOT NULL, IS EMPTY, IS NOT EMPTY, IN, NOT IN, BETWEEN, REGEX.
18 operators with SQL symbols shown inline: `=`, `!=`, `LIKE %..%`, `NOT LIKE %..%`, `LIKE ..%`, `LIKE %..`, `>`, `>=`, `<`, `<=`, IS NULL, IS NOT NULL, IS EMPTY, IS NOT EMPTY, `IN (..)`, `NOT IN (..)`, `BETWEEN`, `~` (regex).

BETWEEN shows two value fields. IN/NOT IN takes comma-separated values.

Expand Down
Loading