Skip to content

Commit

Permalink
feat: support media deept filtering & relation shortcut filters
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandrebodin committed Mar 29, 2024
1 parent a8ca954 commit d59710c
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 11 deletions.
37 changes: 36 additions & 1 deletion packages/core/database/src/query/helpers/join.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,49 @@ const createPivotJoin = (
};

const createJoin = (ctx: Ctx, { alias, refAlias, attributeName, attribute }: JoinOptions) => {
const { db, qb } = ctx;
const { db, qb, uid } = ctx;

if (attribute.type !== 'relation') {
throw new Error(`Cannot join on non relational field ${attributeName}`);
}

const targetMeta = db.metadata.get(attribute.target);

if (attribute.relation.startsWith('morph')) {
const targetAttribute = targetMeta.attributes[attribute.morphBy];

// @ts-expect-error - morphBy is not defined on the attribute
const { joinTable } = targetAttribute;

if (!joinTable) {
return alias;
}

const joinAlias = qb.getAlias();
qb.join({
alias: joinAlias,
referencedTable: joinTable.name,
referencedColumn: joinTable.morphColumn.idColumn.name,
rootColumn: joinTable.morphColumn.idColumn.referencedColumn,
rootTable: alias,
on: {
[joinTable.morphColumn.typeColumn.name]: uid,
field: attributeName,
},
});

const subAlias = refAlias || qb.getAlias();
qb.join({
alias: subAlias,
referencedTable: targetMeta.tableName,
referencedColumn: joinTable.joinColumn.referencedColumn,
rootColumn: joinTable.joinColumn.name,
rootTable: joinAlias,
});

return subAlias;
}

const { joinColumn } = attribute;

if (joinColumn) {
Expand Down
49 changes: 39 additions & 10 deletions packages/core/database/src/query/helpers/where.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { isArray, castArray, keys, isPlainObject } from 'lodash/fp';
import { isArray, castArray, isPlainObject } from 'lodash/fp';
import type { Knex } from 'knex';

import { isOperatorOfType } from '@strapi/utils';
import { isOperator, isOperatorOfType } from '@strapi/utils';
import * as types from '../../utils/types';
import { createField } from '../../fields';
import { createJoin } from './join';
Expand All @@ -12,6 +12,8 @@ import { isKnexQuery } from '../../utils/knex';
import type { Ctx } from '../types';
import type { Attribute } from '../../types';

type WhereCtx = Ctx & { alias?: string; isGroupRoot?: boolean };

const isRecord = (value: unknown): value is Record<string, unknown> => isPlainObject(value);

const castValue = (value: unknown, attribute: Attribute | null) => {
Expand Down Expand Up @@ -72,7 +74,34 @@ const processNested = (where: unknown, ctx: WhereCtx) => {
return processWhere(where, ctx);
};

type WhereCtx = Ctx & { alias?: string };
const processRelationWhere = (where: unknown, ctx: WhereCtx) => {
const { qb, alias } = ctx;

const idAlias = qb.aliasColumn('id', alias);
if (!isRecord(where)) {
return { [idAlias]: where };
}

const keys = Object.keys(where);
const operatorKeys = keys.filter((key) => isOperator(key));

if (operatorKeys.length > 0 && operatorKeys.length !== keys.length) {
throw new Error(`Operator and non-operator keys cannot be mixed in a relation where clause`);
}

if (operatorKeys.length > 1) {
throw new Error(
`Only one operator key is allowed in a relation where clause found ${operatorKeys}`
);
}

if (operatorKeys.length === 1) {
const operator = operatorKeys[0];
return { [idAlias]: { [operator]: processNested(where[operator], ctx) } };
}

return processWhere(where, ctx);
};

/**
* Process where parameter
Expand Down Expand Up @@ -100,8 +129,12 @@ function processWhere(
for (const key of Object.keys(where)) {
const value = where[key];

// if operator $and $or then loop over them
if (isOperatorOfType('group', key) && Array.isArray(value)) {
// if operator $and $or -> process recursively
if (isOperatorOfType('group', key)) {
if (!Array.isArray(value)) {
throw new Error(`Operator ${key} must be an array`);
}

filters[key] = value.map((sub) => processNested(sub, ctx));
continue;
}
Expand Down Expand Up @@ -132,17 +165,13 @@ function processWhere(
attribute,
});

let nestedWhere = processNested(value, {
const nestedWhere = processRelationWhere(value, {
db,
qb,
alias: subAlias,
uid: attribute.target,
});

if (!isRecord(nestedWhere) || isOperatorOfType('where', keys(nestedWhere)[0])) {
nestedWhere = { [qb.aliasColumn('id', subAlias)]: nestedWhere };
}

// TODO: use a better merge logic (push to $and when collisions)
Object.assign(filters, nestedWhere);

Expand Down

0 comments on commit d59710c

Please sign in to comment.