Type safe SQL builder with code generation and automatic query result data mapping

Overview

Jet

CircleCI codecov Go Report Card Documentation GitHub release Gitter

Jet is a complete solution for efficient and high performance database access, consisting of type-safe SQL builder with code generation and automatic query result data mapping.
Jet currently supports PostgreSQL, MySQL and MariaDB. Future releases will add support for additional databases.

jet
Jet is the easiest and the fastest way to write complex type-safe SQL queries as a Go code and map database query result into complex object composition. It is not an ORM.

Motivation

https://medium.com/@go.jet/jet-5f3667efa0cc

Contents

Features

  1. Auto-generated type-safe SQL Builder
  • PostgreSQL:
    • SELECT (DISTINCT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET, FOR, UNION, INTERSECT, EXCEPT, WINDOW, sub-queries)
    • INSERT (VALUES, MODEL, MODELS, QUERY, ON_CONFLICT, RETURNING),
    • UPDATE (SET, MODEL, WHERE, RETURNING),
    • DELETE (WHERE, RETURNING),
    • LOCK (IN, NOWAIT)
    • WITH
  • MySQL and MariaDB:
    • SELECT (DISTINCT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET, FOR, UNION, LOCK_IN_SHARE_MODE, WINDOW, sub-queries)
    • INSERT (VALUES, MODEL, MODELS, ON_DUPLICATE_KEY_UPDATE, query),
    • UPDATE (SET, MODEL, WHERE),
    • DELETE (WHERE, ORDER_BY, LIMIT),
    • LOCK (READ, WRITE)
    • WITH
  1. Auto-generated Data Model types - Go types mapped to database type (table, view or enum), used to store result of database queries. Can be combined to create desired query result destination.
  2. Query execution with result mapping to arbitrary destination structure.

Getting Started

Prerequisites

To install Jet package, you need to install Go and set your Go workspace first.

Go version 1.9+ is required

Installation

Use the bellow command to add jet as a dependency into go.mod project:

$ go get -u github.com/go-jet/jet/v2

Use the bellow command to add jet as a dependency into GOPATH project:

$ go get -u github.com/go-jet/jet

Install jet generator to GOPATH bin folder. This will allow generating jet files from the command line.

cd $GOPATH/src/ && GO111MODULE=off go get -u github.com/go-jet/jet/cmd/jet

Make sure GOPATH bin folder is added to the PATH environment variable.

Quick Start

For this quick start example we will use PostgreSQL sample 'dvd rental' database. Full database dump can be found in ./tests/testdata/init/postgres/dvds.sql. Schema diagram of interest for example can be found here.

Generate SQL Builder and Model files

To generate jet SQL Builder and Data Model files from postgres database, we need to call jet generator with postgres connection parameters and root destination folder path for generated files.
Assuming we are running local postgres database, with user jetuser, user password jetpass, database jetdb and schema dvds we will use this command:

jet -source=PostgreSQL -host=localhost -port=5432 -user=jetuser -password=jetpass -dbname=jetdb -schema=dvds -path=./.gen
Connecting to postgres database: host=localhost port=5432 user=jetuser password=jetpass dbname=jetdb sslmode=disable 
Retrieving schema information...
	FOUND 15 table(s), 7 view(s), 1 enum(s)
Cleaning up destination directory...
Generating table sql builder files...
Generating view sql builder files...
Generating enum sql builder files...
Generating table model files...
Generating view model files...
Generating enum model files...
Done

Procedure is similar for MySQL or MariaDB, except source should be replaced with MySql or MariaDB and schema name should be omitted (both databases doesn't have schema support).
*User has to have a permission to read information schema tables.

As command output suggest, Jet will:

  • connect to postgres database and retrieve information about the tables, views and enums of dvds schema
  • delete everything in schema destination folder - ./gen/jetdb/dvds,
  • and finally generate SQL Builder and Model files for each schema table, view and enum.

Generated files folder structure will look like this:

|-- gen                               # -path
|   `-- jetdb                         # database name
|       `-- dvds                      # schema name
|           |-- enum                  # sql builder package for enums
|           |   |-- mpaa_rating.go
|           |-- table                 # sql builder package for tables
|               |-- actor.go
|               |-- address.go
|               |-- category.go
|               ...
|           |-- view                 # sql builder package for views
|               |-- actor_info.go
|               |-- film_list.go
|               ...
|           |-- model                 # data model types for each table, view and enum
|           |   |-- actor.go
|           |   |-- address.go
|           |   |-- mpaa_rating.go
|           |   ...

Types from table, view and enum are used to write type safe SQL in Go, and model types can be combined to store results of the SQL queries.

Lets write some SQL queries in Go

First we need to import jet and generated files from previous step:

import (
	// dot import so go code would resemble as much as native SQL
	// dot import is not mandatory
	. "github.com/go-jet/jet/v2/examples/quick-start/.gen/jetdb/dvds/table"
	. "github.com/go-jet/jet/v2/postgres"

	"github.com/go-jet/jet/v2/examples/quick-start/gen/jetdb/dvds/model"
)

Lets say we want to retrieve the list of all actors that acted in films longer than 180 minutes, film language is 'English' and film category is not 'Action'.

stmt := SELECT(
    Actor.ActorID, Actor.FirstName, Actor.LastName, Actor.LastUpdate,  // or just Actor.AllColumns
    Film.AllColumns,                                                  
    Language.AllColumns,
    Category.AllColumns,
).FROM(
    Actor.
        INNER_JOIN(FilmActor, Actor.ActorID.EQ(FilmActor.ActorID)).  
        INNER_JOIN(Film, Film.FilmID.EQ(FilmActor.FilmID)).          
        INNER_JOIN(Language, Language.LanguageID.EQ(Film.LanguageID)).
        INNER_JOIN(FilmCategory, FilmCategory.FilmID.EQ(Film.FilmID)).
        INNER_JOIN(Category, Category.CategoryID.EQ(FilmCategory.CategoryID)),
).WHERE(
    Language.Name.EQ(String("English")).             
        AND(Category.Name.NOT_EQ(String("Action"))).  
        AND(Film.Length.GT(Int(180))),               
).ORDER_BY(
    Actor.ActorID.ASC(),
    Film.FilmID.ASC(),
)

Package(dot) import is used so that statement would resemble as much as possible as native SQL.
Note that every column has a type. String column Language.Name and Category.Name can be compared only with string columns and expressions. Actor.ActorID, FilmActor.ActorID, Film.Length are integer columns and can be compared only with integer columns and expressions.

How to get parametrized SQL query from statement?

query, args := stmt.Sql()

query - parametrized query
args - parameters for the query

Click to see `query` and `args`
SELECT actor.actor_id AS "actor.actor_id",
     actor.first_name AS "actor.first_name",
     actor.last_name AS "actor.last_name",
     actor.last_update AS "actor.last_update",
     film.film_id AS "film.film_id",
     film.title AS "film.title",
     film.description AS "film.description",
     film.release_year AS "film.release_year",
     film.language_id AS "film.language_id",
     film.rental_duration AS "film.rental_duration",
     film.rental_rate AS "film.rental_rate",
     film.length AS "film.length",
     film.replacement_cost AS "film.replacement_cost",
     film.rating AS "film.rating",
     film.last_update AS "film.last_update",
     film.special_features AS "film.special_features",
     film.fulltext AS "film.fulltext",
     language.language_id AS "language.language_id",
     language.name AS "language.name",
     language.last_update AS "language.last_update",
     category.category_id AS "category.category_id",
     category.name AS "category.name",
     category.last_update AS "category.last_update"
FROM dvds.actor
     INNER JOIN dvds.film_actor ON (actor.actor_id = film_actor.actor_id)
     INNER JOIN dvds.film ON (film.film_id = film_actor.film_id)
     INNER JOIN dvds.language ON (language.language_id = film.language_id)
     INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id)
     INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
WHERE ((language.name = $1) AND (category.name != $2)) AND (film.length > $3)
ORDER BY actor.actor_id ASC, film.film_id ASC;
[English Action 180]

How to get debug SQL from statement?

debugSql := stmt.DebugSql()

debugSql - query string that can be copy pasted to sql editor and executed. It's not intended to be used in production!!!

Click to see debug sql
SELECT actor.actor_id AS "actor.actor_id",
     actor.first_name AS "actor.first_name",
     actor.last_name AS "actor.last_name",
     actor.last_update AS "actor.last_update",
     film.film_id AS "film.film_id",
     film.title AS "film.title",
     film.description AS "film.description",
     film.release_year AS "film.release_year",
     film.language_id AS "film.language_id",
     film.rental_duration AS "film.rental_duration",
     film.rental_rate AS "film.rental_rate",
     film.length AS "film.length",
     film.replacement_cost AS "film.replacement_cost",
     film.rating AS "film.rating",
     film.last_update AS "film.last_update",
     film.special_features AS "film.special_features",
     film.fulltext AS "film.fulltext",
     language.language_id AS "language.language_id",
     language.name AS "language.name",
     language.last_update AS "language.last_update",
     category.category_id AS "category.category_id",
     category.name AS "category.name",
     category.last_update AS "category.last_update"
FROM dvds.actor
     INNER JOIN dvds.film_actor ON (actor.actor_id = film_actor.actor_id)
     INNER JOIN dvds.film ON (film.film_id = film_actor.film_id)
     INNER JOIN dvds.language ON (language.language_id = film.language_id)
     INNER JOIN dvds.film_category ON (film_category.film_id = film.film_id)
     INNER JOIN dvds.category ON (category.category_id = film_category.category_id)
WHERE ((language.name = 'English') AND (category.name != 'Action')) AND (film.length > 180)
ORDER BY actor.actor_id ASC, film.film_id ASC;

Execute query and store result

Well formed SQL is just a first half of the job. Lets see how can we make some sense of result set returned executing above statement. Usually this is the most complex and tedious work, but with Jet it is the easiest.

First we have to create desired structure to store query result. This is done be combining autogenerated model types or it can be done manually(see wiki for more information).

Let's say this is our desired structure:

var dest []struct {
    model.Actor
    
    Films []struct {
        model.Film
        
        Language    model.Language
        Categories  []model.Category
    }
}

Films field is a slice because one actor can act in multiple films, and because each film belongs to one language Langauge field is just a single model struct. Film can belong to multiple categories.
*There is no limitation of how big or nested destination can be.

Now lets execute a above statement on open database connection (or transaction) db and store result into dest.

err := stmt.Query(db, &dest)
handleError(err)

And thats it.

dest now contains the list of all actors(with list of films acted, where each film has information about language and list of belonging categories) that acted in films longer than 180 minutes, film language is 'English' and film category is not 'Action'.

Lets print dest as a json to see:

jsonText, _ := json.MarshalIndent(dest, "", "\t")
fmt.Println(string(jsonText))
[
	{
		"ActorID": 1,
		"FirstName": "Penelope",
		"LastName": "Guiness",
		"LastUpdate": "2013-05-26T14:47:57.62Z",
		"Films": [
			{
				"FilmID": 499,
				"Title": "King Evolution",
				"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
				"ReleaseYear": 2006,
				"LanguageID": 1,
				"RentalDuration": 3,
				"RentalRate": 4.99,
				"Length": 184,
				"ReplacementCost": 24.99,
				"Rating": "NC-17",
				"LastUpdate": "2013-05-26T14:50:58.951Z",
				"SpecialFeatures": "{Trailers,\"Deleted Scenes\",\"Behind the Scenes\"}",
				"Fulltext": "'action':5 'action-pack':4 'baloon':21 'boy':10 'chase':16 'evolut':2 'king':1 'lumberjack':13 'madman':18 'must':15 'pack':6 'tale':7",
				"Language": {
					"LanguageID": 1,
					"Name": "English             ",
					"LastUpdate": "2006-02-15T10:02:19Z"
				},
				"Categories": [
					{
						"CategoryID": 8,
						"Name": "Family",
						"LastUpdate": "2006-02-15T09:46:27Z"
					}
				]
			}
		]
	},
	{
		"ActorID": 3,
		"FirstName": "Ed",
		"LastName": "Chase",
		"LastUpdate": "2013-05-26T14:47:57.62Z",
		"Films": [
			{
				"FilmID": 996,
				"Title": "Young Language",
				"Description": "A Unbelieveable Yarn of a Boat And a Database Administrator who must Meet a Boy in The First Manned Space Station",
				"ReleaseYear": 2006,
				"LanguageID": 1,
				"RentalDuration": 6,
				"RentalRate": 0.99,
				"Length": 183,
				"ReplacementCost": 9.99,
				"Rating": "G",
				"LastUpdate": "2013-05-26T14:50:58.951Z",
				"SpecialFeatures": "{Trailers,\"Behind the Scenes\"}",
				"Fulltext": "'administr':12 'boat':8 'boy':17 'databas':11 'first':20 'languag':2 'man':21 'meet':15 'must':14 'space':22 'station':23 'unbeliev':4 'yarn':5 'young':1",
				"Language": {
					"LanguageID": 1,
					"Name": "English             ",
					"LastUpdate": "2006-02-15T10:02:19Z"
				},
				"Categories": [
					{
						"CategoryID": 6,
						"Name": "Documentary",
						"LastUpdate": "2006-02-15T09:46:27Z"
					}
				]
			}
		]
	},
	//...(125 more items)
]

What if, we also want to have list of films per category and actors per category, where films are longer than 180 minutes, film language is 'English' and film category is not 'Action'.
In that case we can reuse above statement stmt, and just change our destination:

var dest2 []struct {
    model.Category

    Films []model.Film
    Actors []model.Actor
}

err = stmt.Query(db, &dest2)
handleError(err)
Click to see `dest2` json
[
	{
		"CategoryID": 8,
		"Name": "Family",
		"LastUpdate": "2006-02-15T09:46:27Z",
		"Films": [
			{
				"FilmID": 499,
				"Title": "King Evolution",
				"Description": "A Action-Packed Tale of a Boy And a Lumberjack who must Chase a Madman in A Baloon",
				"ReleaseYear": 2006,
				"LanguageID": 1,
				"RentalDuration": 3,
				"RentalRate": 4.99,
				"Length": 184,
				"ReplacementCost": 24.99,
				"Rating": "NC-17",
				"LastUpdate": "2013-05-26T14:50:58.951Z",
				"SpecialFeatures": "{Trailers,\"Deleted Scenes\",\"Behind the Scenes\"}",
				"Fulltext": "'action':5 'action-pack':4 'baloon':21 'boy':10 'chase':16 'evolut':2 'king':1 'lumberjack':13 'madman':18 'must':15 'pack':6 'tale':7"
			},
			{
				"FilmID": 50,
				"Title": "Baked Cleopatra",
				"Description": "A Stunning Drama of a Forensic Psychologist And a Husband who must Overcome a Waitress in A Monastery",
				"ReleaseYear": 2006,
				"LanguageID": 1,
				"RentalDuration": 3,
				"RentalRate": 2.99,
				"Length": 182,
				"ReplacementCost": 20.99,
				"Rating": "G",
				"LastUpdate": "2013-05-26T14:50:58.951Z",
				"SpecialFeatures": "{Commentaries,\"Behind the Scenes\"}",
				"Fulltext": "'bake':1 'cleopatra':2 'drama':5 'forens':8 'husband':12 'monasteri':20 'must':14 'overcom':15 'psychologist':9 'stun':4 'waitress':17"
			}
		],
		"Actors": [
			{
				"ActorID": 1,
				"FirstName": "Penelope",
				"LastName": "Guiness",
				"LastUpdate": "2013-05-26T14:47:57.62Z"
			},
			{
				"ActorID": 20,
				"FirstName": "Lucille",
				"LastName": "Tracy",
				"LastUpdate": "2013-05-26T14:47:57.62Z"
			},
			{
				"ActorID": 36,
				"FirstName": "Burt",
				"LastName": "Dukakis",
				"LastUpdate": "2013-05-26T14:47:57.62Z"
			},
			{
				"ActorID": 70,
				"FirstName": "Michelle",
				"LastName": "Mcconaughey",
				"LastUpdate": "2013-05-26T14:47:57.62Z"
			},
			{
				"ActorID": 118,
				"FirstName": "Cuba",
				"LastName": "Allen",
				"LastUpdate": "2013-05-26T14:47:57.62Z"
			},
			{
				"ActorID": 187,
				"FirstName": "Renee",
				"LastName": "Ball",
				"LastUpdate": "2013-05-26T14:47:57.62Z"
			},
			{
				"ActorID": 198,
				"FirstName": "Mary",
				"LastName": "Keitel",
				"LastUpdate": "2013-05-26T14:47:57.62Z"
			}
		]
	},
    //...
]

Complete code example can be found at ./examples/quick-start/quick-start.go

This example represent probably the most common use case. Detail info about additional statements, features and use cases can be found at project Wiki page.

Benefits

What are the benefits of writing SQL in Go using Jet?
The biggest benefit is speed. Speed is improved in 3 major areas:

Speed of development

Writing SQL queries is faster and easier, because the developers have help of SQL code completion and SQL type safety directly from Go. Automatic scan to arbitrary structure removes a lot of headache and boilerplate code needed to structure database query result.

Speed of execution

While ORM libraries can introduce significant performance penalties due to number of round-trips to the database, Jet will always perform much better, because of the single database call.

Common web and database server usually are not on the same physical machine, and there is some latency between them. Latency can vary from 5ms to 50+ms. In majority of cases query executed on database is simple query lasting no more than 1ms. In those cases web server handler execution time is directly proportional to latency between server and database. This is not such a big problem if handler calls database couple of times, but what if web server is using ORM to retrieve data from database. ORM sometimes can access the database once for every object needed. Now lets say latency is 30ms and there are 100 different objects required from the database. This handler will last 3 seconds !!!.

With Jet, handler time lost on latency between server and database is constant. Because we can write complex query and return result in one database call. Handler execution will be only proportional to the number of rows returned from database. ORM example replaced with jet will take just 30ms + 'result scan time' = 31ms (rough estimate).

With Jet you can even join the whole database and store the whole structured result in one database call. This is exactly what is being done in one of the tests: TestJoinEverything. The whole test database is joined and query result(~10,000 rows) is stored in a structured variable in less than 0.7s.

How quickly bugs are found

The most expensive bugs are the one on the production and the least expensive are those found during development. With automatically generated type safe SQL, not only queries are written faster but bugs are found sooner.
Lets return to quick start example, and take closer look at a line:

AND(Film.Length.GT(Int(180))),

Lets say someone changes column length to duration from film table. The next go build will fail at that line and the bug will be caught at compile time.

Lets say someone changes the type of length column to some non integer type. Build will also fail at the same line because integer columns and expressions can be only compered to other integer columns and expressions.

Build will also fail if someone removes length column from film table, because Film field will be omitted from SQL Builder and Model types, next time jet generator is run.

Without Jet these bugs will have to be either caught by some test or by manual testing.

Dependencies

At the moment Jet dependence only of:

  • github.com/lib/pq (Used by jet generator to read information about database schema from PostgreSQL)
  • github.com/go-sql-driver/mysql (Used by jet generator to read information about database from MySQL and MariaDB)
  • github.com/google/uuid (Used in data model files and for debug purposes)

To run the tests, additional dependencies are required:

  • github.com/pkg/profile
  • github.com/stretchr/testify
  • github.com/google/go-cmp

Versioning

SemVer is used for versioning. For the versions available, take a look at the releases.

License

Copyright 2019-2020 Goran Bjelanovic
Licensed under the Apache License, Version 2.0.

Issues
  • Infinite loop on types that reference eachother

    Infinite loop on types that reference eachother

    type TableOne struct {
        model.TableOnes
    
        TableTwos []TableTwo
    }
    
    type TableTwo struct {
        model.TableTwos
    
        TableOne TableOne
    }
    

    Preforming a stmt.Query with either of these structs (or anything that has either of these structs) will load infinitely. No error is returned, it was incredibly long and time consuming to find the source of the issue.

    I have tried making the types pointers to no avail. (idea being they would become nullable) This pattern is common when you have a Has(One/Many)BelongsTo relationship.

    enhancement 
    opened by CmpHDL 13
  • Unsigned integers?!

    Unsigned integers?!

    Given a database table mapped by go-jet to:

    type Host struct {
    	IDHost uint64 `sql:"primary_key"`
    	Name string
    	Addr string
    	Port uint16
    }
    

    and

    
    obj := model.Host {
        IDHost: 9980677793214571132,
        Name: "a-long-hash",
        Addr: "127.0.0.1",
        Port: 8080,
    }
    
    

    I should be able to do this

    
    	stm := Host.INSERT(Host.AllColumns).
    		MODEL(obj).
    		ON_DUPLICATE_KEY_UPDATE(
    			Host.IDHost.SET(UInt64(obj.IDHost)),
    			Host.Name.SET(String(obj.Name)),
    			Host.Addr.SET(String(obj.Addr)),
    			Host.Port.SET(UInt16(obj.Port)),
    			)
    

    however jet-go has no UIntXX IntegerExpression. What am I missing?

    opened by ghost 10
  • Custom Go-lang types for DB models (Monetary values)

    Custom Go-lang types for DB models (Monetary values)

    We are currently storing monetary values as type Numeric in our PostgreSQL database and we perform all types of numeric operations. Because of this, the default golang Float64 datatype does not work for our needs as we cannot work with numerical approximations.

    Is there any way to customize the model currently?

    opened by ayushajain 9
  •  enable jet.Windows passed in RawArgs to Raw expressions?

    enable jet.Windows passed in RawArgs to Raw expressions?

    Hello I'm using some of the Raw Expressions to use custom functions of postgres that aren't mapped in gojet yet, such as percentile_disc and within group, I'm running into limitation of using the Raw format, where I have within group in raw, but it needs to contain a standard ORDER BY clause inside it, there's no way for me to substitute the jet.ORDER_BY window into the Raw string via RawArgs, could that be enabled as a feature?

    would look something like:

    args := jet.RawArgs {
       	    "PERCENTILE": percentile, 
       	    "ORDER_BY": jet.ORDER_BY(table.MyTable.MyCol1.DIV(table.MyTable.MyCol2))}
    projection := jet.CAST(jet.CEIL(jet.RawFloat("percentile_disc(0.:PERCENTILE) WITHIN GROUP (:ORDER_BY)", args))).AS_BIGINT().AS("MyProjectedColumn")  
    

    Otherwise, have to resort to hardcoding the full raw snippet including the ORDER BY clause and its table/column references, which defeats the purpose of using the lib. Is it possible to achieve this any other ways?

    missing feature 
    opened by sreuland 7
  • Add RawP expression

    Add RawP expression

    Fixes https://github.com/go-jet/jet/issues/65 It takes a similar approach as fmt.Sprintf, let me know if you like it.

    The only thing is that you can't set parents when using parameters, any idea on how to fix that? Maybe we could add a Parent helper that adds the parents to a given expression.

    opened by Sytten 7
  • Support for conflict clause and with queries

    Support for conflict clause and with queries

    Jet is awesome, but features are a bit limiting for my use cases. Any plan/timeline to add the following features (for postgres, I assume MySQL has an equivalent):

    1. Conflict clause such as:
      INSERT INTO table (col) VALUES (val) ON CONFLICT DO NOTHING
      
    2. WITH Queries (CTE) such as:
      WITH alias AS (
          SELECT x FROM y
      )
      SELECT * FROM z
      WHERE a IN (SELECT a FROM alias)
      

    Thanks for the great work!

    enhancement 
    opened by 0xjac 7
  • Escape reserved words used as identifier in generated sql

    Escape reserved words used as identifier in generated sql

    Hi there!

    I love this idea, I'm building a test app with this library and I found a problem. I'm using a "user" table on PostgreSQL to store my users. This table is on a schema (image_gallery), and if I run:

     statement := SELECT(Picture.AllColumns).
    		FROM(
    			Picture.INNER_JOIN(User, Picture.UserID.EQ(User.UserID)),
    		)
    

    the query fails to execute. The problem is that schema is not being chained to the fields, so the query looks like this:

    SELECT picture.picture_id AS "picture.picture_id",
         picture.user_id AS "picture.user_id",
         picture.image_id AS "picture.image_id",
         picture.title AS "picture.title",
         picture.description AS "picture.description",
         picture.created AS "picture.created",
         picture.num_likes AS "picture.num_likes",
         picture.num_comments AS "picture.num_comments"
    FROM image_gallery.picture
         INNER JOIN image_gallery.user ON (picture.user_id = user.user_id); 
    

    PostgreSQL complains about a wrong syntax on the "user.user_id". I guess "user" is some reserved word. I see that the solution is to scape the table names always like this:

    SELECT "picture".picture_id AS "picture.picture_id",
           "picture".user_id AS "picture.user_id",
           "picture".image_id AS "picture.image_id",
           "picture".title AS "picture.title",
           "picture".description AS "picture.description",
           "picture".created AS "picture.created",
           "picture".num_likes AS "picture.num_likes",
           "picture".num_comments AS "picture.num_comments"
    FROM image_gallery."picture"
             INNER JOIN image_gallery."user" ON ("picture".user_id = "user".user_id);
    

    I think it is an easy solution and will help a lot to avoid collision with reserved words, don't you think? I think I can contribute to the project so if you see this solution as correct (I think for Mysql and MariaDB we can use ` scaping char) I can check if I can contribute to the project.

    Cheers!

    bug 
    opened by jopicornell 7
  • Is the where statement reusable? :tea:

    Is the where statement reusable? :tea:

    Hello, now there is a need for paging, you need to find out the data and total number of the corresponding page, and whether the conditional statement can be reused, example:

    queryStmt := SELECT(User.AllColumns, UserInvite.AllColumns).
    	FROM(UserInvite.INNER_JOIN(User, User.UserID.EQ(UserInvite.UserID))).
    	WHERE(UserInvite.InviteUserID.EQ(Int64(9527)))
    
    countStmt := SELECT(COUNT(User.UserID)).
            FROM(UserInvite.INNER_JOIN(User, User.UserID.EQ(UserInvite.UserID))).
    	WHERE(UserInvite.InviteUserID.EQ(Int64(9527)))
    
    opened by sswanv 6
  • use with github.com/volatiletech/null/v9 panics

    use with github.com/volatiletech/null/v9 panics

    Describe the bug A clear and concise description of what the bug is.

    if model has null.String fields INSERT panics with panic: jet: null.String type can not be used as SQL query parameter

    at internal/jet/sql_builder.go:238 due to

    
    default:
    		if strBindValue, ok := bindVal.(toStringInterface); ok {
    			return stringQuote(strBindValue.String())
    		}
    		panic(fmt.Sprintf("jet: %s type can not be used as SQL query parameter", reflect.TypeOf(value).String()))
    	}
    

    Environment (please complete the following information):

    • OS :Linux
    • Database: postgres
    • Database driver: pgx
    • Jet version: 2.7.1

    Code snippet

    import (
    	"time"
    
    	"github.com/volatiletech/null/v9"
    )
    
    type OrgUser struct {
    	ID           int64 `sql:"primary_key"`
    	Name         string
    	IsEmployee   *bool
    	Salutation   null.String
    	FirstName    null.String
    	LastName     null.String
    	Telephone    null.String
    	Mobile       null.String
    }
    stmt := table.OrgUser.INSERT(table.OrgUser.AllColumns).
    		MODEL(user)
    result, err := stmt.ExecContext(ctx, db)
    
    

    Expected behavior null.String implements scanner and valuer interfaces. insert should work

    bug 
    opened by msonawane 6
  • Feature Request: More qrm error constants

    Feature Request: More qrm error constants

    We have a qrm.ErrNoRows, perhaps we should have more of these common errors as constants, like violating constraints. qrm.ErrDuplicate for duplicate key value violates unique constraint, as an example.

    If anyone runs into or can think of any more that are common usecase, add them to this thread! If theres an existing way to accomplish this, let me know!

    opened by CmpHDL 6
  • Generating a Go structure in the plural

    Generating a Go structure in the plural

    I make a users table in the database, run a generator, it generates a model for me type Users struct {}, I would like type User struct {}

    At the same time, leave users in the table package

    If the number was not pure, it would allow, for example, to make beautiful documentation in GraphGQL, and in general it seems struct is about 1 object.

    Let's say sqlc is done exactly as I describe

    opened by FlymeDllVa 6
  • Add postgres JSON cast type

    Add postgres JSON cast type

    Is your feature request related to a problem? Please describe. We are using a lot of json fields in our postgres database. And it's hard to do the UPSERT with json column type. Currect solution I found is double casting the json string.

      insertStm := table.INSERT(table.AllColumns).MODEL(table).
      ON_CONFLICT(table.SomeColumn).DO_UPDATE(
      postgres.SET(
        table.JsonColumn.SET(postgres.StringExp(postgres.CAST(postgres.String(*data)).AS("json"))),
        ),
    

    Describe the solution you'd like Add postgres.Json() func that would return StringExpression.

    missing feature 
    opened by wexder 2
  • COALESCE missing in MySQL

    COALESCE missing in MySQL

    Describe the bug The postgres dialect exports COALESCE in postgres/functions.go, but the MySQL dialect lacks it. It's a pretty ubiquitous function, so I believe this is probably a bug.

    Code snippet

    q := Film.SELECT(
        Film.Title,
        IntExp(COALESCE(Film.Length, Int(0))).AS("length_not_null")
    )
    

    Expected behavior I expect COALESCE to be available the MySQL dialect.

    As a minor feature request, I think it'd be pretty nice to have the functions listed somewhere on the wiki. Some of them are already on the "Expressions" page, and to be fair a lot of them are just the exact same keywords as in SQL, but when you're new to the library it really helps to just know for sure that they do exist (and especially so when it comes to Jet-specific variants like SUMf() - it shows up in examples, but again, a reference would be nice).

    Having typed this out I now realize that in the time it took I could just have submitted a PR instead. :/

    bug 
    opened by kblomster 2
  • Support JSON tags

    Support JSON tags

    Is your feature request related to a problem? Please describe. The JSON we send back needs to be lower-case. We don't want to add it to the generated model structs because if we re-generate, then we will lose the tags.

    Describe the solution you'd like Ability to add JSON tags in a maintenance-free manner if we re-generate the schema

    missing feature 
    opened by jaupe 1
  • Bump github.com/google/uuid from 1.1.1 to 1.3.0

    Bump github.com/google/uuid from 1.1.1 to 1.3.0

    Bumps github.com/google/uuid from 1.1.1 to 1.3.0.

    Release notes

    Sourced from github.com/google/uuid's releases.

    Add randomness pool mode for V4 UUID and NullUUID for SQL Scanners.

    From Andrey Pechkurov:

    Adds an optional randomness pool mode for Random (Version 4) UUID generation. The pool contains random bytes read from the random number generator on demand in batches. Enabling the pool may improve the UUID generation throughput significantly.

    Since the pool is stored on the Go heap, this feature may be a bad fit for security sensitive applications. That's why it's implemented as an opt-in feature.

    From Samuel Roth:

    Added support for NullUUID

    A NullUUID can be marked not valid (i.e., null) for use with JSON and the database/sql/driver.Scanner interfaces.

    Add NewString()

    This release introduces the NewString() function which is the equivalent of uuid.New().String().

    Syntactic cleanup

    There are no code changes. A missing period was add to a godoc comment and the linter was told to not complain that the results of hash.Write() are ignored (the function cannot fail)

    Further error optimizations

    Do not allocate memory for errors (it is only one word)

    Optimize error reporting

    Optimize length of time it takes to discover an input is bad by no longer using fmt.Errorf, which is quite slow. It now uses a custom error type that formats the string when the Error method is called rather than when generating the error.

    Minor fixes

    This release contains

    • Allow concurrent, re-creatable usage #44 by trabetti was merged on Apr 16, 2019
    • Fix race in NewUUID() #64 by AstolfoKawaii was merged on Jul 2, 2020
    • Minor formatting and typo fixes
    Commits
    • 44b5fee Minor cleanup.
    • e28eb7b Remove TestConformance from null_test.go. It was useful to demonstrate
    • ae25fc6 feat(uuid): Added support for NullUUID (#76)
    • 655bf50 Add randomness pool mode for V4 UUID (#80)
    • 512b657 feat: add public matcher function for custom error type invalidLengthError (#78)
    • bfb86fa Add NewString Func (#73)
    • b5d0d36 hash.go hash error covered and linter error fixed (#71)
    • 9377784 Update sql.go (#72)
    • 85223fa Reduce custom error allocation (#70)
    • edef28d Use a custom error type for invalid lengths, replacing fmt.Errorf (#69)
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies 
    opened by dependabot[bot] 1
  • Bump github.com/jackc/pgconn from 1.12.0 to 1.12.1

    Bump github.com/jackc/pgconn from 1.12.0 to 1.12.1

    Bumps github.com/jackc/pgconn from 1.12.0 to 1.12.1.

    Changelog

    Sourced from github.com/jackc/pgconn's changelog.

    1.12.1 (May 7, 2022)

    • Fix: setting krbspn and krbsrvname in connection string (sireax)
    • Add support for Unix sockets on Windows (Eno Compton)
    • Stop ignoring ErrorResponse during SCRAM auth (Rafi Shamim)
    Commits
    • 831fc21 Release v1.12.1
    • 1d39831 Stop ignoring ErrorResponse during SCRAM auth
    • 0135721 Add support for Unix sockets on Windows
    • 84e8238 Fix: setting krbspn and krbsrvname did'n work
    • See full diff in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies 
    opened by dependabot[bot] 1
  • Bump github.com/jackc/pgx/v4 from 4.16.0 to 4.16.1

    Bump github.com/jackc/pgx/v4 from 4.16.0 to 4.16.1

    Bumps github.com/jackc/pgx/v4 from 4.16.0 to 4.16.1.

    Changelog

    Sourced from github.com/jackc/pgx/v4's changelog.

    4.16.1 (May 7, 2022)

    • Upgrade pgconn to v1.12.1
    • Fix explicitly prepared statements with describe statement cache mode
    Commits
    • 8b9b405 Release v4.16.1
    • 644bd73 Upgrade to pgconn v1.12.1
    • 7ceeea6 Fix explicitly prepared statements with describe statement cache mode
    • c6335a3 Add link to github.com/vgarvardt/pgx-google-uuid
    • See full diff in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies 
    opened by dependabot[bot] 1
Releases(v2.8.0)
  • v2.8.0(May 17, 2022)

    [New] CockroachDB support [New] Table prefix and suffix for multi-tenant environment (issue) [New] EXTRACT time/date support [Bug] Ignore tables, views and enums does not work if dsn is present (pr) [Bug] Statement Query and Exec methods can not be used with sql.Conn (issue) [Bug] DebugSQL panics with Valuer types (issue)

    Source code(tar.gz)
    Source code(zip)
  • v2.7.1(Feb 14, 2022)

  • v2.7.0(Jan 20, 2022)

    • [New] [Posgres] [Sqlite] UPDATE statement FROM clause support (issue, wiki)
    • [New] [Postgres] [MySQL] DELETE statement USING clause support (issue, wiki)
    • [New] [QRM] Prevent recursive scan if destination contains circular dependency (issue)
    • [New] WITH RECURSIVE statement support (wiki)
    • [New] BETWEEN operator support (wiki)
    • [New] Aggregate functions DISTINCT operator support (issue)
    • [New] [Postgres] DISTINCT ON clause support (wiki)
    • [New] Ability to update alias of all sub-query(or CTE) projections (wiki)
    • [New] Ability to exclude list of columns from sub-query(or CTE) projections (wiki)
    • [New] Query logger function with additional execution details (issue, wiki)
    • [New] [Postgres] Order set aggregate functions support (issue)
    • [New] Generator flags to ignore tables, views and enums. (issue, wiki)
    • [New] Automatic type cast for sized integer literals (issue)
    • [Fix] Generator no longer supports MySQL 5.7 (issue)
    Source code(tar.gz)
    Source code(zip)
  • v2.6.0(Oct 25, 2021)

    • [New] Generator and SQL Builder support for SQLite databases.
    • [New] Generator customization support (wiki).
    • [New] Ability to exclude columns from ColumnList (wiki)
    • [New] Generator can now connect to the database using DSN (wiki)
    • [Fix] QueryContext panic when the scanned value is nil and the destination is a slice of primitive (issue91)
    • [Fix] Special characters are not working in postgres password for generator (issue95), (issue34),
    Source code(tar.gz)
    Source code(zip)
  • v2.5.0(May 21, 2021)

    • LATERAL queries support (wiki)
    • RawStatement support (wiki)
    • Expose statement Rows method (wiki)
    • Allow Raw helper to accept named arguments (wiki)
    • Lossless decimal types conversion (wiki)
    • Implicit CROSS JOIN support (wiki)
    • SQL builder schema rename support. (wiki)
    • [Bug][MySQL] UPDATE statement does not escape reserved word when MODEL is used (issue)
    • Allow Bytea literal constructor to accept byte array as well as string
    • New literal constructor functions: (wiki)
      • for each of the integer types - Int32(), UInt32(), Int64, UInt64(), etc..
      • for uuid literal - UUID()
      • for exact decimal literal - Decimal()
    Source code(tar.gz)
    Source code(zip)
  • v2.4.0(Jun 28, 2020)

    Go modules support For new download procedure take a look at Installation. Go mod requires for projects with major version 2 or higher, to include major version at the end of the module paths. This means for go.mod projects, depending of go-jet, will have to update there import paths. This can be achieved with the following command:

    find . -type f \
        -name '*.go' \
        -exec sed -i -e 's,github.com/go-jet/jet,github.com/go-jet/jet/v2,g' {} \;
    

    For GOPATH projects import path remains the same.

    Source code(tar.gz)
    Source code(zip)
  • v2.3.0(Jun 3, 2020)

    • Support for WITH statements and Common Table Expressions(CTE). Sample usage.
    • [postgres] Support for ON CONFLICT clause (sample)
    • [mysql] Support for ON DUPLICATE KEY UDPATE clause (sample)
    • New type-safe SET clause for UPDATE statement (sample)
    • Generated files idempotency (timestamp removed from generated files) (issue)
    • Automatic query logging (sample)
    Source code(tar.gz)
    Source code(zip)
  • v2.2.0(Feb 17, 2020)

    • Interval type support. Sample usage can be seen here.
    • Datetime arithmetic with interval types (ADD/SUB).
    • Dynamic projection list support. Sample usage.
    • [bug] Escape reserved words used as identifiers in generated SQL(issue).
    • [bug] Fix crash on generating enum SQL Builder files when database enum contains numeric values(issue).
    Source code(tar.gz)
    Source code(zip)
  • v2.1.2(Oct 18, 2019)

    • [bug] Incorrect query result mapping into complex destination when UUID is used as primary key(issue).
    • [bug] Query should return error when scanning into struct destination and query result set is empty. Query and QueryContext will now return qrm.ErrNoRows(issue).
    Source code(tar.gz)
    Source code(zip)
  • v2.1.1(Oct 4, 2019)

  • v2.1.0(Sep 30, 2019)

    • Window function support. Sample usage can be seen here.
    • Database views support
      • Jet generator now generates view sqlbuilder and model types.
      • sqlBuilder types are generated in view folder and will behave the same as table sqlbuilder types.
      • model types are generated in model folder. To use view model type as grouping destination primary_key tag has to be specified manually. More information here.
    • Jet internal types are now aliased. This allows passing of manually constructed expression slices as variadic argument of some operators and functions.
    • [API break] ColumnList is aliased and reverted to be defined as slice of Columns. This will allow conditionally constructing the list of columns, by appending to a slice, before the statements. To fix build, just replace parentheses with braces. ColumnList(.....) -> ColumnList{......}
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Aug 20, 2019)

    Changes:

    1. MySQL and MariaDB support:
      • SELECT (DISTINCT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET, FOR, UNION, LOCK_IN_SHARE_MODE, sub-queries)
      • INSERT (VALUES, query),
      • UPDATE (SET, WHERE),
      • DELETE (WHERE, ORDER_BY, LIMIT),
      • LOCK (READ, WRITE)
    2. PostgreSQL import part has changed: github.com/go-jet/jet -> github.com/go-jet/jet/postgres
    3. Query execution mapping will also try to convert result set object to destination type if such a conversion is allowed.
    4. For every time type there is now a constructor method that accepts time.Time (DateT, TimeT, TimestampT, TimezT...)
    5. Some methods and types are changed or replaced:
      • Sql() and DebuSql does not return error as a last parameter. If there is an error in statement this methods will panic.
      • RAW -> Raw
      • SIMILAR_TO -> REGEXP_LIKE
      • NOT_SIMILAR_TO -> NOT_REGEXP_LIKE
      • ColumnList{column1, ...} -> ColumnList(column1, ...)
    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Jul 23, 2019)

    • Auto-generated type-safe SQL Builder
      • Types - boolean, integers(smallint, integer, bigint), floats(real, numeric, decimal, double precision), strings(text, character, character varying), date, time(z), timestamp(z) and enums.
      • Statements:
        • SELECT (DISTINCT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET, FOR, UNION, INTERSECT, EXCEPT, sub-queries)
        • INSERT (VALUES, query, RETURNING),
        • UPDATE (SET, WHERE, RETURNING),
        • DELETE (WHERE, RETURNING),
        • LOCK (IN, NOWAIT)
    • Auto-generated Data Model types - Go types mapped to database type (table or enum), used to store result of database queries. Can be combined to create desired query result destination.
    • Query execution with result mapping to arbitrary destination structure.
    Source code(tar.gz)
    Source code(zip)
Database Abstraction Layer (dbal) for Go. Support SQL builder and get result easily (now only support mysql)

godbal Database Abstraction Layer (dbal) for go (now only support mysql) Motivation I wanted a DBAL that No ORM、No Reflect、Concurrency Save, support S

徐佳军 53 Jan 23, 2022
Data-builder - Data builder with golang

databuilder import "github.com/go-coldbrew/data-builder" Index Variables func Is

Coldbrew 0 Feb 5, 2022
SQL builder and query library for golang

__ _ ___ __ _ _ _ / _` |/ _ \ / _` | | | | | (_| | (_) | (_| | |_| | \__, |\___/ \__, |\__,_| |___/ |_| goqu is an expressive SQL bu

Doug Martin 1.6k Jun 24, 2022
SQL query builder for Go

GoSQL Query builder with some handy utility functions. Documentation For full documentation see the pkg.go.dev or GitBook. Examples // Open database a

Travis Harmon 23 Apr 25, 2022
Fast SQL query builder for Go

sqlf A fast SQL query builder for Go. sqlf statement builder provides a way to: Combine SQL statements from fragments of raw SQL and arguments that ma

Vlad Glushchuk 70 Jun 24, 2022
gosq is a parsing engine for a simplicity-focused, template-based SQL query builder for Go.

gosq is a parsing engine for a simplicity-focused, template-based SQL query builder for Go.

Sang-gon Lee 49 Apr 26, 2022
Generate type safe Go from SQL

sqlc: A SQL Compiler sqlc generates type-safe code from SQL. Here's how it works: You write queries in SQL.

Kyle Conroy 5.8k Jun 26, 2022
Query AWS Athena and download the result as CSV.

Overview This tool can download an Athena SQL query results in CSV format. Installation Using Homebrew: $ brew tap flowerinthenight/tap $ brew install

null 5 Nov 11, 2021
Go database query builder library for PostgreSQL

buildsqlx Go Database query builder library Installation Selects, Ordering, Limit & Offset GroupBy / Having Where, AndWhere, OrWhere clauses WhereIn /

Arthur Kushman 63 Jun 12, 2022
Simple query builder for MongoDB

?? greenleaf - simple, type safe and easy to use query builder for MongoDB Installation To install use: go get github.com/slavabobik/greenleaf Quick

Slava 72 Jun 22, 2022
Squat is an application that provides simple SQL data generation functionality.

Squat Squat is an application that provides simple SQL data generation functionality. It generates synthetic SQL data based on the table definition, t

Mateusz Urbanek 7 Mar 9, 2022
golang orm and sql builder

gosql gosql is a easy ORM library for Golang. Style: var userList []UserModel err := db.FetchAll(&userList, gosql.Columns("id","name"), gosql.

RushTeam 161 Jun 24, 2022
a golang library for sql builder

Gendry gendry is a Go library that helps you operate database. Based on go-sql-driver/mysql, it provides a series of simple but useful tools to prepar

DiDi 1.4k Jun 22, 2022
An easy-use SQL builder.

EQL An easy-use SQL builder. Design We are not English native speaker, so we use Chinese to write the design documents. We plan to translate them to E

Go微服务 25 Jun 17, 2022
A Go (golang) package that enhances the standard database/sql package by providing powerful data retrieval methods as well as DB-agnostic query building capabilities.

ozzo-dbx Summary Description Requirements Installation Supported Databases Getting Started Connecting to Database Executing Queries Binding Parameters

Ozzo Framework 551 Jun 3, 2022
Command line tool to generate idiomatic Go code for SQL databases supporting PostgreSQL, MySQL, SQLite, Oracle, and Microsoft SQL Server

About xo xo is a command-line tool to generate Go code based on a database schema or a custom query. xo works by using database metadata and SQL intro

XO 3.1k Jul 1, 2022
Go fearless SQL. Sqlvet performs static analysis on raw SQL queries in your Go code base.

Sqlvet Sqlvet performs static analysis on raw SQL queries in your Go code base to surface potential runtime errors at build time. Feature highlights:

QP Hou 446 Jun 19, 2022
Fluent SQL generation for golang

sqrl - fat-free version of squirrel - fluent SQL generator for Go Non thread safe fork of squirrel. The same handy fluffy helper, but with extra lette

Ivan Kirichenko 243 Jun 30, 2022
Fluent SQL generation for golang

Squirrel is "complete". Bug fixes will still be merged (slowly). Bug reports are welcome, but I will not necessarily respond to them. If another fork

null 4.9k Jun 25, 2022