fix(index): Add type casting to raw order by (#10899)
**What** Fix index engine query builder to include column type casting on order by Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
co-authored by
Carlos R. L. Rodrigues
parent
11f98f374c
commit
8792d0c062
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/index": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(index): cast order by
|
||||||
@@ -23,6 +23,6 @@ export const schema = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Price @Listeners(values: ["price.created", "price.updated", "price.deleted"]) {
|
type Price @Listeners(values: ["price.created", "price.updated", "price.deleted"]) {
|
||||||
amount: Int
|
amount: Float
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
ModuleRegistrationName,
|
ModuleRegistrationName,
|
||||||
Modules,
|
Modules,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
|
import { initDb, TestDatabaseUtils } from "@medusajs/test-utils"
|
||||||
import { EntityManager } from "@mikro-orm/postgresql"
|
import { EntityManager } from "@mikro-orm/postgresql"
|
||||||
import { IndexData, IndexRelation } from "@models"
|
import { IndexData, IndexRelation } from "@models"
|
||||||
import { asValue } from "awilix"
|
import { asValue } from "awilix"
|
||||||
import { initDb, TestDatabaseUtils } from "@medusajs/test-utils"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { EventBusServiceMock } from "../__fixtures__"
|
import { EventBusServiceMock } from "../__fixtures__"
|
||||||
import { dbName } from "../__fixtures__/medusa-config"
|
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 () => {
|
it("should query products filtering by variant sku", async () => {
|
||||||
const { data } = await module.query({
|
const { data } = await module.query({
|
||||||
fields: ["product.*", "product.variants.*", "product.variants.prices.*"],
|
fields: ["product.*", "product.variants.*", "product.variants.prices.*"],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Knex } from "@mikro-orm/knex"
|
|
||||||
import { IndexTypes } from "@medusajs/framework/types"
|
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 { OrderBy, QueryFormat, QueryOptions, Select } from "@types"
|
||||||
import { isObject, isString, GraphQLUtils } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export const OPERATOR_MAP = {
|
export const OPERATOR_MAP = {
|
||||||
$eq: "=",
|
$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 fullPath = [path, ...field]
|
||||||
const prop = fullPath.pop()
|
const prop = fullPath.pop()
|
||||||
const fieldPath = fullPath.join(".")
|
const fieldPath = fullPath.join(".")
|
||||||
@@ -115,7 +124,18 @@ export class QueryBuilder {
|
|||||||
const isList = graphqlType.endsWith("[]")
|
const isList = graphqlType.endsWith("[]")
|
||||||
graphqlType = graphqlType.replace("[]", "")
|
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(
|
private parseWhere(
|
||||||
@@ -188,7 +208,7 @@ export class QueryBuilder {
|
|||||||
field,
|
field,
|
||||||
value[subKey]
|
value[subKey]
|
||||||
)
|
)
|
||||||
const castType = this.getPostgresCastType(attr, field)
|
const castType = this.getPostgresCastType(attr, [field]).cast
|
||||||
|
|
||||||
const val = operator === "IN" ? subValue : [subValue]
|
const val = operator === "IN" ? subValue : [subValue]
|
||||||
if (operator === "=" && subValue === null) {
|
if (operator === "=" && subValue === null) {
|
||||||
@@ -219,7 +239,7 @@ export class QueryBuilder {
|
|||||||
|
|
||||||
value = this.transformValueToType(attr, field, value)
|
value = this.transformValueToType(attr, field, value)
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
const castType = this.getPostgresCastType(attr, field)
|
const castType = this.getPostgresCastType(attr, field).cast
|
||||||
const inPlaceholders = value.map(() => "?").join(",")
|
const inPlaceholders = value.map(() => "?").join(",")
|
||||||
builder.whereRaw(
|
builder.whereRaw(
|
||||||
`(${aliasMapping[attr]}.data${nested}->>?)${castType} IN (${inPlaceholders})`,
|
`(${aliasMapping[attr]}.data${nested}->>?)${castType} IN (${inPlaceholders})`,
|
||||||
@@ -237,7 +257,7 @@ export class QueryBuilder {
|
|||||||
)}'::jsonb`
|
)}'::jsonb`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const castType = this.getPostgresCastType(attr, field)
|
const castType = this.getPostgresCastType(attr, field).cast
|
||||||
builder.whereRaw(
|
builder.whereRaw(
|
||||||
`(${aliasMapping[attr]}.data${nested}->>?)${castType} ${operator} ?`,
|
`(${aliasMapping[attr]}.data${nested}->>?)${castType} ${operator} ?`,
|
||||||
[...field, value]
|
[...field, value]
|
||||||
@@ -496,10 +516,14 @@ export class QueryBuilder {
|
|||||||
const path = aliasPath.split(".")
|
const path = aliasPath.split(".")
|
||||||
const field = path.pop()
|
const field = path.pop()
|
||||||
const attr = path.join(".")
|
const attr = path.join(".")
|
||||||
|
|
||||||
|
const pgType = this.getPostgresCastType(attr, [field])
|
||||||
const alias = aliasMapping[attr]
|
const alias = aliasMapping[attr]
|
||||||
const direction = orderBy[aliasPath]
|
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()})
|
let sql = `WITH data AS (${queryBuilder.toQuery()})
|
||||||
|
|||||||
Reference in New Issue
Block a user