-
Notifications
You must be signed in to change notification settings - Fork 89
feat: generate ts schema from pg serial field #2952
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
8d4f420 to
f411e70
Compare
| " | ||
| `; | ||
|
|
||
| exports[`generate graphqlSchemaFromSQLSchema creates graphql schema from "postgres" "serial" schema 1`] = ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This snapshot is not intentional as we are not intending to support GQL generation here. It is generated automatically to test against anyway from the schemas in __utils__.
| expect(graphqlSchema).toMatchSnapshot(); | ||
| }); | ||
|
|
||
| it.each([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These cases are not exhaustive over every Field that could exist but cover the most unique cases.
f411e70 to
09e08e8
Compare
09e08e8 to
b793733
Compare
| const createDataType = (type: FieldType): ts.Node => { | ||
| if (type.kind === 'Scalar') { | ||
| const createDataType = (field: Field): ts.Node => { | ||
| const sequenceRegex = /^nextval\(.+::regclass\)$/; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went back and forth on a few approaches to this check but I eventually felt that a quick regex is the simplest surefire way to identify SERIAL fields. In the future I think this logic could be extracted into something like if (canLetDBGenerate(field)) which evaluates rules isSerial(field) || isUserDefinedFunction(field) etc... But I felt that might be premature for this implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For more context, here's what a value for column_default looks like on a serial field when we query the DB schema:
nextval('serial_table_number_seq'::regclass).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we find documentation that this is the canonical way to determine whether a field is serial? In the past we've relied on experimentally-verified "rules" that turned out to be incomplete, so I'd love to get ahead of that this time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added main reply with my research/argument later in thread. I don't believe there is a truly canonical way to identify serial but we can identify if a column is backed by a sequence.
| \\"CoffeeQueue\\": a.model({ | ||
| id: a.integer().default(), | ||
| name: a.string(), | ||
| orderNumber: a.integer().default() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the desired behavior of this PR.
| ] | ||
| `; | ||
|
|
||
| exports[`testPostgresStringDataSourceAdapter sets the model field correctly from a schema with a SERIAL field 1`] = ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This behavior is not new but I am codifying it since generation depends on it now.
| } | ||
| type SerialTable @refersTo(name: \\" serial_table\\") @model { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we know why we have blank spaces before the table name for refersTo directive?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whitespace in my template string with the field info csv was causing this--resolved.
|
|
||
| it('sets the model field correctly from a schema with a SERIAL field', () => { | ||
| const adapter = new PostgresStringDataSourceAdapter(schemas.postgres.serial); | ||
| expect(adapter.getModels()[0].getFields()).toMatchSnapshot(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before this validation, can we add a check to make sure getModels contains an element? If there are failures, the current implementation will throw a generic error like trying to access a property of undefined which could be hard to troubleshoot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modified this to match the other tests, e.g. just adapter.getModels(). I think it's clearer and it also retains info that about the PK:
"primaryKey": Index {
"fields": Array [
"id",
],
"name": "PRIMARY_KEY",
},| if (type.kind === 'Scalar') { | ||
| const createDataType = (field: Field): ts.Node => { | ||
| const sequenceRegex = /^nextval\(.+::regclass\)$/; | ||
| if (field.default?.kind === 'DB_GENERATED' && sequenceRegex.test(field.default.value.toString())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can possible move the check sequenceRegex.test(field.default.value.toString() to a separate method so that it can reused in the future like isSequenceField(field).
the text alignment was causing weird names in snaphot
| const sequenceRegex = /^nextval\(.+::regclass\)$/; | ||
| return field.default?.kind === 'DB_GENERATED' && sequenceRegex.test(field.default.value.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Copying this from main conversation comment)
Can we find documentation that this is the canonical way to determine whether a field is serial? In the past we've relied on experimentally-verified "rules" that turned out to be incomplete, so I'd love to get ahead of that this time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In summary we cannot determine if a column was declared serial, we can identify sequences though, and from this, infer serial fields.
Ultimately serial doesn't really exist as a datatype we can directly introspect. In PG it is basically a macro for "Create a column as an integer and create a sequence to back that column as a default". We end up with an ordinary non-null integer with a special default value.
We can tell if a column is backed by a sequence since its default will be nextval(_), one of the special sequence functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the thorough explanation!
* chore: add missing types * test: ts schema serial field gen happy case * test: ts schema serial field gen sad cases * chore: add DEFAULT_METHOD to TS schema constants * feat: intepret pg serial fields in ts schema gen * test: do not handle db provided int constants * refactor: simpler serial field default condition * test: pg string adapter sets model field correctly from a schema with a SERIAL field * test: align text in template string the text alignment was causing weird names in snaphot * test: serial gen snapshots against all of getModels() * refactor: extract sequence field identification logic * chore: no-op change to force rebuild
Description of changes
Update TS schema generator to properly annotate serial fields from Postgres datasources.
e.g.:
myField SERIAL->myField: a.integer().default()Explanation:
We already populate
field.defaultwhen importing a DB schema, but don't really do anything with it prior to this change.Now, we check if the field default is
DB_GENERATED, and the value for the generated field indicates it is aSERIALcolumn. If this is the case, we annotate the TS schema field with.default().CDK / CloudFormation Parameters Changed
N/A
Issue #, if available
N/A
Description of how you validated changes
See test cases in
packages/amplify-graphql-schema-generator/src/__tests__/generate-ts-data-schema.test.tspackages/amplify-graphql-schema-generator/src/__tests__/pg-string-datasource-adapter.test.tsChecklist
yarn testpassesBy submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.