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