go-gorm / gorm

The fantastic ORM library for Golang, aims to be developer friendly
https://gorm.io
MIT License
36.96k stars 3.93k forks source link

GORM v2 is on going #2886

Closed jinzhu closed 4 years ago

jinzhu commented 4 years ago

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 commented 4 years ago

Can the array and map in postgresql be fully supported?

It is possible to fully support it like https://github.com/go-gorm/datatypes, but we haven't started to work on Postgres specified-thing, Pull Request welcome ;)

pioz commented 4 years ago

@jinzhu in v2 how can I get the model table name? Before I can do db.NewScope(&user).TableName(), but now?

Hi @pioz, you can do it like this, btw, could you let me know your use case?

stmt := &gorm.Statement{DB: DB}
stmt.Parse(value)
stmt.Table

I need the table name to create a trigger.

I handy shortcut method should be great: db.TableName(&user) => "users"

lhypj commented 4 years ago

@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

I have imp the migrations with gormV1 due to i am a pythoner as well .Btw Automigrate maybe a better way for modern app.

pioz commented 4 years ago

@jinzhu I am trying to migrate this model:

type User struct {
    ID       uint64 `gorm:"primary_key;type:bigint unsigned auto_increment"`
    Username string
}

type Product struct {
    ID              uint64
    Name            string `gorm:"type:varchar(255)"`
    CollectorNumber string
    Condition       string
    BundleSize      int    `gorm:"type:smallint"`
    Language        string
    PropertyType    string
    UserId          uint64 `gorm:"not null;autoincrement:false"`
    User            *User  `gorm:"foreignkey:UserId"`
}

But I get this error:

Error 1075: Incorrect table definition; there can be only one auto column and it must be defined as a key
[0.469ms] [rows:0] CREATE TABLE `products` (`id` bigint unsigned AUTO_INCREMENT,`name` varchar(255),`collector_number` longtext,`condition` longtext,`bundle_size` smallint,`language` longtext,`property_type` longtext,`user_id` bigint unsigned auto_increment NOT NULL,PRIMARY KEY (`id`),CONSTRAINT `fk_products_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))

Seems that gorm try to add auto_increment option to foreign_key column by default, also if I set in the tag AutoIncrement:false.

Also, the default type for int is BIGINT and for string is LONGTEXT. In my opinion, it should be INT and VARCHAR(255) (Rails default types).

jinzhu commented 4 years ago

Seems that gorm try to add auto_increment option to foreign_key column by default, also if I set in the tag AutoIncrement:false.

GORM WON'T add auto_increment option to foreign_key column by default, but GORM will use your primary key's data type as foreign key's data type to make sure they are using same data type, then we can create a database foreign key.

The issue is caused by you are setting type:bigint unsigned auto_increment to user's ID, change your User model to this should be ok.

type User struct {
    ID       uint64 `gorm:"primary_key;type:bigint unsigned"` // unsigned bigint is uint64's default data type, so it is not necessary.
    Username string
}

Also, the default type for int is BIGINT

You can use int32 or int16

and for string is LONGTEXT. In my opinion, it should be INT and VARCHAR(255) (Rails default types).

Change the DefaultStringSize when initializing MySQL driver

https://github.com/go-gorm/mysql/blob/544aababdcc9ddc4c75c4056ae46c7c01b07b911/mysql.go#L18-L25

pioz commented 4 years ago

@jinzhu I'm trying to migrate this model:

type User struct {
    Id        uint64
    Email     string    `gorm:"type:varchar(255);not null;index:,unique"`
    Username  string    `gorm:"type:varchar(255);not null;default:'foo'"`
}

But I get Error 1064: You have an error in your SQL syntax;:

CREATE TABLE `users` (`id` bigint unsigned AUTO_INCREMENT,`email` varchar(255) NOT NULL,`username` varchar(255) NOT NULL DEFAULT foo,PRIMARY KEY (`id`),INDEX idx_users_email (`email`))

Seems that the default value is not quoted. How can I set the default value? I need to set also the blank string as default value (name varchar(255) NOT NULL DEFAULT '').

Also the index on email field is not unique, also if the tag specify unique. Thank you!

jinzhu commented 4 years ago

@jinzhu I'm trying to migrate this model:

type User struct {
  Id        uint64
  Email     string    `gorm:"type:varchar(255);not null;index:,unique"`
  Username  string    `gorm:"type:varchar(255);not null;default:'foo'"`
}

But I get Error 1064: You have an error in your SQL syntax;:

CREATE TABLE `users` (`id` bigint unsigned AUTO_INCREMENT,`email` varchar(255) NOT NULL,`username` varchar(255) NOT NULL DEFAULT foo,PRIMARY KEY (`id`),INDEX idx_users_email (`email`))

Seems that the default value is not quoted. How can I set the default value? I need to set also the blank string as default value (name varchar(255) NOT NULL DEFAULT '').

Also the index on email field is not unique, also if the tag specify unique. Thank you!

tests passed, make sure you are using latest release of GORM and MySQL driver.

https://github.com/go-gorm/gorm/blob/fb56fe993af7ce155662c17fd24f94722fb3a8eb/tests/default_value_test.go#L9-L37

pioz commented 4 years ago

@jinzhu I'm trying to migrate this model:

type User struct {
    Id        uint64
    Email     string    `gorm:"type:varchar(255);not null;index:,unique"`
    Username  string    `gorm:"type:varchar(255);not null;default:'foo'"`
}

But I get Error 1064: You have an error in your SQL syntax;:

CREATE TABLE `users` (`id` bigint unsigned AUTO_INCREMENT,`email` varchar(255) NOT NULL,`username` varchar(255) NOT NULL DEFAULT foo,PRIMARY KEY (`id`),INDEX idx_users_email (`email`))

Seems that the default value is not quoted. How can I set the default value? I need to set also the blank string as default value (name varchar(255) NOT NULL DEFAULT ''). Also the index on email field is not unique, also if the tag specify unique. Thank you!

tests passed, make sure you are using latest release of GORM and MySQL driver.

https://github.com/go-gorm/gorm/blob/fb56fe993af7ce155662c17fd24f94722fb3a8eb/tests/default_value_test.go#L9-L37

I've upgraded to gorm v0.2.15 and MySQL driver v0.2.6, but the issue persists (I can't find a way to specify a default value). This seems to happen only when I use type:VARCHAR(255);default:'foo'.

The index is now created correctly (unique).

jinzhu commented 4 years ago

This seems to happen only when I use type:VARCHAR(255);default:'foo'.

Yes, I can reproduce it with the type:VARCHAR(255);, fixed it in v0.2.16.

BTW, it not necessary to set the data type like this, you can just set the size then GORM can generate type compatible with all database drivers

type User struct {
  Name string  `gorm:"size:255;not null;index:,unique"`
}

For MySQL, if you want to change all string fields's default size to 255, you can initialize *gorm.DB like this:

db, err := gorm.Open(mysql.New(mysql.Config{
  DSN: "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local", 
  DefaultStringSize: 255,
}), &gorm.Config{})
pioz commented 4 years ago

This seems to happen only when I use type:VARCHAR(255);default:'foo'.

Yes, I can reproduce it with the type:VARCHAR(255);, fixed it in v0.2.16.

@jinzhu Now works. But, if I use the following tag the default value is not set (default to blank string):

Name string `gorm:"size:255;not null;default:''"`

In this way when I run a query from cli I have to set the name field:

INSERT INTO users (email) VALUES ('foo@bar.com'); -- does not work
INSERT INTO users (email,name) VALUES ('foo@bar.com', '');

I think is this if https://github.com/go-gorm/gorm/blob/c5feff1591518ba500898dc6d1a5b8eb7bee1092/migrator/migrator.go#L67

jinzhu commented 4 years ago

@jinzhu Now works. But, if I use the following tag the default value is not set (default to blank string):

Name string `gorm:"size:255;not null;default:''"`

Fixed it, update to v0.2.17, thank you for your report.

pioz commented 4 years ago

@jinzhu I have the follow schema:

CREATE TABLE `users` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `username` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `first_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `last_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_users_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

My GO model is:

type User struct {
    ID        uint64    `faker:"-"`
    Email     string    `gorm:"size:255;not null;index:,unique" faker:"email,unique"`
    Username  string    `gorm:"size:255;not null;default:''"`
    FirstName string    `gorm:"size:255;not null;default:''"`
    LastName  string    `gorm:"size:255;not null;default:''"`
    CreatedAt time.Time `gorm:"type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP" faker:"-"`
    UpdatedAt time.Time `gorm:"type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" faker:"-"`
}

Now I wish to create a new user with:

u := User{Email: "foo@bar.com"}
db.Create(&u)

but I get the following error:

Error 1292: Incorrect datetime value: '0000-00-00' for column 'created_at' at row 1
[0.544ms] [rows:0] INSERT INTO `users` (`email`,`username`,`first_name`,`last_name`,`created_at`,`updated_at`) VALUES ("foo@bar.com","","","","0000-00-00 00:00:00","0000-00-00 00:00:00")

(notice that is strange that the error message say ...Incorrect datetime value: '0000-00-00' for... instead of ...Incorrect datetime value: '0000-00-00 00:00:00' for...)

Is it possible to achieve this behavior, or I need to use a pointer *time.Time? I've seen that similar behavior works for string datatype:

type User struct {
    ID        uint64    `faker:"-"`
    ...
    Username  string    `gorm:"size:255;not null;default:'foo'"`
    ...
}
u := User{Email: "foo@bar.com"}
db.Create(&u)

create a record with field username equal to "foo" (the default value).

seriallink commented 4 years ago

@jinzhu, it seems that ignored columns are not being ignored on select.

type Person struct {
    Id    uuid.UUID `gorm:"column:id;primary_key"`
    Code  string    `gorm:"column:code"`
    Name  string    `gorm:"column:name"`
    Email string    `gorm:"-"`
}

person := &Person{
    Code:  "abc",
    Email: "test@test.com",
}

if err := db.Where(person).Find(person); err != nil {
    panic(err)
}
pq: column person.Email does not exist
SELECT * FROM "person" WHERE "person"."code" = 'abc' AND "person"."Email" = 'test@test.com'

My real case is much more complex than that, I just can't simply not set the field.

jinzhu commented 4 years ago

@jinzhu I have the follow schema:

CREATE TABLE `users` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `username` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `first_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `last_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_users_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

My GO model is:

type User struct {
  ID        uint64    `faker:"-"`
  Email     string    `gorm:"size:255;not null;index:,unique" faker:"email,unique"`
  Username  string    `gorm:"size:255;not null;default:''"`
  FirstName string    `gorm:"size:255;not null;default:''"`
  LastName  string    `gorm:"size:255;not null;default:''"`
  CreatedAt time.Time `gorm:"type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP" faker:"-"`
  UpdatedAt time.Time `gorm:"type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" faker:"-"`
}

Now I wish to create a new user with:

u := User{Email: "foo@bar.com"}
db.Create(&u)

but I get the following error:

Error 1292: Incorrect datetime value: '0000-00-00' for column 'created_at' at row 1
[0.544ms] [rows:0] INSERT INTO `users` (`email`,`username`,`first_name`,`last_name`,`created_at`,`updated_at`) VALUES ("foo@bar.com","","","","0000-00-00 00:00:00","0000-00-00 00:00:00")

(notice that is strange that the error message say ...Incorrect datetime value: '0000-00-00' for... instead of ...Incorrect datetime value: '0000-00-00 00:00:00' for...)

Is it possible to achieve this behavior, or I need to use a pointer *time.Time? I've seen that similar behavior works for string datatype:

type User struct {
  ID        uint64    `faker:"-"`
    ...
  Username  string    `gorm:"size:255;not null;default:'foo'"`
    ...
}
u := User{Email: "foo@bar.com"}
db.Create(&u)

create a record with field username equal to "foo" (the default value).

If you set the data type to *time.Time or NullTime it should work.

But for a CreatedAt time field, GORM should set current time to the field when creating, it wasn't due to you customized its data type, have fixed this issue in v0.2.18

jinzhu commented 4 years ago

@jinzhu, it seems that ignored columns are not being ignored on select.

type Person struct {
  Id    uuid.UUID `gorm:"column:id;primary_key"`
  Code  string    `gorm:"column:code"`
  Name  string    `gorm:"column:name"`
  Email string    `gorm:"-"`
}

person := &Person{
  Code:  "abc",
  Email: "test@test.com",
}

if err := db.Where(person).Find(person); err != nil {
  panic(err)
}
pq: column person.Email does not exist
SELECT * FROM "person" WHERE "person"."code" = 'abc' AND "person"."Email" = 'test@test.com'

My real case is much more complex than that, I just can't simply not set the field.

Please refer: https://github.com/go-gorm/gorm/wiki/GORM-V2-Release-Note-Draft#field-permissions-support-readonly-writeonly-createonly-updateonly-ignored

We have introduced field permission support, supports: readonly, writeonly, createonly, updateonly, ignored

We should use readonly in this case.

type User struct {
  Name string `gorm:"<-:create"` // allow read and create
  Name string `gorm:"<-:update"` // allow read and update
  Name string `gorm:"<-"`        // allow read and write (create and update)
  Name string `gorm:"->:false;<-:create"` // createonly
  Name string `gorm:"->"` // readonly
  Name string `gorm:"-"`  // ignored
}
seriallink commented 4 years ago

@jinzhu, it seems that ignored columns are not being ignored on select.

type Person struct {
    Id    uuid.UUID `gorm:"column:id;primary_key"`
    Code  string    `gorm:"column:code"`
    Name  string    `gorm:"column:name"`
    Email string    `gorm:"-"`
}

person := &Person{
    Code:  "abc",
    Email: "test@test.com",
}

if err := db.Where(person).Find(person); err != nil {
    panic(err)
}
pq: column person.Email does not exist
SELECT * FROM "person" WHERE "person"."code" = 'abc' AND "person"."Email" = 'test@test.com'

My real case is much more complex than that, I just can't simply not set the field.

Please refer: https://github.com/go-gorm/gorm/wiki/GORM-V2-Release-Note-Draft#field-permissions-support-readonly-writeonly-createonly-updateonly-ignored

We have introduced field permission support, supports: readonly, writeonly, createonly, updateonly, ignored

We should use readonly in this case.

type User struct {
  Name string `gorm:"<-:create"` // allow read and create
  Name string `gorm:"<-:update"` // allow read and update
  Name string `gorm:"<-"`        // allow read and write (create and update)
  Name string `gorm:"->:false;<-:create"` // createonly
  Name string `gorm:"->"` // readonly
  Name string `gorm:"-"`  // ignored
}

I tried the readonly notation, same error.

pq: column person.email does not exist
[17.047ms] [rows:0] SELECT * FROM "person" WHERE "person"."code" = 'abc' AND "person"."email" = 'test@test.com'

The only difference is that the column name is parsed in lowercase.

jinzhu commented 4 years ago

I tried the readonly notation, same error.

Could you create a reproducible pull request here? https://github.com/go-gorm/playground

Thank you.

seriallink commented 4 years ago

I tried the readonly notation, same error.

Could you create a reproducible pull request here? https://github.com/go-gorm/playground

Thank you.

https://github.com/go-gorm/playground/pull/8

suciuvlad commented 4 years ago

@jinzhu Is the NewRecord method going to be included in v2?

seriallink commented 4 years ago

@jinzhu, isn't gorm throwing ErrRecordNotFound anymore?

jinzhu commented 4 years ago

@jinzhu Is the NewRecord method going to be included in v2?

NewRecord just checked the primary key value is blank or not, many people misused it, so we decided to remove this method.

@jinzhu, isn't gorm throwing ErrRecordNotFound anymore?

Only throw ErrRecordNotFound error when you are using First, Last, Take to find a record.

moul commented 4 years ago

I just tried to convert a project from gorm v1 to gorm v2, the callback API seems to be harder to use, on V1, I was doing:

func beforeCreate(sfn *snowflake.Node) func(*gorm.Scope) {
    return func(scope *gorm.Scope) {
        id := sfn.Generate().Int64()
        if err := scope.SetColumn("ID", id); err != nil {
            panic(err)
        }
    }
}

on V2, this is how I made it work:

func beforeCreate(sfn *snowflake.Node) func(*gorm.DB) {
    return func(db *gorm.DB) {
        s := reflect.ValueOf(db.Statement.Dest).Elem()
        if s.Kind() == reflect.Struct {
            f := s.FieldByName("ID")
            if f.IsValid() && f.CanSet() {
                id := sfn.Generate().Int64()
                f.SetInt(id)
            }
        }
    }
}

in both cases, it's registered like this:

db.Callback().Create().Before("gorm:create").Register("snowflake_id", beforeCreate(sfn))

Is there already an easier method available? Do you have plans to add new helpers that may help?

Thank you

jinzhu commented 4 years ago

I just tried to convert a project from gorm v1 to gorm v2, the callback API seems to be harder to use, on V1, I was doing:

func beforeCreate(sfn *snowflake.Node) func(*gorm.Scope) {
  return func(scope *gorm.Scope) {
      id := sfn.Generate().Int64()
      if err := scope.SetColumn("ID", id); err != nil {
          panic(err)
      }
  }
}

on V2, this is how I made it work:

func beforeCreate(sfn *snowflake.Node) func(*gorm.DB) {
  return func(db *gorm.DB) {
      s := reflect.ValueOf(db.Statement.Dest).Elem()
      if s.Kind() == reflect.Struct {
          f := s.FieldByName("ID")
          if f.IsValid() && f.CanSet() {
              id := sfn.Generate().Int64()
              f.SetInt(id)
          }
      }
  }
}

in both cases, it's registered like this:

db.Callback().Create().Before("gorm:create").Register("snowflake_id", beforeCreate(sfn))

Is there already an easier method available? Do you have plans to add new helpers that may help?

Thank you

db.Statement.Schema.LookUpField("ID").Set(db.Statement.ReflectValue, sfn.Generate().Int64())
moul commented 4 years ago

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())
        }
}
seriallink commented 4 years ago

@jinzhu, regarding many2many relationship

type User struct {
    IdUser    int64     `gorm:"column:idUser;primary_key"`
    Companies []Company `gorm:"many2many:credential;ForeignKey:IdUser;References:IdCompany"`
}

type Company struct {
    IdCompany int64 `gorm:"column:idCompany;primary_key"`
}

db, _ := gorm.Open(sqlite.Open("gorm"), &gorm.Config{
    DryRun: true,
    NamingStrategy: schema.NamingStrategy{
        SingularTable: true,
    },
    Logger: logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{
        LogLevel: logger.Info,
    }),
})

db.Model(User{}).Association("Companies").Find([]Company{})
SELECT *
  FROM `company`
  JOIN `credentials` ON `credentials`.`company_id_company` = `company`.`idCompany`
   AND `user_id_user` IN (NULL)
  1. Naming Strategy seems not to be working properly for junction table
  2. If I'm not mistaken, ForeignKey and References should do the trick when not following the naming convention
jinzhu commented 4 years ago

@jinzhu, regarding many2many relationship

type User struct {
  IdUser    int64     `gorm:"column:idUser;primary_key"`
  Companies []Company `gorm:"many2many:credential;ForeignKey:IdUser;References:IdCompany"`
}

type Company struct {
  IdCompany int64 `gorm:"column:idCompany;primary_key"`
}

db, _ := gorm.Open(sqlite.Open("gorm"), &gorm.Config{
  DryRun: true,
  NamingStrategy: schema.NamingStrategy{
      SingularTable: true,
  },
  Logger: logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{
      LogLevel: logger.Info,
  }),
})

db.Model(User{}).Association("Companies").Find([]Company{})
SELECT *
  FROM `company`
  JOIN `credentials` ON `credentials`.`company_id_company` = `company`.`idCompany`
   AND `user_id_user` IN (NULL)
  1. Naming Strategy seems not to be working properly for junction table
  2. If I'm not mistaken, ForeignKey and References should do the trick when not following the naming convention

Fixed that, thank you for your report. https://github.com/go-gorm/gorm/commit/2d048d9ece097f86ecf77872ba050c0ce242bfc0

ghost commented 4 years ago

2.0是否支持查询后对数据处理? 类似于获取器的功能!!

jinzhu commented 4 years ago

I added the SetColumn method back, also added a new method Changed, please check out https://github.com/go-gorm/gorm/wiki/GORM-V2-Release-Note-Draft#use-changed-to-check-fields-changed-or-not

@moul

efrengarcial commented 4 years ago

Hi @jinzhu,

Is it possible to continue using the date function in queries in v2?

db.Model(&Object{}).Select("date(last_reading)").Group("date(in_objects.last_reading)")

First of all, Thanks!

seriallink commented 4 years ago

@jinzhu, regarding many2many relationship

type User struct {
    IdUser    int64     `gorm:"column:idUser;primary_key"`
    Companies []Company `gorm:"many2many:credential;ForeignKey:IdUser;References:IdCompany"`
}

type Company struct {
    IdCompany int64 `gorm:"column:idCompany;primary_key"`
}

db, _ := gorm.Open(sqlite.Open("gorm"), &gorm.Config{
    DryRun: true,
    NamingStrategy: schema.NamingStrategy{
        SingularTable: true,
    },
    Logger: logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{
        LogLevel: logger.Info,
    }),
})

db.Model(User{}).Association("Companies").Find([]Company{})
SELECT *
  FROM `company`
  JOIN `credentials` ON `credentials`.`company_id_company` = `company`.`idCompany`
   AND `user_id_user` IN (NULL)
  1. Naming Strategy seems not to be working properly for junction table
  2. If I'm not mistaken, ForeignKey and References should do the trick when not following the naming convention

Fixed that, thank you for your report. 2d048d9

@jinzhu

SingularTable looks good now, thank you! But I still have issues regarding column naming convention.

I expected this:

SELECT * 
  FROM `company` 
  JOIN `credential` ON `credential`.`idCompany` = `company`.`idCompany` 
   AND `credential`.`idUser` IN (NULL)

But I got this:

SELECT * 
  FROM `company` 
  JOIN `credential` ON `credential`.`company_id_company` = `company`.`idCompany` 
   AND `user_id_user` IN (NULL)

Am I doing something wrong here? Is it possible to set column name in junction table using tags?

Companies []Company `gorm:"many2many:credential;ForeignKey:IdUser;References:IdCompany"`
jinzhu commented 4 years ago

@jinzhu, regarding many2many relationship

type User struct {
  IdUser    int64     `gorm:"column:idUser;primary_key"`
  Companies []Company `gorm:"many2many:credential;ForeignKey:IdUser;References:IdCompany"`
}

type Company struct {
  IdCompany int64 `gorm:"column:idCompany;primary_key"`
}

db, _ := gorm.Open(sqlite.Open("gorm"), &gorm.Config{
  DryRun: true,
  NamingStrategy: schema.NamingStrategy{
      SingularTable: true,
  },
  Logger: logger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{
      LogLevel: logger.Info,
  }),
})

db.Model(User{}).Association("Companies").Find([]Company{})
SELECT *
  FROM `company`
  JOIN `credentials` ON `credentials`.`company_id_company` = `company`.`idCompany`
   AND `user_id_user` IN (NULL)
  1. Naming Strategy seems not to be working properly for junction table
  2. If I'm not mistaken, ForeignKey and References should do the trick when not following the naming convention

Fixed that, thank you for your report. 2d048d9

@jinzhu

SingularTable looks good now, thank you! But I still have issues regarding column naming convention.

I expected this:

SELECT * 
  FROM `company` 
  JOIN `credential` ON `credential`.`idCompany` = `company`.`idCompany` 
   AND `credential`.`idUser` IN (NULL)

But I got this:

SELECT * 
  FROM `company` 
  JOIN `credential` ON `credential`.`company_id_company` = `company`.`idCompany` 
   AND `user_id_user` IN (NULL)

Am I doing something wrong here? Is it possible to set column name in junction table using tags?

Companies []Company `gorm:"many2many:credential;ForeignKey:IdUser;References:IdCompany"`

image

https://github.com/go-gorm/gorm/wiki/GORM-V2-Release-Note-Draft#association-improvements

jinzhu commented 4 years ago

Hi @jinzhu,

Is it possible to continue using the date function in queries in v2?

db.Model(&Object{}).Select("date(last_reading)").Group("date(in_objects.last_reading)")

First of all, Thanks!

Yes, it is expected to work, I just submitted a commit to supporting SQL function for Group. https://github.com/go-gorm/gorm/commit/ee1f46e3a1295f2342e72d5da9dc33f8a2a2a9d5

seriallink commented 4 years ago

image

https://github.com/go-gorm/gorm/wiki/GORM-V2-Release-Note-Draft#association-improvements

I tried using both tags before

Companies []Company `gorm:"many2many:credential;ForeignKey:IdUser;JoinForeignKey:IdUser;References:IdCompany;JoinReferences:IdCompany"`

But GORM still forces the naming convention

SELECT * 
  FROM `company` 
  JOIN `credential` ON `credential`.`id_company` = `company`.`idCompany` 
   AND `id_user` IN (NULL)
jinzhu commented 4 years ago

This is the expected behavior, GORM will use the default naming convention to convert the specified foreign key, change the default naming strategy here. https://github.com/go-gorm/gorm/blob/master/gorm.go#L22 @seriallink

aveyuan commented 4 years ago

use Count exec error

    var all []Album
    var count int64
    if key == "*" {
        if err := db.Model(&Album{}).Count(&count).Order("id desc").Limit(limit).Offset((page - 1) * limit).Find(&all).Error; err != nil {
            return all, 0
        }
        return all, count
    }

I use this code exec is error.

show the log


[0.129ms] [rows:1] SELECT count(1) FROM `albums`

[0.286ms] [rows:1] SELECT count(1) FROM `albums` ORDER BY id desc LIMIT 10
jinzhu commented 4 years ago

use Count exec error

  var all []Album
  var count int64
  if key == "*" {
      if err := db.Model(&Album{}).Count(&count).Order("id desc").Limit(limit).Offset((page - 1) * limit).Find(&all).Error; err != nil {
          return all, 0
      }
      return all, count
  }

I use this code exec is error.

show the log


[0.129ms] [rows:1] SELECT count(1) FROM `albums`

[0.286ms] [rows:1] SELECT count(1) FROM `albums` ORDER BY id desc LIMIT 10

Fixed that. https://github.com/go-gorm/gorm/commit/d02b592c6cd276c169ade515b8999132def9e555

SujalKokh commented 4 years ago

@jinzhu We are eagerly waiting for the release of Go ORM V2.0. Lots of my headaches will be solved with version 2.0.

jinzhu commented 4 years ago

@jinzhu We are eagerly waiting for the release of Go ORM V2.0. Lots of my headaches will be solved with version 2.0.

Hi @SujalKokh, consider migrating your project to v2.0 now :) It has been used in some production services already, and if you found any issues, most of them should be resolved in less than 24 hours.

SujalKokh commented 4 years ago

How do I integrate the Go ORM version 2.0 with my Go app? Running go get -u github.com/jinzhu/gorm installs GORM version 1.9.14. Please help me.

jinzhu commented 4 years ago

https://github.com/go-gorm/gorm/wiki/GORM-V2-Release-Note-Draft#install

fr3fou commented 4 years ago

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

kingwill101 commented 4 years ago

@jinzhu will v2 work with qor admin?

aveyuan commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

@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 commented 4 years ago

the value of JoinForeignKey:model_a_id;JoinReferences:model_b_id needs to be upper case, but I have handled that in lastest commit. https://github.com/go-gorm/gorm/commit/d4f8a524423baf81aecfc6caf2780eb14e2eb187

Icaro-Lima commented 4 years ago

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

kumarsiva07 commented 4 years ago

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 commented 4 years ago

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.

SujalKokh commented 4 years ago

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.