diff --git a/common/changes/@itwin/core-backend/Soham-AddingTestsForIdSetVT_2024-10-14-13-05.json b/common/changes/@itwin/core-backend/Soham-AddingTestsForIdSetVT_2024-10-14-13-05.json
new file mode 100644
index 000000000000..99b35bb89b62
--- /dev/null
+++ b/common/changes/@itwin/core-backend/Soham-AddingTestsForIdSetVT_2024-10-14-13-05.json
@@ -0,0 +1,10 @@
+{
+ "changes": [
+ {
+ "packageName": "@itwin/core-backend",
+ "comment": "",
+ "type": "none"
+ }
+ ],
+ "packageName": "@itwin/core-backend"
+}
\ No newline at end of file
diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 62566f58eb3c..3d7488ed0855 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -10,7 +10,7 @@ importers:
../../core/backend:
specifiers:
- '@bentley/imodeljs-native': 4.11.15
+ '@bentley/imodeljs-native': 4.11.16
'@itwin/build-tools': workspace:*
'@itwin/cloud-agnostic-core': ^2.2.4
'@itwin/core-bentley': workspace:*
@@ -62,7 +62,7 @@ importers:
webpack: ^5.76.0
ws: ^7.5.10
dependencies:
- '@bentley/imodeljs-native': 4.11.15
+ '@bentley/imodeljs-native': 4.11.16
'@itwin/cloud-agnostic-core': 2.2.4_scz6qrwecfbbxg4vskopkl3a7u
'@itwin/core-telemetry': link:../telemetry
'@itwin/object-storage-azure': 2.2.5_scz6qrwecfbbxg4vskopkl3a7u
@@ -3704,8 +3704,8 @@ packages:
resolution: {integrity: sha512-IIs1wDcY2oZ8tJ3EZRw0U51M+0ZL3MvwoDYYmhUXaa9/UZqpFoOyLBGaxjirQteWXqTIMm3mFvmC+Nbn1ok4Iw==}
dev: false
- /@bentley/imodeljs-native/4.11.15:
- resolution: {integrity: sha512-yuz4TLdRXD3Y0H4cKtvhxA9xcX9ARGqcS6uwNwtBWtcIqmbeIfnQEsdM7IM7oFYW/bRlbriqogFZbQ3y24/kjw==}
+ /@bentley/imodeljs-native/4.11.16:
+ resolution: {integrity: sha512-sGGQro7c0UW11aMVb/223rZvJr/9qWeby08e6NqbEx9YURmtnT1xIu4NAQ7PlxHJRzCCDAPssspCFJVK69BT+Q==}
requiresBuild: true
dev: false
diff --git a/core/backend/package.json b/core/backend/package.json
index 1ba948d72938..123d84bd6449 100644
--- a/core/backend/package.json
+++ b/core/backend/package.json
@@ -100,7 +100,7 @@
"webpack": "^5.76.0"
},
"dependencies": {
- "@bentley/imodeljs-native": "4.11.15",
+ "@bentley/imodeljs-native": "4.11.16",
"@itwin/cloud-agnostic-core": "^2.2.4",
"@itwin/core-telemetry": "workspace:*",
"@itwin/object-storage-azure": "^2.2.5",
diff --git a/core/backend/src/test/ecdb/ECSqlQuery.test.ts b/core/backend/src/test/ecdb/ECSqlQuery.test.ts
index 41bcbe9ec0b2..deaa3256507c 100644
--- a/core/backend/src/test/ecdb/ECSqlQuery.test.ts
+++ b/core/backend/src/test/ecdb/ECSqlQuery.test.ts
@@ -551,6 +551,69 @@ describe("ECSql Query", () => {
assert.isTrue(reader.stats.backendCpuTime > 0);
assert.isTrue(reader.stats.backendMemUsed > 100);
});
+ it("concurrent query bind idset in IdSet virtual table", async () => {
+ const ids: string[] = [];
+ for await (const row of imodel1.createQueryReader("SELECT ECInstanceId FROM BisCore.Element LIMIT 23")) {
+ ids.push(row[0]);
+ }
+ const reader = imodel1.createQueryReader("SELECT * FROM BisCore.element, ECVLib.IdSet(?) WHERE id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES", QueryBinder.from([ids]));
+ let props = await reader.getMetaData();
+ assert.equal(props.length, 12); // 11 for BisCore.element and 1 for IdSet
+ let rows = 0;
+ while (await reader.step()) {
+ rows++;
+ }
+ assert.equal(rows, 23);
+ props = await reader.getMetaData();
+ assert.equal(props.length, 12); // 11 for BisCore.element and 1 for IdSet
+ assert.equal(reader.stats.backendRowsReturned, 23);
+ assert.isTrue(reader.stats.backendCpuTime > 0);
+ assert.isTrue(reader.stats.backendMemUsed > 100);
+ });
+ it("concurrent query bind single id in IdSet virtual table", async () => {
+ let ids: string = "";
+ for await (const row of imodel1.createQueryReader("SELECT ECInstanceId FROM BisCore.Element LIMIT 23")) {
+ ids = row[0]; // getting only the first id
+ break;
+ }
+ const reader = imodel1.createQueryReader("SELECT * FROM BisCore.element, ECVLib.IdSet(?) WHERE id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES", QueryBinder.from([ids]));
+ let props = await reader.getMetaData();
+ assert.equal(props.length, 12); // 11 for BisCore.element and 1 for IdSet
+ let rows = 0; // backend will fail to bind so no rows will be returned
+ while (await reader.step()) {
+ rows++;
+ }
+ assert.equal(rows, 0);
+ props = await reader.getMetaData();
+ assert.equal(props.length, 12); // 11 for BisCore.element and 1 for IdSet
+ assert.equal(reader.stats.backendRowsReturned, 0);
+ assert.isTrue(reader.stats.backendCpuTime > 0);
+ });
+ it("concurrent query bind idset with invalid values in IdSet virtual table", async () => {
+ const ids: string[] = ["0x1","ABC","YZ"];
+
+ const reader = imodel1.createQueryReader("SELECT * FROM BisCore.element, ECVLib.IdSet(?) WHERE id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES", QueryBinder.from([ids]));
+ let props = await reader.getMetaData();
+ assert.equal(props.length, 12); // 11 for BisCore.element and 1 for IdSet
+ let rows = 0; // backend will bind successfully but some of the values are not valid for IdSet VT so those values will be ignored
+ while (await reader.step()) {
+ rows++;
+ }
+ assert.equal(rows, 1);
+ props = await reader.getMetaData();
+ assert.equal(props.length, 12); // 11 for BisCore.element and 1 for IdSet
+ assert.equal(reader.stats.backendRowsReturned, 1);
+ assert.isTrue(reader.stats.backendCpuTime > 0);
+ });
+ it("concurrent query bind idset with invalid values in IdSet virtual table", async () => {
+ const ids: string[] = ["ABC", "0x1","YZ"]; // as first value is not an Id so QueryBinder.from will throw error of "unsupported type"
+
+ try{
+ imodel1.createQueryReader("SELECT * FROM BisCore.element, ECVLib.IdSet(?) WHERE id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES", QueryBinder.from([ids]));
+ }catch(err: any){
+ assert.equal(err.message, "unsupported type");
+ }
+ });
it("concurrent query get meta data", async () => {
const reader = imodel1.createQueryReader("SELECT * FROM BisCore.element");
let props = await reader.getMetaData();
diff --git a/core/backend/src/test/ecdb/ECSqlReader.test.ts b/core/backend/src/test/ecdb/ECSqlReader.test.ts
index ea256d917e52..542623740d38 100644
--- a/core/backend/src/test/ecdb/ECSqlReader.test.ts
+++ b/core/backend/src/test/ecdb/ECSqlReader.test.ts
@@ -47,6 +47,45 @@ describe("ECSqlReader", (() => {
});
});
+ it("ecsql reader simple for IdSet", async () => {
+ await using(ECDbTestHelper.createECDb(outDir, "test.ecdb",
+ `
+
+
+
+ `), async (ecdb: ECDb) => {
+ assert.isTrue(ecdb.isOpen);
+ ecdb.saveChanges();
+ const params = new QueryBinder();
+ params.bindIdSet(1, ["0x32"]);
+ const optionBuilder = new QueryOptionsBuilder();
+ optionBuilder.setRowFormat(QueryRowFormat.UseJsPropertyNames);
+ reader = ecdb.createQueryReader("SELECT ECInstanceId, Name FROM meta.ECClassDef, ECVLib.IdSet(?) WHERE id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES", params, optionBuilder.getOptions());
+ const rows = await reader.toArray();
+ assert.equal(rows[0].id, "0x32");
+ assert.equal(rows.length, 1);
+ });
+ });
+
+ it("bindIdSet not working with integer Ids", async () => {
+ await using(ECDbTestHelper.createECDb(outDir, "test.ecdb",
+ `
+
+
+
+ `), async (ecdb: ECDb) => {
+ assert.isTrue(ecdb.isOpen);
+ ecdb.saveChanges();
+ const params = new QueryBinder();
+ params.bindIdSet(1, ["50"]);
+ const optionBuilder = new QueryOptionsBuilder();
+ optionBuilder.setRowFormat(QueryRowFormat.UseJsPropertyNames);
+ reader = ecdb.createQueryReader("SELECT ECInstanceId, Name FROM meta.ECClassDef WHERE InVirtualSet(?, ECInstanceId)", params, optionBuilder.getOptions());
+ const rows = await reader.toArray();
+ assert.equal(rows.length, 0);
+ });
+ });
+
it("ecsql reader simple using query reader", async () => {
await using(ECDbTestHelper.createECDb(outDir, "test.ecdb",
`
diff --git a/core/backend/src/test/ecdb/ECSqlStatement.test.ts b/core/backend/src/test/ecdb/ECSqlStatement.test.ts
index 3d7ba228f973..d67f7e70df8a 100644
--- a/core/backend/src/test/ecdb/ECSqlStatement.test.ts
+++ b/core/backend/src/test/ecdb/ECSqlStatement.test.ts
@@ -1839,6 +1839,123 @@ describe("ECSqlStatement", () => {
});
});
+ it("should bind IdSets to IdSet Virtual Table", async () => {
+ await using(ECDbTestHelper.createECDb(outDir, "bindids.ecdb"), async (ecdb: ECDb) => {
+ assert.isTrue(ecdb.isOpen);
+
+ const idNumbers: number[] = [4444, 4545, 1234, 6758, 1312];
+ ecdb.withPreparedStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(?,?)", (stmt: ECSqlStatement) => {
+ idNumbers.forEach((idNum: number) => {
+ const expectedId = Id64.fromLocalAndBriefcaseIds(idNum, 0);
+ stmt.bindId(1, expectedId);
+ stmt.bindString(2, `${idNum}.txt`);
+ const r: ECSqlInsertResult = stmt.stepForInsert();
+ assert.equal(r.status, DbResult.BE_SQLITE_DONE);
+ assert.isDefined(r.id);
+ assert.equal(r.id!, expectedId);
+ ecdb.saveChanges();
+
+ ecdb.withStatement(`SELECT ECInstanceId, ECClassId, Name FROM ecdbf.ExternalFileInfo WHERE ECInstanceId=${expectedId}`, (confstmt: ECSqlStatement) => {
+ assert.equal(confstmt.step(), DbResult.BE_SQLITE_ROW);
+ const row = confstmt.getRow();
+ assert.equal(row.id, expectedId);
+ assert.equal(row.className, "ECDbFileInfo.ExternalFileInfo");
+ assert.equal(row.name, `${Id64.getLocalId(expectedId).toString()}.txt`);
+ });
+ stmt.reset();
+ stmt.clearBindings();
+ });
+ });
+
+ ecdb.withPreparedStatement("SELECT ECInstanceId, ECClassId, Name from ecdbf.ExternalFileInfo, ECVLib.IdSet(?) WHERE id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES", (stmt: ECSqlStatement) => {
+ let idSet: Id64String[] = [];
+ stmt.bindIdSet(1, idSet);
+ let result = stmt.step();
+ assert.equal(result, DbResult.BE_SQLITE_DONE);
+ stmt.reset();
+ stmt.clearBindings();
+
+ idSet = [Id64.fromLocalAndBriefcaseIds(idNumbers[2], 0)];
+ stmt.bindIdSet(1, idSet);
+ result = stmt.step();
+ assert.equal(result, DbResult.BE_SQLITE_ROW);
+ let row = stmt.getRow();
+ assert.equal(row.name, `${idNumbers[2]}.txt`);
+ stmt.reset();
+ stmt.clearBindings();
+
+ idSet.push(idNumbers[0].toString());
+ stmt.bindIdSet(1, idSet);
+ result = stmt.step();
+ assert.equal(result, DbResult.BE_SQLITE_ROW);
+ row = stmt.getRow();
+ assert.equal(row.name, `${idNumbers[2]}.txt`);
+ result = stmt.step();
+ assert.equal(result, DbResult.BE_SQLITE_ROW);
+ row = stmt.getRow();
+ assert.equal(row.name, `${idNumbers[0]}.txt`);
+ });
+ });
+ });
+
+ it("Error Checking For binding to IdSet statements", async () => {
+ await using(ECDbTestHelper.createECDb(outDir, "bindids.ecdb"), async (ecdb: ECDb) => {
+ assert.isTrue(ecdb.isOpen);
+
+ const idNumbers: number[] = [4444, 4545, 1234, 6758, 1312];
+ ecdb.withPreparedStatement("INSERT INTO ecdbf.ExternalFileInfo(ECInstanceId,Name) VALUES(?,?)", (stmt: ECSqlStatement) => {
+ idNumbers.forEach((idNum: number) => {
+ const expectedId = Id64.fromLocalAndBriefcaseIds(idNum, 0);
+ stmt.bindId(1, expectedId);
+ stmt.bindString(2, `${idNum}.txt`);
+ const r: ECSqlInsertResult = stmt.stepForInsert();
+ assert.equal(r.status, DbResult.BE_SQLITE_DONE);
+ assert.isDefined(r.id);
+ assert.equal(r.id!, expectedId);
+ ecdb.saveChanges();
+
+ ecdb.withStatement(`SELECT ECInstanceId, ECClassId, Name FROM ecdbf.ExternalFileInfo WHERE ECInstanceId=${expectedId}`, (confstmt: ECSqlStatement) => {
+ assert.equal(confstmt.step(), DbResult.BE_SQLITE_ROW);
+ const row = confstmt.getRow();
+ assert.equal(row.id, expectedId);
+ assert.equal(row.className, "ECDbFileInfo.ExternalFileInfo");
+ assert.equal(row.name, `${Id64.getLocalId(expectedId).toString()}.txt`);
+ });
+ stmt.reset();
+ stmt.clearBindings();
+ });
+ });
+
+ ecdb.withPreparedStatement("SELECT ECInstanceId, ECClassId, Name from ecdbf.ExternalFileInfo, ECVLib.IdSet(?) WHERE id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES", (stmt: ECSqlStatement) => {
+ let idSet: Id64String[] = [];
+ stmt.bindIdSet(1, idSet);
+ let result = stmt.step();
+ assert.equal(result, DbResult.BE_SQLITE_DONE);
+ stmt.reset();
+ stmt.clearBindings();
+
+ idSet = ["0X1","ABC"];
+ try{
+ stmt.bindIdSet(1, idSet);
+ }catch(err: any){
+ assert.equal(err.message, "Error binding id set");
+ }
+ result = stmt.step();
+ assert.equal(result, DbResult.BE_SQLITE_DONE);
+ stmt.reset();
+ stmt.clearBindings();
+
+ try{
+ stmt.bindId(1, idNumbers[0].toString());
+ }catch(err: any){
+ assert.equal(err.message, "Error binding Id");
+ }
+ result = stmt.step();
+ assert.equal(result, DbResult.BE_SQLITE_DONE);
+ });
+ });
+ });
+
/* This test doesn't do anything specific with the binder life time but just runs a few scenarios
with and without statement cache to test that stuff works fine */
it("check ECSqlBinder life time", async () => {
diff --git a/core/backend/src/test/ecsql/queries/IdSetVTTests.ecsql.md b/core/backend/src/test/ecsql/queries/IdSetVTTests.ecsql.md
new file mode 100644
index 000000000000..c2edf1a9da11
--- /dev/null
+++ b/core/backend/src/test/ecsql/queries/IdSetVTTests.ecsql.md
@@ -0,0 +1,586 @@
+Copyright © Bentley Systems, Incorporated. All rights reserved. See [LICENSE.md](../../../../LICENSE.md) for license terms and full copyright notice.
+
+# Testing returned values of IdSet virtual table
+
+- dataset: AllProperties.bim
+
+```sql
+SELECT id from ECVLib.IdSet(?) ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| --------- | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- | ------------------ |
+| | id | false | 0 | id | id | Id | long | Id | id |
+
+| id |
+| ---- |
+| 0x15 |
+| 0x18 |
+| 0x19 |
+
+# Testing returned values of IdSet virtual table with alias
+
+- dataset: AllProperties.bim
+
+```sql
+SELECT id a from ECVLib.IdSet(?) OPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| --------- | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- | ------------------ |
+| | a | true | 0 | a | a | Id | long | Id | id |
+
+| a |
+| ---- |
+| 0x15 |
+| 0x18 |
+| 0x19 |
+
+# Testing with hard coded json string with hex ids
+
+- dataset: AllProperties.bim
+
+```sql
+SELECT i FROM aps.TestElement,ECVLib.IdSet('["0x15", "0x18", "0x19"]') where id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- | ------------------ |
+| AllProperties:IPrimitive | i | false | 0 | i | i | undefined | int | Int | i |
+
+| i |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing with hard coded json string with decimal ids
+
+- dataset: AllProperties.bim
+
+```sql
+SELECT i FROM aps.TestElement,ECVLib.IdSet('[21, 24, "25"]') where id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES = TRUE
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- | ------------------ |
+| AllProperties:IPrimitive | i | false | 0 | i | i | undefined | int | Int | i |
+
+| i |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing INNER JOINS with IdSet
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT e.i FROM aps.TestElement e INNER JOIN ECVLib.IdSet(?) v ON e.ECInstanceId = v.id ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES = 1
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- | ------------------ |
+| AllProperties:IPrimitive | i | false | 0 | i | i | undefined | int | Int | i |
+
+| i |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing INNER JOINS with IdSet and also select VirtualProp
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ e.i,
+ v.id
+FROM
+ aps.TestElement e
+ INNER JOIN ECVLib.IdSet (?) v ON v.id = e.ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES = true
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- | ------------------ |
+| AllProperties:IPrimitive | i | false | 0 | i | i | undefined | int | Int | i |
+| | id | false | 1 | id | id | Id | long | Id | id |
+
+| i | id |
+| --- | ---- |
+| 101 | 0x15 |
+| 104 | 0x18 |
+| 105 | 0x19 |
+
+# Testing INNER JOIN with string prop
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ e.ECInstanceId,
+ e.s,
+ v.id
+FROM
+ aps.TestElement e
+ JOIN ECVLib.IdSet (?) v ON e.ECInstanceId = v.id OPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ------------ | ------------ | -------- | ------ | ------------------ |
+| | ECInstanceId | false | 0 | id | ECInstanceId | Id | long | Id | ECInstanceId |
+| AllProperties:IPrimitive | s | false | 1 | s | s | undefined | string | String | s |
+| | id | false | 2 | id_1 | id | Id | long | Id | id |
+
+| ECInstanceId | s | id |
+| ------------ | ---- | ---- |
+| 0x15 | str1 | 0x15 |
+| 0x18 | str4 | 0x18 |
+| 0x19 | str5 | 0x19 |
+
+# Testing LEFT OUTER JOIN on virtual table
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ e.ECInstanceId,
+ e.i,
+ v.id
+FROM
+ ECVLib.IdSet (?) v
+ LEFT OUTER JOIN aps.TestElement e ON e.ECInstanceId = v.id ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ------------ | ------------ | -------- | ---- | ------------------ |
+| | ECInstanceId | false | 0 | id | ECInstanceId | Id | long | Id | ECInstanceId |
+| AllProperties:IPrimitive | i | false | 1 | i | i | undefined | int | Int | i |
+| | id | false | 2 | id_1 | id | Id | long | Id | id |
+
+| ECInstanceId | i | id |
+| ------------ | --- | ---- |
+| 0x15 | 101 | 0x15 |
+| 0x18 | 104 | 0x18 |
+| 0x19 | 105 | 0x19 |
+
+# Testing LEFT OUTER JOIN on test table
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ e.ECInstanceId,
+ e.i,
+ v.id
+FROM
+ aps.TestElement e
+ LEFT OUTER JOIN ECVLib.IdSet (?) v ON e.ECInstanceId = v.id OPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ------------ | ------------ | -------- | ---- | ------------------ |
+| | ECInstanceId | false | 0 | id | ECInstanceId | Id | long | Id | ECInstanceId |
+| AllProperties:IPrimitive | i | false | 1 | i | i | undefined | int | Int | i |
+| | id | false | 2 | id_1 | id | Id | long | Id | id |
+
+| ECInstanceId | i | id |
+| ------------ | --- | --------- |
+| 0x14 | 100 | undefined |
+| 0x15 | 101 | 0x15 |
+| 0x16 | 102 | undefined |
+| 0x17 | 103 | undefined |
+| 0x18 | 104 | 0x18 |
+| 0x19 | 105 | 0x19 |
+| 0x1a | 106 | undefined |
+| 0x1b | 107 | undefined |
+| 0x1c | 108 | undefined |
+| 0x1d | 109 | undefined |
+
+# Testing CROSS JOIN on test table
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ e.S,
+ e.i,
+ v.id
+FROM
+ aps.TestElement e
+ CROSS JOIN ECVLib.IdSet (?) v ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ------ | ------------------ |
+| AllProperties:IPrimitive | s | false | 0 | s | s | undefined | string | String | s |
+| AllProperties:IPrimitive | i | false | 1 | i | i | undefined | int | Int | i |
+| | id | false | 2 | id | id | Id | long | Id | id |
+
+| s | i | id |
+| ---- | --- | ---- |
+| str0 | 100 | 0x15 |
+| str0 | 100 | 0x18 |
+| str0 | 100 | 0x19 |
+| str1 | 101 | 0x15 |
+| str1 | 101 | 0x18 |
+| str1 | 101 | 0x19 |
+| str2 | 102 | 0x15 |
+| str2 | 102 | 0x18 |
+| str2 | 102 | 0x19 |
+| str3 | 103 | 0x15 |
+| str3 | 103 | 0x18 |
+| str3 | 103 | 0x19 |
+| str4 | 104 | 0x15 |
+| str4 | 104 | 0x18 |
+| str4 | 104 | 0x19 |
+| str5 | 105 | 0x15 |
+| str5 | 105 | 0x18 |
+| str5 | 105 | 0x19 |
+| str6 | 106 | 0x15 |
+| str6 | 106 | 0x18 |
+| str6 | 106 | 0x19 |
+| str7 | 107 | 0x15 |
+| str7 | 107 | 0x18 |
+| str7 | 107 | 0x19 |
+| str8 | 108 | 0x15 |
+| str8 | 108 | 0x18 |
+| str8 | 108 | 0x19 |
+| str9 | 109 | 0x15 |
+| str9 | 109 | 0x18 |
+| str9 | 109 | 0x19 |
+
+# Testing FULL JOIN
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ e.s,
+ e.i,
+ v.id
+FROM
+ aps.TestElement e
+ FULL JOIN ECVLib.IdSet (?) v ON e.ECInstanceId = v.id OPTIONS ENABLE_EXPERIMENTAL_FEATURES = TRUE
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ------ | ------------------ |
+| AllProperties:IPrimitive | s | false | 0 | s | s | undefined | string | String | s |
+| AllProperties:IPrimitive | i | false | 1 | i | i | undefined | int | Int | i |
+| | id | false | 2 | id | id | Id | long | Id | id |
+
+| s | i | id |
+| ---- | --- | --------- |
+| str0 | 100 | undefined |
+| str1 | 101 | 0x15 |
+| str2 | 102 | undefined |
+| str3 | 103 | undefined |
+| str4 | 104 | 0x18 |
+| str5 | 105 | 0x19 |
+| str6 | 106 | undefined |
+| str7 | 107 | undefined |
+| str8 | 108 | undefined |
+| str9 | 109 | undefined |
+
+# Testing NATURAL JOIN
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+- errorDuringPrepare: true
+
+```sql
+SELECT e.S, e.i, v.id FROM aps.TestElement e NATURAL JOIN ECVLib.IdSet(?) v ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES = True
+```
+
+# Testing JOIN
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ e.ECInstanceId,
+ e.i,
+ v.id
+FROM
+ aps.TestElement e
+ JOIN ECVLib.IdSet (?) v ON e.ECInstanceId = v.id ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ------------ | ------------ | -------- | ---- | ------------------ |
+| | ECInstanceId | false | 0 | id | ECInstanceId | Id | long | Id | ECInstanceId |
+| AllProperties:IPrimitive | i | false | 1 | i | i | undefined | int | Int | i |
+| | id | false | 2 | id_1 | id | Id | long | Id | id |
+
+| ECInstanceId | i | id |
+| ------------ | --- | ---- |
+| 0x15 | 101 | 0x15 |
+| 0x18 | 104 | 0x18 |
+| 0x19 | 105 | 0x19 |
+
+# Testing by binding with hex ids
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT i FROM aps.TestElement,ECVLib.IdSet(?) where id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- | ------------------ |
+| AllProperties:IPrimitive | i | false | 0 | i | i | undefined | int | Int | i |
+
+| i |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing by binding with decimal ids for ECSql Statement
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [21, 24, 25]
+- mode: Statement
+
+```sql
+SELECT i FROM aps.TestElement,ECVLib.IdSet(?) where id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- | ------------------ |
+| AllProperties:IPrimitive | i | false | 0 | i | i | undefined | int | Int | i |
+
+| i |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing by binding with decimal ids for ConcurrentQuery
+
+`The purpose of this test is to show that bindIdSet when working with ConcurrentQuery only takes into account hex ids and not decimal ids`
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [21, 24, 25]
+- mode: ConcurrentQuery
+
+```sql
+SELECT i FROM aps.TestElement,ECVLib.IdSet(?) where id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type | originPropertyName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- | ------------------ |
+| AllProperties:IPrimitive | i | false | 0 | i | i | undefined | int | Int | i |
+
+| i |
+| --- |
+
+# Testing IdSet following cte subquery
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ b
+FROM
+ (
+ WITH
+ cte (a, b) AS (
+ SELECT ECInstanceId, i FROM aps.TestElement ) SELECT * FROM cte
+ ),
+ ECVLib.IdSet (?)
+WHERE
+ id = a ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type |
+| --------- | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- |
+| | b | true | 0 | b | b | undefined | int | Int |
+
+| b |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing cte subquery following IdSet
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ b
+FROM
+ECVLib.IdSet (?),
+ (
+ WITH
+ cte (a, b) AS (
+ SELECT ECInstanceId, i FROM aps.TestElement ) SELECT * FROM cte
+ )
+WHERE
+ id = a ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type |
+| --------- | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- |
+| | b | true | 0 | b | b | undefined | int | Int |
+
+| b |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing nested CTE subquery following IdSet
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ b
+FROM
+ECVLib.IdSet (?),
+( select * from (
+ WITH
+ cte (a, b) AS (
+ SELECT ECInstanceId, i FROM aps.TestElement ) SELECT * FROM cte
+ ))
+WHERE
+ id = a OPTIONS ENABLE_EXPERIMENTAL_FEATURES = 1
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type |
+| --------- | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- |
+| | b | true | 0 | b | b | undefined | int | Int |
+
+| b |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing IdSet following nested CTE subquery
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ b
+FROM
+( select * from (
+ WITH
+ cte (a, b) AS (
+ SELECT ECInstanceId, i FROM aps.TestElement ) SELECT * FROM cte
+ )),
+ECVLib.IdSet (?)
+WHERE
+ id = a ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName | type |
+| --------- | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- | ---- |
+| | b | true | 0 | b | b | undefined | int | Int |
+
+| b |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing IdSet following nested CTE without sub columns subquery
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ i
+FROM
+( select * from (
+ WITH
+ cte AS (
+ SELECT ECInstanceId, i FROM aps.TestElement ) SELECT * FROM cte
+ )),
+ECVLib.IdSet (?)
+WHERE
+ id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- |
+| AllProperties:IPrimitive | i | false | 0 | i | i | undefined | int |
+
+| i |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing CTE without sub columns subquery following IdSet
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+```sql
+SELECT
+ i
+FROM
+ECVLib.IdSet (?),
+(
+ WITH
+ cte AS (
+ SELECT ECInstanceId, i FROM aps.TestElement ) SELECT * FROM cte
+ )
+WHERE
+ id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+| className | accessString | generated | index | jsonName | name | extendedType | typeName |
+| ------------------------ | ------------ | --------- | ----- | -------- | ---- | ------------ | -------- |
+| AllProperties:IPrimitive | i | false | 0 | i | i | undefined | int |
+
+| i |
+| --- |
+| 101 |
+| 104 |
+| 105 |
+
+# Testing IdSet by setting ENABLE_EXPERIMENTAL_FEATURES to false
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+- errorDuringPrepare: true
+
+```sql
+SELECT i FROM aps.TestElement,ECVLib.IdSet(?) where id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES = False
+```
+
+# Testing IdSet without ENABLE_EXPERIMENTAL_FEATURES
+
+- dataset: AllProperties.bim
+- bindIdSet 1, [0x15, 0x18, 0x19]
+- errorDuringPrepare: true
+
+```sql
+SELECT i FROM aps.TestElement,ECVLib.IdSet(?) where id = ECInstanceId
+```
diff --git a/core/backend/src/test/ecsql/queries/Misc.ecsql.md b/core/backend/src/test/ecsql/queries/Misc.ecsql.md
index f2e9ff866eaf..b98f2e91ebd0 100644
--- a/core/backend/src/test/ecsql/queries/Misc.ecsql.md
+++ b/core/backend/src/test/ecsql/queries/Misc.ecsql.md
@@ -742,9 +742,9 @@ PRAGMA explain_query (
| | notused | true | 2 | notused | notused | undefined | long | Int64 | notused |
| | detail | true | 3 | detail | detail | undefined | string | String | detail |
-| id | parent | notused | detail |
-| --- | ------ | ------- | ---------------------------------------------------------- |
-| 3 | 0 | 0 | SEARCH main.ec_Class USING INDEX ix_ec_Class_Name (Name=?) |
+| detail |
+| ---------------------------------------------------------- |
+| SEARCH main.ec_Class USING INDEX ix_ec_Class_Name (Name=?) |
# Trying PRAGMA explain_query with cte
@@ -761,11 +761,11 @@ PRAGMA explain_query ( [WITH cnt (x,y) AS ( SELECT 100, 200 ) SELEC
| | notused | true | 2 | notused | notused | undefined | long | Int64 | notused |
| | detail | true | 3 | detail | detail | undefined | string | String | detail |
-| id | parent | notused | detail |
-| --- | ------ | ------- | ----------------- |
-| 2 | 0 | 0 | CO-ROUTINE cnt |
-| 3 | 2 | 0 | SCAN CONSTANT ROW |
-| 8 | 0 | 0 | SCAN cnt |
+| detail |
+| ----------------- |
+| CO-ROUTINE cnt |
+| SCAN CONSTANT ROW |
+| SCAN cnt |
# Trying PRAGMA explain_query with recursive cte
@@ -790,14 +790,14 @@ PRAGMA explain_query (
| | notused | true | 2 | notused | notused | undefined | long | Int64 | notused |
| | detail | true | 3 | detail | detail | undefined | string | String | detail |
-| notused | detail |
-| ------- | ----------------- |
-| 0 | CO-ROUTINE cnt |
-| 0 | SETUP |
-| 0 | SCAN CONSTANT ROW |
-| 0 | RECURSIVE STEP |
-| 0 | SCAN cnt |
-| 0 | SCAN cnt |
+| detail |
+| ----------------- |
+| CO-ROUTINE cnt |
+| SETUP |
+| SCAN CONSTANT ROW |
+| RECURSIVE STEP |
+| SCAN cnt |
+| SCAN cnt |
# Using Scalar values in select clause with + operator
diff --git a/docs/learning/ECSqlReference/IdSet.md b/docs/learning/ECSqlReference/IdSet.md
new file mode 100644
index 000000000000..723b0398911b
--- /dev/null
+++ b/docs/learning/ECSqlReference/IdSet.md
@@ -0,0 +1,47 @@
+# IdSet Virtual Table
+
+`IdSet` is an ECSQl built in virtual table which takes in a valid JSON array string of hex or decimal ids and stores the ids as a virtual table. It can be used as an alternative to `InVirtualSet`. The column retuned by `IdSet` virtual table will always be named `id` by default but can be aliased as per choice. `IdSet` virtual table is defined under the schema named `ECVLib`. It is an experimental feature, so the ECSql Option `ENABLE_EXPERIMENTAL_FEATURES` should be passed with the query in order for it to work.
+
+## Syntax
+
+```sql
+SELECT i FROM aps.TestElement, ECVLib.IdSet('["0x15", "0x18", "0x19"]') where id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+OR
+
+```sql
+SELECT i FROM aps.TestElement, ECVLib.IdSet(?) where id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+## Arguments accepted
+
+- `IdSet` accepts a valid string JSON array with valid string hex ids like `["0x15", "0x18", "0x19"]`
+- `IdSet` also accepts a valid string JSON array with valid decimal ids like `[21, 24, 25]`
+- `IdSet` also accepts a valid string JSON array with valid decimal ids being passed on as string like `["21", "24", "25"]`
+
+## BindIdSet support
+
+As `IdSet` is an alternative to `InVirtualSet()`, `bindIdSet` also works with `IdSet` virtual table
+
+```sql
+SELECT i FROM aps.TestElement,ECVLib.IdSet(?) where id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+- bindIdSet 1, [0x15, 0x18, 0x19]
+
+## Migrating from `InVirtualSet`
+
+The following ECSql query using `InVirtualSet`
+
+```sql
+SELECT i FROM aps.TestElement where InVirtualSet(?, ECInstanceId)
+```
+
+can be translated using `IdSet` as follows
+
+```sql
+SELECT i FROM aps.TestElement,ECVLib.IdSet(?) where id = ECInstanceId ECSQLOPTIONS ENABLE_EXPERIMENTAL_FEATURES
+```
+
+[ECSql Syntax](./index.md)
diff --git a/docs/learning/ECSqlReference/index.md b/docs/learning/ECSqlReference/index.md
index 37ffa0488a9d..aa0a8740f9e0 100644
--- a/docs/learning/ECSqlReference/index.md
+++ b/docs/learning/ECSqlReference/index.md
@@ -46,3 +46,4 @@
- [ECSQL Keywords](./ECSqlKeywords.md)
- [Escaping keywords](./ECSqlKeywords.md#escaping-keywords)
- [Views](./Views.md)
+- [IdSet Virtual Table](./IdSet.md)
diff --git a/test-apps/display-test-app/android/imodeljs-test-app/app/build.gradle b/test-apps/display-test-app/android/imodeljs-test-app/app/build.gradle
index 0c745925798f..892f49d73b75 100644
--- a/test-apps/display-test-app/android/imodeljs-test-app/app/build.gradle
+++ b/test-apps/display-test-app/android/imodeljs-test-app/app/build.gradle
@@ -41,7 +41,7 @@ dependencies {
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-ui:2.5.3'
- implementation 'com.github.itwin:mobile-native-android:4.11.15'
+ implementation 'com.github.itwin:mobile-native-android:4.11.16'
implementation 'androidx.webkit:webkit:1.5.0'
}
diff --git a/test-apps/display-test-app/ios/imodeljs-test-app/imodeljs-test-app.xcodeproj/project.pbxproj b/test-apps/display-test-app/ios/imodeljs-test-app/imodeljs-test-app.xcodeproj/project.pbxproj
index 0077dd52fe14..d4ab4fa6d668 100644
--- a/test-apps/display-test-app/ios/imodeljs-test-app/imodeljs-test-app.xcodeproj/project.pbxproj
+++ b/test-apps/display-test-app/ios/imodeljs-test-app/imodeljs-test-app.xcodeproj/project.pbxproj
@@ -453,7 +453,7 @@
repositoryURL = "https://github.com/iTwin/mobile-native-ios";
requirement = {
kind = exactVersion;
- version = 4.11.15;
+ version = 4.11.16;
};
};
/* End XCRemoteSwiftPackageReference section */
diff --git a/tools/internal/ios/core-test-runner/core-test-runner.xcodeproj/project.pbxproj b/tools/internal/ios/core-test-runner/core-test-runner.xcodeproj/project.pbxproj
index fe34f121bc49..fea1b3ba0da4 100644
--- a/tools/internal/ios/core-test-runner/core-test-runner.xcodeproj/project.pbxproj
+++ b/tools/internal/ios/core-test-runner/core-test-runner.xcodeproj/project.pbxproj
@@ -552,7 +552,7 @@
repositoryURL = "https://github.com/iTwin/mobile-native-ios";
requirement = {
kind = exactVersion;
- version = 4.11.15;
+ version = 4.11.16;
};
};
/* End XCRemoteSwiftPackageReference section */