/*############################################################################## Copyright (C) 2011 HPCC Systems. All rights reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . ############################################################################## */ #include "jliball.hpp" #include "eclrtl.hpp" #include "fileview.hpp" #include "fverror.hpp" #include "fvrelate.ipp" #include "deftype.hpp" #include "fvresultset.ipp" #include "thorplugin.hpp" #include "eclrtl_imp.hpp" #include "hqlfold.hpp" #include "hqlvalid.hpp" #include "hqlrepository.hpp" static ViewTransformerRegistry * theTransformerRegistry; static ITypeInfo * stringType; static ITypeInfo * utf8Type; static ITypeInfo * unicodeType; static _ATOM addAtom; MODULE_INIT(INIT_PRIORITY_STANDARD) { addAtom = createIdentifierAtom("add"); stringType = makeStringType(UNKNOWN_LENGTH, NULL, NULL); utf8Type = makeUtf8Type(UNKNOWN_LENGTH, NULL); unicodeType = makeUnicodeType(UNKNOWN_LENGTH, NULL); theTransformerRegistry = new ViewTransformerRegistry; theTransformerRegistry->addTransformer(new ViewFailTransformer); theTransformerRegistry->addTransformer(new ViewAddTransformer); return true; } MODULE_EXIT() { delete theTransformerRegistry; unicodeType->Release(); utf8Type->Release(); stringType->Release(); } //--------------------------------------------------------------------------- void CardinalityElement::init(unsigned len, const char * _text) { if (len == 0) { text.set("1"); min = 1; max = 1; return; } text.set(_text, len); const char * dotdot = strstr(text, ".."); if (dotdot) { min = rtlStrToUInt4(dotdot-text, text); if (stricmp(dotdot+2, "M") == 0) max = Unbounded; else max = rtlVStrToUInt4(dotdot+2); } else { if (stricmp(text, "M") == 0) { min = 0; max = Unbounded; } else { min = rtlVStrToUInt4(text); max = min; } } } void CardinalityMapping::init(const char * text) { const char * colon = strchr(text, ':'); if (colon) { primary.init(colon-text, text); secondary.init(strlen(colon+1), colon+1); } else { //May as well try and make some sense of it primary.init(0, ""); secondary.init(strlen(text), text); } } void getInvertedCardinality(StringBuffer & out, const char * cardinality) { if (cardinality) { CardinalityMapping mapping(cardinality); out.append(mapping.secondary.text).append(":").append(mapping.primary.text); } } //--------------------------------------------------------------------------- ViewFieldTransformer * ViewFieldTransformer::bind(const HqlExprArray & args) { return LINK(this); } void ViewFieldTransformer::transform(MemoryAttr & utfTarget, const MemoryAttr & utfSrc) { //NB: The system utf8 functions typically take a length, whilst MemoryAttr provide a size. unsigned lenTarget; char * target; const char * source = static_cast(utfSrc.get()); unsigned lenSource = rtlUtf8Length(utfSrc.length(), source); transform(lenTarget, target, lenSource, source); unsigned sizeTarget = rtlUtf8Size(lenTarget, target); utfTarget.setOwn(lenTarget, target); } ViewFailTransformer::ViewFailTransformer() : ViewFieldTransformer(failAtom) { } void ViewFailTransformer::transform(unsigned & lenTarget, char * & target, unsigned lenSource, const char * source) { throwError(FVERR_FailTransformation); lenTarget = 0; target = NULL; } ViewAddTransformer::ViewAddTransformer() : ViewFieldTransformer(addAtom) { } ViewAddTransformer::ViewAddTransformer(const HqlExprArray & _args) : ViewFieldTransformer(addAtom) { appendArray(args, _args); } ViewFieldTransformer * ViewAddTransformer::bind(const HqlExprArray & args) { return new ViewAddTransformer(args); } void ViewAddTransformer::transform(unsigned & lenTarget, char * & target, unsigned lenSource, const char * source) { unsigned __int64 value = rtlUtf8ToInt(lenSource, source); if (args.ordinality()) value += args.item(0).queryValue()->getIntValue(); rtlInt8ToStrX(lenTarget, target, value); } //--------------------------------------------------------------------------- void ViewFieldUtf8Transformer::transform(unsigned & lenTarget, char * & target, unsigned lenSource, const char * source) { (*function)(lenTarget, target, lenSource, source); } void ViewFieldUnicodeTransformer::transform(unsigned & lenTarget, char * & target, unsigned lenSource, const char * source) { unsigned lenUnicodeSrc; unsigned lenUnicodeTarget; rtlDataAttr unicodeSrc; rtlDataAttr unicodeTarget; rtlUtf8ToUnicodeX(lenUnicodeSrc, unicodeSrc.refustr(), lenSource, source); (*function)(lenUnicodeTarget, unicodeTarget.refustr(), lenUnicodeSrc, unicodeSrc.getustr()); rtlUnicodeToUtf8X(lenTarget, target, lenUnicodeTarget, unicodeTarget.getustr()); } void ViewFieldStringTransformer::transform(unsigned & lenTarget, char * & target, unsigned lenSource, const char * source) { unsigned lenStringSrc; unsigned lenStringTarget; rtlDataAttr stringSrc; rtlDataAttr stringTarget; rtlUtf8ToStrX(lenStringSrc, stringSrc.refstr(), lenSource, source); (*function)(lenStringTarget, stringTarget.refstr(), lenStringSrc, stringSrc.getstr()); rtlStrToUtf8X(lenTarget, target, lenStringTarget, stringTarget.getstr()); } //--------------------------------------------------------------------------- ViewFieldTransformer * ViewFieldECLTransformer::bind(const HqlExprArray & args) { if (args.ordinality() == 0) return LINK(this); return new ViewFieldBoundECLTransformer(this, args); } void ViewFieldECLTransformer::transform(unsigned & lenTarget, char * & target, unsigned lenSource, const char * source, const HqlExprArray & extraArgs) { Owned sourceType = makeUtf8Type(lenSource, 0); IValue * sourceValue = createUtf8Value(source, LINK(sourceType)); OwnedHqlExpr sourceExpr = createConstant(sourceValue); HqlExprArray actuals; actuals.append(*LINK(sourceExpr)); appendArray(actuals, extraArgs); ThrowingErrorReceiver errors; OwnedHqlExpr call = createBoundFunction(&errors, function, actuals, NULL, true); OwnedHqlExpr castValue = ensureExprType(call, utf8Type); OwnedHqlExpr folded = quickFoldExpression(castValue, NULL, 0); IValue * foldedValue = folded->queryValue(); assertex(foldedValue); unsigned len = foldedValue->queryType()->getStringLen(); const char * data = static_cast(foldedValue->queryValue()); unsigned size = rtlUtf8Size(len, data); lenTarget = len; target = (char *)rtlMalloc(size); memcpy(target, data, size); } void ViewFieldECLTransformer::transform(unsigned & lenTarget, char * & target, unsigned lenSource, const char * source) { HqlExprArray extraArgs; transform(lenTarget, target, lenSource, source, extraArgs); } void ViewFieldBoundECLTransformer::transform(unsigned & lenTarget, char * & target, unsigned lenSource, const char * source) { transformer->transform(lenTarget, target, lenSource, source, args); } //--------------------------------------------------------------------------- void ViewTransformerRegistry::addTransformer(ViewFieldTransformer * ownedTransformer) { transformers.append(*ownedTransformer); } void ViewTransformerRegistry::addFieldUtf8Transformer(const char * name, utf8FieldTransformerFunction func) { transformers.append(* new ViewFieldUtf8Transformer(createIdentifierAtom(name), func)); } void ViewTransformerRegistry::addFieldStringTransformer(const char * name, stringFieldTransformerFunction func) { transformers.append(* new ViewFieldStringTransformer(createIdentifierAtom(name), func)); } void ViewTransformerRegistry::addFieldUnicodeTransformer(const char * name, unicodeFieldTransformerFunction func) { transformers.append(* new ViewFieldUnicodeTransformer(createIdentifierAtom(name), func)); } void ViewTransformerRegistry::addPlugins(const char * name) { loadedPlugins.setown(new SafePluginMap(&pluginCtx, true)); loadedPlugins->loadFromList(name); ThrowingErrorReceiver errors; dataServer.setown(createSourceFileEclRepository(&errors, name, NULL, NULL, 0)); HqlScopeArray scopes; HqlLookupContext ctx(NULL, &errors, NULL, dataServer); dataServer->getRootScopes(scopes, ctx); ForEachItemIn(i, scopes) { IHqlScope * scope = &scopes.item(i); HqlExprArray symbols; scope->getSymbols(symbols); ForEachItemIn(j, symbols) { IHqlExpression & cur = symbols.item(j); if (cur.getOperator() == no_service) addServiceDefinition(&cur); } } } static bool matchesSimpleTransformer(IHqlExpression * definition, ITypeInfo * type) { if (definition->queryBody()->queryType() != type) return false; if (definition->numChildren() != 2) return false; if (definition->queryChild(1)->queryType() != type) return false; return true; } void * ViewTransformerRegistry::resolveExternal(IHqlExpression * funcdef) { IHqlExpression *body = funcdef->queryChild(0); StringBuffer entry; StringBuffer lib; getProperty(body, entrypointAtom, entry); getProperty(body, libraryAtom, lib); if (!lib.length()) getProperty(body, pluginAtom, lib); ensureFileExtension(lib, SharedObjectExtension); Owned match = loadedPlugins->getPluginDll(lib.toCharArray(), NULL, false); // MORE - shouldn't it check the version???? if (!match) return NULL; return GetSharedProcedure(match->getInstance(), entry.toCharArray()); } ViewFieldTransformer * ViewTransformerRegistry::createTransformer(IHqlExpression * funcdef) { IHqlExpression *body = funcdef->queryChild(0); if(!body) return NULL; StringBuffer entry; StringBuffer lib; getProperty(body, entrypointAtom, entry); getProperty(body, libraryAtom, lib); if (!lib.length()) getProperty(body, pluginAtom, lib); if ((entry.length() == 0) || (lib.length() == 0)) return NULL; if(!body->hasProperty(pureAtom) && !body->hasProperty(templateAtom)) return NULL; if(!body->hasProperty(cAtom)) return NULL; if(body->hasProperty(gctxmethodAtom) || body->hasProperty(ctxmethodAtom) || body->hasProperty(omethodAtom)) return NULL; if(body->hasProperty(contextAtom) || body->hasProperty(globalContextAtom)) return NULL; //Special case string->string mapping (e.g., uppercase) if (matchesSimpleTransformer(funcdef, stringType)) { stringFieldTransformerFunction resolved = (stringFieldTransformerFunction)resolveExternal(funcdef); if (resolved) return new ViewFieldStringTransformer(funcdef->queryName(), resolved); } //Special case string->string mapping (e.g., uppercase) if (matchesSimpleTransformer(funcdef, unicodeType)) { unicodeFieldTransformerFunction resolved = (unicodeFieldTransformerFunction)resolveExternal(funcdef); if (resolved) return new ViewFieldUnicodeTransformer(funcdef->queryName(), resolved); } //MORE: special case string->string etc. return new ViewFieldECLTransformer(funcdef); } void ViewTransformerRegistry::addServiceDefinition(IHqlExpression * service) { Owned entry = new ViewServiceEntry; entry->name.set(service->queryName()->str()); HqlExprArray symbols; service->queryScope()->getSymbols(symbols); ForEachItemIn(i, symbols) { IHqlExpression & cur = symbols.item(i); if (cur.getOperator() == no_funcdef && cur.queryChild(0)->getOperator() == no_external) { ViewFieldTransformer * transformer = createTransformer(&cur); if (transformer) entry->transformers.append(*transformer); } } plugins.append(*entry.getClear()); } ViewFieldTransformer * find(const ViewFieldTransformerArray & transformers, const char * name, const HqlExprArray & args) { if (!name) return NULL; _ATOM search = createIdentifierAtom(name); ForEachItemIn(i, transformers) { ViewFieldTransformer & cur = transformers.item(i); if (cur.matches(search)) return cur.bind(args); } return NULL; } ViewFieldTransformer * ViewTransformerRegistry::resolve(const char * name, const HqlExprArray & args) { return find(transformers, name, args); } ViewFieldTransformer * ViewTransformerRegistry::resolve(const char * servicename, const char * functionName, const HqlExprArray & args) { if (!servicename) return NULL; ForEachItemIn(i, plugins) { ViewServiceEntry & cur = plugins.item(i); if (stricmp(servicename, cur.name) == 0) return find(cur.transformers, functionName, args); } return NULL; } IViewTransformerRegistry & queryTransformerRegistry() { return *theTransformerRegistry; } //--------------------------------------------------------------------------- bool containsFail(const ViewFieldTransformerArray & transforms) { ForEachItemIn(i, transforms) { if (transforms.item(i).matches(failAtom)) return true; } return false; } void translateValue(MemoryAttr & result, const MemoryAttr & filterValue, const ViewFieldTransformerArray & transforms) { unsigned numTransforms = transforms.ordinality(); if (numTransforms) { MemoryAttr tempValue[2]; unsigned whichTarget = 0; const MemoryAttr * source = &filterValue; for (unsigned i=0; i < numTransforms-1; i++) { MemoryAttr * target = &tempValue[whichTarget]; transforms.item(i).transform(*target, *source); source = target; whichTarget = 1-whichTarget; } transforms.item(numTransforms-1).transform(result, *source); } else result.set(filterValue.length(), filterValue.get()); } ViewJoinColumn::ViewJoinColumn(unsigned _whichColumn, const ViewFieldTransformerArray & _getTransforms, const ViewFieldTransformerArray & _setTransforms) { whichColumn = _whichColumn; appendArray(getTransforms, _getTransforms); appendArray(setTransforms, _setTransforms); getContainsFail = containsFail(getTransforms); setContainsFail = containsFail(setTransforms); } void ViewJoinColumn::addFilter(IFilteredResultSet * resultSet, const MemoryAttr & value) { const MemoryAttr * source = &value; MemoryAttr tempValue; if (setTransforms.ordinality()) { translateValue(tempValue, value, setTransforms); source = &tempValue; } resultSet->addFilter(whichColumn, source->length(), (const char *)source->get()); } void ViewJoinColumn::clearFilter(IFilteredResultSet * resultSet) { resultSet->clearFilter(whichColumn); } void ViewJoinColumn::getValue(MemoryAttr & value, IResultSetCursor * cursor) { if (getTransforms.ordinality()) { MemoryAttr rowValue; MemoryAttr2IStringVal adaptor(rowValue); cursor->getDisplayText(adaptor, whichColumn); translateValue(value, rowValue, getTransforms); } else { MemoryAttr2IStringVal adaptor(value); cursor->getDisplayText(adaptor, whichColumn); } } //--------------------------------------------------------------------------- struct TextReference { TextReference() { len =0; text = NULL; } TextReference(size32_t _len, const char * _text) : len(_len), text(_text) {} inline bool eq(const char * search) const { return (len == strlen(search)) && (memicmp(text, search, len) == 0); } inline void get(StringAttr & target) const { target.set(text, len); } void set(size32_t _len, const char * _text) { len = _len; text = _text; } IHqlExpression * createIntConstant(); IHqlExpression * createStringConstant(); size32_t len; const char * text; }; IHqlExpression * TextReference::createIntConstant() { return createConstant(rtlStrToInt8(len, text)); } IHqlExpression * TextReference::createStringConstant() { return createConstant(createUnicodeValue(text+1, len-2, "", true, true)); } class MappingParser { enum { TokEof=256, TokId, TokInt, TokString }; public: MappingParser(const IResultSetMetaData & _fieldMeta, bool _datasetSelectorAllowed) : fieldMeta(_fieldMeta), datasetSelectorAllowed(_datasetSelectorAllowed) {} void parseColumnMappingList(FieldTransformInfoArray & results, unsigned len, const char * text); protected: unsigned lexToken(); void assertToken(int expected); void getTokenText(StringAttr & target) { curToken.get(target); } void parseAttribute(FieldTransformInfo & output); void parseColumn(FieldTransformInfo & output); void parseColumnMapping(FieldTransformInfo & output); void parseConstantList(HqlExprArray & args); void parseTransformList(ViewFieldTransformerArray & transforms); protected: const IResultSetMetaData & fieldMeta; bool datasetSelectorAllowed; unsigned tokenType; TextReference curToken; unsigned offset; unsigned lenInput; const char * input; }; inline bool isLeadingIdentChar(byte next) { return isalpha(next) || (next == '_') || (next == '$'); } inline bool isTrailingIdentChar(byte next) { return isalnum(next) || (next == '_') || (next == '$'); } //MORE: This should really use the hqllexer - especially if it gets any more complex! unsigned MappingParser::lexToken() { const byte * buffer = (const byte *)input; unsigned cur = offset; while ((cur < lenInput) && isspace(buffer[cur])) cur++; if (cur < lenInput) { byte next = buffer[cur]; if (isLeadingIdentChar(next)) { cur++; while ((cur < lenInput) && (isTrailingIdentChar(buffer[cur]))) cur++; tokenType = TokId; } else if (isdigit(next) || ((next == '-') && (cur+1 < lenInput) && isdigit(buffer[cur+1]))) { cur++; while ((cur < lenInput) && (isdigit(buffer[cur]))) cur++; tokenType = TokInt; } else if (next == '\'') { cur++; while (cur < lenInput) { byte next = buffer[cur]; if (next == '\'') break; else if (next == '\\') { if (cur+1 < lenInput) cur++; } cur++; } if (cur == lenInput) throwError2(FVERR_BadStringTermination, cur-offset, input+offset); cur++; tokenType = TokString; } else { tokenType = next; cur++; } } else { tokenType = TokEof; } curToken.set(cur-offset, input+offset); offset = cur; return tokenType; } void MappingParser::assertToken(int expected) { if (tokenType != expected) { StringBuffer id; switch (expected) { case TokId: id.append("identifier"); break; default: id.append((char)expected); break; } unsigned len = lenInput-offset; if (len>10) len = 10; throwError3(FVERR_ExpectedX, id.str(), len, input+offset); } } void MappingParser::parseColumn(FieldTransformInfo & output) { output.datasetColumn = NotFound; output.column = NotFound; unsigned firstFieldIndex = 0; const IResultSetMetaData * curMeta = &fieldMeta; loop { StringAttr fieldName; assertToken(TokId); getTokenText(fieldName); lexToken(); //Cheat and cast the meta so the field lookup can be done more efficiently const CResultSetMetaData & castFieldMeta = static_cast(*curMeta); unsigned matchColumn = castFieldMeta.queryColumnIndex(firstFieldIndex, fieldName); if (matchColumn == NotFound) throwError1(FVERR_UnrecognisedFieldX, fieldName.get()); DisplayType kind = fieldMeta.getColumnDisplayType(matchColumn); switch (kind) { case TypeBoolean: case TypeInteger: case TypeUnsignedInteger: case TypeReal: case TypeString: case TypeData: case TypeUnicode: case TypeUnknown: case TypeSet: output.column = matchColumn; break; case TypeBeginRecord: //Restrict the search fields to the contents of the record. firstFieldIndex = matchColumn+1; break; case TypeDataset: { if (!datasetSelectorAllowed) throwError1(FVERR_CannotSelectFromDatasetX, fieldName.get()); if (output.datasetColumn != NotFound) throwError1(FVERR_CannotSelectManyFromDatasetX, fieldName.get()); firstFieldIndex = 0; curMeta = curMeta->getChildMeta(matchColumn); output.datasetColumn = matchColumn; break; } default: throwUnexpected(); } if (output.column != NotFound) return; assertToken('.'); lexToken(); } } void MappingParser::parseConstantList(HqlExprArray & args) { loop { switch (tokenType) { case TokInt: args.append(*curToken.createIntConstant()); break; case TokString: args.append(*curToken.createStringConstant()); break; default: unsigned len = lenInput - offset > 10 ? 10 : lenInput - offset; throwError3(FVERR_ExpectedX, "int or string constant", len, input+offset); } lexToken(); if (tokenType != ',') return; lexToken(); } } void MappingParser::parseTransformList(ViewFieldTransformerArray & transforms) { loop { assertToken(TokId); StringAttr mappingName, childName, grandName; curToken.get(mappingName); lexToken(); if (tokenType == '.') { lexToken(); assertToken(TokId); curToken.get(childName); lexToken(); } if (tokenType == '.') { lexToken(); assertToken(TokId); curToken.get(grandName); lexToken(); } HqlExprArray args; if (tokenType == '(') { lexToken(); if (tokenType != ')') parseConstantList(args); assertToken(')'); lexToken(); } ViewFieldTransformer * transform; if (childName) { transform = theTransformerRegistry->resolve(mappingName, childName, args); if (!transform) { //Maybe they specified the module name - should provide a 3 valued lookup transform = theTransformerRegistry->resolve(childName, grandName, args); } if (!transform) throwError2(FVERR_UnrecognisedMappingFunctionXY, mappingName.get(), childName.get()); } else { transform = theTransformerRegistry->resolve(mappingName, args); if (!transform) throwError1(FVERR_UnrecognisedMappingFunctionX, mappingName.get()); } transforms.append(*transform); if (tokenType != ',') break; lexToken(); } } void MappingParser::parseAttribute(FieldTransformInfo & output) { assertToken(TokId); if (curToken.eq("get")) { lexToken(); assertToken('('); lexToken(); parseTransformList(output.getTransforms); assertToken(')'); lexToken(); } else if (curToken.eq("set")) { lexToken(); assertToken('('); lexToken(); parseTransformList(output.setTransforms); assertToken(')'); lexToken(); } else if (curToken.eq("displayname")) { lexToken(); assertToken('('); lexToken(); assertToken(TokId); // could allow a string I guess curToken.get(output.naturalName); lexToken(); assertToken(')'); lexToken(); } else throwError1(FVERR_ExpectedX, "Definition name"); } void MappingParser::parseColumnMapping(FieldTransformInfo & output) { parseColumn(output); int endToken = '}'; //be flexible and allow () or {}? if (tokenType == '(') endToken = ')'; else if (tokenType != '{') return; lexToken(); if (tokenType != endToken) { loop { parseAttribute(output); if (tokenType != ',') break; lexToken(); } } assertToken(endToken); lexToken(); } void MappingParser::parseColumnMappingList(FieldTransformInfoArray & results, unsigned len, const char * text) { lenInput = len; input = text; offset = 0; lexToken(); if (tokenType == TokEof) return; loop { FieldTransformInfo * next = new FieldTransformInfo; results.append(*next); parseColumnMapping(*next); if (tokenType == TokEof) break; assertToken(','); lexToken(); } } void parseColumnMappingList(FieldTransformInfoArray & results, const IResultSetMetaData & fieldMeta, bool isDatasetAllowed, // if non null dataset.x is allowed, and column returned via pointer const char * text) { MappingParser parser(fieldMeta, isDatasetAllowed); parser.parseColumnMappingList(results, strlen(text), text); } void parseFileColumnMapping(FieldTransformInfoArray & results, const char * text, const IResultSetMetaData & fieldMeta) { MappingParser parser(fieldMeta, false); parser.parseColumnMappingList(results, strlen(text), text); } static void test() { HqlExprArray args; MemoryAttr source; source.set(26,"Gavin H\303\243lliday !!\316\261\316\221\307\272!!"); { Owned transform = theTransformerRegistry->resolve("stringlib","StringToUpperCase",args); MemoryAttr target; transform->transform(target, source); } { Owned transform = theTransformerRegistry->resolve("unicodelib","UnicodeToUpperCase",args); MemoryAttr target; transform->transform(target, source); } source.get(); }