Skip to content

Commit f7de48f

Browse files
authored
Merge pull request #111 from Canner/feature/implement-offset-pagination
Feature: Implement offset and limit of data sources
2 parents 609be10 + 7e67ce5 commit f7de48f

File tree

47 files changed

+998
-130
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+998
-130
lines changed

labs/playground1/sqls/artist/works.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ sample:
1010
id: '1'
1111
profile: duck
1212
profile: duck
13+
pagination:
14+
mode: offset

packages/build/src/lib/document-generator/spec-generator/oas3/oas3SpecGenerator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export class OAS3SpecGenerator extends SpecGenerator<oas3.OpenAPIObject> {
8989
in: this.convertFieldInTypeToOASIn(param.fieldIn),
9090
schema: this.getSchemaObjectFromParameter(param),
9191
required: this.isParameterRequired(param),
92+
description: param.description,
9293
});
9394
}
9495

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import {
2+
APISchema,
3+
FieldDataType,
4+
FieldInType,
5+
PaginationMode,
6+
RequestSchema,
7+
} from '@vulcan-sql/core';
8+
import { RawAPISchema, SchemaParserMiddleware } from './middleware';
9+
10+
export class ExtractPaginationParams extends SchemaParserMiddleware {
11+
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
12+
await next();
13+
const transformedSchemas = schemas as APISchema;
14+
const paginationParameters: RequestSchema[] = [];
15+
if (transformedSchemas.pagination?.mode === PaginationMode.OFFSET) {
16+
paginationParameters.push({
17+
fieldName: 'limit',
18+
fieldIn: FieldInType.QUERY,
19+
description:
20+
'Offset-based Pagination: The maximum number of rows to return. default: 20',
21+
type: FieldDataType.STRING, // We should use STRING type here but not NUMBER because of the issue from normalizeStringValue function (it throw error when input is undefined)
22+
validators: [{ name: 'integer', args: { min: 0 } }],
23+
constraints: [],
24+
});
25+
paginationParameters.push({
26+
fieldName: 'offset',
27+
fieldIn: FieldInType.QUERY,
28+
description:
29+
'Offset-based Pagination: The offset from the row. default: 0',
30+
type: FieldDataType.STRING,
31+
validators: [{ name: 'integer', args: { min: 0 } }],
32+
constraints: [],
33+
});
34+
}
35+
36+
// merge parameters
37+
for (const param of paginationParameters) {
38+
const existed = transformedSchemas.request.find(
39+
(req) =>
40+
req.fieldName === param.fieldName && req.fieldIn === param.fieldIn
41+
);
42+
if (existed) {
43+
existed.description = existed.description || param.description;
44+
existed.validators = [...existed.validators, ...param.validators];
45+
} else {
46+
transformedSchemas.request.push(param);
47+
}
48+
}
49+
}
50+
}

packages/build/src/lib/schema-parser/middleware/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { SetConstraints } from './setConstraints';
1515
import { SchemaParserMiddleware } from './middleware';
1616
import { ResponseSampler } from './responseSampler';
1717
import { CheckProfile } from './checkProfile';
18+
import { ExtractPaginationParams } from './extractPaginationParams';
19+
import { TransformPaginationMode } from './transformPaginationMode';
1820

1921
export * from './middleware';
2022

@@ -32,7 +34,9 @@ export const SchemaParserMiddlewares: ClassType<SchemaParserMiddleware>[] = [
3234
NormalizeDataType,
3335
GeneratePathParameters,
3436
AddRequiredValidatorForPath,
37+
TransformPaginationMode,
3538
SetConstraints,
39+
ExtractPaginationParams, // ExtractPaginationParams should be loaded after SetConstraints
3640
ResponseSampler,
3741
CheckProfile,
3842
];

packages/build/src/lib/schema-parser/middleware/responseSampler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ export class ResponseSampler extends SchemaParserMiddleware {
3838
parameters: schema.sample.parameters,
3939
profileName: schema.sample.profile,
4040
},
41-
// We only need the columns of this query, so we set offset/limit both to 0 here.
41+
// We only need the columns of this query, so we set offset=0 and limit=1 here.
42+
// Some drivers guess the column types by data, so we should at least query 1 rows.
4243
{
43-
limit: 0,
44+
limit: 1,
4445
offset: 0,
4546
}
4647
);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PaginationMode } from '@vulcan-sql/core';
2+
import { RawAPISchema, SchemaParserMiddleware } from './middleware';
3+
4+
// pagination.mode: offset -> OFFSET
5+
export class TransformPaginationMode extends SchemaParserMiddleware {
6+
public async handle(schemas: RawAPISchema, next: () => Promise<void>) {
7+
if (schemas.pagination?.mode)
8+
schemas.pagination.mode =
9+
schemas.pagination.mode.toUpperCase() as PaginationMode;
10+
return next();
11+
}
12+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { RawAPISchema } from '@vulcan-sql/build/schema-parser';
2+
import { ExtractPaginationParams } from '@vulcan-sql/build/schema-parser/middleware/extractPaginationParams';
3+
import {
4+
Constraint,
5+
FieldDataType,
6+
FieldInType,
7+
PaginationMode,
8+
} from '@vulcan-sql/core';
9+
10+
it('Should do nothing when there is no pagination mode configured', async () => {
11+
// Arrange
12+
const schema: RawAPISchema = {
13+
sourceName: 'some-name',
14+
};
15+
const extractPaginationParams = new ExtractPaginationParams();
16+
// Act
17+
await extractPaginationParams.handle(schema, async () => Promise.resolve());
18+
// Assert
19+
expect(schema).toEqual(schema);
20+
});
21+
22+
it('Should add limit and offset when pagination mode is OFFSET', async () => {
23+
// Arrange
24+
const schema: RawAPISchema = {
25+
sourceName: 'some-name',
26+
request: [],
27+
pagination: {
28+
mode: PaginationMode.OFFSET,
29+
},
30+
};
31+
const extractPaginationParams = new ExtractPaginationParams();
32+
// Act
33+
await extractPaginationParams.handle(schema, async () => Promise.resolve());
34+
// Assert
35+
expect(schema.request?.length).toBe(2);
36+
expect(schema.request).toContainEqual(
37+
expect.objectContaining({
38+
fieldName: 'limit',
39+
fieldIn: FieldInType.QUERY,
40+
description:
41+
'Offset-based Pagination: The maximum number of rows to return. default: 20',
42+
})
43+
);
44+
expect(schema.request).toContainEqual(
45+
expect.objectContaining({
46+
fieldName: 'offset',
47+
fieldIn: FieldInType.QUERY,
48+
description:
49+
'Offset-based Pagination: The offset from the row. default: 0',
50+
})
51+
);
52+
});
53+
54+
it('Should merge parameters when some parameters have been defined', async () => {
55+
// Arrange
56+
const schema: RawAPISchema = {
57+
sourceName: 'some-name',
58+
request: [
59+
{
60+
fieldName: 'limit',
61+
fieldIn: FieldInType.QUERY,
62+
description: 'Existed one',
63+
type: FieldDataType.STRING,
64+
validators: [{ name: 'integer', args: { min: -4 } }],
65+
constraints: [],
66+
},
67+
],
68+
pagination: {
69+
mode: PaginationMode.OFFSET,
70+
},
71+
};
72+
const extractPaginationParams = new ExtractPaginationParams();
73+
// Act
74+
await extractPaginationParams.handle(schema, async () => Promise.resolve());
75+
// Assert
76+
expect(schema.request?.length).toBe(2);
77+
expect(schema.request).toContainEqual(
78+
expect.objectContaining({
79+
fieldName: 'limit',
80+
fieldIn: FieldInType.QUERY,
81+
description: 'Existed one',
82+
validators: [
83+
{ name: 'integer', args: { min: -4 } },
84+
{ name: 'integer', args: { min: 0 } },
85+
],
86+
})
87+
);
88+
expect(schema.request).toContainEqual(
89+
expect.objectContaining({
90+
fieldName: 'offset',
91+
fieldIn: FieldInType.QUERY,
92+
description:
93+
'Offset-based Pagination: The offset from the row. default: 0',
94+
})
95+
);
96+
});
97+
98+
it('Should override the description of parameters when the existed one is empty', async () => {
99+
// Arrange
100+
const schema: RawAPISchema = {
101+
sourceName: 'some-name',
102+
request: [
103+
{
104+
fieldName: 'limit',
105+
fieldIn: FieldInType.QUERY,
106+
description: '',
107+
type: FieldDataType.STRING,
108+
validators: [],
109+
constraints: [Constraint.Required()],
110+
},
111+
],
112+
pagination: {
113+
mode: PaginationMode.OFFSET,
114+
},
115+
};
116+
const extractPaginationParams = new ExtractPaginationParams();
117+
// Act
118+
await extractPaginationParams.handle(schema, async () => Promise.resolve());
119+
// Assert
120+
expect(schema.request?.length).toBe(2);
121+
expect(schema.request).toContainEqual(
122+
expect.objectContaining({
123+
fieldName: 'limit',
124+
fieldIn: FieldInType.QUERY,
125+
description:
126+
'Offset-based Pagination: The maximum number of rows to return. default: 20',
127+
constraints: [Constraint.Required()],
128+
})
129+
);
130+
expect(schema.request).toContainEqual(
131+
expect.objectContaining({
132+
fieldName: 'offset',
133+
fieldIn: FieldInType.QUERY,
134+
description:
135+
'Offset-based Pagination: The offset from the row. default: 0',
136+
})
137+
);
138+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { RawAPISchema } from '@vulcan-sql/build/schema-parser';
2+
import { TransformPaginationMode } from '@vulcan-sql/build/schema-parser/middleware/transformPaginationMode';
3+
import { PaginationMode } from '@vulcan-sql/core';
4+
5+
it('Should do nothing when there is no pagination mode configured', async () => {
6+
// Arrange
7+
const schema: RawAPISchema = {
8+
sourceName: 'some-name',
9+
};
10+
const transformPaginationMode = new TransformPaginationMode();
11+
// Act
12+
await transformPaginationMode.handle(schema, async () => Promise.resolve());
13+
// Assert
14+
expect(schema).toEqual(schema);
15+
});
16+
17+
it('Should transform pagination mode when it has been configured', async () => {
18+
// Arrange
19+
const schema: RawAPISchema = {
20+
sourceName: 'some-name',
21+
pagination: {
22+
mode: 'oFfSeT' as any,
23+
},
24+
};
25+
const transformPaginationMode = new TransformPaginationMode();
26+
// Act
27+
await transformPaginationMode.handle(schema, async () => Promise.resolve());
28+
// Assert
29+
expect(schema.pagination?.mode).toEqual(PaginationMode.OFFSET);
30+
});

0 commit comments

Comments
 (0)