uptrace / bun

SQL-first Golang ORM
https://bun.uptrace.dev
BSD 2-Clause "Simplified" License
3.46k stars 206 forks source link

I want to be able to use PostgreSQL's parametric placeholders. #967

Open iwashi623 opened 3 months ago

iwashi623 commented 3 months ago

Hello everyone, I'd like to ask if you have ever wanted to execute the following query in bun

Have you ever wanted to execute the following query in bun?

INSERT INTO table_name (value1) VALUES ($1);

The $1 and $2 parts of the above query are PostgreSQL placeholders. You can execute the query by calling the Exec method with the standard package sql.DB.

query := "INSERT INTO table_name (value1) VALUES ($1);"
url := os.LookupEnv("DB_URL")
db, _ := sql.Open("postgres", url)
db.Exec(query, "test_value1")

However, when I call the Exec method using bun.DB, I get the following error.

There is no parameter $1

On the other hand, using ? as a placeholder can be used to execute successfully.

query := "INSERT INTO table_name (value1) VALUES (?) ;"
url := os.LookupEnv("DB_URL")
db, _ := sql.Open("postgres", url)
db.Exec(query, "test_value1")

DB.Exec() and sql.DB.Exec() are methods of different structures, so it does not matter that they behave differently. Each RDBMS (MySQL, PostgreSQL, Oracle...) Exec() is probably used to abstract the notation of each RDBMS (MySQL, PostgreSQL, Oracle...). However, it would be nice to have compatibility with each DB engine's own notation.

From my research, it works with PostgreSQL placeholders by simply modifying the following sentence in db.go.

func (c Conn) ExecContext(
    ctx context.Context, query string, args . .interface{}, .
) (sql.Result, error) {
    formattedQuery := c.db.format(query, args)
    ctx, event := c.db.beforeQuery(ctx, nil, query, args, formattedQuery, nil)
    res, err := c.Conn.ExecContext(ctx, formattedQuery, args...)  // modified here
    c.db.afterQuery(ctx, event, res, err)
    return res, err
}

I don't know how many people will be happy with this change, but at least I'm happy to make the best ORM while keeping compatibility, as I was initially confused by the PostgreSQL placeholders not working.

By the way, the following works because sql.DB embedded in bun.DB is public. db.DB.Exec() When used this way, Exec in *sql.DB is executed and works with placeholders like $1 without problems. (db=bun.DB, db.DB: sql.DB)