[HN Gopher] How We Went All In on sqlc/pgx for Postgres and Go
       ___________________________________________________________________
        
       How We Went All In on sqlc/pgx for Postgres and Go
        
       Author : conroy
       Score  : 104 points
       Date   : 2021-09-08 19:51 UTC (3 hours ago)
        
 (HTM) web link (brandur.org)
 (TXT) w3m dump (brandur.org)
        
       | nicoburns wrote:
       | I'm still waiting for a compile-to-sql language in the vein of
       | coffeescript or typescript. It seems like there is so much that
       | could be improved with some very simple syntax sugar: variables,
       | expression fragments and even real basics like trailing commas.
        
       | Sytten wrote:
       | As an alternative I suggest people to look at
       | https://github.com/go-jet/jet. I had a good experience working
       | with it and the author is quite responsive.
       | 
       | It really feels like writing SQL but you are writing typesafe
       | golang which I really enjoy doing.
        
       | scrubs wrote:
       | Talk about JIT on target article ... I'll play with this at the
       | office to tomorrow. I've got plans for it
        
       | sa46 wrote:
       | I agree whole-heartedly that writing SQL feels right. Broadly
       | speaking, you can take the following approaches to mapping
       | database queries to Go code:
       | 
       | - Write SQL queries, parse the SQL, generate Go from the queries
       | (sqlc, pggen).
       | 
       | - Write SQL schema files, parse the SQL schema, generate active
       | records based on the tables (gorm)
       | 
       | - Write Go structs, generate SQL schema from the structs, and use
       | a custom query DSL (proteus).
       | 
       | - Write custom query language (YAML or other), generate SQL
       | schema, queries, and Go query interface (xo).
       | 
       | - Skip generated code and use a non-type-safe query builder
       | (squirrel, goqu).
       | 
       | I prefer writing SQL queries so that app logic doesn't depend on
       | the the database table structure.
       | 
       | I started off with sqlc but ran into limitations with more
       | complex queries. It's quite difficult to infer what a SQL query
       | will output even with a proper parse tree. sqlc also didn't work
       | with generated code.
       | 
       | I wrote pggen with the idea that you can just execute the query
       | and have Postgres tell you what the output types and names will
       | be. Here's the original design doc [1] that outlines the
       | motivations. By comparison, sqlc starts from the parse tree, and
       | has the complex task of computing the control flow graph for
       | nullability and type outputs.
       | 
       | [1]:
       | https://docs.google.com/document/d/1NvVKD6cyXvJLWUfqFYad76CW...
       | 
       | Disclaimer: author of pggen (https://github.com/jschaf/pggen),
       | inspired by sqlc
        
       | sam0x17 wrote:
       | > However, without generics, Go's type system can only offer so
       | much
       | 
       | I was reading the whole article waiting to see this line, and the
       | article did not disappoint. This is still the main reason I will
       | stick with Rust or Crystal (depending on the use-case) and avoid
       | Go if I can for the foreseeable future. Generics are just a must
       | these days for non-trivial software projects. It's a shame too
       | because Go has so much promise in other respects.
        
         | hactually wrote:
         | They're really not a `must`. What a silly comment - Docker and
         | Kubernetes and substantial parts of Google wouldn't be classed
         | as trivial.
         | 
         | For the thousands of devs shipping non-trivial code, keep
         | going!
        
           | wvenable wrote:
           | There's plenty of non-trival code written in C as well.
           | That's not a good argument for the benefit of a programming
           | language. You can work around any limitation with enough work
           | -- this article is a perfect example. It's an ugly solution
           | to a simple problem but it works.
        
       | yevpats wrote:
       | Java is awful and slow.
       | 
       | Invent Go.
       | 
       | Waiting for generics....
       | 
       | 5 year later
       | 
       | Go looks like Java. Back to square one :)
        
       | Andys wrote:
       | sqlc is a great code generator that seems to work miracles.
       | 
       | It uses the official postgres parser to know all the types of
       | your tables and queries, and can generate perfect Go structs from
       | this.
       | 
       | It even knows your table and field types just from reading your
       | migrations, tracking changes perfectly, no need to even pg_dump a
       | schema definition.
       | 
       | I also found it works fine with cockroachdb.
        
         | evandwight wrote:
         | How are migrations defined?
         | 
         | I ask because I'm still trying to find a good solution for my
         | project.
        
           | Andys wrote:
           | it supports the migration files of several different Go
           | migrator modules. Usually just a series of text .sql files
           | with up/down sections.
        
       | koeng wrote:
       | As an aside - for anyone working with databases in Go, check out
       | https://pkg.go.dev/modernc.org/sqlite
       | 
       | It allows drop in replacement of SQLite that is in pure Go - no
       | CGO or anything required for compilation, while still having
       | everything implemented from SQLite.
       | 
       | Insert speed is a bit lacking (about ~6x slower in my experience
       | compared to the CGO sqlite3 package), but its good enough for me.
        
         | nickcw wrote:
         | I hadn't realized it was now ready for general use...
         | SQLite 2020-08-14 13:23:32 fca8dc8b578f215a969cd899336378966156
         | 154710873e68b3d9ac5881b0ff3f         0 errors out of 928271
         | tests on 3900x Linux 64-bit little-endian
         | 
         | Whee, I shall have to give it a go - thanks for the heads-up
         | :-)
        
           | pingu2 wrote:
           | Swswsswwzzwwwwwwwwxw
        
         | justinsaccount wrote:
         | It's not really pure go, it's transpiled using
         | https://gitlab.com/cznic/ccgo
         | 
         | Just about all the code looks like this:                 //
         | Call this routine to record the fact that an OOM (out-of-
         | memory) error       // has happened.  This routine will set
         | db->mallocFailed, and also       // temporarily disable the
         | lookaside memory allocator and interrupt       // any running
         | VDBEs.       func Xsqlite3OomFault(tls *libc.TLS, db uintptr) {
         | /* sqlite3.c:28548:21: */        if
         | (int32((*Sqlite3)(unsafe.Pointer(db)).FmallocFailed) == 0) &&
         | (int32((*Sqlite3)(unsafe.Pointer(db)).FbBenignMalloc) == 0) {
         | (*Sqlite3)(unsafe.Pointer(db)).FmallocFailed = U8(1)         if
         | (*Sqlite3)(unsafe.Pointer(db)).FnVdbeExec > 0 {
         | libc.AtomicStoreNInt32((db + 400 /* &.u1 */ /* &.isInterrupted
         | */), int32(1), 0)         }
         | (*Sqlite3)(unsafe.Pointer(db)).Flookaside.FbDisable++
         | (*Sqlite3)(unsafe.Pointer(db)).Flookaside.Fsz = U16(0)
         | if (*Sqlite3)(unsafe.Pointer(db)).FpParse != 0 {          (*Par
         | se)(unsafe.Pointer((*Sqlite3)(unsafe.Pointer(db)).FpParse)).Frc
         | = SQLITE_NOMEM         }        }       }
        
           | psanford wrote:
           | Being translated means it doesn't have the normal cgo calling
           | overhead. It also means you can cross compile it for every
           | platform that the Go toolchain supports without any external
           | compilers.
        
             | justinsaccount wrote:
             | Nope, note their readme says:
             | 
             | These combinations of GOOS and GOARCH are currently
             | supported
             | 
             | darwin amd64, darwin arm64, freebsd amd64, linux 386, linux
             | amd64, linux arm, linux arm64, windows amd64
             | 
             | and if you look at their source tree
             | https://gitlab.com/cznic/sqlite/-/tree/master/lib you can
             | see they have
             | 
             | sqlite_darwin_amd64.go sqlite_darwin_arm64.go
             | sqlite_freebsd_amd64.go sqlite_linux_386.go
             | sqlite_linux_amd64.go sqlite_linux_arm.go
             | sqlite_linux_arm64.go sqlite_linux_s390x.go
             | sqlite_windows_386.go sqlite_windows_amd64.go
        
             | throwaway894345 wrote:
             | OP mentioned that the pure-Go version is ~6 times slower,
             | so the cgo calling overhead is clearly made up for by C.
             | Also, I've heard that sqlite is the rare piece of C
             | software that is actually bulletproof, so I don't think the
             | pure-Go version can make the usual boasts about correctness
             | and security in this particular case.
             | 
             | Not needing extra external compilers is still a nice
             | proposition, however.
        
       | ramenmeal wrote:
       | We just use something like github.com/Masterminds/squirrel in
       | combination with something like github.com/fatih/structs (it's
       | archived, but it's easy code to write) to help with sql query
       | generation, and use github.com/jmoiron/sqlx for easier scanning.
       | I guess it's a little trickier when trying to use postgres
       | specific commands, but we haven't run into many problems.
        
       | jakearmitage wrote:
       | How does it deal with mapping relationships? For example, a Many-
       | to-Many between Posts and Tags, or a Many-to-One like Posts and
       | Comments?
        
       | Something1234 wrote:
       | His codeblocks have broken horizontal scroll on mobile.
       | 
       | Other than that I like it a lot. I built some codegen stuff in
       | the past for test automation and it's really quite nice because
       | it reduces a lot of user errors.
        
       | jonbodner wrote:
       | If you are looking for a way to map SQL queries to type safe Go
       | functions, take a look at my library Proteus:
       | https://github.com/jonbodner/proteus
       | 
       | Proteus generates functions at runtime, avoiding code generation.
       | Performance is identical to writing SQL mapping code yourself. I
       | spoke about its implementation at GopherCon 2017:
       | https://www.youtube.com/watch?v=hz6d7rzqJ6Q
        
       | fprog wrote:
       | From the article:
       | 
       | > I've largely covered sqlc's objective benefits and features,
       | but more subjectively, it just feels good and fast to work with.
       | Like Go itself, the tool's working for you instead of against
       | you, and giving you an easy way to get work done without
       | wrestling with the computer all day.
       | 
       | I've been meaning to write a blog post about sqlc myself, and
       | when I get to it, I'll probably quote this line. sqlc is that
       | rare tool that just "feels right". I think that feeling comes
       | from a combination of things. It's fast. It uses an idiomatic Go
       | approach (code generation, instead of e.g. reflection) to solve
       | the problem at hand, so it feels at home in the Go ecosystem. As
       | noted in the article, it allows you to check that your SQL is
       | valid at compile-time, saving you from discovering errors at
       | runtime, and eliminating the need for certain types of tests.
       | 
       | But perhaps most of all, sqlc lets you _just write SQL_. After
       | using sqlc, using a more conventional ORM almost seemed like a
       | crazy proposition. Why would someone author an ORM, painstakingly
       | creating Go functions that just map to existing SQL features?
       | Such a project is practically destined to be perpetually
       | incomplete, and if one day it is no longer maintained, migration
       | will be painful. And why add to your code a dependency on such a
       | project, when you could use a tool like sqlc that is so
       | drastically lighter, and brings nearly all the benefits?
       | 
       | sqlc embraces the idea that the right tool for talking to a
       | relational database is the one we've had all along, the one which
       | every engineer already knows: SQL. I look forward to using it in
       | more projects.
        
         | donio wrote:
         | A few years ago I had spent a year working with a Go project
         | that made heavy use of one of the (then) popular Go ORMs.
         | Learned my lesson, never again. Magic=Bad.
        
         | pstuart wrote:
         | Hopefully they'll get SQLite working on it soon and I'll be all
         | over it.
        
         | bitwize wrote:
         | > Why would someone author an ORM, painstakingly creating Go
         | functions that just map to existing SQL features?
         | 
         | The answer to this question lies in the assumption you make in
         | this statement:
         | 
         | > the one which every engineer already knows: SQL.
         | 
         | Not every engineer knows, or wants to learn, SQL. I've met very
         | competent engineers, SMEs over their particular system, who
         | were flummoxed by SQL. And many more just want to work in their
         | preferred language. I don't like ORMs either but, like, half
         | the reason why they exist is so the programmer can talk to the
         | RDBMS in Java, JavaScript, etc. and not touch SQL.
        
         | Sytten wrote:
         | Working with SQL in X (any language) usually has a poor
         | developer experience that is why ORM or query builders are
         | popular. Things like proper syntax highlight or type safety (I
         | remain to be convinced that sqlc can really check the validity
         | at compile, usually it only works in specific basic cases).
         | 
         | You just have to choose wisely your tools for sure, but most of
         | the code you write needs to be rewritten anyway every X years.
        
       | grantwu wrote:
       | I was really really excited when I saw the title because I've
       | been having a lot of difficulties with other Go SQL libraries,
       | but the caveats section gives me pause.
       | 
       | Needing to use arrays for the IN use case (see
       | https://github.com/kyleconroy/sqlc/issues/216) and the bulk
       | insert case feel like large divergences from what "idiomatic SQL"
       | looks like. It means that you have to adjust how you write your
       | queries. And that can be intimidating for new developers.
       | 
       | The conditional insert case also just doesn't look particularly
       | elegant and the SQL query is pretty large.
       | 
       | sqlc also just doesn't look like it could help with very dynamic
       | queries I need to generate - I work on a team that owns a little
       | domain-specific search engine. The conditional approach could in
       | theory with here, but it's not good for the query planner:
       | https://use-the-index-luke.com/sql/where-clause/obfuscation/...
        
         | joppy wrote:
         | Arrays are nicer for the IN case because Postgres does not
         | understand an empty list, i.e "WHERE foo IN ()" will error.
         | Using the "WHERE foo = ANY(array)" works as expected with empty
         | arrays.
        
       | conroy wrote:
       | Author of sqlc here. Just wanted to say thanks to everyone in
       | this thread. It's been a really fun project to work on the last
       | two years. Excited to get to work on adding support for more
       | databases and programming languages.
        
       | jakoblorz wrote:
       | For a full featured "go generate(d)" ORM try https://entgo.io/
       | Seems rather similar, with the main difference being that you
       | define your schema in a specific go package, from which the ORM
       | is generated. The nice thing is that you can import this package
       | later again to reuse something like default values etc
        
       | robmccoll wrote:
       | I've used https://github.com/xo/xo, extended it with some custom
       | functions for templating, extended the templates themselves, and
       | can now generate CRUD for anything in the database, functions for
       | common select queries based on the indices that exist in the
       | database, field filtering and scanning, updates for subsets of
       | fields including some atomic operations, etc. The sky is the
       | limit honestly. It has allowed me to start with something
       | approximating a statically generated ORM and extend it with any
       | features I want as time goes on. I also write _.extra.go files
       | along side the generated_.xo.go files to extend the structs that
       | are generated with custom logic and methods to convert data into
       | response formats.
       | 
       | I like the approach of starting with the database schema and
       | generating code to reflect that. I define my schema in sql files
       | and handle database migrations using https://github.com/golang-
       | migrate/migrate.
       | 
       | If you take this approach, you can mostly avoid exposing details
       | about the SQL driver being used, and since the driver is mostly
       | used by a few templates, swapping drivers doesn't take much
       | effort.
        
       ___________________________________________________________________
       (page generated 2021-09-08 23:00 UTC)