fix(oracle): qualify wildcard before appending ROWID
This commit is contained in:
parent
da73867604
commit
358a736c6e
|
|
@ -59,11 +59,55 @@ describe("editable query hidden primary keys", () => {
|
|||
sourceExpressions: { __DBX_ROWID: "ROWIDTOCHAR(ROWID)" },
|
||||
}),
|
||||
).toEqual({
|
||||
sql: 'SELECT *, ROWIDTOCHAR(ROWID) AS "__DBX_PK_0" FROM APP.USERS t WHERE t.ACTIVE = 1',
|
||||
sql: 'SELECT t.*, ROWIDTOCHAR(ROWID) AS "__DBX_PK_0" FROM APP.USERS t WHERE t.ACTIVE = 1',
|
||||
projections: [{ sourceName: "__DBX_ROWID", alias: "__DBX_PK_0" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("qualifies a bare Oracle wildcard when appending a hidden key", () => {
|
||||
expect(
|
||||
buildQueryWithHiddenPrimaryKeys({
|
||||
sql: 'SELECT /*+ FULL("Users") */ * FROM APP."Users"',
|
||||
databaseType: "oracle",
|
||||
primaryKeys: ["__DBX_ROWID"],
|
||||
existingResultNames: ["ID", "NAME"],
|
||||
sourceExpressions: { __DBX_ROWID: "ROWIDTOCHAR(ROWID)" },
|
||||
})?.sql,
|
||||
).toBe('SELECT /*+ FULL("Users") */ "Users".*, ROWIDTOCHAR(ROWID) AS "__DBX_PK_0" FROM APP."Users"');
|
||||
|
||||
expect(
|
||||
buildQueryWithHiddenPrimaryKeys({
|
||||
sql: 'SELECT * FROM APP.USERS "u"',
|
||||
databaseType: "oracle",
|
||||
primaryKeys: ["__DBX_ROWID"],
|
||||
existingResultNames: ["ID", "NAME"],
|
||||
sourceExpressions: { __DBX_ROWID: "ROWIDTOCHAR(ROWID)" },
|
||||
})?.sql,
|
||||
).toBe('SELECT "u".*, ROWIDTOCHAR(ROWID) AS "__DBX_PK_0" FROM APP.USERS "u"');
|
||||
});
|
||||
|
||||
it("rewrites the reported Oracle queries without changing their filters", () => {
|
||||
expect(
|
||||
buildQueryWithHiddenPrimaryKeys({
|
||||
sql: "select * from t_zyys_vte_yyfxbd",
|
||||
databaseType: "oracle",
|
||||
primaryKeys: ["__DBX_ROWID"],
|
||||
existingResultNames: ["ID", "MBMC"],
|
||||
sourceExpressions: { __DBX_ROWID: "ROWIDTOCHAR(ROWID)" },
|
||||
})?.sql,
|
||||
).toBe('select t_zyys_vte_yyfxbd.*, ROWIDTOCHAR(ROWID) AS "__DBX_PK_0" from t_zyys_vte_yyfxbd');
|
||||
|
||||
expect(
|
||||
buildQueryWithHiddenPrimaryKeys({
|
||||
sql: "SELECT * from T_XT_MB WHERE mbmc ='结束时'",
|
||||
databaseType: "oracle",
|
||||
primaryKeys: ["__DBX_ROWID"],
|
||||
existingResultNames: ["ID", "MBMC"],
|
||||
sourceExpressions: { __DBX_ROWID: "ROWIDTOCHAR(ROWID)" },
|
||||
})?.sql,
|
||||
).toBe(`SELECT T_XT_MB.*, ROWIDTOCHAR(ROWID) AS "__DBX_PK_0" from T_XT_MB WHERE mbmc ='结束时'`);
|
||||
});
|
||||
|
||||
it("preserves a WHERE subquery when adding an Oracle ROWID", () => {
|
||||
expect(
|
||||
buildQueryWithHiddenPrimaryKeys({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { sqlSemanticDialectFor } from "@/lib/sql/semantic/dialect";
|
||||
import { buildSqlSemanticModel } from "@/lib/sql/semantic/model";
|
||||
import { tokenizeSqlSemantic } from "@/lib/sql/semantic/tokens";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ export function buildQueryWithHiddenPrimaryKeys(options: { sql: string; database
|
|||
return { sourceName, alias };
|
||||
});
|
||||
const expressions = projections.map(({ sourceName, alias }) => `${options.sourceExpressions?.[sourceName] ?? dialect.quoteIdentifier(sourceName)} AS ${dialect.quoteIdentifier(alias)}`);
|
||||
const sql = appendSelectProjections(options.sql, expressions);
|
||||
const sql = appendSelectProjections(options.sql, expressions, options.databaseType);
|
||||
return sql ? { sql, projections } : undefined;
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ export function hiddenResultColumnIndexes(columns: string[], projections: Hidden
|
|||
});
|
||||
}
|
||||
|
||||
function appendSelectProjections(sql: string, expressions: string[]): string | undefined {
|
||||
function appendSelectProjections(sql: string, expressions: string[], databaseType: DatabaseType): string | undefined {
|
||||
if (expressions.length === 0) return sql;
|
||||
const tokens = tokenizeSqlSemantic(sql);
|
||||
const selectIndex = tokens.findIndex((token) => token.kind === "word" && token.depth === 0 && token.normalized === "select");
|
||||
|
|
@ -57,5 +58,26 @@ function appendSelectProjections(sql: string, expressions: string[]): string | u
|
|||
break;
|
||||
}
|
||||
if (projectionEnd === undefined) return undefined;
|
||||
return `${sql.slice(0, projectionEnd)}, ${expressions.join(", ")}${sql.slice(projectionEnd)}`;
|
||||
|
||||
const projectionTokens = tokens.slice(selectIndex + 1, fromIndex).filter((token) => token.kind !== "comment");
|
||||
const bareWildcard = projectionTokens.length === 1 && projectionTokens[0]?.text === "*" ? projectionTokens[0] : undefined;
|
||||
const sourceReference = bareWildcard && databaseType === "oracle" ? singleOracleSourceReference(sql, bareWildcard.span.start) : undefined;
|
||||
if (!sourceReference || !bareWildcard) {
|
||||
return `${sql.slice(0, projectionEnd)}, ${expressions.join(", ")}${sql.slice(projectionEnd)}`;
|
||||
}
|
||||
|
||||
const qualifiedWildcard = `${sourceReference}.*`;
|
||||
const qualifiedSql = `${sql.slice(0, bareWildcard.span.start)}${qualifiedWildcard}${sql.slice(bareWildcard.span.end)}`;
|
||||
const qualifiedProjectionEnd = projectionEnd + qualifiedWildcard.length - bareWildcard.text.length;
|
||||
return `${qualifiedSql.slice(0, qualifiedProjectionEnd)}, ${expressions.join(", ")}${qualifiedSql.slice(qualifiedProjectionEnd)}`;
|
||||
}
|
||||
|
||||
function singleOracleSourceReference(sql: string, cursor: number): string | undefined {
|
||||
const model = buildSqlSemanticModel(sql, cursor, { databaseType: "oracle" });
|
||||
const sources = model.rowSources.filter((source) => source.kind === "table");
|
||||
if (sources.length !== 1) return undefined;
|
||||
|
||||
const source = sources[0]!;
|
||||
if (source.aliasSpan) return sql.slice(source.aliasSpan.start, source.aliasSpan.end);
|
||||
return source.qualifiedName?.parts[source.qualifiedName.parts.length - 1]?.raw;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue