-
-
Notifications
You must be signed in to change notification settings - Fork 100
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
Implement support for Postgres enums #59
Conversation
user: t.union([t.string, t.undefined]), | ||
dbName: t.union([t.string, t.undefined]), | ||
}), | ||
db: t.union([ |
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.
convenient not to have to include this in config
This allows pgtyped to generate code from queries involving database enum types. The postgres enums will be converted to a string-valued const enum in the generated code. The `getTypes` reflection query now joins on pg_enum and accumulates enum information as it collects other type information. I think it may be possible to accomodate composite types by aggregating over the object graph given by a suitable join query in a similar way. @pgtyped/query now returns `MappableTypes` which are either a string referencing a database type name or a fully-described `Type`. `Type` now includes `EnumType`. This allows type definitions to be realised at different stages in the reflection/mapping process and should allow for custom overrides in config. Signed-off-by: Silas Davis <silas@monax.io>
ae72ada
to
035401b
Compare
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 looks great, thanks for the PR @silasdavis!
Enums will be a nice addition to the list of supported type and a good step towards other composite types.
Added some minor naming/refactoring comments here and there.
I would also propose to make enum names and values PascalCase by default, that should integrate them better into TS land.
@@ -11,11 +11,5 @@ | |||
"emitTemplate": "{{dir}}/{{name}}.types.ts" | |||
} | |||
], | |||
"srcDir": "./src/", | |||
"db": { |
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 suggest to keep the db config in config.json
, not everyone will want to use docker
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.
Actually my removing it was inspired by me not using docker and wanting to not have to modify the config to do so, at least for running the generator. I didn't realise env would override at that point.
How about we optimise for the non-docker case by making the config be for a standard localhost connection, the kind you get with docker run -e POSTGRES_PASSWORD=password -p 5432:5432 postgres
? I think that is probably the lowest friction way to test locally.
As a side-note I think it would make sense to formally double up package/example
as integration tests. For this PR I started off by defining the end result I wanted in package/example
and really the example matter was essential for testing. I think it would work as a minimal integration test wired into CI, plus you know your example content works. What do you think?
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.
Wrapping package/example
with integration testing is a good idea, we should do that.
Eventually, we will want to decouple the example from the integration tests, but for now it should work just fine.
packages/query/src/actions.ts
Outdated
SELECT pt.oid, pt.typname, pt.typtype, pe.enumlabel | ||
FROM pg_type pt | ||
LEFT JOIN pg_enum pe ON pt.oid = pe.enumtypid | ||
WHERE pt.oid IN (${usedTypesOIDs.join(',')})`, |
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.
Soon PgTyped will be self-hosting and this query will be typed by PgTyped, hehe
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.
Oh I was thinking about that...
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.
Okay so I couldn't resist. I generated the necessary SQL but I the issue i came up against was that I cannot run the generated query against AsyncQueue
. It looks like the current interface needs the 'extended query' message to be supported on your wire package to support bindings as required by IDatabaseConnection
. Perhaps we can handle binding substitution on this side of the pipe. It would be nice if AsyncQueue
supported IDatabaseConnection
other than that I think I have this self-hosted.
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.
Haha okay nope I think you do supported the prepared message flow... I see it breaks down into the various parse messages. I will have to have a go at this in a future PR :)
I won't bother to ask why you implemented the wire protocol... it does seem pretty neat. But why did you :) ? Was there a particular frustration with pg, or a hard requirement, or was it just because it was there (I would certainly consider using a small library like this written in typescript directly over pg if it did enough...)?
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.
Yes, self-hosting isn't ready yet :)
Back when I implemented the wire protocol, I couldn't find a good TS native package that provided packet level primitives for interacting with PostgreSQL. These low level messages are needed to query the DB for type data. Another reason was to potentially evolve PgTyped into a self-contained Postgres client. I think there is a good possibility to improve the reliability and performance of existing client, but this is a side goal and not a priority at the moment.
Made all of your changes with variation on config which is closer to running out of the box without docker. Also refactored the type row reduction for clarity and better testing. Added snapshot tests based on that that are in with a chance of surviving us adding additional fields to the type rows query output. |
I did consider this, but I leaned towards not being too clever and leaving them as-is. Reasons:
const enum Foo {
bar = 'bar',
baz = 'baz',
} Then the reverse lookup However if instead we generate: const enum Foo {
Bar = 'bar',
Baz = 'baz',
} Then
|
@silasdavis thanks for all the updates, I will be looking through them a bit later today. |
8b280e6
to
93998b2
Compare
Ugh yeah sorry force of habit... Let me see what I can do with the help of my reflog on that front... |
Also respond to various issues from review: - Tweak names - Make db config work with localhost Signed-off-by: Silas Davis <silas@monax.io>
Second commit has changes post-review |
Re: Enums I think it is useful to have PgTyped transform postgres types to match TS style guidelines by default, potentially even converting snake-case field names into camel-case. Of course it should also be possible to opt-out of such behavior via the config. The good thing about PgTyped is that this conversion can now be deterministic because the DB<->JS interface is typed. For this PR, I think we can merge it as is, but I do want to enable code style transforms by default before next release (with an opt-out switch ofc). Stabilizing this part of the PgTyped API for the end users is important, given its potential to cause generated code breaking changes later on. |
How about adding the verbatim case enum values as well as the converted pascal case ones (is they differ). Another degenerate case is where two different enum values become identical when pascalcased.
Can you give an example? |
How would that look?
It is a very rare case imo, it has to be an enum that looks like
Yes, |
That's the bit I was missing, ESLint seems to let me get away with this with what I thought were pretty standard rules in any case.
enum {
foo = 'foo'
Foo = 'foo'
} Appears to be allowed at least. It would make the membership test work. That's the thing I'd be most reluctant to break. However if you have a linter that is being strict about enum label then presumably that wouldn't work.
We still would need to do something - either fail, emit code that doesn't compile, or just output the verbatim enum values. Presumably the latter is best. Another potential issue, I'm not sure if enum labels limited to latin scripts or can they be unicode. If the latter then there may exist no case conversion. Personally, I would make this kind of cleverness an opt-in rather than an opt-out. But I see your point and you have the benefit of being the benevolent dictator here :) so happy to implement. Shall we address in a subsequent PR? |
Actually, having thought about it a bit more, I agree it does make sense to keep the current behavior as default. Changing case by default can be surprising and given the possible edge-cases it is best to provide it behind a config setting. Thanks for the discussion there, that was helpful. I will open a separate issue for implementing case conversion and a flag for it. |
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 @silasdavis!
That's great, it speaks well of a maintainer who can change their mind on occasion :) Any thoughts on when this will make it into a release? |
Released in v0.7.0 :) |
This allows pgtyped to generate code from queries involving database
enum types. The postgres enums will be converted to a string-valued const enum
in the generated code.
The
getTypes
reflection query now joins on pg_enum and accumulatesenum information as it collects other type information. I think it may
be possible to accomodate composite types by aggregating over the object
graph given by a suitable join query in a similar way.
@pgtyped/query now returns
MappableTypes
which are either a stringreferencing a database type name or a fully-described
Type
.Type
nowincludes
EnumType
. This allows type definitions to be realised atdifferent stages in the reflection/mapping process and should allow for
custom overrides in config.
fixes #54