Refactor the file structure in data-sql (#20433)

* Refactor data-sql types file structure

* used types dir directly

* target conversion outside of conditions, file for filter conversion result

* type import only via index file

* removed unused index files

* Refactor data-sql types file structure

* used types dir directly

* target conversion outside of conditions, file for filter conversion result

* type import only via index file

* removed unused index files

* fixed tests

* rename

---------

Co-authored-by: Jan Arends <jan.arends@mailbox.org>
This commit is contained in:
Nicola Krumschmidt
2023-11-17 20:41:25 +01:00
committed by GitHub
parent 985cef5eb5
commit d9bd5adeb1
45 changed files with 179 additions and 202 deletions

View File

@@ -5,8 +5,7 @@
* @module
*/
import type { AbstractQuery } from '@directus/data';
import type { AbstractSqlClauses, AbstractSqlQuery } from '../types/index.js';
import type { ParameterTypes } from '../types/parameterized-statement.js';
import type { AbstractSqlClauses, AbstractSqlQuery, ParameterTypes } from '../types/index.js';
import { parameterIndexGenerator } from './param-index-generator.js';
import { convertFieldNodes } from './fields/index.js';
import { convertModifiers } from './modifiers/modifiers.js';

View File

@@ -1,8 +1,8 @@
import { expect, test } from 'vitest';
import { randomIdentifier } from '@directus/random';
import type { AbstractQueryFieldNodeRelationalManyToOne } from '@directus/data';
import { randomIdentifier } from '@directus/random';
import { expect, test } from 'vitest';
import type { AbstractSqlQueryJoinNode } from '../../types/index.js';
import { createJoin } from './create-join.js';
import type { AbstractSqlQueryJoinNode } from '../../types/clauses/joins/join.js';
test('Convert m2o relation on single field ', () => {
const randomCurrentCollection = randomIdentifier();

View File

@@ -8,7 +8,7 @@ import type {
AbstractSqlNestedMany,
AbstractSqlQueryConditionNode,
AbstractSqlQueryWhereNode,
} from '../../index.js';
} from '../../types/index.js';
import { convertModifiers } from '../modifiers/modifiers.js';
import { parameterIndexGenerator } from '../param-index-generator.js';
import { convertFieldNodes } from './fields.js';

View File

@@ -1,9 +1,9 @@
import type { AbstractQueryFunction } from '@directus/data';
import { randomAlpha, randomIdentifier } from '@directus/random';
import { describe, expect, test, beforeEach } from 'vitest';
import type { AbstractSqlQueryFnNode } from '../types/clauses/selects/fn.js';
import { parameterIndexGenerator } from './param-index-generator.js';
import { beforeEach, describe, expect, test } from 'vitest';
import type { AbstractSqlQueryFnNode } from '../types/index.js';
import { convertFn } from './functions.js';
import { parameterIndexGenerator } from './param-index-generator.js';
let randomCollection: string;
let idGen: Generator<number, number, number>;

View File

@@ -4,7 +4,7 @@ import { convertGeoCondition } from './geo.js';
import { convertStringNode } from './string.js';
import { convertNumberNode } from './number.js';
import { convertSetCondition } from './set.js';
import type { FilterResult } from '../filter.js';
import type { FilterResult } from '../utils.js';
/**
* Forward the condition to the correct converter.

View File

@@ -2,7 +2,7 @@ import type { ConditionFieldNode } from '@directus/data';
import { randomIdentifier } from '@directus/random';
import { expect, test } from 'vitest';
import { parameterIndexGenerator } from '../../../param-index-generator.js';
import type { FilterResult } from '../filter.js';
import type { FilterResult } from '../utils.js';
import { convertFieldCondition } from './field.js';
test('convert field condition', () => {

View File

@@ -1,6 +1,6 @@
import type { ConditionFieldNode } from '@directus/data';
import type { FilterResult } from '../filter.js';
import { convertTarget } from './utils.js';
import type { FilterResult } from '../utils.js';
import { convertTarget } from '../../target.js';
export function convertFieldCondition(
node: ConditionFieldNode,

View File

@@ -5,7 +5,7 @@ import type { GeoJSONGeometry } from 'wellknown';
import type { AbstractSqlQueryConditionNode } from '../../../../types/clauses/where/index.js';
import { parameterIndexGenerator } from '../../../param-index-generator.js';
import { convertGeoCondition } from './geo.js';
import type { FilterResult } from '../filter.js';
import type { FilterResult } from '../utils.js';
test('convert geo condition', () => {
const idGen = parameterIndexGenerator();

View File

@@ -1,6 +1,6 @@
import type { ConditionGeoIntersectsNode, ConditionGeoIntersectsBBoxNode } from '@directus/data';
import { convertTarget } from './utils.js';
import type { FilterResult } from '../filter.js';
import { convertTarget } from '../../target.js';
import type { FilterResult } from '../utils.js';
export function convertGeoCondition(
node: ConditionGeoIntersectsNode | ConditionGeoIntersectsBBoxNode,

View File

@@ -4,7 +4,7 @@ import { expect, test, beforeEach } from 'vitest';
import { parameterIndexGenerator } from '../../../param-index-generator.js';
import { convertNumberNode } from './number.js';
import type { AbstractSqlQueryConditionNode } from '../../../../index.js';
import type { FilterResult } from '../filter.js';
import type { FilterResult } from '../utils.js';
let idGen: Generator<number, number, number>;
let randomCollection: string;

View File

@@ -1,6 +1,6 @@
import type { ConditionNumberNode } from '@directus/data';
import { convertTarget } from './utils.js';
import type { FilterResult } from '../filter.js';
import { convertTarget } from '../../target.js';
import type { FilterResult } from '../utils.js';
export function convertNumberNode(
node: ConditionNumberNode,

View File

@@ -4,7 +4,7 @@ import { expect, test } from 'vitest';
import type { AbstractSqlQueryConditionNode } from '../../../../types/clauses/where/index.js';
import { parameterIndexGenerator } from '../../../param-index-generator.js';
import { convertSetCondition } from './set.js';
import type { FilterResult } from '../filter.js';
import type { FilterResult } from '../utils.js';
test('convert set condition', () => {
const idGen = parameterIndexGenerator();

View File

@@ -1,6 +1,6 @@
import type { ConditionSetNode } from '@directus/data';
import { convertTarget } from './utils.js';
import type { FilterResult } from '../filter.js';
import { convertTarget } from '../../target.js';
import type { FilterResult } from '../utils.js';
export function convertSetCondition(
node: ConditionSetNode,

View File

@@ -4,7 +4,7 @@ import { expect, test } from 'vitest';
import { parameterIndexGenerator } from '../../../param-index-generator.js';
import { convertStringNode } from './string.js';
import type { AbstractSqlQueryConditionNode } from '../../../../types/index.js';
import type { FilterResult } from '../filter.js';
import type { FilterResult } from '../utils.js';
test('convert string condition', () => {
const idGen = parameterIndexGenerator();

View File

@@ -1,6 +1,6 @@
import type { ConditionStringNode } from '@directus/data';
import { convertTarget } from './utils.js';
import type { FilterResult } from '../filter.js';
import { convertTarget } from '../../target.js';
import type { FilterResult } from '../utils.js';
export function convertStringNode(
node: ConditionStringNode,

View File

@@ -1,12 +1,7 @@
import type { AbstractQueryConditionNode, AbstractQueryFilterNode, AtLeastOneElement } from '@directus/data';
import type { AbstractSqlClauses, AbstractSqlQuery } from '../../../types/index.js';
import { convertCondition } from './conditions/conditions.js';
import { convertLogical } from './logical.js';
export type FilterResult = {
clauses: Required<Pick<AbstractSqlClauses, 'where' | 'joins'>>;
parameters: AbstractSqlQuery['parameters'];
};
import type { FilterResult } from './utils.js';
/**
* Extracts the user provided filter values and puts them in the list of parameters.

View File

@@ -1,2 +0,0 @@
export * from './logical.js';
export * from './filter.js';

View File

@@ -1,6 +1,6 @@
import type { AtLeastOneElement } from '@directus/data';
import type { AbstractSqlQueryWhereNode } from '../../../index.js';
import type { FilterResult } from './filter.js';
import type { AbstractSqlQueryWhereNode } from '../../../types/index.js';
import type { FilterResult } from './utils.js';
export function convertLogical(
children: AtLeastOneElement<FilterResult>,

View File

@@ -0,0 +1,6 @@
import type { AbstractSqlClauses, AbstractSqlQuery } from '../../../types/index.js';
export type FilterResult = {
clauses: Required<Pick<AbstractSqlClauses, 'where' | 'joins'>>;
parameters: AbstractSqlQuery['parameters'];
};

View File

@@ -1,2 +0,0 @@
export * from './filter/index.js';
export * from './sort.js';

View File

@@ -3,10 +3,10 @@ import { beforeEach, expect, test, vi } from 'vitest';
import { randomIdentifier } from '@directus/random';
import { convertSort, type SortConversionResult } from './sort.js';
import { parameterIndexGenerator } from '../param-index-generator.js';
import { convertTarget, type TargetConversionResult } from './filter/conditions/utils.js';
import { convertTarget, type TargetConversionResult } from './target.js';
import type { AbstractSqlQuerySelectNode } from '../../index.js';
vi.mock('./filter/conditions/utils.js', (importOriginal) => {
vi.mock('./target.js', (importOriginal) => {
const original = importOriginal();
return {
...original,

View File

@@ -1,6 +1,6 @@
import type { AbstractQueryNodeSort, AtLeastOneElement } from '@directus/data';
import type { AbstractSqlClauses, AbstractSqlQueryOrderNode } from '../../types/index.js';
import { convertTarget } from './filter/conditions/utils.js';
import { convertTarget } from './target.js';
export type SortConversionResult = {
clauses: Required<Pick<AbstractSqlClauses, 'order' | 'joins'>>;

View File

@@ -1,10 +1,10 @@
import { expect, test, vi } from 'vitest';
import { convertNestedOneTarget, convertTarget, type TargetConversionResult } from './utils.js';
import { convertNestedOneTarget, convertTarget, type TargetConversionResult } from './target.js';
import type { AbstractQueryTargetNestedOne, ConditionNumberNode, ConditionStringNode } from '@directus/data';
import { parameterIndexGenerator } from '../../../param-index-generator.js';
import { parameterIndexGenerator } from '../param-index-generator.js';
import { randomIdentifier, randomInteger } from '@directus/random';
vi.mock('../../../../orm/create-unique-alias.js', () => ({
vi.mock('../../orm/create-unique-alias.js', () => ({
createUniqueAlias: vi.fn().mockImplementation((i) => `${i}_RANDOM`),
}));

View File

@@ -1,33 +1,12 @@
import type {
AbstractQueryFieldNodePrimitive,
AbstractQueryTarget,
AbstractQueryTargetNestedOne,
} from '@directus/data';
import { createUniqueAlias } from '../../../../orm/create-unique-alias.js';
import type { AbstractQueryTarget, AbstractQueryTargetNestedOne } from '@directus/data';
import { createUniqueAlias } from '../../orm/create-unique-alias.js';
import type {
AbstractSqlQueryFnNode,
AbstractSqlQueryJoinNode,
AbstractSqlQuerySelectNode,
} from '../../../../types/index.js';
import { createJoin } from '../../../fields/create-join.js';
import { convertFn } from '../../../functions.js';
/**
* It adds the table name to the node.
* @param collection
* @param primitiveNode
* @returns an unambitious column
*/
export function convertPrimitive(
collection: string,
primitiveNode: AbstractQueryFieldNodePrimitive
): AbstractSqlQuerySelectNode {
return {
type: 'primitive',
table: collection,
column: primitiveNode.field,
};
}
} from '../../types/index.js';
import { createJoin } from '../fields/create-join.js';
import { convertFn } from '../functions.js';
export interface TargetConversionResult {
value: AbstractSqlQuerySelectNode | AbstractSqlQueryFnNode;

View File

@@ -0,0 +1,60 @@
/**
* A set of types which form the abstract SQL query.
* It's still neutral to concrete SQL dialects and databases but provides to SQL drivers with a query type that they can more easy work with.
*
* How the abstract SQL query types differ from the abstract query.
* - In the abstract query the user input values are put directly within the query directly.
* The abstract SQL however stores the user input values in a list of parameters, so that the SQL driver always perform parameterized queries.
* That way we prevent SQL injection.
* Moving the user input values into a list of parameters and replace the input value with the index of the value from the list, is a big part of the converter.
* - Instead of a wrapper for negation, here the negation is a property on the type.
* So the abstract SQL does not have a node of type 'negate' but instead the nodes have a property called 'negate'.
*
* @module
*/
import type { AtLeastOneElement } from '@directus/data';
import type { AbstractSqlClauses } from './clauses.js';
import type { ParameterTypes } from './parameterized-statement.js';
/**
* This is an abstract SQL query which can be passed to all SQL drivers.
*
* @example
* The following query gets the title of all articles and limits the result to 25 rows.
* ```ts
* const query: SqlStatement = {
* clauses: {
* select: [title],
* from: 'articles',
* limit: 0, // this is the index of the parameter
* },
* parameters: [25],
* aliasMapping: ...,
* nestedMany: [],
* };
* ```
*/
export interface AbstractSqlQuery {
/* all clauses each and every driver will use */
clauses: AbstractSqlClauses;
/* the parameters which will be passed separately to the database for security reasons */
parameters: ParameterTypes[];
/* a map from the generated, random alias to the actual path */
aliasMapping: Map<string, string[]>;
/* how o2m relations are handled is driver specific and hence just forwarded */
nestedManys: AbstractSqlNestedMany[];
}
export interface AbstractSqlNestedMany {
/*
* The nested many sub queries cannot be generated completely, since they rely on the result of the root query
* Therefore we use a function here instead, which takes the missing values as parameters to generate the actual sub query.
*/
queryGenerator: (joinFieldValues: AtLeastOneElement<string | number>) => AbstractSqlQuery;
localJoinFields: AtLeastOneElement<string>;
foreignJoinFields: AtLeastOneElement<string>;
alias: string;
}

View File

@@ -0,0 +1,16 @@
import type { AbstractSqlQueryJoinNode } from './clauses/join.js';
import type { AbstractSqlQueryOrderNode } from './clauses/order.js';
import type { AbstractSqlQueryFnNode } from './clauses/select/fn.js';
import type { AbstractSqlQuerySelectNode } from './clauses/select/primitive.js';
import type { AbstractSqlQueryWhereNode } from './clauses/where.js';
import type { ValueNode } from './parameterized-statement.js';
export interface AbstractSqlClauses {
select: (AbstractSqlQuerySelectNode | AbstractSqlQueryFnNode)[];
from: string;
joins?: AbstractSqlQueryJoinNode[];
where?: AbstractSqlQueryWhereNode;
limit?: ValueNode;
offset?: ValueNode;
order?: AbstractSqlQueryOrderNode[];
}

View File

@@ -1,24 +1,5 @@
import type { ValueNode } from '../parameterized-statement.js';
import type { AbstractSqlQueryJoinNode } from './joins/join.js';
import type { AbstractSqlQueryOrderNode } from './order.js';
import type { AbstractSqlQueryFnNode } from './selects/fn.js';
import type { AbstractSqlQuerySelectNode } from './selects/primitive.js';
import type { AbstractSqlQueryConditionNode, AbstractSqlQueryLogicalNode } from './where/index.js';
export interface AbstractSqlClauses {
select: (AbstractSqlQuerySelectNode | AbstractSqlQueryFnNode)[];
from: string;
joins?: AbstractSqlQueryJoinNode[];
where?: AbstractSqlQueryWhereNode;
limit?: ValueNode;
offset?: ValueNode;
order?: AbstractSqlQueryOrderNode[];
}
export type AbstractSqlQueryWhereNode = AbstractSqlQueryConditionNode | AbstractSqlQueryLogicalNode;
export * from './selects/fn.js';
export * from './selects/primitive.js';
export * from './joins/join.js';
export * from './where/index.js';
export * from './join.js';
export * from './order.js';
export * from './select/index.js';
export * from './where.js';
export * from './where/index.js';

View File

@@ -1,4 +1,5 @@
import type { AbstractSqlQueryLogicalNode, AbstractSqlQueryConditionNode } from '../where/index.js';
import type { AbstractSqlQueryConditionNode } from './where/condition.js';
import type { AbstractSqlQueryLogicalNode } from './where/logical.js';
/**
* Used to join another table, regardless of the type of relation.

View File

@@ -1,4 +1,5 @@
import type { AbstractSqlQueryFnNode, AbstractSqlQuerySelectNode } from './index.js';
import type { AbstractSqlQueryFnNode } from './select/fn.js';
import type { AbstractSqlQuerySelectNode } from './select/primitive.js';
export interface AbstractSqlQueryOrderNode {
type: 'order';

View File

@@ -0,0 +1,4 @@
export interface AbstractSqlQueryColumn {
table: string;
column: string;
}

View File

@@ -1,6 +1,6 @@
import type { AbstractSqlQueryColumn } from './primitive.js';
import type { ArrayFn, ExtractFn } from '@directus/data';
import type { ValuesNode } from '../../parameterized-statement.js';
import type { ExtractFn, ArrayFn } from '@directus/data';
import type { AbstractSqlQueryColumn } from './column.js';
/**
* Used to apply a function to a column.

View File

@@ -0,0 +1,4 @@
export * from './column.js';
export * from './fn.js';
export * from './json.js';
export * from './primitive.js';

View File

@@ -1,7 +1,4 @@
export interface AbstractSqlQueryColumn {
table: string;
column: string;
}
import type { AbstractSqlQueryColumn } from './column.js';
/**
* Used to select a specific column from a table.

View File

@@ -0,0 +1,4 @@
import type { AbstractSqlQueryConditionNode } from './where/condition.js';
import type { AbstractSqlQueryLogicalNode } from './where/logical.js';
export type AbstractSqlQueryWhereNode = AbstractSqlQueryConditionNode | AbstractSqlQueryLogicalNode;

View File

@@ -0,0 +1,21 @@
import type { SqlConditionFieldNode } from './conditions/field-condition.js';
import type { SqlConditionGeoNode } from './conditions/geo-condition.js';
import type { SqlConditionNumberNode } from './conditions/number-condition.js';
import type { SqlConditionSetNode } from './conditions/set-condition.js';
import type { SqlConditionStringNode } from './conditions/string-condition.js';
/**
* Condition to filter rows.
* Various condition types are supported, each depending on a specific datatype.
* The condition can also be negated on this level.
*/
export interface AbstractSqlQueryConditionNode {
type: 'condition';
condition:
| SqlConditionStringNode
| SqlConditionNumberNode
| SqlConditionGeoNode
| SqlConditionSetNode
| SqlConditionFieldNode;
negate: boolean;
}

View File

@@ -1,5 +1,5 @@
import type { AbstractSqlQueryFnNode } from '../../selects/fn.js';
import type { AbstractSqlQuerySelectNode } from '../../selects/primitive.js';
import type { AbstractSqlQueryFnNode } from '../../select/fn.js';
import type { AbstractSqlQuerySelectNode } from '../../select/primitive.js';
/**
* Condition to filter rows where two columns of different tables are equal.

View File

@@ -1,6 +1,6 @@
import type { ValueNode } from '../../../parameterized-statement.js';
import type { AbstractSqlQueryFnNode } from '../../selects/fn.js';
import type { AbstractSqlQuerySelectNode } from '../../selects/primitive.js';
import type { AbstractSqlQueryFnNode } from '../../select/fn.js';
import type { AbstractSqlQuerySelectNode } from '../../select/primitive.js';
/**
* Used to retrieve a set of data, where the column in question stores a geographic value which intersects with another given geographic value.

View File

@@ -1,34 +1,5 @@
import type { SqlConditionFieldNode } from './field-condition.js';
import type { SqlConditionGeoNode } from './geo-condition.js';
import type { SqlConditionNumberNode } from './number-condition.js';
import type { SqlConditionSetNode } from './set-condition.js';
import type { SqlConditionStringNode } from './string-condition.js';
/**
* Condition to filter rows.
* Various condition types are supported, each depending on a specific datatype.
* The condition can also be negated on this level.
*/
export interface AbstractSqlQueryConditionNode {
type: 'condition';
condition:
| SqlConditionStringNode
| SqlConditionNumberNode
| SqlConditionGeoNode
| SqlConditionSetNode
| SqlConditionFieldNode;
negate: boolean;
}
export type SqlConditionType =
| 'condition-string'
| 'condition-number'
| 'condition-geo'
| 'condition-set'
| 'condition-field';
export * from './field-condition.js';
export * from './geo-condition.js';
export * from './number-condition.js';
export * from './string-condition.js';
export * from './set-condition.js';
export * from './string-condition.js';

View File

@@ -1,6 +1,6 @@
import type { AbstractSqlQueryFnNode } from '../../selects/fn.js';
import type { AbstractSqlQueryFnNode } from '../../select/fn.js';
import type { ValueNode } from '../../../parameterized-statement.js';
import type { AbstractSqlQuerySelectNode } from '../../selects/primitive.js';
import type { AbstractSqlQuerySelectNode } from '../../select/primitive.js';
/**
* Filter rows where a numeric column is equal, greater than, less than, etc. other given number.

View File

@@ -1,6 +1,6 @@
import type { ValuesNode } from '../../../parameterized-statement.js';
import type { AbstractSqlQueryFnNode } from '../../selects/fn.js';
import type { AbstractSqlQuerySelectNode } from '../../selects/primitive.js';
import type { AbstractSqlQueryFnNode } from '../../select/fn.js';
import type { AbstractSqlQuerySelectNode } from '../../select/primitive.js';
/*
* Condition to filter rows where a column value is in a list of values.

View File

@@ -1,6 +1,6 @@
import type { ValueNode } from '../../../parameterized-statement.js';
import type { AbstractSqlQueryFnNode } from '../../selects/fn.js';
import type { AbstractSqlQuerySelectNode } from '../../selects/primitive.js';
import type { AbstractSqlQueryFnNode } from '../../select/fn.js';
import type { AbstractSqlQuerySelectNode } from '../../select/primitive.js';
/**
* Condition to filter rows where a string column value contains, starts with, ends with, or is equal to another given string.

View File

@@ -1,2 +1,3 @@
export * from './condition.js';
export * from './conditions/index.js';
export * from './logical.js';

View File

@@ -1,5 +1,5 @@
import type { AtLeastOneElement } from '@directus/data';
import type { AbstractSqlQueryConditionNode } from './conditions/index.js';
import type { AbstractSqlQueryConditionNode } from './condition.js';
/**
* A wrapper to add multiple conditions at once.

View File

@@ -1,63 +1,4 @@
/**
* A set of types which form the abstract SQL query.
* It's still neutral to concrete SQL dialects and databases but provides to SQL drivers with a query type that they can more easy work with.
*
* How the abstract SQL query types differ from the abstract query.
* - In the abstract query the user input values are put directly within the query directly.
* The abstract SQL however stores the user input values in a list of parameters, so that the SQL driver always perform parameterized queries.
* That way we prevent SQL injection.
* Moving the user input values into a list of parameters and replace the input value with the index of the value from the list, is a big part of the converter.
* - Instead of a wrapper for negation, here the negation is a property on the type.
* So the abstract SQL does not have a node of type 'negate' but instead the nodes have a property called 'negate'.
*
* @module
*/
import type { ParameterTypes } from './parameterized-statement.js';
import type { AbstractSqlClauses } from './clauses/index.js';
import type { AtLeastOneElement } from '@directus/data';
/**
* This is an abstract SQL query which can be passed to all SQL drivers.
*
* @example
* The following query gets the title of all articles and limits the result to 25 rows.
* ```ts
* const query: SqlStatement = {
* clauses: {
* select: [title],
* from: 'articles',
* limit: 0, // this is the index of the parameter
* },
* parameters: [25],
* aliasMapping: ...,
* nestedMany: [],
* };
* ```
*/
export interface AbstractSqlQuery {
/* all clauses each and every driver will use */
clauses: AbstractSqlClauses;
/* the parameters which will be passed separately to the database for security reasons */
parameters: ParameterTypes[];
/* a map from the generated, random alias to the actual path */
aliasMapping: Map<string, string[]>;
/* how o2m relations are handled is driver specific and hence just forwarded */
nestedManys: AbstractSqlNestedMany[];
}
export interface AbstractSqlNestedMany {
/*
* The nested many sub queries cannot be generated completely, since they rely on the result of the root query.
* Therefore we use a function here instead, which takes the missing values as parameters to generate the actual sub query.
*/
queryGenerator: (joinFieldValues: AtLeastOneElement<string | number>) => AbstractSqlQuery;
localJoinFields: AtLeastOneElement<string>;
foreignJoinFields: AtLeastOneElement<string>;
alias: string;
}
export * from './abstract-sql.js';
export * from './clauses.js';
export * from './clauses/index.js';
export * from './parameterized-statement.js';