fix(sqlserver): support legacy pagination syntax
This commit is contained in:
parent
5549828e27
commit
82f78bc805
|
|
@ -87,9 +87,10 @@ export function buildPaginatedQuerySql(
|
|||
const base = `SELECT * FROM (${statement.sql}) ${alias}`;
|
||||
|
||||
if (databaseType === "sqlserver") {
|
||||
if (safeOffset > 0) return { ok: false, reason: "unsupported" };
|
||||
return {
|
||||
ok: true,
|
||||
sql: `${base} ORDER BY (SELECT NULL) OFFSET ${safeOffset} ROWS FETCH NEXT ${safeLimit} ROWS ONLY`,
|
||||
sql: `SELECT TOP (${safeLimit}) * FROM (${statement.sql}) ${alias}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,8 +90,14 @@ export function buildTableSelectSql(options: BuildTableSelectSqlOptions): string
|
|||
}
|
||||
|
||||
if (databaseType === "sqlserver") {
|
||||
const stableOrder = order || " ORDER BY (SELECT NULL)";
|
||||
return `SELECT * FROM ${table}${where}${stableOrder} OFFSET ${options.offset ?? 0} ROWS FETCH NEXT ${limit} ROWS ONLY`;
|
||||
return buildSqlServerTableSelectSql({
|
||||
table,
|
||||
where,
|
||||
orderBy: orderBy ?? "(SELECT NULL)",
|
||||
columns: options.columns,
|
||||
limit,
|
||||
offset: options.offset ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
const offset = options.offset ? ` OFFSET ${options.offset}` : "";
|
||||
|
|
@ -121,6 +127,34 @@ function buildSelectColumns(databaseType: DatabaseType | undefined, columns?: st
|
|||
.join(", ");
|
||||
}
|
||||
|
||||
function buildSqlServerTableSelectSql(options: {
|
||||
table: string;
|
||||
where: string;
|
||||
orderBy: string;
|
||||
columns?: string[];
|
||||
limit: number;
|
||||
offset: number;
|
||||
}): string {
|
||||
const columns = options.columns?.length
|
||||
? options.columns.map((column) => quoteTableIdentifier("sqlserver", column)).join(", ")
|
||||
: "*";
|
||||
const order = options.orderBy === "(SELECT NULL)" ? "" : ` ORDER BY ${options.orderBy}`;
|
||||
if (options.offset <= 0) {
|
||||
return `SELECT TOP (${options.limit}) ${columns} FROM ${options.table}${options.where}${order}`;
|
||||
}
|
||||
|
||||
const pageAlias = quoteTableIdentifier("sqlserver", "dbx_page");
|
||||
const rowNumberAlias = quoteTableIdentifier("sqlserver", "__dbx_row_num");
|
||||
const end = options.offset + options.limit;
|
||||
const rowNumberOrder = `ORDER BY ${options.orderBy}`;
|
||||
return (
|
||||
`WITH ${pageAlias} AS (` +
|
||||
`SELECT ${columns}, ROW_NUMBER() OVER (${rowNumberOrder}) AS ${rowNumberAlias} FROM ${options.table}${options.where}` +
|
||||
`) SELECT ${columns} FROM ${pageAlias}` +
|
||||
` WHERE ${rowNumberAlias} > ${options.offset} AND ${rowNumberAlias} <= ${end} ORDER BY ${rowNumberAlias}`
|
||||
);
|
||||
}
|
||||
|
||||
function isTdengineTbname(databaseType: DatabaseType | undefined, name: string): boolean {
|
||||
return databaseType === "tdengine" && name.toLowerCase() === DBX_TDENGINE_TBNAME_COLUMN;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ test("builds capped export page queries", () => {
|
|||
tableName: "accounts",
|
||||
limit: DATABASE_EXPORT_ROW_LIMIT,
|
||||
}),
|
||||
`SELECT * FROM [dbo].[accounts] ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH NEXT ${DATABASE_EXPORT_ROW_LIMIT} ROWS ONLY`,
|
||||
`SELECT TOP (${DATABASE_EXPORT_ROW_LIMIT}) * FROM [dbo].[accounts]`,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,21 @@ test("uses MySQL style quoting for paginated query alias", () => {
|
|||
});
|
||||
});
|
||||
|
||||
test("uses SQL Server offset fetch pagination", () => {
|
||||
const result = buildPaginatedQuerySql("SELECT id FROM users", "sqlserver", 100, 300);
|
||||
test("uses SQL Server TOP pagination for the first page to support SQL Server 2008", () => {
|
||||
const result = buildPaginatedQuerySql("SELECT id FROM users", "sqlserver", 100, 0);
|
||||
|
||||
assert.deepEqual(result, {
|
||||
ok: true,
|
||||
sql: "SELECT * FROM (SELECT id FROM users) [dbx_page] ORDER BY (SELECT NULL) OFFSET 300 ROWS FETCH NEXT 100 ROWS ONLY",
|
||||
sql: "SELECT TOP (100) * FROM (SELECT id FROM users) [dbx_page]",
|
||||
});
|
||||
});
|
||||
|
||||
test("does not generate SQL Server OFFSET pagination for later query-result pages", () => {
|
||||
const result = buildPaginatedQuerySql("SELECT id FROM users", "sqlserver", 100, 300);
|
||||
|
||||
assert.deepEqual(result, {
|
||||
ok: false,
|
||||
reason: "unsupported",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -133,7 +142,10 @@ test("uses SQL pagination instead of jdbc cursor for random agent page jumps", (
|
|||
useAgentCursor: true,
|
||||
});
|
||||
|
||||
assert.equal(plan.sqlToExecute, 'SELECT * FROM (SELECT * FROM events) "dbx_page" OFFSET 1500 ROWS FETCH FIRST 500 ROWS ONLY');
|
||||
assert.equal(
|
||||
plan.sqlToExecute,
|
||||
'SELECT * FROM (SELECT * FROM events) "dbx_page" OFFSET 1500 ROWS FETCH FIRST 500 ROWS ONLY',
|
||||
);
|
||||
assert.equal(plan.pageSql, plan.sqlToExecute);
|
||||
assert.equal(plan.pageLimit, 500);
|
||||
assert.equal(plan.pageOffset, 1500);
|
||||
|
|
|
|||
|
|
@ -99,10 +99,7 @@ test("qualifies JDBC table data default order columns with a table alias", () =>
|
|||
limit: 100,
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
sql,
|
||||
'SELECT * FROM "SJT_THEME"."BATCH_QUERY" dbx_t ORDER BY dbx_t."TABLE_NAME" ASC LIMIT 100;',
|
||||
);
|
||||
assert.equal(sql, 'SELECT * FROM "SJT_THEME"."BATCH_QUERY" dbx_t ORDER BY dbx_t."TABLE_NAME" ASC LIMIT 100;');
|
||||
});
|
||||
|
||||
test("expands Hive table data queries into aliased table columns", () => {
|
||||
|
|
@ -116,7 +113,7 @@ test("expands Hive table data queries into aliased table columns", () => {
|
|||
assert.equal(sql, "SELECT `id` AS `id`, `name` AS `name` FROM `departments` LIMIT 100;");
|
||||
});
|
||||
|
||||
test("builds SQL Server first page query with schema-aware brackets", () => {
|
||||
test("builds SQL Server first page query with TOP for SQL Server 2008 compatibility", () => {
|
||||
const sql = buildTableSelectSql({
|
||||
databaseType: "sqlserver",
|
||||
schema: "dbo",
|
||||
|
|
@ -126,23 +123,36 @@ test("builds SQL Server first page query with schema-aware brackets", () => {
|
|||
primaryKeys: ["id"],
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
sql,
|
||||
"SELECT * FROM [dbo].[accounts] WHERE (id = 1) ORDER BY [id] ASC OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY",
|
||||
);
|
||||
assert.equal(sql, "SELECT TOP (25) * FROM [dbo].[accounts] WHERE (id = 1) ORDER BY [id] ASC");
|
||||
});
|
||||
|
||||
test("builds SQL Server later pages with OFFSET and FETCH", () => {
|
||||
test("builds SQL Server first page query without a synthetic ORDER BY when no order is available", () => {
|
||||
const sql = buildTableSelectSql({
|
||||
databaseType: "sqlserver",
|
||||
schema: "dbo",
|
||||
tableName: "logs",
|
||||
columns: ["message"],
|
||||
limit: 25,
|
||||
});
|
||||
|
||||
assert.equal(sql, "SELECT TOP (25) [message] FROM [dbo].[logs]");
|
||||
});
|
||||
|
||||
test("builds SQL Server later pages with ROW_NUMBER for SQL Server 2008 compatibility", () => {
|
||||
const sql = buildTableSelectSql({
|
||||
databaseType: "sqlserver",
|
||||
schema: "sales",
|
||||
tableName: "orders",
|
||||
columns: ["order_id", "customer"],
|
||||
primaryKeys: ["order_id"],
|
||||
limit: 50,
|
||||
offset: 100,
|
||||
});
|
||||
|
||||
assert.equal(sql, "SELECT * FROM [sales].[orders] ORDER BY [order_id] ASC OFFSET 100 ROWS FETCH NEXT 50 ROWS ONLY");
|
||||
assert.equal(
|
||||
sql,
|
||||
"WITH [dbx_page] AS (SELECT [order_id], [customer], ROW_NUMBER() OVER (ORDER BY [order_id] ASC) AS [__dbx_row_num] FROM [sales].[orders]) SELECT [order_id], [customer] FROM [dbx_page] WHERE [__dbx_row_num] > 100 AND [__dbx_row_num] <= 150 ORDER BY [__dbx_row_num]",
|
||||
);
|
||||
});
|
||||
|
||||
test("builds SQL Server pages with fallback order columns when there is no primary key", () => {
|
||||
|
|
@ -150,12 +160,16 @@ test("builds SQL Server pages with fallback order columns when there is no prima
|
|||
databaseType: "sqlserver",
|
||||
schema: "dbo",
|
||||
tableName: "logs",
|
||||
columns: ["created_at", "message"],
|
||||
fallbackOrderColumns: ["created_at"],
|
||||
limit: 50,
|
||||
offset: 50,
|
||||
});
|
||||
|
||||
assert.equal(sql, "SELECT * FROM [dbo].[logs] ORDER BY [created_at] ASC OFFSET 50 ROWS FETCH NEXT 50 ROWS ONLY");
|
||||
assert.equal(
|
||||
sql,
|
||||
"WITH [dbx_page] AS (SELECT [created_at], [message], ROW_NUMBER() OVER (ORDER BY [created_at] ASC) AS [__dbx_row_num] FROM [dbo].[logs]) SELECT [created_at], [message] FROM [dbx_page] WHERE [__dbx_row_num] > 50 AND [__dbx_row_num] <= 100 ORDER BY [__dbx_row_num]",
|
||||
);
|
||||
});
|
||||
|
||||
test("builds Oracle table data queries with ROWID for keyless editing", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue