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