UNPKG

7.94 kBPlain TextView Raw
1import TemplateContext, { TableContext } from "./RelationalDBSchemaTransformer";
2import { getNamedType, getNonNullType, getInputValueDefinition, getGraphQLTypeFromMySQLType,
3 getTypeDefinition, getFieldDefinition, getInputTypeDefinition } from './RelationalDBSchemaTransformerUtils'
4import { AuroraDataAPIClient } from "./AuroraDataAPIClient";
5import { IRelationalDBReader } from "./IRelationalDBReader";
6
7/**
8 * A class to manage interactions with a Aurora Serverless MySQL Relational Databse
9 * using the Aurora Data API
10 */
11export class AuroraServerlessMySQLDatabaseReader implements IRelationalDBReader {
12
13 auroraClient: AuroraDataAPIClient
14 dbRegion: string
15 awsSecretStoreArn: string
16 dbClusterOrInstanceArn: string
17 database: string
18
19 setAuroraClient(auroraClient: AuroraDataAPIClient) {
20 this.auroraClient = auroraClient
21 }
22
23 constructor(dbRegion: string, awsSecretStoreArn: string, dbClusterOrInstanceArn: string, database: string, aws:any) {
24 this.auroraClient = new AuroraDataAPIClient(dbRegion, awsSecretStoreArn,
25 dbClusterOrInstanceArn, database, aws)
26 this.dbRegion = dbRegion
27 this.awsSecretStoreArn = awsSecretStoreArn
28 this.dbClusterOrInstanceArn = dbClusterOrInstanceArn
29 this.database = database
30 }
31
32 /**
33 * Stores some of the Aurora Serverless MySQL context into the template context,
34 * for later consumption.
35 *
36 * @param contextShell the basic template context, with db source independent fields set.
37 * @returns a fully hydrated template context, complete with Aurora Serverless MySQL context.
38 */
39 hydrateTemplateContext = async(contextShell: TemplateContext): Promise<TemplateContext> => {
40
41 /**
42 * Information needed for creating the AppSync - RDS Data Source
43 * Store as part of the TemplateContext
44 */
45 contextShell.secretStoreArn = this.awsSecretStoreArn
46 contextShell.rdsClusterIdentifier = this.dbClusterOrInstanceArn
47 contextShell.databaseSchema = 'mysql'
48 contextShell.databaseName = this.database
49 contextShell.region = this.dbRegion
50 return contextShell
51 }
52
53 /**
54 * Gets a list of all the table names in the provided database.
55 *
56 * @returns a list of tablenames inside the database.
57 */
58 listTables = async (): Promise<string[]> => {
59 const results = await this.auroraClient.listTables()
60 return results
61 }
62
63 /**
64 * Looks up any foreign key constraints that might exist for the provided table.
65 * This is done to ensure our generated schema includes nested types, where possible.
66 *
67 * @param tableName the name of the table to be checked for foreign key constraints.
68 * @returns a list of table names that are applicable as having constraints.
69 */
70 getTableForeignKeyReferences = async (tableName: string) : Promise<string[]> => {
71 const results = await this.auroraClient.getTableForeignKeyReferences(tableName)
72 return results
73 }
74
75 /**
76 * For the provided table, this will create a table context. That context holds definitions for
77 * the base table type, the create input type, and the update input type (e.g. Post, CreatePostInput, and UpdatePostInput, respectively),
78 * as well as the table primary key structure for proper operation definition.
79 *
80 * Create inputs will only differ from the base table type in that any nested types will not be present. Update table
81 * inputs will differ in that the only required field will be the primary key/identifier, as all fields don't have to
82 * be updated. Instead, it assumes the proper ones were provided on create.
83 *
84 * @param tableName the name of the table to be translated into a GraphQL type.
85 * @returns a promise of a table context structure.
86 */
87 describeTable = async (tableName: string): Promise<TableContext> => {
88 const columnDescriptions = await this.auroraClient.describeTable(tableName)
89 // Fields in the general type (e.g. Post). Both the identifying field and any others the db dictates will be required.
90 const fields = new Array()
91 // Fields in the update input type (e.g. UpdatePostInput). Only the identifying field will be required, any others will be optional.
92 const updateFields = new Array()
93 // Field in the create input type (e.g. CreatePostInput).
94 const createFields = new Array()
95
96 // The primary key, used to help generate queries and mutations
97 let primaryKey = ""
98 let primaryKeyType = ""
99
100 // Field Lists needed as context for auto-generating the Query Resolvers
101 const intFieldList = new Array()
102 const stringFieldList = new Array()
103
104 for (const columnDescription of columnDescriptions) {
105 // If a field is the primary key, save it.
106 if (columnDescription.Key == 'PRI') {
107 primaryKey = columnDescription.Field
108 primaryKeyType = getGraphQLTypeFromMySQLType(columnDescription.Type)
109 } else {
110 /**
111 * If the field is not a key, then store it in the fields list.
112 * As we need this information later to generate query resolvers
113 *
114 * Currently we will only auto-gen query resolvers for the Int and String scalars
115 */
116 const type = getGraphQLTypeFromMySQLType(columnDescription.Type)
117 if (type === 'Int') {
118 intFieldList.push(columnDescription.Field)
119 } else if (type === 'String') {
120 stringFieldList.push(columnDescription.Field)
121 }
122 }
123
124 // Create the basic field type shape, to be consumed by every field definition
125 const baseType = getNamedType(getGraphQLTypeFromMySQLType(columnDescription.Type))
126
127 const isPrimaryKey = columnDescription.Key == 'PRI'
128 const isNullable = columnDescription.Null == 'YES'
129
130 // Generate the field for the general type and the create input type
131 const type = (!isPrimaryKey && isNullable) ? baseType : getNonNullType(baseType)
132 fields.push(getFieldDefinition(columnDescription.Field, type))
133
134 createFields.push(getInputValueDefinition(type, columnDescription.Field))
135
136 // Update<type>Input has only the primary key as required, ignoring all other that the database requests as non-nullable
137 const updateType = !isPrimaryKey ? baseType : getNonNullType(baseType)
138 updateFields.push(getInputValueDefinition(updateType, columnDescription.Field))
139 }
140
141 // Add foreign key for this table
142
143 // NOTE from @mikeparisstuff: It would be great to re-enable this such that foreign key relationships are
144 // resolver automatically. This code was breaking compilation because it was not
145 // creating XConnection types correctly. This package also does not yet support
146 // wiring up the resolvers (or ideally selection set introspection & automatic JOINs)
147 // so there is not point in creating these connection fields anyway. Disabling until
148 // supported.
149 // let tablesWithRef = await this.getTableForeignKeyReferences(tableName)
150 // for (const tableWithRef of tablesWithRef) {
151 // if (tableWithRef && tableWithRef.length > 0) {
152 // const baseType = getNamedType(`${tableWithRef}Connection`)
153 // fields.push(getFieldDefinition(`${tableWithRef}`, baseType))
154 // }
155 // }
156
157 return new TableContext(getTypeDefinition(fields, tableName), getInputTypeDefinition(createFields, `Create${tableName}Input`),
158 getInputTypeDefinition(updateFields, `Update${tableName}Input`), primaryKey, primaryKeyType, stringFieldList, intFieldList)
159 }
160}
\No newline at end of file