Interface PlanSerializable

All Known Subinterfaces:
AggregateValue, AvailableFields.CopyIfPredicate, BooleanValue, Comparisons.Comparison, Comparisons.ComparisonWithParameter, CreatesDynamicTypesValue, IndexableAggregateValue, IndexKeyValueToPartialRecord.Copier, IndexScanParameters, LeafQueryPredicate, LeafValue, MessageHelpers.CoercionBiFunction, Ordering.DirectOrderPreservingValue, Ordering.InverseOrderPreservingValue, Ordering.OrderPreservingValue, PredicateWithValue, QuantifiedValue, QueryPredicate, QueryPredicateWithChild, RecordQueryPlan, RecordQueryPlanWithChild, RecordQueryPlanWithChildren, RecordQueryPlanWithComparisonKeyValues, RecordQueryPlanWithComparisons, RecordQueryPlanWithIndex, RecordQueryPlanWithMatchCandidate, RecordQueryPlanWithNoChildren, RecordQuerySetPlan, RecordQuerySetPlan.ComparisonKeyFunction, StreamableAggregateValue, Type, Type.Erasable, Value, Value.IndexOnlyValue, Value.InvertableValue<V>, Value.NondeterministicValue, Value.NonEvaluableValue, Value.RangeMatchableValue, ValueWithChild
All Known Implementing Classes:
AbstractArrayConstructorValue, AbstractArrayConstructorValue.LightArrayConstructorValue, AbstractQueryPredicate, AbstractValue, AndOrPredicate, AndOrValue, AndPredicate, ArithmeticValue, AvailableFields.ConditionalUponPathPredicate, AvailableFields.TruePredicate, CollateValue, Column, Comparisons.InvertedFunctionComparison, Comparisons.ListComparison, Comparisons.MultiColumnComparison, Comparisons.NullComparison, Comparisons.OpaqueEqualityComparison, Comparisons.ParameterComparison, Comparisons.ParameterComparisonBase, Comparisons.SimpleComparison, Comparisons.SimpleComparisonBase, Comparisons.TextComparison, Comparisons.TextContainsAllPrefixesComparison, Comparisons.TextWithMaxDistanceComparison, Comparisons.ValueComparison, CompatibleTypeEvolutionPredicate, CompatibleTypeEvolutionPredicate.FieldAccessTrieNode, ComposedBitmapIndexQueryPlan, ConditionSelectorValue, ConstantObjectValue, ConstantPredicate, ConstantValue, CountValue, DatabaseObjectDependenciesPredicate, DatabaseObjectDependenciesPredicate.UsedIndex, DerivedValue, EmptyValue, ExistsPredicate, ExistsValue, FieldValue, FieldValue.FieldPath, FieldValue.ResolvedAccessor, FirstOrDefaultValue, FromOrderedBytesValue, InComparandSource, IndexAggregateFunction, IndexedValue, IndexEntryObjectValue, IndexKeyValueToPartialRecord, IndexKeyValueToPartialRecord.FieldCopier, IndexKeyValueToPartialRecord.FieldWithValueCopier, IndexKeyValueToPartialRecord.MessageCopier, IndexOnlyAggregateValue, IndexOnlyAggregateValue.MaxEverValue, IndexOnlyAggregateValue.MinEverValue, IndexScanComparisons, IndexScanType, InOpValue, InParameterSource, InSource, InValuesSource, LikeOperatorValue, LiteralValue, MessageHelpers.CoercionTrieNode, MessageHelpers.TransformationTrieNode, MultidimensionalIndexScanComparisons, NotPredicate, NotValue, NullValue, NumericAggregationValue, NumericAggregationValue.Avg, NumericAggregationValue.BitmapConstructAgg, NumericAggregationValue.Max, NumericAggregationValue.Min, NumericAggregationValue.Sum, ObjectValue, OfTypeValue, OrPredicate, PatternForLikeValue, PickValue, Placeholder, PredicateWithValueAndRanges, PromoteValue, PromoteValue.ArrayCoercionBiFunction, PromoteValue.PrimitiveCoercionBiFunction, QuantifiedObjectValue, QuantifiedRecordValue, Quantifier.Physical, QueriedValue, QueryPlanConstraint, RangeConstraints, RangeConstraints.CompilableRange, RankValue, RecordConstructorValue, RecordQueryAbstractDataModificationPlan, RecordQueryAggregateIndexPlan, RecordQueryChooserPlanBase, RecordQueryComparatorPlan, RecordQueryCoveringIndexPlan, RecordQueryDamPlan, RecordQueryDefaultOnEmptyPlan, RecordQueryDeletePlan, RecordQueryExplodePlan, RecordQueryFetchFromPartialRecordPlan, RecordQueryFilterPlan, RecordQueryFilterPlanBase, RecordQueryFirstOrDefaultPlan, RecordQueryFlatMapPlan, RecordQueryInComparandJoinPlan, RecordQueryIndexPlan, RecordQueryInJoinPlan, RecordQueryInParameterJoinPlan, RecordQueryInsertPlan, RecordQueryIntersectionOnKeyExpressionPlan, RecordQueryIntersectionOnValuesPlan, RecordQueryIntersectionPlan, RecordQueryInUnionOnKeyExpressionPlan, RecordQueryInUnionOnValuesPlan, RecordQueryInUnionPlan, RecordQueryInValuesJoinPlan, RecordQueryLoadByKeysPlan, RecordQueryMapPlan, RecordQueryPredicatesFilterPlan, RecordQueryRangePlan, RecordQueryRecursiveUnionPlan, RecordQueryScanPlan, RecordQueryScoreForRankPlan, RecordQueryScoreForRankPlan.ScoreForRank, RecordQuerySelectorPlan, RecordQuerySetPlan.ComparisonKeyFunction.OnKeyExpression, RecordQuerySetPlan.ComparisonKeyFunction.OnValues, RecordQuerySortKey, RecordQuerySortPlan, RecordQueryStreamingAggregationPlan, RecordQueryTextIndexPlan, RecordQueryTypeFilterPlan, RecordQueryUnionOnKeyExpressionPlan, RecordQueryUnionOnValuesPlan, RecordQueryUnionPlan, RecordQueryUnionPlanBase, RecordQueryUnorderedDistinctPlan, RecordQueryUnorderedPrimaryKeyDistinctPlan, RecordQueryUnorderedUnionPlan, RecordQueryUpdatePlan, RecordTypeKeyComparison.RecordTypeComparison, RecordTypeValue, RelOpValue, RelOpValue.BinaryRelOpValue, RelOpValue.UnaryRelOpValue, ScanComparisons, ScanComparisons.Builder, SortedInComparandSource, SortedInParameterSource, SortedInValuesSource, TempTableInsertPlan, TempTableScanPlan, ThrowsValue, TimeWindowAggregateFunction, TimeWindowForFunction, TimeWindowScanComparisons, ToOrderedBytesValue, Type.Any, Type.AnyRecord, Type.Array, Type.Enum, Type.Enum.EnumValue, Type.None, Type.Null, Type.Primitive, Type.Record, Type.Record.Field, Type.Relation, UdfValue, ValuePredicate, VariadicFunctionValue, VersionValue, WindowedValue

@API(UNSTABLE) public interface PlanSerializable
Base interface to indicate that a java class is reachable through a RecordQueryPlan and therefore needs to be capable of serialization/deserialization. In addition to the toProto(PlanSerializationContext) defined here each implementor also needs to provide a public static fromProto(PlanSerializationContext, PClass) method that we can dynamically dispatch to when deserializing a proto message.
Plan serialization and naturally the inverse, plan deserialization, is done using precompiled protobuf. There is some boilerplate that is necessary to be aware of when extending existing plan structures or adding new plan operators themselves.
All classes that are serializable for plan serialization purposes must implement PlanSerializable. That interface complements PlanHashable which is doing similar things to compute a deterministic and stable hash code for a plan. Each class SomeClass implementing PlanSerializable has a counterpart protobuf message called PSomeClass. This convention was chosen in order to allow both classes to be referenced with just regular imports within the same java class.
PlanSerializable defines a method Message toProto(PlanSerializationContext) that implementors must provide. Note that this method should be covariant if possible. There are situations where that’s not possible in which case we revert to a less elegant approach. In any case, the serialization framework will only interpret the result as a proto Message. This method is used to transform from SomeClass to PSomeClass.
The inverse method called fromProto(...) requires a little more magic. One would naturally add that method to PSomeClass which is unfortunately not possible as PSomeClass is generated by the protobuf compiler. We instead add fromProto(...) as a public static factory method to SomeClass instead. The full signature of this method is
 
 @Nonnull
 public static SomeClass fromProto(PlanSerializationContext sC,
                                   PSomeClass someClassProto);
 
 

Every class implementing PlanSerializable is expected to implement this method as well.
Now that we have a toProto(...) and a fromProto(...), not everything is sorted out yet. Again, we are in control of toProto(...) which also happens to be implemented within the record layer class structure. Therefore, we can easily instantiate the correct proto class and populate its fields. However, the opposite is not true. If we were to call a fromProto() method directly we may know the exact record layer class and could potentially instantiate that class, however, a lot of record layer classes implement a common interface (e.g. RecordQueryPlan) and then refer to any kind of specific plan just by using a reference to the interface or a superclass. We have to build that substitution principle (we can substitute a reference to a super class or interface with a subclass respectively implementing class) into the serialization framework.
The example we use here is for the record layer interface Value which has dozens of implementors. Value itself extends quite a few interfaces of its own. Value has a protobuf correspondent called PValue which is defined as follows (incomplete):
 
 message PValue {
   extensions 5000 to max;

   oneof specific_value {
     PLightArrayConstructorValue light_array_constructor_value = 1;
     PAndOrValue and_or_value = 2;
     PArithmeticValue arithmetic_value = 3;
     PConditionSelectorValue condition_selector_value = 4;
     PConstantObjectValue constant_object_value = 5;
     PConstantValue constant_value = 6;
     PCountValue count_value = 7;
     ...
     PBinaryRelOpValue binary_rel_op_value = 34;
     PUnaryRelOpValue unary_rel_op_value = 35;
     PVariadicFunctionValue variadic_function_value = 36;
     PVersionValue version_value = 37;
   }
 }
 
 
PValue is a protobuf message that only ever holds exactly one actual implementing P-message. We could now provide a fromProto(...) in Value to create the correct object of the right implementing class. That, however, is just boilerplate and quite tedious to maintain. We are effectively doing the dynamic dispatch we get for free on the toProto(...) path by hand. If there is no help at this point we were forced to write this code:
 
 if (proto.hasLightArrayConstructorValue()) {
     return LightArrayConstructorValue.fromProto(sC, proto.getLightArrayConstructorValue());
 } else if (proto.hasAndOrValue()) {
     return AndOrValue.fromProto(sC, proto.getAndOrValue());
 } else if (proto.hasArithmeticValue()) {
     return ArithmeticValue.fromProto(sC, proto.getArithmeticValue());
 } else if (...) {
     ...
 } else {
     throw new RecordCoreException("you mad?");
 }
 
 
This sort of logic is hard to maintain over time. Instead, we use an auto-service pattern on the interface PlanDeserializer that must also be implemented by every specific implementor of Value that relates proto-class and java class (in the example PLightArrayConstructorValue and LightArrayConstructorValue:
 
   @AutoService(PlanDeserializer.class)
   public static class Deserializer implements PlanDeserializer<PLightArrayConstructorValue, LightArrayConstructorValue> {
       @@Nonnull
       @Override
       public Class<PLightArrayConstructorValue> getProtoMessageClass() {
           return PLightArrayConstructorValue.class;
       }

       @Nonnull
       @Override
       public LightArrayConstructorValue fromProto(final PlanSerializationContext serializationContext,
                                                   final PLightArrayConstructorValue lightArrayConstructorValueProto) {
           return LightArrayConstructorValue.fromProto(serializationContext, lightArrayConstructorValueProto);
       }
   }