1 | import { Kind, ObjectTypeDefinitionNode, SchemaDefinitionNode, InputObjectTypeDefinitionNode, DocumentNode } from 'graphql';
|
2 | import {
|
3 | getNamedType,
|
4 | getOperationFieldDefinition,
|
5 | getNonNullType,
|
6 | getInputValueDefinition,
|
7 | getTypeDefinition,
|
8 | getFieldDefinition,
|
9 | getDirectiveNode,
|
10 | getOperationTypeDefinition,
|
11 | } from './RelationalDBSchemaTransformerUtils';
|
12 | import { RelationalDBParsingException } from './RelationalDBParsingException';
|
13 | import { IRelationalDBReader } from './IRelationalDBReader';
|
14 | import { toUpper } from 'graphql-transformer-common';
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | export class TableContext {
|
22 | tableTypeDefinition: ObjectTypeDefinitionNode;
|
23 | createTypeDefinition: InputObjectTypeDefinitionNode;
|
24 | updateTypeDefinition: InputObjectTypeDefinitionNode;
|
25 |
|
26 | tableKeyField: string;
|
27 | tableKeyFieldType: string;
|
28 | stringFieldList: string[];
|
29 | intFieldList: string[];
|
30 | constructor(
|
31 | typeDefinition: ObjectTypeDefinitionNode,
|
32 | createDefinition: InputObjectTypeDefinitionNode,
|
33 | updateDefinition: InputObjectTypeDefinitionNode,
|
34 | primaryKeyField: string,
|
35 | primaryKeyType: string,
|
36 | stringFieldList: string[],
|
37 | intFieldList: string[]
|
38 | ) {
|
39 | this.tableTypeDefinition = typeDefinition;
|
40 | this.tableKeyField = primaryKeyField;
|
41 | this.createTypeDefinition = createDefinition;
|
42 | this.updateTypeDefinition = updateDefinition;
|
43 | this.tableKeyFieldType = primaryKeyType;
|
44 | this.stringFieldList = stringFieldList;
|
45 | this.intFieldList = intFieldList;
|
46 | }
|
47 | }
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | export class TemplateContext {
|
57 | schemaDoc: DocumentNode;
|
58 | typePrimaryKeyMap: Map<string, string>;
|
59 | typePrimaryKeyTypeMap: Map<string, string>;
|
60 | stringFieldMap: Map<string, string[]>;
|
61 | intFieldMap: Map<string, string[]>;
|
62 | secretStoreArn: string;
|
63 | rdsClusterIdentifier: string;
|
64 | databaseName: string;
|
65 | databaseSchema: string;
|
66 | region: string;
|
67 |
|
68 | constructor(
|
69 | schemaDoc: DocumentNode,
|
70 | typePrimaryKeyMap: Map<string, string>,
|
71 | stringFieldMap: Map<string, string[]>,
|
72 | intFieldMap: Map<string, string[]>,
|
73 | typePrimaryKeyTypeMap?: Map<string, string>
|
74 | ) {
|
75 | this.schemaDoc = schemaDoc;
|
76 | this.typePrimaryKeyMap = typePrimaryKeyMap;
|
77 | this.stringFieldMap = stringFieldMap;
|
78 | this.intFieldMap = intFieldMap;
|
79 | this.typePrimaryKeyTypeMap = typePrimaryKeyTypeMap;
|
80 | }
|
81 | }
|
82 |
|
83 | export class RelationalDBSchemaTransformer {
|
84 | dbReader: IRelationalDBReader;
|
85 | database: string;
|
86 |
|
87 | constructor(dbReader: IRelationalDBReader, database: string) {
|
88 | this.dbReader = dbReader;
|
89 | this.database = database;
|
90 | }
|
91 |
|
92 | public introspectDatabaseSchema = async (): Promise<TemplateContext> => {
|
93 |
|
94 | let tableNames = null;
|
95 | try {
|
96 | tableNames = await this.dbReader.listTables();
|
97 | } catch (err) {
|
98 | throw new RelationalDBParsingException(`Failed to list tables in ${this.database}`, err.stack);
|
99 | }
|
100 |
|
101 | let typeContexts = new Array();
|
102 | let types = new Array();
|
103 | let pkeyMap = new Map<string, string>();
|
104 | let pkeyTypeMap = new Map<string, string>();
|
105 | let stringFieldMap = new Map<string, string[]>();
|
106 | let intFieldMap = new Map<string, string[]>();
|
107 |
|
108 | for (const tableName of tableNames) {
|
109 | let type: TableContext = null;
|
110 | try {
|
111 | type = await this.dbReader.describeTable(tableName);
|
112 | } catch (err) {
|
113 | throw new RelationalDBParsingException(`Failed to describe table ${tableName}`, err.stack);
|
114 | }
|
115 |
|
116 |
|
117 |
|
118 | if (type.tableKeyField) {
|
119 | typeContexts.push(type);
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 | types.push(type.createTypeDefinition);
|
126 |
|
127 | types.push(type.tableTypeDefinition);
|
128 |
|
129 | types.push(type.updateTypeDefinition);
|
130 |
|
131 |
|
132 | stringFieldMap.set(tableName, type.stringFieldList);
|
133 | intFieldMap.set(tableName, type.intFieldList);
|
134 | pkeyMap.set(tableName, type.tableKeyField);
|
135 | pkeyTypeMap.set(tableName, type.tableKeyFieldType);
|
136 | } else {
|
137 | console.warn(`Skipping table ${type.tableTypeDefinition.name.value} because it does not have a single PRIMARY KEY.`);
|
138 | }
|
139 | }
|
140 |
|
141 |
|
142 | types.push(this.getMutations(typeContexts));
|
143 | types.push(this.getQueries(typeContexts));
|
144 | types.push(this.getSubscriptions(typeContexts));
|
145 | types.push(this.getSchemaType());
|
146 |
|
147 | let context = this.dbReader.hydrateTemplateContext(
|
148 | new TemplateContext({ kind: Kind.DOCUMENT, definitions: types }, pkeyMap, stringFieldMap, intFieldMap, pkeyTypeMap)
|
149 | );
|
150 | return context;
|
151 | };
|
152 |
|
153 | |
154 |
|
155 |
|
156 |
|
157 |
|
158 | getSchemaType(): SchemaDefinitionNode {
|
159 | return {
|
160 | kind: Kind.SCHEMA_DEFINITION,
|
161 | directives: [],
|
162 | operationTypes: [
|
163 | getOperationTypeDefinition('query', getNamedType('Query')),
|
164 | getOperationTypeDefinition('mutation', getNamedType('Mutation')),
|
165 | getOperationTypeDefinition('subscription', getNamedType('Subscription')),
|
166 | ],
|
167 | };
|
168 | }
|
169 |
|
170 | |
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 | private getMutations(types: TableContext[]): ObjectTypeDefinitionNode {
|
178 | const fields = [];
|
179 | for (const typeContext of types) {
|
180 | const type = typeContext.tableTypeDefinition;
|
181 | const formattedTypeValue = toUpper(type.name.value);
|
182 | fields.push(
|
183 | getOperationFieldDefinition(
|
184 | `delete${formattedTypeValue}`,
|
185 | [getInputValueDefinition(getNonNullType(getNamedType(typeContext.tableKeyFieldType)), typeContext.tableKeyField)],
|
186 | getNamedType(`${type.name.value}`),
|
187 | null
|
188 | )
|
189 | );
|
190 | fields.push(
|
191 | getOperationFieldDefinition(
|
192 | `create${formattedTypeValue}`,
|
193 | [getInputValueDefinition(getNonNullType(getNamedType(`Create${formattedTypeValue}Input`)), `create${formattedTypeValue}Input`)],
|
194 | getNamedType(`${type.name.value}`),
|
195 | null
|
196 | )
|
197 | );
|
198 | fields.push(
|
199 | getOperationFieldDefinition(
|
200 | `update${formattedTypeValue}`,
|
201 | [getInputValueDefinition(getNonNullType(getNamedType(`Update${formattedTypeValue}Input`)), `update${formattedTypeValue}Input`)],
|
202 | getNamedType(`${type.name.value}`),
|
203 | null
|
204 | )
|
205 | );
|
206 | }
|
207 | return getTypeDefinition(fields, 'Mutation');
|
208 | }
|
209 |
|
210 | |
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 | private getSubscriptions(types: TableContext[]): ObjectTypeDefinitionNode {
|
218 | const fields = [];
|
219 | for (const typeContext of types) {
|
220 | const type = typeContext.tableTypeDefinition;
|
221 | const formattedTypeValue = toUpper(type.name.value);
|
222 | fields.push(
|
223 | getOperationFieldDefinition(`onCreate${formattedTypeValue}`, [], getNamedType(`${type.name.value}`), [
|
224 | getDirectiveNode(`create${formattedTypeValue}`),
|
225 | ])
|
226 | );
|
227 | }
|
228 | return getTypeDefinition(fields, 'Subscription');
|
229 | }
|
230 |
|
231 | |
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 | private getQueries(types: TableContext[]): ObjectTypeDefinitionNode {
|
239 | const fields = [];
|
240 | for (const typeContext of types) {
|
241 | const type = typeContext.tableTypeDefinition;
|
242 | const formattedTypeValue = toUpper(type.name.value);
|
243 | fields.push(
|
244 | getOperationFieldDefinition(
|
245 | `get${formattedTypeValue}`,
|
246 | [getInputValueDefinition(getNonNullType(getNamedType(typeContext.tableKeyFieldType)), typeContext.tableKeyField)],
|
247 | getNamedType(`${type.name.value}`),
|
248 | null
|
249 | )
|
250 | );
|
251 | fields.push(getOperationFieldDefinition(`list${formattedTypeValue}s`, [], getNamedType(`[${type.name.value}]`), null));
|
252 | }
|
253 | return getTypeDefinition(fields, 'Query');
|
254 | }
|
255 |
|
256 | |
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 | getConnectionType(tableName: string): ObjectTypeDefinitionNode {
|
263 | return getTypeDefinition(
|
264 | [getFieldDefinition('items', getNamedType(`[${tableName}]`)), getFieldDefinition('nextToken', getNamedType('String'))],
|
265 | `${tableName}Connection`
|
266 | );
|
267 | }
|
268 | }
|