fix(sql): preserve Oracle identifier semantics in star expansion
This commit is contained in:
1 parent
3597a00759
commit
aa4ed91c40
3 files changed
+81
-11
No files matched your search
@@ -475,13 +475,13 @@ FROM (
|
||||
});
|
||||
|
||||
it.each([
|
||||
["Oracle", "oracle", "mysql", '"ID", o."created at", o."SELECT", o.safe_name'],
|
||||
["MySQL", "mysql", "mysql", "`ID`, o.`created at`, o.`SELECT`, o.safe_name"],
|
||||
["PostgreSQL", "postgres", "postgres", '"ID", o."created at", o."SELECT", o.safe_name'],
|
||||
["SQL Server", "sqlserver", "sqlserver", "[ID], o.[created at], o.[SELECT], o.safe_name"],
|
||||
["dialect fallback", undefined, "mysql", "`ID`, o.`created at`, o.`SELECT`, o.safe_name"],
|
||||
["Oracle", "oracle", "mysql", 'ID, o."created at", o."SELECT", o."safe_name", o."OrderId", o."order_id"'],
|
||||
["MySQL", "mysql", "mysql", "`ID`, o.`created at`, o.`SELECT`, o.safe_name, o.`OrderId`, o.order_id"],
|
||||
["PostgreSQL", "postgres", "postgres", '"ID", o."created at", o."SELECT", o.safe_name, o."OrderId", o.order_id'],
|
||||
["SQL Server", "sqlserver", "sqlserver", "[ID], o.[created at], o.[SELECT], o.safe_name, o.[OrderId], o.order_id"],
|
||||
["dialect fallback", undefined, "mysql", "`ID`, o.`created at`, o.`SELECT`, o.safe_name, o.`OrderId`, o.order_id"],
|
||||
] as const)("uses %s identifier quoting in qualified star completion items", (_label, databaseType, dialect, expected) => {
|
||||
const columnsByTable = new Map<string, SqlCompletionColumn[]>([["orders", ["ID", "created at", "SELECT", "safe_name"].map((name) => ({ name, table: "orders" }))]]);
|
||||
const columnsByTable = new Map<string, SqlCompletionColumn[]>([["orders", ["ID", "created at", "SELECT", "safe_name", "OrderId", "order_id"].map((name) => ({ name, table: "orders" }))]]);
|
||||
|
||||
const starItems = semanticCompletion("SELECT o.*| FROM orders o", { columnsByTable }, { databaseType, dialect }).items;
|
||||
const selectAllItems = semanticCompletion("SELECT o.| FROM orders o", { columnsByTable }, { databaseType, dialect }).items;
|
||||
@@ -492,13 +492,13 @@ FROM (
|
||||
|
||||
it("uses Oracle quoting for an unqualified multi-table star completion item", () => {
|
||||
const columnsByTable = new Map<string, SqlCompletionColumn[]>([
|
||||
["ORDERS", ["ID", "created at"].map((name) => ({ name, table: "ORDERS" }))],
|
||||
["AUDIT", ["ID", "SELECT"].map((name) => ({ name, table: "AUDIT" }))],
|
||||
["ORDERS", ["ID", "created at", "OrderId"].map((name) => ({ name, table: "ORDERS" }))],
|
||||
["AUDIT", ["ID", "SELECT", "order_id"].map((name) => ({ name, table: "AUDIT" }))],
|
||||
]);
|
||||
|
||||
const { items } = semanticCompletion("SELECT *| FROM ORDERS o JOIN AUDIT a ON a.ID = o.ID", { columnsByTable }, { databaseType: "oracle", dialect: "mysql" });
|
||||
|
||||
expect(items.find((item) => item.label === "* \u2192 columns")?.apply).toBe('o."ID", o."created at", a."ID", a."SELECT"');
|
||||
expect(items.find((item) => item.label === "* \u2192 columns")?.apply).toBe('o.ID, o."created at", o."OrderId", a.ID, a."SELECT", a."order_id"');
|
||||
});
|
||||
|
||||
it("generates collision-free table aliases from semantic row sources", () => {
|
||||
|
||||
@@ -334,7 +334,76 @@ describe("SELECT star expansion", () => {
|
||||
qualifierSql,
|
||||
databaseType,
|
||||
),
|
||||
).toBe(`id, ${qualifierSql}.${quotedColumn}`);
|
||||
).toBe(`${databaseType === "oracle" ? '"id"' : "id"}, ${qualifierSql}.${quotedColumn}`);
|
||||
});
|
||||
|
||||
it("does not quote all-uppercase Oracle columns when expanding alias.* (regression #9163)", () => {
|
||||
const sql = "SELECT t.* FROM device_table AS t";
|
||||
const cursor = sql.indexOf("*") + 1;
|
||||
const context = sqlCompletionContextFromSemantic(buildSqlSemanticModel(sql, cursor, { databaseType: "oracle", dialect: "mysql" }), getSqlCompletionContext(sql, cursor, { databaseType: "oracle", dialect: "mysql" }));
|
||||
|
||||
expect(
|
||||
buildSelectStarExpansion(
|
||||
context,
|
||||
new Map([
|
||||
[
|
||||
"device_table",
|
||||
[
|
||||
{ name: "AGENT_NAME", table: "device_table" },
|
||||
{ name: "PROTOCOL", table: "device_table" },
|
||||
{ name: "created at", table: "device_table" },
|
||||
],
|
||||
],
|
||||
]),
|
||||
"mysql",
|
||||
"t",
|
||||
"oracle",
|
||||
),
|
||||
).toBe('AGENT_NAME, t.PROTOCOL, t."created at"');
|
||||
});
|
||||
|
||||
it("does not quote all-uppercase Oracle columns for an unqualified star when only databaseType is set", () => {
|
||||
const sql = "SELECT * FROM device_table";
|
||||
const cursor = "SELECT *".length;
|
||||
const context = sqlCompletionContextFromSemantic(buildSqlSemanticModel(sql, cursor, { databaseType: "oracle" }), getSqlCompletionContext(sql, cursor, { databaseType: "oracle" }));
|
||||
|
||||
expect(
|
||||
buildSelectStarExpansion(
|
||||
context,
|
||||
new Map([
|
||||
[
|
||||
"device_table",
|
||||
[
|
||||
{ name: "AGENT_NAME", table: "device_table" },
|
||||
{ name: "SPARE1", table: "device_table" },
|
||||
],
|
||||
],
|
||||
]),
|
||||
undefined,
|
||||
undefined,
|
||||
"oracle",
|
||||
),
|
||||
).toBe("AGENT_NAME, SPARE1");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["oracle", "mysql"],
|
||||
["oracle", undefined],
|
||||
[undefined, "oracle"],
|
||||
] as const)("preserves required Oracle column quotes with databaseType %s and dialect %s", (databaseType, dialect) => {
|
||||
const columnsByTable = new Map([["orders", ["OrderId", "order_id", "AGENT_NAME", "SELECT", "created at", "ACCOUNT$SYS"].map((name) => ({ name, table: "orders" }))]]);
|
||||
const contextOptions = { databaseType, dialect: "mysql" as const };
|
||||
|
||||
for (const [sql, expected] of [
|
||||
["SELECT o.* FROM orders o", '"OrderId", o."order_id", o.AGENT_NAME, o."SELECT", o."created at", o.ACCOUNT$SYS'],
|
||||
["SELECT * FROM orders", '"OrderId", "order_id", AGENT_NAME, "SELECT", "created at", ACCOUNT$SYS'],
|
||||
]) {
|
||||
const cursor = sql.indexOf("*") + 1;
|
||||
const context = sqlCompletionContextFromSemantic(buildSqlSemanticModel(sql, cursor, contextOptions), getSqlCompletionContext(sql, cursor, contextOptions));
|
||||
|
||||
expect(buildSelectStarExpansion(context, columnsByTable, dialect, context.qualifier, databaseType)).toBe(expected);
|
||||
expect(buildSelectStarExpansion(context, new Map(), dialect, context.qualifier, databaseType)).toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
it("expands an unqualified star from result columns when the table has an alias", () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { BACKSLASH_ESCAPE_STRING_DIALECTS } from "@/lib/sql/sqlStatementRanges";
|
||||
import { requiresMysqlIdentifierQuote, requiresPostgresIdentifierQuote } from "@/lib/sql/sqlIdentifier";
|
||||
import { identifierMatchScore, matchesIdentifierSearch } from "@/lib/sql/identifierSearch";
|
||||
import { containsHan, orderedSubsequenceSpan, pinyinFirstLetters } from "@/lib/common/pinyin";
|
||||
import { quoteTableIdentifier } from "@/lib/table/tableSelectSql";
|
||||
import { quoteTableIdentifier, quoteTableIdentifierIfNeeded } from "@/lib/table/tableSelectSql";
|
||||
import { driverProfileCompletionObjects, driverProfileCompletionTableMetadata, driverProfileCompletionTables, driverProfileRoutineSignatures } from "@/lib/database/driverProfileExtensions";
|
||||
import { DORIS_FUNCTION_DOCS, DORIS_FUNCTION_SIGNATURES } from "@/lib/sql/doris/functions";
|
||||
import { rejectsAliasReferenceInHaving } from "@/lib/database/databaseFeatureSupport";
|
||||
@@ -3760,6 +3760,7 @@ function quoteCompletionRoutineName(applyName: string, dialect?: SqlCompletionAp
|
||||
}
|
||||
|
||||
function quoteSelectStarColumnIdentifier(identifier: string, dialect?: SqlCompletionApplyDialect, databaseType?: DatabaseType): string {
|
||||
if (databaseType === "oracle" || (databaseType === undefined && dialect === "oracle")) return quoteTableIdentifierIfNeeded("oracle", identifier);
|
||||
if (!requiresPostgresIdentifierQuote(identifier, POSTGRES_IDENTIFIER_KEYWORDS)) return identifier;
|
||||
if (databaseType) return quoteTableIdentifier(databaseType, identifier);
|
||||
if (dialect === "mysql") return `\`${identifier.replaceAll("`", "``")}\``;
|
||||
|
||||
Reference in new issue
Block a user