1 | import { TemplateContext } from './RelationalDBSchemaTransformer';
|
2 | import { DocumentNode } from 'graphql';
|
3 | import { Fn } from 'cloudform';
|
4 | import AppSync from 'cloudform-types/types/appSync';
|
5 | import { print, obj, set, str, list, forEach, ref, compoundExpression } from 'graphql-mapping-template';
|
6 | import { graphqlName, toUpper, plurality } from 'graphql-transformer-common';
|
7 | import { ResourceConstants } from './ResourceConstants';
|
8 | import { RelationalDBMappingTemplate } from './RelationalDBMappingTemplate';
|
9 | import * as fs from 'fs-extra';
|
10 |
|
11 | const s3BaseUrl = 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/${ResolverFileName}';
|
12 | const resolverFileName = 'ResolverFileName';
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | export class RelationalDBResolverGenerator {
|
22 | document: DocumentNode;
|
23 | typePrimaryKeyMap: Map<string, string>;
|
24 | stringFieldMap: Map<string, string[]>;
|
25 | intFieldMap: Map<string, string[]>;
|
26 | resolverFilePath: string;
|
27 | typePrimaryKeyTypeMap: Map<string, string>;
|
28 |
|
29 | constructor(context: TemplateContext) {
|
30 | this.document = context.schemaDoc;
|
31 | this.typePrimaryKeyMap = context.typePrimaryKeyMap;
|
32 | this.stringFieldMap = context.stringFieldMap;
|
33 | this.intFieldMap = context.intFieldMap;
|
34 | this.typePrimaryKeyTypeMap = context.typePrimaryKeyTypeMap;
|
35 | }
|
36 |
|
37 | |
38 |
|
39 |
|
40 |
|
41 | public createRelationalResolvers(resolverFilePath: string) {
|
42 | let resources = {};
|
43 | this.resolverFilePath = resolverFilePath;
|
44 | this.typePrimaryKeyMap.forEach((value: string, key: string) => {
|
45 | const resourceName = key.replace(/[^A-Za-z0-9]/g, '');
|
46 | resources = {
|
47 | ...resources,
|
48 | ...{ [resourceName + 'CreateResolver']: this.makeCreateRelationalResolver(key) },
|
49 | ...{ [resourceName + 'GetResolver']: this.makeGetRelationalResolver(key) },
|
50 | ...{ [resourceName + 'UpdateResolver']: this.makeUpdateRelationalResolver(key) },
|
51 | ...{ [resourceName + 'DeleteResolver']: this.makeDeleteRelationalResolver(key) },
|
52 | ...{ [resourceName + 'ListResolver']: this.makeListRelationalResolver(key) },
|
53 | };
|
54 |
|
55 | });
|
56 |
|
57 | return resources;
|
58 | }
|
59 |
|
60 | |
61 |
|
62 |
|
63 |
|
64 | |
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | private makeCreateRelationalResolver(type: string, mutationTypeName: string = 'Mutation') {
|
72 | const fieldName = graphqlName('create' + toUpper(type));
|
73 | let createSql = `INSERT INTO ${type} $colStr VALUES $valStr`;
|
74 | let selectSql;
|
75 | if (this.typePrimaryKeyTypeMap.get(type).includes('String')) {
|
76 | selectSql = `SELECT * FROM ${type} WHERE ${this.typePrimaryKeyMap.get(type)}=\'$ctx.args.create${toUpper(
|
77 | type
|
78 | )}Input.${this.typePrimaryKeyMap.get(type)}\'`;
|
79 | } else {
|
80 | selectSql = `SELECT * FROM ${type} WHERE ${this.typePrimaryKeyMap.get(type)}=$ctx.args.create${toUpper(
|
81 | type
|
82 | )}Input.${this.typePrimaryKeyMap.get(type)}`;
|
83 | }
|
84 |
|
85 | const reqFileName = `${mutationTypeName}.${fieldName}.req.vtl`;
|
86 | const resFileName = `${mutationTypeName}.${fieldName}.res.vtl`;
|
87 |
|
88 | const reqTemplate = print(
|
89 | compoundExpression([
|
90 | set(ref('cols'), list([])),
|
91 | set(ref('vals'), list([])),
|
92 | forEach(ref('entry'), ref(`ctx.args.create${toUpper(type)}Input.keySet()`), [
|
93 | set(ref('discard'), ref(`cols.add($entry)`)),
|
94 | set(ref('discard'), ref(`vals.add("'$ctx.args.create${toUpper(type)}Input[$entry]'")`)),
|
95 | ]),
|
96 | set(ref('valStr'), ref('vals.toString().replace("[","(").replace("]",")")')),
|
97 | set(ref('colStr'), ref('cols.toString().replace("[","(").replace("]",")")')),
|
98 | RelationalDBMappingTemplate.rdsQuery({
|
99 | statements: list([str(createSql), str(selectSql)]),
|
100 | }),
|
101 | ])
|
102 | );
|
103 |
|
104 | const resTemplate = print(ref('utils.toJson($utils.parseJson($utils.rds.toJsonString($ctx.result))[1][0])'));
|
105 |
|
106 | fs.writeFileSync(`${this.resolverFilePath}/${reqFileName}`, reqTemplate, 'utf8');
|
107 | fs.writeFileSync(`${this.resolverFilePath}/${resFileName}`, resTemplate, 'utf8');
|
108 |
|
109 | let resolver = new AppSync.Resolver({
|
110 | ApiId: Fn.Ref(ResourceConstants.PARAMETERS.AppSyncApiId),
|
111 | DataSourceName: Fn.GetAtt(ResourceConstants.RESOURCES.RelationalDatabaseDataSource, 'Name'),
|
112 | TypeName: mutationTypeName,
|
113 | FieldName: fieldName,
|
114 | RequestMappingTemplateS3Location: Fn.Sub(s3BaseUrl, {
|
115 | [ResourceConstants.PARAMETERS.S3DeploymentBucket]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
|
116 | [ResourceConstants.PARAMETERS.S3DeploymentRootKey]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
|
117 | [resolverFileName]: reqFileName,
|
118 | }),
|
119 | ResponseMappingTemplateS3Location: Fn.Sub(s3BaseUrl, {
|
120 | [ResourceConstants.PARAMETERS.S3DeploymentBucket]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
|
121 | [ResourceConstants.PARAMETERS.S3DeploymentRootKey]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
|
122 | [resolverFileName]: resFileName,
|
123 | }),
|
124 | }).dependsOn([ResourceConstants.RESOURCES.RelationalDatabaseDataSource]);
|
125 | return resolver;
|
126 | }
|
127 |
|
128 | |
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 | private makeGetRelationalResolver(type: string, queryTypeName: string = 'Query') {
|
136 | const fieldName = graphqlName('get' + toUpper(type));
|
137 | let sql;
|
138 | if (this.typePrimaryKeyTypeMap.get(type).includes('String')) {
|
139 | sql = `SELECT * FROM ${type} WHERE ${this.typePrimaryKeyMap.get(type)}=\'$ctx.args.${this.typePrimaryKeyMap.get(type)}\'`;
|
140 | } else {
|
141 | sql = `SELECT * FROM ${type} WHERE ${this.typePrimaryKeyMap.get(type)}=$ctx.args.${this.typePrimaryKeyMap.get(type)}`;
|
142 | }
|
143 | const reqFileName = `${queryTypeName}.${fieldName}.req.vtl`;
|
144 | const resFileName = `${queryTypeName}.${fieldName}.res.vtl`;
|
145 |
|
146 | const reqTemplate = print(
|
147 | compoundExpression([
|
148 | RelationalDBMappingTemplate.rdsQuery({
|
149 | statements: list([str(sql)]),
|
150 | }),
|
151 | ])
|
152 | );
|
153 |
|
154 | const resTemplate = print(ref('utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])'));
|
155 |
|
156 | fs.writeFileSync(`${this.resolverFilePath}/${reqFileName}`, reqTemplate, 'utf8');
|
157 | fs.writeFileSync(`${this.resolverFilePath}/${resFileName}`, resTemplate, 'utf8');
|
158 |
|
159 | let resolver = new AppSync.Resolver({
|
160 | ApiId: Fn.Ref(ResourceConstants.PARAMETERS.AppSyncApiId),
|
161 | DataSourceName: Fn.GetAtt(ResourceConstants.RESOURCES.RelationalDatabaseDataSource, 'Name'),
|
162 | FieldName: fieldName,
|
163 | TypeName: queryTypeName,
|
164 | RequestMappingTemplateS3Location: Fn.Sub(s3BaseUrl, {
|
165 | [ResourceConstants.PARAMETERS.S3DeploymentBucket]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
|
166 | [ResourceConstants.PARAMETERS.S3DeploymentRootKey]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
|
167 | [resolverFileName]: reqFileName,
|
168 | }),
|
169 | ResponseMappingTemplateS3Location: Fn.Sub(s3BaseUrl, {
|
170 | [ResourceConstants.PARAMETERS.S3DeploymentBucket]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
|
171 | [ResourceConstants.PARAMETERS.S3DeploymentRootKey]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
|
172 | [resolverFileName]: resFileName,
|
173 | }),
|
174 | }).dependsOn([ResourceConstants.RESOURCES.RelationalDatabaseDataSource]);
|
175 | return resolver;
|
176 | }
|
177 |
|
178 | |
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 | private makeUpdateRelationalResolver(type: string, mutationTypeName: string = 'Mutation') {
|
186 | const fieldName = graphqlName('update' + toUpper(type));
|
187 | const updateSql = `UPDATE ${type} SET $update WHERE ${this.typePrimaryKeyMap.get(type)}=$ctx.args.update${toUpper(
|
188 | type
|
189 | )}Input.${this.typePrimaryKeyMap.get(type)}`;
|
190 | let selectSql;
|
191 | if (this.typePrimaryKeyTypeMap.get(type).includes('String')) {
|
192 | selectSql = `SELECT * FROM ${type} WHERE ${this.typePrimaryKeyMap.get(type)}=\'$ctx.args.update${toUpper(
|
193 | type
|
194 | )}Input.${this.typePrimaryKeyMap.get(type)}\'`;
|
195 | } else {
|
196 | selectSql = `SELECT * FROM ${type} WHERE ${this.typePrimaryKeyMap.get(type)}=$ctx.args.update${toUpper(
|
197 | type
|
198 | )}Input.${this.typePrimaryKeyMap.get(type)}`;
|
199 | }
|
200 | const reqFileName = `${mutationTypeName}.${fieldName}.req.vtl`;
|
201 | const resFileName = `${mutationTypeName}.${fieldName}.res.vtl`;
|
202 |
|
203 | const reqTemplate = print(
|
204 | compoundExpression([
|
205 | set(ref('updateList'), obj({})),
|
206 | forEach(ref('entry'), ref(`ctx.args.update${toUpper(type)}Input.keySet()`), [
|
207 | set(ref('discard'), ref(`updateList.put($entry, "'$ctx.args.update${toUpper(type)}Input[$entry]'")`)),
|
208 | ]),
|
209 | set(ref('update'), ref(`updateList.toString().replace("{","").replace("}","")`)),
|
210 | RelationalDBMappingTemplate.rdsQuery({
|
211 | statements: list([str(updateSql), str(selectSql)]),
|
212 | }),
|
213 | ])
|
214 | );
|
215 |
|
216 | const resTemplate = print(ref('utils.toJson($utils.parseJson($utils.rds.toJsonString($ctx.result))[1][0])'));
|
217 |
|
218 | fs.writeFileSync(`${this.resolverFilePath}/${reqFileName}`, reqTemplate, 'utf8');
|
219 | fs.writeFileSync(`${this.resolverFilePath}/${resFileName}`, resTemplate, 'utf8');
|
220 |
|
221 | let resolver = new AppSync.Resolver({
|
222 | ApiId: Fn.Ref(ResourceConstants.PARAMETERS.AppSyncApiId),
|
223 | DataSourceName: Fn.GetAtt(ResourceConstants.RESOURCES.RelationalDatabaseDataSource, 'Name'),
|
224 | TypeName: mutationTypeName,
|
225 | FieldName: fieldName,
|
226 | RequestMappingTemplateS3Location: Fn.Sub(s3BaseUrl, {
|
227 | [ResourceConstants.PARAMETERS.S3DeploymentBucket]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
|
228 | [ResourceConstants.PARAMETERS.S3DeploymentRootKey]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
|
229 | [resolverFileName]: reqFileName,
|
230 | }),
|
231 | ResponseMappingTemplateS3Location: Fn.Sub(s3BaseUrl, {
|
232 | [ResourceConstants.PARAMETERS.S3DeploymentBucket]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
|
233 | [ResourceConstants.PARAMETERS.S3DeploymentRootKey]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
|
234 | [resolverFileName]: resFileName,
|
235 | }),
|
236 | }).dependsOn([ResourceConstants.RESOURCES.RelationalDatabaseDataSource]);
|
237 | return resolver;
|
238 | }
|
239 |
|
240 | |
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 | private makeDeleteRelationalResolver(type: string, mutationTypeName: string = 'Mutation') {
|
248 | const fieldName = graphqlName('delete' + toUpper(type));
|
249 | let selectSql;
|
250 | if (this.typePrimaryKeyTypeMap.get(type).includes('String')) {
|
251 | selectSql = `SELECT * FROM ${type} WHERE ${this.typePrimaryKeyMap.get(type)}=\'$ctx.args.${this.typePrimaryKeyMap.get(type)}\'`;
|
252 | } else {
|
253 | selectSql = `SELECT * FROM ${type} WHERE ${this.typePrimaryKeyMap.get(type)}=$ctx.args.${this.typePrimaryKeyMap.get(type)}`;
|
254 | }
|
255 | const deleteSql = `DELETE FROM ${type} WHERE ${this.typePrimaryKeyMap.get(type)}=$ctx.args.${this.typePrimaryKeyMap.get(type)}`;
|
256 | const reqFileName = `${mutationTypeName}.${fieldName}.req.vtl`;
|
257 | const resFileName = `${mutationTypeName}.${fieldName}.res.vtl`;
|
258 | const reqTemplate = print(
|
259 | compoundExpression([
|
260 | RelationalDBMappingTemplate.rdsQuery({
|
261 | statements: list([str(selectSql), str(deleteSql)]),
|
262 | }),
|
263 | ])
|
264 | );
|
265 | const resTemplate = print(ref('utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])'));
|
266 |
|
267 | fs.writeFileSync(`${this.resolverFilePath}/${reqFileName}`, reqTemplate, 'utf8');
|
268 | fs.writeFileSync(`${this.resolverFilePath}/${resFileName}`, resTemplate, 'utf8');
|
269 |
|
270 | let resolver = new AppSync.Resolver({
|
271 | ApiId: Fn.Ref(ResourceConstants.PARAMETERS.AppSyncApiId),
|
272 | DataSourceName: Fn.GetAtt(ResourceConstants.RESOURCES.RelationalDatabaseDataSource, 'Name'),
|
273 | TypeName: mutationTypeName,
|
274 | FieldName: fieldName,
|
275 | RequestMappingTemplateS3Location: Fn.Sub(s3BaseUrl, {
|
276 | [ResourceConstants.PARAMETERS.S3DeploymentBucket]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
|
277 | [ResourceConstants.PARAMETERS.S3DeploymentRootKey]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
|
278 | [resolverFileName]: reqFileName,
|
279 | }),
|
280 | ResponseMappingTemplateS3Location: Fn.Sub(s3BaseUrl, {
|
281 | [ResourceConstants.PARAMETERS.S3DeploymentBucket]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
|
282 | [ResourceConstants.PARAMETERS.S3DeploymentRootKey]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
|
283 | [resolverFileName]: resFileName,
|
284 | }),
|
285 | }).dependsOn([ResourceConstants.RESOURCES.RelationalDatabaseDataSource]);
|
286 |
|
287 | return resolver;
|
288 | }
|
289 |
|
290 | |
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 | private makeListRelationalResolver(type: string, queryTypeName: string = 'Query') {
|
298 | const fieldName = graphqlName('list' + plurality(toUpper(type)));
|
299 | const sql = `SELECT * FROM ${type}`;
|
300 | const reqFileName = `${queryTypeName}.${fieldName}.req.vtl`;
|
301 | const resFileName = `${queryTypeName}.${fieldName}.res.vtl`;
|
302 | const reqTemplate = print(
|
303 | RelationalDBMappingTemplate.rdsQuery({
|
304 | statements: list([str(sql)]),
|
305 | })
|
306 | );
|
307 | const resTemplate = print(ref('utils.toJson($utils.rds.toJsonObject($ctx.result)[0])'));
|
308 |
|
309 | fs.writeFileSync(`${this.resolverFilePath}/${reqFileName}`, reqTemplate, 'utf8');
|
310 | fs.writeFileSync(`${this.resolverFilePath}/${resFileName}`, resTemplate, 'utf8');
|
311 |
|
312 | let resolver = new AppSync.Resolver({
|
313 | ApiId: Fn.Ref(ResourceConstants.PARAMETERS.AppSyncApiId),
|
314 | DataSourceName: Fn.GetAtt(ResourceConstants.RESOURCES.RelationalDatabaseDataSource, 'Name'),
|
315 | TypeName: queryTypeName,
|
316 | FieldName: fieldName,
|
317 | RequestMappingTemplateS3Location: Fn.Sub(s3BaseUrl, {
|
318 | [ResourceConstants.PARAMETERS.S3DeploymentBucket]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
|
319 | [ResourceConstants.PARAMETERS.S3DeploymentRootKey]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
|
320 | [resolverFileName]: reqFileName,
|
321 | }),
|
322 | ResponseMappingTemplateS3Location: Fn.Sub(s3BaseUrl, {
|
323 | [ResourceConstants.PARAMETERS.S3DeploymentBucket]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
|
324 | [ResourceConstants.PARAMETERS.S3DeploymentRootKey]: Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
|
325 | [resolverFileName]: resFileName,
|
326 | }),
|
327 | }).dependsOn([ResourceConstants.RESOURCES.RelationalDatabaseDataSource]);
|
328 |
|
329 | return resolver;
|
330 | }
|
331 | }
|