diff --git a/.changeset/nice-tigers-visit.md b/.changeset/nice-tigers-visit.md new file mode 100644 index 0000000000..5f988d5b34 --- /dev/null +++ b/.changeset/nice-tigers-visit.md @@ -0,0 +1,5 @@ +--- +"@medusajs/index": patch +--- + +fix(index): cast order by diff --git a/packages/modules/index/integration-tests/__fixtures__/schema.ts b/packages/modules/index/integration-tests/__fixtures__/schema.ts index 1f07b3d9da..17dfeb3804 100644 --- a/packages/modules/index/integration-tests/__fixtures__/schema.ts +++ b/packages/modules/index/integration-tests/__fixtures__/schema.ts @@ -23,6 +23,6 @@ export const schema = ` } type Price @Listeners(values: ["price.created", "price.updated", "price.deleted"]) { - amount: Int + amount: Float } ` diff --git a/packages/modules/index/integration-tests/__tests__/query-builder.spec.ts b/packages/modules/index/integration-tests/__tests__/query-builder.spec.ts index 6aea157b00..7a5549637a 100644 --- a/packages/modules/index/integration-tests/__tests__/query-builder.spec.ts +++ b/packages/modules/index/integration-tests/__tests__/query-builder.spec.ts @@ -11,10 +11,10 @@ import { ModuleRegistrationName, Modules, } from "@medusajs/framework/utils" +import { initDb, TestDatabaseUtils } from "@medusajs/test-utils" import { EntityManager } from "@mikro-orm/postgresql" import { IndexData, IndexRelation } from "@models" import { asValue } from "awilix" -import { initDb, TestDatabaseUtils } from "@medusajs/test-utils" import path from "path" import { EventBusServiceMock } from "../__fixtures__" import { dbName } from "../__fixtures__/medusa-config" @@ -343,6 +343,116 @@ describe("IndexModuleService query", function () { ]) }) + it("should query all products ordered by price", async () => { + const { data } = await module.query({ + fields: ["product.*", "product.variants.*", "product.variants.prices.*"], + pagination: { + order: { + product: { + variants: { + prices: { + amount: "DESC", + }, + }, + }, + }, + }, + }) + + expect(data).toEqual([ + { + id: "prod_1", + variants: [ + { + id: "var_1", + sku: "aaa test aaa", + prices: [ + { + id: "money_amount_1", + amount: 100, + }, + ], + }, + { + id: "var_2", + sku: "sku 123", + prices: [ + { + id: "money_amount_2", + amount: 10, + }, + ], + }, + ], + }, + { + id: "prod_2", + title: "Product 2 title", + deep: { + a: 1, + obj: { + b: 15, + }, + }, + variants: [], + }, + ]) + + const { data: dataAsc } = await module.query({ + fields: ["product.*", "product.variants.*", "product.variants.prices.*"], + pagination: { + order: { + product: { + variants: { + prices: { + amount: "ASC", + }, + }, + }, + }, + }, + }) + + expect(dataAsc).toEqual([ + { + id: "prod_2", + title: "Product 2 title", + deep: { + a: 1, + obj: { + b: 15, + }, + }, + variants: [], + }, + { + id: "prod_1", + variants: [ + { + id: "var_2", + sku: "sku 123", + prices: [ + { + id: "money_amount_2", + amount: 10, + }, + ], + }, + { + id: "var_1", + sku: "aaa test aaa", + prices: [ + { + id: "money_amount_1", + amount: 100, + }, + ], + }, + ], + }, + ]) + }) + it("should query products filtering by variant sku", async () => { const { data } = await module.query({ fields: ["product.*", "product.variants.*", "product.variants.prices.*"], diff --git a/packages/modules/index/src/utils/query-builder.ts b/packages/modules/index/src/utils/query-builder.ts index 89c818b603..b6cbec4fc9 100644 --- a/packages/modules/index/src/utils/query-builder.ts +++ b/packages/modules/index/src/utils/query-builder.ts @@ -1,7 +1,7 @@ -import { Knex } from "@mikro-orm/knex" import { IndexTypes } from "@medusajs/framework/types" +import { GraphQLUtils, isObject, isString } from "@medusajs/framework/utils" +import { Knex } from "@mikro-orm/knex" import { OrderBy, QueryFormat, QueryOptions, Select } from "@types" -import { isObject, isString, GraphQLUtils } from "@medusajs/framework/utils" export const OPERATOR_MAP = { $eq: "=", @@ -108,6 +108,15 @@ export class QueryBuilder { "": "", } + const defaultValues = { + Int: "0", + Float: "0", + Boolean: "false", + Date: "1970-01-01 00:00:00", + Time: "00:00:00", + "": "", + } + const fullPath = [path, ...field] const prop = fullPath.pop() const fieldPath = fullPath.join(".") @@ -115,7 +124,18 @@ export class QueryBuilder { const isList = graphqlType.endsWith("[]") graphqlType = graphqlType.replace("[]", "") - return (graphqlToPostgresTypeMap[graphqlType] ?? "") + (isList ? "[]" : "") + const cast = + (graphqlToPostgresTypeMap[graphqlType] ?? "") + (isList ? "[]" : "") + + function generateCoalesceExpression(field) { + const defaultValue = defaultValues[graphqlType] + return `COALESCE(${field}, '${defaultValue}')${cast}` + } + + return { + cast, + coalesce: generateCoalesceExpression, + } } private parseWhere( @@ -188,7 +208,7 @@ export class QueryBuilder { field, value[subKey] ) - const castType = this.getPostgresCastType(attr, field) + const castType = this.getPostgresCastType(attr, [field]).cast const val = operator === "IN" ? subValue : [subValue] if (operator === "=" && subValue === null) { @@ -219,7 +239,7 @@ export class QueryBuilder { value = this.transformValueToType(attr, field, value) if (Array.isArray(value)) { - const castType = this.getPostgresCastType(attr, field) + const castType = this.getPostgresCastType(attr, field).cast const inPlaceholders = value.map(() => "?").join(",") builder.whereRaw( `(${aliasMapping[attr]}.data${nested}->>?)${castType} IN (${inPlaceholders})`, @@ -237,7 +257,7 @@ export class QueryBuilder { )}'::jsonb` ) } else { - const castType = this.getPostgresCastType(attr, field) + const castType = this.getPostgresCastType(attr, field).cast builder.whereRaw( `(${aliasMapping[attr]}.data${nested}->>?)${castType} ${operator} ?`, [...field, value] @@ -496,10 +516,14 @@ export class QueryBuilder { const path = aliasPath.split(".") const field = path.pop() const attr = path.join(".") + + const pgType = this.getPostgresCastType(attr, [field]) const alias = aliasMapping[attr] const direction = orderBy[aliasPath] - queryBuilder.orderByRaw(`${alias}.data->>'${field}' ${direction}`) + queryBuilder.orderByRaw( + pgType.coalesce(`${alias}.data->>'${field}'`) + " " + direction + ) } let sql = `WITH data AS (${queryBuilder.toQuery()})