common api - null equivalence (#15926)
closes https://github.com/twentyhq/core-team-issues/issues/1629 To do before requesting review : - filter update Migration to come in an other PR Strat : 1/ Null transformation - [x] Transform NULL equivalent value to NULL in field validation in common api - pre-query - with feature flag - [ ] Same logic in ORM (Not done, complex to handle feature flag here) - [x] Transform NULL value to equivalent in data formatting in ORM - post-query 2/ Migration (in other PR) for fieldMetadata not nullable with default defaultValue (empty string, ...) - [ ] Remove NOT NULL db constraint - [ ] Update record value to NULL - [ ] Update field metadata : isNullable:true - [ ] Update uniqueIndex whereClause (also for standard uniqueIndex) - [ ] Activate feature flag 3/ Update metadata creation - [x] No more default default value - [x] Update standard field nullability - [x] Remove index default whereClause for standard field 4/ Update filter - [x] When filtering on NULL or empty string, be sure all records are returned (the one with NULL + the one with "") 5/ Test - [ ] Strat. to do
This commit is contained in:
+4
-2
@@ -1,6 +1,6 @@
|
||||
import { compositeTypeDefinitions } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
import { type WhereExpressionBuilder } from 'typeorm';
|
||||
import { compositeTypeDefinitions } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
@@ -60,12 +60,12 @@ export class GraphqlQueryFilterFieldParser {
|
||||
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
const { sql, params } = computeWhereConditionParts({
|
||||
operator,
|
||||
objectNameSingular,
|
||||
key,
|
||||
value,
|
||||
fieldMetadataType: fieldMetadata.type,
|
||||
});
|
||||
|
||||
if (isFirst) {
|
||||
@@ -125,7 +125,9 @@ export class GraphqlQueryFilterFieldParser {
|
||||
operator,
|
||||
objectNameSingular,
|
||||
key: fullFieldName,
|
||||
subFieldKey,
|
||||
value,
|
||||
fieldMetadataType: fieldMetadata.type,
|
||||
});
|
||||
|
||||
if (isFirst && index === 0) {
|
||||
|
||||
+27
-7
@@ -1,5 +1,8 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { findPostgresDefaultNullEquivalentValue } from 'src/engine/api/common/common-args-processors/data-arg-processor/utils/find-postgres-default-null-equivalent-value.util';
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
@@ -15,30 +18,45 @@ export const computeWhereConditionParts = ({
|
||||
operator,
|
||||
objectNameSingular,
|
||||
key,
|
||||
subFieldKey,
|
||||
value,
|
||||
fieldMetadataType,
|
||||
}: {
|
||||
operator: string;
|
||||
objectNameSingular: string;
|
||||
key: string;
|
||||
subFieldKey?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any;
|
||||
fieldMetadataType: FieldMetadataType;
|
||||
}): WhereConditionParts => {
|
||||
const uuid = Math.random().toString(36).slice(2, 7);
|
||||
|
||||
const secondUuid = Math.random().toString(36).slice(2, 7);
|
||||
|
||||
//TODO : Remove filter null equivalence injection once feature flag removed + null equivalence transformation added in ORM
|
||||
const nullEquivalentFieldValue = findPostgresDefaultNullEquivalentValue(
|
||||
value,
|
||||
fieldMetadataType,
|
||||
subFieldKey,
|
||||
);
|
||||
|
||||
const hasNullEquivalentFieldValue = isDefined(nullEquivalentFieldValue);
|
||||
|
||||
switch (operator) {
|
||||
case 'isEmptyArray':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}" = '{}'`,
|
||||
sql: `"${objectNameSingular}"."${key}" = '{}'${hasNullEquivalentFieldValue ? ` OR "${objectNameSingular}"."${key}" IS NULL` : ''}`,
|
||||
params: {},
|
||||
};
|
||||
case 'eq':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}" = :${key}${uuid}`,
|
||||
sql: `"${objectNameSingular}"."${key}" = :${key}${uuid}${hasNullEquivalentFieldValue ? ` OR "${objectNameSingular}"."${key}" IS NULL` : ''}`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
case 'neq':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}" != :${key}${uuid}`,
|
||||
sql: `"${objectNameSingular}"."${key}" != :${key}${uuid}${hasNullEquivalentFieldValue ? ` OR "${objectNameSingular}"."${key}" IS NOT NULL` : ''}`,
|
||||
params: { [`${key}${uuid}`]: value },
|
||||
};
|
||||
case 'gt':
|
||||
@@ -68,17 +86,19 @@ export const computeWhereConditionParts = ({
|
||||
};
|
||||
case 'is':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}" IS ${value === 'NULL' ? 'NULL' : 'NOT NULL'}`,
|
||||
params: {},
|
||||
sql: `"${objectNameSingular}"."${key}" IS ${value === 'NULL' ? 'NULL' : 'NOT NULL'}${hasNullEquivalentFieldValue ? ` OR "${objectNameSingular}"."${key}" = :${key}${secondUuid}` : ''}`,
|
||||
params: hasNullEquivalentFieldValue
|
||||
? { [`${key}${secondUuid}`]: nullEquivalentFieldValue }
|
||||
: {},
|
||||
};
|
||||
case 'like':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}"::text LIKE :${key}${uuid}`,
|
||||
sql: `"${objectNameSingular}"."${key}"::text LIKE :${key}${uuid}${hasNullEquivalentFieldValue ? ` OR "${objectNameSingular}"."${key}" IS NULL` : ''}`,
|
||||
params: { [`${key}${uuid}`]: `${value}` },
|
||||
};
|
||||
case 'ilike':
|
||||
return {
|
||||
sql: `"${objectNameSingular}"."${key}"::text ILIKE :${key}${uuid}`,
|
||||
sql: `"${objectNameSingular}"."${key}"::text ILIKE :${key}${uuid}${hasNullEquivalentFieldValue ? ` OR "${objectNameSingular}"."${key}" IS NULL` : ''}`,
|
||||
params: { [`${key}${uuid}`]: `${value}` },
|
||||
};
|
||||
case 'startsWith':
|
||||
|
||||
Reference in New Issue
Block a user