-
Notifications
You must be signed in to change notification settings - Fork 214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for common table expression #697
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
9a1f718
cte test and docs
khanaffan 3af1e5d
rush change
khanaffan 1775a63
NextVersion
khanaffan 4e2bbea
Merge branch 'master' into cte-test-and-docs
khanaffan 0c3fc42
Merge branch 'master' into cte-test-and-docs
mergify[bot] d9062db
Merge branch 'master' into cte-test-and-docs
mergify[bot] 411b0cc
Merge branch 'master' into cte-test-and-docs
mergify[bot] 7f5de2c
Merge branch 'master' into cte-test-and-docs
mergify[bot] 898ca17
Merge branch 'master' of /~https://github.com/imodeljs/imodeljs into ct…
khanaffan 21ac28f
spelling+clarity
pmconne efc15d9
Fix link.
pmconne f4c03ec
ECSql => ECSQL
pmconne File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
common/changes/@bentley/imodeljs-backend/master_2021-02-01-21-52.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"changes": [ | ||
{ | ||
"packageName": "@bentley/imodeljs-backend", | ||
"comment": "Add docs and test for CTE support", | ||
"type": "none" | ||
} | ||
], | ||
"packageName": "@bentley/imodeljs-backend", | ||
"email": "khanaffan@users.noreply.github.com" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
|
||
/*--------------------------------------------------------------------------------------------- | ||
* 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 { IModelDb, SnapshotDb } from "../../imodeljs-backend"; | ||
import { IModelTestUtils } from "../IModelTestUtils"; | ||
|
||
// cspell:ignore mirukuru ibim | ||
|
||
async function executeQuery(iModel: IModelDb, ecsql: string, bindings?: any[] | object, abbreviateBlobs?: boolean): Promise<any[]> { | ||
const rows: any[] = []; | ||
for await (const row of iModel.query(ecsql, bindings, undefined, undefined, undefined, abbreviateBlobs)) { | ||
rows.push(row); | ||
} | ||
return rows; | ||
} | ||
|
||
describe("Common table expression support in ECSQL", () => { | ||
let imodel1: SnapshotDb; | ||
|
||
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 group_concat( DISTINCT p.Name) prop from base_classes join meta.ECPropertyDef p on p.Class.id = aId`; | ||
const rows = await executeQuery(imodel1, query, ["Element"]); | ||
const expected = ["BBoxHigh", "BBoxLow", "Category", "GeometryStream", "Origin", "Rotation", "TypeDefinition", "CodeScope", "CodeSpec", "CodeValue", "FederationGuid", "JsonProperties", "LastMod", "Model", "Parent", "UserLabel", "Description", "Rank", "IsPrivate", "Recipe", "Data", "Type", "Angle", "Pitch", "Roll", "Yaw", "BaseModel", "Extents", "RotationAngle", "CategorySelector", "DisplayStyle", "Properties", "Name", "InSpatialIndex", "Enabled", "ModelSelector", "EyePoint", "FocusDistance", "IsCameraOn", "LensAngle", "RepositoryGuid", "Url", "PaletteName", "Height", "Scale", "SheetTemplate", "Width", "Border", "BorderTemplate", "Flags", "Format", "View", "DrawingModel", "ViewAttachment"]; | ||
const actual = (rows[0].prop as string).split(","); | ||
assert.sameOrderedMembers(actual, expected); | ||
}); | ||
|
||
it("Generate mandelbrot set", async () => { | ||
const rows = await executeQuery(imodel1, ` | ||
WITH RECURSIVE | ||
[xaxis]([x]) AS( | ||
VALUES (- 2.0) | ||
UNION ALL | ||
SELECT [x] + 0.05 | ||
FROM [xaxis] | ||
WHERE [x] < 1.2 | ||
), | ||
[yaxis]([y]) AS( | ||
VALUES (- 1.0) | ||
UNION ALL | ||
SELECT [y] + 0.1 | ||
FROM [yaxis] | ||
WHERE [y] < 1.0 | ||
), | ||
[m]([iter], [cx], [cy], [x], [y]) AS( | ||
SELECT | ||
0, | ||
[x], | ||
[y], | ||
0.0, | ||
0.0 | ||
FROM [xaxis], | ||
[yaxis] | ||
UNION ALL | ||
SELECT | ||
[iter] + 1, | ||
[cx], | ||
[cy], | ||
[x] * [x] - [y] * [y] + [cx], | ||
2.0 * [x] * [y] + [cy] | ||
FROM [m] | ||
WHERE ([x] * [x] + [y] * [y]) < 4.0 AND [iter] < 28 | ||
), | ||
[m2]([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 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`); | ||
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. | ||
} | ||
|
||
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 | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# Common table expression | ||
|
||
``` | ||
WITH [RECURSIVE] cte-table-name AS ( select-stmt )[,...] primary-select-stmt | ||
``` | ||
|
||
## What are Common Table Expressions? | ||
Common table expressions ("CTEs") act like temporary views that exist only for the duration of a single ECSQL statement. There are two types of CTE: | ||
|
||
### Ordinary Common Table Expressions | ||
This is mainly used to factor out subqueries, making the overall ECSQL statement easier to read and understand. It contains just a `SELECT` statement with or without `RECURSIVE` keyword. | ||
|
||
```sql | ||
WITH | ||
el (Id, ClassId) AS ( | ||
SELECT ECInstanceId, ECClassId FROM bis.Element | ||
) SELECT * FROM el; | ||
``` | ||
|
||
### Recursive Common Table Expressions | ||
|
||
A recursive common table expression can be used to walk a tree or graph. It is of the following form: | ||
|
||
``` | ||
cte-table-name AS ( initial-select) UNION [ALL] recursive-select) | ||
``` | ||
|
||
Here is a simple example of how we can write a CTE. In the following query we want to generate a sequence from 1 through 5. We start with an initial value of x = 1 and then recursively do x+1 until the value of x is less then 6. | ||
|
||
```sql | ||
WITH RECURSIVE | ||
cnt (x) AS ( | ||
SELECT 1 | ||
UNION ALL | ||
SELECT x+1 FROM cnt WHERE x<6 | ||
) | ||
SELECT * from cnt; | ||
|
||
-- output | ||
x | ||
------ | ||
1 | ||
2 | ||
3 | ||
4 | ||
5 | ||
6 | ||
``` | ||
|
||
As another example, we might want to traverse a class hierarchy starting from a base class down to all derived classes, generating a row for each class. Each row should could contain 2 columns: the depth of the derived class relative to the base class and a path string describing its relationship to the base class. Using `BisCore:GeometricElement2d` as the base class produces the following ECSQL and resultant output: | ||
|
||
```sql | ||
WITH RECURSIVE | ||
base_classes (aId, aParentId, aPath, aDepth) AS ( | ||
SELECT c.ECInstanceId, NULL, c.Name, 0 FROM meta.ECClassDef c WHERE c.Name='GeometricElement2d' | ||
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 | ||
) | ||
SELECT bc.aDepth depth, bc.aPath FROM base_classes bc | ||
JOIN meta.ECClassDef a ON a.ECInstanceId= bc.aId | ||
JOIN meta.ECClassDef b ON b.ECInstanceId= bc.aParentId;; | ||
|
||
-- output | ||
depth | aPath | ||
--------------------------------------- | ||
1 | GeometricElement2d/GraphicalElement2d | ||
2 | GeometricElement2d/GraphicalElement2d/AnnotationElement2d | ||
2 | GeometricElement2d/GraphicalElement2d/DrawingGraphic | ||
2 | GeometricElement2d/GraphicalElement2d/ViewAttachment | ||
2 | GeometricElement2d/GraphicalElement2d/DetailingSymbol | ||
3 | GeometricElement2d/GraphicalElement2d/AnnotationElement2d/TextAnnotation2d | ||
3 | GeometricElement2d/GraphicalElement2d/DrawingGraphic/SheetBorder | ||
3 | GeometricElement2d/GraphicalElement2d/DetailingSymbol/Callout | ||
3 | GeometricElement2d/GraphicalElement2d/DetailingSymbol/TitleText | ||
3 | GeometricElement2d/GraphicalElement2d/DetailingSymbol/ViewAttachmentLabel | ||
4 | GeometricElement2d/GraphicalElement2d/DetailingSymbol/Callout/DetailCallout | ||
4 | GeometricElement2d/GraphicalElement2d/DetailingSymbol/Callout/ElevationCallout | ||
4 | GeometricElement2d/GraphicalElement2d/DetailingSymbol/Callout/PlanCallout | ||
4 | GeometricElement2d/GraphicalElement2d/DetailingSymbol/Callout/SectionCalloutt | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you switch to forward slash or else won't work on linux/mac?