{"record":{"id":"2c1e2a28b20e546e","repo":"databendlabs/databend","slug":"map-inner-type-must-be-a-tuple","errorCode":null,"errorMessage":"map inner type must be a tuple","messagePattern":"map inner type must be a tuple","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/query/sql/src/planner/semantic/type_check/variant.rs","lineNumber":541,"sourceCode":"                    ScalarExpr::passthrough_nullable_type(DataType::from(&table_data_type), [\n                        &scalar,\n                    ]);\n                scalar = FunctionCall {\n                    span: expr_span,\n                    func_name: \"get\".to_string(),\n                    params: vec![Scalar::Number(NumberScalar::Int64((idx + 1) as i64))],\n                    arguments: vec![scalar.clone()],\n                    return_type: Box::new(return_type),\n                }\n                .into();\n                continue;\n            }\n            let box (path_scalar, _) = self.resolve_literal(span, &path_lit)?;\n            table_data_type = match table_data_type {\n                TableDataType::Array(inner_type) => *inner_type,\n                TableDataType::Map(inner_type) => match inner_type.remove_nullable() {\n                    TableDataType::Tuple { fields_type, .. } => fields_type[1].clone(),\n                    _ => unreachable!(\"map inner type must be a tuple\"),\n                },\n                TableDataType::EmptyArray | TableDataType::EmptyMap => TableDataType::Null,\n                data_type => data_type,\n            };\n            table_data_type = table_data_type.wrap_nullable();\n            scalar = FunctionCall {\n                span: path_scalar.span(),\n                func_name: \"get\".to_string(),\n                params: vec![],\n                arguments: vec![scalar.clone(), path_scalar],\n                return_type: Box::new(DataType::from(&table_data_type)),\n            }\n            .into();\n        }\n        let return_type = scalar.data_type().into_owned();\n        Ok(Box::new((scalar, return_type)))\n    }\n","sourceCodeStart":523,"sourceCodeEnd":559,"githubUrl":"https://github.com/databendlabs/databend/blob/288d84d76e20a2f8f7173bda9691eb6ece301aa9/src/query/sql/src/planner/semantic/type_check/variant.rs#L523-L559","documentation":"When resolving map access of the form map['key'] or map[key], the planner expects the Map's inner type to be a TUPLE(key_type, value_type) so it can extract the value type. If the Map's inner type is anything else (not a tuple), the assertion `unreachable!(\"map inner type must be a tuple\")` panics. In a healthy catalog every Map(K, V) is stored with a tuple inner type, so this indicates a malformed map type reached the resolver.","triggerScenarios":"Performing key-based access on a value typed as Map whose inner type is not Tuple(String, T) — e.g. after a bad CAST to MAP(...) with a non-tuple inner type, schema metadata drift, or map-typed values produced by UDFs/functions with unconventional types.","commonSituations":"Tables created by other engines/import pipelines whose Map type metadata doesn't match Databend's internal Map(K,V) tuple representation; UDFs returning improperly shaped map types; older dumps restored into newer versions.","solutions":["Inspect the type with SELECT typeof(col); ensure it is exactly MAP(key_type, value_type)","Re-cast explicitly: CAST(col AS MAP(VARCHAR, VARIANT)) before subscripting","Fix the source of the malformed type (UDF return type, import pipeline, table schema) rather than casting around it","If a valid Map triggers the panic, report a planner bug with the schema"],"exampleFix":"// before: access on a map with malformed inner type\nSELECT m['k'] FROM t;\n// after: normalize the type first\nSELECT CAST(m AS MAP(VARCHAR, VARIANT))['k'] FROM t;","handlingStrategy":"type-guard","validationCode":"SELECT typeof(m) FROM t LIMIT 1; -- must be MAP(K, V); recast if the inner type is malformed","typeGuard":"fn is_proper_map(ty: &str) -> bool {\n    let inner = ty.trim_start_matches(\"MAP(\").trim_end_matches(')');\n    ty.starts_with(\"MAP(\") && inner.split(',').count() == 2\n}","tryCatchPattern":null,"preventionTips":["Always declare Map columns as MAP(key_type, value_type) at table creation","Recast values from import pipelines or UDFs to canonical MAP types before subscripting","Validate restored/dumped tables with typeof() checks before querying"],"tags":["internal-invariant","map-access","type-checking","variant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"288d84d76e20a2f8f7173bda9691eb6ece301aa9","analyzedAt":"2026-09-11T11:29:36.208Z","contentChangedAt":"2026-09-11T11:29:36.208Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}