fix(sql): quote postgres reserved identifiers
This commit is contained in:
parent
3b54fa56df
commit
cc340605b8
|
|
@ -71,7 +71,7 @@ import { isTableDataEditable } from "@/lib/table/tableEditing";
|
|||
import { tableMetaForDataTab } from "@/lib/table/tableDataTabMeta";
|
||||
import { dataTabExecutionDatabase } from "@/lib/table/dataTabExecutionDatabase";
|
||||
import { formatShortcut } from "@/lib/editor/shortcutRegistry";
|
||||
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { codeMirrorSqlDialect, effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { chartableColumnIndexes } from "@/lib/dataGrid/chartData";
|
||||
import { elasticsearchJsonResponseForResult } from "@/lib/elasticsearch/elasticsearchJsonResponse";
|
||||
import * as api from "@/lib/backend/api";
|
||||
|
|
@ -268,11 +268,7 @@ const activeTabDimension = computed(() => {
|
|||
|
||||
const activeSqlFormatDialect = computed<SqlFormatDialect>(() => sqlFormatDialectForDbType(activeEffectiveDatabaseType.value));
|
||||
|
||||
const editorDialect = computed<"mysql" | "postgres" | "sqlserver">(() => {
|
||||
if (activeEffectiveDatabaseType.value === "postgres" || activeEffectiveDatabaseType.value === "kwdb") return "postgres";
|
||||
if (activeEffectiveDatabaseType.value === "sqlserver") return "sqlserver";
|
||||
return "mysql";
|
||||
});
|
||||
const editorDialect = computed<"mysql" | "postgres" | "sqlserver">(() => codeMirrorSqlDialect(activeEffectiveDatabaseType.value));
|
||||
|
||||
const shortcutModifier = computed(() => (navigator.platform.toLowerCase().includes("mac") ? "Cmd" : "Ctrl"));
|
||||
|
||||
|
|
|
|||
|
|
@ -2763,7 +2763,115 @@ function requiresPostgresIdentifierQuote(identifier: string): boolean {
|
|||
return POSTGRES_IDENTIFIER_KEYWORDS.has(identifier);
|
||||
}
|
||||
|
||||
const POSTGRES_IDENTIFIER_KEYWORDS = new Set(SQL_KEYWORDS.map((keyword) => keyword.toLowerCase()).concat(["current_user", "session_user", "user"]));
|
||||
// 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));
|
||||
|
||||
function buildTableItems(
|
||||
context: Pick<SqlCompletionContext, "prefix" | "qualifier">,
|
||||
|
|
|
|||
|
|
@ -344,6 +344,36 @@ test("leaves safe PostgreSQL column identifiers unquoted when completion inserts
|
|||
assert.equal(column?.apply, "article");
|
||||
});
|
||||
|
||||
test("quotes PostgreSQL reserved-word column identifiers when completion inserts them", () => {
|
||||
// Reserved words absent from the completion keyword phrase list (which has
|
||||
// "ORDER BY" but not "order") must still be quoted as column references.
|
||||
const reservedColumns = ["order", "do", "returning", "ilike", "window", "true"];
|
||||
const reservedColumnsByTable = new Map<string, SqlCompletionColumn[]>([["public.bookings", reservedColumns.map((name) => ({ name, table: "bookings", schema: "public", dataType: "text" }))]]);
|
||||
const sql = "select from bookings";
|
||||
const items = buildSqlCompletionItems(sql, "select ".length, {
|
||||
tables: [{ name: "bookings", schema: "public", type: "table" }],
|
||||
columnsByTable: reservedColumnsByTable,
|
||||
dialect: "postgres",
|
||||
});
|
||||
|
||||
for (const name of reservedColumns) {
|
||||
const column = items.find((item) => item.type === "column" && item.label === name);
|
||||
assert.equal(column?.apply, `"${name}"`, `expected reserved column "${name}" to be quoted`);
|
||||
}
|
||||
});
|
||||
|
||||
test("quotes PostgreSQL reserved-word table identifiers when completion inserts them", () => {
|
||||
const sql = "select * from ord";
|
||||
const items = buildSqlCompletionItems(sql, sql.length, {
|
||||
tables: [{ name: "order", schema: "public", type: "table" }],
|
||||
columnsByTable: new Map(),
|
||||
dialect: "postgres",
|
||||
});
|
||||
|
||||
const table = items.find((item) => item.type === "table" && item.label === "order");
|
||||
assert.equal(table?.apply, '"order"');
|
||||
});
|
||||
|
||||
test("suggests matching table names after FROM", () => {
|
||||
const sql = "select * from us";
|
||||
const items = buildSqlCompletionItems(sql, sql.length, {
|
||||
|
|
|
|||
Loading…
Reference in New Issue