GORM unable to update data in one to many relationship - postgresql

I have two tables users and documents. They are related in such a way that each document must belong to a user using a one to many relationship. When I try updating a document I get the following error
ERROR: insert or update on table "documents" violates foreign key
constraint "fk_users_documents" (SQLSTATE 23503)
Here are my structs definition and update function
type User struct {
gorm.Model
Name string
Email string
Password string
Documents []Document
}
type Document struct {
gorm.Model
Name string
UserID uint
}
//Update document by id
func (h handler)UpdateDocument(w http.ResponseWriter, r *http.Request) {
// once again, we will need to parse the path parameters
var updatedDoc Document
reqBody, _ := ioutil.ReadAll(r.Body)
json.Unmarshal(reqBody, &updatedDoc)
var document Document
vars := mux.Vars(r)
id := vars["id"]
if result := Db.First(&updatedDoc, id); result.Error != nil {
fmt.Println(result.Error)
}
document.Name=updatedDoc.Name
Db.Save(&document)
json.NewEncoder(w).Encode(&updatedDoc)
}

You are calling Db.Save(&document) but document has only its Name field populated. This means that the UserID is set to 0. I'm guessing you don't have any user with ID 0 present in the User table, therefore this violates the foreign key constraint.
The UserID field shall always be set to an existing user when updating a document otherwise the query will fail.
Regardless of this, I'd suggest you to study a bit of database and golang basics because the code you posted is quite messy.

Related

Gorm Association Delete does not remove rows, instead update rows

A client has many Roles. I want to delete all Roles once a client is deleted.
type Client struct {
Id string `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
Roles [] Role
}
type Role struct {
Id uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
ClientID string
}
return db.Transaction(func(tx *gorm.DB) error {
err = db.Model(&clientToRemove).Association("Roles").Delete(&clientToRemove.Roles)
if err != nil {
return err
}
err = db.Delete(&clientToRemove).Error
if err != nil {
return err
}
return nil
})
I expect related rows in role to be removed, instead of delete query, it executes an update query to remove client_id.
[210.834ms] [rows:1] UPDATE "role" SET "client_id"=NULL WHERE "role"."client_id" = 'xxxxxxxxxxx' AND "role"."id" = 9
How to completely remove rows in associated role table?
Database is Postgres
As stated in the documentation, the delete with association operation will just remove the references between Client and TenantRole. In your case, it just updated the TenantRole records to set the client_id to NULL.
If you want to delete the objects as well, you can try using Select with the delete operation. Please note that this only works if the primary key is not zero, so your query might look something like this:
err = db.Select("TenantRoles").Delete(&Client{Id: clientId}).Error
or just use clientToRemove if it already has the Id field populated
err = db.Select("TenantRoles").Delete(&clientToRemove).Error

Update fields create new foreignkey items instead of updating them

I've got a one-to-one relationship, Location, working with postgresql:
type App struct {
gorm.Model
PersoID string `gorm:"primary_key;unique" json:"perso_id"`
Location OllyLocation `gorm:"foreignkey:location_id"`
LocationID *uint `json:"-" gorm:"type:integer REFERENCES locations(id)"`
Users []User `json:"users,omitempty" gorm:"many2many:app_users;"`
}
type Location struct {
ID uint `json:"-" gorm:"primary_key;unique"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time
Lat float64 `json:"lat"`
Long float64 `json:"long"`
Address string `json:"address"`
Country string `json:"country"`
}
But as you can see, the App structure has its own primaryKey id as string (and the GORM one).
Thus this block from doing a simple db.Save() from gorm.
So I've tried to do:
func (previousModel *App) UpdateAndReturnedUpdated(db *gorm.DB, appUpdated *App) error {
if err := db.Model(previousModel).Update(appUpdated).Error; err != nil {
return err
}
return db.Preload("Location").First(previousModel, App{PersoID: appUpdated.PersoID}).Error
}
With this, the App is correctly updated - fantastic! - but not the Location.
The location is re created in the db with the new values and the foreignKey LocationID is updated on the App.
This is super annoying because if I want to do an update of the address only without specifying other fields, they are just re created with the original type value (lat / long will be 0)
Any ideas about how to update the foreignKey? Thanks!
You can use this to not modify the associated entities
db.Set("gorm:save_associations", false).Save(previousModel)
or to save only one field of the struct
db.Model(previousModel).UpdateColumn("PersoID", appUpdated.PersoID)
if you want to update an associated entity and not the relation
you will need to update the entity itself
db.Model(Location{ID:*previousModel.LocationID}).Update(appUpdated.Location)
Well, once more (but let's not blame the guy, he is alone) the main problem comes from an incomplete documentation.
All my problems were solved when I checked that I properly preloaded my foreign object + indicate it's ID (which blocked the fact that the foreignKey was Inserted instead of Updated)
Then, to be able to update it properly I had to do:
db.
Model(previousModel).
UpdateColumns(appUpdated).
Model(&previousModel.Location).
Updates(appUpdated.Location)
Was very painful, but at least it's solved.

How to insert a document with mgo and get the value returned

For the record, I'm learning Go. I'm trying to use and the mgo package and I'd like to insert a new document and return this newly created document to user (I'm trying to write a basic API). I've wrote the following code:
EDIT: Here's the struct for the model:
type Book struct {
ISBN string `json:"isbn"`
Title string `json:"title"`
Authors []string `json:"authors"`
Price string `json:"price"`
}
session := s.Copy()
defer session.Close()
var book Book
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&book)
if err != nil {
ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
return
}
c := session.DB("store").C("books")
info, err := c.Upsert(nil, book)
if err != nil {
ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed insert book: ", err)
return
}
respBody, err := json.MarshalIndent(info, "", " ")
if err != nil {
log.Fatal(err)
}
ResponseWithJSON(w, respBody, http.StatusOK)
Please note that Book is a struct I have created earlier. The above code does work but what it returns is the upsert result like so:
{
"Updated": 1,
"Removed": 0,
"Matched": 1,
"UpsertedId": null
}
Which is not the recently created object. How can I get the the recently created object to return as a response (please note that ideally I'd like the confirmation that the document was successfully inserted. I have seen other questions where the ID is generated beforehand but for what I've seen it doesn't confirm that the document was created was it?)
Let's clear the concepts first. In MongoDB, each document must have an _id property which acts as its unique document identifier inside a collection. Either you provide the value of this _id or it is assigned automatically by MongoDB.
So it would be ideal (or it's strongly recommended) for your model types to include a field for the _id document identifier. Since we're talking about books here, books already have a unique identifier called ISBN, which you may opt to use as the value of the _id field.
The mapping between MongoDB fields and Go struct fields must be specified using the bson tag (not json). So you should provide bson tag values along with json tags.
So change your model to:
type Book struct {
ISBN string `json:"isbn" bson:"_id"`
Title string `json:"title" bson:"title"`
Authors []string `json:"authors" bson:"authors"`
Price string `json:"price" bson:"price"`
}
If you want to insert a new document (a new book), you should always use Collection.Insert().
And what will be the ID of the newly inserted document? The field you set to the Book.ISBN field as we declared it to be the document ID with the bson:"_id" tag.
You should only use Collection.Upsert() if you are not sure whether the document already exists, but either way you want it to be the document you have at hand. Collection.Upsert() will try to find a document to update, and if one is found, that will be updated. If no document is found, then an insert operation will be performed. The first parameter is the selector to find the document to be updated. Since you passed nil, that means any document may qualify, so one will be selected "randomly". So if you already have books saved, any may get selected and get overwritten. This is certainly not want you want.
Since now the ISBN is the ID, you should specify a selector that filters by ISBN, like this:
info, err := c.Upsert(bson.M{"_id": book.ISBN}, book)
Or since we're filtering by ID, use the more convenient Collection.UpsertId():
info, err := c.UpsertId(book.ISBN, book)
If you want to update an existing document, for that you may use Collection.Update(). This is similar to Collection.Upsert(), but the difference is that if no documents match the selector, an insert will not be performed. Updating a document matched by ID can also be done with the more convenient Collection.UpdateId() (which is analogue to Collection.UpsertId()).
For other documents which do not have a unique identifier naturally (like books having ISBN), you may use generated IDs. The mgo library provides the bson.NewObjectId() function for such purpose, which returns you a value of type bson.ObjectId. When saving new documents with Collection.Insert(), you can acquire a new unique ID with bson.NewObjectId() and assign it to the struct field that is mapped to the MongoDB _id property. If the insert succeeds, you can be sure the document's ID is what you just set before calling Collection.Insert(). This ID generation is designed to work even in a distributed environment, so it will generate unique IDs even if 2 of your nodes attempt to generate an ID at the same time.
So for example if you don't have the ISBN for a book when saving it, then you must have a separate, designated ID field in your Book type, for example:
type Book struct {
ID bson.ObjectId `bson:"_id"`
ISBN string `json:"isbn" bson:"isbn"`
Title string `json:"title" bson:"title"`
Authors []string `json:"authors" bson:"authors"`
Price string `json:"price" bson:"price"`
}
And when saving a new book:
var book Book
// Fill what you have about the book
book.ID = bson.NewObjectId()
c := session.DB("store").C("books")
err = c.Insert(book)
// check error
// If no error, you can refer to this document via book.ID
Banged my head for a while on this.
info, err := c.Upsert(nil, book)
The nil selector for the upsert will match everything so if your collection is empty the selector won't match and all will be fine, the info object will contain the ObjectId in the UpsertedId field, but on every following upsert with nil selector the collection will have records and the nil selector for the upsert will match, therefore it won't return you the UpsertedId and you will be updating the first matched record.
You could use never matching selector for the upsert (which was my solution), but note that this way you will only insert and get the inserted ObjectId and never update a record.
You can checkout the following test:
func TestFoo(t *testing.T) {
foo := struct {
Bar string `bson:"bar"`
}{
Bar: "barrr",
}
session, _ := mgo.DialWithInfo(&mgo.DialInfo{
Addrs: []string{"127.0.0.1:27017"},
})
coll := session.DB("foo").C("bar")
coll.DropCollection()
info, _ := coll.Upsert(nil, foo) // will insert
count, _ := coll.Count()
fmt.Printf("%+v, collecton records:%v\n", info, count) // &{Updated:0 Removed:0 Matched:0 UpsertedId:ObjectIdHex("5c35e8ee1f5b80c932b44afb")}, collection records:1
info, _ = coll.Upsert(bson.M{}, foo) // will update
count, _ = coll.Count()
fmt.Printf("%+v, collecton records:%v\n", info, count) // &{Updated:1 Removed:0 Matched:1 UpsertedId:<nil>}, collection records:1
info, _ = coll.Upsert(bson.M{"nonsense": -1}, foo) // will insert duplicate record (due to the selector that will never match anything)
count, _ = coll.Count()
fmt.Printf("%+v, collecton records:%v\n", info, count) // &{Updated:0 Removed:0 Matched:0 UpsertedId:ObjectIdHex("5c35ea2a1f5b80c932b44b1d")}, collection records:2
foo.Bar = "baz"
info, _ = coll.Upsert(nil, foo) // will update the first matched (since the selector matches everything)
count, _ = coll.Count()
fmt.Printf("%+v, collecton records:%v\n", info, count)
// after the test the collection will have the following records
//> use foo
//> db.bar.find()
//{ "_id" : ObjectId("5c35ebf41f5b80c932b44b81"), "bar" : "baz" } // the first matched on the last update with nil selector
//{ "_id" : ObjectId("5c35ebf41f5b80c932b44b86"), "bar" : "barrr" } // the duplicated record with the selector that never matches anything
}
EDIT: Note that you should have an index on the never matching field, or your insert query will take too long if you have many records, because the upsert on not indexed filed will scan all the documents in the collection.

Foreign key not created with one to many association

I have a Task type that has a list of Runner type objects in it. I am trying to map it to database using golang gorm but it doesn't have foreign key and i am getting invalid association during migration
My Task struct:
type Task struct {
gorm.Model
Name string `gorm:"not null;unique_index"`
Description string
Runners []Runner
}
My Runner struct:
type Runner struct {
gorm.Model
Name string `gorm:"not null;unique"`
Description string
}
My migration code:
func migrateSchema () (err error) {
db, err := context.DBProvider()
if err != nil {
return
}
db.Model(&Task{}).Related(&Runner{})
db.AutoMigrate(&Task{})
db.AutoMigrate(&Runner{})
return
}
On db.AutoMigrate(&Task{}) I get invalid association message in console and when I check the database there is no foreign key created or no reference field created on runners table
What am I doing wrong?
I had a similar issue, and it took me forever to figure it out. I believe the GORM documentation could definitely be better. Here's the relevant code snippet from the GORM site:
//User has many emails, UserID is the foreign key
type User struct {
gorm.Model
Emails []Email
}
type Email struct {
gorm.Model
Email string
UserID uint
}
db.Model(&user).Related(&emails)
//// SELECT * FROM emails WHERE user_id = 111; // 111 is user's primary key
Why your code isn't working:
First you need to add a TaskID field to your Runner struct.
db.Model(&Task{}).Related(&Runner{}) doesn't do what you think it does. If you look at the code snippet from GORM, the SELECT comment kind of explains it (not very well though). The example is assuming that the &user is already populated and has an ID of 111, then it fetches the emails storing them in &emails that match the UserID of &user.

`gorm` Ignoring `sql:"index"` Tags

Why gorm is ignoring sql:"index" tags? No indexes got created.
Database in use here is PostgreSQL (importing _ "github.com/lib/pq"). This Model struct is used (because default gorm.Model uses an auto increment number - serial - as primary key and I wanted to set id myself):
type Model struct {
ID int64 `sql:"type:bigint PRIMARY KEY;default:0"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
And one of actual models is:
type TUHistory struct {
Model
TUID int64 `json:"tu_id,string" gorm:"column:tu_id" sql:"index"`
}
func (x *TUHistory) TableName() string {
return "tu_history"
}
And the table is created by db.CreateTable(&TUHistory{}) which creates the table correctly except for indexes.
As a temporary work around, I do db.Model(&TUHistory{}).AddIndex("ix_tuh_tu_id", "tu_id") to create indexes.
From my experience, the db.CreateTable only creates the table and it's fields. You are better off using the AutoMigrate function with the model structure that you want to migrate:
db, err := gorm.Open("postgres", connectionString)
...
// error checking
...
db.AutoMigrate(&Model)
Also, I tried AutoMigrating the model you posted and got an error saying that multiple primary keys are not allowed, so I changed the model to:
type Model struct {
Id int64 `sql:"type:bigint;default:0"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
and the AutoMigration created all PKs and indexes just fine.
Edit:
Checking the GORM's README, on this example, the Email structure goes as:
type Email struct {
ID int
UserID int `sql:"index"` // Foreign key (belongs to), tag `index` will create index for this field when using AutoMigrate
Email string `sql:"type:varchar(100);unique_index"` // Set field's sql type, tag `unique_index` will create unique index
Subscribed bool
}
Notice the comment on the UserId field saying it will create the index when using AutoMigrate.
Also, it's worth taking a look at how the AutoMigrate does it's job:
// Automating Migration
db.AutoMigrate(&User{})
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
db.AutoMigrate(&User{}, &Product{}, &Order{})
// Feel free to change your struct, AutoMigrate will keep your database up-to-date.
// AutoMigrate will ONLY add *new columns* and *new indexes*,
// WON'T update current column's type or delete unused columns, to protect your data.
// If the table is not existing, AutoMigrate will create the table automatically.