{"record":{"id":"d2f486181380f3ab","repo":"apache/iceberg","slug":"hour-transform-is-not-supported","errorCode":null,"errorMessage":"Hour transform is not supported","messagePattern":"Hour transform is not supported","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"api/src/main/java/org/apache/iceberg/transforms/PartitionSpecVisitor.java","lineNumber":81,"sourceCode":"\n  default T month(String sourceName, int sourceId) {\n    throw new UnsupportedOperationException(\"Month transform is not supported\");\n  }\n\n  default T day(int fieldId, String sourceName, int sourceId) {\n    return day(sourceName, sourceId);\n  }\n\n  default T day(String sourceName, int sourceId) {\n    throw new UnsupportedOperationException(\"Day transform is not supported\");\n  }\n\n  default T hour(int fieldId, String sourceName, int sourceId) {\n    return hour(sourceName, sourceId);\n  }\n\n  default T hour(String sourceName, int sourceId) {\n    throw new UnsupportedOperationException(\"Hour transform is not supported\");\n  }\n\n  default T alwaysNull(int fieldId, String sourceName, int sourceId) {\n    throw new UnsupportedOperationException(\"Void transform is not supported\");\n  }\n\n  default T unknown(int fieldId, String sourceName, int sourceId, String transform) {\n    throw new UnsupportedOperationException(\n        String.format(\"Unknown transform %s is not supported\", transform));\n  }\n\n  /**\n   * Visit the fields of a {@link PartitionSpec}.\n   *\n   * @param spec a partition spec to visit\n   * @param visitor a partition spec visitor\n   * @param <R> return type of the visitor\n   * @return a list of the result produced by visiting each partition field","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/api/src/main/java/org/apache/iceberg/transforms/PartitionSpecVisitor.java#L63-L99","documentation":"PartitionSpecVisitor's default hour() methods throw UnsupportedOperationException when the visitor does not implement the hour temporal transform. Dispatch in PartitionSpecVisitor.visit matches Timestamps.MICROS_TO_HOUR/NANOS_TO_HOUR or Hours and calls hour(); the throwing default indicates the concrete visitor's transform coverage is incomplete. This intentional failure prevents a silently wrong partition-field mapping.","triggerScenarios":"PartitionSpecVisitor.visit on a table partitioned with hour(ts_col) where the visitor implementation lacks hour(int, String, int) or hour(String, int) overrides.","commonSituations":"Streaming/high-frequency ingestion tables are often partitioned by hour; custom visitor-based translators written for batch tables (identity/day only) blow up when pointed at such tables. Also occurs when copying specs between catalogs that don't support hourly partitioning.","solutions":["Override hour(int fieldId, String sourceName, int sourceId) in your visitor to handle the hour transform","Validate the spec's partitionType() up front to detect hour transforms and route to a supported path","If hourly partitioning is unsupported downstream, override hour() to produce an explicit unsupported error in your own terms"],"exampleFix":"// before\nvisitor without hour override -> UnsupportedOperationException\n// after\n@Override\npublic String hour(int fieldId, String sourceName, int sourceId) {\n  return \"hours(\" + sourceName + \")\";\n}","handlingStrategy":"validation","validationCode":"boolean hasHourTransform(PartitionSpec spec) {\n  return spec.fields().stream().anyMatch(f -> f.transform() instanceof Hours\n      || f.transform() == Timestamps.MICROS_TO_HOUR || f.transform() == Timestamps.NANOS_TO_HOUR);\n}\n// reject early or add hour() support before visiting","typeGuard":"if (field.transform() instanceof Hours) {\n  throw new IllegalStateException(\"Visitor does not support hour transform: \" + field);\n}","tryCatchPattern":"try {\n  PartitionSpecVisitor.visit(spec, visitor);\n} catch (UnsupportedOperationException e) {\n  throw new IllegalStateException(\"Hour-partitioned table not supported by this visitor\", e);\n}","preventionTips":["Stream-ingested tables are often hour-partitioned — always override hour() in catalog translators","Test visitors against hour(ts) specs from streaming workloads","Surface unsupported transforms in a preflight check rather than mid-visit"],"tags":["java","partitioning","visitor-pattern","unsupported-transform"],"backgroundTag":"unsupported-operation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}