TestIsUserQueryError pins the SQLSTATE-class-based discriminator that splits Query execution log lines between Info ("user wrote something that doesn't make sense") and Error ("the system itself failed"). The logger uses this to keep the Error level meaningful for alerting; a regression here would e
(t *testing.T)
| 377 | // for alerting; a regression here would either drown alerts in user- |
| 378 | // typo noise or silently downgrade real infra failures. |
| 379 | func TestIsUserQueryError(t *testing.T) { |
| 380 | tests := []struct { |
| 381 | name string |
| 382 | err error |
| 383 | want bool // true == user error (Info), false == infra error (Error) |
| 384 | }{ |
| 385 | // Class 42 — by far the most common user errors (table/column not |
| 386 | // found, syntax errors, access rule violations). |
| 387 | {"missing table (42P01)", errors.New("Catalog Error: Table with name users does not exist!"), true}, |
| 388 | {"missing column (42703)", errors.New("Binder Error: Referenced column \"missing_col\" not found in FROM clause!"), true}, |
| 389 | {"syntax error (42601)", errors.New("Parser Error: syntax error at or near \"FORM\""), true}, |
| 390 | {"missing function (42883)", errors.New("Catalog Error: Scalar Function with name no_such_func does not exist!"), true}, |
| 391 | {"permission denied (42501)", errors.New("Permission Error: not allowed to write here"), true}, |
| 392 | {"duplicate table (42P07)", errors.New("Catalog Error: Table with name \"t\" already exists!"), true}, |
| 393 | |
| 394 | // Other user classes — bad input, integrity, transaction misuse. |
| 395 | {"data exception conversion (22P02)", errors.New("Conversion Error: Could not convert string 'abc' to INT32"), true}, |
| 396 | {"data exception out of range (22003)", errors.New("Out of Range Error: Overflow in multiplication of INT32"), true}, |
| 397 | {"unique violation (23505)", errors.New("Constraint Error: Duplicate key \"id: 1\" violates primary key constraint"), true}, |
| 398 | {"not null violation (23502)", errors.New("Constraint Error: NOT NULL constraint failed: t.col"), true}, |
| 399 | {"invalid transaction state (25000)", errors.New("Transaction Error: cannot begin within an existing transaction"), true}, |
| 400 | {"missing schema (3F000)", errors.New("Catalog Error: Schema with name \"missing\" does not exist!"), true}, |
| 401 | {"dependent objects (2BP01)", errors.New("Dependency Error: Cannot drop entry because there are other entries that depend on it"), true}, |
| 402 | |
| 403 | // 57014 cancellation — class 57 is "operator intervention". Caller- |
| 404 | // driven cancellation (Ctrl-C, deadline, client disconnect) is filtered |
| 405 | // at the call site via clientConn.isCallerCancellation, so any |
| 406 | // cancellation reaching isUserQueryError is infra (gRPC client closed |
| 407 | // because the worker died, takeover, etc.) and must surface at Error. |
| 408 | {"infra cancellation (57014)", errors.New("context canceled"), false}, |
| 409 | |
| 410 | // Infra classes — must NOT be treated as user errors. |
| 411 | {"unknown error → XX000", errors.New("something went wrong"), false}, |
| 412 | {"SSL closed → infra", errors.New("SSL connection has been closed unexpectedly"), false}, |
| 413 | {"nil error", nil, false}, |
| 414 | |
| 415 | // 40001 retryable conflicts are a special case handled before the |
| 416 | // SQLSTATE check fires (logQueryError emits its own Warn for them), |
| 417 | // so they never reach this function in production. But verify the |
| 418 | // classification is unambiguously infra-side here so a future |
| 419 | // caller doesn't accidentally bucket retries as user errors. |
| 420 | {"transaction conflict 40001 is not user", errors.New("Transaction conflict on commit"), false}, |
| 421 | } |
| 422 | for _, tt := range tests { |
| 423 | t.Run(tt.name, func(t *testing.T) { |
| 424 | if got := isUserQueryError(tt.err); got != tt.want { |
| 425 | t.Errorf("isUserQueryError(%v) = %v, want %v (SQLSTATE=%s)", |
| 426 | tt.err, got, tt.want, classifyErrorCodeOrEmpty(tt.err)) |
| 427 | } |
| 428 | }) |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | // classifyErrorCodeOrEmpty is a test helper to surface the computed |
| 433 | // SQLSTATE in failure messages without crashing on nil errors. |
nothing calls this directly
no test coverage detected