{"record":{"id":"489274b15903d287","repo":"vitessio/vitess","slug":"malformed-spec-minkey-maxkey-cannot-be-in-the-mid","errorCode":null,"errorMessage":"malformed spec: MinKey/MaxKey cannot be in the middle of the spec: %q","messagePattern":"malformed spec: MinKey/MaxKey cannot be in the middle of the spec: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/key/key.go","lineNumber":295,"sourceCode":"// string may serve both as the start and end of the keyspace: -a-b-\n// will be parsed as start-a, a-b, b-end.\n// \"0\" is treated as \"-\", to allow us to not have to special-case\n// client code.\nfunc ParseShardingSpec(spec string) ([]*topodatapb.KeyRange, error) {\n\tparts := strings.Split(spec, \"-\")\n\tif len(parts) == 1 {\n\t\tif spec == \"0\" {\n\t\t\tparts = []string{\"\", \"\"}\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"malformed spec: doesn't define a range: %q\", spec)\n\t\t}\n\t}\n\told := parts[0]\n\tranges := make([]*topodatapb.KeyRange, len(parts)-1)\n\n\tfor i, p := range parts[1:] {\n\t\tif p == \"\" && i != (len(parts)-2) {\n\t\t\treturn nil, fmt.Errorf(\"malformed spec: MinKey/MaxKey cannot be in the middle of the spec: %q\", spec)\n\t\t}\n\t\tif p != \"\" && p <= old {\n\t\t\treturn nil, fmt.Errorf(\"malformed spec: shard limits should be in order: %q\", spec)\n\t\t}\n\t\ts, err := hex.DecodeString(old)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(s) == 0 {\n\t\t\ts = nil\n\t\t}\n\t\te, err := hex.DecodeString(p)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(e) == 0 {\n\t\t\te = nil\n\t\t}","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/key/key.go#L277-L313","documentation":"ParseShardingSpec parses a sharding spec string like '0-80,80-' into a list of KeyRange objects. Empty strings in the spec represent MinKey (start of keyspace) or MaxKey (end of keyspace); they are only legal as the first or last limit. The library throws this when an empty limit appears in any other position, which would produce an unbounded/meaningless keyrange in the middle of the shard map.","triggerScenarios":"Calling ParseShardingSpec with a spec string containing an empty segment before the final position, e.g. '0-,80-' or '0-,-' or '-80-'. The check fires when parts[1:][i] == \"\" and i != len(parts)-2 (i.e., the empty limit is not the last element).","commonSituations":"Hand-written sharding specs in vtctld/topo configuration with a stray or missing hex boundary, scripted generation of shard specs that leaves empty limits mid-list, copy-paste errors like '80-,80-,'.","solutions":["Fix the spec so only the first and last limits may be empty: e.g. '-80,80-' or '0-80,80-'.","Replace mid-spec empty limits with explicit hex boundaries (e.g. use the actual boundary value).","Validate the spec with ParseShardingSpec in a test or preflight script before deploying topo config.","Check for duplicated commas or accidental MinKey ('-') tokens in the middle when editing keyspace sharding settings."],"exampleFix":"// before\nParseShardingSpec(\"0-,80-\") // error: MinKey in the middle\n// after\nParseShardingSpec(\"-80,80-\") // MinKey only as first limit","handlingStrategy":"validation","validationCode":"func validSpec(spec string) bool {\n\tparts := strings.Split(spec, \"-\")\n\tfor i, p := range parts[1:] {\n\t\tif p == \"\" && i != len(parts)-2 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}","typeGuard":null,"tryCatchPattern":"kr, err := key.ParseShardingSpec(spec)\nif err != nil {\n\treturn fmt.Errorf(\"invalid sharding spec %q: %w\", spec, err)\n}","preventionTips":["Only place MinKey ('') as the first limit and MaxKey ('') as the last limit","Validate specs with ParseShardingSpec in unit tests before deploying","Avoid hand-editing spec strings; generate them programmatically"],"tags":["go","sharding","configuration","input-validation"],"backgroundTag":"malformed-sharding-spec","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}