fix(gaussdb): quote identifiers by compatibility mode

This commit is contained in:
zipg authored and GitHub committed 2026-07-29 23:34:58 +08:00
1 parent d0c3a3fd2e
commit 66f94f23f1
28 files changed
+1207 -134

No files matched your search

+3 -115
View File
@@ -7,6 +7,7 @@ import { sqlSemanticDialectFor } from "@/lib/sql/semantic/dialect";
import { findActiveSqlStatementSpan, tokenizeSqlSemantic } from "@/lib/sql/semantic/tokens";
import type { SqlSemanticBuildOptions, SqlSemanticSpan } from "@/lib/sql/semantic/types";
import { DEFAULT_SQL_SNIPPETS, MANTICORESEARCH_SQL_SNIPPETS, resolveSqlSnippetBodyForDatabase } from "@/lib/sql/sqlSnippetTemplates";
import { requiresPostgresIdentifierQuote } from "@/lib/sql/sqlIdentifier";
export { DEFAULT_SQL_SNIPPETS, resolveSqlSnippetBodyForDatabase } from "@/lib/sql/sqlSnippetTemplates";
@@ -2781,124 +2782,11 @@ function unquoteIdentifier(value: string): string {
}
export function quoteSqlIdentifier(identifier: string, dialect?: "mysql" | "postgres" | "sqlserver"): string {
if (dialect !== "postgres" || !requiresPostgresIdentifierQuote(identifier)) return identifier;
if (dialect !== "postgres" || !requiresPostgresIdentifierQuote(identifier, POSTGRES_IDENTIFIER_KEYWORDS)) return identifier;
return `"${identifier.replaceAll('"', '""')}"`;
}
function requiresPostgresIdentifierQuote(identifier: string): boolean {
if (!/^[a-z_][a-z0-9_$]*$/.test(identifier)) return true;
return POSTGRES_IDENTIFIER_KEYWORDS.has(identifier);
}
// PostgreSQL reserved_keyword + type_func_name_keyword categories (pg_get_keywords()
// catcodes R and T) — identifiers matching these cannot appear as a bare column
// reference and must be quoted. SQL_KEYWORDS alone misses most of them because it is
// a completion phrase list ("ORDER BY", not "order").
const POSTGRES_RESERVED_KEYWORDS = [
"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",
];
const POSTGRES_IDENTIFIER_KEYWORDS = new Set(SQL_KEYWORDS.map((keyword) => keyword.toLowerCase()).concat(POSTGRES_RESERVED_KEYWORDS));
const POSTGRES_IDENTIFIER_KEYWORDS = new Set(SQL_KEYWORDS.map((keyword) => keyword.toLowerCase()));
function buildTableItems(
context: Pick<SqlCompletionContext, "prefix" | "qualifier">,
+200
View File
@@ -0,0 +1,200 @@
const SIMPLE_LOWER_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",
]);
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 quoteGaussDbJdbcIdentifier(identifier: string, identifierQuote: string): string {
if (isExplicitlyQuotedSqlIdentifier(identifier)) return identifier;
const quote = identifierQuote.trim();
if (!quote) return identifier;
const requiresQuote = !SIMPLE_LOWER_IDENTIFIER.test(identifier) || POSTGRES_RESERVED_IDENTIFIER_KEYWORDS.has(identifier) || (quote === "`" && MYSQL_ONLY_RESERVED_IDENTIFIER_KEYWORDS.has(identifier));
if (!requiresQuote) return identifier;
return `${quote}${identifier.replaceAll(quote, quote + quote)}${quote}`;
}