From 877bb1c5414957126683ff3c7a895d1aae369bed Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Sun, 24 Nov 2024 16:35:15 +0700 Subject: [PATCH 1/5] fix: support top-level And Condition in query --- .../__tests__/convertQueryToClause.test.ts | 104 +++++++++++++-- packages/sdk/src/convertQuerytoClause.ts | 125 ++++++------------ 2 files changed, 130 insertions(+), 99 deletions(-) diff --git a/packages/sdk/src/__tests__/convertQueryToClause.test.ts b/packages/sdk/src/__tests__/convertQueryToClause.test.ts index fa8838c6..725cd817 100644 --- a/packages/sdk/src/__tests__/convertQueryToClause.test.ts +++ b/packages/sdk/src/__tests__/convertQueryToClause.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest"; import { MockSchemaType, schema } from "../__example__/index"; import { convertQueryToClause } from "../convertQuerytoClause"; -import { QueryType, SchemaType } from "../types"; +import { QueryType } from "../types"; describe("convertQueryToClause", () => { it("should convert a single model query with conditions", () => { @@ -134,12 +134,90 @@ describe("convertQueryToClause", () => { const result = convertQueryToClause(query, schema); - console.log("result", result); - // Updated expectation to match the actual output expect(result).toEqual({ Composite: { operator: "Or", + clauses: [ + { + Composite: { + operator: "And", + clauses: [ + { + Member: { + model: "world-player", + member: "score", + operator: "Gt", + value: { Primitive: { U32: 100 } }, + }, + }, + { + Composite: { + operator: "Or", + clauses: [ + { + Member: { + model: "world-player", + member: "name", + operator: "Eq", + value: { String: "Alice" }, + }, + }, + { + Member: { + model: "world-player", + member: "name", + operator: "Eq", + value: { String: "Bob" }, + }, + }, + ], + }, + }, + ], + }, + }, + { + Member: { + model: "world-item", + member: "durability", + operator: "Lt", + value: { Primitive: { U32: 50 } }, + }, + }, + ], + }, + }); + }); + + it("should handle top level AND conditions", () => { + const query: QueryType = { + world: { + player: { + $: { + where: { + And: [ + { score: { $gt: 100 } }, + { name: { $eq: "Alice" } }, + { + Or: [ + { name: { $eq: "Bob" } }, + { name: { $eq: "Charlie" } }, + ], + }, + ], + }, + }, + }, + }, + }; + + const result = convertQueryToClause(query, schema); + + // Updated expectation to match the actual output + expect(result).toEqual({ + Composite: { + operator: "And", clauses: [ { Member: { @@ -149,6 +227,14 @@ describe("convertQueryToClause", () => { value: { Primitive: { U32: 100 } }, }, }, + { + Member: { + model: "world-player", + member: "name", + operator: "Eq", + value: { String: "Alice" }, + }, + }, { Composite: { operator: "Or", @@ -158,7 +244,7 @@ describe("convertQueryToClause", () => { model: "world-player", member: "name", operator: "Eq", - value: { String: "Alice" }, + value: { String: "Bob" }, }, }, { @@ -166,20 +252,12 @@ describe("convertQueryToClause", () => { model: "world-player", member: "name", operator: "Eq", - value: { String: "Bob" }, + value: { String: "Charlie" }, }, }, ], }, }, - { - Member: { - model: "world-item", - member: "durability", - operator: "Lt", - value: { Primitive: { U32: 50 } }, - }, - }, ], }, }); diff --git a/packages/sdk/src/convertQuerytoClause.ts b/packages/sdk/src/convertQuerytoClause.ts index d855fc00..117f81b4 100644 --- a/packages/sdk/src/convertQuerytoClause.ts +++ b/packages/sdk/src/convertQuerytoClause.ts @@ -17,23 +17,33 @@ export function convertQueryToClause( schema: T ): torii.Clause | undefined { const clauses: torii.Clause[] = []; + let hasOnlyAndCondition = true; for (const [namespace, models] of Object.entries(query)) { - if (namespace === "entityIds") continue; // Skip entityIds + if (namespace === "entityIds") continue; if (models && typeof models === "object") { const modelClauses = processModels(namespace, models, schema); if (modelClauses.length > 0) { + if ( + !modelClauses.every( + (clause) => + clause && + "Composite" in clause && + clause.Composite.operator === "And" + ) + ) { + hasOnlyAndCondition = false; + } clauses.push(...modelClauses); } } } - // If there are clauses, combine them under a single Composite clause if (clauses.length > 1) { return { Composite: { - operator: "Or", + operator: hasOnlyAndCondition ? "And" : "Or", clauses: clauses, }, }; @@ -41,7 +51,6 @@ export function convertQueryToClause( return clauses[0]; } - // If there are no clauses, return undefined return undefined; } @@ -73,7 +82,6 @@ function processModels( ) { const whereClause = conditions.where; if (whereClause && typeof whereClause === "object") { - // Iterate over each member in the whereClause to handle $is for (const [member, memberConditions] of Object.entries( whereClause )) { @@ -82,7 +90,6 @@ function processModels( memberConditions !== null && "$is" in memberConditions ) { - // Convert $is to EntityKeysClause const isClauses = convertQueryToEntityKeyClauses( { [namespace]: { @@ -103,7 +110,6 @@ function processModels( ); clauses.push(...(isClauses as any)); - // Remove $is from memberConditions to prevent further processing const { $is, ...remainingConditions } = memberConditions; (whereClause as Record)[member] = @@ -111,27 +117,20 @@ function processModels( } } - // After handling all $is, build the remaining whereClause const clause = buildWhereClause( namespaceModel, whereClause ); if (clause) { - if ( - "Composite" in clause && - clause.Composite.operator === "And" - ) { - // If the composite operator is "And", flatten the clauses - clauses.push(...clause.Composite.clauses); + if (Array.isArray(clause)) { + clauses.push(...clause); } else { - // Otherwise, keep the composite as is to preserve logical structure clauses.push(clause); } } } } } else { - // Handle the case where there are no conditions clauses.push({ Keys: { keys: [undefined], @@ -150,13 +149,12 @@ function processModels( * * @param {string} namespaceModel - The namespaced model identifier. * @param {Record} where - The where clause conditions. - * @returns {torii.Clause | undefined} - The constructed Torii clause or undefined. + * @returns {torii.Clause | torii.Clause[] | undefined} - The constructed Torii clause or undefined. */ function buildWhereClause( namespaceModel: string, where: Record -): torii.Clause | undefined { - // Define logical operator mapping +): torii.Clause | torii.Clause[] | undefined { const logicalOperators: Record = { And: "And", Or: "Or", @@ -167,21 +165,24 @@ function buildWhereClause( const logicalKey = keys.find((key) => key in logicalOperators); if (logicalKey) { + // Handle explicit And/Or conditions const operator = logicalOperators[logicalKey]; const conditions = where[logicalKey] as Array>; const subClauses: torii.Clause[] = []; - for (const condition of conditions) { const clause = buildWhereClause(namespaceModel, condition); if (clause) { - subClauses.push(clause); + if (Array.isArray(clause)) { + subClauses.push(...clause); + } else { + subClauses.push(clause); + } } } - if (subClauses.length === 1) { - return subClauses[0]; - } + if (subClauses.length === 0) return undefined; + if (subClauses.length === 1) return subClauses[0]; return { Composite: { @@ -196,73 +197,25 @@ function buildWhereClause( for (const [member, memberValue] of Object.entries(where)) { if (typeof memberValue === "object" && memberValue !== null) { - const memberKeys = Object.keys(memberValue); - // Check if memberValue contains logical operators - const memberLogicalKey = memberKeys.find( - (key) => key in logicalOperators - ); - if (memberLogicalKey) { - const operator = logicalOperators[memberLogicalKey]; - const conditions = memberValue[memberLogicalKey] as Array< - Record - >; - - const nestedClauses: torii.Clause[] = []; - for (const condition of conditions) { - const clause = buildWhereClause(namespaceModel, condition); - if (clause) { - nestedClauses.push(clause); - } - } - - if (nestedClauses.length === 1) { - memberClauses.push(nestedClauses[0]); - } else { - memberClauses.push({ - Composite: { - operator: operator, - clauses: nestedClauses, - }, - }); - } - } else { - // Process operators like $eq, $gt, etc - for (const [op, val] of Object.entries(memberValue)) { - memberClauses.push({ - Member: { - model: namespaceModel, - member, - operator: convertOperator(op), - value: convertToPrimitive(val), - }, - }); - } + for (const [op, val] of Object.entries(memberValue)) { + if (!op.startsWith("$")) continue; + memberClauses.push({ + Member: { + model: namespaceModel, + member, + operator: convertOperator(op), + value: convertToPrimitive(val), + }, + }); } - } else { - // Assume equality condition - memberClauses.push({ - Member: { - model: namespaceModel, - member, - operator: "Eq", - value: convertToPrimitive(memberValue), - }, - }); } } - if (memberClauses.length === 1) { - return memberClauses[0]; - } else if (memberClauses.length > 1) { - return { - Composite: { - operator: "And", - clauses: memberClauses, - }, - }; - } + if (memberClauses.length === 0) return undefined; + if (memberClauses.length === 1) return memberClauses[0]; - return undefined; + // Return array of Member clauses directly + return memberClauses; } /** From a1eaf8c974dda039cfffe4d5e89d331ef622e693 Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Sun, 24 Nov 2024 16:37:58 +0700 Subject: [PATCH 2/5] fix: remove unnecessary console.log --- packages/sdk/src/convertQuerytoClause.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sdk/src/convertQuerytoClause.ts b/packages/sdk/src/convertQuerytoClause.ts index 117f81b4..3501326b 100644 --- a/packages/sdk/src/convertQuerytoClause.ts +++ b/packages/sdk/src/convertQuerytoClause.ts @@ -252,7 +252,6 @@ function convertToPrimitive(value: any): torii.MemberValue { * @throws {Error} - If the operator is unsupported. */ function convertOperator(operator: string): torii.ComparisonOperator { - console.log(operator); switch (operator) { case "$eq": return "Eq"; From 55d2c240efac5fb677fe57b770f5729792154d95 Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Sun, 24 Nov 2024 17:04:33 +0700 Subject: [PATCH 3/5] fix: recover comment --- packages/sdk/src/convertQuerytoClause.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/convertQuerytoClause.ts b/packages/sdk/src/convertQuerytoClause.ts index 3501326b..19325deb 100644 --- a/packages/sdk/src/convertQuerytoClause.ts +++ b/packages/sdk/src/convertQuerytoClause.ts @@ -20,7 +20,7 @@ export function convertQueryToClause( let hasOnlyAndCondition = true; for (const [namespace, models] of Object.entries(query)) { - if (namespace === "entityIds") continue; + if (namespace === "entityIds") continue; // Skip entityIds; if (models && typeof models === "object") { const modelClauses = processModels(namespace, models, schema); @@ -40,6 +40,7 @@ export function convertQueryToClause( } } + // If there are multiple clauses, wrap them in a Composite clause if (clauses.length > 1) { return { Composite: { @@ -90,6 +91,7 @@ function processModels( memberConditions !== null && "$is" in memberConditions ) { + // Convert $is to EntityKeysClause const isClauses = convertQueryToEntityKeyClauses( { [namespace]: { @@ -110,6 +112,7 @@ function processModels( ); clauses.push(...(isClauses as any)); + // Remove $is from the where clause const { $is, ...remainingConditions } = memberConditions; (whereClause as Record)[member] = @@ -117,6 +120,7 @@ function processModels( } } + // After handling all $is, build the remaining whereClause const clause = buildWhereClause( namespaceModel, whereClause @@ -131,6 +135,7 @@ function processModels( } } } else { + // Handle the case where there are no conditions clauses.push({ Keys: { keys: [undefined], From 2918c87d6f65867e9e599bdf70a1e4392307cbb7 Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Sun, 24 Nov 2024 17:08:35 +0700 Subject: [PATCH 4/5] fix: tweak --- packages/sdk/src/convertQuerytoClause.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sdk/src/convertQuerytoClause.ts b/packages/sdk/src/convertQuerytoClause.ts index 19325deb..5f0e2f86 100644 --- a/packages/sdk/src/convertQuerytoClause.ts +++ b/packages/sdk/src/convertQuerytoClause.ts @@ -52,6 +52,7 @@ export function convertQueryToClause( return clauses[0]; } + // If there are no clauses, return undefined return undefined; } From e9806bdc7f5f341486d11da434f52da1e0450d19 Mon Sep 17 00:00:00 2001 From: Asuma Yamada Date: Sun, 24 Nov 2024 17:10:03 +0700 Subject: [PATCH 5/5] fix: test case --- .../sdk/src/__tests__/convertQueryToClause.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/sdk/src/__tests__/convertQueryToClause.test.ts b/packages/sdk/src/__tests__/convertQueryToClause.test.ts index 725cd817..5fae9a86 100644 --- a/packages/sdk/src/__tests__/convertQueryToClause.test.ts +++ b/packages/sdk/src/__tests__/convertQueryToClause.test.ts @@ -198,11 +198,11 @@ describe("convertQueryToClause", () => { where: { And: [ { score: { $gt: 100 } }, - { name: { $eq: "Alice" } }, + { name: { $eq: "Charlie" } }, { Or: [ + { name: { $eq: "Alice" } }, { name: { $eq: "Bob" } }, - { name: { $eq: "Charlie" } }, ], }, ], @@ -232,7 +232,7 @@ describe("convertQueryToClause", () => { model: "world-player", member: "name", operator: "Eq", - value: { String: "Alice" }, + value: { String: "Charlie" }, }, }, { @@ -244,7 +244,7 @@ describe("convertQueryToClause", () => { model: "world-player", member: "name", operator: "Eq", - value: { String: "Bob" }, + value: { String: "Alice" }, }, }, { @@ -252,7 +252,7 @@ describe("convertQueryToClause", () => { model: "world-player", member: "name", operator: "Eq", - value: { String: "Charlie" }, + value: { String: "Bob" }, }, }, ],