Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GORM v2 is on going #2886

Closed
jinzhu opened this issue Feb 16, 2020 · 216 comments
Closed

GORM v2 is on going #2886

jinzhu opened this issue Feb 16, 2020 · 216 comments

Comments

@jinzhu
Copy link
Member

jinzhu commented Feb 16, 2020

Hello All,

GORM v2 is under active development (https://github.com/jinzhu/gorm/tree/v2_dev), going to release in the next two months.

Before that, I am NOT going to merge any pull requests based on the master branch.

V2 will be overwritten from scratch with similar public API, it would focus on performance, improve usability and fix fragile designs. We will provide a migration guide to help users migrate before the release.

With the new architecture, it opens the possibility to have a better migration tool, a static code generator that generates type-safe code with all the features of GORM, visualizes models and its relationships to help a new team member quickly understand your project, and even support Redis & MongoDB...

Your code review or suggestions would be much appreciated, please comment to this thread, thank you.

@jinzhu jinzhu pinned this issue Feb 16, 2020
@donutloop
Copy link

donutloop commented Feb 16, 2020

@jinzhu Could you please add support for generation of migration files like djangos makemigrations? Django's orm is pretty stable, very useful and has lot of good features.

Ref: https://docs.djangoproject.com/en/3.0/ref/django-admin/#django-admin-makemigrations

Example Django application with migrations:

https://github.com/stadtulm/cykel/tree/master/bikesharing/migrations

@MurtadhaS
Copy link

  • Fix the foreign key relations (that work in a very convoluted way) and in Postgres only.
  • Write a sensible comprehensible documentation that describes your library in the right way.

@patrikeh
Copy link

patrikeh commented Feb 16, 2020

Preloads using joins for PostgreSQL (I don't know how that's implemented in other dialects), for me that is by far the most important feature absent in the current version.

As it currently stands, preloads literally stop working once the number of parameters exceed a certain amount. Before then preloads are prohibitively slow partly due to the extra roundtrip per preload, but more importantly due to the missed opportunity for query optimization.

@sljeff sljeff mentioned this issue Feb 16, 2020
3 tasks
@rjeczalik
Copy link

Has any work on static code generator started? I'd be interested in contributing.

@jinzhu
Copy link
Member Author

jinzhu commented Feb 16, 2020

Hi @rjeczalik haven't started it, just created some draft notes, was going to start it after the release of v2, but if you are interested, I can create a project for the generator under GORM's organization. ( yes, we will have an organization ;) )

Here is the draft notes (feedback is welcome)

Generate static code based on relationships parsed by https://github.com/jinzhu/gorm/tree/v2_dev/schema

generated code looks like

package models // user defiend

type DB struct {
  DB *gorm.DB
  User User
}

type User struct {
  ID       IntField
  Name     StringField
  Languages HasManyRelation
}

var user = User{
  ID: IntField{
    // xxx
  },
  Name: StringField{
    // xxx
  }
  Languages: HasManyRelation{
    // xxx
  }
}

func New(gorm.Dialector, gorm.Config) *DB {
  return &DB{
    DB: gormDB,
    User: user,
  }
}

Usage API

initalize db

db := models.NewDB(sqlite.Open('xxx'), gorm.Config{
})

with context

db = db.WithContext(ctx)

API

// find
db.User.Select(db.User.Name, db.User.Age).Find() (users []*yourapp.User, err error)

// first
db.User.Select(db.User.Name, db.User.Age).First() (*yourapp.User, error)

// last
db.User.Select(db.User.Name, db.User.Age).Last() (*yourapp.User, error)

// last
db.User.Where(db.User.Name.Like("%jinzhu"), db.User.Age.Eq(10)).Last() (*yourapp.User, error)

// preload
db.User.Preload(db.User.Languages.Where(
  db.Languages.Code.Eq("zh-CN")
)).Select(db.User.Name, db.User.Age).Last() (*yourapp.User, error)

// update
db.User.Update(user)

// Relations
db.User.Languages.Model(user).Add([]*yourapp.Languages{})
db.User.Languages.Model(user).Replace(]*yourapp.Languages{})
db.User.Languages.Model(user).Delete([]*yourapp.Languages{})

@rjeczalik
Copy link

@jinzhu Not quite sure what e.g. IntField is, is this a pseudocode for wrapper types like sql.NullInt64 or null.Int64?

There's already a functional generator for MySQL - https://github.com/smallnest/gen. What I wanted to achieve is db-agnostic generator with the ability of generating fields for associations (preloading), configurable via struct tags.

@madeofstars0
Copy link

@jinzhu Could you please add support for generation of migration files like djangos makemigrations? Django's orm is pretty stable, very useful and has lot of good features.

Maybe instead of coming up with another migration toolkit, have documentation and examples (or an integration) for using pressly/goose for migrations. There are a few other libraries, but goose can be fully integrated or it can be standalone with sql files for the migrations. Couple that with a generator that follows a convention or specified template for the migrations (generator like in rails, where you generate a new migration file with a rails command)

/cc @donutloop

@jinzhu
Copy link
Member Author

jinzhu commented Feb 17, 2020

@rjeczalik sorry, haven't made things clear, this tool WON'T generate models definition from database

You need to defined it by yourself, for example, you already have your user package

package user

type User struct {
  gorm.Model
  Name         string
  Language     Language
  LanguageCode string
}

type Language struct {
  Code string `gorm:primarykey`
}

With this tool, it will generate another package named db:

package db

type DB struct {
  DB *gorm.DB
  User User
}

type User struct {
  ID        IntField
  Name      StringField
  Languages HasManyRelation
}

func (user User) First() (user.User, error) {
  // xxx
}

var user = User{
  ID: IntField{
    DBName: "id",
  },
  Name: StringField{
    DBName: "name",
  }
  Languages: HasManyRelation{
    ReferenceTable: "languages",
    ForeignKey: "code",
  }
}

func New(gorm.Dialector, gorm.Config) *DB {
  return &DB{
    DB: gormDB,
    User: user,
  }
}

In your application, if you want to use the generated code like:

import 'yourapp/db'

var db = db.New(gorm.dialector, gorm.Config{})

func handler(w http.ResponseWriter, r *http.Request) {
  user, err := db.User.First()
  fmt.Println(user.Name) // string
  json.NewEncoder(w).Encode(user)
}

func main() {
  http.HandleFunc("/", handler)
  log.Fatal(http.ListenAndServe(":8080", nil))
}

@dobegor
Copy link

dobegor commented Feb 17, 2020

Please review some crucial fixes, we even had to use our fork to mitigate these problems:

https://github.com/jinzhu/gorm/pull/2737 -- erros on nested preloads get ignored, this is a critical issue.

Also please support context for cancellation and tracing support.

Our fork with context support and fix for ignored nested preload errors: https://github.com/readdle/gorm/tree/nested-preload-fix

@rjeczalik
Copy link

With this tool, it will generate another package named db:

@jinzhu Let me know if I understand it correctly - you want to have a generator which will generate objects with API similar to what Active Record in RoR is offering, correct?

@jinzhu
Copy link
Member Author

jinzhu commented Feb 17, 2020

@rjeczalik yes, generate type-safe code with similar API like that, suggestions?

@edwardsb
Copy link

allow https://golang.org/pkg/context/ to be passed to the underlying SQL driver.

@jinzhu
Copy link
Member Author

jinzhu commented Feb 20, 2020

@edwardsb the API will work like this:

tx := db.WithContext(ctx)
tx.First(&user)
tx.Update(&user)
db.WithContext(ctx).First(&user)

(similar to https://golang.org/pkg/net/http/#Request.WithContext)

@chowyu08
Copy link

could remove cgo depend for sqlites

@eslizn
Copy link

eslizn commented Feb 20, 2020

dynamic model support

@jinzhu
Copy link
Member Author

jinzhu commented Feb 23, 2020

Fix the foreign key relations (that work in a very convoluted way) and in Postgres only.

@MurtadhaS just finished that ;)

@ianarco
Copy link

ianarco commented Feb 26, 2020

The most important feature for my team would be a more advanced migration tool. I'm really missing Django's migration framework.

Integration into Goose would be fine, as long as the up/down SQL is generated by Gorm.

The main thing is that the migration tool is able to determine automatically what has changed between versions.

@dineshsprabu
Copy link

Postgres ErrorCodes support please.
https://www.postgresql.org/docs/10/errcodes-appendix.html

@heww
Copy link

heww commented Mar 4, 2020

Savepoint (nested transaction) support in transaction please.

@ianarco
Copy link

ianarco commented Mar 4, 2020

Also better handling of Postgres ENUM types would be great.

@MurtadhaS
Copy link

Another thing.
Handle GUID internally.

@DoubleDi
Copy link

DoubleDi commented Mar 9, 2020

Hi. Please check out my PR #2921

@fr3fou
Copy link

fr3fou commented Mar 12, 2020

Have a clear-well-defined API.

In my experience with gorm I found that there are multiple ways to achieve the same things which bothered me a lot. I could provide some examples in the next day or so

@vzool
Copy link

vzool commented Mar 12, 2020

@jinzhu Nice to see v2 rising. So, please don't forget this #1436 ^_^
Good luck

@jinzhu
Copy link
Member Author

jinzhu commented Mar 12, 2020

@vzool will make it works with API like db.Joins("Profile").Find(&users)

@profbiss
Copy link

There is no api for subqueries.

@kingwill101
Copy link

@jinzhu will v2 work with qor admin?

@aveyuan
Copy link

aveyuan commented Jul 3, 2020

Hi,jinzhu, I Use gorm.Model in my struct and created this , and i Create data not set CreatedAt and CreatedAt value, exec this is error, report Trace: reflect: call of reflect.flag.mustBeExported on zero Value

@jinzhu
Copy link
Member Author

jinzhu commented Jul 3, 2020

@jinzhu will v2 work with qor admin?

QOR Admin will migrate to GORM v2, but I need to check with the current maintainer for its plan.

When will the gorm.io docs be up to date?

Working on it should be ready in the next two weeks

Hi,jinzhu, I Use gorm.Model in my struct and created this , and i Create data not set CreatedAt and CreatedAt value, exec this is error, report Trace: reflect: call of reflect.flag.mustBeExported on zero Value

Please follow the document, if you believe there is an issue, create a PR here https://github.com/go-gorm/playground

@popsUlfr
Copy link

popsUlfr commented Jul 3, 2020

@jinzhu Hi I encountered an issue with the JoinForeignKey and JoinReferences tags. I made a minimal reproducible test case :

package main

import (
	"log"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type ModelA struct {
	ID []byte `gorm:"COLUMN:id;TYPE:BINARY(32);PRIMARY_KEY;NOT NULL" json:"id"`
}

type ModelB struct {
	ID []byte `gorm:"COLUMN:id;TYPE:BINARY(10);PRIMARY_KEY;NOT NULL" json:"id"`

	ModelAs []*ModelA `gorm:"Many2Many:model_a_model_b;ForeignKey:id;References:id;JoinForeignKey:model_a_id;JoinReferences:model_b_id" json:"model_as,omitempty"`
}

func main() {
	db, err := gorm.Open(sqlite.Open(":memory:?_foreign_keys=1"), nil)
	if err != nil {
		log.Fatal(err)
	}

	err = db.AutoMigrate(
		&ModelA{},
		&ModelB{})
	if err != nil {
		log.Fatal(err)
	}
}

Results in a panic

panic: reflect.StructOf: field "model_a_id" is unexported but missing PkgPath

goroutine 1 [running]:
reflect.runtimeStructField(0x6cbb6d, 0xa, 0x0, 0x0, 0x744d80, 0x6a7240, 0xc0000e6840, 0x35, 0x0, 0x0, ...)
        /usr/lib/go/src/reflect/type.go:2769 +0x1df
reflect.StructOf(0xc00009d2b0, 0x2, 0x2, 0x0, 0x0)
        /usr/lib/go/src/reflect/type.go:2385 +0x1e3f
gorm.io/gorm/schema.(*Schema).buildMany2ManyRelation(0xc00018e1c0, 0xc000182090, 0xc000083680, 0x6cbb32, 0xf)
        /home/popsulfr/go/pkg/mod/gorm.io/gorm@v0.2.20-0.20200703022618-f93345afa8e1/schema/relationship.go:249 +0xccc
gorm.io/gorm/schema.(*Schema).parseRelation(0xc00018e1c0, 0xc000083680)
        /home/popsulfr/go/pkg/mod/gorm.io/gorm@v0.2.20-0.20200703022618-f93345afa8e1/schema/relationship.go:77 +0x9d0
gorm.io/gorm/schema.Parse(0x6a0f00, 0xc0001597a0, 0xc0001595f0, 0x742f00, 0xc000180220, 0x0, 0x0, 0x0)
        /home/popsulfr/go/pkg/mod/gorm.io/gorm@v0.2.20-0.20200703022618-f93345afa8e1/schema/schema.go:212 +0x157e
gorm.io/gorm.(*Statement).Parse(0xc00009f1e0, 0x6a0f00, 0xc0001597a0, 0x0, 0x1)
        /home/popsulfr/go/pkg/mod/gorm.io/gorm@v0.2.20-0.20200703022618-f93345afa8e1/statement.go:332 +0x5f
gorm.io/gorm/migrator.Migrator.ReorderModels.func1(0x6a0f00, 0xc0001597a0, 0xc000100301)
        /home/popsulfr/go/pkg/mod/gorm.io/gorm@v0.2.20-0.20200703022618-f93345afa8e1/migrator/migrator.go:537 +0xce
gorm.io/gorm/migrator.Migrator.ReorderModels(0xc000168c01, 0xc000159620, 0x743a40, 0xc000165010, 0xc000180c20, 0x2, 0x2, 0xc000180c01, 0x20, 0x20, ...)
        /home/popsulfr/go/pkg/mod/gorm.io/gorm@v0.2.20-0.20200703022618-f93345afa8e1/migrator/migrator.go:585 +0x5c9
gorm.io/gorm/migrator.Migrator.AutoMigrate(0x5c2a01, 0xc000159620, 0x743a40, 0xc000165010, 0xc000180c20, 0x2, 0x2, 0x743a40, 0xc000165010)
        /home/popsulfr/go/pkg/mod/gorm.io/gorm@v0.2.20-0.20200703022618-f93345afa8e1/migrator/migrator.go:83 +0x95
gorm.io/gorm.(*DB).AutoMigrate(0xc000159620, 0xc000180c20, 0x2, 0x2, 0x0, 0x0)
        /home/popsulfr/go/pkg/mod/gorm.io/gorm@v0.2.20-0.20200703022618-f93345afa8e1/migrator.go:17 +0x70
main.main()
        /home/popsulfr/work/rezom/datascience/dsp2p/cmd/problem/main.go:26 +0x162
exit status 2

Without JoinForeignKey:model_a_id;JoinReferences:model_b_id it otherwise works. Am I using them wrong ?

@jinzhu
Copy link
Member Author

jinzhu commented Jul 3, 2020

the value of JoinForeignKey:model_a_id;JoinReferences:model_b_id needs to be upper case, but I have handled that in lastest commit. d4f8a52

@Icaro-Lima
Copy link

Add a way for omitting some columns also on querying, not only on saving.

@kumarsiva07
Copy link

fyi, I needed to add some additional checks to avoid triggering panic in some cases:

func beforeCreate(sfn *snowflake.Node) func(*gorm.DB) {
        return func(db *gorm.DB) {
                if db.Statement == nil || db.Statement.Schema == nil || !db.Statement.ReflectValue.IsValid() {
                        return
                }
                db.Statement.Schema.LookUpField("ID").Set(db.Statement.ReflectValue, sfn.Generate().Int64())
        }
}

I am setting below thing for each request in http middleware.

tx := db.Scopes(func(db *gorm.DB) *gorm.DB {
			db = db.Set("IP", IP)
			return db.Set("org_id", ctx.Value("org_id"))
		})
ctx = context.WithValue(ctx, "tx", tx)


	db.Callback().Create().Before("gorm:update").Register("update_org", updateOrg)
	db.Callback().Row().Before("gorm:row_query").Register("query_org", queryOrg)

func queryOrg(scope *gorm.DB) {
	if scope.HasColumn("org_id") {
		orgID, ok := scope.Get("org_id")
		if !ok {
			return
		}
		scope.Search.Where(scope.TableName()+".org_id = ?", orgID)
}

func updateOrg(scope *gorm.Scope) {
	if scope.HasColumn("org_id") {
		orgID, ok := scope.Get("org_id")
		if !ok {
			return
		}
		scope.Search.Where(scope.TableName()+".org_id = ?", orgID)
	}
}

Can you please give the replacement for the above thing. @jinzhu @moul

@jinzhu
Copy link
Member Author

jinzhu commented Jul 8, 2020

I have finished the draft v2 documents, please check it out http://v2.gorm.io (will use http://gorm.io after the final release)

Closing this issue, please create a new issue on any bug report or suggestion.

@jinzhu jinzhu closed this as completed Jul 8, 2020
@SujalKokh
Copy link

Tried out GORM v2 and it is fantastic. However, I am having difficulty finding a proper way of implementing migrations. Can we expect a migration for v2 similar to that of Ruby on Rails/Django @jinzhu ? The problem is that once I run the auto-migration I need to manually delete or update the names of the column of tables if it requires changes.

@vabshere
Copy link

vabshere commented Jul 17, 2020

@SujalKokh That is the expected behaviour. Deletes/updates through auto-migrations are not safe and can cause severe damage. You can use DropColumn for dropping a column though.

@raygervais
Copy link

Question, I've been rewriting an API backend which utilized Gorm 1.X, and I notice the following doesn't return any associated books. What am I doing wrong? Thanks for any help you can provide, I absolutely love GORM 2's API.

When hitting the API with /series/1 (which is valid and created with the following CURL command: curl -d '{"description":"value1", "title":"value2", "books":[{"title":"Harry Potter", "author": "lol"}]}' -H "Content-Type: application/json" -X POST http://localhost:8000/series), the response is:

{
  "ID":1,
  "CreatedAt":"2020-07-19T10:26:13.948013-04:00",
  "UpdatedAt":"2020-07-19T10:26:13.948013-04:00",
  "DeletedAt": {
    "Time":"0001-01-01T00:00:00Z",
    "Valid":false
  },
  "title":"value2",
  "description":"value1",
  "books":[]
}

Confused because looking into the sqlite database, I see the bridge table and book being created.

type Book struct {
	gorm.Model
	Title  string `json:"title"`
	Author string `json:"author"`
}

type Series struct {
	gorm.Model
	Title       string `json:"title"`
	Description string `json:"description"`
	Books       []Book `json:"books" gorm:"many2many:Series_Book"`
}

func CreateSeries(app *application.Application) gin.HandlerFunc {
	return func(c *gin.Context) {
		var input models.CreateSeriesInput
		if err := c.ShouldBindJSON(&input); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		series := models.Series{Title: input.Title, Description: input.Description, Books: input.Books}

		app.Database.Client.Create(&series)

		c.JSON(http.StatusOK, series)
	}
}

func RetrieveSeriesByID(db *gorm.DB, c *gin.Context, series *models.Series) bool {
	if err := db.Where("id = ?", c.Param("id")).First(&series).Error; err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
		return false
	}

	// Associtation Mode
	if err := db.Model(&series).Association("Books").Error; err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	}

	db.Model(&series).Association("Books").Find(&series.Books)

	return true
}

@JoveYu
Copy link

JoveYu commented Jul 31, 2020

when i write a custom logger, how can i get sql.DB object for get sql.DBStats, i want to log DBStatus.InUse and DBStatus.Idle

@wolfgang-braun
Copy link

wolfgang-braun commented Aug 5, 2020

@jinzhu great work, thanks!

I'm currently trying out v2 (MySQL) and have 3 questions:

  • Won't there be a Close() connection func anymore?
  • I'm reusing a single instance of *gorm.DB (provided by gorm.Open()) for multiple queries and faced unexpected behavior: It seemed like it stored conditions of queries executed before and reused them when executing other queries. Using a *gorm.DB instance from db.Session(&gorm.Session{}) the problem disappeared. Is this how it should be used?
  • Testing the batch inserts I got Error 1390: Prepared statement contains too many placeholders - gorm could execute it in chunks to avoid this, right? Otherwise everybody would have to reimplement the chunk logic..

@jinzhu
Copy link
Member Author

jinzhu commented Aug 6, 2020

@jinzhu great work, thanks!

I'm currently trying out v2 (MySQL) and have 3 questions:

  • Won't there be a Close() connection func anymore?
  • I'm reusing a single instance of *gorm.DB (provided by gorm.Open()) for multiple queries and faced unexpected behavior: It seemed like it stored conditions of queries executed before and reused them when executing other queries. Using a *gorm.DB instance from db.Session(&gorm.Session{}) the problem disappeared. Is this how it should be used?
  • Testing the batch inserts I got Error 1390: Prepared statement contains too many placeholders - gorm could execute it in chunks to avoid this, right? Otherwise everybody would have to reimplement the chunk logic..

http://v2.gorm.io/docs/connecting_to_the_database.html#Connection-Pool
http://v2.gorm.io/docs/generic_interface.html
http://v2.gorm.io/docs/method_chaining.html

@jinzhu
Copy link
Member Author

jinzhu commented Aug 6, 2020

when i write a custom logger, how can i get sql.DB object for get sql.DBStats, i want to log DBStatus.InUse and DBStatus.Idle

initialize the logger with DB

@larry-a4
Copy link

larry-a4 commented Aug 6, 2020

@jinzhu We are in the process of migrating from gorm V1 to V2, and V2 has been great!

One question though: does V2 Postgres driver support jsonb? I can't seem to find the relevant functions.

@friesencr
Copy link

I might be having a bug with Joins. When adding joins to a query it isn't in the query in the same order as v1.

@Awezome
Copy link

Awezome commented Aug 7, 2020

can I use the last commit in production ?

@jinzhu
Copy link
Member Author

jinzhu commented Aug 7, 2020

can I use the last commit in production?

There are hundreds of services already using GORM V2 ;)

@jinzhu
Copy link
Member Author

jinzhu commented Aug 7, 2020

@jinzhu We are in the process of migrating from gorm V1 to V2, and V2 has been great!

One question though: does V2 Postgres driver support jsonb? I can't seem to find the relevant functions.

https://github.com/go-gorm/datatypes

@jinzhu
Copy link
Member Author

jinzhu commented Aug 7, 2020

I might be having a bug with Joins. When adding joins to a query it isn't in the query in the same order as v1.

consider creating a PR on Playground

@dulumao
Copy link

dulumao commented Aug 10, 2020

Hope to develop a cache system plug-in

@MilesLin
Copy link

MilesLin commented Aug 10, 2020

could remove cgo depend for sqlites

This is not GORM stuff. cgo come from the sql driver.

Here is the all driver list, some of them are using cgo. Unfortunately, sqlite is using cgo.
https://github.com/golang/go/wiki/SQLDrivers

@Hi-Rui
Copy link

Hi-Rui commented Aug 18, 2020

@jinzhu
gorm.io/driver/mysql v0.3.1
gorm.io/gorm v0.2.31
Error 1292: Incorrect datetime value: '0000-00-00' for column 'created_at' at row 1
gorm.Model.CreatedAt default value
db.Save(&row)

@douyacun
Copy link

hello world

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests