[HN Gopher] Parrot - type-safe SQL in Gleam, supports SQlite, Po...
       ___________________________________________________________________
        
       Parrot - type-safe SQL in Gleam, supports SQlite, PostgreSQL and
       MySQL
        
       Author : TheWiggles
       Score  : 109 points
       Date   : 2025-10-05 00:51 UTC (22 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | killingtime74 wrote:
       | What is Gleam? No obvious hits when googling.
        
         | conroy wrote:
         | https://gleam.run/ - Gleam is a friendly language for building
         | type-safe systems that scale!
        
           | fzeindl wrote:
           | What is the benefit over Elixir?
        
             | jesperwe wrote:
             | Strong typing built in from the start. More approachable
             | syntax (unless you are used to Ruby).
        
               | filmor wrote:
               | Static typing. Elixir already has strong typing (no
               | implicit conversions).
        
             | isodev wrote:
             | Static type checking and Gleam can compile to JavaScript.
             | Gleam and Elixir can be mixed in the same project too, so
             | it's easy to start adding Gleam to an elixir code base or
             | use Elixir libraries in Gleam.
        
             | victorbjorklund wrote:
             | As an elixir dev I'm jealous that gleam can transpile to JS
             | as a target. Must make so interesting things possible.
        
               | oDot wrote:
               | Indeed! Please check out this project I made to basically
               | make the server an extension of the front end by having
               | it reply to client side ui messages:
               | 
               | https://github.com/weedonandscott/omnimessage
        
               | jamauro wrote:
               | For elixir, check out https://hologram.page/
        
               | victorbjorklund wrote:
               | Yea, I have seen that and its an interesting library but
               | doesnt feel as integrated as in Gleam
        
       | jsight wrote:
       | So, basically sqlc for Gleam? Sounds nice.
       | 
       | I wonder how Gleam compares to the type checking support being
       | added to Elixir?
        
         | m8s wrote:
         | The two feel very different. Elixir is introducing gradual set-
         | theoretic typing (https://hexdocs.pm/elixir/1.18.4/gradual-set-
         | theoretic-types...) while Gleam has a static Hindley-Milner
         | style type system.
         | 
         | To me, Gleam feels like if Elm's type system met Rust's syntax.
         | I really like it. But I also really liked Elixir too, I just
         | can't live without Gleam's type system anymore.
        
           | seivan wrote:
           | What are the options for GenServer, GenStage, Tasks,
           | Supervisors in gleam?
        
       | atombender wrote:
       | I'm a big fan of sqlc (which this is essentially a port of), so
       | this is great to see.
       | 
       | I think the sqlc approach is ultimately the right one for
       | integrating SQL into code because it allows queries to be
       | expressed using the full syntax of native SQL, and only needs to
       | handle the input/output glue. So you get the best of both worlds:
       | Pure SQL unencumbered by non-SQL code, and type safety. ORMs will
       | always struggle to catch up with SQL's richness and
       | expressiveness.
       | 
       | I've not used Gleam for anything yet, but this makes me more
       | excited to try it out.
        
         | ncruces wrote:
         | The problem with sqlc is dynamic queries (mostly dynamic
         | filters and ordering, which don't really affect type safety of
         | the parameter or result-set mappings).
         | 
         | Even after years, the solutions are unsatisfactory from a
         | database query planner perspective.
        
           | atombender wrote:
           | A minority of my sqlc queries use CASE expressions, but I've
           | never had an issue with them. As far as I can see, Postgres
           | is good at optimizing them away. Is that not generally the
           | case?
           | 
           | I wonder if a sufficiently smart sqlc could do the
           | optimization itself. Basically have an SQL parser that is
           | able to identify dynamic parts statically and encoding an
           | efficient representation that allows constant folding at
           | runtime.
           | 
           | Another weak point in sqlc is the absence of any
           | composability, so every query has to be self-contained. But
           | views can help you with that.
        
             | ncruces wrote:
             | PostgreSQL is probably smarter than SQLite. :)
             | 
             | Anyway, at minimum you have prepared statements where a
             | given plan may be great for one execution and terrible for
             | the next. Maybe the database re-prepares for you, and then
             | it's the same as a dynamic query.
        
               | atombender wrote:
               | Postgres is smarter about that, too! It evaluates whether
               | the prepared statement is sensitive to parameter changes,
               | and will either use a "generic" plan that's shared across
               | all invocations, or a custom one that is hardwired to the
               | values. There's a setting that controls the behaviour.
               | 
               | https://www.postgresql.org/docs/current/sql-prepare.html
               | 
               | I've not compared the actual behaviour in the codebases I
               | work on, however.
        
       | seivan wrote:
       | The best query builder I've seen is Kysely and it works because
       | of anonymous sum types. So far no other library in any other
       | language has come remotely close. You'd think Rust would have
       | that level of type safety, but alas no.
        
         | olejorgenb wrote:
         | Typescript is magic:
         | https://kysely.dev/docs/examples/select/aliases
         | const persons = await db           .selectFrom('person as p')
         | .select([             'first_name as fn',
         | 'p.last_name as ln'           ])           .execute()
        
       | johtso wrote:
       | sqlc always seemed like a really neat concept to me. I love the
       | idea of being able to write my queries as raw SQL in separate
       | files, would make everything so much more interoperable with
       | other tools. Having your queries defined as strings inside some
       | other programming language always struck me as not so awesome.
       | 
       | I'd love to see something like this for Typescript and Effect's
       | SQL integration with schemas.
        
         | denysonique wrote:
         | https://github.com/adelsz/pgtyped
        
         | LudwigNagasena wrote:
         | SQL is barely parametrizable, so, in my opinion, the DX is much
         | worse than using a query builder and not much better than
         | simply using strings.
        
           | danieldisu wrote:
           | You just have to duplicate things a bit more, but with the
           | help of LLMs building the queries is very simple and
           | extending them even easier
        
       | jiehong wrote:
       | Nice!
       | 
       | It reminds me of Jooq in Java-land. Does Parrot also try to fill-
       | in the gaps in support between databases so that the same query
       | works on all databases?
        
         | daniellionel wrote:
         | hi, author here! it does not no :) it takes the json
         | description of the sql queries generated by sqlc and converts
         | them to gleam code. but if sqlc supported that feature, parrot
         | could also take advantage of it.
        
       | stux wrote:
       | These sqlc-style libraries are a great solution to the problem of
       | "make running a query as easy as calling a function", but I've
       | always thought SQL's lack of composability is a more interesting
       | problem that I haven't seen addressed (the problems with views
       | are well documented).
        
         | boxed wrote:
         | I haven't worked with many ORMs, but Django's is imo very good
         | in tackling the lack of composability in SQL.
        
         | jitl wrote:
         | There's two different approaches to solving sql composability
         | issues:
         | 
         | 1. Compiles-to-SQL domain specific languages. This category
         | spans from ORM DSLs embedded in another programming language,
         | like Ruby/Rail's ActiveRecord/AREL or Django's ORM; to stand-
         | alone text-based languages like PRQL Pipelined Relational Query
         | Language" (https://prql-lang.org) that a compiler program
         | converts to SQL text or SQL files. The downside to the DSL
         | option is that it requires practitioners be fluent in both the
         | SQL query they want, and in the DLS language - to know how to
         | obtain the SQL query in the DSL.
         | 
         | 2. Query fragment literals in the caller programming language,
         | like sql`name = ${name}` in TypeScript (eg
         | https://github.com/gajus/slonik). These are usually thin
         | abstraction over concatenating a `{ queryText: string[],
         | queryArgs: T[] }` structure. The author only needs to be fluent
         | in SQL, and in the caller language, but do less to save you
         | from the expressive limitations of SQL itself.
         | 
         | I've found query fragment composition to be the sweet spot.
         | Easy SQL queries remain trivial to express and understand,
         | since it's Just SQL:                   sql`SELECT * FROM block
         | WHERE id = ${args.id}`
         | 
         | But you can DRY up repetition in the codebase through regular
         | function calls. Abbreviated example from Notion's client code:
         | function selectOfflinePageMetadata(args: { userId: string }) {
         | return sql`             SELECT               offline_page.id,
         | offline_page.space_id,
         | offline_page.download_status,
         | offline_page.last_downloaded_at,
         | offline_page.last_downloaded_version,
         | offline_page.last_downloaded_sync_cursor,
         | offline_page.target_sync_cursor,               CASE
         | WHEN EXISTS (                     SELECT 1 FROM offline_action
         | WHERE offline_action.impacted_page_id = offline_page.id
         | AND offline_action.origin_page_id = offline_page.id
         | AND offline_action.autosync_type = 'not_autosynced'
         | )                   THEN 1                   ELSE 0
         | END AS is_explicitly_offlined_origin,               CASE
         | WHEN EXISTS (                     SELECT 1 FROM offline_action
         | WHERE offline_action.impacted_page_id = offline_page.id
         | AND offline_action.origin_page_id = offline_page.id
         | AND offline_action.autosync_type = 'created_offline'
         | )                   THEN 1                   ELSE 0
         | END AS is_offline_created_origin,               CASE
         | WHEN EXISTS (                     SELECT 1 FROM offline_action
         | WHERE offline_action.impacted_page_id = offline_page.id
         | AND offline_action.origin_page_id = offline_page.id
         | AND offline_action.autosync_type = 'frecent'
         | )                   THEN 1                   ELSE 0
         | END AS is_autosynced_origin             FROM offline_page
         | WHERE offline_page.meta_user_id = ${args.userId}           `
         | }              function selectOfflinePageById(args: {
         | userId: string           pageId: string         }) {
         | const { userId, pageId, } = args                    return sql`
         | SELECT * FROM (${selectOfflinePageMetadata({ userId })}) WHERE
         | id = ${pageId}           `         }                  function
         | selectOfflineAutosyncedOrigins(args: {           userId: string
         | spaceId: string         }) {           const { userId, spaceId,
         | } = args                    return sql`             WITH
         | offline_page_metadata AS (
         | ${selectOfflinePageMetadata({ userId })}             )
         | SELECT offline_page_metadata.* FROM offline_page_metadata
         | WHERE space_id = ${spaceId}             AND
         | is_autosynced_origin = 1             ORDER BY
         | last_downloaded_at ASC           `         }
         | 
         | I'm not sure if it solves your "view problem", but it does a
         | pretty good job for _my_ view problem.
        
       | mdarens wrote:
       | This looks really cool, but heads up, there were no up front
       | descriptions or links I could follow to find out what Gleam is (a
       | programming language that runs on the Erlang VM).
        
       | nextaccountic wrote:
       | This is like clorinde https://github.com/halcyonnouveau/clorinde
       | but for Gleam rather than Rust
        
       ___________________________________________________________________
       (page generated 2025-10-05 23:01 UTC)