feat: query.index (#11348)

What:
 - `query.index` helper. It queries the index module, and aggregate the rest of requested fields/relations if needed like `query.graph`.
 
Not covered in this PR:
 - Hydrate only sub entities returned by the query. Example: 1 out of 5 variants have returned, it should only hydrate the data of the single entity, currently it will merge all the variants of the product.
 - Generate types of indexed data
 
 example:
 ```ts
 const query = container.resolve(ContainerRegistrationKeys.QUERY)
        
 await query.index({
  entity: "product",
  fields: [
    "id",
    "description",
    "status",
    "variants.sku",
    "variants.barcode",
    "variants.material",
    "variants.options.value",
    "variants.prices.amount",
    "variants.prices.currency_code",
    "variants.inventory_items.inventory.sku",
    "variants.inventory_items.inventory.description",
  ],
  filters: {
    "variants.sku": { $like: "%-1" },
    "variants.prices.amount": { $gt: 30 },
  },
  pagination: {
    order: {
      "variants.prices.amount": "DESC",
    },
  },
})
```
This query return all products where at least one variant has the title ending in `-1` and at least one price bigger than `30`.
 
The Index Module only hold the data used to paginate and filter, and the returned object is:
```json
{
  "id": "prod_01JKEAM2GJZ14K64R0DHK0JE72",
  "title": null,
  "variants": [
    {
      "id": "variant_01JKEAM2HC89GWS95F6GF9C6YA",
      "sku": "extra-variant-1",
      "prices": [
        {
          "id": "price_01JKEAM2JADEWWX72F8QDP6QXT",
          "amount": 80,
          "currency_code": "USD"
        }
      ]
    }
  ]
}
```

All the rest of the fields will be hydrated from their respective modules, and the final result will be:

```json
{
  "id": "prod_01JKEAY2RJTF8TW9A23KTGY1GD",
  "description": "extra description",
  "status": "draft",
  "variants": [
    {
      "sku": "extra-variant-1",
      "barcode": null,
      "material": null,
      "id": "variant_01JKEAY2S945CRZ6X4QZJ7GVBJ",
      "options": [
        {
          "value": "Red"
        }
      ],
      "prices": [
        {
          "amount": 20,
          "currency_code": "CAD",
          "id": "price_01JKEAY2T2EEYSWZHPGG11B7W7"
        },
        {
          "amount": 80,
          "currency_code": "USD",
          "id": "price_01JKEAY2T2NJK2E5468RK84CAR"
        }
      ],
      "inventory_items": [
        {
          "variant_id": "variant_01JKEAY2S945CRZ6X4QZJ7GVBJ",
          "inventory_item_id": "iitem_01JKEAY2SNY2AWEHPZN0DDXVW6",
          "inventory": {
            "sku": "extra-variant-1",
            "description": "extra variant 1",
            "id": "iitem_01JKEAY2SNY2AWEHPZN0DDXVW6"
          }
        }
      ]
    }
  ]
}
```

Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
Carlos R. L. Rodrigues
2025-02-12 12:55:09 +00:00
committed by GitHub
co-authored by Adrien de Peretti
parent 8d10731343
commit 22276648ad
19 changed files with 1209 additions and 316 deletions
@@ -8,10 +8,12 @@ import {
import {
MikroOrmBaseRepository as BaseRepository,
ContainerRegistrationKeys,
deepMerge,
InjectManager,
InjectTransactionManager,
isDefined,
MedusaContext,
promiseAll,
toMikroORMEntity,
} from "@medusajs/framework/utils"
import {
@@ -249,20 +251,29 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
const select = normalizeFieldsSelection(fields)
const where = flattenObjectKeys(filters)
const joinWhere = flattenObjectKeys(joinFilters)
const orderBy = flattenObjectKeys(inputOrderBy)
const { manager } = sharedContext as { manager: SqlEntityManager }
let hasPagination = false
if (isDefined(skip)) {
let hasCount = false
if (isDefined(skip) || isDefined(take)) {
hasPagination = true
if (isDefined(skip)) {
hasCount = true
}
}
const requestedFields = deepMerge(deepMerge(select, filters), inputOrderBy)
const connection = manager.getConnection()
const qb = new QueryBuilder({
schema: this.schemaObjectRepresentation_,
entityMap: this.schemaEntitiesMap_,
knex: connection.getKnex(),
rawConfig: config,
selector: {
select,
where,
@@ -274,19 +285,40 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
keepFilteredEntities,
orderBy,
},
requestedFields,
})
const sql = qb.buildQuery(hasPagination, !!keepFilteredEntities)
const [sql, sqlCount] = qb.buildQuery({
hasPagination,
returnIdOnly: !!keepFilteredEntities,
hasCount,
})
let resultSet = await manager.execute(sql)
const count = hasPagination ? +(resultSet[0]?.count ?? 0) : undefined
const promises: Promise<any>[] = []
promises.push(manager.execute(sql))
if (hasCount && sqlCount) {
promises.push(manager.execute(sqlCount))
}
let [resultSet, count] = await promiseAll(promises)
const resultMetadata: IndexTypes.QueryFunctionReturnPagination | undefined =
hasPagination
? {
count: hasCount ? parseInt(count[0].count) : undefined,
skip,
take,
}
: undefined
if (keepFilteredEntities) {
const mainEntity = Object.keys(select)[0]
const ids = resultSet.map((r) => r[`${mainEntity}.id`])
if (ids.length) {
return await this.query<TEntry>(
const result = await this.query<TEntry>(
{
fields,
joinFilters,
@@ -300,6 +332,8 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
} as IndexTypes.IndexQueryConfig<TEntry>,
sharedContext
)
result.metadata ??= resultMetadata
return result
}
}
@@ -307,13 +341,7 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
data: qb.buildObjectFromResultset(
resultSet
) as IndexTypes.QueryResultSet<TEntry>["data"],
metadata: hasPagination
? {
count: count!,
skip,
take,
}
: undefined,
metadata: resultMetadata,
}
}
@@ -365,12 +393,19 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
return acc
}, {}) as TData
await indexRepository.upsert({
id: cleanedEntityData.id,
name: entity,
data: cleanedEntityData,
staled_at: null,
})
await indexRepository.upsert(
{
id: cleanedEntityData.id,
name: entity,
data: cleanedEntityData,
staled_at: null,
},
{
onConflictAction: "merge",
onConflictFields: ["id", "name"],
onConflictMergeFields: ["data", "staled_at"],
}
)
/**
* Retrieve the parents to attach it to the index entry.
@@ -391,12 +426,19 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
: [parentData]
for (const parentData_ of parentDataCollection) {
await indexRepository.upsert({
id: (parentData_ as any).id,
name: parentEntity,
data: parentData_,
staled_at: null,
})
await indexRepository.upsert(
{
id: (parentData_ as any).id,
name: parentEntity,
data: parentData_,
staled_at: null,
},
{
onConflictAction: "merge",
onConflictFields: ["id", "name"],
onConflictMergeFields: ["data", "staled_at"],
}
)
await indexRelationRepository.upsert(
{
@@ -416,6 +458,7 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
"parent_name",
"child_name",
],
onConflictMergeFields: ["staled_at"],
}
)
}
@@ -453,17 +496,24 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
)
await indexRepository.upsertMany(
data_.map((entityData) => {
return {
id: entityData.id,
name: entity,
data: entityProperties.reduce((acc, property) => {
acc[property] = entityData[property]
return acc
}, {}),
staled_at: null,
data_.map(
(entityData) => {
return {
id: entityData.id,
name: entity,
data: entityProperties.reduce((acc, property) => {
acc[property] = entityData[property]
return acc
}, {}),
staled_at: null,
}
},
{
onConflictAction: "merge",
onConflictFields: ["id", "name"],
onConflictMergeFields: ["data", "staled_at"],
}
})
)
)
}
@@ -605,12 +655,19 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
return acc
}, {}) as TData
await indexRepository.upsert({
id: cleanedEntityData.id,
name: entity,
data: cleanedEntityData,
staled_at: null,
})
await indexRepository.upsert(
{
id: cleanedEntityData.id,
name: entity,
data: cleanedEntityData,
staled_at: null,
},
{
onConflictAction: "merge",
onConflictFields: ["id", "name"],
onConflictMergeFields: ["data", "staled_at"],
}
)
/**
* Create the index relation entries for the parent entity and the child entity
@@ -634,6 +691,7 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
"parent_name",
"child_name",
],
onConflictMergeFields: ["staled_at"],
}
)
@@ -655,6 +713,7 @@ export class PostgresProvider implements IndexTypes.StorageProvider {
"parent_name",
"child_name",
],
onConflictMergeFields: ["staled_at"],
}
)
}