@itwin/core-backend
Version:
iTwin.js backend components
177 lines • 7.63 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { assert } from "chai";
import { QueryBinder, QueryRowFormat } from "@itwin/core-common";
import { SnapshotDb } from "../../core-backend";
import { IModelTestUtils } from "../IModelTestUtils";
import { SequentialLogMatcher } from "../SequentialLogMatcher";
// cspell:ignore mirukuru ibim
async function executeQuery(iModel, ecsql, bindings, abbreviateBlobs) {
const rows = [];
for await (const queryRow of iModel.createQueryReader(ecsql, QueryBinder.from(bindings), { rowFormat: QueryRowFormat.UseJsPropertyNames, abbreviateBlobs })) {
rows.push(queryRow.toRow());
}
return rows;
}
describe("Common table expression support in ECSQL", () => {
let imodel1;
before(async () => {
imodel1 = SnapshotDb.openFile(IModelTestUtils.resolveAssetFile("test.bim"));
});
after(async () => {
imodel1.close();
});
it("collect base properties recursively", async () => {
const query = `
WITH RECURSIVE
base_classes (aId, aParentId, aPath, aDepth) AS (
SELECT c.ECInstanceId, null, c.Name, 0 FROM meta.ECClassDef c WHERE c.Name=?
UNION ALL
SELECT c.ECInstanceId, cbc.TargetECInstanceId, aPath || '/' || c.Name, aDepth + 1
FROM meta.ECClassDef c
JOIN meta.ClassHasBaseClasses cbc ON cbc.SourceECInstanceId = c.ECInstanceId
JOIN base_classes ON aId = cbc.TargetECInstanceId
ORDER BY 1
)
SELECT p.Name prop from base_classes join meta.ECPropertyDef p on p.Class.id = aId GROUP BY p.Name`;
const rows = await executeQuery(imodel1, query, ["Element"]);
const expected = ["Angle", "BaseModel", "BBoxHigh", "BBoxLow", "Border", "BorderTemplate", "Category", "CategorySelector", "CodeScope", "CodeSpec", "CodeValue", "Data", "Description", "DisplayStyle", "DrawingModel", "Enabled", "Extents", "EyePoint", "FederationGuid", "Flags", "FocusDistance", "Format", "GeometryStream", "Height", "InSpatialIndex", "IsCameraOn", "IsPrivate", "JsonProperties", "LastMod", "LensAngle", "Model", "ModelSelector", "Name", "Origin", "PaletteName", "Parent", "Pitch", "Properties", "Rank", "Recipe", "RepositoryGuid", "Roll", "Rotation", "RotationAngle", "Scale", "SheetTemplate", "Type", "TypeDefinition", "Url", "UserLabel", "View", "ViewAttachment", "Width", "Yaw"];
const actual = rows.map((r) => r.prop);
assert.sameOrderedMembers(actual, expected);
});
it("generate mandelbrot set", async () => {
const rows = await executeQuery(imodel1, `
WITH RECURSIVE
[]([x]) AS(
VALUES (- 2.0)
UNION ALL
SELECT [x] + 0.05
FROM [xaxis]
WHERE [x] < 1.2
),
[]([y]) AS(
VALUES (- 1.0)
UNION ALL
SELECT [y] + 0.1
FROM [yaxis]
WHERE [y] < 1.0
),
[]([iter], [cx], [cy], [x], [y]) AS(
SELECT
0,
[],
[],
0.0,
0.0
FROM [xaxis],
[]
UNION ALL
SELECT
[] + 1,
[],
[],
[] * [x] - [y] * [y] + [cx],
2.0 * [x] * [y] + [cy]
FROM [m]
WHERE ([x] * [x] + [y] * [y]) < 4.0 AND [iter] < 28
),
[]([iter], [cx], [cy]) AS(
SELECT
MAX ([iter]),
[cx],
[cy]
FROM [m]
GROUP BY
[cx],
[cy]
),
[a]([t]) AS(
SELECT GROUP_CONCAT (SUBSTR (' .+*#', 1 + (CASE WHEN [iter] / 7 > 4 THEN 4 ELSE [iter] / 7 END), 1), '')
FROM [m2]
GROUP BY [cy]
)
SELECT GROUP_CONCAT (RTRIM ([t]), CHAR (0xa)) mandelbrot_set
FROM [a];
`);
const expected = " ....#\n" +
" ..#*..\n" +
" ..+####+.\n" +
" .......+####.... +\n" +
" ..##+*##########+.++++\n" +
" .+.##################+.\n" +
" .............+###################+.+\n" +
" ..++..#.....*#####################+.\n" +
" ...+#######++#######################.\n" +
" ....+*################################.\n" +
" #############################################...\n" +
" ....+*################################.\n" +
" ...+#######++#######################.\n" +
" ..++..#.....*#####################+.\n" +
" .............+###################+.+\n" +
" .+.##################+.\n" +
" ..##+*##########+.++++\n" +
" .......+####.... +\n" +
" ..+####+.\n" +
" ..#*..\n" +
" ....#\n" +
" +.";
assert(rows[0].mandelbrot_set === expected);
});
it("basic cte test", async () => {
let rows = [];
rows = await executeQuery(imodel1, `
WITH RECURSIVE
cnt (x,y) AS (
SELECT 100, 200
UNION ALL
SELECT x+1, 200 FROM cnt WHERE x<210
)
SELECT * from cnt`);
assert(rows.length === 111);
rows = await executeQuery(imodel1, `
WITH RECURSIVE
cnt (x,y) AS (
SELECT 100, 200
)
SELECT * from cnt`);
let slm = new SequentialLogMatcher();
// these two are generated by sqlite
slm.append().error().category("ECDb").message(/BE_SQLITE_ERROR duplicate WITH table name/gm);
assert(rows.length === 1);
try {
rows = await executeQuery(imodel1, `
WITH
cte_1 (a,b,c) AS (
SELECT 100, 400, 300
),
cte_1 (a,b,c) AS (
SELECT 100, 400, 300
)
SELECT * from cte_1`);
assert(false);
}
catch {
assert(true); // should fail as cte_1 is used for two ct expression.
}
assert.isTrue(slm.finishAndDispose());
slm = new SequentialLogMatcher();
// these two are generated by ECSQL. Its not clear why this message is logged twice.
slm.append().error().category("ECDb").message(/Common table 'cte_1' has 3 values for columns 2/gm);
try {
rows = await executeQuery(imodel1, `
WITH
cte_1 (a,b,c) AS (
SELECT 100, 400
)
SELECT * from cte_1`);
assert(false);
}
catch {
assert(true); // number are to ct expression does not match select
}
assert.isTrue(slm.finishAndDispose());
});
});
//# sourceMappingURL=CTE.test.js.map