瀏覽代碼

HPCC-13828 Generate key comparison functions for sorts

Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
Gavin Halliday 10 年之前
父節點
當前提交
b671c90106
共有 7 個文件被更改,包括 169 次插入87 次删除
  1. 11 4
      ecl/hqlcpp/hqlcpp.cpp
  2. 4 1
      ecl/hqlcpp/hqlcpp.ipp
  3. 7 0
      ecl/hqlcpp/hqlcset.cpp
  4. 1 1
      ecl/hqlcpp/hqlcset.ipp
  5. 136 79
      ecl/hqlcpp/hqlhtcpp.cpp
  6. 6 2
      rtl/include/eclhelper.hpp
  7. 4 0
      rtl/include/eclhelper_base.hpp

+ 11 - 4
ecl/hqlcpp/hqlcpp.cpp

@@ -8115,10 +8115,17 @@ void HqlCppTranslator::expandSimpleOrder(IHqlExpression * left, IHqlExpression *
 
     if (left->isDatarow())
     {
-        IHqlExpression * record = left->queryRecord();
-        assertex(right->isDatarow() && (record == right->queryRecord()));
-        expandRowOrder(left, record, leftValues, !isActiveRow(left) && (left->getOperator() != no_select));
-        expandRowOrder(right, record, rightValues, !isActiveRow(right) && (right->getOperator() != no_select));
+        IHqlExpression * leftRecord = left->queryRecord();
+        IHqlExpression * rightRecord = right->queryRecord();
+        assertex(right->isDatarow());
+        if (leftRecord != rightRecord)
+        {
+            OwnedHqlExpr leftSerialRecord = getSerializedForm(leftRecord, diskAtom);
+            OwnedHqlExpr rightSerialRecord = getSerializedForm(rightRecord, diskAtom);
+            assertex(leftSerialRecord  == rightSerialRecord);
+        }
+        expandRowOrder(left, leftRecord, leftValues, !isActiveRow(left) && (left->getOperator() != no_select));
+        expandRowOrder(right, rightRecord, rightValues, !isActiveRow(right) && (right->getOperator() != no_select));
     }
     else
     {

+ 4 - 1
ecl/hqlcpp/hqlcpp.ipp

@@ -790,6 +790,7 @@ class AliasExpansionInfo;
 class HashCodeCreator;
 class ParentExtract;
 class ConstantRowArray;
+class SerializeKeyInfo;
 
 enum PEtype {
     PETnone,
@@ -1815,9 +1816,11 @@ protected:
 
     void doFilterAssignment(BuildCtx & ctx, TransformBuilder * builder, HqlExprArray & assigns, IHqlExpression * expr);
     void doFilterAssignments(BuildCtx & ctx, TransformBuilder * builder, HqlExprArray & assigns, IHqlExpression * expr);
+    bool extractSerializeKey(SerializeKeyInfo & info, const DatasetReference & dataset, const HqlExprArray & sorts, bool isGlobal);
     void generateSerializeAssigns(BuildCtx & ctx, IHqlExpression * record, IHqlExpression * selector, IHqlExpression * selfSelect, IHqlExpression * leftSelect, const DatasetReference & srcDataset, const DatasetReference & tgtDataset, HqlExprArray & srcSelects, HqlExprArray & tgtSelects, bool needToClear, node_operator serializeOp, IAtom * serialForm);
     void generateSerializeFunction(BuildCtx & ctx, const char * funcName, const DatasetReference & srcDataset, const DatasetReference & tgtDataset, HqlExprArray & srcSelects, HqlExprArray & tgtSelects, node_operator serializeOp, IAtom * serialForm);
-    void generateSerializeKey(BuildCtx & ctx, node_operator side, const DatasetReference & dataset, const HqlExprArray & sorts, bool isGlobal, bool generateCompares, bool canReuseLeft);             //NB: sorts are ats.xyz
+    void generateSerializeKey(BuildCtx & nestedctx, node_operator side, SerializeKeyInfo & keyInfo, const DatasetReference & dataset, bool generateCompares);
+    void generateSerializeKey(BuildCtx & ctx, node_operator side, const DatasetReference & dataset, const HqlExprArray & sorts, bool isGlobal, bool generateCompares);             //NB: sorts are ats.xyz
     void generateSortCompare(BuildCtx & nestedctx, BuildCtx & ctx, node_operator index, const DatasetReference & dataset, const HqlExprArray & sorts, IHqlExpression * noSortAttr, bool canReuseLeft, bool isLightweight, bool isLocal);
     void addSchemaField(IHqlExpression *field, MemoryBuffer &schema, IHqlExpression *selector);
     void addSchemaFields(IHqlExpression * record, MemoryBuffer &schema, IHqlExpression *selector);

+ 7 - 0
ecl/hqlcpp/hqlcset.cpp

@@ -835,6 +835,13 @@ IHqlExpression * InlineLinkedDictionaryCursor::getFirstSearchValue(IHqlExpressio
     return matched;
 }
 
+BoundRow * InlineLinkedDictionaryCursor::buildIterateLoop(BuildCtx & ctx, bool needToBreak)
+{
+    BoundRow * row = doBuildIterateLoop(ctx, needToBreak, true);
+    row->setConditional(true);
+    return row;
+}
+
 BoundRow * InlineLinkedDictionaryCursor::buildSelectMap(BuildCtx & ctx, IHqlExpression * mapExpr)
 {
     Owned<BoundRow> tempRow = translator.declareLinkedRow(ctx, mapExpr, false);

+ 1 - 1
ecl/hqlcpp/hqlcset.ipp

@@ -102,7 +102,7 @@ class InlineLinkedDictionaryCursor : public InlineLinkedDatasetCursor
 public:
     InlineLinkedDictionaryCursor(HqlCppTranslator & _translator, IHqlExpression * _ds, CHqlBoundExpr & _boundDs);
 
-    virtual BoundRow * buildIterateLoop(BuildCtx & ctx, bool needToBreak) { return doBuildIterateLoop(ctx, needToBreak, true); }
+    virtual BoundRow * buildIterateLoop(BuildCtx & ctx, bool needToBreak);
     virtual BoundRow * buildSelectMap(BuildCtx & ctx, IHqlExpression * indexExpr);
     virtual void buildInDataset(BuildCtx & ctx, IHqlExpression * inExpr, CHqlBoundExpr & tgt);
     virtual void buildIterateClass(BuildCtx & ctx, StringBuffer & cursorName, BuildCtx * initctx) { throwUnexpected(); }

+ 136 - 79
ecl/hqlcpp/hqlhtcpp.cpp

@@ -8288,7 +8288,7 @@ ABoundActivity * HqlCppTranslator::doBuildActivityMerge(BuildCtx & ctx, IHqlExpr
         DatasetReference dsRef(dataset, no_activetable, NULL);
         buildCompareClass(instance->nestedctx, "compare", sortOrder, dsRef);
         if (!instance->isLocal)
-            generateSerializeKey(instance->nestedctx, no_none, dsRef, sorts, !instance->isChildActivity(), true, false);
+            generateSerializeKey(instance->nestedctx, no_none, dsRef, sorts, !instance->isChildActivity(), true);
     }
     else
         throwError(HQLERR_InputMergeNotSorted);
@@ -11616,97 +11616,126 @@ void HqlCppTranslator::generateSerializeFunction(BuildCtx & ctx, const char * fu
     buildReturnRecordSize(r2kctx, serialize ? tgtCursor : srcCursor);
 }
 
-void HqlCppTranslator::generateSerializeKey(BuildCtx & nestedctx, node_operator side, const DatasetReference & dataset, const HqlExprArray & sorts, bool isGlobal, bool generateCompares, bool canReuseLeft)
+class SerializeKeyInfo
 {
-    //check if there are any ifblocks, and if so don't allow it.  Even more accurate would be no join fields used in ifblocks
-    IHqlExpression * record = dataset.queryDataset()->queryRecord();
-    bool canSerialize = targetThor() && isGlobal && !recordContainsIfBlock(record);
-    const char * sideText = (side == no_none) ? "" : (side == no_left) ? "Left" : "Right";
-    StringBuffer s, s2;
-
+public:
     HqlExprArray keyFields;
     HqlExprArray keySelects;
-    HqlExprArray datasetSelects;
     HqlExprArray keyCompares;
-    if (canSerialize)
+    HqlExprArray allKeyCompares;
+    HqlExprArray datasetSelects;
+    HqlExprArray filteredSorts;
+    OwnedHqlExpr keyRecord;
+    OwnedHqlExpr keyDataset;
+};
+
+bool HqlCppTranslator::extractSerializeKey(SerializeKeyInfo & info, const DatasetReference & dataset, const HqlExprArray & sorts, bool isGlobal)
+{
+    if (!targetThor() || !isGlobal)
+        return false;
+
+    //check if there are any ifblocks, and if so don't allow it.  Even more accurate would be no join fields used in ifblocks
+    //This test could be removed if keyToRecord() wasn't generated anymore
+    IHqlExpression * record = dataset.queryDataset()->queryRecord();
+    if (recordContainsIfBlock(record))
+        return false;
+
+    ForEachItemIn(idx, sorts)
     {
-        ForEachItemIn(idx, sorts)
-        {
-            //MORE: Nested - this won't serialize the key if sorting by a field in a nested record
-            //      If this is a problem we will need to create new fields for each value.
-            IHqlExpression & cur = sorts.item(idx);
-            IHqlExpression * value = &cur;
-            if (value->getOperator() == no_negate)
-                value=value->queryChild(0);
-            if ((value->getOperator() == no_select) && (value->queryChild(0)->queryNormalizedSelector() == dataset.querySelector()))
-            {
-                if (value->queryType()->getTypeCode() == type_alien)
-                {
-                    //MORE: Really should check if a self contained alien data type.
-                    canSerialize = false;
-                    break;
-                }
+        //MORE: Nested - this won't serialize the key if sorting by a field in a nested record
+        //      If this is a problem we will need to create new fields for each value.
+        IHqlExpression & cur = sorts.item(idx);
+        IHqlExpression * value = &cur;
+        if (value->getOperator() == no_negate)
+            value=value->queryChild(0);
+        //It isn't possible to sensibly compare serialized dictionaries at the moment.
+        if (value->isDictionary())
+            return false;
 
-                OwnedHqlExpr serializedField = getSerializedForm(value->queryChild(1), diskAtom); // Could be internal, but may require serialized compare
-                OwnedHqlExpr mappedSelect = dataset.mapScalar(value,queryActiveTableSelector());
-                keyFields.append(*LINK(serializedField));
-                keySelects.append(*createSelectExpr(LINK(mappedSelect->queryChild(0)), LINK(serializedField)));
-                datasetSelects.append(*LINK(value));
-                keyCompares.append(*dataset.mapScalar(&cur,queryActiveTableSelector()));
-            }
-            else if (!value->isConstant())
+        if ((value->getOperator() == no_select) && (value->queryChild(0)->queryNormalizedSelector() == dataset.querySelector()))
+        {
+            if (value->queryType()->getTypeCode() == type_alien)
             {
-                canSerialize = false;
-                break;
+                //MORE: Really should check if a self contained alien data type.
+                return false;
             }
-        }
+
+            OwnedHqlExpr serializedField = getSerializedForm(value->queryChild(1), diskAtom); // Could be internal, but may require serialized compare
+            OwnedHqlExpr mappedSelect = dataset.mapScalar(value,queryActiveTableSelector());
+            OwnedHqlExpr keyedCompare = dataset.mapScalar(&cur,queryActiveTableSelector());
+            info.keyFields.append(*LINK(serializedField));
+            info.keySelects.append(*createSelectExpr(LINK(mappedSelect->queryChild(0)), LINK(serializedField)));
+            info.datasetSelects.append(*LINK(value));
+            info.keyCompares.append(*LINK(keyedCompare));
+            info.filteredSorts.append(OLINK(cur));
+            info.allKeyCompares.append(*LINK(keyedCompare));
+        }
+        else if (value->isConstant())
+            info.allKeyCompares.append(OLINK(cur));
+        else
+            return false;
     }
 
+    bool aggressive = false;
+    // When projecting is done by the serialize() function this will be worth changing to true
+    // otherwise the extra cost of the project probably isn't likely to outweigh the extra copy
+    unsigned numToSerialize = aggressive ? info.filteredSorts.ordinality() : sorts.ordinality();
+
     //The following test will need to change if we serialize when nested fields are used (see above)
-    if (sorts.ordinality() >= getFlatFieldCount(record))
-        canSerialize = false;
+    if (numToSerialize >= getFlatFieldCount(record))
+        return false;
 
-    if (canSerialize)
-    {
-        if (canReuseLeft)
-        {
-            assertex(!generateCompares);
-            s.clear().append("virtual ISortKeySerializer * querySerialize").append(sideText).append("() { return &serializerLeft; }");
-            nestedctx.addQuoted(s);
-        }
-        else
-        {
-            StringBuffer memberName;
-            memberName.append("serializer").append(sideText);
+    info.keyRecord.setown(createRecord(info.keyFields));
+    info.keyDataset.setown(createDataset(no_anon, LINK(info.keyRecord)));
+    return true;
+}
 
-            BuildCtx classctx(nestedctx);
-            beginNestedClass(classctx, memberName, "ISortKeySerializer");
+void HqlCppTranslator::generateSerializeKey(BuildCtx & nestedctx, node_operator side, SerializeKeyInfo & keyInfo, const DatasetReference & dataset, bool generateCompares)
+{
+    //check if there are any ifblocks, and if so don't allow it.  Even more accurate would be no join fields used in ifblocks
+    //IHqlExpression * record = dataset.queryDataset()->queryRecord();
+    const char * sideText = (side == no_none) ? "" : (side == no_left) ? "Left" : "Right";
+    StringBuffer s;
 
-            IHqlExpression * keyRecord = createRecord(keyFields);
-            Owned<IHqlExpression> keyDataset = createDataset(no_anon, keyRecord);
+    StringBuffer memberName;
+    memberName.append("serializer").append(sideText);
 
-            DatasetReference keyActiveRef(keyDataset, no_activetable, NULL);
+    BuildCtx classctx(nestedctx);
+    beginNestedClass(classctx, memberName, "ISortKeySerializer");
 
-            generateSerializeFunction(classctx, "recordToKey", dataset, keyActiveRef, datasetSelects, keySelects, no_serialize, diskAtom);
-            generateSerializeFunction(classctx, "keyToRecord", keyActiveRef, dataset, keySelects, datasetSelects, no_deserialize, diskAtom);
-            buildMetaMember(classctx, keyRecord, false, "queryRecordSize");
+    DatasetReference keyActiveRef(keyInfo.keyDataset, no_activetable, NULL);
+    OwnedHqlExpr keyOrder = createValueSafe(no_sortlist, makeSortListType(NULL), keyInfo.keyCompares);
 
-            endNestedClass();
+    generateSerializeFunction(classctx, "recordToKey", dataset, keyActiveRef, keyInfo.datasetSelects, keyInfo.keySelects, no_serialize, diskAtom);
+    generateSerializeFunction(classctx, "keyToRecord", keyActiveRef, dataset, keyInfo.keySelects, keyInfo.datasetSelects, no_deserialize, diskAtom);
+    buildMetaMember(classctx, keyInfo.keyRecord, false, "queryRecordSize");
 
-            s.clear().append("virtual ISortKeySerializer * querySerialize").append(sideText).append("() { return &serializer").append(sideText).append("; }");
-            nestedctx.addQuoted(s);
+    buildCompareMember(classctx, "CompareKey", keyOrder, keyActiveRef);
+    doCompareLeftRight(classctx, "CompareRowKey", dataset, keyActiveRef, keyInfo.filteredSorts, keyInfo.keyCompares);
 
-            if (generateCompares)
-            {
-                OwnedHqlExpr keyOrder = createValueSafe(no_sortlist, makeSortListType(NULL), keyCompares);
-                buildCompareMember(nestedctx, "CompareKey", keyOrder, keyActiveRef);
+    endNestedClass();
 
-                doCompareLeftRight(nestedctx, "CompareRowKey", dataset, keyActiveRef, sorts, keyCompares);
-            }
-        }
+    s.clear().append("virtual ISortKeySerializer * querySerialize").append(sideText).append("() { return &").append(memberName).append("; }");
+    nestedctx.addQuoted(s);
+
+    if (generateCompares)
+    {
+        s.clear().append("virtual ICompare * queryCompareKey() { return ").append(memberName).append(".queryCompareKey(); }");
+        nestedctx.addQuoted(s);
+        s.clear().append("virtual ICompare * queryCompareRowKey() { return ").append(memberName).append(".queryCompareRowKey(); }");
+        nestedctx.addQuoted(s);
     }
 }
 
+void HqlCppTranslator::generateSerializeKey(BuildCtx & nestedctx, node_operator side, const DatasetReference & dataset, const HqlExprArray & sorts, bool isGlobal, bool generateCompares)
+{
+    SerializeKeyInfo keyInfo;
+    if (!extractSerializeKey(keyInfo, dataset, sorts, isGlobal))
+        return;
+
+    generateSerializeKey(nestedctx, side, keyInfo, dataset, generateCompares);
+}
+
 IHqlExpression * HqlCppTranslator::createFailMessage(const char * prefix, IHqlExpression * limit, IHqlExpression * filename, unique_id_t id)
 {
     StringBuffer s;
@@ -11849,9 +11878,9 @@ ABoundActivity * HqlCppTranslator::doBuildActivityJoinOrDenormalize(BuildCtx & c
     if (expr->hasAttribute(allAtom))
         isAllJoin = true;
 
-    bool isLookupJoin = expr->hasAttribute(lookupAtom);
     bool isSmartJoin = expr->hasAttribute(smartAtom);
-    bool isHashJoin = targetThor() && expr->hasAttribute(hashAtom);
+    bool isLookupJoin = expr->hasAttribute(lookupAtom) && !isSmartJoin;
+    bool isHashJoin = targetThor() && expr->hasAttribute(hashAtom) && !isSmartJoin;
     bool isLocalJoin = !isHashJoin && expr->hasAttribute(localAtom);
     bool joinToSelf = (op == no_selfjoin);
     bool allowAllToLookupConvert = !options.noAllToLookupConversion;
@@ -12081,8 +12110,7 @@ ABoundActivity * HqlCppTranslator::doBuildActivityJoinOrDenormalize(BuildCtx & c
     buildActivityFramework(instance);
 
     buildInstancePrefix(instance);
-    StringBuffer s,temp;
-
+    bool partitionRight = expr->hasAttribute(partitionRightAtom) && (kind != TAKselfjoin) && (joinInfo.slidingMatches.ordinality() == 0);
     DatasetReference lhsDsRef(dataset1, no_activetable, NULL);
     DatasetReference rhsDsRef(dataset2, no_activetable, NULL);
 
@@ -12101,9 +12129,37 @@ ABoundActivity * HqlCppTranslator::doBuildActivityJoinOrDenormalize(BuildCtx & c
             generateSortCompare(instance->nestedctx, instance->classctx, no_left, lhsDsRef, joinInfo.queryLeftSort(), noSortAttr, false, isLightweight, isLocalSort);
         generateSortCompare(instance->nestedctx, instance->classctx, no_right, rhsDsRef, joinInfo.queryRightSort(), noSortAttr, canReuseLeftCompare, isLightweight, isLocalSort);
 
-        bool isGlobal = !isLocalJoin && !instance->isChildActivity();
-        generateSerializeKey(instance->nestedctx, no_left, lhsDsRef, joinInfo.queryLeftSort(), isGlobal, false, false);
-        generateSerializeKey(instance->nestedctx, no_right, rhsDsRef, joinInfo.queryRightSort(), isGlobal, false, canReuseLeftCompare);
+        //Only joins that partition need the serialization functions
+        if (!isHashJoin && !isLookupJoin)
+        {
+            bool isGlobal = !isLocalJoin && !instance->isChildActivity();
+            BuildCtx nestedctx(instance->nestedctx);
+            SerializeKeyInfo keyInfo;
+            if (!partitionRight)
+            {
+                if (extractSerializeKey(keyInfo, lhsDsRef, joinInfo.queryLeftSort(), isGlobal))
+                {
+                    generateSerializeKey(nestedctx, no_left, keyInfo, lhsDsRef, false);
+                    DatasetReference keyActiveRef(keyInfo.keyDataset, no_activetable, NULL);
+                    HqlExprArray keyRequired;
+                    appendArray(keyRequired, keyInfo.allKeyCompares);
+                    keyRequired.trunc(joinInfo.numRequiredEqualities());
+                    doCompareLeftRight(nestedctx, "CompareLeftKeyRightRow", keyActiveRef, rhsDsRef, keyRequired, joinInfo.queryRightReq());
+                }
+            }
+            else
+            {
+                if (extractSerializeKey(keyInfo, rhsDsRef, joinInfo.queryRightSort(), isGlobal))
+                {
+                    generateSerializeKey(nestedctx, no_right, keyInfo, rhsDsRef, false);
+                    DatasetReference keyActiveRef(keyInfo.keyDataset, no_activetable, NULL);
+                    HqlExprArray keyRequired;
+                    appendArray(keyRequired, keyInfo.allKeyCompares);
+                    keyRequired.trunc(joinInfo.numRequiredEqualities());
+                    doCompareLeftRight(nestedctx, "CompareRightKeyLeftRow", keyActiveRef, lhsDsRef, keyRequired, joinInfo.queryLeftReq());
+                }
+            }
+        }
     }
 
     StringBuffer flags;
@@ -12113,7 +12169,7 @@ ABoundActivity * HqlCppTranslator::doBuildActivityJoinOrDenormalize(BuildCtx & c
     if (expr->hasAttribute(firstAtom)) flags.append("|JFfirst");
     if (expr->hasAttribute(firstLeftAtom)) flags.append("|JFfirstleft");
     if (expr->hasAttribute(firstRightAtom)) flags.append("|JFfirstright");
-    if (expr->hasAttribute(partitionRightAtom)) flags.append("|JFpartitionright");
+    if (partitionRight) flags.append("|JFpartitionright");
     if (expr->hasAttribute(parallelAtom)) flags.append("|JFparallel");
     if (expr->hasAttribute(sequentialAtom)) flags.append("|JFsequential");
     if (transformContainsSkip(transform))
@@ -12325,6 +12381,7 @@ ABoundActivity * HqlCppTranslator::doBuildActivityJoinOrDenormalize(BuildCtx & c
         buildCompareMemberLR(instance->nestedctx, "PrefixCompare", compare, dataset1, dataset2, selSeq);
     }
 
+//    buildCompareMemberLR(instance->nestedctx, "CompareLeftKeyRightRow", compare, dataset1, dataset2, selSeq);
 
     buildInstanceSuffix(instance);
 
@@ -14127,7 +14184,7 @@ ABoundActivity * HqlCppTranslator::doBuildActivityDistribute(BuildCtx & ctx, IHq
         DatasetReference dsRef(dataset);
         buildCompareClass(instance->nestedctx, "compare", sortOrder, dsRef);
         if (!instance->isLocal)
-            generateSerializeKey(instance->nestedctx, no_none, dsRef, sorts, true, true, false);
+            generateSerializeKey(instance->nestedctx, no_none, dsRef, sorts, true, true);
 
         buildInstanceSuffix(instance);
 
@@ -16361,7 +16418,7 @@ ABoundActivity * HqlCppTranslator::doBuildActivitySort(BuildCtx & ctx, IHqlExpre
     HqlExprArray sorts;
     sortlist->unwindList(sorts, no_sortlist);
     bool tryToSerializeKey = (actKind == TAKsort) && !isGroupedActivity(expr) && !isLocalActivity(expr) && !instance->isChildActivity();
-    generateSerializeKey(instance->nestedctx, no_none, DatasetReference(dataset), sorts, tryToSerializeKey, false, false);
+    generateSerializeKey(instance->nestedctx, no_none, DatasetReference(dataset), sorts, tryToSerializeKey, false);
 
     buildSkewThresholdMembers(instance->classctx, expr);
 

+ 6 - 2
rtl/include/eclhelper.hpp

@@ -39,8 +39,8 @@ if the supplied pointer was not from the roxiemem heap. Usually an OwnedRoxieStr
 
 //Should be incremented whenever the virtuals in the context or a helper are changed, so
 //that a work unit can't be rerun.  Try as hard as possible to retain compatibility.
-#define ACTIVITY_INTERFACE_VERSION      158
-#define MIN_ACTIVITY_INTERFACE_VERSION  158             //minimum value that is compatible with current interface - without using selectInterface
+#define ACTIVITY_INTERFACE_VERSION      159
+#define MIN_ACTIVITY_INTERFACE_VERSION  159             //minimum value that is compatible with current interface - without using selectInterface
 
 typedef unsigned char byte;
 
@@ -1078,6 +1078,8 @@ struct ISortKeySerializer
     virtual size32_t keyToRecord(ARowBuilder & rowBuilder, const void * _key, size32_t & recordSize) = 0;       // both return size of key!
     virtual size32_t recordToKey(ARowBuilder & rowBuilder, const void * _record, size32_t & recordSize) = 0;        // record size in 3rd parameter
     virtual IOutputMetaData * queryRecordSize() = 0;
+    virtual ICompare * queryCompareKey() = 0;
+    virtual ICompare * queryCompareRowKey() = 0;
 };
 
 
@@ -1705,6 +1707,8 @@ struct IHThorJoinBaseArg : public IHThorAnyJoinBaseArg
     virtual ICompare * queryPrefixCompare() = 0;
 
     virtual size32_t onFailTransform(ARowBuilder & rowBuilder, const void * _left, const void * _right, IException * e) { return 0; }
+    virtual ICompare * queryCompareLeftKeyRightRow()=0;                         // compare serialized left key with right row
+    virtual ICompare * queryCompareRightKeyLeftRow()=0;                         // as above if partition right selected
 };
 
 struct IHThorFetchContext : public IInterface

+ 4 - 0
rtl/include/eclhelper_base.hpp

@@ -1788,6 +1788,8 @@ class CThorJoinArg : public CThorArg, implements IHThorJoinArg
     virtual ICompare * queryCompareLeftRightLower()     { return NULL; }
     virtual ICompare * queryCompareLeftRightUpper()     { return NULL; }
     virtual ICompare * queryPrefixCompare()             { return NULL; }
+    virtual ICompare * queryCompareLeftKeyRightRow()    { return NULL; }
+    virtual ICompare * queryCompareRightKeyLeftRow()    { return NULL; }
 
     virtual size32_t onFailTransform(ARowBuilder & rowBuilder, const void * _left, const void * _right, IException * e) { return 0; }
 
@@ -1880,6 +1882,8 @@ class CThorHashJoinArg : public CThorArg, implements IHThorHashJoinArg
     virtual ICompare * queryCompareLeft()               { return NULL; }        // not needed for lookup
     virtual ICompare * queryCompareRight()              { return NULL; }        // not needed for many lookup
     virtual ICompare * queryPrefixCompare()             { return NULL; }
+    virtual ICompare * queryCompareLeftKeyRightRow()    { return NULL; }
+    virtual ICompare * queryCompareRightKeyLeftRow()    { return NULL; }
 
     virtual size32_t onFailTransform(ARowBuilder & rowBuilder, const void * _left, const void * _right, IException * e) { return 0; }