diff --git a/docs/api.json b/docs/api.json index deb5e54..f27df19 100644 --- a/docs/api.json +++ b/docs/api.json @@ -1585,11 +1585,16 @@ }, { "jsDoc": { - "doc": "Returns the total number of entities corresponding to the data schema.\n", + "doc": "Returns the total number of entities corresponding to the data schema.\nOptionally, you can specify search criteria and a maximum number of results to count.\n", "tags": [ { "kind": "example", - "doc": "```typescript\nimport { createLens } from \"ldkit\";\nimport { schema } from \"ldkit/namespaces\";\n\n// Create a schema\nconst PersonSchema = {\n \"@type\": schema.Person,\n name: schema.name,\n} as const;\n\n// Create a resource using the data schema above\nconst Persons = createLens(PersonSchema);\n\n// Count all persons\nconst count = await Persons.count(); // number\n```\n" + "doc": "```typescript\nimport { createLens } from \"ldkit\";\nimport { schema } from \"ldkit/namespaces\";\n\n// Create a schema\nconst PersonSchema = {\n \"@type\": schema.Person,\n name: schema.name,\n} as const;\n\n// Create a resource using the data schema above\nconst Persons = createLens(PersonSchema);\n\n// Count all persons\nconst count = await Persons.count(); // number\n\n// Count all persons with name that starts with \"Ada\"\nconst adaCount = await Persons.count({\n where: {\n name: { $strStarts: \"Ada\" },\n },\n});\n\n// Count all persons, but limit the result to 100\nconst limitedCount = await Persons.count({ max: 100 });\n```\n" + }, + { + "kind": "param", + "name": "options", + "doc": "Search criteria and maximum number of results to count" }, { "kind": "return", @@ -1606,14 +1611,74 @@ "functionDef": { "params": [ { - "kind": "identifier", - "name": "max", - "optional": true, - "tsType": { - "repr": "number", - "kind": "keyword", - "keyword": "number" - } + "kind": "assign", + "left": { + "kind": "identifier", + "name": "options", + "optional": false, + "tsType": { + "repr": "", + "kind": "typeLiteral", + "typeLiteral": { + "constructors": [], + "methods": [], + "properties": [ + { + "name": "where", + "location": { + "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", + "line": 147, + "col": 4, + "byteIndex": 4617 + }, + "params": [], + "computed": false, + "optional": true, + "tsType": { + "repr": "SchemaSearchInterface", + "kind": "typeRef", + "typeRef": { + "typeParams": [ + { + "repr": "T", + "kind": "typeRef", + "typeRef": { + "typeParams": null, + "typeName": "T" + } + } + ], + "typeName": "SchemaSearchInterface" + } + }, + "typeParams": [] + }, + { + "name": "max", + "location": { + "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", + "line": 148, + "col": 4, + "byteIndex": 4655 + }, + "params": [], + "computed": false, + "optional": true, + "tsType": { + "repr": "number", + "kind": "keyword", + "keyword": "number" + }, + "typeParams": [] + } + ], + "callSignatures": [], + "indexSignatures": [] + } + } + }, + "right": "[UNSUPPORTED]", + "tsType": null } ], "returnType": { @@ -1637,9 +1702,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 134, + "line": 146, "col": 2, - "byteIndex": 4122 + "byteIndex": 4590 } }, { @@ -1726,9 +1791,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 174, + "line": 193, "col": 2, - "byteIndex": 5473 + "byteIndex": 6082 } }, { @@ -1775,9 +1840,9 @@ "name": "where", "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 217, + "line": 236, "col": 6, - "byteIndex": 6711 + "byteIndex": 7320 }, "params": [], "computed": false, @@ -1828,9 +1893,9 @@ "name": "take", "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 218, + "line": 237, "col": 6, - "byteIndex": 6773 + "byteIndex": 7382 }, "params": [], "computed": false, @@ -1846,9 +1911,9 @@ "name": "skip", "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 219, + "line": 238, "col": 6, - "byteIndex": 6794 + "byteIndex": 7403 }, "params": [], "computed": false, @@ -1916,9 +1981,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 215, + "line": 234, "col": 2, - "byteIndex": 6678 + "byteIndex": 7287 } }, { @@ -1979,9 +2044,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 266, + "line": 285, "col": 2, - "byteIndex": 8164 + "byteIndex": 8773 } }, { @@ -2078,9 +2143,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 296, + "line": 315, "col": 2, - "byteIndex": 9056 + "byteIndex": 9665 } }, { @@ -2174,9 +2239,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 330, + "line": 349, "col": 2, - "byteIndex": 10121 + "byteIndex": 10730 } }, { @@ -2207,9 +2272,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 337, + "line": 356, "col": 2, - "byteIndex": 10351 + "byteIndex": 10960 } }, { @@ -2300,9 +2365,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 369, + "line": 388, "col": 2, - "byteIndex": 11106 + "byteIndex": 11715 } }, { @@ -2375,9 +2440,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 409, + "line": 428, "col": 2, - "byteIndex": 12240 + "byteIndex": 12849 } }, { @@ -2459,9 +2524,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 441, + "line": 460, "col": 2, - "byteIndex": 13052 + "byteIndex": 13661 } }, { @@ -2552,9 +2617,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 478, + "line": 497, "col": 2, - "byteIndex": 14195 + "byteIndex": 14804 } }, { @@ -2627,9 +2692,9 @@ }, "location": { "filename": "file:///C:/Users/Karel/Projekty/ldkit/library/lens/lens.ts", - "line": 522, + "line": 541, "col": 2, - "byteIndex": 15459 + "byteIndex": 16068 } } ], diff --git a/library/lens/lens.ts b/library/lens/lens.ts index 6233b1f..8dbd1bc 100644 --- a/library/lens/lens.ts +++ b/library/lens/lens.ts @@ -110,6 +110,7 @@ export class Lens { /** * Returns the total number of entities corresponding to the data schema. + * Optionally, you can specify search criteria and a maximum number of results to count. * * @example * ```typescript @@ -127,12 +128,30 @@ export class Lens { * * // Count all persons * const count = await Persons.count(); // number + * + * // Count all persons with name that starts with "Ada" + * const adaCount = await Persons.count({ + * where: { + * name: { $strStarts: "Ada" }, + * }, + * }); + * + * // Count all persons, but limit the result to 100 + * const limitedCount = await Persons.count({ max: 100 }); * ``` * + * @param options Search criteria and maximum number of results to count * @returns total number of entities corresponding to the data schema */ - async count(max?: number): Promise { - const q = this.queryBuilder.countQuery(max); + async count(options: { + where?: SchemaSearchInterface; + max?: number; + } = {}): Promise { + const { where, max } = { + where: {}, + ...options, + }; + const q = this.queryBuilder.countQuery(where, max); this.log(q); const bindings = await this.engine.queryBindings(q); return parseInt(bindings[0].get("count")!.value); diff --git a/library/lens/query_builder.ts b/library/lens/query_builder.ts index 4d95ab2..1a5d66f 100644 --- a/library/lens/query_builder.ts +++ b/library/lens/query_builder.ts @@ -149,8 +149,11 @@ export class QueryBuilder { return conditions; } - countQuery(max?: number) { - const quads = this.getShape(Flags.ExcludeOptional | Flags.IncludeTypes); + countQuery(where: SearchSchema, max?: number) { + const quads = this.getShape( + Flags.ExcludeOptional | Flags.IncludeTypes, + where, + ); const innerQuery = max === undefined ? quads : SELECT`?iri`.WHERE`${quads}`.LIMIT(max); diff --git a/library/lens/query_helper.ts b/library/lens/query_helper.ts deleted file mode 100644 index a8d70ab..0000000 --- a/library/lens/query_helper.ts +++ /dev/null @@ -1,116 +0,0 @@ -import type { QueryContext, RDF } from "../rdf.ts"; -import type { Property, Schema } from "../schema/mod.ts"; -import { encode } from "../encoder.ts"; -import type { Entity } from "./types.ts"; - -/** - * @deprecated - * TODO: Remove this class - */ -export class QueryHelper { - private readonly entity: Entity; - private readonly schema: Schema; - private readonly context: QueryContext; - private readonly variableInitCounter: number; - - private quads?: RDF.Quad[]; - private variableQuads?: RDF.Quad[]; - - constructor( - entity: Entity, - schema: Schema, - context: QueryContext, - variableInitCounter = 0, - ) { - this.entity = entity; - this.schema = schema; - this.context = context; - this.variableInitCounter = variableInitCounter; - } - - getQuads() { - if (!this.quads) { - this.quads = encode( - this.entity, - this.schema, - this.context, - true, - this.variableInitCounter, - ); - } - return this.quads; - } - - getVariableQuads() { - if (!this.variableQuads) { - this.variableQuads = encode( - this.getEntityWithReplacedVariables(), - this.schema, - this.context, - true, - this.variableInitCounter, - ); - } - return this.variableQuads; - } - - getDeleteQuads() { - return this.getVariableQuads().filter( - (quad) => quad.object.termType === "Variable", - ); - } - - getInsertQuads() { - return this.getQuads(); - } - - getWhereQuads() { - return this.getVariableQuads(); - } - - private getEntityWithReplacedVariables() { - return this.replaceVariables(this.entity, this.schema); - } - - private replaceVariables(entity: Entity, schema: Schema) { - return Object.keys(entity).reduce((output, key) => { - const value = entity[key]; - - if (key === "$id") { - output.$id = value as string; - return output; - } - if (key === "$type") { - output.$type = value; - return output; - } - - const property = schema[key] as Property; - if (!property) { - throw new Error("Unknown field '${key}' detected in entity"); - } - - if (!property["@schema"]) { - output[key] = null; - return output; - } - - if (property["@array"]) { - if ((value as Entity[]).length === 0) { - output[key] = null; - } else { - output[key] = (value as Entity[]).map((subEntity) => - this.replaceVariables(subEntity, property["@schema"]!) - ); - } - } else { - output[key] = this.replaceVariables( - value as Entity, - property["@schema"], - ); - } - - return output; - }, {} as Entity); - } -} diff --git a/tests/e2e/dbpedia.test.ts b/tests/e2e/dbpedia.test.ts index f9119f8..87da48f 100644 --- a/tests/e2e/dbpedia.test.ts +++ b/tests/e2e/dbpedia.test.ts @@ -58,6 +58,6 @@ Deno.test("E2E / DBpedia / Query multiple random remote entities", async () => { }); Deno.test("E2E / DBpedia / Query count", async () => { - const count = await Actors.count(100); + const count = await Actors.count({ max: 100 }); assert(count > 0); }); diff --git a/tests/lens_common.test.ts b/tests/lens_common.test.ts index 5eafe20..9dde157 100644 --- a/tests/lens_common.test.ts +++ b/tests/lens_common.test.ts @@ -142,6 +142,37 @@ Deno.test("Lens / Common / Count resources", async () => { assertEquals(count, 2); }); +Deno.test("Lens / Common / Count resources max", async () => { + const { directors } = init(); + const count = await directors.count({ max: 1 }); + assertEquals(count, 1); +}); + +Deno.test("Lens / Common / Count resources where", async () => { + const { directors } = init(); + const count = await directors.count({ + where: { name: { $strStarts: "Quentin" } }, + }); + assertEquals(count, 1); +}); + +Deno.test("Lens / Common / Count resources where nested", async () => { + const { movies } = init(); + const count = await movies.count({ + where: { director: { name: "Stanley Kubrick" } }, + }); + assertEquals(count, 2); +}); + +Deno.test("Lens / Common / Count resources max where nested", async () => { + const { movies } = init(); + const count = await movies.count({ + where: { director: { name: "Stanley Kubrick" } }, + max: 1, + }); + assertEquals(count, 1); +}); + Deno.test("Lens / Common / Insert multiple resources", async () => { const { directors, empty, assertStore } = init(); await empty();