Bladeren bron

Merge pull request #10827 from ghalliday/issue18916

HPCC-18916 Generate conditions for IFBLOCKS at compile time

Reviewed-By: Richard Chapman <rchapman@hpccsystems.com>
Richard Chapman 7 jaren geleden
bovenliggende
commit
f8a3740b15

+ 2 - 0
ecl/hql/hqlerrors.hpp

@@ -501,6 +501,7 @@
 #define HQLWRN_NoFieldsMatch                    3148
 #define HQLWRN_DFSdenied                        3149
 #define HQLERR_NonConstantRange                 3150
+#define HQLERR_ExprTooComplexForValueSet        3151
 
 #define HQLERR_DedupFieldNotFound_Text          "Field removed from dedup could not be found"
 #define HQLERR_CycleWithModuleDefinition_Text   "Module definition contains an illegal cycle/recursive definition %s"
@@ -545,6 +546,7 @@
 #define HQLERR_DFSlookupFailure_Text            "Failed to resolve record information in DFS for file %s"
 #define HQLERR_DFSlookupIncompatible_Text       "Resolved record information is not compatible file %s"
 #define HQLERR_NonConstantRange_Text            "Non constant substrings not supported"
+#define HQLERR_ExprTooComplexForValueSet_Text   "Cannot create a value set for expression %s"
 
 /* parser error */
 #define ERR_PARSER_CANNOTRECOVER    3005  /* The parser can not recover from previous error(s) */

+ 12 - 3
ecl/hql/hqlexpr.cpp

@@ -14054,9 +14054,18 @@ void exportJsonType(StringBuffer &ret, IHqlExpression *table)
 
 bool exportBinaryType(MemoryBuffer &ret, IHqlExpression *table)
 {
-    Owned<IRtlFieldTypeDeserializer> deserializer(createRtlFieldTypeDeserializer(nullptr));
-    const RtlTypeInfo *typeInfo = buildRtlType(*deserializer.get(), table->queryType());
-    return dumpTypeInfo(ret, typeInfo);
+    try
+    {
+        Owned<IRtlFieldTypeDeserializer> deserializer(createRtlFieldTypeDeserializer(nullptr));
+        const RtlTypeInfo *typeInfo = buildRtlType(*deserializer.get(), table->queryType());
+        return dumpTypeInfo(ret, typeInfo);
+    }
+    catch (IException * e)
+    {
+        DBGLOG(e);
+        e->Release();
+    }
+    return false;
 }
 
 const RtlTypeInfo *queryRtlType(IRtlFieldTypeDeserializer &deserializer, IHqlExpression *table)

+ 252 - 44
ecl/hql/hqlfilter.cpp

@@ -371,6 +371,47 @@ static IHqlExpression * createExpandedRecord(IHqlExpression * expr)
 }
 
 
+IHqlExpression * castToFieldAndBack(IHqlExpression * left, IHqlExpression * right)
+{
+    node_operator op = left->getOperator();
+    switch (op)
+    {
+    case no_cast:
+    case no_implicitcast:
+        {
+            IHqlExpression * uncast = left->queryChild(0);
+            ITypeInfo * castType = right->queryType();
+            ITypeInfo * uncastType = uncast->queryType();
+
+            OwnedHqlExpr castRight = ensureExprType(right, uncastType);
+            OwnedHqlExpr base = castToFieldAndBack(uncast, castRight);
+            //If this cast doesn't lose any information and child didn't change then don't bother
+            //casting back and forwards.
+            if ((base == castRight) && !castLosesInformation(uncastType, castType))
+                return LINK(right);
+            return ensureExprType(base, castType);
+        }
+
+    case no_select:
+        {
+            ITypeInfo * leftType = left->queryType();
+            ITypeInfo * rightType = right->queryType();
+            if (leftType == rightType || !castLosesInformation(leftType, rightType))
+                return LINK(right);
+            OwnedHqlExpr castToField = ensureExprType(right, leftType);
+            return ensureExprType(castToField, rightType);
+        }
+    case no_substring:
+    case no_add:
+    case no_sub:
+        return castToFieldAndBack(left->queryChild(0), right);
+    default:
+        throwUnexpected();
+    }
+}
+
+
+
 //---------------------------------------------------------------------------------------------------------------------
 
 FilterExtractor::FilterExtractor(IErrorReceiver & _errorReceiver, IHqlExpression * _tableExpr, int _numKeyableFields, bool _isDiskRead, bool forceValueSets)
@@ -610,46 +651,6 @@ bool FilterExtractor::isIndexInvariant(IHqlExpression * expr, bool includeRoot)
 
 
 
-IHqlExpression * FilterExtractor::castToFieldAndBack(IHqlExpression * left, IHqlExpression * right)
-{
-    node_operator op = left->getOperator();
-    switch (op)
-    {
-    case no_cast:
-    case no_implicitcast:
-        {
-            IHqlExpression * uncast = left->queryChild(0);
-            ITypeInfo * castType = right->queryType();
-            ITypeInfo * uncastType = uncast->queryType();
-
-            OwnedHqlExpr castRight = ensureExprType(right, uncastType);
-            OwnedHqlExpr base = castToFieldAndBack(uncast, castRight);
-            //If this cast doesn't lose any information and child didn't change then don't bother
-            //casting back and forwards.
-            if ((base == castRight) && !castLosesInformation(uncastType, castType))
-                return LINK(right);
-            return ensureExprType(base, castType);
-        }
-
-    case no_select:
-        {
-            ITypeInfo * leftType = left->queryType();
-            ITypeInfo * rightType = right->queryType();
-            if (leftType == rightType || !castLosesInformation(leftType, rightType))
-                return LINK(right);
-            return ensureExprType(right, leftType);
-        }
-    case no_substring:
-    case no_add:
-    case no_sub:
-        return castToFieldAndBack(left->queryChild(0), right);
-    default:
-        UNIMPLEMENTED;
-    }
-}
-
-
-
 IHqlExpression * FilterExtractor::invertTransforms(IHqlExpression * left, IHqlExpression * right)
 {
     node_operator op = left->getOperator();
@@ -1709,6 +1710,207 @@ IHqlExpression * FilterExtractor::querySimpleJoinValue(IHqlExpression * selector
 }
 
 //-- Runtime filter generation
+static __declspec(noreturn) void throwTooComplex(IHqlExpression * expr) __attribute__((noreturn));
+static void throwTooComplex(IHqlExpression * expr)
+{
+    StringBuffer ecl;
+    getExprECL(expr, ecl);
+    throwError1(HQLERR_ExprTooComplexForValueSet, ecl.str());
+}
+
+static IHqlExpression * getNormalizedCompareValue(IHqlExpression * expr)
+{
+    switch (expr->getOperator())
+    {
+    case no_cast:
+    case no_implicitcast:
+    {
+        OwnedHqlExpr arg = getNormalizedCompareValue(expr->queryChild(0));
+        return ensureExprType(arg, expr->queryType());
+    }
+    case no_nofold:
+        return getNormalizedCompareValue(expr->queryChild(0));
+    default:
+        return LINK(expr);
+    }
+}
+
+static bool normalizeValueCompare(OwnedHqlExpr & normalized, bool & truncated, IHqlExpression * lhs, IHqlExpression * value)
+{
+    //Primarily to aid creating test cases....
+    OwnedHqlExpr rhs = getNormalizedCompareValue(value);
+    if (!rhs->queryValue())
+        return false;
+
+    truncated = false;
+    LinkedHqlExpr compareValue = rhs->queryBody();
+    OwnedHqlExpr recastValue;
+    if ((lhs->getOperator() != no_select) || (lhs->queryType() != compareValue->queryType()))
+    {
+        OwnedHqlExpr temp  = castToFieldAndBack(lhs, compareValue);
+        if (temp != compareValue)
+        {
+            truncated = true;
+        }
+    }
+
+    normalized.set(rhs);
+    return true;
+}
+
+
+IValueSet * FilterExtractor::createValueSetInExpr(IHqlExpression * selector, const RtlTypeInfo & type, IHqlExpression * expr) const
+{
+    if (!exprReferencesDataset(expr, tableExpr))
+        throwTooComplex(expr);        //MORE: Possibly report another error
+
+    IHqlExpression * rhs = expr->queryChild(1);
+    Owned<IValueSet> values = createValueSet(type);
+    switch (rhs->getOperator())
+    {
+    case no_null:
+        return values.getClear();
+    case no_all:
+        values->addAll();
+        return values.getClear();
+    case no_list:
+        break;
+    default:
+        throwTooComplex(expr);
+    }
+
+    ForEachChild(i, rhs)
+    {
+        OwnedHqlExpr normalized;
+        bool truncated = false;
+        if (!normalizeValueCompare(normalized, truncated, selector, rhs->queryChild(i)))
+            throwTooComplex(expr);        //MORE: Possibly report another error
+
+        if (!normalized->queryValue())
+            throwTooComplex(expr);
+
+        if (!truncated)
+        {
+            MemoryBuffer compareValue;
+            if (!createConstantField(compareValue, selector, normalized))
+                throwTooComplex(expr);
+
+            const char * compareRaw = compareValue.toByteArray();
+            values->addRawRange(compareRaw, compareRaw);
+        }
+    }
+
+    if (expr->getOperator() == no_notin)
+        values->invertSet();
+    return values.getClear();
+}
+
+
+IValueSet * FilterExtractor::createValueSetCompareExpr(IHqlExpression * selector, const RtlTypeInfo & type, IHqlExpression * expr) const
+{
+    if (!exprReferencesDataset(expr, tableExpr))
+        throwTooComplex(expr);        //MORE: Possibly report another error
+
+    OwnedHqlExpr normalized;
+    bool truncated = false;
+    if (!normalizeValueCompare(normalized, truncated, selector, expr->queryChild(1)))
+        throwTooComplex(expr);        //MORE: Possibly report another error
+
+    Owned<IValueSet> values = createValueSet(type);
+
+    MemoryBuffer compareValue;
+    if (!normalized->queryValue())
+        throwTooComplex(expr);
+
+    if (!createConstantField(compareValue, selector, normalized))
+        throwTooComplex(expr);
+
+    node_operator op = expr->getOperator();
+    const char * compareRaw = compareValue.toByteArray();
+    switch (op)
+    {
+    case no_eq:
+        if (!truncated)
+            values->addRawRange(compareRaw, compareRaw);
+        break;
+    case no_ne:
+        values->addAll();
+        if (!truncated)
+            values->killRawRange(compareRaw, compareRaw);
+        break;
+    case no_le:
+    {
+        Owned<IValueTransition> upper = values->createRawTransition(CMPle, compareRaw);
+        values->addRange(nullptr, upper);
+        break;
+    }
+    case no_lt:
+    {
+        Owned<IValueTransition> upper = values->createRawTransition(truncated ? CMPle: CMPlt, compareRaw);
+        values->addRange(nullptr, upper);
+        break;
+    }
+    case no_ge:
+    {
+        Owned<IValueTransition> lower = values->createRawTransition(truncated ? CMPgt : CMPge, compareRaw);
+        values->addRange(lower, nullptr);
+        break;
+    }
+    case no_gt:
+    {
+        Owned<IValueTransition> lower = values->createRawTransition(CMPgt, compareRaw);
+        values->addRange(lower, nullptr);
+        break;
+    }
+    case no_between:
+    case no_notbetween:
+        {
+            //NB: This should only be generated for substring queries.  User betweens are converted
+            //to two separate comparisons to cope with range issues.
+            throwUnexpectedOp(op);
+        }
+    default:
+        throwUnexpectedOp(op);
+    }
+    return values.getClear();
+}
+
+
+IValueSet * FilterExtractor::createValueSetExpr(IHqlExpression * selector, const RtlTypeInfo & type, IHqlExpression * expr) const
+{
+    node_operator op = expr->getOperator();
+    switch (op)
+    {
+    case no_in:
+    case no_notin:
+        return createValueSetInExpr(selector, type, expr);
+    case no_if:
+        break; // Report an error
+    case no_and:
+    {
+        Owned<IValueSet> left = createValueSetExpr(selector, type, expr->queryChild(0));
+        Owned<IValueSet> right = createValueSetExpr(selector, type, expr->queryChild(1));
+        left->intersectSet(right);
+        return left.getClear();
+    }
+    case no_or:
+    {
+        Owned<IValueSet> left = createValueSetExpr(selector, type, expr->queryChild(0));
+        Owned<IValueSet> right = createValueSetExpr(selector, type, expr->queryChild(1));
+        left->unionSet(right);
+        return left.getClear();
+    }
+    case no_eq:
+    case no_ne:
+    case no_gt:
+    case no_ge:
+    case no_lt:
+    case no_le:
+        return createValueSetCompareExpr(selector, type, expr);
+    }
+
+    throwTooComplex(expr);
+}
 
 IFieldFilter * FilterExtractor::createSingleFieldFilter(IRtlFieldTypeDeserializer &deserializer) const
 {
@@ -1720,18 +1922,24 @@ IFieldFilter * FilterExtractor::createFieldFilter(IRtlFieldTypeDeserializer &des
 {
     const RtlTypeInfo * fieldType = buildRtlType(deserializer, selector->queryType());
     Owned<IValueSet> values;
+    HqlExprArray conditions;
     ForEachItemIn(i, keyed.conditions)
     {
         KeyCondition & cur = keyed.conditions.item(i);
         if (cur.selector == selector)
-        {
-            //MORE: This will be implemented in HPCC-18916
-        }
+            conditions.append(*LINK(cur.expr));
+    }
+
+    if (conditions.ordinality())
+    {
+        OwnedITypeInfo boolType = makeBoolType();
+        OwnedHqlExpr fullExpr = createBalanced(no_and, boolType, conditions);
+        values.setown(createValueSetExpr(selector, *fieldType, fullExpr));
     }
 
     unsigned fieldIndex = keyableSelects.find(*selector);
     if (values)
-        return ::createFieldFilter(fieldIndex, *fieldType, values);
+        return ::createFieldFilter(fieldIndex, values);
 
     return createWildFieldFilter(fieldIndex, *fieldType);
 }

+ 5 - 1
ecl/hql/hqlfilter.hpp

@@ -128,7 +128,6 @@ public:
     IFieldFilter * createFieldFilter(IRtlFieldTypeDeserializer &deserializer, IHqlExpression * selector) const;
 
 protected:
-    IHqlExpression * castToFieldAndBack(IHqlExpression * left, IHqlExpression * right);
     bool containsTableSelects(IHqlExpression * expr);
     IHqlExpression * createRangeCompare(IHqlExpression * selector, IHqlExpression * value, IHqlExpression * lengthExpr, bool compareEqual);
     KeyCondition * createTranslatedCondition(IHqlExpression * cond, KeyedKind keyedKind);
@@ -154,6 +153,10 @@ protected:
 
     virtual IHqlExpression * getRangeLimit(ITypeInfo * fieldType, IHqlExpression * lengthExpr, IHqlExpression * value, int whichBoundary);
 
+    IValueSet * createValueSetExpr(IHqlExpression * selector, const RtlTypeInfo & type, IHqlExpression * expr) const;
+    IValueSet * createValueSetInExpr(IHqlExpression * selector, const RtlTypeInfo & type, IHqlExpression * expr) const;
+    IValueSet * createValueSetCompareExpr(IHqlExpression * selector, const RtlTypeInfo & type, IHqlExpression * expr) const;
+
 protected:
     IErrorReceiver & errorReceiver;
     IHqlExpression * tableExpr;
@@ -178,5 +181,6 @@ protected:
 };
 
 extern HQL_API IHqlExpression * getExplicitlyPromotedCompare(IHqlExpression * filter);
+extern HQL_API IHqlExpression * castToFieldAndBack(IHqlExpression * left, IHqlExpression * right);
 
 #endif

+ 7 - 2
ecl/hql/hqlutil.cpp

@@ -9831,7 +9831,8 @@ static IFieldFilter * createIfBlockFilter(IRtlFieldTypeDeserializer &deserialize
 {
     //See if the condition can be matched to a simple field filter
     OwnedHqlExpr dummyDataset = createDataset(no_anon, LINK(rowRecord));
-    OwnedHqlExpr mappedCondition = replaceSelector(ifblock->queryChild(0), querySelfReference(), dummyDataset);
+    IHqlExpression * cond = ifblock->queryChild(0);
+    OwnedHqlExpr mappedCondition = replaceSelector(cond, querySelfReference(), dummyDataset);
     Owned <IErrorReceiver> errorReceiver = createThrowingErrorReceiver();
 
     FilterExtractor extractor(*errorReceiver, dummyDataset, rowRecord->numChildren(), true, true);
@@ -9840,7 +9841,11 @@ static IFieldFilter * createIfBlockFilter(IRtlFieldTypeDeserializer &deserialize
 
     bool isComplex = extraFilter || !extractor.isSingleMatchCondition();
     if (isComplex)
-        return nullptr;
+    {
+        StringBuffer ecl;
+        getExprECL(cond, ecl);
+        throwError1(HQLERR_ExprTooComplexForValueSet, ecl.str());
+    }
 
     return extractor.createSingleFieldFilter(deserializer);
 }

+ 11 - 7
rtl/eclrtl/rtlkey.hpp

@@ -186,10 +186,14 @@ enum TransitionMask : byte
     CMPnovalue = 0x80,  // Used when serializing.
 };
 
-class ValueTransition;
 interface RtlTypeInfo;
 class RtlRow;
 
+//Currently just an opaque link counted type
+interface IValueTransition : public IInterface
+{
+};
+
 /*
  * The IValueSet interface represents a set of ranges of values.
  *
@@ -198,12 +202,12 @@ class RtlRow;
 interface IValueSet : public IInterface
 {
 //The following methods are used for creating a valueset
-    virtual ValueTransition * createTransition(TransitionMask mask, unsigned __int64 value) const = 0;
-    virtual ValueTransition * createStringTransition(TransitionMask mask, size32_t len, const char * value) const = 0;
-    virtual ValueTransition * createUtf8Transition(TransitionMask mask, size32_t len, const char * value) const = 0;
-    virtual void addRange(ValueTransition * loval, ValueTransition * hival) = 0;
+    virtual IValueTransition * createTransition(TransitionMask mask, unsigned __int64 value) const = 0;
+    virtual IValueTransition * createStringTransition(TransitionMask mask, size32_t len, const char * value) const = 0;
+    virtual IValueTransition * createUtf8Transition(TransitionMask mask, size32_t len, const char * value) const = 0;
+    virtual void addRange(IValueTransition * loval, IValueTransition * hival) = 0;
     virtual void addAll() = 0;
-    virtual void killRange(ValueTransition * loval, ValueTransition * hival) = 0;
+    virtual void killRange(IValueTransition * loval, IValueTransition * hival) = 0;
     virtual void reset() = 0;
     virtual void invertSet() = 0;
     virtual void unionSet(const IValueSet *) = 0;
@@ -214,7 +218,7 @@ interface IValueSet : public IInterface
     virtual bool equals(const IValueSet & _other) const = 0;
 
 //following are primarily for use from the code generator
-    virtual ValueTransition * createRawTransition(TransitionMask mask, const void * value) const = 0;
+    virtual IValueTransition * createRawTransition(TransitionMask mask, const void * value) const = 0;
     virtual void addRawRange(const void * lower, const void * upper) = 0;
     virtual void killRawRange(const void * lower, const void * upper) = 0;
 

+ 15 - 11
rtl/eclrtl/rtlnewkey.cpp

@@ -127,8 +127,8 @@ public:
 
     virtual void addRange(TransitionMask lowerMask, const StringBuffer & lowerString, TransitionMask upperMask, const StringBuffer & upperString) override
     {
-        Owned<ValueTransition> lower = lowerString ? set.createUtf8Transition(lowerMask, rtlUtf8Length(lowerString.length(), lowerString), lowerString) : nullptr;
-        Owned<ValueTransition> upper = upperString ? set.createUtf8Transition(upperMask, rtlUtf8Length(upperString.length(), upperString), upperString) : nullptr;
+        Owned<IValueTransition> lower = lowerString ? set.createUtf8Transition(lowerMask, rtlUtf8Length(lowerString.length(), lowerString), lowerString) : nullptr;
+        Owned<IValueTransition> upper = upperString ? set.createUtf8Transition(upperMask, rtlUtf8Length(upperString.length(), upperString), upperString) : nullptr;
         set.addRange(lower, upper);
     }
 
@@ -153,7 +153,7 @@ void deserializeSet(IValueSet & set, const char * filter)
  * The value is always represented in the same way as a field of that type would be in a record.
  */
 
-class ValueTransition : implements CInterface
+class ValueTransition : implements CInterfaceOf<IValueTransition>
 {
 public:
     ValueTransition(TransitionMask _mask, const RtlTypeInfo & type, const void *_value)
@@ -355,7 +355,7 @@ private:
     OwnedMalloc<byte> value;
 };
 
-typedef CIArrayOf<ValueTransition> ValueTransitionArray;
+typedef IArrayOf<ValueTransition> ValueTransitionArray;
 
 
 //---------------------------------------------------------------------------------------------------------------------
@@ -381,21 +381,21 @@ public:
     }
 
 // Methods for creating a value set
-    virtual ValueTransition * createTransition(TransitionMask mask, unsigned __int64 value) const override
+    virtual IValueTransition * createTransition(TransitionMask mask, unsigned __int64 value) const override
     {
         MemoryBuffer buff;
         MemoryBufferBuilder builder(buff, 0);
         type.buildInt(builder, 0, nullptr, value);
         return new ValueTransition(mask, type, buff.toByteArray());
     }
-    virtual ValueTransition * createStringTransition(TransitionMask mask, size32_t len, const char * value) const override
+    virtual IValueTransition * createStringTransition(TransitionMask mask, size32_t len, const char * value) const override
     {
         MemoryBuffer buff;
         MemoryBufferBuilder builder(buff, 0);
         type.buildString(builder, 0, nullptr, len, value);
         return new ValueTransition(mask, type, buff.toByteArray());
     }
-    virtual ValueTransition * createUtf8Transition(TransitionMask mask, size32_t len, const char * value) const override
+    virtual IValueTransition * createUtf8Transition(TransitionMask mask, size32_t len, const char * value) const override
     {
         MemoryBuffer buff;
         MemoryBufferBuilder builder(buff, 0);
@@ -403,8 +403,10 @@ public:
         return new ValueTransition(mask, type, buff.toByteArray());
     }
 
-    virtual void addRange(ValueTransition * lower, ValueTransition * upper) override
+    virtual void addRange(IValueTransition * _lower, IValueTransition * _upper) override
     {
+        ValueTransition * lower = static_cast<ValueTransition *>(_lower);
+        ValueTransition * upper = static_cast<ValueTransition *>(_upper);
         Owned<ValueTransition> minBound;
         Owned<ValueTransition> maxBound;
         if (!lower)
@@ -542,8 +544,10 @@ public:
         reset();
         addRange(nullptr, nullptr);
     }
-    virtual void killRange(ValueTransition * lower, ValueTransition * upper) override
+    virtual void killRange(IValueTransition * _lower, IValueTransition * _upper) override
     {
+        ValueTransition * lower = static_cast<ValueTransition *>(_lower);
+        ValueTransition * upper = static_cast<ValueTransition *>(_upper);
         Owned<ValueTransition> minBound;
         Owned<ValueTransition> maxBound;
         if (!lower)
@@ -1708,8 +1712,8 @@ protected:
 
 static void addRange(IValueSet * set, const char * lower, const char * upper)
 {
-    Owned<ValueTransition> lowerBound = lower ? set->createUtf8Transition(CMPge, rtlUtf8Length(strlen(lower), lower), lower) : nullptr;
-    Owned<ValueTransition> upperBound = upper ? set->createUtf8Transition(CMPle, rtlUtf8Length(strlen(upper), upper), upper) : nullptr;
+    Owned<IValueTransition> lowerBound = lower ? set->createUtf8Transition(CMPge, rtlUtf8Length(strlen(lower), lower), lower) : nullptr;
+    Owned<IValueTransition> upperBound = upper ? set->createUtf8Transition(CMPle, rtlUtf8Length(strlen(upper), upper), upper) : nullptr;
     set->addRange(lowerBound, upperBound);
 };
 

File diff suppressed because it is too large
+ 1005 - 0
testing/regress/ecl/key/serializeifblocks.xml


File diff suppressed because it is too large
+ 6 - 6
testing/regress/ecl/key/serializetypes.xml


+ 165 - 0
testing/regress/ecl/serializeifblocks.ecl

@@ -0,0 +1,165 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2017 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+//Test serialization of various record/dataset types, including default values
+
+s := service
+   string dumpRecordType(virtual record val) : eclrtl,pure,library='eclrtl',entrypoint='dumpRecordType',fold;
+   string dumpRecordTypeNF(virtual record val) : eclrtl,pure,library='eclrtl',entrypoint='dumpRecordType';
+   data serializeRecordType(virtual record val) : eclrtl,pure,library='eclrtl',entrypoint='serializeRecordType',fold;
+   data serializeRecordTypeNF(virtual record val) : eclrtl,pure,library='eclrtl',entrypoint='serializeRecordType';
+end;
+
+//Simplest case (and only one likely to need!)
+r1 := RECORD
+    UNSIGNED id;
+    IFBLOCK(SELF.id = 4)
+        UNSIGNED extra;
+    END;
+END;
+
+//More complicated use of and or and equalities
+r2 := RECORD
+    UNSIGNED id;
+    IFBLOCK((SELF.id > 4 AND SELF.id < 10) OR (SELF.id >= 200 AND SELF.id <= 250))
+        UNSIGNED extra;
+    END;
+END;
+
+//Example that simplifies to >= 200 and < 1000
+r3 := RECORD
+    UNSIGNED id;
+    IFBLOCK((SELF.id > 4 AND SELF.id < 1000) AND (SELF.id >= 200 AND SELF.id <= 2500))
+        UNSIGNED extra;
+    END;
+END;
+
+//Check set syntax works
+r4 := RECORD
+    UNSIGNED id;
+    IFBLOCK(SELF.id IN [1,2,3,99,1000])
+        UNSIGNED extra;
+    END;
+END;
+
+//This example does not currently work since the expression folds to TRUE
+r5 := RECORD
+    UNSIGNED id;
+    IFBLOCK(SELF.id IN ALL)
+        UNSIGNED extra;
+    END;
+END;
+
+//Test with strings
+r6 := RECORD
+    STRING2 id;
+    IFBLOCK(SELF.id IN ['a','b','c','xx'] OR (SELF.id >= 'f' AND SELF.id < 'k'))
+        UNSIGNED extra;
+    END;
+END;
+
+//Range testing - variable length strings
+r7 := RECORD
+    STRING id;
+    IFBLOCK(SELF.id IN ['\'', 'a','b ','bxx'] OR (SELF.id >= 'faa' AND SELF.id <= 'kaa') OR (SELF.id > 'xxa' AND SELF.id < 'xyz'))
+        UNSIGNED extra;
+    END;
+END;
+
+//Range testing - some values out of range
+r8 := RECORD
+    STRING2 id;
+    IFBLOCK(SELF.id IN ['a','b ','bxx'] OR (SELF.id >= 'faa' AND SELF.id <= 'kaa') OR (SELF.id > 'xxa' AND SELF.id < 'xyz') OR (SELF.id >= 'zfa' AND SELF.id <= 'zfx'))
+        UNSIGNED extra;
+    END;
+END;
+
+//Range testing - some values out of range
+r9 := RECORD
+    STRING2 id;
+    IFBLOCK(SELF.id between 'faa' AND 'kaa')
+        UNSIGNED extra;
+    END;
+END;
+
+//Range testing - some values out of range
+r10 := RECORD
+    STRING2 id;
+    IFBLOCK(SELF.id = NOFOLD('a') OR SELF.id = NOFOLD('b') OR SELF.id = NOFOLD('bxx'))
+        UNSIGNED extra;
+    END;
+END;
+
+r11 := RECORD
+    STRING2 id;
+    IFBLOCK((SELF.id >= NOFOLD('axx') AND SELF.id <= NOFOLD('bxx')) OR (SELF.id > NOFOLD('dxx') AND SELF.id <= NOFOLD('exx')))
+        UNSIGNED extra;
+    END;
+END;
+
+//This cannot create a simple ifblock since it would require a post filter check.  Could possibly truncate and adjust the test conditions....
+r12 := RECORD
+    integer id;
+    IFBLOCK((SELF.id >= 12.23 AND SELF.id <= 14.67) OR (SELF.id > 98.74 AND SELF.id <= 123.4))
+        UNSIGNED extra;
+    END;
+END;
+
+r13 := RECORD
+    STRING2 id;
+    IFBLOCK(SELF.id IN [NOFOLD('a'),NOFOLD('b '),NOFOLD('bxx')])
+        UNSIGNED extra;
+    END;
+END;
+
+//Same as r1 - check that it works with reversed operands
+r14 := RECORD
+    UNSIGNED id;
+    IFBLOCK(4 = SELF.id)
+        UNSIGNED extra;
+    END;
+END;
+
+//Same as r2 - check reversed operands
+r15 := RECORD
+    UNSIGNED id;
+    IFBLOCK((4 < SELF.id AND 10 > SELF.id) OR (200 <= SELF.id AND 250 >= SELF.id))
+        UNSIGNED extra;
+    END;
+END;
+
+TEST(REC) := MACRO
+s.dumpRecordType(_empty_(REC)[1]);
+s.dumpRecordTypeNF(_empty_(REC)[1]);
+s.dumpRecordType(_empty_(REC)[1]) = s.dumpRecordTypeNF(_empty_(REC)[1]);
+ENDMACRO;
+
+TEST(r1);
+TEST(r2);
+TEST(r3);
+TEST(r4);
+//TEST(r5);         //This example does not currently work since the expression folds to TRUE
+TEST(r6);
+TEST(r7);
+TEST(r8);
+TEST(r9);
+TEST(r10);
+TEST(r11);
+//TEST(r12);        // currently too complex
+TEST(r13);
+TEST(r14);
+TEST(r15);