Skip to content

🐛 fix: surface UnexpectedNullableFound instead of remapping to ColumnNotFound (closes #560)#712

Open
MavenRain wants to merge 1 commit into
playframework:mainfrom
MavenRain:fix/560-null-error-message
Open

🐛 fix: surface UnexpectedNullableFound instead of remapping to ColumnNotFound (closes #560)#712
MavenRain wants to merge 1 commit into
playframework:mainfrom
MavenRain:fix/560-null-error-message

Conversation

@MavenRain
Copy link
Copy Markdown

Closes #560.

Problem

scalar[T].single (and other non-Option parsers) on a column containing
NULL produced this confusing error:

'.completed_average' not found, available columns: completed_average,
completed_average

The column was actually present; it just contained NULL. The root cause
was an internal parseColumn helper in SqlParser that remapped
UnexpectedNullableFound to ColumnNotFound. @gaeljw confirmed the
impact in
#560 (comment).

Fix

Two parts:

  1. SqlParser.scala: drop the UnexpectedNullableFound → ColumnNotFound
    remap in parseColumn. Since the helper then only forwarded to
    c.tupled(input), it has been inlined at its three call sites and
    removed. Errors from Column[T].tupled now surface unchanged, so
    users see UnexpectedNullableFound(column_name) and can distinguish
    NULL from missing column.

  2. RowParser.scala (.? combinator): the optional-column combinator
    previously only recovered from ColumnNotFound. Its scaladoc has
    always promised "missing or null column as None", and the existing
    singleOpt / ? semantics depended on the buggy mapping to convert
    NULL to None. Updated .? to also recover from
    UnexpectedNullableFound, honouring its documented contract
    independently of the parseColumn remap.

Regression test

AnormSpec gains one test verifying that scalar[String].single on a
NULL result throws an error whose message contains
UnexpectedNullableFound and does not contain
not found, available columns.

Behaviour change

User code that catches ColumnNotFound specifically to recognise a NULL
column will now see UnexpectedNullableFound instead. This was the
documented behaviour all along; the buggy remap was the deviation. No
other observable change.

Local validation

  • ++ 2.13; anorm-core/compile anorm-core/Test/compile clean
  • ++ 3; anorm-core/compile anorm-core/Test/compile clean
  • ++ 2.13; anorm-core/test 294 / 294 pass (including the new test and
    the existing "be parsed from mapping with optional column" test which
    previously depended on the buggy mapping)
  • ++ 3; anorm-core/testOnly tests.AnormSpec 52 / 52 pass
  • scalafmtCheckAll clean
  • anorm-core/mimaReportBinaryIssues no binary issues

…nNotFound

  Previously, NULL columns triggered an `UnexpectedNullableFound` error
  from `Column[T]`, but `SqlParser`'s internal `parseColumn` helper
  remapped it to `ColumnNotFound`, producing misleading "column not found"
  errors like "'X' not found, available columns: X, X" where the column
  was actually present and merely contained NULL.

  The fix drops the remap, and the now-trivial `parseColumn` helper is
  inlined at its three call sites.  The `.?` combinator on `RowParser`
  previously depended on the remap to convert NULL to `None`; it now
  recovers from `UnexpectedNullableFound` directly, honouring its
  documented "missing or null column as None" contract.

  Closes playframework#560.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Success(None)
case Success(a) => Success(Some(a))
case Error(ColumnNotFound(_, _)) => Success(None)
case Error(UnexpectedNullableFound(_)) => Success(None)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the desired behaviour, the pattern could be merge with the previous case

@gaeljw
Copy link
Copy Markdown
Member

gaeljw commented May 19, 2026

Thanks for the PR @MavenRain , this is pretty much the same as what Copilot already suggested in #665 but I haven't had time to dig a bit more at the consequences of the changes as I'm not yet familiar with the codebase and was a bit reluctant to merge without better understanding.

I'll try to find some time soon to review this.

Unless this is straightforward good for you @cchantep :)

* that will turn missing or null column as None.
*/
def ? : RowParser[Option[A]] = RowParser {
// Note: `UnexpectedNullableFound` is the error raised by `Column` when a
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this entire comment. Sounds like classic AI over commenting ;)

case in @ (_, m) =>
parseColumn(row, m.column.qualified, c, in)
case in @ (_, _) =>
// Previously a `parseColumn` helper mapped `UnexpectedNullableFound` to
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment, I don't think the comment is useful.

@cchantep
Copy link
Copy Markdown
Member

Thanks for the PR @MavenRain , this is pretty much the same as what Copilot already suggested in #665 but I haven't had time to dig a bit more at the consequences of the changes as I'm not yet familiar with the codebase and was a bit reluctant to merge without better understanding.

I'll try to find some time soon to review this.

Unless this is straightforward good for you @cchantep :)

Not straightforward, needs cautious review.
If there is already an ongoing PR, would rather go on with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect error message when column is unexpectedly NULL

3 participants