Golang ORM with focus on PostgreSQL features and performance

Overview

PostgreSQL client and ORM for Golang

Build Status PkgGoDev Documentation Chat

Maintenance mode

go-pg is in a maintenance mode and only critical issues are addressed. New development happens in Bun repo which offers similar functionality but works with PostgreSQL, MySQL, and SQLite.


Ecosystem

Features

Installation

go-pg supports 2 last Go versions and requires a Go version with modules support. So make sure to initialize a Go module:

go mod init github.com/my/repo

And then install go-pg (note v10 in the import; omitting it is a popular mistake):

go get github.com/go-pg/pg/v10

Quickstart

", u.Id, u.Name, u.Emails) } type Story struct { Id int64 Title string AuthorId int64 Author *User `pg:"rel:has-one"` } func (s Story) String() string { return fmt.Sprintf("Story<%d %s %s>", s.Id, s.Title, s.Author) } func ExampleDB_Model() { db := pg.Connect(&pg.Options{ User: "postgres", }) defer db.Close() err := createSchema(db) if err != nil { panic(err) } user1 := &User{ Name: "admin", Emails: []string{"[email protected]", "[email protected]"}, } _, err = db.Model(user1).Insert() if err != nil { panic(err) } _, err = db.Model(&User{ Name: "root", Emails: []string{"[email protected]", "[email protected]"}, }).Insert() if err != nil { panic(err) } story1 := &Story{ Title: "Cool story", AuthorId: user1.Id, } _, err = db.Model(story1).Insert() if err != nil { panic(err) } // Select user by primary key. user := &User{Id: user1.Id} err = db.Model(user).WherePK().Select() if err != nil { panic(err) } // Select all users. var users []User err = db.Model(&users).Select() if err != nil { panic(err) } // Select story and associated author in one query. story := new(Story) err = db.Model(story). Relation("Author"). Where("story.id = ?", story1.Id). Select() if err != nil { panic(err) } fmt.Println(user) fmt.Println(users) fmt.Println(story) // Output: User<1 admin [[email protected] [email protected]]> // [User<1 admin [[email protected] [email protected]]> User<2 root [[email protected] [email protected]]>] // Story<1 Cool story User<1 admin [[email protected] [email protected]]>> } // createSchema creates database schema for User and Story models. func createSchema(db *pg.DB) error { models := []interface{}{ (*User)(nil), (*Story)(nil), } for _, model := range models { err := db.Model(model).CreateTable(&orm.CreateTableOptions{ Temp: true, }) if err != nil { return err } } return nil } ">
package pg_test

import (
    "fmt"

    "github.com/go-pg/pg/v10"
    "github.com/go-pg/pg/v10/orm"
)

type User struct {
    Id     int64
    Name   string
    Emails []string
}

func (u User) String() string {
    return fmt.Sprintf("User<%d %s %v>", u.Id, u.Name, u.Emails)
}

type Story struct {
    Id       int64
    Title    string
    AuthorId int64
    Author   *User `pg:"rel:has-one"`
}

func (s Story) String() string {
    return fmt.Sprintf("Story<%d %s %s>", s.Id, s.Title, s.Author)
}

func ExampleDB_Model() {
    db := pg.Connect(&pg.Options{
        User: "postgres",
    })
    defer db.Close()

    err := createSchema(db)
    if err != nil {
        panic(err)
    }

    user1 := &User{
        Name:   "admin",
        Emails: []string{"[email protected]", "[email protected]"},
    }
    _, err = db.Model(user1).Insert()
    if err != nil {
        panic(err)
    }

    _, err = db.Model(&User{
        Name:   "root",
        Emails: []string{"[email protected]", "[email protected]"},
    }).Insert()
    if err != nil {
        panic(err)
    }

    story1 := &Story{
        Title:    "Cool story",
        AuthorId: user1.Id,
    }
    _, err = db.Model(story1).Insert()
    if err != nil {
        panic(err)
    }

    // Select user by primary key.
    user := &User{Id: user1.Id}
    err = db.Model(user).WherePK().Select()
    if err != nil {
        panic(err)
    }

    // Select all users.
    var users []User
    err = db.Model(&users).Select()
    if err != nil {
        panic(err)
    }

    // Select story and associated author in one query.
    story := new(Story)
    err = db.Model(story).
        Relation("Author").
        Where("story.id = ?", story1.Id).
        Select()
    if err != nil {
        panic(err)
    }

    fmt.Println(user)
    fmt.Println(users)
    fmt.Println(story)
    // Output: User<1 admin [[email protected] [email protected]]>
    // [User<1 admin [[email protected] [email protected]]> User<2 root [[email protected] [email protected]]>]
    // Story<1 Cool story User<1 admin [[email protected] [email protected]]>>
}

// createSchema creates database schema for User and Story models.
func createSchema(db *pg.DB) error {
    models := []interface{}{
        (*User)(nil),
        (*Story)(nil),
    }

    for _, model := range models {
        err := db.Model(model).CreateTable(&orm.CreateTableOptions{
            Temp: true,
        })
        if err != nil {
            return err
        }
    }
    return nil
}

See also

Issues
  • Add support for programmatic migrations

    Add support for programmatic migrations

    I'm using go-pg in a web framework of mine, jargo. The goal of jargo is to have the developer as little as possible about database interactions. Currently, whenever the Application launches, I call db.Model(myModel).CreateTable(IfNotExists). This does not allow for dynamic updating of the database when the model changes at next Application launch.

    It would be amazing to have an UpdateTable or AlterTable method updating existing columns. It would even be enough if it would just do an ALTER TABLE x ADD COLUMN IF NOT EXISTS to be able to add new model fields.

    opened by CrushedPixel 23
  • Issue With Relation Join With Respect to SQL Tags

    Issue With Relation Join With Respect to SQL Tags

    So with relation to https://github.com/go-pg/pg/issues/862, took your advice and tried to replicate it with your examples by creating a working example

    https://play.golang.org/p/bEA67rTlfOY

    I ran the above and I got a similar error - the structs you see above are actually from my working code but modified.

    Essentially the error is:

    // SELECT blog_place.*, "place"."place_id", "place"."name" FROM place AS "place" JOIN discovr.blog_place AS blog_place ON (blog_place."blog_blog_id") IN (1) WHERE ("place"."place_id" = blog_place."place_id")
    // panic: ERROR #42703 column blog_place.blog_blog_id does not exist
    

    I am still not understanding as to why it is appending blog_ prefix to my blog_id in my JOIN call for blog_place table..

    opened by mdere 18
  • Method for testing a connection

    Method for testing a connection

    I am trying to write a piece of code that regularly checks the database connection to test if it is still open.

    I can't find any method to do this, is there a way? If not, is there any way to be notified if the database connection is closed?

    I think something like https://golang.org/pkg/database/sql/#DB.Ping would be useful

    opened by joelclouddistrict 17
  • mellium.im/sasl: unrecognized import path

    mellium.im/sasl: unrecognized import path

    go get -u github.com/go-pg/pg
    package mellium.im/sasl: unrecognized import path "mellium.im/sasl" (https fetch: Get https://mellium.im/sasl?go-get=1: read tcp 10.10.49.76:63173->198.199.66.189:443: wsarecv: An existing connection was forcibly closed by the remote host.)
    

    This repository has been deleted Our apologies, but the repository "mellium/sasl" has been deleted.

    It now lives at https://github.com/mellium/sasl.

    opened by niski84 16
  • Connecting to Heroku

    Connecting to Heroku

    I'm curious as to why there is not a convenience method for URL Schemes? Connecting to a remote heroku database, for example, is a bit less than trivial. Would you mind making a quick example of how to structure a valid connection to a simple Hobby-Dev Postgres database? Or consider writing up a URL Scheme handler?

    I'd be happy to write up some documentation and contribute towards a nice WIKI with examples for this package in exchange :)

    opened by thinkclay 15
  • Feature Request: Add hooks (BeforeUpdate/AfterInsert, etc...)

    Feature Request: Add hooks (BeforeUpdate/AfterInsert, etc...)

    Are hooks on the roadmap? I can understand if they're omitted for performance concerns, but it would be handy if I had a standard way to perform things like expiring redis cache, updating row counts, or re-indexing elasticsearch results.

    I had looked at Notifications, but disregarded those because of the Not thread-safe. text.

    opened by ryanbyyc 15
  • Getting error `pg: pool.go:317: Conn has unread data` when using `pg.In()`

    Getting error `pg: pool.go:317: Conn has unread data` when using `pg.In()`

    following the sample on the website, I have these models defined:

    type BaseModel struct {
    	Id        string     `json:"id" pg:"cuid,pk,notnull,unique" sql:"index"`
    	CreatedAt time.Time  `json:"created_at"`
    	UpdatedAt time.Time  `json:"updated_at"`
    	DeletedAt *time.Time `json:"deleted_at" sql:"index"`
    }
    
    type User struct {
    	BaseModel
    	Firstname     *string    `json:"firstname" pg:",type:VARCHAR(64)"`
    	Lastname      *string    `json:"lastname" pg:",type:VARCHAR(64)"`
    	Projects      []*Project `json:"projects" pg:",many2many:project_users"`
    	Username      *string    `json:"username" pg:",type:VARCHAR(64)"`
    	Roles         []*Role    `json:"roles" pg:",type:VARCHAR(32)[],array"`
    }
    
    type CreateProjectInput struct {
    	Name           string    `json:"name" `
    	Users          []*string `json:"users" `
    }
    

    and whenever I try to run the following:

    func ResolveCreateProject(ctx *gin.Context, data model.CreateProjectInput) (*model.Project, error) {
    
    	var users []model.User
    
    	err := database.DB.Model(&users).
    		Where("cuid IN (?)", pg.In(data.Users)).
    		Select()
    
    	return nil, err
    }
    

    I get the following error trace:

    pg: 2020/06/28 13:42:03 pool.go:317: Conn has unread data
    reflect: call of reflect.Value.IsNil on string Value
    
    goroutine 47 [running]:
    runtime/debug.Stack(0x0, 0x0, 0x0)
            /usr/local/Cellar/go/1.13.8/libexec/src/runtime/debug/stack.go:24 +0xa1
    runtime/debug.PrintStack()
            /usr/local/Cellar/go/1.13.8/libexec/src/runtime/debug/stack.go:16 +0x22
    github.com/99designs/gqlgen/graphql.DefaultRecover(0x1e06900, 0xc0003aba70, 0x1bcb040, 0xc000238200, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/99designs/[email protected]/graphql/recovery.go:16 +0xf0
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/controller.(*executionContext)._Mutation_createProject.func1(0xc00034c120, 0xc00034d940, 0xc00048c628)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/controller/graphql.go:1299 +0x9e
    panic(0x1bcb040, 0xc000238200)
            /usr/local/Cellar/go/1.13.8/libexec/src/runtime/panic.go:679 +0x1e0
    reflect.Value.IsNil(0x1b861c0, 0xc00034dab0, 0x198, 0xc000509100)
            /usr/local/Cellar/go/1.13.8/libexec/src/reflect/value.go:1073 +0x198
    github.com/go-pg/pg/v10/types.ptrScannerFunc.func1(0x1b861c0, 0xc00034dab0, 0x198, 0x1e0ce60, 0xc0002381c0, 0xb, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/types/scan_value.go:151 +0x349
    github.com/go-pg/pg/v10/types.ArrayScanner.func1(0x1b71820, 0xc0001ce488, 0x197, 0x1e0ce60, 0xc000384438, 0x31, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/types/array_scan.go:103 +0x628
    github.com/go-pg/pg/v10/orm.(*Field).ScanValue(0xc0001b4280, 0x1cab000, 0xc0001ce410, 0x199, 0x1e0ce60, 0xc000384438, 0x31, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/orm/field.go:113 +0x100
    github.com/go-pg/pg/v10/orm.(*structTableModel).scanColumn(0xc0003998c0, 0x7, 0xc0005090f0, 0x5, 0x1e0ce60, 0xc000384438, 0x31, 0xc0003fa200, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/orm/model_table_struct.go:278 +0x623
    github.com/go-pg/pg/v10/orm.(*structTableModel).ScanColumn(0xc0003998c0, 0x7, 0xc0005090f0, 0x5, 0x1e0ce60, 0xc000384438, 0x31, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/orm/model_table_struct.go:236 +0xa4
    github.com/go-pg/pg/v10.readDataRow(0x1e06880, 0xc0000c0000, 0xc0003843c0, 0x1df8ce0, 0xc0003998c0, 0xc000101080, 0xe, 0xe, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/messages.go:810 +0x3f7
    github.com/go-pg/pg/v10.readSimpleQueryData(0x1e06880, 0xc0000c0000, 0xc0003843c0, 0x1cc6bc0, 0xc0003998c0, 0x0, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/messages.go:873 +0x427
    github.com/go-pg/pg/v10.(*baseDB).simpleQueryData.func1(0xc0003843c0, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/base.go:621 +0xbc
    github.com/go-pg/pg/v10/internal/pool.(*Conn).WithReader.func1(0x1e06880, 0xc0000c0000, 0x1e0e520, 0x23f7168, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/internal/pool/conn.go:80 +0x158
    github.com/go-pg/pg/v10/internal.WithSpan(0x1e06880, 0xc0000c0000, 0x1ce789e, 0xb, 0xc00048aa10, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/internal/util.go:84 +0x2e4
    github.com/go-pg/pg/v10/internal/pool.(*Conn).WithReader(0xc0003ae190, 0x1e06880, 0xc0000c0000, 0x0, 0xc00048aab8, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/internal/pool/conn.go:73 +0xbe
    github.com/go-pg/pg/v10.(*baseDB).simpleQueryData(0xc0003d2000, 0x1e06880, 0xc0000c0000, 0xc0003ae190, 0x1cc6bc0, 0xc0003998c0, 0xc0003ab9b0, 0x0, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/base.go:619 +0x1ed
    github.com/go-pg/pg/v10.(*baseDB).query.func1.1(0x1e06880, 0xc0000c0000, 0xc0003ae190, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/base.go:324 +0xdf
    github.com/go-pg/pg/v10.(*baseDB).withConn.func1(0x1e06880, 0xc0000c0000, 0x1e0e520, 0x23f7168, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/base.go:175 +0x29e
    github.com/go-pg/pg/v10/internal.WithSpan(0x1e06880, 0xc0000c0000, 0x1ce62b9, 0x9, 0xc00048ae08, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/internal/util.go:84 +0x2e4
    github.com/go-pg/pg/v10.(*baseDB).withConn(0xc0003d2000, 0x1e06880, 0xc0000c0000, 0xc00048af20, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/base.go:142 +0xad
    github.com/go-pg/pg/v10.(*baseDB).query.func1(0x1e06880, 0xc0000c0000, 0x1e0e520, 0x23f7168, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/base.go:323 +0x34b
    github.com/go-pg/pg/v10/internal.WithSpan(0x1e06880, 0xc0000c0000, 0x1ce2056, 0x5, 0xc00048b238, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/internal/util.go:84 +0x2e4
    github.com/go-pg/pg/v10.(*baseDB).query(0xc0003d2000, 0x1e06880, 0xc0000c0000, 0x1cc6bc0, 0xc0003998c0, 0x1c5a8e0, 0xc0002380a0, 0xc00034da70, 0x1, 0x1, ...)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/base.go:314 +0x559
    github.com/go-pg/pg/v10.(*baseDB).QueryContext(0xc0003d2000, 0x1e06880, 0xc0000c0000, 0x1cc6bc0, 0xc0003998c0, 0x1c5a8e0, 0xc0002380a0, 0xc00034da70, 0x1, 0x1, ...)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/base.go:293 +0xde
    github.com/go-pg/pg/v10/orm.(*Query).query(0xc00000c3c0, 0x1e06880, 0xc0000c0000, 0x1e0dca0, 0xc0003998c0, 0x1c5a8e0, 0xc0002380a0, 0x0, 0x0, 0x0, ...)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/orm/query.go:865 +0x4c4
    github.com/go-pg/pg/v10/orm.(*Query).Select(0xc00000c3c0, 0x0, 0x0, 0x0, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/go-pg/pg/[email protected]/orm/query.go:834 +0x218
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/controller.ResolveCreateProject(0xc0003d8270, 0xc000456134, 0xe, 0xc0000d8e18, 0x1, 0x1, 0xc00034d9b0, 0x0, 0x0, 0x0)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/controller/project.go:18 +0x33f
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/resolver.(*mutationResolver).CreateProject(0xc0000d8e20, 0x1e06900, 0xc0003aba70, 0xc000456134, 0xe, 0xc0000d8e18, 0x1, 0x1, 0xc00034d9b0, 0x0, ...)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/resolver/schema.resolvers.go:46 +0x149
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/controller.(*executionContext)._Mutation_createProject.func2.1(0x1e06900, 0xc0003aba70, 0x0, 0x0, 0x0, 0x0)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/controller/graphql.go:1321 +0x1d1
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/router.graphqlHandler.func2(0x1e06900, 0xc0003aba70, 0x0, 0x0, 0xc00032dfe0, 0xc00034d9f0, 0x1, 0x1, 0x0, 0x0, ...)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/router/router.go:93 +0x38b
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/controller.(*executionContext)._Mutation_createProject.func2.2(0x1e06900, 0xc0003aba70, 0x0, 0x0, 0x0, 0x0)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/controller/graphql.go:1331 +0x2b6
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/controller.(*executionContext)._Mutation_createProject.func2(0x1e06900, 0xc0003aba70, 0x0, 0x0, 0x0, 0x0)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/controller/graphql.go:1334 +0x194
    github.com/99designs/gqlgen/graphql/handler.newExecutor.func3(0x1e06900, 0xc0003aba70, 0xc00032dfa0, 0x0, 0x0, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/99designs/[email protected]/graphql/handler/executor.go:36 +0x6d
    github.com/99designs/gqlgen/graphql/handler.newExecutor.func6.1(0x1e06900, 0xc0003aba70, 0x0, 0x0, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/99designs/[email protected]/graphql/handler/executor.go:64 +0x94
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/router.Introspection.InterceptField(0xc0002f4340, 0x0, 0x1e06900, 0xc0003aba70, 0xc00032dfc0, 0x0, 0x0, 0x0, 0x0)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/router/extension.go:36 +0x84
    github.com/99designs/gqlgen/graphql/handler.newExecutor.func6(0x1e06900, 0xc0003aba70, 0xc00032dfa0, 0x0, 0x0, 0x0, 0x0)
            /Users/aien/go/pkg/mod/github.com/99designs/[email protected]/graphql/handler/executor.go:63 +0x11c
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/controller.(*executionContext)._Mutation_createProject(0xc00034c120, 0x1e06900, 0xc0003aa990, 0xc0002f6780, 0xc00032c300, 0x2, 0x2, 0x0, 0x0)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/controller/graphql.go:1318 +0x545
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/controller.(*executionContext)._Mutation(0xc00034c120, 0x1e06900, 0xc0003aa990, 0xc000507dc0, 0x2, 0x2, 0x0, 0x0)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/controller/graphql.go:4531 +0xf83
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/controller.(*executableSchema).Exec.func2.1(0x1e06900, 0xc0003aa960, 0x0, 0x0, 0x0, 0x0)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/controller/graphql.go:499 +0xd9
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/controller.(*executionContext)._mutationMiddleware(0xc00034c120, 0x1e06900, 0xc0003aa960, 0xc00015c8c0, 0xc00032c2e0, 0x0, 0x0)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/controller/graphql.go:881 +0x3ea
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/controller.(*executableSchema).Exec.func2(0x1e06900, 0xc0003aa960, 0x0)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/controller/graphql.go:498 +0x12a
    github.com/99designs/gqlgen/graphql/handler.executor.DispatchOperation.func1.1.1(0x1e06900, 0xc0003aa960, 0x0)
            /Users/aien/go/pkg/mod/github.com/99designs/[email protected]/graphql/handler/executor.go:100 +0x67
    github.com/99designs/gqlgen/graphql/handler.newExecutor.func2(0x1e06900, 0xc0003aa960, 0xc00034c130, 0x0)
            /Users/aien/go/pkg/mod/github.com/99designs/[email protected]/graphql/handler/executor.go:33 +0x43
    github.com/99designs/gqlgen/graphql/handler.executor.DispatchOperation.func1.1(0x1e06900, 0xc0003aa960, 0x0)
            /Users/aien/go/pkg/mod/github.com/99designs/[email protected]/graphql/handler/executor.go:99 +0x118
    github.com/99designs/gqlgen/graphql/handler/transport.POST.Do(0x33661b0, 0xc0003d8270, 0xc0002f8300, 0x1e04d80, 0xc0003d3ae0)
            /Users/aien/go/pkg/mod/github.com/99designs/[email protected]/graphql/handler/transport/http_post.go:52 +0x758
    github.com/99designs/gqlgen/graphql/handler.(*Server).ServeHTTP(0xc00045e000, 0x33661b0, 0xc0003d8270, 0xc0002f8300)
            /Users/aien/go/pkg/mod/github.com/99designs/[email protected]/graphql/handler/server.go:140 +0x201
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/router.graphqlHandler.func4(0xc0003d8270)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/router/router.go:148 +0xa8
    github.com/gin-gonic/gin.(*Context).Next(0xc0003d8270)
            /Users/aien/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:147 +0x8c
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/middleware.GinContextToContextMiddleware.func1(0xc0003d8270)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/middleware/gin.go:12 +0x130
    github.com/gin-gonic/gin.(*Context).Next(0xc0003d8270)
            /Users/aien/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:147 +0x8c
    riant.jetbrains.space/p/apollo/code/apollo/apps/mission-control/middleware.JsonHeaderMiddleWare(0xc0003d8270)
            /Users/aien/Programming/Lo/apollo/apps/mission-control/middleware/header.go:9 +0x63
    github.com/gin-gonic/gin.(*Context).Next(0xc0003d8270)
            /Users/aien/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:147 +0x8c
    github.com/gin-gonic/gin.RecoveryWithWriter.func1(0xc0003d8270)
            /Users/aien/go/pkg/mod/github.com/gin-gonic/[email protected]/recovery.go:83 +0x74
    github.com/gin-gonic/gin.(*Context).Next(0xc0003d8270)
            /Users/aien/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:147 +0x8c
    github.com/toorop/gin-logrus.Logger.func1(0xc0003d8270)
            /Users/aien/go/pkg/mod/github.com/toorop/[email protected]74ad36b67/logger.go:31 +0xcc
    github.com/gin-gonic/gin.(*Context).Next(0xc0003d8270)
            /Users/aien/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:147 +0x8c
    github.com/gin-contrib/sessions.Sessions.func1(0xc0003d8270)
            /Users/aien/go/pkg/mod/github.com/gin-contrib/[email protected]/sessions.go:52 +0x233
    github.com/gin-gonic/gin.(*Context).Next(0xc0003d8270)
            /Users/aien/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:147 +0x8c
    github.com/gin-gonic/gin.(*Engine).handleHTTPRequest(0xc00044e000, 0xc0003d8270)
            /Users/aien/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:403 +0x434
    github.com/gin-gonic/gin.(*Engine).ServeHTTP(0xc00044e000, 0x1e049c0, 0xc0003cc2a0, 0xc000462000)
            /Users/aien/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:364 +0xd6
    net/http.serverHandler.ServeHTTP(0xc0003cc1c0, 0x1e049c0, 0xc0003cc2a0, 0xc000462000)
            /usr/local/Cellar/go/1.13.8/libexec/src/net/http/server.go:2802 +0x20f
    net/http.(*conn).serve(0xc0003d0140, 0x1e06840, 0xc0002ec800)
            /usr/local/Cellar/go/1.13.8/libexec/src/net/http/server.go:1890 +0x1716
    created by net/http.(*Server).Serve
            /usr/local/Cellar/go/1.13.8/libexec/src/net/http/server.go:2928 +0x931
    
    

    If I don't use the pg.In() then no results will return, although I'm pretty sure the user ids exist.

    I will provide more details if you need.

    thanks

    opened by AienTech 13
  • Multi-column foreign key?

    Multi-column foreign key?

    @vmihailenco what are your thoughts on a multi column foreign key?

    I'm using go-pg for a multi tenant application so everything is separated by an account_id column.

    I'd like to create certain tables that ensure the same account_id for the items. For example:

    type Account struct {
    	tableName string `sql:"accounts"`
    
    	AccountID uint64 `json:"accountId" sql:"account_id,pk,type:bigint"`
    	Name      string `json:"name" sql:"name"`
    }
    
    type Product struct {
    	tableName string `sql:"products"`
    
    	ProductID uint64   `json:"productId" sql:"product_id,pk,type:bigint"`
    	AccountID uint64   `json:"accountId" sql:"account_id,notnull,on_delete:CASCADE"`
    	Account   *Account `json:"account"`
    	Title     string   `json:"title" sql:"title"`
    }
    
    type Variation struct {
    	tableName string `sql:"variations"`
    
    	VariationID uint64   `json:"variationId" sql:"variation_id,pk,type:bigint"`
    	AccountID   uint64   `json:"accountId" sql:"account_id,notnull,on_delete:CASCADE"`
    	Account     *Account `json:"-" `
    	ProductID   uint64   `json:"productId" sql:"product_id,notnull,on_delete:CASCADE"`
    	Product     *Product `json:"-"`
    	SKU         string   `json:"sku" sql:"sku"`
    }
    

    These models will generate the following queries:

    CREATE TABLE IF NOT EXISTS accounts
    (
        "account_id" bigserial,
        "name"       text,
        PRIMARY KEY ("account_id")
    );
    
    CREATE TABLE IF NOT EXISTS products
    (
        "product_id" bigserial,
        "account_id" bigint NOT NULL,
        "title"      text,
        PRIMARY KEY ("product_id"),
        FOREIGN KEY ("account_id") REFERENCES accounts ("account_id") ON DELETE CASCADE
    );
    
    CREATE TABLE IF NOT EXISTS variations
    (
        "variation_id" bigserial,
        "account_id"   bigint NOT NULL,
        "product_id"   bigint NOT NULL,
        "sku"          text,
        PRIMARY KEY ("variation_id"),
        FOREIGN KEY ("account_id") REFERENCES accounts ("account_id") ON DELETE CASCADE,
        FOREIGN KEY ("product_id") REFERENCES products ("product_id") ON DELETE CASCADE
    );
    

    I'd like to make it so that when a variation is inserted it guarantees that the product is for the same account. Would it make sense to add support for a foreign tag? Something like this?

    type Variation struct {
    	tableName string `sql:"variations"`
    
    	VariationID uint64   `json:"variationId" sql:"variation_id,pk,type:bigint"`
    	AccountID   uint64   `json:"accountId" sql:"account_id,notnull,on_delete:CASCADE,foreign:account_product_fk"`
    	Account     *Account `json:"-" `
    	ProductID   uint64   `json:"productId" sql:"product_id,notnull,on_delete:CASCADE,foreign:account_product_fk"`
    	Product     *Product `json:"-"`
    	SKU         string   `json:"sku" sql:"sku"`
    }
    

    And then the create query would be something like:

    CREATE TABLE IF NOT EXISTS variations
    (
        "variation_id" bigserial,
        "account_id"   bigint NOT NULL,
        "product_id"   bigint NOT NULL,
        "sku"          text,
        PRIMARY KEY ("variation_id"),
        FOREIGN KEY ("account_id") REFERENCES accounts ("account_id") ON DELETE CASCADE,
        FOREIGN KEY ("product_id", "account_id") REFERENCES products ("product_id", "account_id") ON DELETE CASCADE
    );
    

    This would be ideal since it will validate that any record will always need to relate to another record within the same account. This also wouldn't need to affect relations for querying at all, this would only enforce the constraint for writes.

    What are your thoughts?

    opened by elliotcourant 13
  • How specify schema in queries?

    How specify schema in queries?

    I may specify db schema in TableName (example, TableName struct{} 'sql:web.user') field model, but this case not convenient. Im use same models for different schemes. Solve - when create connection make query db.Exec("SET SCHEMA web"), but... application lost connection to db and you library reconnect? If so, query SET SCHEMA not execute again. How i can specify schema for connection?

    opened by njspok 13
  • UpdateNotZero does not update nullable fields by design, how to do?

    UpdateNotZero does not update nullable fields by design, how to do?

    I'm having hard time trying to understand how to nullish a model field using UpdateNotZero().

    Example:

    type Player struct {
      ID            int
      CreatedAt     time.Time `pg:"default:now(),notnull"`
      UpdatedAt     time.Time
      AccountID     *int
    }
    

    Let's say I have this player now:

    +----+------------+------------+------------+
    | ID | created_at | updated_at | account_id |
    +----+------------+------------+------------+
    |  1 | 2020-06-16 | NULL       |         12 |
    +----+------------+------------+------------+
    

    in my GO code I need to "remove" AccountID, I need to nullish it: from 12 to NULL.

    If I use update() like this:

    ...
    player.AccountID = nil
    _, err := db.Model(player).WherePK().Update()
    

    it gives me the error:

    ERROR #23502 null value in column \"created_at\" violates not-null constraint"
    

    If I use UpdateNotZero() like this:

    ...
    player.AccountID = nil
    _, err := db.Model(player).WherePK().UpdateNotZero()
    

    it doesn't update AccountID at all.

    How to do?


    Related issues I think:

    • https://github.com/go-pg/pg/pull/1580
    • https://github.com/go-pg/pg/issues/1557
    opened by frederikhors 12
  • How to work with composite PK and FK?

    How to work with composite PK and FK?

    I've next problem

    type Model struct {
    	ID        string    `json:"id" sql:",pk" testdiff:"ignore"`
    	TenantID  uint32    `json:"-" sql:",pk" testdiff:"ignore"`
    	CreatedAt time.Time `json:"created_at" testdiff:"ignore"`
    }
    
    type Phone struct {
    	Model
    	tableName struct{} `sql:"contact_phones"`
    	Phone     string   `json:"phone"`
    	ContactID string   `json:"contact_id"`
    }
    
    type Contact struct {
    	Model
            SomeField string `json:"some_field"`
    	Phones          []*Phone    `json:"phones" pg:"fk:contact_id"`
    }
    
    
    func getContac(tenantID, contactID string, phones []string) ([]Contact, error){
             contacts := make([]models.Contact, 0)
             err := DB.Model(&contacts).
    			Column("contact.*", "Phones").
    			Relation("Phones", func(q *orm.Query) (*orm.Query, error) {
    				return q.Where("tenant_id = ?", tenantID).Where("phone IN ?", phones), nil
    			}).
    			Where("tenant_id = ?", tenantID).
    			Select()
              return contacts, err
    }
    

    I have an error model=Contact does not have relation="Phones"

    If I remove tags json:"phones" pg:"fk:contact_id"

    Then I've invalid SQL query

    SELECT "phone"."id", "phone"."tenant_id", "phone"."created_at", "phone"."phone", "phone"."contact_id" FROM contact_phones AS "phone" WHERE (tenant_id = 238) AND ("phone"."contact_id" IN (('1Bec1PUgP2H5ac0vQ5biwQdoTTW', 238)))
    
    opened by snaffi 12
  • CountEstimate is not safe to be called from transaction context

    CountEstimate is not safe to be called from transaction context

    Expected Behavior

    CountEstimate(threshold int) should be safely called from any context, including transaction.

    Current Behavior

    If a transaction is run containing a CountEstimate() instruction without preliminary run of other query using this function, the transaction will fail.

    It is due to the fact that CountEstimate() use an error-based lazy loading in count_estimate.go

    
    if err != nil {
      if pgerr, ok := err.(internal.PGError); ok && pgerr.Field('C') == "42883" {
        // undefined_function
        err = q.createCountEstimateFunc()
        // ...
    }
    

    This lazy-loading cause a rollback of the transaction, which fails.

    Possible Solution

    Several possible solutions:

    1. Change the "lazy init" of this function by checking it's existence whenever we use the function CountEstimate()... But can lead to performance issue...
    2. Handle the specific case of transaction whenever we fails to create this function
    3. Don't create the pgCountEstimateFunc from the query db whenever it's a transaction, but from the baseDB of the transaction...
    4. Make it a feature, add some docs to the function with the workaround ^^

    Steps to Reproduce

    (Error handling is omitted)

    func main(){
      dbh, _ := getDBHandler() // return a db handler (*pg.DB) on a "clean" db (without _go_pg_count_estimate_v2 function)
      
      tx, _ := db.Begin()
      _, err := tx.Model(&SomeModel{}).CountEstimate(10)
      if err != nil {
         return panic(fmt.Sprintf("countEstimate failure. %s", err))
      }
    }
    
    

    This code will panic with message: panic: countEstimate failure. ERROR #25P02 current transaction is aborted, commands ignored until end of transaction block

    The code below will succeed, since the first query will create the _go_pg_count_estimate_v2, and avoid the error in the transaction

    func main(){
      dbh, _ := getDBHandler() // return a db handler (*pg.DB) on a "clean" db (without _go_pg_count_estimate_v2 function)
    
      // This will create the _go_pg_count_estimate_v2 function, which will make it available in above transaction
      db.Model(&SomeModel{}).CountEstimate(10)
      
      tx, _ := db.Begin()
      _, err := tx.Model(&SomeModel{}).CountEstimate(10)
      if err != nil {
         return panic(fmt.Sprintf("countEstimate failure. %s", err))
      }
    }
    
    

    Context (Environment)

    • Posgresql version : 14.1
    • go-pg version : v10.10.6
    opened by Ezian 0
  • "BeforeUpdate" hook not working for individual column updates

    type User struct {
    	ID        int64
    	CreatedAt time.Time `sql:"default:now()"`
    	UpdatedAt time.Time `sql:"default:now()"`
    	Name      string
    	Email    string
    }
    
    func (m *User) BeforeUpdate(ctx context.Context) (context.Context, error) {
    	m.UpdatedAt = timeNow().Round(time.Microsecond)
    	return ctx, nil
    }
    

    The following two queries don't update the updated_at column

    user := &User{ID:1, Email:"[email protected]"} 
    _, err := db.ModelContext(ctx, user).Column("email").Where("id = ?", user.ID).Update()
    _, err := db.ModelContext(ctx, user).Set("email = ?",users.Email ).Where("id = ?", user.ID).Update()
    

    The below queries work fine and update the updated_at column

    user := &User{ID:1, Email:"[email protected]"} 
    _, err := db.ModelContext(ctx, user)..WherePK().UpdateNotZero()
    _, err := db.ModelContext(ctx, user).Where("id = ?", id).Update()
    

    Is this intended behavior where BeforeUpdate hook is only triggered and used when the values are updated using struct? Is it possible to have a update tag which updates the updated_at column if it exists. Similar to how soft delete works, where we update deleted_at is set to current time if the column exists and we mention the tag in struct.

    opened by Omkarz7 0
  • panic on non nil

    panic on non nil "Result" with nil value in "AfterQuery" hook

    Query hook receives non nil Result with nil value. This leads to a panic:

    runtime error: invalid memory address or nil pointer dereference
    

    Expected Behavior

    Code like this don't trigger a nil pointer panic as it have a e.Result != nil check.

    func (h hook) AfterQuery(ctx context.Context, e *pg.QueryEvent) error {
    	if e.Err != nil {
    		h.log("error", e.Err)
    	}
    	if e.Result != nil {
    		h.log("returned", e.Result.RowsReturned())
    		h.log("affected", e.Result.RowsAffected())
    	}
    	return nil
    }
    

    Current Behavior

    Part of panic stack trace

    	/usr/local/go/src/runtime/panic.go:1038 +0x215
    github.com/go-pg/pg/v10.(*result).RowsReturned(0xdc4c80)
    	/go/pkg/mod/github.com/go-pg/pg/[email protected]/result.go:52
    <REDACTED>.AfterQuery({{0xdae1c0, 0xc00011e110}}, {0xdc4c80, 0xc000b5b7d0}, 0xc0004a1900)
    	<REDACTED>.go:110 +0xf2
    github.com/go-pg/pg/v10.(*baseDB).afterQueryFromIndex(0xc0000af0e0, {0xdc4c80, 0xc000b5b7d0}, 0x0, 0xdd8890)
    	/go/pkg/mod/github.com/go-pg/pg/[email protected]/hook.go:130 +0x75
    github.com/go-pg/pg/v10.(*baseDB).afterQuery(0xc000146d00, {0xdc4c80, 0xc000b5b7d0}, 0xc0009c8005, {0xdbce20, 0x0}, {0xdae1c0, 0xc00011e110})
    	/go/pkg/mod/github.com/go-pg/pg/[email protected]/hook.go:125 +0xaa
    

    Possible Solution

    Explicitly set nil for Result when *result is nil. Related article https://codefibershq.com/blog/golang-why-nil-is-not-always-nil

    Steps to Reproduce

    1. Add a query hook as provided above
    2. Execute SQL which fails
    3. nil pointer panic is triggered

    Here is an example of how nil becomes not nil https://goplay.tools/snippet/DVV1XNz5icn

    Context (Environment)

    Detailed Description

    On first line res is a nil. At the last line it assigned a value from db.simpleQuery https://github.com/go-pg/pg/blob/51b8018c9110133183f54aa5d786c316bbfd11fc/base.go#L236-L246

    In db.simpleQuery result might get returned with nil value of type *result, but in db.simpleQuery it will be wrapped into Result and will pass != nil check https://github.com/go-pg/pg/blob/51b8018c9110133183f54aa5d786c316bbfd11fc/base.go#L544-L553

    Note: Same issue exists in other parts of a code.

    Possible Implementation

    Workaround of this issue is to change a hook to look like this:

    func (h hook) AfterQuery(ctx context.Context, e *pg.QueryEvent) error {
    	if e.Err != nil {
    		h.log("error", e.Err)
    	} else if e.Result != nil {
    		h.log("returned", e.Result.RowsReturned())
    		h.log("affected", e.Result.RowsAffected())
    	}
    	return nil
    }
    
    opened by server-may-cry 0
  • Update omit zero when no fields produces invalid syntax

    Update omit zero when no fields produces invalid syntax

    https://github.com/go-pg/pg/blob/691def15f539b232452a5c982c08c5804b52bef1/orm/update_test.go#L59

    When updating and the model contains all empty fields invalid update syntax is produced.

    Suggest returning specific error to capture the case of a non-update

    opened by mgazza 1
  • How do you change which field/column in the base table is referenced by the foreign key of a sub table?

    How do you change which field/column in the base table is referenced by the foreign key of a sub table?

    I have two structs I'm currently trying to link via foreign key:

    type Child struct{
    	ID              int 		   `pg:",pk"`
    	MyFK         int 
    	CreatedAt time.Time 
    	UpdatedAt time.Time 
    	DeletedAt time.Time `pg:",soft_delete"`
     
    	Base          Base	`pg:"fk:my_fk"`
    }
    
    type Base struct{
    	ID 		               int `pg:",pk"`
    	FieldToReference    int `pg:",unique"`
    	CreatedAt               time.Time 
    	UpdatedAt             time.Time 
    	DeletedAt              time.Time `pg:",soft_delete"`
     }
    

    I want the "MyFK" field of my child struct to reference the "FieldToReference" field of the base struct. How do I accomplish this? MyFK by default references the ID field instead of my target field.

    Expected Behavior

    The table to establish a foreign key bewteen MyFK and FieldToReference

    Current Behavior

    The table instead connects MyFK to ID,

    Steps to Reproduce

    type Child struct{
    	ID              int 		   `pg:",pk"`
    	MyFK         int 
    	CreatedAt time.Time 
    	UpdatedAt time.Time 
    	DeletedAt time.Time `pg:",soft_delete"`
     
    	Base          Base	`pg:"fk:my_fk"`
    }
    
    type Base struct{
    	ID 		               int `pg:",pk"`
    	FieldToReference    int `pg:",unique"`
    	CreatedAt               time.Time 
    	UpdatedAt             time.Time 
    	DeletedAt              time.Time `pg:",soft_delete"`
     }
    
    func main(){
              db.Model( &Base{}, &Child{}).CreateTable(&orm.CreateTableOptions{
    		FKConstraints: true,
    	})
    }
    
    opened by darienmiller88 0
Releases(v10.10.6)
Go-mysql-orm - Golang mysql orm,dedicated to easy use of mysql

golang mysql orm 个人学习项目, 一个易于使用的mysql-orm mapping struct to mysql table golang结构

magacy 2 Apr 17, 2022
100% type-safe ORM for Go (Golang) with code generation and MySQL, PostgreSQL, Sqlite3, SQL Server support. GORM under the hood.

go-queryset 100% type-safe ORM for Go (Golang) with code generation and MySQL, PostgreSQL, Sqlite3, SQL Server support. GORM under the hood. Contents

Denis Isaev 660 May 9, 2022
An orm library support nGQL for Golang

norm An ORM library support nGQL for Golang. Overview Build insert nGQL by struct / map (Support vertex, edge). Parse Nebula execute result to struct

Zhihu 65 May 9, 2022
golang orm

korm golang orm, 一个简单易用的orm, 支持嵌套事务 安装 go get github.com/wdaglb/korm go get github.com/go-sql-driver/mysql 支持数据库 mysql https://github.com/go-sql-driv

null 6 Oct 11, 2021
The fantastic ORM library for Golang, aims to be developer friendly

GORM The fantastic ORM library for Golang, aims to be developer friendly. Overview Full-Featured ORM Associations (Has One, Has Many, Belongs To, Many

Yadhu Sasidharan 0 Nov 11, 2021
SQL mapper ORM framework for Golang

SQL mapper ORM framework for Golang English 中文 Please read the documentation website carefully when using the tutorial. DOC Powerful Features High Per

null 0 Dec 8, 2021
Golang mysql orm, a personal learning project, dedicated to easy use of mysql

golang mysql orm 个人学习项目, 一个易于使用的mysql-orm mapping struct to mysql table golang结构

magacy 1 Dec 30, 2021
Mybatis for golang - SQL mapper ORM framework

SQL mapper ORM framework for Golang English 中文 Please read the documentation website carefully when using the tutorial. DOC Powerful Features High Per

Tim 2 Mar 20, 2022
Simple and Powerful ORM for Go, support mysql,postgres,tidb,sqlite3,mssql,oracle, Moved to https://gitea.com/xorm/xorm

xorm HAS BEEN MOVED TO https://gitea.com/xorm/xorm . THIS REPOSITORY WILL NOT BE UPDATED ANY MORE. 中文 Xorm is a simple and powerful ORM for Go. Featur

null 6.5k May 11, 2022
A better ORM for Go, based on non-empty interfaces and code generation.

reform A better ORM for Go and database/sql. It uses non-empty interfaces, code generation (go generate), and initialization-time reflection as oppose

null 1.3k May 14, 2022
A better ORM for Go, based on non-empty interfaces and code generation.

A better ORM for Go and database/sql. It uses non-empty interfaces, code generation (go generate), and initialization-time reflection as opposed to interface{}, type system sidestepping, and runtime reflection. It will be kept simple.

null 1.2k May 12, 2022
Simple and performant ORM for sql.DB

Simple and performant ORM for sql.DB Main features are: Works with PostgreSQL, MySQL, SQLite. Selecting into a map, struct, slice of maps/structs/vars

Uptrace 894 May 14, 2022
Examples of using various popular database libraries and ORM in Go.

Introduction Examples of using various popular database libraries and ORM in Go. sqlx sqlc Gorm sqlboiler ent The aim is to demonstrate and compare us

Hafiz Shafruddin 0 Dec 12, 2021
Examples of using various popular database libraries and ORM in Go.

Introduction Examples of using various popular database libraries and ORM in Go. sqlx sqlc Gorm sqlboiler ent The aim is to demonstrate and compare us

Hafiz Shafruddin 3 Mar 18, 2022
beedb is a go ORM,support database/sql interface,pq/mysql/sqlite

Beedb ❗ IMPORTANT: Beedb is being deprecated in favor of Beego.orm ❗ Beedb is an ORM for Go. It lets you map Go structs to tables in a database. It's

astaxie 709 Mar 8, 2022
A simple wrapper around sql.DB to help with structs. Not quite an ORM.

go-modeldb A simple wrapper around sql.DB to help with structs. Not quite an ORM. Philosophy: Don't make an ORM Example: // Setup require "modeldb" db

Jae Kwon 17 Nov 16, 2019
ORM-ish library for Go

We've moved! gorp is now officially maintained at: https://github.com/go-gorp/gorp This fork was created when the project was moved, and is provided f

James Cooper 53 Nov 22, 2021
Simple Go ORM for Google/Firebase Cloud Firestore

go-firestorm Go ORM (Object-relational mapping) for Google Cloud Firestore. Goals Easy to use Non intrusive Non exclusive Fast Features Basic CRUD ope

Jens Kjær Schødt 28 May 8, 2022
Database agnostic ORM for Go

If you are looking for something more lightweight and flexible, have a look at jet For questions, suggestions and general topics visit the group. Inde

Erik Aigner 693 May 6, 2022