1

I'm evaluating Bun for a new project. I started to model some queries just to found out that usually they return the same "empty" struct, regardless of the underlying failure.

For instance, consider something like the following SELECT:

ctx := context.Background()
category := new(model.Category)
db.NewSelect().Model(category).Where("id = ?", id).Scan(ctx)

When checking for the returned error, the only way to figure it out (that I know of) what type of failure happened is by analyzing/parsing the returned string value in the error type.

# Failure because nothing matched
[bun]  13:30:38.945   SELECT                7.532ms  SELECT "category"."id", "category"."name", "category"."description", "category"."picture" FROM "categories" AS "category" WHERE (id = 'f9f0e038-ba40-4553-9fb6-a41ec2a892fb')        *errors.errorString: sql: no rows in result set 
2023/08/01 13:30:38 Category = [&{{} 00000000-0000-0000-0000-000000000000   }]
2023/08/01 13:30:38 [sql: no rows in result set]
13:30:38 | 404 |     1.4ms |       127.0.0.1 | GET     | /categories/f9f0e038-ba40-4553-9fb6-a41ec2a892fb

# Failure because the database server is unreachable
2023/08/01 13:39:59 Searching for category with ID [5c5b2317-a88f-45f1-9c69-bfbe74cef628]
"category"."description", "category"."picture" FROM "categories" AS "category" WHERE (id = '5c5b2317-a88f-45f1-9c69-bfbe74cef628')        *net.OpError: dial tcp [::1]:5432: connect: connection refused 
2023/08/01 13:39:59 Category = [&{{} 00000000-0000-0000-0000-000000000000   }]
2023/08/01 13:39:59 [dial tcp [::1]:5432: connect: connection refused]
13:39:59 | 404 |     1ms |       127.0.0.1 | GET     | /categories/5c5b2317-a88f-45f1-9c69-bfbe74cef628

Basically, I'm trying to find a way to return the correct HTTP response based on the actions executed — without parsing string values. Currently an HTTP 404 is returned, but for those cases where a system failure is expected, an HTTP 500 is more appropriate, rather than stating there is no resource available.

So I'm not sure if Bun could return nil for those cases where nothing was found from the database. Not sure what's the best outcome here, and if it's possible to do that currently.

Any suggestions with this? As you can tell, I'm fairly new in the Go world ;)


UPDATES

While checking for all those errors might be something that provides more control, I really think this is "a problem" with the underlying PostgreSQL driver. I've been testing some other libraries and they fail, as expected, for the use cases I was testing (server down, saturated pool, etc.), while returning the correct datasets whenever the requested data was present or not.

x80486
  • 6,627
  • 5
  • 52
  • 111
  • You should compare the returned error value against `sql.ErrNoRows` to check against no-row errors (i.e. 404 errors). For other db errors you can type-assert the error value as the error type of the driver that you are using. For example if the driver is `github.com/lib/pq` then you can do `err, ok := err.(*pq.Error)` to get more details about the error, e.g. SQL error code, name of violated constraint, name of file in which the backend's function that failed is defined, etc. – mkopriva Aug 01 '23 at 18:30
  • The `dial tcp [::1]:5432: connect: connection refused` looks like it's gonna be of type `*net.OpError` returned by the dialer from the `net` package. You can also type-assert against that if you want... or you can simply "fallback" and return 500 for any non-`sql.ErrNoRows` and any non-``. – mkopriva Aug 01 '23 at 18:36
  • Parsing error strings should be the last resort. You'd only need to do that in cases where the returned error has no exported sentinel variable (like `sql.ErrNoRows`) and also its dynamic type is unexported, which means you cannot do a normal equality comparison and you also cannot do a type-assertion. – mkopriva Aug 01 '23 at 18:43
  • 1
    Example from bun: https://bun.uptrace.dev/postgres/#pgdriver-error. Note that not all drivers return the stdlib's `sql.ErrNoRows`, for example pgx will return its own version of [`ErrNoRows`](https://github.com/jackc/pgx/blob/d626dfe94e250e911b77ac929e2be06f81042bd0/conn.go#L103), so comparing that to `sql.ErrNoRows` will not succeed. Check your driver's docs and check bun's docs to make sure what to compare the errors against. – mkopriva Aug 01 '23 at 19:06
  • I tried that as well (following [this](https://go.dev/doc/tutorial/database-access) article), but `errors.Is(err, sql.ErrNoRows)` never succeeds. – x80486 Aug 01 '23 at 19:07
  • 1
    Although I don't use `bun` I'm fairly confident it works, on github there's plenty of code using bun orm and comparing against `sql.ErrNoRows`. You just need to make sure you are comparing against the correct sentinel errors, and type-asserting against the correct driver error types, etc. – mkopriva Aug 01 '23 at 19:16
  • If you can't figure out what errors you're getting back you can use `fmt` for debugging. For example use `fmt.Printf("%T\n", err)` to get the error's dynamic type, or use `fmt.Printf("%p\n", err)` to get the address of the error value stored in the variable, or use `fmt.Printf("%#v\n", err)` to get a Go-syntax representation of the error value. – mkopriva Aug 01 '23 at 19:23
  • Thanks. Those are super-helpful. I'm appalled by the amount of low-level coding (read: code that libraries should take care of) I have to put forward in order to finish simple workflows. It could be that I'm not familiar with `Go`, but really checking for SQL errors at that level, in my opinion, should be done by the frameworks and a proper "translation" should be the returned for users. If you put your comments as response I'll accept it. – x80486 Aug 01 '23 at 19:56

0 Answers0