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

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.

Comments
  • 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.

  • 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?

  • 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?

  • Use alias tag to skip destination field during scan

    Use alias tag to skip destination field during scan

    Hey y'all, I was wondering if it is possible to skip certain fields while scanning rows into a destination.

    I think there could be two potential solutions that could work together:

    type Model struct {
      ID string              `sql:"primary_key" alias:"model.id"`
      Complex *ComplexStruct `alias:"-"` // skip this one
    }
    

    Or alternatively as a method on the QRM that only scans fields tagged with alias or sql

    stmt.QueryWithTag(db, &rows, map[string]string{ "alias": "json", "sql": "sql" }) // override alias to json
    stmt.QueryWithTagOption(db, &rows, Tags.DEFAULT, TagOptions.PERMISSIVE) // default behavior
    stmt.QueryWithTagOption(db, &rows, Tags.DEFAULT, TagOptions.STRICT) // only 'alias' or 'sql'
    

    If there isn't a way currently I can take a crack at it.

  • Print Full SQL Instead of PreparedStatement

    Print Full SQL Instead of PreparedStatement

    Is your feature request related to a problem? Please describe. Currently we are using RDS Data API which has a very specific way of doing queries (Doesn't support ? character).

    We required to either have named placeholders (each prefixed by :) and typed arguments, or we just call ExecuteStatement with the fully generated and escaped SQL.

    Connecting directly via Connection String is not an option because we are using AWS Lambda.

    Source

    Describe the solution you'd like A .RawSql() Function that prints the whole SQL

  •  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?

  • 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.

  • 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!

  • 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!

  • 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)))
    
  • 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

  • Bump github.com/pkg/profile from 1.6.0 to 1.7.0

    Bump github.com/pkg/profile from 1.6.0 to 1.7.0

    Bumps github.com/pkg/profile from 1.6.0 to 1.7.0.

    Release notes

    Sourced from github.com/pkg/profile's releases.

    pkg/profile 1.7.0

    Adds support for fgprof, thanks @​yonderblue

    Commits

    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)
  • Bump github.com/stretchr/testify from 1.8.0 to 1.8.1

    Bump github.com/stretchr/testify from 1.8.0 to 1.8.1

    Bumps github.com/stretchr/testify from 1.8.0 to 1.8.1.

    Commits

    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)
  • Bump github.com/google/go-cmp from 0.5.8 to 0.5.9

    Bump github.com/google/go-cmp from 0.5.8 to 0.5.9

    Bumps github.com/google/go-cmp from 0.5.8 to 0.5.9.

    Release notes

    Sourced from github.com/google/go-cmp's releases.

    v0.5.9

    Reporter changes:

    • (#299) Adjust heuristic for line-based versus byte-based diffing
    • (#306) Use value.TypeString in PathStep.String

    Code cleanup changes:

    • (#297) Use reflect.Value.IsZero
    • (#304) Format with Go 1.19 formatter
    • (#300 )Fix typo in Result documentation
    • (#302) Pre-declare global type variables
    • (#309) Run tests on Go 1.19
    Commits

    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)
  • Bump github.com/go-sql-driver/mysql from 1.5.0 to 1.7.0

    Bump github.com/go-sql-driver/mysql from 1.5.0 to 1.7.0

    Bumps github.com/go-sql-driver/mysql from 1.5.0 to 1.7.0.

    Release notes

    Sourced from github.com/go-sql-driver/mysql's releases.

    Version 1.7

    Changes:

    • Drop support of Go 1.12 (#1211)
    • Refactoring (*textRows).readRow in a more clear way (#1230)
    • util: Reduce boundary check in escape functions. (#1316)
    • enhancement for mysqlConn handleAuthResult (#1250)

    New Features:

    • support Is comparison on MySQLError (#1210)
    • return unsigned in database type name when necessary (#1238)
    • Add API to express like a --ssl-mode=PREFERRED MySQL client (#1370)
    • Add SQLState to MySQLError (#1321)

    Bugfixes:

    • Fix parsing 0 year. (#1257)

    Version 1.6.0

    Major Release

    • Migrate the CI service from travis-ci to GitHub Actions (#1176, #1183, #1190)
    • NullTime is deprecated (#960, #1144)
    • Reduce allocations when building SET command (#1111)
    • Performance improvement for time formatting (#1118)
    • Performance improvement for time parsing (#1098, #1113)

    See CHANGELOG.md for full details

    Changelog

    Sourced from github.com/go-sql-driver/mysql's changelog.

    Version 1.7 (2022-11-29)

    Changes:

    • Drop support of Go 1.12 (#1211)
    • Refactoring (*textRows).readRow in a more clear way (#1230)
    • util: Reduce boundary check in escape functions. (#1316)
    • enhancement for mysqlConn handleAuthResult (#1250)

    New Features:

    • support Is comparison on MySQLError (#1210)
    • return unsigned in database type name when necessary (#1238)
    • Add API to express like a --ssl-mode=PREFERRED MySQL client (#1370)
    • Add SQLState to MySQLError (#1321)

    Bugfixes:

    • Fix parsing 0 year. (#1257)

    Version 1.6 (2021-04-01)

    Changes:

    • Migrate the CI service from travis-ci to GitHub Actions (#1176, #1183, #1190)
    • NullTime is deprecated (#960, #1144)
    • Reduce allocations when building SET command (#1111)
    • Performance improvement for time formatting (#1118)
    • Performance improvement for time parsing (#1098, #1113)

    New Features:

    • Implement driver.Validator interface (#1106, #1174)
    • Support returning uint64 from Valuer in ConvertValue (#1143)
    • Add json.RawMessage for converter and prepared statement (#1059)
    • Interpolate json.RawMessage as string (#1058)
    • Implements CheckNamedValue (#1090)

    Bugfixes:

    • Stop rounding times (#1121, #1172)
    • Put zero filler into the SSL handshake packet (#1066)
    • Fix checking cancelled connections back into the connection pool (#1095)
    • Fix remove last 0 byte for mysql_old_password when password is empty (#1133)

    Version 1.5 (2020-01-07)

    Changes:

    ... (truncated)

    Commits

    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)
  • Use own struct for dest

    Use own struct for dest

    Hi Is it possible to use my own struct as the dest to stmt.Query(db, &dest)? Currently as the dest is different than my domain struct, I have to copy all dest's fields to my own struct(domain struct) and this isn't performant.

    Like:

    type MyPerson []struct {
    	FirstName string 
    	LastName  string
    	Comments  []struct {
    		Title string
    		Body  string
    	}
    }
    

    Currently is:

    type dest []struct {
    	model.Person
    	Comments []struct {
    		model.Comment
    	}
    }
    
  • Bump github.com/mattn/go-sqlite3 from 1.14.8 to 1.14.16

    Bump github.com/mattn/go-sqlite3 from 1.14.8 to 1.14.16

    Bumps github.com/mattn/go-sqlite3 from 1.14.8 to 1.14.16.

    Release notes

    Sourced from github.com/mattn/go-sqlite3's releases.

    1.14.16

    What's Changed

    New Contributors

    Full Changelog: https://github.com/mattn/go-sqlite3/compare/v1.14.15...v1.14.16

    Commits
    • bce3773 Update expected test output
    • 31c7618 Update amalgamation code
    • 4b8633c Updating vtable example, "BestIndex" method (#1099)
    • 0b37084 Update README.md to include vtable feature (#1100)
    • 90900be Cross Compiling for Mac OS via musl-cross
    • be28dec Golang's linker add mingwex and mingw32 automatically,so we don't need add th...
    • 17f6553 Add support for sqlite_math_functions tag (#1059)
    • 7476442 こんにちわ is wrong Japanse. The correct word is こんにちは
    • da62659 Fix "ennviroment" (#1077)
    • 4ef63c9 Rollback on constraint failure (#1071)
    • 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)
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

Nov 17, 2022
Data-builder - Data builder with golang

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

Feb 5, 2022
SQL builder and query library for golang

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

Dec 30, 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

Dec 12, 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

Dec 23, 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.

Oct 24, 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.

Dec 31, 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

Nov 11, 2021
sqlc implements a Dynamic Query Builder for SQLC and more specifically MySQL queries.

sqlc-go-builder sqlc implements a Dynamic Query Builder for SQLC and more specifically MySQL queries. It implements a parser using vitess-go-sqlparser

May 9, 2023
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 /

Dec 23, 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

Nov 27, 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

Sep 22, 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.

Dec 22, 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

Dec 31, 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

Dec 26, 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

Dec 26, 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

Jan 8, 2023
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:

Dec 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

Dec 16, 2022