{"record":{"id":"aede97503be50761","repo":"risingwavelabs/risingwave","slug":"expect-return-type-but-got-risingwave-r","errorCode":null,"errorMessage":"Expect return type {:?} but got {:?}, RisingWave return type is {:?}, input type is {:?}, transform type is {:?}","messagePattern":"Expect return type (.+?) but got (.+?), RisingWave return type is (.+?), input type is (.+?), transform type is (.+?)","errorType":"validation","errorClass":"InvalidParam","httpStatus":null,"severity":"error","filePath":"src/expr/impl/src/scalar/external/iceberg.rs","lineNumber":144,"sourceCode":"            .to_arrow_field(\"\", &return_type)?\n            .data_type()\n            .clone(),\n    )\n    .map_err(|err| ExprError::InvalidParam {\n        name: \"return type in iceberg_transform\",\n        reason: format!(\n            \"Failed to convert return type to iceberg type, got error: {}\",\n            err.as_report()\n        )\n        .into(),\n    })?;\n\n    ensure!(\n        (expect_res_type == actual_res_type)\n            ||\n            // This is a confusing stuff.<https://github.com/apache/iceberg/pull/11749>\n            (matches!(transform_type, Transform::Day) && matches!(actual_res_type, IcebergType::Primitive(PrimitiveType::Int))),\n        ExprError::InvalidParam {\n            name: \"return type in iceberg_transform\",\n            reason: format!(\n                \"Expect return type {:?} but got {:?}, RisingWave return type is {:?}, input type is {:?}, transform type is {:?}\",\n                expect_res_type,\n                actual_res_type,\n                return_type,\n                (input_type, input_arrow_type),\n                transform_type\n            )\n            .into()\n        }\n    );\n\n    let child = match children.remove(1) {\n        BoxedExpression::Sync(child) => child,\n        BoxedExpression::Async(_) => {\n            return Err(ExprError::Internal(anyhow!(\n                \"async child in iceberg_transform is not supported\"","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/expr/impl/src/scalar/external/iceberg.rs#L126-L162","documentation":"The iceberg_transform builder checks that the Iceberg transform's result type matches the expected RisingWave return type. Mismatch raises InvalidParam with a detailed comparison of expected vs actual Iceberg type, the RisingWave return type, input type, and transform. The Transform::Day special case allows Int because Iceberg's day transform yields an int per apache/iceberg#11749.","triggerScenarios":"Calling build for `iceberg_transform(varchar, any)` where the transform function's output IcebergType differs from the type inferred from the RisingWave return type — e.g. transform `year` on a timestamp with a VARCHAR RisingWave result, or a day transform whose result is not mapped to Int correctly.","commonSituations":"Defining an Iceberg partition table where the RisingWave column type doesn't match the Iceberg partition field's transformed type; Iceberg spec/version mismatches around transform result types; hand-written DDL declaring the wrong result type.","solutions":["Align the declared RisingWave column type with the Iceberg transform's result type (e.g. day-transform on date/timestamp should be INT).","Check the transform argument string for typos (year/month/day/hour/identity/truncate/bucket).","Verify input type is compatible with the chosen transform (e.g. truncate on strings vs numerics).","If on an older Iceberg spec mapping, upgrade/adjust code to account for the day->Int convention (PR #11749)."],"exampleFix":"// before\nCREATE TABLE t (ts TIMESTAMP, PRIMARY KEY (x)) PARTITIONED BY (day(ts) AS VARCHAR)\n// after\n-- declare the partition/result column as INT for day transform\nPARTITIONED BY (day(ts))  -- result type INT","handlingStrategy":"validation","validationCode":"// Before DDL: verify the partition transform's Iceberg result type matches the RW column type\nlet actual = transform.result_type(input_type)?;\nlet compatible = actual == expected\n    || (matches!(transform, Transform::Day) && matches!(actual, IcebergType::Primitive(PrimitiveType::Int)));\nif !compatible { return Err(format!(\"{transform:?} of {input_type:?} yields {actual:?}, expected {expected:?}\")); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Map each Iceberg transform to its documented result type when declaring partition columns.","Remember day(ts) yields INT, not DATE, in the Iceberg spec.","Test partition DDL against a small table before applying to production schemas."],"tags":["iceberg","type-mismatch","transform"],"backgroundTag":"type-mismatch","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}