JacksonKjar commented on PR #59215:
URL: https://github.com/apache/spark/pull/59215#issuecomment-6330404884

   ## Records with JavaBean-style getters
   
   Thanks for raising this. You're right that it changes the result for records 
that declared `getX()`/`isX()` getters as a workaround. I think this is the 
right call, with my reasoning explained below. Just want to confirm you're 
aligned with making this change. If so, I'm happy to add a migration guide 
entry and a PR description note.
   
   ---
   
   ### Why should we introduce a breaking change?
   
   Adding bean-style getters doesn't make a record a bean. Records are 
immutable, so they can't have setters. As far as I know, it's undocumented how 
Spark should handle these invalid "beans". In practice, the workaround only 
ever worked for encoding: decoding back into the record failed with 
`EXPRESSION_DECODING_FAILED`. Users reached for it because Spark had no record 
support. This PR adds that support, so they can use the record encoder 
directly, which is likely what they wanted in the first place.
   
   In order to keep existing code working unchanged,  `Encoders.bean` would 
have to opt-out of the new record component based encoder. This would 
effectively make `Encoders.bean` a legacy API. Today it works as a general 
entry point for Java types: it's built on the same type inference, so it also 
handles types like `String`, `Integer` or `LocalDate`, nested beans, and now 
records. Users of records would have to know to switch to `Encoders.record`, 
and some cases still wouldn't work: a record nested inside a bean, or passed to 
`createDataFrame(..., beanClass)`, would still produce no columns. That's a 
broader change, affecting more users than those who relied on the workaround.
   
   ### Migration Options
   For users who relied on the workaround, the simplest path is to make the 
record's components match the schema they want, in the order they want the 
columns, since the canonical constructor now defines it. For example, a derived 
getter becomes a component, filled in by a convenience constructor:
   
   ```java
   record Person(String first, String fullName, String last) {
     Person(String first, String last) { this(first, first + " " + last, last); 
}
   }
   ```
   
   As a bonus, these records can now also be decoded.
   
   There are lighter options that avoid rewriting the records, but my opinion 
is that they're not worth it:
   - **An opt-in overload,** such as `Encoders.bean(cls, legacyRecords)`. Users 
would still have to change every call site, so it saves little over fixing the 
record, and it adds a mode flag to every bean entry point that we'd have to 
support indefinitely.
   - **A legacy SQL config,** such as `spark.sql.legacy.encodeRecordsAsBeans`, 
that restores getter-based encoding without code changes and can be removed 
later. The caveat is that encoders are inferred when they're created. In Spark 
Connect that happens on the client, which doesn't see the server's SQL config, 
so the flag would be ignored there unless we add plumbing.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to