diff --git a/packages/sdk/src/__tests__/convertQueryToClause.test.ts b/packages/sdk/src/__tests__/convertQueryToClause.test.ts index fa8838c6..5fae9a86 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: "Charlie" } }, + { + Or: [ + { name: { $eq: "Alice" } }, + { name: { $eq: "Bob" } }, + ], + }, + ], + }, + }, + }, + }, + }; + + 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: "Charlie" }, + }, + }, { Composite: { operator: "Or", @@ -172,14 +258,6 @@ describe("convertQueryToClause", () => { ], }, }, - { - 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..5f0e2f86 100644 --- a/packages/sdk/src/convertQuerytoClause.ts +++ b/packages/sdk/src/convertQuerytoClause.ts @@ -17,23 +17,34 @@ 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; // Skip entityIds; 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 there are multiple clauses, wrap them in a Composite clause if (clauses.length > 1) { return { Composite: { - operator: "Or", + operator: hasOnlyAndCondition ? "And" : "Or", clauses: clauses, }, }; @@ -73,7 +84,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 )) { @@ -103,7 +113,7 @@ function processModels( ); clauses.push(...(isClauses as any)); - // Remove $is from memberConditions to prevent further processing + // Remove $is from the where clause const { $is, ...remainingConditions } = memberConditions; (whereClause as Record)[member] = @@ -117,14 +127,9 @@ function processModels( 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); } } @@ -150,13 +155,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 +171,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 +203,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; } /** @@ -299,7 +258,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";