diff options
Diffstat (limited to 'src/backend/tioga/tgRecipe.c')
-rw-r--r-- | src/backend/tioga/tgRecipe.c | 694 |
1 files changed, 694 insertions, 0 deletions
diff --git a/src/backend/tioga/tgRecipe.c b/src/backend/tioga/tgRecipe.c new file mode 100644 index 00000000000..ea52a715104 --- /dev/null +++ b/src/backend/tioga/tgRecipe.c @@ -0,0 +1,694 @@ +/*------------------------------------------------------------------------- + * + * tgRecipe.c-- + * Tioga recipe-related definitions + * these functions can be used in both the frontend and the + * backend + * + * this file must be kept current with recipe-schema.sql + * + * Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $Header: /cvsroot/pgsql/src/backend/tioga/Attic/tgRecipe.c,v 1.1.1.1 1996/07/09 06:22:00 scrappy Exp $ + * + *------------------------------------------------------------------------- + */ + +#include <stdlib.h> +#include "tioga/tgRecipe.h" + +#include "catalog/catalog.h" /*for newoid() */ + +static Arr_TgString *TextArray2ArrTgString(char *str); + +#define ARRAY_LEFT_DELIM '{' +#define ARRAY_RIGHT_DELIM '}' +#define ARRAY_ELEM_LEFT '"' +#define ARRAY_ELEM_RIGHT '"' +#define ARRAY_ELEM_SEPARATOR ',' + +/* maximum length of query string */ +#define MAX_QBUF_LENGTH 2048 + +/**** the queries being used ********/ +#define Q_RETRIEVE_RECIPE_BYNAME \ + "select * from Recipes where Recipes.elemName = '%s';" +#define Q_RETRIEVE_ELEMENTS_IN_RECIPE \ + "select e.* from Element e, Node n where n.belongsTo = '%s' and n.nodeElem = e.elemName;" +#define Q_RETRIEVE_NODES_IN_RECIPE \ + "select * from Node n where n.belongsTo = '%s'" +#define Q_LOOKUP_EDGES_IN_RECIPE \ + "select * from Edge e where e.belongsTo = '%s'" + +/* static functions only used here */ +static void fillTgElement(TgElement *elem, PortalBuffer *pbuf, int tupno); +static void fillTgNode(TgRecipe *r, TgNode *node, PortalBuffer *pbuf, int tupno); +static TgRecipe* fillTgRecipe(PortalBuffer* pbuf, int tupno); +static void lookupEdges(TgRecipe *r, char* name); +static void fillAllNodes(TgRecipe *r, char* name); +static void fillAllElements(TgRecipe *r, char* name); +static TgNode* connectTee(TgRecipe *r, TgNodePtr fromNode, TgNodePtr toNode, + int fromPort, int toPort); + +/* + * TextArray2ArrTgString -- take a string of the form: + * {"fooo", "bar", "xxxxx"} (for postgres) + * and parse it into a Array of TgString's + * + * always returns a valid Arr_TgString. It could be a newly initialized one with + * zero elements + */ +Arr_TgString* +TextArray2ArrTgString(char *str) +{ + Arr_TgString *result; + + char* beginQuote; + char* endQuote; + int nextlen; + char* word; + + result = newArr_TgString(); + + if ((str == NULL) || (str[0] == '\0')) + return result; + + if (*str != ARRAY_LEFT_DELIM) { + elog(NOTICE,"TextArray2ArrTgString: badly formed string, must have %c as \ +first character\n", ARRAY_LEFT_DELIM); + return result; + } + + str++; /* skip the first { */ + while ( *str != '}' ) { + if (*str == '\0') { + elog(NOTICE,"TextArray2ArrTgString: text string ended prematurely\n"); + return result; + } + + if ((beginQuote = index(str, ARRAY_ELEM_LEFT)) == NULL) { + elog(NOTICE,"textArray2ArrTgString: missing a begin quote\n"); + return result; + } + if ( (endQuote = index(beginQuote+1,'"')) == NULL) { + elog(NOTICE,"textArray2ArrTgString: missing an end quote\n"); + return result; + } + nextlen = endQuote - beginQuote; /* don't subtract one here because we + need the extra character for \0 anyway */ + word = (char*) malloc(nextlen); + strncpy(word, beginQuote+1, nextlen-1); + word[nextlen-1] ='\0'; + addArr_TgString(result, (TgString*)&word); + free (word); + str = endQuote + 1; + } + return result; +} + +/* ------------------------------------- +findElemInRecipe() + given an element name, find that element in the TgRecipe structure and return it. + + XXX Currently, this is done by linear search. Change to using a hash table. +-------------------------------------- */ + +TgElement* +findElemInRecipe(TgRecipe *r, char* elemName) +{ + int i; + Arr_TgElementPtr* arr = r->elements; + TgElement* e; + + for (i=0;i<arr->num;i++) { + e = (TgElement*)arr->val[i]; + if (strcmp(e->elemName, elemName) == 0) + return e; + } + elog (NOTICE, "Element named %s not found in recipe named %s", elemName, r->elmValue.elemName); + return NULL; +} + +/* ------------------------------------- +findNodeInRecipe() + given an node name, find that node in the TgRecipe structure and return it. + + XXX Currently, this is done by linear search. Change to using a hash table. +-------------------------------------- */ + +TgNode* +findNodeInRecipe(TgRecipe *r, char* nodeName) +{ + int i; + Arr_TgNodePtr* arr = r->allNodes; + TgNode *n; + + for (i=0;i<arr->num;i++) { + n = (TgNode*)arr->val[i]; + if (strcmp(n->nodeName, nodeName) == 0) + return n; + } + elog (NOTICE, "Node named %s not found in recipe named %s", nodeName, r->elmValue.elemName); + return NULL; +} + + +/* ------------------------------------- +fillTgNode + takes a query result in the PortalBuffer containing a Node + and converts it to a C Node strcture. + The Node structure passed in is 'filled' appropriately + +-------------------------------------- */ + +void +fillTgNode(TgRecipe* r, TgNode *node, PortalBuffer *pbuf, int tupno) +{ + char* nodeType; + char* nodeElem; + char* locString; /* ascii string rep of the point */ + static int attnums_initialized = 0; + static int nodeName_attnum; + static int nodeElem_attnum; + static int nodeType_attnum; + static int loc_attnum; + TgNodePtr BlankNodePtr; + int i; + + if (!attnums_initialized) { + /* the first time fillTgNode is called, + we find out all the relevant attribute numbers in a TgNode + so subsequent calls are speeded up, + the assumption is that the schema won't change between calls*/ + nodeName_attnum = PQfnumber(pbuf, tupno, "nodeName"); + nodeElem_attnum = PQfnumber(pbuf, tupno, "nodeElem"); + nodeType_attnum = PQfnumber(pbuf, tupno, "nodeType"); + loc_attnum = PQfnumber(pbuf, tupno, "loc"); + attnums_initialized = 1; + } + node->nodeName = PQgetAttr(pbuf, tupno, nodeName_attnum); + locString = PQgetvalue(pbuf, tupno, loc_attnum); + if (locString == NULL || locString[0] == '\0') { + node->loc.x = 0; node->loc.y = 0; /* assign to zero for default */ + } + else + { + float x,y; + sscanf(locString, "(%f, %f)", &x, &y); + node->loc.x = x; + node->loc.y = y; + } + nodeElem = PQgetvalue(pbuf, tupno, nodeElem_attnum); + node->nodeElem = findElemInRecipe(r,nodeElem); + node->inNodes = newArr_TgNodePtr(); + node->outNodes = newArr_TgNodePtr(); + + /* fill the inNodes array with as many NULL's are there are inPorts in + * the underlying element */ + BlankNodePtr = (TgNodePtr)NULL; + for (i = 0 ; i < node->nodeElem->inPorts->num ; i++) + addArr_TgNodePtr(node->inNodes, &BlankNodePtr); + + /* fill the outNodes array with as many NULL's are there are inPorts in + * the underlying element */ + for (i = 0 ; i < node->nodeElem->outPorts->num ; i++) + addArr_TgNodePtr(node->outNodes, &BlankNodePtr); + + nodeType = PQgetvalue(pbuf, tupno, nodeType_attnum); + + if (strcmp(nodeType, "Ingred") == 0) + node->nodeType = TG_INGRED_NODE; + else if (strcmp(nodeType, "Eye") == 0) + node->nodeType = TG_EYE_NODE; + else if (strcmp(nodeType, "Recipe") == 0) + node->nodeType = TG_RECIPE_NODE; + else + elog(NOTICE, "fillTgNode: unknown nodeType field value : %s\n", nodeType); + +} + +/* ------------------------------------- +fillTgElement + takes a query result in the PortalBuffer containing a Element + and converts it to a C TgElement strcture. + The TgElement structure passed in is 'filled' appropriately + ------------------------------------ */ + +void +fillTgElement(TgElement *elem, PortalBuffer *pbuf, int tupno) +{ + char* srcLang, *elemType; + static int attnums_initialized = 0; + static int elemName_attnum; + static int elemType_attnum; + static int inPorts_attnum; + static int inTypes_attnum; + static int outPorts_attnum; + static int outTypes_attnum; + static int doc_attnum; + static int keywords_attnum; + static int icon_attnum; + static int srcLang_attnum; + static int src_attnum; + static int owner_attnum; + + if (!attnums_initialized) { + /* the first time fillTgElement is called, + we find out all the relevant attribute numbers in a TgElement + so subsequent calls are speeded up, + the assumption is that the schema won't change between calls*/ + elemName_attnum = PQfnumber(pbuf, tupno, "elemName"); + elemType_attnum = PQfnumber(pbuf, tupno, "elemType"); + inPorts_attnum = PQfnumber(pbuf, tupno, "inPorts"); + inTypes_attnum = PQfnumber(pbuf, tupno, "inTypes"); + outPorts_attnum = PQfnumber(pbuf, tupno, "outPorts"); + outTypes_attnum = PQfnumber(pbuf, tupno, "outTypes"); + doc_attnum = PQfnumber(pbuf, tupno, "doc"); + keywords_attnum = PQfnumber(pbuf, tupno, "keywords"); + icon_attnum = PQfnumber(pbuf, tupno, "icon"); + srcLang_attnum = PQfnumber(pbuf, tupno, "srcLang"); + src_attnum = PQfnumber(pbuf, tupno, "src"); + attnums_initialized = 1; + } + + elem->elemName = PQgetAttr(pbuf, tupno, elemName_attnum); + elem->inPorts = TextArray2ArrTgString(PQgetvalue(pbuf, tupno, inPorts_attnum)); + elem->inTypes = TextArray2ArrTgString(PQgetvalue(pbuf, tupno, inTypes_attnum)); + elem->outPorts = TextArray2ArrTgString(PQgetvalue(pbuf, tupno, outPorts_attnum)); + elem->outTypes = TextArray2ArrTgString(PQgetvalue(pbuf, tupno, outTypes_attnum)); + elem->doc = PQgetAttr(pbuf, tupno, doc_attnum); + elem->keywords = TextArray2ArrTgString(PQgetvalue(pbuf, tupno, keywords_attnum)); + elem->icon = PQgetAttr(pbuf,tupno, icon_attnum); + elem->src = PQgetAttr(pbuf,tupno, src_attnum); + elem->owner = PQgetAttr(pbuf,tupno, owner_attnum); + + /* we don't need to keep the value returned so use PQgetvalue() + instead of PQgetAttr() */ + srcLang = PQgetvalue(pbuf,tupno, srcLang_attnum); + + if (strcmp(srcLang, "SQL") == 0) + elem->srcLang = TG_SQL; + else + if (strcmp(srcLang, "C") == 0) + elem->srcLang = TG_C; + else + if (strcmp(srcLang, "RecipeGraph") == 0) + elem->srcLang = TG_RECIPE_GRAPH; + else + if (strcmp(srcLang, "Compiled") == 0) + elem->srcLang = TG_COMPILED; + else + elog(NOTICE, "fillTgElement(): unknown srcLang field value : %s\n", srcLang); + + elemType = PQgetvalue(pbuf, tupno, elemType_attnum); + if (strcmp(elemType, "Ingred") == 0) + elem->elemType = TG_INGRED; + else if (strcmp(elemType, "Eye") == 0) + elem->elemType = TG_EYE; + else if (strcmp(elemType, "Recipe") == 0) + elem->elemType = TG_RECIPE; + else + elog(NOTICE, "fillTgElement(): unknown elemType field value : %s\n", elemType); + + +} +/* ------------------------------------- +lookupEdges - + look up the edges of a recipe and fill in the inNodes + and outNodes of each node. + In the process of connecting edges, we detect tee's and create + teeNodes. We add the teeNodes to the allNodes field of r as well +------------------------------------ */ +void +lookupEdges(TgRecipe *r, char* name) +{ + char qbuf[MAX_QBUF_LENGTH]; + int i; + char *pqres; + char *pbufname; + PortalBuffer *pbuf; + int ntups; + int fromNode_attnum; + int fromPort_attnum; + int toPort_attnum; + int toNode_attnum; + char *toNode, *fromNode; + char *toPortStr, *fromPortStr; + int toPort, fromPort; + + TgNodePtr fromNodePtr, toNodePtr; + + sprintf(qbuf, Q_LOOKUP_EDGES_IN_RECIPE, name); + pqres = PQexec(qbuf); + pqres = PQexec(qbuf); + if (*pqres == 'R' || *pqres == 'E') { + elog(NOTICE, "lookupEdges(): Error while executing query : %s\n", qbuf); + elog(NOTICE, "result = %s, error is %s\n", pqres, PQerrormsg); + return; + } + pbufname = ++pqres; + pbuf = PQparray(pbufname); + ntups = PQntuplesGroup(pbuf,0); + + if (ntups == 0) { return; } + + fromNode_attnum = PQfnumber(pbuf, 0, "fromNode"); + fromPort_attnum = PQfnumber(pbuf, 0, "fromPort"); + toNode_attnum = PQfnumber(pbuf, 0, "toNode"); + toPort_attnum = PQfnumber(pbuf, 0, "toPort"); + + for (i=0;i<ntups;i++) { + + fromNode = PQgetvalue(pbuf, i, fromNode_attnum); + toNode = PQgetvalue(pbuf, i, toNode_attnum); + fromPortStr = PQgetvalue(pbuf, i, fromPort_attnum); + toPortStr = PQgetvalue(pbuf, i, toPort_attnum); + + if (!fromPortStr || fromPortStr[0] == '\0') { + elog(NOTICE, "lookupEdges(): SANITY CHECK failed. Edge with invalid fromPort value!"); + return; + } + if (!toPortStr || toPortStr[0] == '\0') { + elog(NOTICE, "lookupEdges(): SANITY CHECK failed. Edge with invalid toPort value!!"); + return; + } + fromPort = atoi(fromPortStr); + toPort = atoi(toPortStr); + + fromNodePtr = findNodeInRecipe(r, fromNode); + if (!fromNodePtr) { + elog(NOTICE, "lookupEdges(): SANITY CHECK failed. Edge with bad fromNode value!"); + return; + } + toNodePtr = findNodeInRecipe(r, toNode); + if (!toNodePtr) { + elog(NOTICE, "lookupEdges(): SANITY CHECK failed. Edge with bad toNode value!"); + return; + } + + /* check to see if the from port is already connected. + if it is, then this means we should construct a Tee node + */ + if (fromNodePtr->outNodes->val[fromPort-1] != NULL) { + TgNodePtr tn; + + tn = connectTee(r,fromNodePtr, toNodePtr, fromPort, toPort); + addArr_TgNodePtr(r->allNodes,&tn); + } else { + fromNodePtr->outNodes->val[fromPort-1] = toNodePtr; + toNodePtr->inNodes->val[toPort-1] = fromNodePtr; + } + } + + PQclear(pbufname); +} + +/* + handle tee connections here + Everytime an output port is connected multiply, + we explicitly insert TgTeeNode + + returns the teeNode created +*/ +static TgNode* +connectTee(TgRecipe *r, TgNodePtr fromNode, TgNodePtr toNode, + int fromPort, int toPort) +{ + TgNodePtr origToNode; + TgNodePtr tn; + TgNodePtr BlankNodePtr; + int origToPort; + int i; + + /* the toNode formerly pointed to */ + origToNode = fromNode->outNodes->val[fromPort-1]; + + if (origToNode == NULL) { + elog(NOTICE,"Internal Error: connectTee() called with a null origToNode"); + return; + } + + for (i=0;i<origToNode->inNodes->num;i++) { + if (origToNode->inNodes->val[i] == fromNode) + break; + } + + /* the inport of the former toNode */ + /* ports start with 1, array indices start from 0 */ + origToPort = i + 1; + + /* add a tee node now. */ + tn = malloc(sizeof(TgNode)); + /* generate a name for the tee node table */ + tn->nodeName = malloc(50); + sprintf(tn->nodeName, "tee_%d", newoid()); +/* tn->nodeName = NULL; */ + + tn->nodeType = TG_TEE_NODE; + tn->nodeElem = NULL; + tn->inNodes = newArr_TgNodePtr(); + tn->outNodes = newArr_TgNodePtr(); + + BlankNodePtr = (TgNodePtr)NULL; + /* each TgTeeNode has one input and two outputs, NULL them initiallly */ + addArr_TgNodePtr(tn->inNodes, &BlankNodePtr); + addArr_TgNodePtr(tn->outNodes, &BlankNodePtr); + addArr_TgNodePtr(tn->outNodes, &BlankNodePtr); + + /* make the old toNode the left parent of the tee node + add the new toNode as the right parent of the tee node */ + tn->outNodes->val[0] = origToNode; + origToNode->inNodes->val[origToPort-1] = tn; + + tn->outNodes->val[1] = toNode; + toNode->inNodes->val[toPort-1] = tn; + + /* connect the fromNode to the new tee node */ + fromNode->outNodes->val[fromPort-1] = tn; + tn->inNodes->val[0] = fromNode; + + return tn; +} + +/* ------------------------------------- +fillAllNodes + fill out the nodes of a recipe + ------------------------------------ */ +void +fillAllNodes(TgRecipe *r, char* name) +{ + char qbuf[MAX_QBUF_LENGTH]; + int i; + char *pqres; + char *pbufname; + PortalBuffer *pbuf; + int ntups; + TgElement *elem; + TgNode *node; + + /* 1) fill out the elements that are in the recipe */ + sprintf(qbuf, Q_RETRIEVE_ELEMENTS_IN_RECIPE, name); + pqres = PQexec(qbuf); + if (*pqres == 'R' || *pqres == 'E') { + elog(NOTICE, "fillAllNodes(): Error while executing query : %s\n", qbuf); + elog(NOTICE, "result = %s, error is %s\n", pqres, PQerrormsg); + return; + } + pbufname = ++pqres; + pbuf = PQparray(pbufname); + ntups = PQntuplesGroup(pbuf,0); + for (i=0;i<ntups;i++) { + elem = malloc(sizeof(TgElement)); + fillTgElement(elem, pbuf, i); + addArr_TgElementPtr(r->elements, &elem); + } + PQclear(pbufname); + + sprintf(qbuf, Q_RETRIEVE_NODES_IN_RECIPE, name); + pqres = PQexec(qbuf); + if (*pqres == 'R' || *pqres == 'E') { + elog(NOTICE, "fillAllNodes(): Error while executing query : %s\n", qbuf); + elog(NOTICE, "result = %s, error is %s\n", pqres, PQerrormsg); + return; + } + pbufname = ++pqres; + pbuf = PQparray(pbufname); + ntups = PQntuplesGroup(pbuf,0); + for (i=0;i<ntups;i++) { + node = malloc(sizeof(TgNode)); + fillTgNode(r, node, pbuf, i); + addArr_TgNodePtr(r->allNodes, &node); + } + PQclear(pbufname); + +} + + +/* ------------------------------------- +fillAllElements + fill out the elements of a recipe + ------------------------------------ */ +void +fillAllElements(TgRecipe *r, char* name) +{ + char qbuf[MAX_QBUF_LENGTH]; + int i; + char *pqres; + char *pbufname; + PortalBuffer *pbuf; + int ntups; + TgElement *elem; + + sprintf(qbuf, Q_RETRIEVE_ELEMENTS_IN_RECIPE, name); + pqres = PQexec(qbuf); + if (*pqres == 'R' || *pqres == 'E') { + elog(NOTICE, "fillAllElements(): Error while executing query : %s\n", qbuf); + elog(NOTICE, "result = %s, error is %s\n", pqres, PQerrormsg); + return; + } + pbufname = ++pqres; + pbuf = PQparray(pbufname); + ntups = PQntuplesGroup(pbuf,0); + for (i=0;i<ntups;i++) { + elem = malloc(sizeof(TgElement)); + fillTgElement(elem, pbuf, i); + addArr_TgElementPtr(r->elements, &elem); + } + PQclear(pbufname); + +} + + +/* ------------------------------------- +fillTgRecipe + takes a query result in the PortalBuffer containing a Recipe + and converts it to a C TgRecipe strcture + ------------------------------------ */ +TgRecipe* +fillTgRecipe(PortalBuffer* pbuf, int tupno) +{ + TgRecipe* r; + int i,j; + + /* 1) set up the recipe structure */ + r = (TgRecipe*)malloc(sizeof(TgRecipe)); + fillTgElement(&r->elmValue, pbuf, 0); + r->elmValue.elemType = TG_RECIPE; + r->allNodes = newArr_TgNodePtr(); + r->rootNodes = newArr_TgNodePtr(); + r->eyes = newArr_TgNodePtr(); + r->tees = newArr_TgNodePtr(); + r->elements = newArr_TgElementPtr(); + + /* 2) find all the elements. There may be less elements than nodes + because you can have multiple instantiations of an element + in a recipe*/ + fillAllElements(r, r->elmValue.elemName); + + /* 3) find all the nodes in the recipe*/ + fillAllNodes(r, r->elmValue.elemName); + + /* 4) find all the edges, and connect the nodes, + may also add tee nodes to the allNodes field*/ + lookupEdges(r, r->elmValue.elemName); + + /* 5) find all the rootNodes in the recipe */ + /* root nodes are nodes with no incoming nodes or + whose incoming nodes are all null */ + /* 6) find all the eyes in the recipe */ + /* eye nodes are nodes with the node type TG_EYE_NODE */ + /* 7) find all the tee nodes in the recipe */ + /* tee nodes are nodes with the node type TG_TEE_NODE */ + for (i=0;i<r->allNodes->num;i++) { + TgNode* nptr = r->allNodes->val[i]; + + if (nptr->nodeType == TG_EYE_NODE) + addArr_TgNodePtr(r->eyes, &nptr); + else + if (nptr->nodeType == TG_TEE_NODE) + addArr_TgNodePtr(r->tees, &nptr); + + if (nptr->inNodes->num == 0) + addArr_TgNodePtr(r->rootNodes, &nptr); + else { + for (j=0; + j<nptr->inNodes->num && (nptr->inNodes->val[j] == NULL); + j++); + if (j == nptr->inNodes->num) + addArr_TgNodePtr(r->rootNodes, &nptr); + } + } + + return r; + +} + + +/* ------------------------------------- +retrieveRecipe + find the recipe with the given name + ------------------------------------ */ +TgRecipe* +retrieveRecipe(char* name) +{ + char qbuf[MAX_QBUF_LENGTH]; + TgRecipe* recipe; + char *pqres; + char *pbufname; + PortalBuffer *pbuf; + int ntups; + + sprintf(qbuf, Q_RETRIEVE_RECIPE_BYNAME, name); + + pqres = PQexec(qbuf); + if (*pqres == 'R' || *pqres == 'E') { + elog(NOTICE, "retrieveRecipe: Error while executing query : %s\n", qbuf); + elog(NOTICE, "result = %s, error is %s\n", pqres, PQerrormsg); + return NULL; + } + pbufname = ++pqres; + pbuf = PQparray(pbufname); + ntups = PQntuplesGroup(pbuf,0); + if (ntups == 0) { + elog(NOTICE, "retrieveRecipe(): No recipe named %s exists\n", name); + return NULL; + } + if (ntups != 1) { + elog(NOTICE, "retrieveRecipe(): Multiple (%d) recipes named %s exists\n", ntups, name); + return NULL; + } + + recipe = fillTgRecipe(pbuf,0); + + PQclear(pbufname); + return recipe; + +} + +/* -------------------- copyXXX functions ----------------------- */ +void copyTgElementPtr(TgElementPtr* from, TgElementPtr* to) +{ + *to = *from; +} + +void copyTgNodePtr(TgNodePtr* from, TgNodePtr* to) +{ + *to = *from; +} + +void copyTgRecipePtr(TgRecipePtr* from, TgRecipePtr* to) +{ + *to = *from; +} + +void copyTgString(TgString* from, TgString* to) +{ + TgString fromTgString = *from; + TgString toTgString; + toTgString = (TgString)malloc(strlen(fromTgString)+1); + strcpy(toTgString, fromTgString); + *to = toTgString; +} + |