diff --git a/DBSyn/MainView.swift b/DBSyn/MainView.swift index df4faf0..4ac1831 100644 --- a/DBSyn/MainView.swift +++ b/DBSyn/MainView.swift @@ -1217,10 +1217,22 @@ struct MainView: View { private var editableResultTarget: (database: String, table: String)? { let statements = model.sql.components(separatedBy: ";") guard let select = statements.last(where: { $0.range(of: #"(?i)\bSELECT\b"#, options: .regularExpression) != nil }) else { return nil } - let pattern = #"(?is)^\s*SELECT\s+\*\s+FROM\s+((?:`[^`]+`|[A-Za-z_][A-Za-z0-9_]*)(?:\.(?:`[^`]+`|[A-Za-z_][A-Za-z0-9_]*))?)(?:\s+LIMIT\s+\d+(?:\s*,\s*\d+|\s+OFFSET\s+\d+)?)?\s*$"# + // Permit ordinary filtered/sorted single-table SELECT * results. The + // primary-key checks in DBSynInlineRowEditor still guard every UPDATE. + // Joins, grouping, unions and computed result sets remain read-only. + let pattern = #"(?is)^\s*SELECT\s+\*\s+FROM\s+((?:`[^`]+`|[A-Za-z_][A-Za-z0-9_]*)(?:\.(?:`[^`]+`|[A-Za-z_][A-Za-z0-9_]*))?)(?=\s|$)(.*)$"# guard let regex = try? NSRegularExpression(pattern: pattern), let match = regex.firstMatch(in: select, range: NSRange(select.startIndex..., in: select)), let range = Range(match.range(at: 1), in: select) else { return nil } + guard let tailRange = Range(match.range(at: 2), in: select) else { return nil } + let tail = String(select[tailRange]).trimmingCharacters(in: .whitespacesAndNewlines) + // Only permit WHERE / ORDER BY / LIMIT clauses; do not treat joins, + // unions, grouped rows, or derived expressions as directly editable. + if !tail.isEmpty { + guard tail.range(of: #"(?i)^(WHERE|ORDER\s+BY|LIMIT)\b"#, options: .regularExpression) != nil, + tail.range(of: #"(?i)\b(JOIN|UNION|GROUP\s+BY|HAVING|INTO|FOR\s+UPDATE|LOCK\s+IN\s+SHARE\s+MODE)\b"#, options: .regularExpression) == nil, + !tail.contains(";") else { return nil } + } let parts = String(select[range]).replacingOccurrences(of: "`", with: "").components(separatedBy: ".") let database = parts.count == 2 ? parts[0] : model.completionDatabase let table = parts.last ?? ""