-
-
Notifications
You must be signed in to change notification settings - Fork 282
feat: Add production-ready SQL formatter with dialect support #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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" | ||
| ] | ||
| } | ||
|
|
||
| // 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
|
||
| } | ||
|
|
||
| // 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
|
||
| } | ||
|
|
||
| // 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() | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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.