Auto generate UUID before create with GORM + Postgres + Go Gin + Graphql - postgresql

Is there any way to auto generate UUID with GORM while saving the object in DB in go?
I am having experience with ROR migrations, where ID would be auto generated and PK by default.
Here is my code
Todo.go
package model
type Todo struct {
ID string `json:"id"`
Text string `json:"text"`
Done bool `json:"done"`
}
schema.resolvers.go
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
db, _ := database.connect()
defer db.Close()
todo := &model.Todo{
Text: input.Text,
ID: fmt.Sprintf("%d", rand.Int()), // I don't want to provide ID like this
}
db.Create(&todo)
...
}
models_gen.go
# GraphQL schema example
#
# https://gqlgen.com/getting-started/
type Todo {
id: ID!
text: String!
done: Boolean!
}
input NewTodo {
text: String!
userId: String!
}
type Mutation {
createTodo(input: NewTodo!): Todo!
}
Any help would be really appreciated.

Assuming you're using gorm v2, you can use hooks to achieve this. The documentation about hooks is here
In particular, you can have a look at the BeforeCreate hook. Applied on a model, it provides a container for a function to be run before the creation in the database as its name state.
The GORM documentation also provides an example to generate a UUID when we create a user entity in a database with Gorm, which seems to be what you're looking for:
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()
if !u.IsValid() {
err = errors.New("can't save invalid data")
}
return
}

GORM Hooks is the most obvious solution to generate UUID when creating an object.
func (t *Todo) BeforeCreate(tx *gorm.DB) (err error) {
t.ID = uuid.New().String()
return
}
But if you want to generate UUID for every object, you could define a base struct and embed it into object structs.
type Base struct {
ID string `json:"id"`
}
func (b *Base) BeforeCreate(tx *gorm.DB) (err error) {
b.ID = uuid.New().String()
return
}
type Todo struct {
Base
Text string `json:"text"`
Done bool `json:"done"`
}
type User struct {
Base
Name string `json:"name"`
}
Or just use the GORM plugin next to generate UUID for objects.
import "github.com/invzhi/next"
plugin := NewPlugin()
// register uuid generate function
plugin.Register("uuid", func(_, zero bool) (interface{}, error) {
if !zero {
return nil, SkipField
}
return uuid.New().String()
})
_ = db.Use(plugin)
type Todo struct {
ID string `gorm:"type:varchar(20);column:id;next:uuid"` // next:uuid meaning generate uuid when create
Text string `gorm:"type:varchar(100);column:text`
Done bool `gorm:"type:boolean;column:done`
}
type User struct {
ID string `gorm:"type:varchar(20);column:id;next:uuid"` // next:uuid meaning generate uuid when create
Name string `gorm:"type:varchar(100);column:name"`
}

Related

How to insert a map[string] into a jsonb field

I have a struct Huygens:
type Huygens struct {
Worlds map[string]World `json:"worlds" sql:"type:JSONB"`
}
type World struct {
Diameter int
Name string
}
When I try to insert into a Postgres DB using GORM I get:
sql: converting argument $1 type: unsupported type map[string]huygens.world, a map
To insert I simply use db.Create(&World{})
Does anyone know how to insert that column as a JSONB column and avoid that error?
Thanks
You need to specify that Huygens is a gorm.Model and dump your World struct to a []byte type.
import (
"encoding/json"
"fmt"
"gorm.io/datatypes"
)
type Huygens struct {
gorm.Model
Worlds datatypes.JSON
}
type World struct {
Diameter int
Name string
}
world := World{Diameter: 3, Name: Earth}
b, err := json.Marshal(world)
if err != nil {
fmt.Println("error:", err)
}
db.Create(&Huygens{Worlds: datatypes.JSON(b)})
You can see more here.
Another way, which makes the model more usable, is to create a type that implements Scan/Value, and in those Unmarshal from/Marshal to JSON:
type Huygens struct {
Worlds WorldsMap `json:"worlds"`
}
type World struct {
Diameter int
Name string
}
type WorldsMap map[string]World
func (WorldsMap) GormDataType() string {
return "JSONB"
}
func (w *WorldsMap) Scan(value interface{}) error {
var bytes []byte
switch v := value.(type) {
case []byte:
bytes = v
case string:
bytes = []byte(v)
default:
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
}
err := json.Unmarshal(bytes, w)
return err
}
func (w WorldsMap) Value() (driver.Value, error) {
bytes, err := json.Marshal(s)
return string(bytes), err
}
Then you can use the Worlds field in your code like a map, as opposed to an awkward byte slice.

Unsupported relations in gorm

I'm trying to Preload data from a One to Many relationship, yet I always get an "ApiKeys: unsupported relations for schema Client" error. (The reason structs are pointers is because I'm using gqlgen and that's the default configuration)
type Client struct {
// Client ID
ID int `json:"id"`
UserName string `json:"userName"`
// Client login hashed password
Password string `json:"password"`
// ApiKeys
APIKeys []*APIKey `json:"apiKeys"`
}
type APIKey struct {
// ApiKey Index
ID int `json:"id"`
// ApiKey Value
Key string `json:"key"`
// ApiKey Client Relation
ClientID int `json:"clientID"`
// ApiKey Client Info
Client *Client `json:"client"`
}
And this is the function that calls the Preload of ApiKeys.
func (r *queryResolver) ClientInfoResolver(username string, password string) (*model.Client, error) {
var clients []*model.Client
var client *model.Client
query := r.Resolver.DB
query = query.Where("user_name = ? AND password = ?", username, password).Preload("ApiKeys").Find(&clients)
if query.Error != nil {
return client, query.Error
}
return clients[0], nil
}
I understand by gorm's documentation that the foreign key for the relation is ClientID, despite not being explicit (doesn't work by specifying it either) am I understanding something wrong here?
You list APIKeys as the struct field name but try and use ApiKeys as the FK.
.Preload("ApiKeys")
// Should be
.Preload("APIKeys")
Or, if you want to use ApiKeys as the foreign key, use a Gorm struct tag to do this.
Full working example
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Client struct {
// ApiKey Index
ID int `json:"id"`
UserName string `json:"userName"`
// Client login hashed password
Password string `json:"password"`
// ApiKeys
APIKeys []*APIKey `json:"apiKeys"`
}
type APIKey struct {
// ApiKey Index
ID int `json:"id"`
// ApiKey Value
Key string `json:"key"`
// ApiKey Client Relation
ClientID int `json:"clientID"`
// ApiKey Client Info
Client *Client `json:"client"`
}
func main() {
db, err := gorm.Open(sqlite.Open("many2many.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
err = db.AutoMigrate(&APIKey{}, &Client{})
if err != nil {
fmt.Print(err)
}
clientOne := Client{
UserName: "Client One",
}
db.Create(&clientOne)
apiKeyOne := APIKey{
Key:"one",
Client: &clientOne,
}
apiKeyTwo := APIKey{
Key:"two",
Client: &clientOne,
}
db.Create(&apiKeyOne)
db.Create(&apiKeyTwo)
// Fetch from DB
fetchedClient := Client{}
db.Debug().Preload("APIKeys").Find(&fetchedClient, clientOne.ID)
fmt.Println(fetchedClient)
db.Delete(&clientOne)
db.Delete(&apiKeyOne)
db.Delete(&apiKeyTwo)
}
Has it been resolved? I also encountered the same problem。
when i trying to Preload data from a One to Many relationship,
report:unsupported relations for schema TeacherInfo
github.com/99designs/gqlgen v0.13.0
gorm.io/gorm v1.21.8
models_gen:
func (TeacherInfo) TableName() string {
return "teacher_info"
}
type TeacherInfo struct {
ID int `json:"id" gorm:"primaryKey"`
Name string `json:"name"`
Avatar string `json:"avatar" `
Info string `json:"info" `
Score float64 `json:"score" `
Role int `json:"role" `
TeacherScore []*TeacherScore `json:"teacherScore" gorm:"foreignkey:TID; references:ID"`
}
func (TeacherScore) TableName() string {
return "teacher_score"
}
type TeacherScore struct {
ID int `json:"id" `
TID int `json:"t_id" `
Comment string `json:"comment" `
UID int `json:"u_id" `
Score float64 `json:"score" `
}
resolvers:
func (r *queryResolver) TeacherScore(ctx context.Context, id int) (*model.TeacherInfo, error) {
var teacherInfo model.TeacherInfo
wrong: dao.DB.Debug().Preload("teacher_score").First(&teacherInfo)
right: here is teacherInfo's cloume TeacherScore
dao.DB.Debug().Preload("TeacherScore").First(&teacherInfo)
return &teacherInfo, nil
}
resolved

Get _id from mongoDB using gqlgen

I'm using Go and gqlgen to access my mongoDB database and was wondering how do I access the id field from the database? This is what I have currently and _id returns an empty string
type Post {
_id: ID!
title: String!
content: String!
comments: [Comment!]
author: User!
created: Date!
}
type Query {
post(_id: ID!): Post
...
}
func (r *queryResolver) Post(ctx context.Context, id string) (*model.Post, error) {
var post model.Post
_id, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, err
}
err = db.Posts.FindOne(context.TODO(), bson.D{{Key: "_id", Value: _id}}).Decode(&post)
if err != nil {
return nil, err
}
return &post, nil
}
The ID type in gqlgen creates a string, but _id in mongo is probably a primitive.ObjectId which can create issues depending on how you interact with mongo.
Better to set a bson tag as _id
consider setting the following struct to overwrite the gql generated flow. You can convert the id into a string using the Hex() method.
type Post struct{
ID primitive.ObjectID `bson:"_id" json:"id"`
title: string
content: string
comments: []Comment
author: User
created: time.Time
}
You might want to do this automatically if you have many structs with _ids. To avoid having to overwrite, you can implement a hook to autogenerate the bson tags for you
type Post {
id: ID!
title: String!
content: String!
comments: [Comment!]
author: User!
created: Date!
}
now a new directory in your file structure called "hooks" and create a new file "bson.go"
copy and paste the following
package main
import (
"fmt"
"os"
"github.com/99designs/gqlgen/api"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/plugin/modelgen"
)
func mutateHook(b *modelgen.ModelBuild) *modelgen.ModelBuild {
for _, model := range b.Models {
for _, field := range model.Fields {
name := field.Name
if name == "id" {
name = "_id"
}
field.Tag += ` bson:"` + name + `"`
}
}
return b
}
func main() {
cfg, err := config.LoadConfigFromDefaultLocations()
if err != nil {
fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
os.Exit(2)
}
p := modelgen.Plugin{
MutateHook: mutateHook,
}
err = api.Generate(cfg,
api.NoPlugins(),
api.AddPlugin(&p),
)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(3)
}
}
now in your main.go add this to the top
//go:generate go run hooks/bson.go
now when you run go generate not only will gqlgen do it's normal generation, but it will also add bson tags to all of our models. As well as any field with the name id to have a bson tag of _id
source : https://github.com/99designs/gqlgen/issues/865

scan to a struct fields that some of them are pointers

I'm writing a GO application and I'm trying to find an easy method to scan a row from the database to struct fields.
I use pgx to connect to a postgresql database
gqlgen generated this class:
type Profile struct {
Name string `json:"name"`
JoinedAt time.Time `json:"joined_at"`
Bio *string `json:"bio"`
}
and then I'm got the function to get the user profile from db:
func GetUserProfile(ctx context.Context, profileDir string) (*model.Profile, error) {
sqlQuery := `select name,joined_at::timestamptz,bio from foo where profile_dir=$1`
var profile model.Profile
if err := Connection.QueryRow(ctx, sqlQuery, profileDir).
Scan(&profile.Name, &profile.JoinedAt, &profile.Bio); err != nil {
return nil, err
} else {
return &profile, nil
}
}
now since Bio is a pointer, I need to create a variable who's not a pointer, scan to it and assign it's address after that to the struct:
var profile model.Profile
var mybio string
...
Connection.QueryRow(...).Scan(...,&mybio)
profile.Bio=&mybio
is there an easier way to scan a row to a struct that might have pointers ?
If Bio is already a pointer, you don't need to take an extra pointer in the Scan call:
profile := Profile{
Bio: new(string),
}
if err := Connection.QueryRow(ctx, sqlQuery, profileDir).
Scan(&profile.Name, &profile.JoinedAt, profile.Bio); err != nil {
return nil, err
} else {
return &profile, nil
}

Using vapor-fluent to upsert models

I am currently struggling with doing an upsert with vapor/fluent. I have a model something like this:
struct DeviceToken: PostgreSQLModel {
var id: Int?
var token: String
var updatedAt: Date = Date()
init(id: Int? = nil, token: String, updatedAt: Date = Date()) {
self.id = id
self.token = token
self.updatedAt = updatedAt
}
}
struct Account: PostgreSQLModel {
var id: Int?
let username: String
let service: String
...
let deviceTokenId: DeviceToken.ID
init(id: Int? = nil, service: String, username: String, ..., deviceTokenId: DeviceToken.ID) {
self.id = id
self.username = username
....
self.deviceTokenId = deviceTokenId
}
}
From the client something like
{
"deviceToken": {
"token": "ab123",
"updatedAt": "01-01-2019 10:10:10"
},
"account": {
"username": "user1",
"service": "some service"
}
}
is send.
What I'd like to do is to insert the new models if they do not exist else update them. I saw the create(orUpdate:) method however this will only update if the id is the same (in my understanding). Since the client does not send the id i am not quite sure how to handle this.
Also I can't decode the model since the account is send without the deviceTokenId and therefore the decoding will fail. I guess I can address the latter problem by overriding NodeCovertible or by using two different models (one for decoding the json without the id and the actual model from above). However the first problem still remains.
What I exactly want to do is:
Update a DeviceToken if an entry with token already exists else create it
If an account with the combination of username and service already exists update its username, service and deviceTokenId else create it. DeviceTokenId is the id returned from 1.
Any chance you can help me out here ?
For everyone who is interested:
I solved it by writing an extension on PostgreSQLModel to supply an upsert method. I added a gist for you to have a look at: here.
Since these kind of links sometimes are broken when you need the information here a quick overview:
Actual upsert implementation:
extension QueryBuilder
where Result: PostgreSQLModel, Result.Database == Database {
/// Creates the model or updates it depending on whether a model
/// with the same ID already exists.
internal func upsert(_ model: Result,
columns: [PostgreSQLColumnIdentifier]) -> Future<Result> {
let row = SQLQueryEncoder(PostgreSQLExpression.self).encode(model)
/// remove id from row if not available
/// otherwise the not-null constraint will break
row = row.filter { (key, value) -> Bool in
if key == "id" && value.isNull { return false }
return true
}
let values = row
.map { row -> (PostgreSQLIdentifier, PostgreSQLExpression) in
return (.identifier(row.key), row.value)
}
self.query.upsert = .upsert(columns, values)
return create(model)
}
}
Convenience methods
extension PostgreSQLModel {
/// Creates the model or updates it depending on whether a model
/// with the same ID already exists.
internal func upsert(on connection: DatabaseConnectable) -> Future<Self> {
return Self
.query(on: connection)
.upsert(self, columns: [.keyPath(Self.idKey)])
}
internal func upsert<U>(on connection: DatabaseConnectable,
onConflict keyPath: KeyPath<Self, U>) -> Future<Self> {
return Self
.query(on: connection)
.upsert(self, columns: [.keyPath(keyPath)])
}
....
}
I solved the other problem I had that my database model could not be decoded since the id was not send from the client, by using a inner struct which would hold only the properties the client would send. The id and other database generated properties are in the outer struct. Something like:
struct DatabaseModel: PostgreSQLModel {
var id: Int?
var someProperty: String
init(id: Int? = nil, form: DatabaseModelForm) {
self.id = id
self.someProperty = form.someProperty
}
struct DatabaseModelForm: Content {
let someProperty: String
}
}