2020-10-27 11:27:17 +01:00
'use strict' ;
2019-09-20 12:44:24 +02:00
//TODO: move to dbal
2019-03-13 19:27:18 +01:00
const _ = require ( 'lodash' ) ;
2019-12-04 12:17:45 +01:00
const parseType = require ( './parse-type' ) ;
2019-03-13 19:27:18 +01:00
2019-07-18 19:28:52 +02:00
const isAttribute = ( model , field ) =>
2020-06-30 15:50:52 +02:00
_ . has ( model . allAttributes , field ) || model . primaryKey === field || field === 'id' ;
2019-03-22 12:16:09 +01:00
2019-03-28 17:12:43 +01:00
/ * *
* Returns the model , attribute name and association from a path of relation
* @ param { Object } options - Options
2020-12-08 11:30:05 +01:00
* @ param { Object } options . model - Strapi model
2020-01-07 12:51:21 +01:00
* @ param { string } options . field - path of relation / attribute
2019-03-28 17:12:43 +01:00
* /
const getAssociationFromFieldKey = ( { model , field } ) => {
2019-03-13 19:27:18 +01:00
const fieldParts = field . split ( '.' ) ;
2019-03-22 12:16:09 +01:00
let tmpModel = model ;
2019-03-28 17:12:43 +01:00
let association ;
let attribute ;
2019-03-22 12:16:09 +01:00
for ( let i = 0 ; i < fieldParts . length ; i ++ ) {
2019-03-28 17:12:43 +01:00
const part = fieldParts [ i ] ;
attribute = part ;
2019-03-22 12:16:09 +01:00
2019-03-28 17:12:43 +01:00
const assoc = tmpModel . associations . find ( ast => ast . alias === part ) ;
2019-03-22 12:16:09 +01:00
if ( assoc ) {
2019-03-28 17:12:43 +01:00
association = assoc ;
2020-05-29 17:23:42 +02:00
tmpModel = strapi . db . getModelByAssoc ( assoc ) ;
2019-03-22 12:16:09 +01:00
continue ;
}
2020-06-30 15:50:52 +02:00
if ( ! assoc && ( ! isAttribute ( tmpModel , part ) || i !== fieldParts . length - 1 ) ) {
2019-03-28 17:12:43 +01:00
const err = new Error (
` Your filters contain a field ' ${ field } ' that doesn't appear on your model definition nor it's relations `
) ;
err . status = 400 ;
throw err ;
2019-03-13 19:27:18 +01:00
}
2019-03-22 12:16:09 +01:00
}
2019-03-13 19:27:18 +01:00
2019-03-28 17:12:43 +01:00
return {
association ,
model : tmpModel ,
attribute ,
} ;
} ;
/ * *
2019-12-04 12:17:45 +01:00
* Cast an input value
2019-03-28 17:12:43 +01:00
* @ param { Object } options - Options
* @ param { string } options . type - type of the atribute
* @ param { * } options . value - value tu cast
2019-12-04 12:17:45 +01:00
* @ param { string } options . operator - name of operator
2019-03-28 17:12:43 +01:00
* /
2019-12-04 12:17:45 +01:00
const castInput = ( { type , value , operator } ) => {
return Array . isArray ( value )
? value . map ( val => castValue ( { type , operator , value : val } ) )
: castValue ( { type , operator , value : value } ) ;
2019-03-13 19:27:18 +01:00
} ;
2019-07-04 19:10:17 -03:00
/ * *
* Cast basic values based on attribute type
* @ param { Object } options - Options
* @ param { string } options . type - type of the atribute
* @ param { * } options . value - value tu cast
* @ param { string } options . operator - name of operator
* /
2019-07-18 19:28:52 +02:00
const castValue = ( { type , value , operator } ) => {
2019-12-04 12:17:45 +01:00
if ( operator === 'null' ) return parseType ( { type : 'boolean' , value } ) ;
return parseType ( { type , value } ) ;
2019-07-18 19:28:52 +02:00
} ;
2020-01-07 12:51:21 +01:00
/ * *
*
* @ param { Object } options - Options
* @ param { string } options . model - The model
* @ param { string } options . field - path of relation / attribute
* /
const normalizeFieldName = ( { model , field } ) => {
const fieldPath = field . split ( '.' ) ;
return _ . last ( fieldPath ) === 'id'
? _ . initial ( fieldPath )
. concat ( model . primaryKey )
. join ( '.' )
: fieldPath . join ( '.' ) ;
} ;
2020-06-25 20:20:02 +02:00
const BOOLEAN _OPERATORS = [ 'or' ] ;
2020-12-10 11:17:41 +01:00
const hasDeepFilters = ( { where = [ ] , sort = [ ] } , { minDepth = 1 } = { } ) => {
2020-12-08 11:30:05 +01:00
// A query uses deep filtering if some of the clauses contains a sort or a match expression on a field of a relation
// We don't use minDepth here because deep sorting is limited to depth 1
const hasDeepSortClauses = sort . some ( ( { field } ) => field . includes ( '.' ) ) ;
const hasDeepWhereClauses = where . some ( ( { field , operator , value } ) => {
if ( BOOLEAN _OPERATORS . includes ( operator ) ) {
return value . some ( clauses => hasDeepFilters ( { where : clauses } ) ) ;
}
2020-06-25 20:20:02 +02:00
2020-12-08 11:30:05 +01:00
return field . split ( '.' ) . length > minDepth ;
} ) ;
return hasDeepSortClauses || hasDeepWhereClauses ;
2020-06-25 20:20:02 +02:00
} ;
2020-12-10 08:50:28 +01:00
const normalizeWhereClauses = ( whereClauses , { model } ) => {
2020-06-25 20:20:02 +02:00
return whereClauses
2021-04-07 13:37:42 +02:00
. filter ( ( { field , value } ) => {
if ( _ . isNull ( value ) ) {
return false ;
} else if ( _ . isUndefined ( value ) ) {
strapi . log . warn ( ` The value of field: ' ${ field } ', in your where filter, is undefined. ` ) ;
return false ;
2021-02-01 16:18:17 +01:00
}
2021-04-07 13:37:42 +02:00
return true ;
} )
. map ( ( { field , operator , value } ) => {
2020-06-25 20:20:02 +02:00
if ( BOOLEAN _OPERATORS . includes ( operator ) ) {
return {
field ,
operator ,
2020-12-10 08:50:28 +01:00
value : value . map ( clauses => normalizeWhereClauses ( clauses , { model } ) ) ,
2020-06-25 20:20:02 +02:00
} ;
}
const { model : assocModel , attribute } = getAssociationFromFieldKey ( {
model ,
field ,
} ) ;
const { type } = _ . get ( assocModel , [ 'allAttributes' , attribute ] , { } ) ;
// cast value or array of values
const castedValue = castInput ( { type , operator , value } ) ;
return {
field : normalizeFieldName ( { model , field } ) ,
operator ,
value : castedValue ,
} ;
} ) ;
} ;
2020-12-10 08:50:28 +01:00
const normalizeSortClauses = ( clauses , { model } ) => {
const normalizedClauses = clauses . map ( ( { field , order } ) => ( {
field : normalizeFieldName ( { model , field } ) ,
order ,
} ) ) ;
normalizedClauses . forEach ( ( { field } ) => {
2021-01-11 17:21:01 +01:00
const fieldDepth = field . split ( '.' ) . length - 1 ;
if ( fieldDepth === 1 ) {
2020-12-10 08:50:28 +01:00
// Check if the relational field exists
getAssociationFromFieldKey ( { model , field } ) ;
2021-01-11 17:21:01 +01:00
} else if ( fieldDepth > 1 ) {
const err = new Error (
` Sorting on ${ field } is not possible: you cannot sort at a depth greater than 1 `
) ;
err . status = 400 ;
throw err ;
2020-12-10 08:50:28 +01:00
}
} ) ;
return normalizedClauses ;
} ;
2019-03-26 14:57:02 +01:00
/ * *
*
* @ param { Object } options - Options
* @ param { Object } options . model - The model for which the query will be built
* @ param { Object } options . filters - The filters for the query ( start , sort , limit , and where clauses )
* @ param { Object } options . rest - In case the database layer requires any other params pass them
* /
2019-03-28 17:12:43 +01:00
const buildQuery = ( { model , filters = { } , ... rest } ) => {
2020-12-08 11:30:05 +01:00
const { where , sort } = filters ;
2019-03-26 14:57:02 +01:00
// Validate query clauses
2020-12-08 11:30:05 +01:00
if ( [ where , sort ] . some ( Array . isArray ) ) {
2020-12-10 11:17:41 +01:00
if ( hasDeepFilters ( { where , sort } , { minDepth : 2 } ) ) {
2019-03-26 14:57:02 +01:00
strapi . log . warn (
'Deep filtering queries should be used carefully (e.g Can cause performance issues).\nWhen possible build custom routes which will in most case be more optimised.'
) ;
}
2020-12-08 11:30:05 +01:00
if ( sort ) {
2020-12-10 08:50:28 +01:00
filters . sort = normalizeSortClauses ( sort , { model } ) ;
2020-12-08 11:30:05 +01:00
}
if ( where ) {
// Cast where clauses to match the inner types
2020-12-10 08:50:28 +01:00
filters . where = normalizeWhereClauses ( where , { model } ) ;
2020-12-08 11:30:05 +01:00
}
2019-03-13 19:27:18 +01:00
}
2020-12-08 11:30:05 +01:00
// call the ORM's buildQuery implementation
2020-06-30 15:50:52 +02:00
return strapi . db . connectors . get ( model . orm ) . buildQuery ( { model , filters , ... rest } ) ;
2019-03-13 19:27:18 +01:00
} ;
2020-06-26 17:37:52 +02:00
module . exports = {
buildQuery ,
hasDeepFilters ,
} ;