fix(ddl): preserve safe Oracle and Dameng identifiers
This commit is contained in:
3 files changed
+385
-3
No files matched your search
@@ -20,4 +20,24 @@ describe("omitDdlIdentifierQuotes", () => {
|
||||
const ddl = "CREATE TABLE [demo_table] ([order] int, [key] int, [user] nvarchar(50), [id] int)";
|
||||
expect(omitDdlIdentifierQuotes(ddl, "sqlserver")).toBe("CREATE TABLE demo_table ([order] int, [key] int, [user] nvarchar(50), id int)");
|
||||
});
|
||||
|
||||
it("removes quotes from ordinary uppercase Oracle identifiers", () => {
|
||||
const ddl = 'CREATE TABLE "DBX_TEST"."PRODUCTS" ("ID" NUMBER(10), "SKU" VARCHAR2(32)) TABLESPACE "USERS";';
|
||||
expect(omitDdlIdentifierQuotes(ddl, "oracle")).toBe("CREATE TABLE DBX_TEST.PRODUCTS (ID NUMBER(10), SKU VARCHAR2(32)) TABLESPACE USERS;");
|
||||
});
|
||||
|
||||
it("keeps quotes required by Oracle case and naming rules", () => {
|
||||
const ddl = 'CREATE TABLE "CamelCase" ("lowercase" NUMBER, "WITH SPACE" NUMBER, "ORDER" NUMBER)';
|
||||
expect(omitDdlIdentifierQuotes(ddl, "oracle")).toBe(ddl);
|
||||
});
|
||||
|
||||
it("removes quotes from ordinary uppercase Dameng identifiers", () => {
|
||||
const ddl = 'CREATE TABLE "DBX_TEST"."PRODUCTS" ("ID" INT, "NAME" VARCHAR(128)) STORAGE (ON "MAIN", CLUSTERBTR)';
|
||||
expect(omitDdlIdentifierQuotes(ddl, "dameng")).toBe("CREATE TABLE DBX_TEST.PRODUCTS (ID INT, NAME VARCHAR(128)) STORAGE (ON MAIN, CLUSTERBTR)");
|
||||
});
|
||||
|
||||
it("keeps quotes required by Dameng case, naming, and reserved-word rules", () => {
|
||||
const ddl = 'CREATE TABLE "CamelCase" ("lowercase" INT, "WITH SPACE" INT, "ORDER" INT, "A$B" INT)';
|
||||
expect(omitDdlIdentifierQuotes(ddl, "dameng")).toBe(ddl);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { requiresMysqlIdentifierQuote, requiresPostgresIdentifierQuote } from "@/lib/sql/sqlIdentifier";
|
||||
import { requiresDamengIdentifierQuote, requiresMysqlIdentifierQuote, requiresOracleIdentifierQuote, requiresPostgresIdentifierQuote } from "@/lib/sql/sqlIdentifier";
|
||||
import { tokenizeSqlSemantic, unquoteSqlSemanticIdentifier } from "@/lib/sql/semantic/tokens";
|
||||
import type { SqlFormatDialect } from "@/lib/sql/sqlFormatter";
|
||||
|
||||
@@ -11,11 +11,13 @@ function canRenderUnquoted(identifier: string, dialect: SqlFormatDialect): boole
|
||||
return !requiresMysqlIdentifierQuote(identifier);
|
||||
case "postgres":
|
||||
case "sqlite":
|
||||
case "oracle":
|
||||
case "duckdb":
|
||||
case "dameng":
|
||||
case "generic":
|
||||
return !requiresPostgresIdentifierQuote(identifier);
|
||||
case "dameng":
|
||||
return !requiresDamengIdentifierQuote(identifier);
|
||||
case "oracle":
|
||||
return !requiresOracleIdentifierQuote(identifier);
|
||||
case "sqlserver":
|
||||
return SIMPLE_SQLSERVER_IDENTIFIER.test(identifier) && !requiresMysqlIdentifierQuote(identifier.toLowerCase());
|
||||
default:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const SIMPLE_LOWER_IDENTIFIER = /^[a-z_][a-z0-9_$]*$/;
|
||||
const SIMPLE_MYSQL_IDENTIFIER = /^[A-Za-z_$\u0080-\uFFFF][A-Za-z0-9_$\u0080-\uFFFF]*$/u;
|
||||
const SIMPLE_DAMENG_IDENTIFIER = /^[A-Z_][A-Z0-9_]*$/;
|
||||
const SIMPLE_ORACLE_IDENTIFIER = /^[A-Z][A-Z0-9_$#]*$/;
|
||||
|
||||
// PostgreSQL pg_get_keywords() categories R and T. GaussDB's PostgreSQL-
|
||||
// compatible modes use the same identifier constraints for bare names.
|
||||
@@ -181,6 +183,356 @@ const MYSQL_ONLY_RESERVED_IDENTIFIER_KEYWORDS = new Set([
|
||||
"zerofill",
|
||||
]);
|
||||
|
||||
// Oracle 11g SQL reserved words. Ordinary unquoted Oracle identifiers are
|
||||
// uppercase-folded, so only uppercase names outside this set are safe to unwrap.
|
||||
const ORACLE_RESERVED_IDENTIFIER_KEYWORDS = new Set([
|
||||
"ACCESS",
|
||||
"ADD",
|
||||
"ALL",
|
||||
"ALTER",
|
||||
"AND",
|
||||
"ANY",
|
||||
"AS",
|
||||
"ASC",
|
||||
"AUDIT",
|
||||
"BETWEEN",
|
||||
"BY",
|
||||
"CHAR",
|
||||
"CHECK",
|
||||
"CLUSTER",
|
||||
"COLUMN",
|
||||
"COMMENT",
|
||||
"COMPRESS",
|
||||
"CONNECT",
|
||||
"CREATE",
|
||||
"CURRENT",
|
||||
"DATE",
|
||||
"DECIMAL",
|
||||
"DEFAULT",
|
||||
"DELETE",
|
||||
"DESC",
|
||||
"DISTINCT",
|
||||
"DROP",
|
||||
"ELSE",
|
||||
"EXCLUSIVE",
|
||||
"EXISTS",
|
||||
"FILE",
|
||||
"FLOAT",
|
||||
"FOR",
|
||||
"FROM",
|
||||
"GRANT",
|
||||
"GROUP",
|
||||
"HAVING",
|
||||
"IDENTIFIED",
|
||||
"IMMEDIATE",
|
||||
"IN",
|
||||
"INCREMENT",
|
||||
"INDEX",
|
||||
"INITIAL",
|
||||
"INSERT",
|
||||
"INTEGER",
|
||||
"INTERSECT",
|
||||
"INTO",
|
||||
"IS",
|
||||
"LEVEL",
|
||||
"LIKE",
|
||||
"LOCK",
|
||||
"LONG",
|
||||
"MAXEXTENTS",
|
||||
"MINUS",
|
||||
"MLSLABEL",
|
||||
"MODE",
|
||||
"MODIFY",
|
||||
"NOAUDIT",
|
||||
"NOCOMPRESS",
|
||||
"NOT",
|
||||
"NOWAIT",
|
||||
"NULL",
|
||||
"NUMBER",
|
||||
"OF",
|
||||
"OFFLINE",
|
||||
"ON",
|
||||
"ONLINE",
|
||||
"OPTION",
|
||||
"OR",
|
||||
"ORDER",
|
||||
"PCTFREE",
|
||||
"PRIOR",
|
||||
"PRIVILEGES",
|
||||
"PUBLIC",
|
||||
"RAW",
|
||||
"RENAME",
|
||||
"RESOURCE",
|
||||
"REVOKE",
|
||||
"ROW",
|
||||
"ROWID",
|
||||
"ROWNUM",
|
||||
"ROWS",
|
||||
"SELECT",
|
||||
"SESSION",
|
||||
"SET",
|
||||
"SHARE",
|
||||
"SIZE",
|
||||
"SMALLINT",
|
||||
"START",
|
||||
"SUCCESSFUL",
|
||||
"SYNONYM",
|
||||
"SYSDATE",
|
||||
"TABLE",
|
||||
"THEN",
|
||||
"TO",
|
||||
"TRIGGER",
|
||||
"UID",
|
||||
"UNION",
|
||||
"UNIQUE",
|
||||
"UPDATE",
|
||||
"USER",
|
||||
"VALIDATE",
|
||||
"VALUES",
|
||||
"VARCHAR",
|
||||
"VARCHAR2",
|
||||
"VIEW",
|
||||
"WHENEVER",
|
||||
"WHERE",
|
||||
"WITH",
|
||||
]);
|
||||
|
||||
// V$RESERVED_WORDS rows marked RESERVED = Y across the supported DM8 test
|
||||
// instances. DM's native identifier rules use uppercase ordinary names.
|
||||
const DAMENG_RESERVED_IDENTIFIER_KEYWORDS = new Set([
|
||||
"ABSOLUTE",
|
||||
"ABSTRACT",
|
||||
"ADD",
|
||||
"ADMIN",
|
||||
"ALL",
|
||||
"ALTER",
|
||||
"AND",
|
||||
"ANY",
|
||||
"ARRAY",
|
||||
"ARRAYLEN",
|
||||
"AS",
|
||||
"ASC",
|
||||
"ASSIGN",
|
||||
"AUDIT",
|
||||
"AUTHORIZATION",
|
||||
"AUTO_INCREMENT",
|
||||
"BEGIN",
|
||||
"BETWEEN",
|
||||
"BIGDATEDIFF",
|
||||
"BINARY",
|
||||
"BOOL",
|
||||
"BOTH",
|
||||
"BREAK",
|
||||
"BSTRING",
|
||||
"BY",
|
||||
"BYTE",
|
||||
"CALL",
|
||||
"CASE",
|
||||
"CAST",
|
||||
"CATCH",
|
||||
"CHAR",
|
||||
"CHECK",
|
||||
"CLASS",
|
||||
"CLUSTER",
|
||||
"CLUSTERBTR",
|
||||
"COLLATION",
|
||||
"COLLECT",
|
||||
"COLUMN",
|
||||
"COMMENT",
|
||||
"COMMIT",
|
||||
"COMMITWORK",
|
||||
"CONNECT",
|
||||
"CONNECT_BY_ROOT",
|
||||
"CONST",
|
||||
"CONSTRAINT",
|
||||
"CONTAINS",
|
||||
"CONTEXT",
|
||||
"CONTINUE",
|
||||
"CONVERT",
|
||||
"CORRESPONDING",
|
||||
"CREATE",
|
||||
"CROSS",
|
||||
"CRYPTO",
|
||||
"CUBE",
|
||||
"CURRENT",
|
||||
"CURSOR",
|
||||
"DATEADD",
|
||||
"DATEDIFF",
|
||||
"DATEPART",
|
||||
"DECIMAL",
|
||||
"DECLARE",
|
||||
"DECODE",
|
||||
"DEFAULT",
|
||||
"DELETE",
|
||||
"DESC",
|
||||
"DISABLE",
|
||||
"DISKSPACE",
|
||||
"DISTINCT",
|
||||
"DISTRIBUTED",
|
||||
"DO",
|
||||
"DOMAIN",
|
||||
"DOUBLE",
|
||||
"DROP",
|
||||
"ELSE",
|
||||
"ELSEIF",
|
||||
"ELSIF",
|
||||
"ENABLE",
|
||||
"END",
|
||||
"EQU",
|
||||
"EXCHANGE",
|
||||
"EXEC",
|
||||
"EXECUTE",
|
||||
"EXISTS",
|
||||
"EXIT",
|
||||
"EXPLAIN",
|
||||
"EXTERN",
|
||||
"EXTRACT",
|
||||
"FETCH",
|
||||
"FINAL",
|
||||
"FINALLY",
|
||||
"FIRST",
|
||||
"FLASHBACK",
|
||||
"FLOAT",
|
||||
"FOR",
|
||||
"FOREIGN",
|
||||
"FROM",
|
||||
"FULL",
|
||||
"FULLY",
|
||||
"FUNCTION",
|
||||
"GET",
|
||||
"GOTO",
|
||||
"GRANT",
|
||||
"GROUP",
|
||||
"GROUPING",
|
||||
"HAVING",
|
||||
"IDENTITY",
|
||||
"IF",
|
||||
"IFNULL",
|
||||
"IMMEDIATE",
|
||||
"IN",
|
||||
"INDEX",
|
||||
"INLINE",
|
||||
"INNER",
|
||||
"INSERT",
|
||||
"INT",
|
||||
"INTERSECT",
|
||||
"INTERVAL",
|
||||
"INTO",
|
||||
"IS",
|
||||
"JOIN",
|
||||
"KEEP",
|
||||
"LEADING",
|
||||
"LEFT",
|
||||
"LEXER",
|
||||
"LIKE",
|
||||
"LIST",
|
||||
"LNNVL",
|
||||
"LOGIN",
|
||||
"LOOP",
|
||||
"MEMBER",
|
||||
"MINUS",
|
||||
"MULTISET",
|
||||
"NATURAL",
|
||||
"NEW",
|
||||
"NEXT",
|
||||
"NOCOPY",
|
||||
"NOCYCLE",
|
||||
"NOT",
|
||||
"NULL",
|
||||
"OBJECT",
|
||||
"OF",
|
||||
"ON",
|
||||
"OR",
|
||||
"ORDER",
|
||||
"OUT",
|
||||
"OVER",
|
||||
"OVERLAY",
|
||||
"OVERRIDE",
|
||||
"PARTITION",
|
||||
"PENDANT",
|
||||
"PERCENT",
|
||||
"PRIMARY",
|
||||
"PRINT",
|
||||
"PRIOR",
|
||||
"PRIVATE",
|
||||
"PRIVILEGES",
|
||||
"PROCEDURE",
|
||||
"PROTECTED",
|
||||
"PUBLIC",
|
||||
"RAISE",
|
||||
"RECORD",
|
||||
"REF",
|
||||
"REFERENCE",
|
||||
"REFERENCES",
|
||||
"REFERENCING",
|
||||
"RELATIVE",
|
||||
"REPEAT",
|
||||
"REPLICATE",
|
||||
"RETURN",
|
||||
"RETURNING",
|
||||
"REVERSE",
|
||||
"REVOKE",
|
||||
"RIGHT",
|
||||
"ROLLBACK",
|
||||
"ROLLUP",
|
||||
"ROW",
|
||||
"ROWNUM",
|
||||
"ROWS",
|
||||
"SAVEPOINT",
|
||||
"SBYTE",
|
||||
"SCHEMA",
|
||||
"SEALED",
|
||||
"SECTION",
|
||||
"SELECT",
|
||||
"SET",
|
||||
"SETS",
|
||||
"SHORT",
|
||||
"SIZEOF",
|
||||
"SOME",
|
||||
"STATIC",
|
||||
"STRUCT",
|
||||
"SUBPARTITION",
|
||||
"SWITCH",
|
||||
"SYNONYM",
|
||||
"TABLE",
|
||||
"THROW",
|
||||
"TIMESTAMPADD",
|
||||
"TIMESTAMPDIFF",
|
||||
"TO",
|
||||
"TOP",
|
||||
"TRAILING",
|
||||
"TRIGGER",
|
||||
"TRIM",
|
||||
"TRUNCATE",
|
||||
"TRY",
|
||||
"TYPEDEF",
|
||||
"TYPEOF",
|
||||
"UINT",
|
||||
"ULONG",
|
||||
"UNION",
|
||||
"UNIQUE",
|
||||
"UNTIL",
|
||||
"UPDATE",
|
||||
"USER",
|
||||
"USHORT",
|
||||
"USING",
|
||||
"VALUES",
|
||||
"VARRAY",
|
||||
"VERIFY",
|
||||
"VIEW",
|
||||
"VIRTUAL",
|
||||
"VOID",
|
||||
"WHEN",
|
||||
"WHENEVER",
|
||||
"WHERE",
|
||||
"WHILE",
|
||||
"WITH",
|
||||
"WITHIN",
|
||||
"XMLAGG",
|
||||
"XMLPARSE",
|
||||
"XMLTABLE",
|
||||
]);
|
||||
|
||||
export function isExplicitlyQuotedSqlIdentifier(value: string): boolean {
|
||||
if (value.length < 2) return false;
|
||||
return (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("`") && value.endsWith("`")) || (value.startsWith("[") && value.endsWith("]"));
|
||||
@@ -197,6 +549,14 @@ export function requiresMysqlIdentifierQuote(identifier: string, additionalKeywo
|
||||
return POSTGRES_RESERVED_IDENTIFIER_KEYWORDS.has(normalized) || additionalKeywords?.has(normalized) === true || MYSQL_ONLY_RESERVED_IDENTIFIER_KEYWORDS.has(normalized);
|
||||
}
|
||||
|
||||
export function requiresOracleIdentifierQuote(identifier: string): boolean {
|
||||
return !SIMPLE_ORACLE_IDENTIFIER.test(identifier) || ORACLE_RESERVED_IDENTIFIER_KEYWORDS.has(identifier);
|
||||
}
|
||||
|
||||
export function requiresDamengIdentifierQuote(identifier: string): boolean {
|
||||
return !SIMPLE_DAMENG_IDENTIFIER.test(identifier) || DAMENG_RESERVED_IDENTIFIER_KEYWORDS.has(identifier);
|
||||
}
|
||||
|
||||
export function quoteGaussDbJdbcIdentifier(identifier: string, identifierQuote: string): string {
|
||||
if (isExplicitlyQuotedSqlIdentifier(identifier)) return identifier;
|
||||
const quote = identifierQuote.trim();
|
||||
|
||||
Reference in new issue
Block a user