|
@@ -0,0 +1,857 @@
|
|
|
+/*##############################################################################
|
|
|
+
|
|
|
+ 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 <http://www.gnu.org/licenses/>.
|
|
|
+############################################################################## */
|
|
|
+
|
|
|
+#include "jliball.hpp"
|
|
|
+#include "eclrtl.hpp"
|
|
|
+
|
|
|
+#include "fileview.hpp"
|
|
|
+#include "fverror.hpp"
|
|
|
+#include "fvrelate.ipp"
|
|
|
+#include "deftype.hpp"
|
|
|
+#include "fvresultset.ipp"
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+void ViewFile::addRelation(ViewRelation & _relation)
|
|
|
+{
|
|
|
+ relations.append(_relation);
|
|
|
+}
|
|
|
+
|
|
|
+void ViewFile::addSuperFile(ViewFile & _superFile)
|
|
|
+{
|
|
|
+ if (!superFiles.contains(_superFile))
|
|
|
+ superFiles.append(_superFile);
|
|
|
+}
|
|
|
+
|
|
|
+void ViewFile::addSubFile(ViewFile & _subFile)
|
|
|
+{
|
|
|
+ throwUnexpected();
|
|
|
+}
|
|
|
+
|
|
|
+const char * ViewFile::queryFilename() const
|
|
|
+{
|
|
|
+ return definition->queryLogicalName();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+INewResultSet * ViewFile::queryResultSet()
|
|
|
+{
|
|
|
+ if (!cachedResultSet)
|
|
|
+ {
|
|
|
+ //This used to protect against exceptions - but complicated indexes now complain when the cursor is created, so no need to protect this.
|
|
|
+ cachedResultSet.setown(options.resultSetFactory->createNewFileResultSet(queryFilename(), options.cluster));
|
|
|
+ }
|
|
|
+
|
|
|
+ return cachedResultSet;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void ViewSuperFile::addSubFile(ViewFile & _subFile)
|
|
|
+{
|
|
|
+ if (!subFiles.contains(_subFile))
|
|
|
+ subFiles.append(_subFile);
|
|
|
+}
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+void ViewJoinColumnMapping::addFilterToPrimary(IFilteredResultSet * primaryResultSet, IResultSetCursor * secondaryCursor)
|
|
|
+{
|
|
|
+ primary->clearFilter(primaryResultSet);
|
|
|
+ if (secondaryIteratorColumn != NotFound)
|
|
|
+ {
|
|
|
+ Owned<IResultSetCursor> childCursor = secondaryCursor->getChildren(secondaryIteratorColumn);
|
|
|
+ if (childCursor->first())
|
|
|
+ {
|
|
|
+ do
|
|
|
+ {
|
|
|
+ doAddFilterToPrimary(primaryResultSet, childCursor);
|
|
|
+ } while (childCursor->next());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ doAddFilterToPrimary(primaryResultSet, secondaryCursor);
|
|
|
+}
|
|
|
+
|
|
|
+void ViewJoinColumnMapping::addFilterToSecondary(IFilteredResultSet * secondaryResultSet, IResultSetCursor * primaryCursor)
|
|
|
+{
|
|
|
+ assertex(secondaryIteratorColumn == NotFound);
|
|
|
+ MemoryAttr value;
|
|
|
+ primary->getValue(value, primaryCursor);
|
|
|
+ secondary->clearFilter(secondaryResultSet);
|
|
|
+ secondary->addFilter(secondaryResultSet, value);
|
|
|
+}
|
|
|
+
|
|
|
+void ViewJoinColumnMapping::doAddFilterToPrimary(IFilteredResultSet * primaryResultSet, IResultSetCursor * secondaryCursor)
|
|
|
+{
|
|
|
+ MemoryAttr value;
|
|
|
+ secondary->getValue(value, secondaryCursor);
|
|
|
+ primary->addFilter(primaryResultSet, value);
|
|
|
+}
|
|
|
+
|
|
|
+bool ViewJoinColumnMapping::canMapPrimaryToSecondary() const
|
|
|
+{
|
|
|
+ return (secondaryIteratorColumn == NotFound) && primary->canGet() && secondary->canSet();
|
|
|
+}
|
|
|
+
|
|
|
+bool ViewJoinColumnMapping::canMapSecondaryToPrimary() const
|
|
|
+{
|
|
|
+ return primary->canSet() && secondary->canGet();
|
|
|
+}
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+inline bool isSameString(const char * left, const char * right)
|
|
|
+{
|
|
|
+ if (left == right)
|
|
|
+ return true;
|
|
|
+ if (left && right)
|
|
|
+ return strcmp(left, right) == 0;
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+void ViewRelation::addFilterToPrimary(IFilteredResultSet * primaryResultSet, IResultSetCursor * secondaryCursor)
|
|
|
+{
|
|
|
+ ForEachItemIn(i, columnMappings)
|
|
|
+ columnMappings.item(i).addFilterToPrimary(primaryResultSet, secondaryCursor);
|
|
|
+}
|
|
|
+
|
|
|
+void ViewRelation::addFilterToSecondary(IFilteredResultSet * secondaryResultSet, IResultSetCursor * primaryCursor)
|
|
|
+{
|
|
|
+ ForEachItemIn(i, columnMappings)
|
|
|
+ columnMappings.item(i).addFilterToSecondary(secondaryResultSet, primaryCursor);
|
|
|
+}
|
|
|
+
|
|
|
+unsigned ViewRelation::queryMappingField(unsigned whichMapping, bool needPrimary) const
|
|
|
+{
|
|
|
+ if (whichMapping >= columnMappings.ordinality())
|
|
|
+ return NotFound;
|
|
|
+
|
|
|
+ ViewJoinColumnMapping & mapping = columnMappings.item(whichMapping);
|
|
|
+ if (needPrimary)
|
|
|
+ return mapping.primary->queryBaseColumn();
|
|
|
+ return mapping.secondary->queryBaseColumn();
|
|
|
+}
|
|
|
+
|
|
|
+//Not in the constructor so exceptions can be handled cleanly.
|
|
|
+void ViewRelation::init()
|
|
|
+{
|
|
|
+ INewResultSet * primaryResultSet = primaryFile->queryResultSet();
|
|
|
+ INewResultSet * secondaryResultSet = secondaryFile->queryResultSet();
|
|
|
+ if (primaryResultSet && secondaryResultSet)
|
|
|
+ {
|
|
|
+ FieldTransformInfoArray primaryFields;
|
|
|
+ FieldTransformInfoArray secondaryFields;
|
|
|
+
|
|
|
+ parseColumnMappingList(primaryFields, primaryResultSet->getMetaData(), false, definition->queryPrimaryFields());
|
|
|
+ parseColumnMappingList(secondaryFields, secondaryResultSet->getMetaData(), true, definition->querySecondaryFields());
|
|
|
+
|
|
|
+ //MORE: Check that if a secondary dataset column is specified that there is only one of them.
|
|
|
+ if (primaryFields.ordinality() == secondaryFields.ordinality())
|
|
|
+ {
|
|
|
+ ForEachItemIn(i, primaryFields)
|
|
|
+ {
|
|
|
+ FieldTransformInfo & curPrimary = primaryFields.item(i);
|
|
|
+ FieldTransformInfo & curSecondary = secondaryFields.item(i);
|
|
|
+
|
|
|
+ Owned<ViewJoinColumn> primaryColumn = new ViewJoinColumn(curPrimary.column, curPrimary.getTransforms, curPrimary.setTransforms);
|
|
|
+ Owned<ViewJoinColumn> secondaryColumn = new ViewJoinColumn(curSecondary.column, curSecondary.getTransforms, curSecondary.setTransforms);
|
|
|
+ columnMappings.append(* new ViewJoinColumnMapping(primaryColumn, secondaryColumn, curSecondary.datasetColumn));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ throwError4(FVERR_NumJoinFieldsMismatchXY, primaryFields.ordinality(), secondaryFields.ordinality(), primaryFile->queryFilename(), secondaryFile->queryFilename());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+bool ViewRelation::matches(IFileRelationship * searchDefinition, ViewFile * searchPrimaryFile, ViewFile * searchSecondaryFile) const
|
|
|
+{
|
|
|
+ if ((searchPrimaryFile != primaryFile) || (searchSecondaryFile != secondaryFile))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (!isSameString(definition->queryPrimaryFields(), searchDefinition->queryPrimaryFields()))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (!isSameString(definition->querySecondaryFields(), searchDefinition->querySecondaryFields()))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (!isSameString(definition->queryKind(), searchDefinition->queryKind()))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (definition->isPayload() != searchDefinition->isPayload())
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool ViewRelation::canMapPrimaryToSecondary(bool isEfficient) const
|
|
|
+{
|
|
|
+ //MORE: Implement isEfficient!
|
|
|
+ ForEachItemIn(i, columnMappings)
|
|
|
+ if (!columnMappings.item(i).canMapPrimaryToSecondary())
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool ViewRelation::canMapSecondaryToPrimary(bool isEfficient) const
|
|
|
+{
|
|
|
+ ForEachItemIn(i, columnMappings)
|
|
|
+ if (!columnMappings.item(i).canMapSecondaryToPrimary())
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void ViewRelation::queryPrimaryColumns(UnsignedArray & columns)
|
|
|
+{
|
|
|
+ ForEachItemIn(i, columnMappings)
|
|
|
+ {
|
|
|
+ ViewJoinColumnMapping & mapping = columnMappings.item(i);
|
|
|
+ columns.append(mapping.primary->queryBaseColumn());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void ViewRelation::querySecondaryColumns(UnsignedArray & columns)
|
|
|
+{
|
|
|
+ ForEachItemIn(i, columnMappings)
|
|
|
+ {
|
|
|
+ ViewJoinColumnMapping & mapping = columnMappings.item(i);
|
|
|
+ if (mapping.secondaryIteratorColumn != NotFound)
|
|
|
+ columns.append(mapping.secondaryIteratorColumn);
|
|
|
+ else
|
|
|
+ columns.append(mapping.secondary->queryBaseColumn());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+unsigned BrowseCreateInfo::getNestingCount(const ViewFile * search)
|
|
|
+{
|
|
|
+ unsigned count = 0;
|
|
|
+ ForEachItemIn(i, nestedFiles)
|
|
|
+ {
|
|
|
+ if (&nestedFiles.item(i) == search)
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+ViewFileWeb::ViewFileWeb(IResultSetFactory & resultSetFactory, const char * cluster)
|
|
|
+: viewOptions(resultSetFactory, cluster), directory(queryDistributedFileDirectory())
|
|
|
+{
|
|
|
+ nextId = 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ViewFile * ViewFileWeb::addFile(IDistributedFile * definition)
|
|
|
+{
|
|
|
+ ViewFile * file;
|
|
|
+ if (definition->querySuperFile())
|
|
|
+ file = new ViewSuperFile(++nextId, viewOptions, definition);
|
|
|
+ else
|
|
|
+ file = new ViewFile(++nextId, viewOptions, definition);
|
|
|
+ files.append(*file);
|
|
|
+ return file;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ViewRelation * ViewFileWeb::addRelation(IFileRelationship * definition, ViewFile * primaryFile, ViewFile * secondaryFile)
|
|
|
+{
|
|
|
+ ForEachItemIn(i, relations)
|
|
|
+ {
|
|
|
+ ViewRelation & cur = relations.item(i);
|
|
|
+ if (cur.matches(definition, primaryFile, secondaryFile))
|
|
|
+ return &cur;
|
|
|
+ }
|
|
|
+
|
|
|
+ Owned<ViewRelation> relation = new ViewRelation(++nextId, definition, primaryFile, secondaryFile);
|
|
|
+ relation->init();
|
|
|
+ relations.append(*LINK(relation));
|
|
|
+ primaryFile->addRelation(*relation);
|
|
|
+ secondaryFile->addRelation(*relation);
|
|
|
+ return relation;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+IFileTreeBrowser * ViewFileWeb::createBrowseTree(const char * browseRootFilename, bool isEfficient, unsigned maxRecursion)
|
|
|
+{
|
|
|
+ ViewFile * root = findFile(browseRootFilename);
|
|
|
+ if (!root)
|
|
|
+ throwError1(FVERR_CouldNotResolveX, browseRootFilename);
|
|
|
+
|
|
|
+ CFileTreeBrowser * browser = new CFileTreeBrowser(this);
|
|
|
+ BrowseCreateInfo info(browser, isEfficient, maxRecursion);
|
|
|
+ gatherBrowseFiles(info, root, NULL, NULL);
|
|
|
+ return browser;
|
|
|
+}
|
|
|
+
|
|
|
+ViewFile * ViewFileWeb::findFile(const char * filename)
|
|
|
+{
|
|
|
+ ForEachItemIn(i, files)
|
|
|
+ {
|
|
|
+ ViewFile & cur = files.item(i);
|
|
|
+ if (cur.matches(filename))
|
|
|
+ return &cur;
|
|
|
+ }
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+void ViewFileWeb::gatherBrowseFiles(BrowseCreateInfo & info, ViewFile * file, CRelatedBrowseFile * parentFile, ViewRelation * parentRelation)
|
|
|
+{
|
|
|
+ if (info.getNestingCount(file) >= info.maxRecursion)
|
|
|
+ return;
|
|
|
+
|
|
|
+ CRelatedBrowseFile * browseFile = info.browser->addFile(file, parentFile, parentRelation);
|
|
|
+ info.nestedFiles.append(*file);
|
|
|
+ ForEachItemIn(i2, file->relations)
|
|
|
+ {
|
|
|
+ ViewRelation & cur = file->relations.item(i2);
|
|
|
+ if (&cur != parentRelation)
|
|
|
+ {
|
|
|
+ if (stricmp(cur.queryDefinition()->queryKind(), S_LINK_RELATIONSHIP_KIND) != 0)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ //Check for secondary first - so that recursive definitions default to secondary->primary
|
|
|
+ if (file == cur.querySecondary())
|
|
|
+ {
|
|
|
+ if (cur.canMapSecondaryToPrimary(info.isEfficient))
|
|
|
+ gatherBrowseFiles(info, cur.queryPrimary(), browseFile, &cur);
|
|
|
+ }
|
|
|
+ else if (file == cur.queryPrimary())
|
|
|
+ {
|
|
|
+ if (cur.canMapPrimaryToSecondary(info.isEfficient))
|
|
|
+ gatherBrowseFiles(info, cur.querySecondary(), browseFile, &cur);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ info.nestedFiles.pop();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void ViewFileWeb::gatherWeb(const char * rootFilename, const ViewGatherOptions & options)
|
|
|
+{
|
|
|
+ ViewWalkOptions localOptions(options);
|
|
|
+ if (!localOptions.kind)
|
|
|
+ localOptions.kind = S_LINK_RELATIONSHIP_KIND;
|
|
|
+ localOptions.isExplicitFile = true;
|
|
|
+
|
|
|
+ if (!walkFile(rootFilename, NULL, localOptions))
|
|
|
+ throwError1(FVERR_CouldNotResolveX, rootFilename);
|
|
|
+
|
|
|
+ //MORE: Should possibly percolate relations between superfiles down to files they contain.
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void ViewFileWeb::gatherWebFromPattern(const char * filenamePattern, const ViewGatherOptions & options)
|
|
|
+{
|
|
|
+ ViewWalkOptions localOptions(options);
|
|
|
+ if (!localOptions.kind)
|
|
|
+ localOptions.kind = S_LINK_RELATIONSHIP_KIND;
|
|
|
+
|
|
|
+ Owned<IDistributedFileIterator> iter = queryDistributedFileDirectory().getIterator(filenamePattern, false, NULL);
|
|
|
+ if (iter->first())
|
|
|
+ {
|
|
|
+ do
|
|
|
+ {
|
|
|
+ IDistributedFile & cur = iter->query();
|
|
|
+ localOptions.isExplicitFile = true;
|
|
|
+ walkFile(cur.queryLogicalName(), &cur, localOptions);
|
|
|
+ } while (iter->next());
|
|
|
+ }
|
|
|
+ else
|
|
|
+ throwError1(FVERR_CouldNotResolveX, filenamePattern);
|
|
|
+
|
|
|
+ //MORE: Should possibly percolate relations between superfiles down to files they contain.
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+IViewRelatedFileIterator * ViewFileWeb::getFileIterator()
|
|
|
+{
|
|
|
+ return new CViewRelatedFileIterator(files, 0, this);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+IViewRelatedFile * ViewFileWeb::queryFile(unsigned i)
|
|
|
+{
|
|
|
+ if (files.isItem(i))
|
|
|
+ return &files.item(i);
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+void ViewFileWeb::walk(IViewFileWebVisitor & visitor)
|
|
|
+{
|
|
|
+ ForEachItemIn(i1, files)
|
|
|
+ visitor.visit(&files.item(i1));
|
|
|
+
|
|
|
+ ForEachItemIn(i2, relations)
|
|
|
+ visitor.visit(&relations.item(i2));
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ViewFile * ViewFileWeb::walkFile(const char * filename, IDistributedFile * alreadyResolved, ViewWalkOptions & options)
|
|
|
+{
|
|
|
+ ViewFile * thisFile = findFile(filename);
|
|
|
+ if (thisFile)
|
|
|
+ return thisFile;
|
|
|
+
|
|
|
+ if (options.explicitFilesOnly)
|
|
|
+ {
|
|
|
+ if (!options.isExplicitFile)
|
|
|
+ return NULL;
|
|
|
+ options.isExplicitFile = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ Owned<IDistributedFile> resolved = alreadyResolved ? LINK(alreadyResolved) : directory.lookup(filename);
|
|
|
+ if (!resolved)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ thisFile = addFile(resolved);
|
|
|
+ if (options.maximumDepth > 0)
|
|
|
+ {
|
|
|
+ options.maximumDepth--;
|
|
|
+
|
|
|
+ if (options.primaryDepth > 0)
|
|
|
+ {
|
|
|
+ options.primaryDepth--;
|
|
|
+ Owned<IFileRelationshipIterator> iter = directory.lookupFileRelationships(NULL, filename, NULL, NULL, options.kind, NULL, options.payload);
|
|
|
+ ForEach(*iter)
|
|
|
+ {
|
|
|
+ IFileRelationship & cur = iter->query();
|
|
|
+ ViewFile * otherFile = walkFile(cur.queryPrimaryFilename(), NULL, options);
|
|
|
+ if (otherFile)
|
|
|
+ {
|
|
|
+ addRelation(&cur, otherFile, thisFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ options.primaryDepth++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (options.secondaryDepth > 0)
|
|
|
+ {
|
|
|
+ options.secondaryDepth--;
|
|
|
+ Owned<IFileRelationshipIterator> iter = directory.lookupFileRelationships(filename, NULL, NULL, NULL, options.kind, NULL, options.payload);
|
|
|
+ ForEach(*iter)
|
|
|
+ {
|
|
|
+ IFileRelationship & cur = iter->query();
|
|
|
+ ViewFile * otherFile = walkFile(cur.querySecondaryFilename(), NULL, options);
|
|
|
+ if (otherFile)
|
|
|
+ {
|
|
|
+ addRelation(&cur, thisFile, otherFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ options.primaryDepth++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((options.superDepth > 0) && resolved->isSubFile())
|
|
|
+ {
|
|
|
+ options.superDepth--;
|
|
|
+ Owned<IDistributedSuperFileIterator> iter = resolved->getOwningSuperFiles();
|
|
|
+ ForEach(*iter)
|
|
|
+ {
|
|
|
+ IDistributedSuperFile & cur = iter->query();
|
|
|
+ //ViewFile * otherFile = walkFile(cur.queryLogicalName(), &cur, options): // could pass in to save lookup
|
|
|
+ ViewFile * otherFile = walkFile(cur.queryLogicalName(), &cur, options);
|
|
|
+ if (otherFile)
|
|
|
+ {
|
|
|
+ thisFile->addSuperFile(*otherFile);
|
|
|
+ otherFile->addSubFile(*thisFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ options.superDepth++;
|
|
|
+ }
|
|
|
+
|
|
|
+ IDistributedSuperFile * super = resolved->querySuperFile();
|
|
|
+ if ((options.subDepth > 0) && super)
|
|
|
+ {
|
|
|
+ options.subDepth--;
|
|
|
+ Owned<IDistributedFileIterator> iter = super->getSubFileIterator(false);
|
|
|
+ ForEach(*iter)
|
|
|
+ {
|
|
|
+ IDistributedFile & cur = iter->query();
|
|
|
+ ViewFile * otherFile = walkFile(cur.queryLogicalName(), &cur, options);
|
|
|
+ if (otherFile)
|
|
|
+ {
|
|
|
+ thisFile->addSubFile(*otherFile);
|
|
|
+ otherFile->addSuperFile(*thisFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ options.subDepth++;
|
|
|
+ }
|
|
|
+ options.maximumDepth--;
|
|
|
+ }
|
|
|
+ return thisFile;
|
|
|
+}
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+FilteredSecondaryResultSetCursor::FilteredSecondaryResultSetCursor(INewResultSet * _resultSet, IResultSetCursor * _primary, ViewRelation * _relation)
|
|
|
+: DelayedFilteredResultSetCursor(_resultSet)
|
|
|
+{
|
|
|
+ primary = _primary;
|
|
|
+ relation = _relation;
|
|
|
+}
|
|
|
+
|
|
|
+void FilteredSecondaryResultSetCursor::noteRelatedFileChanged()
|
|
|
+{
|
|
|
+ DelayedFilteredResultSetCursor::noteRelatedFileChanged();
|
|
|
+ clearFilters();
|
|
|
+}
|
|
|
+
|
|
|
+void FilteredSecondaryResultSetCursor::ensureFiltered()
|
|
|
+{
|
|
|
+ relation->addFilterToSecondary(filtered, primary);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+FilteredPrimaryResultSetCursor::FilteredPrimaryResultSetCursor(INewResultSet * _resultSet, IResultSetCursor * _secondary, ViewRelation * _relation)
|
|
|
+: DelayedFilteredResultSetCursor(_resultSet)
|
|
|
+{
|
|
|
+ secondary = _secondary;
|
|
|
+ relation = _relation;
|
|
|
+}
|
|
|
+
|
|
|
+void FilteredPrimaryResultSetCursor::noteRelatedFileChanged()
|
|
|
+{
|
|
|
+ DelayedFilteredResultSetCursor::noteRelatedFileChanged();
|
|
|
+
|
|
|
+ clearFilters();
|
|
|
+}
|
|
|
+
|
|
|
+void FilteredPrimaryResultSetCursor::ensureFiltered()
|
|
|
+{
|
|
|
+ relation->addFilterToPrimary(filtered, secondary);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+CRelatedBrowseFile::CRelatedBrowseFile(ViewFile * _file, CRelatedBrowseFile * _parent, ViewRelation * _relation)
|
|
|
+{
|
|
|
+ file = _file;
|
|
|
+ parent = _parent;
|
|
|
+ relation = _relation;
|
|
|
+
|
|
|
+ INewResultSet * resultSet = file->queryResultSet();
|
|
|
+ if (!resultSet)
|
|
|
+ throwError1(FVERR_CannotBrowseFile, file->queryFilename());
|
|
|
+
|
|
|
+ if (!parent)
|
|
|
+ {
|
|
|
+ filtered.setown(new DelayedFilteredResultSetCursor(resultSet));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (file == relation->querySecondary())
|
|
|
+ {
|
|
|
+ filtered.setown(new FilteredSecondaryResultSetCursor(resultSet, parent->queryCursor(), relation));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ filtered.setown(new FilteredPrimaryResultSetCursor(resultSet, parent->queryCursor(), relation));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ notifier.setown(new NotifyingResultSetCursor(filtered));
|
|
|
+}
|
|
|
+
|
|
|
+void CRelatedBrowseFile::addChild(CRelatedBrowseFile * child)
|
|
|
+{
|
|
|
+ children.append(*child);
|
|
|
+ notifier->addDependent(*child->notifier);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+IViewRelatedFile * CRelatedBrowseFile::queryDefinition() const
|
|
|
+{
|
|
|
+ return file;
|
|
|
+}
|
|
|
+
|
|
|
+IRelatedBrowseFile * CRelatedBrowseFile::queryParent() const
|
|
|
+{
|
|
|
+ return parent;
|
|
|
+}
|
|
|
+
|
|
|
+IViewRelation * CRelatedBrowseFile::queryParentRelation() const
|
|
|
+{
|
|
|
+ return relation;
|
|
|
+}
|
|
|
+
|
|
|
+IRelatedBrowseFile * CRelatedBrowseFile::queryChild(unsigned i) const
|
|
|
+{
|
|
|
+ if (children.isItem(i))
|
|
|
+ return &children.item(i);
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+IResultSetCursor * CRelatedBrowseFile::queryCursor()
|
|
|
+{
|
|
|
+ return notifier;
|
|
|
+}
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+CFileTreeBrowser::CFileTreeBrowser(ViewFileWeb * _web) : web(_web)
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
+CRelatedBrowseFile * CFileTreeBrowser::addFile(ViewFile * file, CRelatedBrowseFile * parent, ViewRelation * relation)
|
|
|
+{
|
|
|
+ CRelatedBrowseFile * next = new CRelatedBrowseFile(file, parent, relation);
|
|
|
+ files.append(*next);
|
|
|
+ if (parent)
|
|
|
+ parent->addChild(next);
|
|
|
+
|
|
|
+ return next;
|
|
|
+}
|
|
|
+
|
|
|
+IRelatedBrowseFile * CFileTreeBrowser::queryRootFile()
|
|
|
+{
|
|
|
+ return &files.item(0);
|
|
|
+}
|
|
|
+
|
|
|
+IResultSetFilter * CFileTreeBrowser::queryRootFilter()
|
|
|
+{
|
|
|
+ return &files.item(0);
|
|
|
+}
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+void ViewERdiagramVisitor::visit(ViewFile * file)
|
|
|
+{
|
|
|
+ activeFileId.clear().append(file->queryId());
|
|
|
+ activeFieldCount = 0;
|
|
|
+
|
|
|
+ IDistributedFile * definition = file->queryDefinition();
|
|
|
+ builder.beginFile(activeFileId, file->queryFilename(), definition);
|
|
|
+
|
|
|
+ activeFieldPrefix.clear().append(activeFileId).append("_");
|
|
|
+
|
|
|
+ INewResultSet * resultSet = file->queryResultSet();
|
|
|
+ if (resultSet)
|
|
|
+ {
|
|
|
+ const IResultSetMetaData & imeta = resultSet->getMetaData();
|
|
|
+ //Cheat and use an existing internal function
|
|
|
+ const CResultSetMetaData & meta = static_cast<const CResultSetMetaData &>(imeta);
|
|
|
+ meta.getXmlSchema(*this, false);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ StringBuffer dummyFieldId;
|
|
|
+ dummyFieldId.append(activeFieldPrefix).append(0);
|
|
|
+ builder.noteField(dummyFieldId, "**field-information-unavailable**", "");
|
|
|
+ }
|
|
|
+
|
|
|
+ builder.endFile();
|
|
|
+}
|
|
|
+
|
|
|
+void ViewERdiagramVisitor::visit(ViewRelation * relation)
|
|
|
+{
|
|
|
+ UnsignedArray primaryColumns;
|
|
|
+ UnsignedArray secondaryColumns;
|
|
|
+ relation->queryPrimaryColumns(primaryColumns);
|
|
|
+ relation->querySecondaryColumns(secondaryColumns);
|
|
|
+
|
|
|
+ unsigned sourceFieldId;
|
|
|
+ unsigned targetFieldId;
|
|
|
+ if (primaryColumns.ordinality())
|
|
|
+ {
|
|
|
+ sourceFieldId = primaryColumns.item(0);
|
|
|
+ targetFieldId = secondaryColumns.item(0);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ sourceFieldId = relation->queryPrimary()->queryFirstFieldId();
|
|
|
+ targetFieldId = relation->querySecondary()->queryFirstFieldId();
|
|
|
+ }
|
|
|
+
|
|
|
+ StringBuffer id, sourceId, targetId;
|
|
|
+ id.append(relation->queryId());
|
|
|
+ sourceId.append(relation->queryPrimary()->queryId()).append("_").append(sourceFieldId);
|
|
|
+ targetId.append(relation->querySecondary()->queryId()).append("_").append(targetFieldId);
|
|
|
+ builder.noteRelation(id, sourceId, targetId, relation->queryDefinition());
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void ViewERdiagramVisitor::addField(const char * name, ITypeInfo & type)
|
|
|
+{
|
|
|
+ StringBuffer eclTypeName;
|
|
|
+ noteNextField();
|
|
|
+ type.getECLType(eclTypeName);
|
|
|
+ if (name && (stricmp(name, "__fileposition__") == 0))
|
|
|
+ builder.noteField(activeFieldId.str(), "{virtual(fileposition)}", eclTypeName);
|
|
|
+ else
|
|
|
+ builder.noteField(activeFieldId.str(), name, eclTypeName);
|
|
|
+}
|
|
|
+
|
|
|
+void ViewERdiagramVisitor::addSetField(const char * name, const char * itemname, ITypeInfo & type)
|
|
|
+{
|
|
|
+ addField(name, type);
|
|
|
+}
|
|
|
+
|
|
|
+void ViewERdiagramVisitor::beginIfBlock()
|
|
|
+{
|
|
|
+ activeFieldCount++;
|
|
|
+}
|
|
|
+
|
|
|
+bool ViewERdiagramVisitor::beginDataset(const char * name, const char * childname)
|
|
|
+{
|
|
|
+ noteNextField();
|
|
|
+ builder.noteField(activeFieldId.str(), name, "dataset");
|
|
|
+ savedFieldCounts.append(activeFieldCount);
|
|
|
+ savedFieldCounts.append(activeFieldPrefix.length());
|
|
|
+ activeFieldPrefix.append(activeFieldCount).append("_");
|
|
|
+ activeFieldCount = 0;
|
|
|
+ return false; // don't expand contents
|
|
|
+}
|
|
|
+
|
|
|
+void ViewERdiagramVisitor::beginRecord(const char * name)
|
|
|
+{
|
|
|
+ noteNextField();
|
|
|
+ builder.noteField(activeFieldId.str(), name, "record");
|
|
|
+ builder.beginCompound();
|
|
|
+}
|
|
|
+
|
|
|
+void ViewERdiagramVisitor::endIfBlock()
|
|
|
+{
|
|
|
+ activeFieldCount++;
|
|
|
+}
|
|
|
+
|
|
|
+void ViewERdiagramVisitor::endDataset(const char * name, const char * childname)
|
|
|
+{
|
|
|
+ activeFieldPrefix.setLength(savedFieldCounts.pop());
|
|
|
+ activeFieldCount = savedFieldCounts.pop();
|
|
|
+}
|
|
|
+
|
|
|
+void ViewERdiagramVisitor::endRecord(const char * name)
|
|
|
+{
|
|
|
+ builder.endCompound();
|
|
|
+ activeFieldCount++;
|
|
|
+}
|
|
|
+
|
|
|
+void ViewERdiagramVisitor::noteNextField()
|
|
|
+{
|
|
|
+ activeFieldId.clear().append(activeFieldPrefix).append(activeFieldCount++);
|
|
|
+}
|
|
|
+
|
|
|
+//---------------------------------------------------------------------------
|
|
|
+
|
|
|
+ViewXgmmlERdiagramVisitor & ViewXgmmlERdiagramVisitor::addAttr(const char * name, const char * value)
|
|
|
+{
|
|
|
+ xgmml.append(" ").append(name).append("=\"");
|
|
|
+ encodeXML(value, xgmml, ENCODE_NEWLINES, (unsigned)-1, false);
|
|
|
+ xgmml.append("\"");
|
|
|
+ return *this;
|
|
|
+}
|
|
|
+
|
|
|
+ViewXgmmlERdiagramVisitor & ViewXgmmlERdiagramVisitor::addAttrTag(const char * name, const char * value)
|
|
|
+{
|
|
|
+ if (value && *value)
|
|
|
+ {
|
|
|
+ xgmml.append("<attr");
|
|
|
+ addAttr("name", name).addAttr("value", value);
|
|
|
+ xgmml.append("/>").newline();
|
|
|
+ }
|
|
|
+ return *this;
|
|
|
+}
|
|
|
+
|
|
|
+void ViewXgmmlERdiagramVisitor::beginFile(const char * id, const char * name, IDistributedFile * definition)
|
|
|
+{
|
|
|
+ xgmml.append(" <node");
|
|
|
+ addAttr("id", id).addAttr("label", name);
|
|
|
+ xgmml.append("><att>").newline();
|
|
|
+ xgmml.append(" <graph>").newline();
|
|
|
+}
|
|
|
+
|
|
|
+void ViewXgmmlERdiagramVisitor::noteField(const char * id, const char * name, const char * type)
|
|
|
+{
|
|
|
+ xgmml.append(" <node id=\"").append(id).append("\" label=\"").append(name).append("\">").newline();
|
|
|
+ addAttrTag("type", type);
|
|
|
+ xgmml.append(" </node>").newline();
|
|
|
+}
|
|
|
+
|
|
|
+void ViewXgmmlERdiagramVisitor::endFile()
|
|
|
+{
|
|
|
+ xgmml.append(" </graph>").newline();
|
|
|
+ xgmml.append(" </att></node>").newline();
|
|
|
+}
|
|
|
+
|
|
|
+void ViewXgmmlERdiagramVisitor::noteRelation(const char * id, const char * primaryId, const char * secondaryId, IFileRelationship * definition)
|
|
|
+{
|
|
|
+ StringBuffer temp;
|
|
|
+
|
|
|
+ //Note we want the direction from the secondary to the primary, so need to reverse some things
|
|
|
+ xgmml.append(" <edge");
|
|
|
+ addAttr("id", id).addAttr("source", secondaryId).addAttr("target", primaryId);
|
|
|
+
|
|
|
+ const char * kind = definition->queryKind();
|
|
|
+ if (kind && (stricmp(kind, S_VIEW_RELATIONSHIP_KIND) == 0))
|
|
|
+ addAttr("label", "view of");
|
|
|
+ xgmml.append(">").newline();
|
|
|
+
|
|
|
+ getInvertedCardinality(temp.clear(), definition->queryCardinality());
|
|
|
+ addAttrTag("cardinality", temp.str());
|
|
|
+ xgmml.append(" </edge>").newline();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void ViewXgmmlERdiagramVisitor::beginDiagram()
|
|
|
+{
|
|
|
+ xgmml.append("<xgmml>").newline();
|
|
|
+ xgmml.append("<graph>").newline();
|
|
|
+}
|
|
|
+
|
|
|
+void ViewXgmmlERdiagramVisitor::endDiagram()
|
|
|
+{
|
|
|
+ xgmml.append("</graph>").newline();
|
|
|
+ xgmml.append("</xgmml>").newline();
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+void ViewXgmmlERdiagramVisitor::beginCompound()
|
|
|
+{
|
|
|
+ depth++;
|
|
|
+}
|
|
|
+
|
|
|
+void ViewXgmmlERdiagramVisitor::endCompound()
|
|
|
+{
|
|
|
+ depth--;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+IViewFileWeb * createViewFileWeb(IResultSetFactory & resultSetFactory, const char * cluster)
|
|
|
+{
|
|
|
+ return new ViewFileWeb(resultSetFactory, cluster);
|
|
|
+}
|
|
|
+
|
|
|
+void createERdiagram(StringBuffer & xgmml, IViewFileWeb & _web)
|
|
|
+{
|
|
|
+ ViewFileWeb & web = static_cast<ViewFileWeb &>(_web);
|
|
|
+
|
|
|
+ ViewXgmmlERdiagramVisitor builder(xgmml);
|
|
|
+ ViewERdiagramVisitor visitor(builder);
|
|
|
+ builder.beginDiagram();
|
|
|
+ web.walk(visitor);
|
|
|
+ builder.endDiagram();
|
|
|
+}
|
|
|
+
|