Browse Source

HPCC-8745 Implement inline dictionaries as compile time constants

Signed-off-by: Gavin Halliday <gavin.halliday@lexisnexis.com>
Gavin Halliday 12 years ago
parent
commit
4108dff7b0

+ 3 - 1
ecl/hql/hqlfold.cpp

@@ -312,6 +312,8 @@ static IHqlExpression * compareLists(node_operator op, IHqlExpression * leftList
     {
         IValue * leftValue = leftList->queryChild(i)->queryValue();
         IValue * rightValue = rightList->queryChild(i)->queryValue();
+        if (!leftValue || !rightValue)
+            return NULL;
         order = orderValues(leftValue, rightValue);
         if (order != 0)
             return createCompareResult(op, order);
@@ -421,7 +423,7 @@ static IHqlExpression * optimizeCompare(IHqlExpression * expr)
     if ((rightOp == no_all) && leftChild->isConstant())
         return createCompareResult(op, -1);
     
-    if ((leftOp == no_list) && (rightOp == no_list))
+    if (((leftOp == no_sortlist) || (leftOp == no_list)) && ((rightOp == no_sortlist) || (rightOp == no_list)))
         return compareLists(op, leftChild, rightChild);
 
     IValue * leftValue = leftChild->queryValue();

+ 0 - 28
ecl/hql/hqlgram2.cpp

@@ -5331,34 +5331,6 @@ ITypeInfo *HqlGram::checkPromoteNumeric(attribute &a1, bool extendPrecision)
 }
 
 
-void expandRecord(HqlExprArray & fields, IHqlExpression * selector, IHqlExpression * expr)
-{
-    switch (expr->getOperator())
-    {
-    case no_record:
-        {
-            ForEachChild(i, expr)
-                expandRecord(fields, selector, expr->queryChild(i));
-            break;
-        }
-    case no_field:
-        {
-            OwnedHqlExpr subSelector = createSelectExpr(LINK(selector), LINK(expr));
-            if (expr->queryRecord() && !expr->isDataset() && !expr->isDictionary())
-                expandRecord(fields, subSelector, expr->queryRecord());
-            else
-            {
-                if (fields.find(*subSelector) == NotFound)
-                    fields.append(*subSelector.getClear());
-            }
-            break;
-        }
-    case no_ifblock:
-        expandRecord(fields, selector, expr->queryChild(1));
-        break;
-    }
-}
-
 bool HqlGram::expandWholeAndExcept(IHqlExpression * dataset, const attribute & errpos, HqlExprArray & parms)
 {
     HqlExprArray results;

+ 40 - 0
ecl/hql/hqlutil.cpp

@@ -524,6 +524,34 @@ IHqlExpression * queryLastNonAttribute(IHqlExpression * expr)
     return NULL;
 }
 
+void expandRecord(HqlExprArray & selects, IHqlExpression * selector, IHqlExpression * expr)
+{
+    switch (expr->getOperator())
+    {
+    case no_record:
+        {
+            ForEachChild(i, expr)
+                expandRecord(selects, selector, expr->queryChild(i));
+            break;
+        }
+    case no_field:
+        {
+            OwnedHqlExpr subSelector = createSelectExpr(LINK(selector), LINK(expr));
+            if (expr->queryRecord() && !expr->isDataset() && !expr->isDictionary())
+                expandRecord(selects, subSelector, expr->queryRecord());
+            else
+            {
+                if (selects.find(*subSelector) == NotFound)
+                    selects.append(*subSelector.getClear());
+            }
+            break;
+        }
+    case no_ifblock:
+        expandRecord(selects, selector, expr->queryChild(1));
+        break;
+    }
+}
+
 //---------------------------------------------------------------------------
 
 static IHqlExpression * queryOnlyTableChild(IHqlExpression * expr)
@@ -4950,6 +4978,18 @@ bool isConstantDataset(IHqlExpression * expr)
     return true;
 }
 
+bool isConstantDictionary(IHqlExpression * expr)
+{
+    if (expr->getOperator() == no_null)
+        return true;
+    if (expr->getOperator() != no_createdictionary)
+        return false;
+    IHqlExpression * dataset = expr->queryChild(0);
+    if (dataset->getOperator() == no_inlinetable)
+        return isConstantDataset(dataset);
+    return false;
+}
+
 inline bool iseol(char c) { return c == '\r' || c == '\n'; }
 
 static unsigned skipSpace(unsigned start, unsigned len, const char * buffer)

+ 3 - 0
ecl/hql/hqlutil.hpp

@@ -54,6 +54,8 @@ extern HQL_API IHqlExpression * queryFirstField(IHqlExpression * record);
 extern HQL_API IHqlExpression * queryLastField(IHqlExpression * record);
 extern HQL_API IHqlExpression * queryLastNonAttribute(IHqlExpression * expr);
 extern HQL_API IHqlExpression * queryNextRecordField(IHqlExpression * recorhqlutid, unsigned & idx);
+extern HQL_API void expandRecord(HqlExprArray & selects, IHqlExpression * selector, IHqlExpression * expr);
+
 extern HQL_API int compareSymbolsByName(IInterface * * pleft, IInterface * * pright);
 extern HQL_API int compareScopesByName(IInterface * * pleft, IInterface * * pright);
 extern HQL_API int compareAtoms(IInterface * * pleft, IInterface * * pright);
@@ -180,6 +182,7 @@ extern HQL_API IHqlExpression * removeVirtualFields(IHqlExpression * record);
 extern HQL_API void unwindTransform(HqlExprCopyArray & exprs, IHqlExpression * transform);
 extern HQL_API bool isConstantTransform(IHqlExpression * transform);
 extern HQL_API bool isConstantDataset(IHqlExpression * expr);
+extern HQL_API bool isConstantDictionary(IHqlExpression * expr);
 extern HQL_API bool isSimpleTransformToMergeWith(IHqlExpression * expr);
 extern HQL_API IHqlExpression * queryUncastExpr(IHqlExpression * expr);
 extern HQL_API bool areConstant(const HqlExprArray & args);

+ 2 - 0
ecl/hqlcpp/hqlcatom.cpp

@@ -241,6 +241,7 @@ _ATOM deserializerSkipVUniAtom;
 _ATOM destroyRegexAtom;
 _ATOM destroyWRegexAtom;
 _ATOM destructMetaMemberAtom;
+_ATOM dictionaryAtom;
 _ATOM dictionaryCountAtom;
 _ATOM dictionaryLookupAtom;
 _ATOM dictionaryLookupExistsAtom;
@@ -961,6 +962,7 @@ MODULE_INIT(INIT_PRIORITY_HQLATOM-1)
     MAKEATOM(destroyRegex);
     MAKEATOM(destroyWRegex);
     MAKEATOM(destructMetaMember);
+    MAKEATOM(dictionary);
     MAKEATOM(dictionaryCount);
     MAKEATOM(dictionaryLookup);
     MAKEATOM(dictionaryLookupExists);

+ 1 - 0
ecl/hqlcpp/hqlcatom.hpp

@@ -241,6 +241,7 @@ extern _ATOM deserializerSkipVUniAtom;
 extern _ATOM destroyRegexAtom;
 extern _ATOM destroyWRegexAtom;
 extern _ATOM destructMetaMemberAtom;
+extern _ATOM dictionaryAtom;
 extern _ATOM dictionaryCountAtom;
 extern _ATOM dictionaryLookupAtom;
 extern _ATOM dictionaryLookupExistsAtom;

+ 5 - 0
ecl/hqlcpp/hqlcpp.ipp

@@ -762,6 +762,8 @@ public:
 class AliasExpansionInfo;
 class HashCodeCreator;
 class ParentExtract;
+class ConstantRowArray;
+
 enum PEtype {
     PETnone,
     PETchild,       // child query
@@ -1210,8 +1212,11 @@ public:
     BoundRow * buildDatasetIterateSpecialTempTable(BuildCtx & ctx, IHqlExpression * expr, bool needToBreak);
     BoundRow * buildDatasetIterateStreamedCall(BuildCtx & ctx, IHqlExpression * expr, bool needToBreak);
 
+    void createInlineDictionaryRows(HqlExprArray & args, ConstantRowArray & boundRows, IHqlExpression * keyRecord, IHqlExpression * nullRow);
+    bool buildConstantRows(ConstantRowArray & boundRows, IHqlExpression * transforms);
     void doBuildDatasetLimit(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt, ExpressionFormat format);
     bool doBuildDatasetInlineTable(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt, ExpressionFormat format);
+    bool doBuildDictionaryInlineTable(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt, ExpressionFormat format);
 
     void doBuildCheckDatasetLimit(BuildCtx & ctx, IHqlExpression * expr, const CHqlBoundExpr & bound);
 

+ 271 - 23
ecl/hqlcpp/hqlcppds.cpp

@@ -2222,12 +2222,19 @@ void HqlCppTranslator::doBuildDataset(BuildCtx & ctx, IHqlExpression * expr, CHq
         }
     case no_createdictionary:
         {
+            if (isConstantDictionary(expr))
+            {
+                if (doBuildDictionaryInlineTable(ctx, expr, tgt, format))
+                    return;
+            }
+
             IHqlExpression * record = expr->queryRecord();
+            IHqlExpression * dataset = expr->queryChild(0);
             Owned<IHqlCppDatasetBuilder> builder = createLinkedDictionaryBuilder(record);
 
             builder->buildDeclare(ctx);
 
-            buildDatasetAssign(ctx, builder, expr->queryChild(0));
+            buildDatasetAssign(ctx, builder, dataset);
 
             builder->buildFinish(ctx, tgt);
             ctx.associateExpr(expr, tgt);
@@ -2532,6 +2539,17 @@ void HqlCppTranslator::buildDatasetAssign(BuildCtx & ctx, const CHqlBoundTarget
         }
     case no_createdictionary:
         {
+            if (isConstantDictionary(expr))
+            {
+                CHqlBoundExpr temp;
+                if (doBuildDictionaryInlineTable(ctx, expr, temp, FormatNatural))
+                {
+                    OwnedHqlExpr translated = temp.getTranslatedExpr();
+                    buildDatasetAssign(ctx, target, translated);
+                    return;
+                }
+            }
+
             IHqlExpression * record = expr->queryRecord();
             Owned<IHqlCppDatasetBuilder> builder = createLinkedDictionaryBuilder(record);
             builder->buildDeclare(ctx);
@@ -2750,6 +2768,42 @@ void HqlCppTranslator::doBuildDatasetLimit(BuildCtx & ctx, IHqlExpression * expr
     doBuildCheckDatasetLimit(ctx, expr, tgt);
 }
 
+class ConstantRow : public CInterface
+{
+public:
+    ConstantRow(IHqlExpression * _transform, IHqlExpression * _boundRow) : transform(_transform), boundRow(_boundRow)
+    {
+    }
+
+public:
+    IHqlExpression * transform;
+    LinkedHqlExpr boundRow;
+};
+
+class ConstantRowArray : public CIArrayOf<ConstantRow> {};
+
+bool HqlCppTranslator::buildConstantRows(ConstantRowArray & boundRows, IHqlExpression * transforms)
+{
+    HqlExprArray rows;
+
+    ForEachChild(row, transforms)
+    {
+        OwnedHqlExpr constRow = createConstantRowExpr(transforms->queryChild(row));
+        if (!constRow || !canGenerateStringInline(constRow->queryType()->getSize()))
+            return false;
+        rows.append(*constRow.getClear());
+    }
+
+    ForEachItemIn(i, rows)
+    {
+        IHqlExpression * transform = transforms->queryChild(i);
+        CHqlBoundExpr bound;
+        buildConstRow(transform->queryRecord(), &rows.item(i), bound);
+        boundRows.append(*new ConstantRow(transform, bound.expr));
+    }
+    return true;
+}
+
 bool HqlCppTranslator::doBuildDatasetInlineTable(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt, ExpressionFormat format)
 {
     if (!options.generateStaticInlineTables)
@@ -2766,38 +2820,232 @@ bool HqlCppTranslator::doBuildDatasetInlineTable(BuildCtx & ctx, IHqlExpression
 
     BuildCtx declareCtx(*code, literalAtom);
     //Remove unique id when checking for constant datasets already generated
-    OwnedHqlExpr exprNoUnique = removeProperty(expr, _uid_Atom);
-    if (declareCtx.getMatchExpr(exprNoUnique, tgt))
+    OwnedHqlExpr exprKey = removeProperty(expr, _uid_Atom);
+    if (declareCtx.getMatchExpr(exprKey, tgt))
         return true;
 
-    HqlExprArray rows;
-    unsigned maxRows = transforms->numChildren();
-    unsigned row;
-    for (row = 0; row < maxRows; row++)
+    ConstantRowArray boundRows;
+    if (!buildConstantRows(boundRows, transforms))
+        return false;
+
+    Owned<ITypeInfo> rowType = makeConstantModifier(makeReferenceModifier(makeRowType(LINK(queryRecordType(expr->queryType())))));
+
+    HqlExprArray args;
+    ForEachItemIn(i, boundRows)
+        args.append(*LINK(boundRows.item(i).boundRow));
+    OwnedHqlExpr values = createValue(no_list, makeSetType(LINK(rowType)), args);
+
+    unsigned maxRows = values->numChildren();
+    Owned<ITypeInfo> declareType = makeConstantModifier(makeArrayType(LINK(rowType), maxRows));
+    OwnedITypeInfo rowsType = makeOutOfLineModifier(makeTableType(LINK(rowType), NULL, NULL, NULL));
+    if (options.canLinkConstantRows)
+        rowsType.setown(setLinkCountedAttr(rowsType, true));
+
+    OwnedHqlExpr table = declareCtx.getTempDeclare(declareType, values);
+    if (options.spanMultipleCpp)
     {
-        OwnedHqlExpr constRow = createConstantRowExpr(transforms->queryChild(row));
-        if (!constRow || !canGenerateStringInline(constRow->queryType()->getSize()))
-            return false;
-        rows.append(*constRow.getClear());
+        BuildCtx protoctx(*code, mainprototypesAtom);
+        protoctx.addDeclareExternal(table);
     }
 
-    HqlExprArray boundRows;
-    ForEachItemIn(i, rows)
+    tgt.count.setown(getSizetConstant(maxRows));
+    tgt.expr.setown(createValue(no_typetransfer, LINK(rowsType), LINK(table)));
+
+    declareCtx.associateExpr(exprKey, tgt);
+    return true;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+//The code generator uses the the run time library code used to build dictionaries to try and ensure they stay compatible.
+//
+//The following classes allow IHqlExpressions to be used with those external classes.  A ConstantRow * is used where a
+//row would normally be used at runtime.
+
+//This class provides the minimal functionality for the interface required to call the dictionary builder class
+class EclccEngineRowAllocator : public CInterfaceOf<IEngineRowAllocator>
+{
+public:
+    virtual byte * * createRowset(unsigned _numItems) { return (byte * *)malloc(_numItems * sizeof(byte *)); }
+    virtual byte * * linkRowset(byte * * rowset) { throwUnexpected(); }
+    virtual void releaseRowset(unsigned count, byte * * rowset) { free(rowset); }
+    virtual byte * * appendRowOwn(byte * * rowset, unsigned newRowCount, void * row)
     {
-        CHqlBoundExpr bound;
-        buildConstRow(record, &rows.item(i), bound);
-        boundRows.append(*bound.expr.getClear());
+        byte * * expanded = reallocRows(rowset, newRowCount-1, newRowCount);
+        expanded[newRowCount-1] = (byte *)row;
+        return expanded;
     }
+    virtual byte * * reallocRows(byte * * rowset, unsigned oldRowCount, unsigned newRowCount)
+    {
+        return (byte * *)realloc(rowset, newRowCount * sizeof(byte *));
+    }
+
+    virtual void * createRow() { throwUnexpected(); }
+    virtual void releaseRow(const void * row) {  } // can occur if a row is removed from a dictionary.
+    virtual void * linkRow(const void * row) { return const_cast<void *>(row); }  // can occur if a dictionary is resized.
+
+//Used for dynamically sizing rows.
+    virtual void * createRow(size32_t & allocatedSize) { throwUnexpected(); }
+    virtual void * resizeRow(size32_t newSize, void * row, size32_t & size) { throwUnexpected(); }
+    virtual void * finalizeRow(size32_t newSize, void * row, size32_t oldSize) { throwUnexpected(); }
+
+    virtual IOutputMetaData * queryOutputMeta() { return NULL; }
+    virtual unsigned queryActivityId() { return 0; }
+    virtual StringBuffer &getId(StringBuffer & out) { return out; }
+    virtual IOutputRowSerializer *createDiskSerializer(ICodeContext *ctx = NULL) { throwUnexpected(); }
+    virtual IOutputRowDeserializer *createDiskDeserializer(ICodeContext *ctx) { throwUnexpected(); }
+    virtual IOutputRowSerializer *createInternalSerializer(ICodeContext *ctx = NULL) { throwUnexpected(); }
+    virtual IOutputRowDeserializer *createInternalDeserializer(ICodeContext *ctx) { throwUnexpected(); }
+};
+
+//Use a (constant) transform to map selectors of the form queryActiveTableSelector().field
+static IHqlExpression * mapExprViaTransform(IHqlExpression * transform, IHqlExpression * expr)
+{
+    NewProjectMapper2 mapper;
+    mapper.setMapping(transform);
+    return mapper.expandFields(expr, queryActiveTableSelector(), queryActiveTableSelector(), queryActiveTableSelector());
+}
+
+//Implement hash - constructor parameter is the hash expression
+class EclccCHash : implements IHash
+{
+public:
+    EclccCHash(IHqlExpression * _hashExpr) : hashExpr(_hashExpr) {}
+
+    virtual unsigned hash(const void *data)
+    {
+        const ConstantRow * row = reinterpret_cast<const ConstantRow *>(data);
+        OwnedHqlExpr expanded = mapExprViaTransform(row->transform, hashExpr);
+        OwnedHqlExpr folded = foldHqlExpression(expanded);
+        assertex(folded->queryValue());
+        return (unsigned)getIntValue(folded, 0);
+    }
+
+protected:
+    LinkedHqlExpr hashExpr;
+};
+
+// implement compare -the constructor parameter is the list of fields to compare
+class EclccCCompare : implements ICompare
+{
+public:
+    EclccCCompare(IHqlExpression * _sortorder) : sortorder(_sortorder) {}
+
+    virtual int docompare(const void * _left,const void * _right) const
+    {
+        const ConstantRow * left = reinterpret_cast<const ConstantRow *>(_left);
+        const ConstantRow * right = reinterpret_cast<const ConstantRow *>(_right);
+
+        OwnedHqlExpr expandedLeft = mapExprViaTransform(left->transform, sortorder);
+        OwnedHqlExpr expandedRight = mapExprViaTransform(right->transform, sortorder);
+        OwnedHqlExpr order = createValue(no_order, LINK(signedType), expandedLeft.getClear(), expandedRight.getClear());
+        OwnedHqlExpr folded = foldHqlExpression(order);
+        assertex(folded->queryValue());
+        return (int)getIntValue(folded, 0);
+    }
+
+protected:
+    LinkedHqlExpr sortorder;
+};
+
+//The dictionary information class - the hash lookup versions are not implemented
+class EclccHashLookupInfo : implements IHThorHashLookupInfo
+{
+public:
+    EclccHashLookupInfo(IHqlExpression * hashExpr, IHqlExpression * sortorder)
+        : hasher(hashExpr), comparer(sortorder)
+    {
+    }
+
+    virtual IHash * queryHash() { return &hasher; }
+    virtual ICompare * queryCompare() { return &comparer; }
+    virtual IHash * queryHashLookup() { throwUnexpected(); }
+    virtual ICompare * queryCompareLookup() { throwUnexpected(); }
+
+protected:
+    EclccCHash hasher;
+    EclccCCompare comparer;
+};
 
+void HqlCppTranslator::createInlineDictionaryRows(HqlExprArray & args, ConstantRowArray & boundRows, IHqlExpression * keyRecord, IHqlExpression * nullRow)
+{
+    //The code generator uses the the run time library code used to build dictionaries to try and ensure they stay compatible.
+    HqlExprArray keyedDictFields;
+    expandRecord(keyedDictFields, queryActiveTableSelector(), keyRecord);
+    OwnedHqlExpr keyedlist = createSortList(keyedDictFields);
+    OwnedHqlExpr hash = createValue(no_hash32, LINK(unsignedType), LINK(keyedlist));
+
+    //Estimate a good hash table size from the number of rows - otherwise the size can be more than double the number of rows
+    size32_t hashSize = (boundRows.ordinality() * 4 / 3) + 1;
+    EclccEngineRowAllocator rowsetAllocator;
+    EclccHashLookupInfo hasher(hash, keyedlist);
+    RtlLinkedDictionaryBuilder builder(&rowsetAllocator, &hasher, hashSize);
+    ForEachItemIn(i, boundRows)
+        builder.appendOwn(&boundRows.item(i));
+
+    unsigned size = builder.getcount();
+    ConstantRow * * rows = reinterpret_cast<ConstantRow * *>(builder.queryrows());
+    for (unsigned i=0; i < size; i++)
+    {
+        if (rows[i])
+            args.append(*LINK(rows[i]->boundRow));
+        else
+            args.append(*LINK(nullRow)); 
+    }
+}
+
+bool HqlCppTranslator::doBuildDictionaryInlineTable(BuildCtx & ctx, IHqlExpression * expr, CHqlBoundExpr & tgt, ExpressionFormat format)
+{
+    if (!options.generateStaticInlineTables || !options.canLinkConstantRows)
+        return false;
+
+    IHqlExpression * dataset = expr->queryChild(0);
+    assertex(dataset->getOperator() == no_inlinetable);
+
+    IHqlExpression * transforms = dataset->queryChild(0);
+    IHqlExpression * record = dataset->queryRecord();
+    if (transforms->numChildren() == 0)
+    {
+        OwnedHqlExpr null = createDictionary(no_null, LINK(record));
+        buildDataset(ctx, null, tgt, format);
+        return true;
+    }
+
+    BuildCtx declareCtx(*code, literalAtom);
+    //Remove unique id when checking for constant datasets already generated
+    OwnedHqlExpr exprNoUnique = removeProperty(dataset, _uid_Atom);
+    OwnedHqlExpr exprKey = createAttribute(dictionaryAtom, exprNoUnique.getClear());
+    if (declareCtx.getMatchExpr(exprKey, tgt))
+        return true;
+
+    ConstantRowArray boundRows;
+    if (!buildConstantRows(boundRows, transforms))
+        return false;
+
+    OwnedHqlExpr keyRecord = getDictionaryKeyRecord(record);
     Owned<ITypeInfo> rowType = makeConstantModifier(makeReferenceModifier(makeRowType(LINK(queryRecordType(expr->queryType())))));
-    OwnedHqlExpr values = createValue(no_list, makeSetType(LINK(rowType)), boundRows);
+    OwnedHqlExpr nullExpr = createValue(no_nullptr, LINK(rowType));
 
-    Owned<ITypeInfo> tableType = makeConstantModifier(makeArrayType(LINK(rowType), maxRows));
-    OwnedITypeInfo rowsType = makeOutOfLineModifier(makeTableType(LINK(rowType), NULL, NULL, NULL));
-    if (options.canLinkConstantRows)
-        rowsType.setown(setLinkCountedAttr(rowsType, true));
+    HqlExprArray args;
+    try
+    {
+        createInlineDictionaryRows(args, boundRows, keyRecord, nullExpr);
+    }
+    catch (IException * e)
+    {
+        //If the hash or compare couldn't be done (e.g., some strange field type that isn't constant folded)
+        //then generate a warning and fall back to the default inline dictionary code
+        EXCLOG(e, "Generating an inline dictionary");
+        e->Release();
+        return false;
+    }
+    OwnedHqlExpr values = createValue(no_list, makeSetType(LINK(rowType)), args);
+
+    unsigned maxRows = values->numChildren();
+    Owned<ITypeInfo> declareType = makeConstantModifier(makeArrayType(LINK(rowType), maxRows));
+    OwnedITypeInfo rowsType = makeOutOfLineModifier(makeDictionaryType(LINK(rowType)));
+    rowsType.setown(setLinkCountedAttr(rowsType, true));
 
-    OwnedHqlExpr table = declareCtx.getTempDeclare(tableType, values);
+    OwnedHqlExpr table = declareCtx.getTempDeclare(declareType, values);
     if (options.spanMultipleCpp)
     {
         BuildCtx protoctx(*code, mainprototypesAtom);
@@ -2807,7 +3055,7 @@ bool HqlCppTranslator::doBuildDatasetInlineTable(BuildCtx & ctx, IHqlExpression
     tgt.count.setown(getSizetConstant(maxRows));
     tgt.expr.setown(createValue(no_typetransfer, LINK(rowsType), LINK(table)));
 
-    declareCtx.associateExpr(exprNoUnique, tgt);
+    declareCtx.associateExpr(exprKey, tgt);
     return true;
 }
 

+ 1 - 1
ecl/hqlcpp/hqlresource.cpp

@@ -2256,7 +2256,7 @@ protected:
             noteDataset(expr, expr, true);
             return;
         case no_createdictionary:
-            if (isEvaluateable(expr))
+            if (isEvaluateable(expr) && !isConstantDictionary(expr))
                 noteDataset(expr, expr, true);
             return;
 

+ 1 - 1
ecl/regress/serial7a.ecl

@@ -17,7 +17,7 @@
 
 IMPORT SerialTest;
 
-interestingWords := DICTIONARY([{'elves'},{'cheddar'}], SerialTest.wordRec);
+interestingWords := DICTIONARY([{'elves'},{'cheddar'},{'cheddar'}], SerialTest.wordRec);
 
 output(SerialTest.bookIndex(WILD(title), EXISTS(words(word IN interestingWords))));
 

+ 5 - 2
rtl/eclrtl/rtlds.cpp

@@ -536,7 +536,10 @@ void RtlLinkedDictionaryBuilder::init(IEngineRowAllocator * _rowAllocator, IHTho
 {
     hash  = _hashInfo->queryHash();
     compare  = _hashInfo->queryCompare();
-    initialSize = _initialSize;
+    if (_initialSize >= 4)
+        initialSize = _initialSize;
+    else
+        initialSize = 4;
     rowAllocator = LINK(_rowAllocator);
     table = NULL;
     usedCount = 0;
@@ -598,7 +601,7 @@ void RtlLinkedDictionaryBuilder::checkSpace()
         usedLimit = (tableSize * 3) / 4;
         usedCount = 0;
     }
-    else if (usedCount > usedLimit)
+    else if (usedCount >= usedLimit)
     {
         // Rehash
         byte * * oldTable = table;

+ 16 - 0
testing/ecl/dict5.ecl

@@ -0,0 +1,16 @@
+
+
+string s1 := 'one' : stored('s1');
+string s2 := 'five' : stored('s2');
+
+
+d1 := dictionary([{'one'},{'two'},{'three'}], { string name });
+d2 := dictionary([{'one'},{'two'},{'three'},{'four'}], { string name });
+d3 := dictionary([{'one' => 1},{'one' => 2},{'two'=>2},{'four'=>4}], { string name => unsigned number });
+
+s1 in d1;
+s2 not in d1;
+s1 in d2;
+s2 not in d2;
+s1 in d3;
+s2 not in d3;

+ 16 - 0
testing/ecl/dict5a.ecl

@@ -0,0 +1,16 @@
+
+
+string s1 := 'one' : stored('s1');
+string s2 := 'five' : stored('s2');
+
+
+d1 := dictionary([{'one'},{'two'},{'three'}], { string name }) : independent;
+d2 := dictionary([{'one'},{'two'},{'three'},{'four'}], { string name }) : independent;
+d3 := dictionary([{'one' => 1},{'one' => 2},{'two'=>2},{'four'=>4}], { string name => unsigned number }) : independent;
+
+s1 in d1;
+s2 not in d1;
+s1 in d2;
+s2 not in d2;
+s1 in d3;
+s2 not in d3;

+ 18 - 0
testing/ecl/key/dict5.xml

@@ -0,0 +1,18 @@
+<Dataset name='Result 1'>
+ <Row><Result_1>true</Result_1></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>true</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>true</Result_3></Row>
+</Dataset>
+<Dataset name='Result 4'>
+ <Row><Result_4>true</Result_4></Row>
+</Dataset>
+<Dataset name='Result 5'>
+ <Row><Result_5>true</Result_5></Row>
+</Dataset>
+<Dataset name='Result 6'>
+ <Row><Result_6>true</Result_6></Row>
+</Dataset>

+ 18 - 0
testing/ecl/key/dict5a.xml

@@ -0,0 +1,18 @@
+<Dataset name='Result 1'>
+ <Row><Result_1>true</Result_1></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>true</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>true</Result_3></Row>
+</Dataset>
+<Dataset name='Result 4'>
+ <Row><Result_4>true</Result_4></Row>
+</Dataset>
+<Dataset name='Result 5'>
+ <Row><Result_5>true</Result_5></Row>
+</Dataset>
+<Dataset name='Result 6'>
+ <Row><Result_6>true</Result_6></Row>
+</Dataset>