Files
dbx/apps/desktop/src/lib/sql/sqlIdentifier.ts
T

568 lines
9.0 KiB
TypeScript

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.
const POSTGRES_RESERVED_IDENTIFIER_KEYWORDS = new Set([
"all",
"analyse",
"analyze",
"and",
"any",
"array",
"as",
"asc",
"asymmetric",
"authorization",
"binary",
"both",
"case",
"cast",
"check",
"collate",
"collation",
"column",
"concurrently",
"constraint",
"create",
"cross",
"current_catalog",
"current_date",
"current_role",
"current_schema",
"current_time",
"current_timestamp",
"current_user",
"default",
"deferrable",
"desc",
"distinct",
"do",
"else",
"end",
"except",
"false",
"fetch",
"for",
"foreign",
"freeze",
"from",
"full",
"grant",
"group",
"having",
"ilike",
"in",
"initially",
"inner",
"intersect",
"into",
"is",
"isnull",
"join",
"lateral",
"leading",
"left",
"like",
"limit",
"localtime",
"localtimestamp",
"natural",
"not",
"notnull",
"null",
"offset",
"on",
"only",
"or",
"order",
"outer",
"overlaps",
"placing",
"primary",
"references",
"returning",
"right",
"select",
"session_user",
"similar",
"some",
"symmetric",
"system_user",
"table",
"tablesample",
"then",
"to",
"trailing",
"true",
"union",
"unique",
"user",
"using",
"variadic",
"verbose",
"when",
"where",
"window",
"with",
]);
// GaussDB MySQL-compatible drivers report a backtick. These additional words
// cover MySQL-only names that cannot safely be emitted as bare identifiers.
const MYSQL_ONLY_RESERVED_IDENTIFIER_KEYWORDS = new Set([
"accessible",
"auto_increment",
"change",
"database",
"databases",
"delayed",
"describe",
"div",
"dual",
"enclosed",
"escaped",
"explain",
"force",
"fulltext",
"high_priority",
"ignore",
"index",
"infile",
"key",
"keys",
"kill",
"linear",
"lines",
"load",
"lock",
"low_priority",
"master_ssl_verify_server_cert",
"maxvalue",
"mediumint",
"mod",
"no_write_to_binlog",
"optimize",
"optionally",
"outfile",
"partition",
"purge",
"range",
"read_write",
"regexp",
"release",
"rename",
"replace",
"require",
"rlike",
"schema",
"schemas",
"separator",
"show",
"spatial",
"sql_big_result",
"sql_calc_found_rows",
"sql_small_result",
"ssl",
"starting",
"straight_join",
"terminated",
"tinyint",
"unlock",
"unsigned",
"use",
"utc_date",
"utc_time",
"utc_timestamp",
"values",
"varbinary",
"varchar",
"write",
"xor",
"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("]"));
}
export function requiresPostgresIdentifierQuote(identifier: string, additionalKeywords?: ReadonlySet<string>): boolean {
if (!SIMPLE_LOWER_IDENTIFIER.test(identifier)) return true;
return POSTGRES_RESERVED_IDENTIFIER_KEYWORDS.has(identifier) || additionalKeywords?.has(identifier) === true;
}
export function requiresMysqlIdentifierQuote(identifier: string, additionalKeywords?: ReadonlySet<string>): boolean {
if (!SIMPLE_MYSQL_IDENTIFIER.test(identifier)) return true;
const normalized = identifier.toLowerCase();
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();
if (!quote) return identifier;
const requiresQuote = quote === "`" ? requiresPostgresIdentifierQuote(identifier) || MYSQL_ONLY_RESERVED_IDENTIFIER_KEYWORDS.has(identifier.toLowerCase()) : requiresPostgresIdentifierQuote(identifier);
if (!requiresQuote) return identifier;
return `${quote}${identifier.replaceAll(quote, quote + quote)}${quote}`;
}