go-jet / jet

Type safe SQL builder with code generation and automatic query result data mapping
Apache License 2.0
2.23k stars 110 forks source link

Is there a way to make generic db functions for all models/tables? #353

Closed ZerGo0 closed 4 weeks ago

ZerGo0 commented 1 month ago

Hey,

this is more of a question because I can't figure it out myself.

I'm currently trying to implement some database functions that I pretty much need for all my jet models. I'm currently using interface{} and type check which type it is, but that would mean that I have to add types manually when I add new models, which I want to avoid.

So I was wondering if there is a way to do this with actual generics?

Here is what I do right now:

package database

import (
    "context"
    "database/sql"
    "shared/gen/DBNAME/table"
    "strings"
    "time"

    "github.com/go-jet/jet/v2/mysql"
    "go.uber.org/zap"
)

func GetFirst[M any](db *sql.DB,
    dbTable interface{},
    selectColumns mysql.Projection,
    expression mysql.BoolExpression) (
    M, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()

    // I want to avoid this part
    var stmt mysql.SelectStatement
    switch t := dbTable.(type) {
    case *table.CreatorsTable:
        stmt = t.
            SELECT(selectColumns).
            FROM(t).
            WHERE(expression).
            LIMIT(1)
    case *table.AutoMessagesTable:
        stmt = t.
            SELECT(selectColumns).
            FROM(t).
            WHERE(expression).
            LIMIT(1)
    }

    var obj M
    err := stmt.QueryContext(ctx, db, &obj)
    if err != nil {
        if err != sql.ErrNoRows && !strings.Contains(err.Error(), "no rows in result set") {
            zap.L().Error("Error querying database", zap.Error(err))
        }

        return obj, err
    }

    return obj, nil
}
ZerGo0 commented 4 weeks ago

I was able to solve it myself. I have to add new models to the generic models interface, but I get a build error when I try to use a type that I haven't added yet.

Here is how I did it:

package database

import (
    "context"
    "database/sql"
    "shared/gen/DBNAME/model"
    "strings"
    "time"

    "github.com/go-jet/jet/v2/mysql"
    "go.uber.org/zap"
)

type models interface {
    model.ModelA | model.ModelB | model.ModelC
}

func GetFirst[M models](db *sql.DB,
    dbTable mysql.Table,
    selectColumns mysql.Projection,
    expression mysql.BoolExpression) (
    M, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()

    stmt := dbTable.
        SELECT(selectColumns).
        FROM(dbTable)

    if expression != nil {
        stmt = stmt.WHERE(expression)
    }

    stmt = stmt.LIMIT(1)

    var obj M
    err := stmt.QueryContext(ctx, db, &obj)
    if err != nil {
        if err != sql.ErrNoRows && !strings.Contains(err.Error(), "no rows in result set") {
            zap.L().Error("Error querying database", zap.Error(err))
        }

        return obj, err
    }

    return obj, nil
}