Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Join Condition Parsing for Complex Queries #69

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
6bf9c7f
build: :green_heart: uncomment env docker compose file
ZaidMaslouhi Dec 6, 2024
4f9b078
fix: :pencil2: change joinCondition delimiter to '&' symbol
ZaidMaslouhi Dec 6, 2024
42c583b
test: :test_tube: add tests for join condition value contains array o…
ZaidMaslouhi Dec 6, 2024
d7b003d
fix: :bug: remove query string parser options
ZaidMaslouhi Dec 6, 2024
85c7ab1
test: :test_tube: add tests for join condition value contains array o…
ZaidMaslouhi Dec 6, 2024
536a222
docs: :memo: update doc to new delimiter for join condition 'on clause'
ZaidMaslouhi Dec 6, 2024
baea0cf
docs: :memo: add example for join condition contains array of values
ZaidMaslouhi Dec 6, 2024
a4d2b96
Merge pull request #2 from ZaidMaslouhi/fixes
ZaidMaslouhi Dec 6, 2024
f6b3c55
ci: :green_heart: fix CI docker build error
ZaidMaslouhi Dec 11, 2024
f4a4710
ci: :rewind: revert changes
ZaidMaslouhi Dec 11, 2024
63e6278
ci: :construction_worker: add docker compose file
ZaidMaslouhi Dec 11, 2024
c777bcb
Merge pull request #3 from ZaidMaslouhi/fixes
ZaidMaslouhi Dec 11, 2024
f3e4411
feat: :label: add new array delimiter
ZaidMaslouhi Dec 21, 2024
d7d7cba
feat: :sparkles: add array delimiter from query builder options
ZaidMaslouhi Dec 21, 2024
b49c342
docs: :memo: add array delimiter to the request query builder options
ZaidMaslouhi Dec 21, 2024
4acba98
feat: :sparkles: get array delimiter from config options to stringify…
ZaidMaslouhi Dec 21, 2024
8e11ab2
test: :white_check_mark: add tests for the new array delimiter option
ZaidMaslouhi Dec 21, 2024
c36821c
Merge pull request #4 from ZaidMaslouhi/fix-join-conditions
ZaidMaslouhi Dec 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 2 additions & 14 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,57 +1,45 @@
# This is a basic workflow to help you get started with Actions

name: Tests

# Controls when the action will run.
on:
# Triggers the workflow on push or pull request events but only for the master branch
push:
branches: [master]
pull_request:
branches: [master]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
test:
# The type of runner that the job will run on
runs-on: ubuntu-latest
container: node:20
# env:
# COMPOSE_FILE: ./docker-compose.yml

env:
COMPOSE_FILE: ./docker-compose.yml
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

# Runs a single command using the runners shell
- name: build docker db
run: docker compose up -d

- name: install
run: yarn install

- name: build
run: yarn build

- name: check docker
run: docker compose up -d

# Runs a set of commands using the runners shell
- name: tests
run: yarn test:coverage

- name: Coveralls Parallel
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.github_token }}
flag-name: run-${{ matrix.test_number }}
parallel: true

coverage:
needs: test
runs-on: ubuntu-latest
Expand Down
4 changes: 3 additions & 1 deletion docs/requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ _Examples:_

The join parameter now supports specifying a WHERE condition within the ON clause of the join using the on property. This allows for more granular control over the joined data.

> ?join[]=**relation**||**field1**,**field2**,...||on[0]=**field**||**\$condition**||**value**,on[1]=**field**||**\$condition**...&join[]=...
> ?join[]=**relation**||**field1**,**field2**,...||on[0]=**field**||**\$condition**||**value**&on[1]=**field**||**\$condition**...&join[]=...

_Examples:_

Expand Down Expand Up @@ -295,6 +295,7 @@ import { RequestQueryBuilder } from '@dataui/crud-request';
RequestQueryBuilder.setOptions({
delim: '||',
delimStr: ',',
delimArr: '&',
paramNamesMap: {
fields: ['fields', 'select'],
search: 's',
Expand Down Expand Up @@ -356,6 +357,7 @@ qb.select(['foo', 'bar'])
on: [
{ field: 'bar', operator: 'eq', value: 100 },
{ field: 'baz', operator: 'isnull' },
{ field: 'date', operator: 'between', value: ['2023-01-01', '2023-12-31'] },
],
})
.sortBy({ field: 'bar', order: 'DESС' })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface RequestQueryBuilderOptions {
delim?: string;
delimStr?: string;
delimArr?: string;
paramNamesMap?: {
fields?: string | string[];
search?: string | string[];
Expand Down
15 changes: 8 additions & 7 deletions packages/crud-request/src/request-query.builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class RequestQueryBuilder {
private static _options: RequestQueryBuilderOptions = {
delim: '||',
delimStr: ',',
delimArr: '&',
paramNamesMap: {
fields: ['fields', 'select'],
search: 's',
Expand All @@ -58,11 +59,6 @@ export class RequestQueryBuilder {
private paramNames: {
[key in keyof RequestQueryBuilderOptions['paramNamesMap']]: string;
} = {};
private joinConditionString: IStringifyOptions = {
encode: false,
delimiter: this.options.delimStr,
arrayFormat: 'indices',
};
public queryObject: { [key: string]: any } = {};
public queryString: string;

Expand Down Expand Up @@ -213,7 +209,12 @@ export class RequestQueryBuilder {
}

private addJoin(join: QueryJoin | QueryJoinArr): string {
const { delim, delimStr } = this.options;
const { delim, delimStr, delimArr } = this.options;
const conditionStringifyOptions: IStringifyOptions = {
encode: false,
delimiter: delimArr,
arrayFormat: 'indices',
};

const normalizedJoin = Array.isArray(join)
? { field: join[0], select: join[1], on: join[2] }
Expand All @@ -230,7 +231,7 @@ export class RequestQueryBuilder {
? delim + normalizedJoin.select.join(delimStr)
: '';
const conditionsPart = conditions
? delim + stringify(conditions, this.joinConditionString)
? delim + stringify(conditions, conditionStringifyOptions)
: '';

return fieldPart + selectPart + conditionsPart;
Expand Down
10 changes: 3 additions & 7 deletions packages/crud-request/src/request-query.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,6 @@ export class RequestQueryParser implements ParsedRequestParams {
private _paramNames: string[];
private _paramsOptions: ParamsOptions;

private _joinConditionParseOptions: IParseOptions = {
delimiter: this._options.delimStr,
};

private get _options(): RequestQueryBuilderOptions {
return RequestQueryBuilder.getOptions();
}
Expand Down Expand Up @@ -356,14 +352,14 @@ export class RequestQueryParser implements ParsedRequestParams {
}

private parseJoinConditions(conditionsString: string): QueryFilter[] {
const conditions: string[] = parse(conditionsString, this._joinConditionParseOptions)[
'on'
];
const conditions: string[] = parse(conditionsString)['on'];

return conditions.map((cond: string) => this.conditionParser('filter', {}, cond));
}

private joinParser(data: string): QueryJoin {
const param = data.split(this._options.delim);

const field = param[0];
const selectString = param[1];
const conditions = param.slice(2).join(this._options.delim);
Expand Down
31 changes: 28 additions & 3 deletions packages/crud-request/test/request-query.builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ describe('#request-query', () => {
const _options = (RequestQueryBuilder as any)._options;
expect(_options.delim).toBe(override);
});
it('should merge options, 3', () => {
const override = '#';
RequestQueryBuilder.setOptions({ delimArr: override });
const _options = (RequestQueryBuilder as any)._options;
expect(_options.delimArr).toBe(override);
});
});

describe('#select', () => {
Expand Down Expand Up @@ -223,7 +229,24 @@ describe('#request-query', () => {
],
},
]);
const expected = ['baz||a,b,c||on[0]=bar||eq||100,on[1]=foo||isnull'];
const expected = ['baz||a,b,c||on[0]=bar||eq||100&on[1]=foo||isnull'];
expect(qb.queryObject.join).toIncludeSameMembers(expected);
});
it('should set join, 9', () => {
qb.setJoin([
{
field: 'qux',
select: ['a', 'b', 'c'],
on: [
{ field: 'bar', operator: 'eq', value: 100 },
{ field: 'foo', operator: 'isnull' },
{ field: 'date', operator: 'between', value: ['2023-12-06', '2023-12-12'] },
],
},
]);
const expected = [
'qux||a,b,c||on[0]=bar||eq||100&on[1]=foo||isnull&on[2]=date||between||2023-12-06,2023-12-12',
];
expect(qb.queryObject.join).toIncludeSameMembers(expected);
});
});
Expand Down Expand Up @@ -384,6 +407,7 @@ describe('#request-query', () => {
on: [
{ field: 'foo', operator: 'eq', value: 'baz' },
{ field: 'bar', operator: 'isnull' },
{ field: 'qux', operator: 'between', value: [1000, 8000] },
],
})
.setLimit(1)
Expand All @@ -394,7 +418,7 @@ describe('#request-query', () => {
.setIncludeDeleted(1)
.query(false);
const expected =
'fields=foo,bar&filter[0]=is||notnull&or[0]=ok||ne||false&join[0]=voo||h,data||on[0]=foo||eq||baz,on[1]=bar||isnull&limit=1&offset=2&page=3&sort[0]=foo,DESC&cache=0&include_deleted=1';
'fields=foo,bar&filter[0]=is||notnull&or[0]=ok||ne||false&join[0]=voo||h,data||on[0]=foo||eq||baz&on[1]=bar||isnull&on[2]=qux||between||1000,8000&limit=1&offset=2&page=3&sort[0]=foo,DESC&cache=0&include_deleted=1';
expect(test).toBe(expected);
});
});
Expand Down Expand Up @@ -437,6 +461,7 @@ describe('#request-query', () => {
on: [
{ field: 'foo', operator: 'eq', value: 'baz' },
{ field: 'bar', operator: 'isnull' },
{ field: 'qux', operator: 'between', value: [1000, 8000] },
],
},
limit: 1,
Expand All @@ -446,7 +471,7 @@ describe('#request-query', () => {
resetCache: true,
}).query(false);
const expected =
'fields=foo,bar&filter[0]=is||notnull&or[0]=ok||ne||false&join[0]=voo||h,data||on[0]=foo||eq||baz,on[1]=bar||isnull&limit=1&offset=2&page=3&sort[0]=foo,DESC&cache=0';
'fields=foo,bar&filter[0]=is||notnull&or[0]=ok||ne||false&join[0]=voo||h,data||on[0]=foo||eq||baz&on[1]=bar||isnull&on[2]=qux||between||1000,8000&limit=1&offset=2&page=3&sort[0]=foo,DESC&cache=0';
expect(test).toBe(expected);
});
it('should return a valid query string, 2', () => {
Expand Down
41 changes: 40 additions & 1 deletion packages/crud-request/test/request-query.parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ describe('#request-query', () => {
join: [
'foo',
'bar||baz,boo',
'bar||baz,boo||on[0]=name||eq||jhon,on[1]=foo||isnull',
'bar||baz,boo||on[0]=name||eq||jhon&on[1]=foo||isnull',
],
};
const expected: QueryJoin[] = [
Expand All @@ -285,6 +285,45 @@ describe('#request-query', () => {
expect(test.join[1]).toMatchObject(expected[1]);
expect(test.join[2]).toMatchObject(expected[2]);
});
it('should set array, 4', () => {
const query = {
join: [
'foo',
'bar||baz,boo',
'bar||baz,boo||on[0]=name||eq||jhon&on[1]=foo||isnull',
'qux||qubaz,quboo||on[0]=quux||between||06-12-2023,12-12-2023',
],
};
const expected: QueryJoin[] = [
{ field: 'foo' },
{ field: 'bar', select: ['baz', 'boo'] },
{
field: 'bar',
select: ['baz', 'boo'],
on: [
{ field: 'name', operator: 'eq', value: 'jhon' },
{ field: 'foo', operator: 'isnull', value: '' },
],
},
{
field: 'qux',
select: ['qubaz', 'quboo'],
on: [
{
field: 'quux',
operator: 'between',
value: ['06-12-2023', '12-12-2023'],
},
],
},
];
const test = qp.parseQuery(query);

expect(test.join[0]).toMatchObject(expected[0]);
expect(test.join[1]).toMatchObject(expected[1]);
expect(test.join[2]).toMatchObject(expected[2]);
expect(test.join[3]).toMatchObject(expected[3]);
});
});

describe('#parse sort', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/crud/src/crud/swagger.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ export class Swagger {
const {
delim: d,
delimStr: coma,
delimArr: amp,
fields,
search,
filter,
Expand Down Expand Up @@ -578,6 +579,7 @@ export class Swagger {
return {
delim: qbOptions.delim,
delimStr: qbOptions.delimStr,
delimArr: qbOptions.delimArr,
fields: name('fields'),
search: name('search'),
filter: name('filter'),
Expand Down
3 changes: 2 additions & 1 deletion packages/crud/test/crud-config.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ describe('#crud', () => {
const conf: CrudGlobalConfig = {
queryParser: {
delim: '__',
delimArr: '§',
},
};
const expected = { ...CrudConfigService.config };
CrudConfigService.load(conf);
expect(CrudConfigService.config).toEqual(expect.objectContaining(expected));
expect(RequestQueryBuilder.getOptions()).toEqual(
expect.objectContaining({ ...requestOptions, delim: '__' }),
expect.objectContaining({ ...requestOptions, delim: '__', delimArr: '§' }),
);
});
it('should set query, routes, params', () => {
Expand Down
Loading