Last active
February 5, 2020 21:29
-
-
Save jmakeig/0a331823ad9a458167f6 to your computer and use it in GitHub Desktop.
Evaluate a function in MarkLogic in a different context, such as against a different database, in another transaction, or as another user
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Return a function proxy to invoke a function in another context. | |
* The proxy can be called just like the original function, with the | |
* same arguments and return types. Example uses: to run the input | |
* as another user, against another database, or in a separate | |
* transaction. | |
* | |
* @param {Function} fct The function to invoke | |
* @param {Object} [options] The `xdmp.eval` options. | |
* Use `options.user` as a shortcut to | |
* specify a user name (versus an ID). | |
* `options.database` can take a `string` | |
* or a `number`. | |
* @param {Object} [thisArg] The `this` context when calling `fct` | |
* @return {Function} A function that accepts the same arguments as | |
* the originally input function. | |
*/ | |
function applyAs(fct, options, thisArg) { | |
return function _wrappedApplyAs() { | |
var args = Array.prototype.slice.call(arguments); | |
console.log(`args: ${args}`); | |
// Curry the function to include the params by closure. | |
// xdmp.invokeFunction requires that invoked functions have | |
// an arity of zero. | |
var f = function _internalArrayWrapper() { | |
// Nested Sequences are flattened. Thus if `fct` returns a Seqence | |
// there’s no way to differentiate it from the Sequence that | |
// `xdmp.invokeFunction` (or `xdmp.eval` or `xdmp.invoke` or `xdmp.spawn`) | |
// return. However, by wrapping the returned Sequence in something else— | |
// an array here—we can “pop” the stack to get the acutual return value. | |
return [fct.apply(thisArg, args)]; | |
}; | |
options = options || {}; | |
// Allow passing in database name, rather than id | |
if ("string" === typeof options.database) { | |
options.database = xdmp.database(options.database); | |
} | |
// Allow passing in user name, rather than id | |
if (options.user) { | |
options.userId = xdmp.user(options.user); | |
delete options.user; | |
} | |
// Allow the functions themselves to declare their transaction mode | |
if (fct.transactionMode && !options.transactionMode) { | |
options.transactionMode = fct.transactionMode; | |
} | |
return fn.head(xdmp.invokeFunction(f, options)).pop(); | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Return a function proxy to invoke a function in another context. | |
* The proxy can be called just like the original function, with the | |
* same arguments and return types. Example uses: to run the input | |
* as another user, against another database, or in a separate | |
* transaction. | |
* | |
* @param {Function} fct The function to invoke | |
* @param {Object} [options] The `xdmp.eval` options. | |
* Use `options.user` as a shortcut to | |
* specify a user name (versus an ID). | |
* `options.database` can take a `string` | |
* or a `number`. | |
* @param {Object} [thisArg] The `this` context when calling `fct` | |
* @return {Function} A function that accepts the same arguments as | |
* the originally input function. | |
*/ | |
function applyAs(fct, options, thisArg) { | |
return function _wrappedApplyAs() { | |
// Curry the function to include the params by closure. | |
// xdmp.invokeFunction requires that invoked functions have | |
// an arity of zero. | |
const f = () => { | |
// Nested Sequences are flattened. Thus if `fct` returns a Seqence | |
// there’s no way to differentiate it from the Sequence that | |
// `xdmp.invokeFunction` (or `xdmp.eval` or `xdmp.invoke` or `xdmp.spawn`) | |
// return. However, by wrapping the returned Sequence in something else— | |
// an array here—we can “pop” the stack to get the acutual return value. | |
return [fct.apply(thisArg, arguments)]; | |
}; | |
options = options || {}; | |
// Allow passing in database name, rather than id | |
if('string' === typeof options.database) { options.database = xdmp.database(options.database); } | |
// Allow passing in user name, rather than id | |
if(options.user) { options.userId = xdmp.user(options.user); delete options.user; } | |
// Allow the functions themselves to declare their transaction mode | |
if(fct.transactionMode && !(options.transactionMode)) { options.transactionMode = fct.transactionMode; } | |
return fn.head(xdmp.invokeFunction(f, options)).pop(); | |
//return xdmp.invokeFunction(f, options).toArray().pop().pop(); // <https://bugtrack.marklogic.com/bug/38646> | |
} | |
} | |
// For exmaple, a better MarkLogic eval: | |
const evil = applyAs( | |
eval, // Built-in eval | |
{ | |
database: 'Documents', | |
isolation: 'different-transaction' | |
} | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var out = [xdmp.transaction()]; | |
var transactionSame = applyAs(xdmp.transaction, {isolation: 'same-statement'}, 'one'); | |
var transactionDiff = applyAs(xdmp.transaction, {isolation: 'different-transaction'}, 'one'); | |
out.concat([transactionSame(), transactionDiff()]); | |
/* | |
[ | |
"9830740160355080717", | |
"9830740160355080717", // same | |
"15639343846052397268" // different | |
] | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Creates a new function that maps over the iterable results of another function. | |
* If the return of the input function isn’t iterable, it just yields the result. | |
* Strings aren’t considered Iterable. (And really shouldn’t be…ever.) | |
* (HOF FTW!) | |
* | |
* @example | |
* const getRoleIDs = map(applyAs(sec.getRoleIds, { database: xdmp.securityDatabase() }), fn.data); | |
* | |
* @param {Function} fun - The function whose output will be mapped | |
* @param {Function} [mapper] - The optional map function. Defaults to the identity function. | |
* @returns {GeneratorFunction} - A generator over the mapped results of calling `fun` | |
*/ | |
function map(fct, mapper) { | |
const ident = item => item; | |
mapper = mapper || ident; | |
return function* _map() { | |
const itr = fct.apply(null, arguments); | |
if('string' === typeof itr || !(itr[Symbol.iterator])) { | |
yield itr; | |
} else { | |
for(const item of itr) { | |
yield mapper(item); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment