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
233 changes: 233 additions & 0 deletions TablePro/Core/Services/SQLDialectProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
//
// SQLDialectProvider.swift
// TablePro
//
// Created by OpenCode on 1/17/26.
//

import Foundation

// MARK: - MySQL/MariaDB Dialect

struct MySQLDialect: SQLDialectProvider {
let identifierQuote = "`"

let keywords: Set<String> = [
// Core DML keywords
"SELECT", "FROM", "WHERE", "JOIN", "INNER", "LEFT", "RIGHT", "OUTER", "CROSS",
"ON", "USING", "AND", "OR", "NOT", "IN", "LIKE", "BETWEEN", "AS", "ALIAS",
"ORDER", "BY", "GROUP", "HAVING", "LIMIT", "OFFSET",
"INSERT", "INTO", "VALUES", "UPDATE", "SET", "DELETE",

// DDL keywords
"CREATE", "ALTER", "DROP", "TABLE", "INDEX", "VIEW", "DATABASE", "SCHEMA",
"PRIMARY", "KEY", "FOREIGN", "REFERENCES", "UNIQUE", "CONSTRAINT",
"ADD", "MODIFY", "CHANGE", "COLUMN", "RENAME",

// Data types
"NULL", "IS", "ASC", "DESC", "DISTINCT", "ALL", "ANY", "SOME",

// Control flow
"CASE", "WHEN", "THEN", "ELSE", "END", "IF", "IFNULL", "COALESCE",

// Set operations
"UNION", "INTERSECT", "EXCEPT",

// MySQL-specific
"FORCE", "USE", "IGNORE", "STRAIGHT_JOIN", "DUAL",
"SHOW", "DESCRIBE", "DESC", "EXPLAIN"
]

let functions: Set<String> = [
// Aggregate
"COUNT", "SUM", "AVG", "MAX", "MIN", "GROUP_CONCAT",

// String
"CONCAT", "SUBSTRING", "LEFT", "RIGHT", "LENGTH", "LOWER", "UPPER",
"TRIM", "LTRIM", "RTRIM", "REPLACE",

// Date/Time
"NOW", "CURDATE", "CURTIME", "DATE", "TIME", "YEAR", "MONTH", "DAY",
"DATE_ADD", "DATE_SUB", "DATEDIFF", "TIMESTAMPDIFF",

// Math
"ROUND", "CEIL", "FLOOR", "ABS", "MOD", "POW", "SQRT",

// Conversion
"CAST", "CONVERT"
]

let dataTypes: Set<String> = [
// Integer types
"INT", "INTEGER", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT",

// Decimal types
"DECIMAL", "NUMERIC", "FLOAT", "DOUBLE", "REAL",

// String types
"CHAR", "VARCHAR", "TEXT", "TINYTEXT", "MEDIUMTEXT", "LONGTEXT",
"BLOB", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB",

// Date/Time types
"DATE", "TIME", "DATETIME", "TIMESTAMP", "YEAR",

// Other types
"ENUM", "SET", "JSON", "BOOL", "BOOLEAN"
]
Comment on lines +15 to +76
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several overlaps between the keywords, functions, and dataTypes sets in MySQLDialect. For example, "LEFT" and "RIGHT" appear in both keywords (line 17) and functions (line 46). "DATE" and "TIME" appear in both functions (line 50) and dataTypes (line 72). "MAX" and "MIN" appear in both functions (line 43) and could conceptually overlap with data attributes. While the code may still work, these overlaps can lead to ambiguity in token classification and should be resolved to ensure predictable formatting behavior.

Copilot uses AI. Check for mistakes.
}

// MARK: - PostgreSQL Dialect

struct PostgreSQLDialect: SQLDialectProvider {
let identifierQuote = "\""

let keywords: Set<String> = [
// Core DML keywords
"SELECT", "FROM", "WHERE", "JOIN", "INNER", "LEFT", "RIGHT", "OUTER", "CROSS", "FULL",
"ON", "USING", "AND", "OR", "NOT", "IN", "LIKE", "ILIKE", "BETWEEN", "AS",
"ORDER", "BY", "GROUP", "HAVING", "LIMIT", "OFFSET", "FETCH", "FIRST", "ROWS", "ONLY",
"INSERT", "INTO", "VALUES", "UPDATE", "SET", "DELETE",

// DDL keywords
"CREATE", "ALTER", "DROP", "TABLE", "INDEX", "VIEW", "DATABASE", "SCHEMA",
"PRIMARY", "KEY", "FOREIGN", "REFERENCES", "UNIQUE", "CONSTRAINT",
"ADD", "MODIFY", "COLUMN", "RENAME",

// Data attributes
"NULL", "IS", "ASC", "DESC", "DISTINCT", "ALL", "ANY", "SOME",

// Control flow
"CASE", "WHEN", "THEN", "ELSE", "END", "COALESCE", "NULLIF",

// Set operations
"UNION", "INTERSECT", "EXCEPT",

// PostgreSQL-specific
"RETURNING", "WITH", "RECURSIVE", "AS", "MATERIALIZED",
"EXPLAIN", "ANALYZE", "VERBOSE",
"WINDOW", "OVER", "PARTITION",
"LATERAL", "ORDINALITY"
]

let functions: Set<String> = [
// Aggregate
"COUNT", "SUM", "AVG", "MAX", "MIN", "STRING_AGG", "ARRAY_AGG",

// String
"CONCAT", "SUBSTRING", "LEFT", "RIGHT", "LENGTH", "LOWER", "UPPER",
"TRIM", "LTRIM", "RTRIM", "REPLACE", "SPLIT_PART",

// Date/Time
"NOW", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP",
"DATE_TRUNC", "EXTRACT", "AGE", "TO_CHAR", "TO_DATE",

// Math
"ROUND", "CEIL", "CEILING", "FLOOR", "ABS", "MOD", "POW", "POWER", "SQRT",

// Conversion
"CAST", "TO_NUMBER", "TO_TIMESTAMP",

// JSON
"JSON_BUILD_OBJECT", "JSON_AGG", "JSONB_BUILD_OBJECT"
]

let dataTypes: Set<String> = [
// Integer types
"INTEGER", "INT", "SMALLINT", "BIGINT", "SERIAL", "BIGSERIAL", "SMALLSERIAL",

// Decimal types
"DECIMAL", "NUMERIC", "REAL", "DOUBLE", "PRECISION",

// String types
"CHAR", "CHARACTER", "VARCHAR", "TEXT",

// Date/Time types
"DATE", "TIME", "TIMESTAMP", "TIMESTAMPTZ", "INTERVAL",

// Other types
"BOOLEAN", "BOOL", "JSON", "JSONB", "UUID", "BYTEA", "ARRAY"
]
Comment on lines +84 to +149
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to MySQLDialect, PostgreSQLDialect has overlaps between keywords, functions, and dataTypes sets. "LEFT" and "RIGHT" appear in both keywords (line 86) and functions (line 117). "DATE", "TIME" appear in both functions (via CURRENT_DATE, TO_DATE, etc. at lines 121-122) and dataTypes (line 145). Additionally, "AS" appears twice in keywords (lines 87 and 106), creating a duplicate within the same set.

Copilot uses AI. Check for mistakes.
}

// MARK: - SQLite Dialect

struct SQLiteDialect: SQLDialectProvider {
let identifierQuote = "`"

let keywords: Set<String> = [
// Core DML keywords
"SELECT", "FROM", "WHERE", "JOIN", "INNER", "LEFT", "RIGHT", "OUTER", "CROSS",
"ON", "AND", "OR", "NOT", "IN", "LIKE", "GLOB", "BETWEEN", "AS",
"ORDER", "BY", "GROUP", "HAVING", "LIMIT", "OFFSET",
"INSERT", "INTO", "VALUES", "UPDATE", "SET", "DELETE",

// DDL keywords
"CREATE", "ALTER", "DROP", "TABLE", "INDEX", "VIEW", "TRIGGER",
"PRIMARY", "KEY", "FOREIGN", "REFERENCES", "UNIQUE", "CONSTRAINT",
"ADD", "COLUMN", "RENAME",

// Data attributes
"NULL", "IS", "ASC", "DESC", "DISTINCT", "ALL",

// Control flow
"CASE", "WHEN", "THEN", "ELSE", "END", "COALESCE", "IFNULL", "NULLIF",

// Set operations
"UNION", "INTERSECT", "EXCEPT",

// SQLite-specific
"AUTOINCREMENT", "WITHOUT", "ROWID", "PRAGMA",
"REPLACE", "ABORT", "FAIL", "IGNORE", "ROLLBACK",
"TEMP", "TEMPORARY", "VACUUM", "EXPLAIN", "QUERY", "PLAN"
]

let functions: Set<String> = [
// Aggregate
"COUNT", "SUM", "AVG", "MAX", "MIN", "GROUP_CONCAT", "TOTAL",

// String
"LENGTH", "SUBSTR", "SUBSTRING", "LOWER", "UPPER", "TRIM", "LTRIM", "RTRIM",
"REPLACE", "INSTR", "PRINTF",

// Date/Time
"DATE", "TIME", "DATETIME", "JULIANDAY", "STRFTIME",

// Math
"ABS", "ROUND", "RANDOM", "MIN", "MAX",

// Conversion
"CAST", "TYPEOF",

// Other
"COALESCE", "IFNULL", "NULLIF", "HEX", "QUOTE"
]

let dataTypes: Set<String> = [
// SQLite's storage classes
"INTEGER", "REAL", "TEXT", "BLOB", "NUMERIC",

// Type affinities
"INT", "TINYINT", "SMALLINT", "MEDIUMINT", "BIGINT",
"UNSIGNED", "BIG", "INT2", "INT8",
"CHARACTER", "VARCHAR", "VARYING", "NCHAR", "NATIVE",
"NVARCHAR", "CLOB",
"DOUBLE", "PRECISION", "FLOAT",
"DECIMAL", "BOOLEAN", "DATE", "DATETIME"
]
Comment on lines +157 to +216
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQLiteDialect also has overlaps between keywords, functions, and dataTypes sets. "DATE", "TIME", and "DATETIME" appear in both functions (line 193) and dataTypes (line 215). "COALESCE", "IFNULL", and "NULLIF" appear in both keywords (line 173) and functions (line 202). "MIN" and "MAX" appear in both functions (line 196) and are aggregate functions. These overlaps can lead to unpredictable token classification during formatting.

Copilot uses AI. Check for mistakes.
}

// MARK: - Dialect Factory

struct SQLDialectFactory {
/// Create a dialect provider for the given database type
static func createDialect(for databaseType: DatabaseType) -> SQLDialectProvider {
switch databaseType {
case .mysql, .mariadb:
return MySQLDialect()
case .postgresql:
return PostgreSQLDialect()
case .sqlite:
return SQLiteDialect()
}
}
}
Loading