Chris@439: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@439: Chris@439: /* Chris@439: Sonic Visualiser Chris@439: An audio file viewer and annotation editor. Chris@439: Centre for Digital Music, Queen Mary, University of London. Chris@439: This file copyright 2008 QMUL. Chris@439: Chris@439: This program is free software; you can redistribute it and/or Chris@439: modify it under the terms of the GNU General Public License as Chris@439: published by the Free Software Foundation; either version 2 of the Chris@439: License, or (at your option) any later version. See the file Chris@439: COPYING included with this distribution for more information. Chris@439: */ Chris@439: Chris@439: #include "SimpleSPARQLQuery.h" Chris@439: #include "base/ProgressReporter.h" Chris@480: #include "base/Profiler.h" Chris@480: Chris@480: #include Chris@480: #include Chris@481: #include Chris@480: Chris@480: #include Chris@439: Chris@480: #include Chris@480: Chris@492: //#define DEBUG_SIMPLE_SPARQL_QUERY 1 Chris@461: Chris@439: #include Chris@439: Chris@439: using std::cerr; Chris@439: using std::endl; Chris@439: Chris@480: class WredlandWorldWrapper Chris@480: { Chris@480: public: Chris@492: WredlandWorldWrapper(); Chris@492: ~WredlandWorldWrapper(); Chris@480: Chris@492: bool isOK() const; Chris@480: Chris@492: bool loadUriIntoDefaultModel(QString uriString, QString &errorString); Chris@480: Chris@480: librdf_world *getWorld() { return m_world; } Chris@480: const librdf_world *getWorld() const { return m_world; } Chris@480: Chris@492: librdf_model *getDefaultModel() { return m_defaultModel; } Chris@492: const librdf_model *getDefaultModel() const { return m_defaultModel; } Chris@492: Chris@492: librdf_model *getModel(QString fromUri); Chris@492: void freeModel(QString forUri); Chris@480: Chris@480: private: Chris@492: QMutex m_mutex; Chris@492: Chris@480: librdf_world *m_world; Chris@492: librdf_storage *m_defaultStorage; Chris@492: librdf_model *m_defaultModel; Chris@480: Chris@492: std::set m_defaultModelUris; Chris@492: Chris@492: std::map m_ownStorageUris; Chris@492: std::map m_ownModelUris; Chris@492: Chris@492: bool loadUri(librdf_model *model, QString uri, QString &errorString); Chris@480: }; Chris@480: Chris@492: WredlandWorldWrapper::WredlandWorldWrapper() : Chris@492: m_world(0), m_defaultStorage(0), m_defaultModel(0) Chris@492: { Chris@492: m_world = librdf_new_world(); Chris@492: if (!m_world) { Chris@492: cerr << "SimpleSPARQLQuery: ERROR: Failed to create LIBRDF world!" << endl; Chris@492: return; Chris@492: } Chris@492: librdf_world_open(m_world); Chris@492: Chris@492: m_defaultStorage = librdf_new_storage(m_world, "trees", NULL, NULL); Chris@492: if (!m_defaultStorage) { Chris@495: std::cerr << "SimpleSPARQLQuery: WARNING: Failed to initialise Redland trees datastore, falling back to memory store" << std::endl; Chris@492: m_defaultStorage = librdf_new_storage(m_world, NULL, NULL, NULL); Chris@492: if (!m_defaultStorage) { Chris@492: std::cerr << "SimpleSPARQLQuery: ERROR: Failed to initialise Redland memory datastore" << std::endl; Chris@492: return; Chris@492: } Chris@492: } Chris@492: m_defaultModel = librdf_new_model(m_world, m_defaultStorage, NULL); Chris@492: if (!m_defaultModel) { Chris@492: std::cerr << "SimpleSPARQLQuery: ERROR: Failed to initialise Redland data model" << std::endl; Chris@492: return; Chris@492: } Chris@492: } Chris@492: Chris@492: WredlandWorldWrapper::~WredlandWorldWrapper() Chris@492: { Chris@492: while (!m_ownModelUris.empty()) { Chris@492: librdf_free_model(m_ownModelUris.begin()->second); Chris@492: m_ownModelUris.erase(m_ownModelUris.begin()); Chris@492: } Chris@492: while (!m_ownStorageUris.empty()) { Chris@492: librdf_free_storage(m_ownStorageUris.begin()->second); Chris@492: m_ownStorageUris.erase(m_ownStorageUris.begin()); Chris@492: } Chris@492: if (m_defaultModel) librdf_free_model(m_defaultModel); Chris@492: if (m_defaultStorage) librdf_free_storage(m_defaultStorage); Chris@492: if (m_world) librdf_free_world(m_world); Chris@492: } Chris@492: Chris@492: bool Chris@492: WredlandWorldWrapper::isOK() const { Chris@492: return (m_defaultModel != 0); Chris@492: } Chris@492: Chris@492: bool Chris@492: WredlandWorldWrapper::loadUriIntoDefaultModel(QString uriString, QString &errorString) Chris@492: { Chris@492: QMutexLocker locker(&m_mutex); Chris@492: Chris@492: if (m_defaultModelUris.find(uriString) != m_defaultModelUris.end()) { Chris@492: return true; Chris@492: } Chris@492: Chris@492: if (loadUri(m_defaultModel, uriString, errorString)) { Chris@492: m_defaultModelUris.insert(uriString); Chris@492: return true; Chris@492: } else { Chris@492: return false; Chris@492: } Chris@492: } Chris@492: Chris@492: librdf_model * Chris@492: WredlandWorldWrapper::getModel(QString fromUri) Chris@492: { Chris@492: QMutexLocker locker(&m_mutex); Chris@492: if (fromUri == "") { Chris@492: return getDefaultModel(); Chris@492: } Chris@492: if (m_ownModelUris.find(fromUri) != m_ownModelUris.end()) { Chris@492: return m_ownModelUris[fromUri]; Chris@492: } Chris@492: librdf_storage *storage = librdf_new_storage(m_world, "trees", NULL, NULL); Chris@492: if (!storage) { // don't warn here, we probably already did it in main ctor Chris@492: storage = librdf_new_storage(m_world, NULL, NULL, NULL); Chris@492: } Chris@492: librdf_model *model = librdf_new_model(m_world, storage, NULL); Chris@492: if (!model) { Chris@492: std::cerr << "SimpleSPARQLQuery: ERROR: Failed to create new model" << std::endl; Chris@492: librdf_free_storage(storage); Chris@492: return 0; Chris@492: } Chris@492: QString err; Chris@492: if (!loadUri(model, fromUri, err)) { Chris@492: std::cerr << "SimpleSPARQLQuery: ERROR: Failed to parse into new model: " << err.toStdString() << std::endl; Chris@492: librdf_free_model(model); Chris@492: librdf_free_storage(storage); Chris@492: m_ownModelUris[fromUri] = 0; Chris@492: return 0; Chris@492: } Chris@492: m_ownModelUris[fromUri] = model; Chris@492: m_ownStorageUris[fromUri] = storage; Chris@492: return model; Chris@492: } Chris@492: Chris@492: void Chris@492: WredlandWorldWrapper::freeModel(QString forUri) Chris@492: { Chris@493: #ifdef DEBUG_SIMPLE_SPARQL_QUERY Chris@493: std::cerr << "SimpleSPARQLQuery::freeModel: Model uri = \"" << forUri.toStdString() << "\"" << std::endl; Chris@493: #endif Chris@493: Chris@492: QMutexLocker locker(&m_mutex); Chris@492: if (forUri == "") { Chris@492: std::cerr << "SimpleSPARQLQuery::freeModel: ERROR: Can't free default model" << std::endl; Chris@492: return; Chris@492: } Chris@492: if (m_ownModelUris.find(forUri) == m_ownModelUris.end()) { Chris@493: #ifdef DEBUG_SIMPLE_SPARQL_QUERY Chris@493: std::cerr << "SimpleSPARQLQuery::freeModel: NOTE: Unknown or already-freed model (uri = \"" << forUri.toStdString() << "\")" << std::endl; Chris@493: #endif Chris@492: return; Chris@492: } Chris@492: Chris@492: librdf_model *model = m_ownModelUris[forUri]; Chris@492: if (model) librdf_free_model(model); Chris@492: m_ownModelUris.erase(forUri); Chris@492: Chris@492: if (m_ownStorageUris.find(forUri) != m_ownStorageUris.end()) { Chris@492: librdf_storage *storage = m_ownStorageUris[forUri]; Chris@492: if (storage) librdf_free_storage(storage); Chris@492: m_ownStorageUris.erase(forUri); Chris@492: } Chris@492: } Chris@492: Chris@492: bool Chris@492: WredlandWorldWrapper::loadUri(librdf_model *model, QString uri, QString &errorString) Chris@492: { Chris@492: librdf_uri *luri = librdf_new_uri Chris@492: (m_world, (const unsigned char *)uri.toUtf8().data()); Chris@492: if (!luri) { Chris@492: errorString = "Failed to construct librdf_uri!"; Chris@492: return false; Chris@492: } Chris@492: Chris@492: librdf_parser *parser = librdf_new_parser(m_world, "guess", NULL, NULL); Chris@492: if (!parser) { Chris@492: errorString = "Failed to initialise Redland parser"; Chris@492: return false; Chris@492: } Chris@492: Chris@492: std::cerr << "About to parse \"" << uri.toStdString() << "\"" << std::endl; Chris@492: Chris@492: Profiler p("SimpleSPARQLQuery: Parse URI into LIBRDF model"); Chris@492: Chris@492: if (librdf_parser_parse_into_model(parser, luri, NULL, model)) { Chris@492: Chris@492: errorString = QString("Failed to parse RDF from URI \"%1\"") Chris@492: .arg(uri); Chris@492: librdf_free_parser(parser); Chris@492: return false; Chris@492: Chris@492: } else { Chris@492: Chris@492: librdf_free_parser(parser); Chris@492: return true; Chris@492: } Chris@492: } Chris@492: Chris@492: Chris@439: class SimpleSPARQLQuery::Impl Chris@439: { Chris@439: public: Chris@489: Impl(SimpleSPARQLQuery::QueryType, QString query); Chris@439: ~Impl(); Chris@439: Chris@489: static bool addSourceToModel(QString sourceUri); Chris@492: static void closeSingleSource(QString sourceUri); Chris@489: Chris@439: void setProgressReporter(ProgressReporter *reporter) { m_reporter = reporter; } Chris@439: bool wasCancelled() const { return m_cancelled; } Chris@439: Chris@439: ResultList execute(); Chris@439: Chris@439: bool isOK() const; Chris@439: QString getErrorString() const; Chris@439: Chris@439: protected: Chris@480: static QMutex m_mutex; Chris@480: Chris@492: static WredlandWorldWrapper m_redland; Chris@480: Chris@480: ResultList executeDirectParser(); Chris@480: ResultList executeDatastore(); Chris@492: ResultList executeFor(QString modelUri); Chris@480: Chris@489: QueryType m_type; Chris@439: QString m_query; Chris@439: QString m_errorString; Chris@439: ProgressReporter *m_reporter; Chris@439: bool m_cancelled; Chris@439: }; Chris@439: Chris@492: WredlandWorldWrapper SimpleSPARQLQuery::Impl::m_redland; Chris@480: Chris@480: QMutex SimpleSPARQLQuery::Impl::m_mutex; Chris@480: Chris@489: SimpleSPARQLQuery::SimpleSPARQLQuery(QueryType type, QString query) : Chris@489: m_impl(new Impl(type, query)) Chris@480: { Chris@480: } Chris@439: Chris@439: SimpleSPARQLQuery::~SimpleSPARQLQuery() Chris@439: { Chris@439: delete m_impl; Chris@439: } Chris@439: Chris@439: void Chris@439: SimpleSPARQLQuery::setProgressReporter(ProgressReporter *reporter) Chris@439: { Chris@439: m_impl->setProgressReporter(reporter); Chris@439: } Chris@439: Chris@439: bool Chris@439: SimpleSPARQLQuery::wasCancelled() const Chris@439: { Chris@439: return m_impl->wasCancelled(); Chris@439: } Chris@439: Chris@439: SimpleSPARQLQuery::ResultList Chris@439: SimpleSPARQLQuery::execute() Chris@439: { Chris@439: return m_impl->execute(); Chris@439: } Chris@439: Chris@439: bool Chris@439: SimpleSPARQLQuery::isOK() const Chris@439: { Chris@439: return m_impl->isOK(); Chris@439: } Chris@439: Chris@439: QString Chris@439: SimpleSPARQLQuery::getErrorString() const Chris@439: { Chris@439: return m_impl->getErrorString(); Chris@439: } Chris@439: Chris@489: bool Chris@489: SimpleSPARQLQuery::addSourceToModel(QString sourceUri) Chris@480: { Chris@489: return SimpleSPARQLQuery::Impl::addSourceToModel(sourceUri); Chris@480: } Chris@480: Chris@492: void Chris@492: SimpleSPARQLQuery::closeSingleSource(QString sourceUri) Chris@492: { Chris@492: SimpleSPARQLQuery::Impl::closeSingleSource(sourceUri); Chris@492: } Chris@492: Chris@489: SimpleSPARQLQuery::Impl::Impl(QueryType type, QString query) : Chris@489: m_type(type), Chris@439: m_query(query), Chris@439: m_reporter(0), Chris@439: m_cancelled(false) Chris@439: { Chris@439: } Chris@439: Chris@439: SimpleSPARQLQuery::Impl::~Impl() Chris@439: { Chris@439: } Chris@439: Chris@439: bool Chris@439: SimpleSPARQLQuery::Impl::isOK() const Chris@439: { Chris@439: return (m_errorString == ""); Chris@439: } Chris@439: Chris@439: QString Chris@439: SimpleSPARQLQuery::Impl::getErrorString() const Chris@439: { Chris@439: return m_errorString; Chris@439: } Chris@439: Chris@439: SimpleSPARQLQuery::ResultList Chris@439: SimpleSPARQLQuery::Impl::execute() Chris@439: { Chris@439: ResultList list; Chris@439: Chris@490: QMutexLocker locker(&m_mutex); Chris@480: Chris@492: if (!m_redland.isOK()) { Chris@492: cerr << "ERROR: SimpleSPARQLQuery::execute: Failed to initialise Redland datastore" << endl; Chris@492: return list; Chris@440: } Chris@480: Chris@489: if (m_type == QueryFromSingleSource) { Chris@480: return executeDirectParser(); Chris@480: } else { Chris@480: return executeDatastore(); Chris@480: } Chris@492: Chris@492: #ifdef DEBUG_SIMPLE_SPARQL_QUERY Chris@492: if (m_errorString != "") { Chris@492: std::cerr << "SimpleSPARQLQuery::execute: error returned: \"" Chris@492: << m_errorString.toStdString() << "\"" << std::endl; Chris@492: } Chris@492: #endif Chris@480: } Chris@480: Chris@480: SimpleSPARQLQuery::ResultList Chris@480: SimpleSPARQLQuery::Impl::executeDirectParser() Chris@480: { Chris@492: #ifdef DEBUG_SIMPLE_SPARQL_QUERY Chris@492: std::cerr << "SimpleSPARQLQuery::executeDirectParser: Query is: \"" << m_query.toStdString() << "\"" << std::endl; Chris@492: #endif Chris@492: Chris@480: ResultList list; Chris@480: Chris@480: Profiler profiler("SimpleSPARQLQuery::executeDirectParser"); Chris@480: Chris@492: static QRegExp fromRE("from\\s+<([^>]+)>", Qt::CaseInsensitive); Chris@492: QString fromUri; Chris@492: Chris@492: if (fromRE.indexIn(m_query) < 0) { Chris@492: std::cerr << "SimpleSPARQLQuery::executeDirectParser: Query contains no FROM clause, nothing to parse from" << std::endl; Chris@492: return list; Chris@492: } else { Chris@492: fromUri = fromRE.cap(1); Chris@492: #ifdef DEBUG_SIMPLE_SPARQL_QUERY Chris@492: std::cerr << "SimpleSPARQLQuery::executeDirectParser: FROM URI is <" Chris@492: << fromUri.toStdString() << ">" << std::endl; Chris@440: #endif Chris@439: } Chris@439: Chris@492: return executeFor(fromUri); Chris@439: } Chris@440: Chris@480: SimpleSPARQLQuery::ResultList Chris@480: SimpleSPARQLQuery::Impl::executeDatastore() Chris@480: { Chris@492: #ifdef DEBUG_SIMPLE_SPARQL_QUERY Chris@492: std::cerr << "SimpleSPARQLQuery::executeDatastore: Query is: \"" << m_query.toStdString() << "\"" << std::endl; Chris@492: #endif Chris@492: Chris@480: ResultList list; Chris@489: Chris@480: Profiler profiler("SimpleSPARQLQuery::executeDatastore"); Chris@480: Chris@492: return executeFor(""); Chris@492: } Chris@492: Chris@492: SimpleSPARQLQuery::ResultList Chris@492: SimpleSPARQLQuery::Impl::executeFor(QString modelUri) Chris@492: { Chris@492: ResultList list; Chris@492: librdf_query *query; Chris@492: Chris@493: #ifdef DEBUG_SIMPLE_SPARQL_QUERY Chris@480: static std::map counter; Chris@480: if (counter.find(m_query) == counter.end()) counter[m_query] = 1; Chris@480: else ++counter[m_query]; Chris@480: std::cerr << "Counter for this query: " << counter[m_query] << std::endl; Chris@492: std::cerr << "Base URI is: \"" << modelUri.toStdString() << "\"" << std::endl; Chris@493: #endif Chris@480: Chris@480: { Chris@480: Profiler p("SimpleSPARQLQuery: Prepare LIBRDF query"); Chris@480: query = librdf_new_query Chris@492: (m_redland.getWorld(), "sparql", NULL, Chris@489: (const unsigned char *)m_query.toUtf8().data(), NULL); Chris@480: } Chris@480: Chris@480: if (!query) { Chris@480: m_errorString = "Failed to construct query"; Chris@480: return list; Chris@480: } Chris@480: Chris@480: librdf_query_results *results; Chris@480: { Chris@480: Profiler p("SimpleSPARQLQuery: Execute LIBRDF query"); Chris@492: results = librdf_query_execute(query, m_redland.getModel(modelUri)); Chris@480: } Chris@480: Chris@480: if (!results) { Chris@480: cerr << "SimpleSPARQLQuery: LIBRDF query failed" << endl; Chris@480: librdf_free_query(query); Chris@480: return list; Chris@480: } Chris@480: Chris@480: if (!librdf_query_results_is_bindings(results)) { Chris@480: cerr << "SimpleSPARQLQuery: LIBRDF query has wrong result type (not bindings)" << endl; Chris@480: librdf_free_query_results(results); Chris@480: librdf_free_query(query); Chris@480: return list; Chris@480: } Chris@480: Chris@480: int resultCount = 0; Chris@480: int resultTotal = librdf_query_results_get_count(results); // probably wrong Chris@480: m_cancelled = false; Chris@480: Chris@480: while (!librdf_query_results_finished(results)) { Chris@480: Chris@480: int count = librdf_query_results_get_bindings_count(results); Chris@480: Chris@480: KeyValueMap resultmap; Chris@480: Chris@480: for (int i = 0; i < count; ++i) { Chris@480: Chris@480: const char *name = Chris@480: librdf_query_results_get_binding_name(results, i); Chris@480: Chris@490: if (!name) { Chris@490: std::cerr << "WARNING: Result " << i << " of query has no name" << std::endl; Chris@490: continue; Chris@490: } Chris@490: Chris@480: librdf_node *node = Chris@480: librdf_query_results_get_binding_value(results, i); Chris@480: Chris@480: QString key = (const char *)name; Chris@480: Chris@480: if (!node) { Chris@492: #ifdef DEBUG_SIMPLE_SPARQL_QUERY Chris@492: std::cerr << i << ". " << key.toStdString() << " -> (nil)" << std::endl; Chris@492: #endif Chris@480: resultmap[key] = Value(); Chris@480: continue; Chris@480: } Chris@480: Chris@480: ValueType type = LiteralValue; Chris@481: QString text; Chris@481: Chris@481: if (librdf_node_is_resource(node)) { Chris@481: Chris@481: type = URIValue; Chris@481: librdf_uri *uri = librdf_node_get_uri(node); Chris@490: const char *us = (const char *)librdf_uri_as_string(uri); Chris@490: Chris@490: if (!us) { Chris@490: std::cerr << "WARNING: Result " << i << " of query claims URI type, but has null URI" << std::endl; Chris@490: } else { Chris@490: text = us; Chris@490: } Chris@481: Chris@481: } else if (librdf_node_is_literal(node)) { Chris@481: Chris@481: type = LiteralValue; Chris@490: Chris@490: const char *lit = (const char *)librdf_node_get_literal_value(node); Chris@490: if (!lit) { Chris@490: std::cerr << "WARNING: Result " << i << " of query claims literal type, but has no literal" << std::endl; Chris@490: } else { Chris@490: text = lit; Chris@490: } Chris@481: Chris@481: } else if (librdf_node_is_blank(node)) { Chris@481: Chris@481: type = BlankValue; Chris@481: Chris@494: const char *lit = (const char *)librdf_node_get_literal_value(node); Chris@494: if (lit) text = lit; Chris@494: Chris@481: } else { Chris@481: Chris@480: cerr << "SimpleSPARQLQuery: LIBRDF query returned unknown node type (not resource, literal, or blank)" << endl; Chris@480: } Chris@480: Chris@480: #ifdef DEBUG_SIMPLE_SPARQL_QUERY Chris@481: cerr << i << ". " << key.toStdString() << " -> " << text.toStdString() << " (type " << type << ")" << endl; Chris@480: #endif Chris@480: Chris@480: resultmap[key] = Value(type, text); Chris@480: Chris@492: // librdf_free_node(node); Chris@480: } Chris@480: Chris@480: list.push_back(resultmap); Chris@480: Chris@480: librdf_query_results_next(results); Chris@480: Chris@480: resultCount++; Chris@480: Chris@480: if (m_reporter) { Chris@480: if (resultCount >= resultTotal) { Chris@480: if (m_reporter->isDefinite()) m_reporter->setDefinite(false); Chris@480: m_reporter->setProgress(resultCount); Chris@480: } else { Chris@480: m_reporter->setProgress((resultCount * 100) / resultTotal); Chris@480: } Chris@480: Chris@480: if (m_reporter->wasCancelled()) { Chris@480: m_cancelled = true; Chris@480: break; Chris@480: } Chris@480: } Chris@480: } Chris@480: Chris@480: librdf_free_query_results(results); Chris@480: librdf_free_query(query); Chris@480: Chris@481: #ifdef DEBUG_SIMPLE_SPARQL_QUERY Chris@492: cerr << "SimpleSPARQLQuery::executeDatastore: All results retrieved (" << resultCount << " of them)" << endl; Chris@481: #endif Chris@480: Chris@480: return list; Chris@489: } Chris@489: Chris@489: bool Chris@489: SimpleSPARQLQuery::Impl::addSourceToModel(QString sourceUri) Chris@489: { Chris@489: QString err; Chris@489: Chris@490: QMutexLocker locker(&m_mutex); Chris@489: Chris@492: if (!m_redland.isOK()) { Chris@492: std::cerr << "SimpleSPARQLQuery::addSourceToModel: Failed to initialise Redland datastore" << std::endl; Chris@492: return false; Chris@489: } Chris@489: Chris@492: if (!m_redland.loadUriIntoDefaultModel(sourceUri, err)) { Chris@489: std::cerr << "SimpleSPARQLQuery::addSourceToModel: Failed to add source URI \"" << sourceUri.toStdString() << ": " << err.toStdString() << std::endl; Chris@489: return false; Chris@489: } Chris@489: return true; Chris@480: } Chris@480: Chris@492: void Chris@492: SimpleSPARQLQuery::Impl::closeSingleSource(QString sourceUri) Chris@492: { Chris@492: QMutexLocker locker(&m_mutex); Chris@492: Chris@492: m_redland.freeModel(sourceUri); Chris@492: } Chris@492: Chris@440: SimpleSPARQLQuery::Value Chris@489: SimpleSPARQLQuery::singleResultQuery(QueryType type, Chris@480: QString query, QString binding) Chris@440: { Chris@489: SimpleSPARQLQuery q(type, query); Chris@440: ResultList results = q.execute(); Chris@440: if (!q.isOK()) { Chris@440: cerr << "SimpleSPARQLQuery::singleResultQuery: ERROR: " Chris@440: << q.getErrorString().toStdString() << endl; Chris@440: return Value(); Chris@440: } Chris@440: if (results.empty()) { Chris@440: return Value(); Chris@440: } Chris@440: for (int i = 0; i < results.size(); ++i) { Chris@440: if (results[i].find(binding) != results[i].end() && Chris@440: results[i][binding].type != NoValue) { Chris@440: return results[i][binding]; Chris@440: } Chris@440: } Chris@440: return Value(); Chris@440: } Chris@440: Chris@440: Chris@440: