{"record":{"id":"f4dbc0067a1681f6","repo":"ory/hydra","slug":"cannot-scan-t-into-region-region","errorCode":null,"errorMessage":"cannot scan %T into region.Region","messagePattern":"cannot scan %T into region\\.Region","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/region/region.go","lineNumber":96,"sourceCode":"\t\treturn other == AsiaNorthEast\n\tcase US:\n\t\treturn other == USEast || other == USWest\n\t}\n\treturn false\n}\n\n// Scan implements sql.Scanner. NULL and empty scan to the zero value;\n// validate via Region.Valid if \"unset\" must be rejected.\nfunc (r *Region) Scan(src any) error {\n\tswitch s := src.(type) {\n\tcase nil:\n\t\t*r = \"\"\n\tcase string:\n\t\t*r = Region(s)\n\tcase []byte:\n\t\t*r = Region(s)\n\tdefault:\n\t\treturn errors.Errorf(\"cannot scan %T into region.Region\", src)\n\t}\n\treturn nil\n}\n\n// Value implements driver.Valuer. The empty Region writes as \"\".\nfunc (r Region) Value() (driver.Value, error) {\n\treturn string(r), nil\n}\n\n// IsEqual compares two nullable *Region pointers (both nil = equal).\nfunc IsEqual(a, b *Region) bool {\n\tif a == nil && b == nil {\n\t\treturn true\n\t}\n\tif a == nil || b == nil {\n\t\treturn false\n\t}\n\treturn *a == *b","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/region/region.go#L78-L114","documentation":"Region implements sql.Scanner; Scan only accepts string, []byte, and nil (empty string) values. When the driver yields any other Go type — most commonly int, int64, or time.Time from a non-text column — the scan is rejected with this error. It signals that the DB column behind Region is not text-typed.","triggerScenarios":"SELECTing/scanning a column whose driver type is numeric, enum-as-int, UUID object, or time into a *Region field; using a driver that returns non-string types for text columns (rare); raw queries that SELECT non-text expressions (e.g. region_id integer) into a Region model field.","commonSituations":"Schema drift: a column that used to be TEXT/VARCHAR was migrated to an integer enum; switching drivers (e.g. Postgres numeric or a UUID extension returning driver-specific types); copying a raw row value into Region via database/sql Scan.","solutions":["Change the column type to TEXT/VARCHAR in the schema (via a migration) so the driver yields string/[]byte.","Cast in SQL: SELECT region_id::text FROM ... so the driver returns a string.","Scan into a native type (int/uuid) first, then convert to region.Region in Go before assignment.","Check the driver: ensure it returns string/[]byte for the column (some drivers return native typed values for certain column types)."],"exampleFix":"// before\nrows.Scan(&region) // region is region.Region; column is INT -> error\n\n// after\nrows.Scan(\"SELECT region_id::text\") // or:\nvar id int64\nrows.Scan(&id)\nregion = region.Region(strconv.FormatInt(id, 10))","handlingStrategy":"type-guard","validationCode":"// verify the column is text-typed before scanning\nvar dataType string\nerr := db.QueryRow(`SELECT data_type FROM information_schema.columns\n  WHERE table_name=$1 AND column_name=$2`, \"locations\", \"region\").Scan(&dataType)\n// dataType must be \"text\", \"character varying\", or similar","typeGuard":"func scannableIntoRegion(v any) bool {\n    switch v.(type) {\n    case nil, string, []byte:\n        return true\n    default:\n        return false\n    }\n}\n// check scannableIntoRegion(raw) before assigning to Region","tryCatchPattern":"var r region.Region\nif err := rows.Scan(&r); err != nil {\n    if strings.Contains(err.Error(), \"cannot scan\") {\n        // column is not text; scan into native type and convert\n        var raw any\n        _ = rows.Scan(&raw)\n        return fmt.Errorf(\"region column has non-text type %T: %w\", raw, err)\n    }\n    return err\n}","preventionTips":["Declare region columns as TEXT/VARCHAR in the schema and enforce with a migration test.","Use ::text casts in raw queries that select values destined for Region fields.","Avoid switching drivers without re-checking the Go types returned for each column.","Add a smoke test that scans every model column into its field type."],"tags":["database","scan","type-mismatch"],"backgroundTag":"sql-scan-type-mismatch","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}