BDB XML allows you to define your own functions that you can access
from your XQueries. To do this, you must provide an implementation
of XmlExternalFunction
, and you must
implement a XmlResolver
class that resolves
which external function to call.
XmlExternalFunction
implementations only
require you to implement the execute()
method with your function code. You must also implement a
close()
method that cleans up after
whatever activities your execute()
method
calls.
The execute()
method offers three
parameters:
XmlTransaction
This is the transaction in use, if any, at the time the external function was called.
XmlManager
The XmlManager
instance in use
at the time the function was called.
XmlArguments
An array of XmlResults
objects
which hold the argument values needed by this function.
For example, suppose you wanted to write an external function that takes two numbers and returns the first number to the power of the second number. It would look like this:
class MyExternalFunctionPow : public XmlExternalFunction { public: XmlResults execute(XmlTransaction &txn, XmlManager &mgr, const XmlArguments &args) const; void close(); }; /* External function pow() implementation */ XmlResults MyExternalFunctionPow::execute(XmlTransaction &txn, XmlManager &mgr, const XmlArguments &args) const { XmlResults argResult1 = args.getArgument(0); XmlResults argResult2 = args.getArgument(1); XmlValue arg1; XmlValue arg2; // Retrieve argument as XmlValue argResult1.next(arg1); argResult2.next(arg2); // Call pow() from C++ double result = pow(arg1.asNumber(),arg2.asNumber()); // Create an XmlResults for return XmlResults results = mgr.createResults(); XmlValue va(result); results.add(va); return results; } /* * In this example implementation, the resolver will return a new * instance of this function every time this function is called * so this function is required to clean up after itself. * * You could alternatively write the resolver such that it deletes * all function instances when the resolver is destroyed. If you * did that, the implementation of this method is not required. */ void MyExternalFunctionPow::close() { delete this; }
The XmlResolver
class is used to
provide a handle to the appropriate external function, when a given XQuery
statement requires an external function. For this reason, your
XmlResolver
implementation must have
knowledge of every external function you have implemented.
The resolver is responsible for instantiating an instance of the required external function. It is also responsible for destroying that instance, either once the query has been process or when the resolver instance itself is being destroyed. Which is the correct option for your application is an implementation detail that is up to you.
It is possible for your code to have multiple instances of an
XmlResolver
class, each instance of
which can potentially be responsible for different collections
of external functions. For this reason, you uniquely identify
each resolver class with a URI.
In order to call a specific external function, your XQueries must provide a URI as identification, as well as a function name. You can decide which external function to return based on the URI, the function name, and/or the number of arguments provided in the XQuery. Which of these are necessary for you to match the correct external function is driven by how many external functions you have implemented, how many resolver classes you have implemented, and how many variations on functions with the same name you have implemented. In theory, a very simple implementation could return an external function instance based only on the function name. Other implementation may need to match based on all possible criteria.
For the absolute most correct and safest implementation, you should match on all three criteria: URI, function name, and number of arguments.
For example, suppose you had two external functions:
SmallFunction
and
BigFunction
.
SmallFunction
is a small function that
requires few resources to instantiate and is called
infrequently. BigFunction
is a larger
function that opens containers, obtains lots of memory and from
a performance perspective is something that is best
instantiated once and then not destroyed until program
shutdown. Further, SmallFunction
takes two
arguments while BigFunction
takes five.
And XmlResolver
implementation for this
example would be as follows:
// Class declaration class MyFunResolver : public XmlResolver { public: MyFunResolver(); XmlExternalFunction *resolveExternalFunction(XmlTransaction *txn, XmlManager &mgr, const std::string &uri, const std::string &name, size_t numberOfArgs) const; string getUri(){ return uri_; } private: const string uri_; XmlExternalFunction *bigFun_; }; // Class constructor. The only required thing is to initialize the private // data member, uri_. We also instantiate a BigFunction instance in the // constructor, just because in our imaginary example we know we will // always need an instance of this reusable function. MyFunResolver::MyFunResolver() : uri_("my://my.fun.resolver"), bigFun_(0) { bigFun_ = new BigFunction(); } // Class destroyer MyFunResolver::~MyFunResolver() { bigFun_->close(); delete bigFun_; } // Resolver implementation. Here, we match based on the URI, the name // of the function, and the number of arguments. However, for a simple // example such as this, we could potentially match on just the function // name. XmlExternalFunction* MyFunResolver::resolveExternalFunction(XmlTransaction *txn, XmlManager &mgr, const std::string &uri, const std::string &name, size_t numberOfArgs) const { XmlExternalFunction *fun = 0; if (uri == uri_ && name == "sfunc" && numberOfArgs == 2 ) { fun = new SmallFunction(); } else if (uri == uri_ && name == "bfunc" && numberOfArgs == 5) { return bigFun_; } return fun; }
In order to use your external functions, you must register the
resolver that manages them. You do this with the
XmlManager::registerResolver()
method. You then set a URI prefix for the URI that you use to
identify your resolver. For example:
try { // Create an XmlManager XmlManager mgr; // Create an function resolver MyFunResolver resolver; // Register the function resolver to XmlManager mgr.registerResolver(resolver); XmlQueryContext context = mgr.createQueryContext(); // Set the prefix URI context.setNamespace("myxfunc", resolver.getUri());
To use the external function, declare them in the preamble of your query, and then use them as you would any XQuery function (for a complete explanation of examining query results, see the next section). For example:
declare function myxfunc:sfunc($a as xs:double, $b as xs:double) \ as xs:double external; myxfunc:sfunc(2,3);
You run this query as if you were running any other query.
string query = "declare function "; query += "myxfunc:sfunc($a as xs:double, $b as xs:double) "; query += "as xs:double external;\nmy:pow(2,3)"; // The first query returns the result of pow(2,3) XmlResults results = mgr.query(query, context); XmlValue va; while (results.next(va)) { cout << "The result of sfunc(2,3) is : " << va.asNumber() << endl; } } catch (XmlException &xe) { cout << "XmlException: " << xe.what() << endl; } return 0;