-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[Feature] 允许扩展 ReflectLoader#loadField 支持自定义字段取值处理器 #443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ayasaz
wants to merge
3
commits into
alibaba:main
Choose a base branch
from
Ayasaz:feature/extend-field-handler
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
src/main/java/com/alibaba/qlexpress4/runtime/function/ExtendFieldHandler.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.alibaba.qlexpress4.runtime.function; | ||
|
|
||
| /** | ||
| * Custom field-access handler bound to a specific receiver type. | ||
| * <p> | ||
| * It extends the behaviour of {@link com.alibaba.qlexpress4.runtime.ReflectLoader#loadField} | ||
| * so that non-standard containers (such as Flink Row, JDBC ResultSet or user-defined | ||
| * MapLike/CollectionLike structures) can be accessed with the regular {@code obj.fieldName} | ||
| * syntax in QL expressions. | ||
| * <p> | ||
| * A handler is registered against a binding class via | ||
| * {@link com.alibaba.qlexpress4.Express4Runner#addExtendFieldHandler(Class, ExtendFieldHandler)} | ||
| * and is only invoked when the bean is assignable to that binding class. Binding to a class | ||
| * keeps each registration isolated (handlers cannot conflict with each other) and frees the | ||
| * caller from dealing with low-level runtime structures: just return the raw field value. | ||
| * | ||
| * <p>Example —— supporting Flink Row: | ||
| * <pre>{@code | ||
| * runner.addExtendFieldHandler(org.apache.flink.types.Row.class, | ||
| * (bean, fieldName) -> ((Row) bean).getField(fieldName)); | ||
| * }</pre> | ||
| * | ||
| * <p>If the handler returns {@code null}, QLExpress falls back to the default Java reflection | ||
| * field-access logic of {@code ReflectLoader}. | ||
| * | ||
| * @author ayasaz | ||
| * @since QLExpress4 | ||
| */ | ||
| @FunctionalInterface | ||
| public interface ExtendFieldHandler { | ||
|
|
||
| /** | ||
| * Resolve the value of {@code fieldName} from the given bean. | ||
| * | ||
| * @param bean the receiver object, guaranteed to be assignable to the binding class | ||
| * @param fieldName the field name being accessed | ||
| * @return the raw field value, or {@code null} to fall back to the default reflection logic | ||
| */ | ||
| Object getField(Object bean, String fieldName); | ||
| } |
92 changes: 92 additions & 0 deletions
92
src/test/java/com/alibaba/qlexpress4/ExtendFieldHandlerTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| package com.alibaba.qlexpress4; | ||
|
|
||
| import com.alibaba.qlexpress4.runtime.Value; | ||
| import org.junit.Assert; | ||
| import org.junit.Test; | ||
|
|
||
| /** | ||
| * Unit tests for {@link com.alibaba.qlexpress4.runtime.function.ExtendFieldHandler}. | ||
| * They verify class-bound custom field access: a matched handler resolves the value, | ||
| * a non-matching bean falls through to the default reflection logic, a handler returning | ||
| * {@code null} falls back to the default logic, and binding to a super type works for subtypes. | ||
| * | ||
| * @author ayasaz | ||
| */ | ||
| public class ExtendFieldHandlerTest { | ||
|
|
||
| /** | ||
| * A non-standard MapLike container (a simplified model of Flink Row / Spark Row). | ||
| * Fields are stored as String[] + Object[] and can only be read through getValue(name); | ||
| * they are not reachable through ordinary Java reflection getters. | ||
| */ | ||
| static class RowLike { | ||
| private final String[] fields; | ||
| private final Object[] values; | ||
|
|
||
| RowLike(String[] fields, Object[] values) { | ||
| this.fields = fields; | ||
| this.values = values; | ||
| } | ||
|
|
||
| Object getValue(String fieldName) { | ||
| for (int i = 0; i < fields.length; i++) { | ||
| if (fields[i].equals(fieldName)) { | ||
| return values[i]; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testCustomFieldHandlerMatches() { | ||
| Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); | ||
|
|
||
| runner.addExtendFieldHandler(RowLike.class, (bean, fieldName) -> ((RowLike) bean).getValue(fieldName)); | ||
|
|
||
| RowLike row = new RowLike(new String[] { "name", "age" }, new Object[] { "张三", 30 }); | ||
| Value result = runner.loadField(row, "name"); | ||
| Assert.assertEquals("张三", result.get()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCustomFieldHandlerNotMatches() { | ||
| Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); | ||
|
|
||
| runner.addExtendFieldHandler(RowLike.class, (bean, fieldName) -> ((RowLike) bean).getValue(fieldName)); | ||
|
|
||
| // a plain Java object that is not a RowLike should still go through the default reflection path. | ||
| // String has a getter for bytes, so it works as an ordinary Java bean here. | ||
| String hello = "hello"; | ||
| Value result = runner.loadField(hello, "bytes"); | ||
| Assert.assertNotNull(result); | ||
| Assert.assertTrue(result.get() instanceof byte[]); | ||
| } | ||
|
|
||
| @Test | ||
| public void testFieldHandlerReturnsNullFallsThrough() { | ||
| Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); | ||
|
|
||
| // always returns null -> should fall back to the default logic | ||
| runner.addExtendFieldHandler(java.util.Map.class, (bean, fieldName) -> null); | ||
|
|
||
| // the default loadField has built-in support for Map (returns MapItemValue) | ||
| java.util.Map<String, Object> map = new java.util.HashMap<>(); | ||
| map.put("key", "value"); | ||
| Value result = runner.loadField(map, "key"); | ||
| Assert.assertNotNull(result); | ||
| Assert.assertEquals("value", result.get()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testHandlerBoundToSuperTypeMatchesSubType() { | ||
| Express4Runner runner = new Express4Runner(InitOptions.DEFAULT_OPTIONS); | ||
|
|
||
| // bind to the super type; a subclass instance should still be dispatched to this handler | ||
| runner.addExtendFieldHandler(RowLike.class, (bean, fieldName) -> ((RowLike) bean).getValue(fieldName)); | ||
|
|
||
| RowLike row = new RowLike(new String[] { "city" }, new Object[] { "杭州" }) { | ||
| }; | ||
| Assert.assertEquals("杭州", runner.loadField(row, "city").get()); | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.