Thanks for your feedback and attention to detail!
There’s a couple of misunderstandings here and an actual problem that is scheduled to be solved in future releases. I’ll address them in order.
they try and pretend like we can infer domain types from the database schema
I absolutely agree that this is a wrong approach and actually a very common mistake in Haskell. A data type should model only one concept. However in many codebases they use a single type to model a database row and a domain model that also happens to have a JSON instance that surfaces in the public API, which results in coupling of everything, leading to changes caused by one tiny aspect escalating to the entire codebase or worse, silently breaking APIs. I want to stress that this is not what pGenie does or ever will do.
I’d much rather see this called TrackInfoRow (or even TrackInfoPgRow) and be explicit that this is a representation of a database row
Not in this case. The generated type you mention is located under the *.Types.* namespace, which stands for user-defined types as per the database. This one resembles not a row, but this user-defined Postgres type.
In case of types resembling rows of a result set, what you suggest is actually what we do. E.g., SelectAlbumWithTracksResultRow is a generated type that represents a row of the result set of the SelectAlbumWithTracks statement.
Otherwise tags :: Maybe (Vector (Maybe Text)) just seems hideous – in the real world you’ll probably want to have something like tags :: Set Text which is easier to read, more ergonomic and also more correct, because you’re unlikely to want to have duplicate tags.
The core problem here is that in Postgres all fields of composite types are nullable and there’s no control over that. Elements of all arrays are also nullable and there’s no control over that either. So this merely represents the truth. Yes, it’s not pretty, but silently pretending that this problem doesn’t exist will only lead to bugs in the user’s code. In this case we’re talking about a user-defined composite type.
In case of result sets you get complete control of issues like this via the signature files. They let you narrow down such types by stating that you expect only non-null values for both the array and its elements, as is done here, which leads to generation of a Vector Text without maybes. Doing such tweaks will lead to runtime decoding errors in case the database will present you with nulls, but that’s a tradeoff that you can choose to make in order to have a more ergonomic API. The default is safe.
Adding similar signature files for user-defined types is on the roadmap and thus it will give you an option of controlling this.
Now regarding Set. Postgres doesn’t have a set type. So the starting argument is again about the truth. Interpreting Postgres arrays as sets is imposing domain logic over the database integration. The safest way to do this is to control such conversions explicitly outside of the integration layer. However I hear your point about ergonomics and I think that such features can be added via the signature files in the future as well. Would be great to have a discussion about this and get an input from other users before implementing this.