66 */
77package org .elasticsearch .xpack .esql .core .expression ;
88
9+ import org .elasticsearch .TransportVersions ;
910import org .elasticsearch .common .Strings ;
1011import org .elasticsearch .common .io .stream .NamedWriteableRegistry ;
1112import org .elasticsearch .common .io .stream .StreamInput ;
1213import org .elasticsearch .common .io .stream .StreamOutput ;
14+ import org .elasticsearch .core .Nullable ;
1315import org .elasticsearch .xpack .esql .core .tree .NodeInfo ;
1416import org .elasticsearch .xpack .esql .core .tree .Source ;
1517import org .elasticsearch .xpack .esql .core .type .DataType ;
1618import org .elasticsearch .xpack .esql .core .type .EsField ;
1719import org .elasticsearch .xpack .esql .core .util .PlanStreamInput ;
1820import org .elasticsearch .xpack .esql .core .util .PlanStreamOutput ;
19- import org .elasticsearch .xpack .esql .core .util .StringUtils ;
2021
2122import java .io .IOException ;
2223import java .util .Objects ;
2324
25+ import static org .elasticsearch .xpack .esql .core .util .PlanStreamInput .readCachedStringWithVersionCheck ;
26+ import static org .elasticsearch .xpack .esql .core .util .PlanStreamOutput .writeCachedStringWithVersionCheck ;
27+
2428/**
2529 * Attribute for an ES field.
2630 * To differentiate between the different type of fields this class offers:
@@ -37,32 +41,31 @@ public class FieldAttribute extends TypedAttribute {
3741 FieldAttribute ::readFrom
3842 );
3943
40- private final FieldAttribute parent ;
41- private final String path ;
44+ private final String parentName ;
4245 private final EsField field ;
4346
4447 public FieldAttribute (Source source , String name , EsField field ) {
4548 this (source , null , name , field );
4649 }
4750
48- public FieldAttribute (Source source , FieldAttribute parent , String name , EsField field ) {
49- this (source , parent , name , field , Nullability .TRUE , null , false );
51+ public FieldAttribute (Source source , @ Nullable String parentName , String name , EsField field ) {
52+ this (source , parentName , name , field , Nullability .TRUE , null , false );
5053 }
5154
52- public FieldAttribute (Source source , FieldAttribute parent , String name , EsField field , boolean synthetic ) {
53- this (source , parent , name , field , Nullability .TRUE , null , synthetic );
55+ public FieldAttribute (Source source , @ Nullable String parentName , String name , EsField field , boolean synthetic ) {
56+ this (source , parentName , name , field , Nullability .TRUE , null , synthetic );
5457 }
5558
5659 public FieldAttribute (
5760 Source source ,
58- FieldAttribute parent ,
61+ @ Nullable String parentName ,
5962 String name ,
6063 EsField field ,
6164 Nullability nullability ,
62- NameId id ,
65+ @ Nullable NameId id ,
6366 boolean synthetic
6467 ) {
65- this (source , parent , name , field .getDataType (), field , nullability , id , synthetic );
68+ this (source , parentName , name , field .getDataType (), field , nullability , id , synthetic );
6669 }
6770
6871 /**
@@ -71,17 +74,16 @@ public FieldAttribute(
7174 */
7275 FieldAttribute (
7376 Source source ,
74- FieldAttribute parent ,
77+ @ Nullable String parentName ,
7578 String name ,
7679 DataType type ,
7780 EsField field ,
7881 Nullability nullability ,
79- NameId id ,
82+ @ Nullable NameId id ,
8083 boolean synthetic
8184 ) {
8285 super (source , name , type , nullability , id , synthetic );
83- this .path = parent != null ? parent .name () : StringUtils .EMPTY ;
84- this .parent = parent ;
86+ this .parentName = parentName ;
8587 this .field = field ;
8688 }
8789
@@ -91,16 +93,16 @@ public FieldAttribute(
9193 */
9294 private FieldAttribute (
9395 Source source ,
94- FieldAttribute parent ,
96+ @ Nullable String parentName ,
9597 String name ,
9698 DataType type ,
9799 EsField field ,
98- String qualifier ,
100+ @ Nullable String qualifier ,
99101 Nullability nullability ,
100- NameId id ,
102+ @ Nullable NameId id ,
101103 boolean synthetic
102104 ) {
103- this (source , parent , name , type , field , nullability , id , synthetic );
105+ this (source , parentName , name , type , field , nullability , id , synthetic );
104106 }
105107
106108 private FieldAttribute (StreamInput in ) throws IOException {
@@ -114,8 +116,8 @@ private FieldAttribute(StreamInput in) throws IOException {
114116 */
115117 this (
116118 Source .readFrom ((StreamInput & PlanStreamInput ) in ),
117- in . readOptionalWriteable ( FieldAttribute :: readFrom ),
118- (( PlanStreamInput ) in ). readCachedString ( ),
119+ readParentName ( in ),
120+ readCachedStringWithVersionCheck ( in ),
119121 DataType .readFrom (in ),
120122 EsField .readFrom (in ),
121123 in .readOptionalString (),
@@ -129,8 +131,8 @@ private FieldAttribute(StreamInput in) throws IOException {
129131 public void writeTo (StreamOutput out ) throws IOException {
130132 if (((PlanStreamOutput ) out ).writeAttributeCacheHeader (this )) {
131133 Source .EMPTY .writeTo (out );
132- out . writeOptionalWriteable ( parent );
133- (( PlanStreamOutput ) out ). writeCachedString ( name ());
134+ writeParentName ( out );
135+ writeCachedStringWithVersionCheck ( out , name ());
134136 dataType ().writeTo (out );
135137 field .writeTo (out );
136138 // We used to write the qualifier here. We can still do if needed in the future.
@@ -145,22 +147,49 @@ public static FieldAttribute readFrom(StreamInput in) throws IOException {
145147 return ((PlanStreamInput ) in ).readAttributeWithCache (FieldAttribute ::new );
146148 }
147149
150+ private void writeParentName (StreamOutput out ) throws IOException {
151+ if (out .getTransportVersion ().onOrAfter (TransportVersions .ESQL_FIELD_ATTRIBUTE_PARENT_SIMPLIFIED )) {
152+ ((PlanStreamOutput ) out ).writeOptionalCachedString (parentName );
153+ } else {
154+ // Previous versions only used the parent field attribute to retrieve the parent's name, so we can use just any
155+ // fake FieldAttribute here as long as the name is correct.
156+ FieldAttribute fakeParent = parentName () == null ? null : new FieldAttribute (Source .EMPTY , parentName (), field ());
157+ out .writeOptionalWriteable (fakeParent );
158+ }
159+ }
160+
161+ private static String readParentName (StreamInput in ) throws IOException {
162+ if (in .getTransportVersion ().onOrAfter (TransportVersions .ESQL_FIELD_ATTRIBUTE_PARENT_SIMPLIFIED )) {
163+ return ((PlanStreamInput ) in ).readOptionalCachedString ();
164+ }
165+
166+ FieldAttribute parent = in .readOptionalWriteable (FieldAttribute ::readFrom );
167+ return parent == null ? null : parent .name ();
168+ }
169+
148170 @ Override
149171 public String getWriteableName () {
150172 return ENTRY .name ;
151173 }
152174
153175 @ Override
154176 protected NodeInfo <FieldAttribute > info () {
155- return NodeInfo .create (this , FieldAttribute ::new , parent , name (), dataType (), field , (String ) null , nullable (), id (), synthetic ());
156- }
157-
158- public FieldAttribute parent () {
159- return parent ;
177+ return NodeInfo .create (
178+ this ,
179+ FieldAttribute ::new ,
180+ parentName ,
181+ name (),
182+ dataType (),
183+ field ,
184+ (String ) null ,
185+ nullable (),
186+ id (),
187+ synthetic ()
188+ );
160189 }
161190
162- public String path () {
163- return path ;
191+ public String parentName () {
192+ return parentName ;
164193 }
165194
166195 /**
@@ -174,7 +203,7 @@ public String fieldName() {
174203 if ((synthetic () || name ().startsWith (SYNTHETIC_ATTRIBUTE_NAME_PREFIX )) == false ) {
175204 return name ();
176205 }
177- return Strings .hasText (path ) ? path + "." + field .getName () : field .getName ();
206+ return Strings .hasText (parentName ) ? parentName + "." + field .getName () : field .getName ();
178207 }
179208
180209 public EsField .Exact getExactInfo () {
@@ -190,13 +219,13 @@ public FieldAttribute exactAttribute() {
190219 }
191220
192221 private FieldAttribute innerField (EsField type ) {
193- return new FieldAttribute (source (), this , name () + "." + type .getName (), type , nullable (), id (), synthetic ());
222+ return new FieldAttribute (source (), name () , name () + "." + type .getName (), type , nullable (), id (), synthetic ());
194223 }
195224
196225 @ Override
197226 protected Attribute clone (Source source , String name , DataType type , Nullability nullability , NameId id , boolean synthetic ) {
198227 // Ignore `type`, this must be the same as the field's type.
199- return new FieldAttribute (source , parent , name , field , nullability , id , synthetic );
228+ return new FieldAttribute (source , parentName , name , field , nullability , id , synthetic );
200229 }
201230
202231 @ Override
@@ -206,13 +235,13 @@ public Attribute withDataType(DataType type) {
206235
207236 @ Override
208237 public int hashCode () {
209- return Objects .hash (super .hashCode (), path , field );
238+ return Objects .hash (super .hashCode (), parentName , field );
210239 }
211240
212241 @ Override
213242 public boolean equals (Object obj ) {
214243 return super .equals (obj )
215- && Objects .equals (path , ((FieldAttribute ) obj ).path )
244+ && Objects .equals (parentName , ((FieldAttribute ) obj ).parentName )
216245 && Objects .equals (field , ((FieldAttribute ) obj ).field );
217246 }
218247
0 commit comments