Merge lp:~zorba-coders/zorba/caching into lp:zorba

Proposed by Matthias Brantner
Status: Superseded
Proposed branch: lp:~zorba-coders/zorba/caching
Merge into: lp:zorba
Diff against target: 1103 lines (+643/-39)
24 files modified
ChangeLog (+2/-0)
doc/zorba/options.dox (+21/-0)
include/zorba/pregenerated/diagnostic_list.h (+6/-0)
modules/com/zorba-xquery/www/modules/pregenerated/errors.xq (+4/-0)
modules/com/zorba-xquery/www/modules/pregenerated/warnings.xq (+20/-1)
src/annotations/annotations.cpp (+7/-1)
src/annotations/annotations.h (+2/-0)
src/compiler/codegen/plan_visitor.cpp (+5/-1)
src/context/static_context_consts.h (+2/-0)
src/diagnostics/diagnostic_en.xml (+35/-0)
src/diagnostics/pregenerated/diagnostic_list.cpp (+9/-0)
src/diagnostics/pregenerated/dict_en.cpp (+7/-0)
src/functions/udf.cpp (+211/-5)
src/functions/udf.h (+19/-0)
src/runtime/core/fncall_iterator.cpp (+161/-30)
src/runtime/core/fncall_iterator.h (+33/-0)
src/runtime/visitors/printer_visitor_impl.cpp (+17/-1)
src/store/api/index.h (+12/-0)
src/store/naive/simple_index_general.cpp (+14/-0)
src/store/naive/simple_index_general.h (+2/-0)
src/store/naive/simple_store.cpp (+3/-0)
test/rbkt/ExpCompilerResults/IterPlan/zorba/udf/udf-fib-rec.iter (+42/-0)
test/rbkt/ExpQueryResults/zorba/udf/udf-fib-rec.xml.res (+1/-0)
test/rbkt/Queries/zorba/udf/udf-fib-rec.xq (+8/-0)
To merge this branch: bzr merge lp:~zorba-coders/zorba/caching
Reviewer Review Type Date Requested Status
Matthias Brantner Approve
Markos Zaharioudakis Pending
Review via email: mp+82787@code.launchpad.net

This proposal supersedes a proposal from 2011-11-17.

This proposal has been superseded by a proposal from 2011-12-09.

Commit message

- automatic caching of recursive, non-sequential, and deterministic functions with atomic parameter and return types
- %ann:cache and %ann:no-cache for controlling function result caching

Description of the change

- automatic caching of recursive, non-sequential, and deterministic functions with atomic parameter and return types
- %ann:cache and %ann:no-cache for controlling function result caching

To post a comment you must log in.
Revision history for this message
Zorba Build Bot (zorba-buildbot) wrote : Posted in a previous version of this proposal
Revision history for this message
Zorba Build Bot (zorba-buildbot) wrote : Posted in a previous version of this proposal

The attempt to merge lp:~matthias-brantner/zorba/caching into lp:zorba failed. Below is the output from the failed tests.

CMake Error at /home/ceej/zo/testing/zorbatest/tester/TarmacLander.cmake:272 (message):
  Validation queue job caching-2011-11-04T17-29-08.827Z is finished. The
  final status was:

  31 tests did not succeed - changes not commited.

Error in read script: /home/ceej/zo/testing/zorbatest/tester/TarmacLander.cmake

Revision history for this message
Markos Zaharioudakis (markos-za) wrote : Posted in a previous version of this proposal

Here are a few problems I can see so far:

1. I don't think we can do function caching for variadic functions. The current implementation of user_function::computeResultCaching certainly does not support variadic functions, but more importantly, we need a fixed number of params to form the index key (unless we start thinking towards having more than one cache for variadic functions).

2. The user_function::computeResultCaching does the full computation every time it is invoked (and it may be invoked multiple times from the codegen). It should cache the result of the 1st computation and simply return it afterwards.

3. In UDFunctionCallIterator::createCache, the line:
   lSpec.theKeyTypes[i] = sig[i]->get_qname().getp();
is not correct if the type of the param is a user-defined type (because the store will not understand its name). You must call getBaseBuiltinType() of the param type first (like the translator does in line 4401).

4. It's probably worthwhile to do something so that we don't have to recompute the args in case of a cache miss. What we have to do is create a temp sequence for each arg, and fill it with the corresponding arg value; then bind the arg reference to that temp seq, instead the arg wrapper.

5. It's probably worthwhile to allow subtypes of xs:anyAtomicType? as param types. Index creation already supports "null" keys, but we will need a new probe function. This we should probably do as a phase-2 task.

Revision history for this message
Matthias Brantner (matthias-brantner) wrote : Posted in a previous version of this proposal

Adressed comments 1-4.

Revision history for this message
Matthias Brantner (matthias-brantner) : Posted in a previous version of this proposal
review: Approve
Revision history for this message
Zorba Build Bot (zorba-buildbot) wrote : Posted in a previous version of this proposal

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Revision history for this message
Zorba Build Bot (zorba-buildbot) wrote :
Revision history for this message
Zorba Build Bot (zorba-buildbot) wrote :

The attempt to merge lp:~matthias-brantner/zorba/caching into lp:zorba failed. Below is the output from the failed tests.

CMake Error at /home/ceej/zo/testing/zorbatest/tester/TarmacLander.cmake:272 (message):
  Validation queue job caching-2011-11-21T22-11-29.225Z is finished. The
  final status was:

  3 tests did not succeed - changes not commited.

Error in read script: /home/ceej/zo/testing/zorbatest/tester/TarmacLander.cmake

Revision history for this message
Markos Zaharioudakis (markos-za) wrote :

Matthias, can you change the ownership to zorba-coders so that I can do some small changes (documentation and style)?

Revision history for this message
Markos Zaharioudakis (markos-za) wrote :

I think there is a bug in user_function::computeResultCaching, starting at line 542. The condition: if (lExplicitCacheRequest) appears twice and theCacheResults will actually be set to true if the udf is sequential or non-deterministic.

Revision history for this message
Matthias Brantner (matthias-brantner) :
review: Approve
Revision history for this message
Zorba Build Bot (zorba-buildbot) wrote :

Attempt to merge into lp:zorba failed due to conflicts:

text conflict in ChangeLog
text conflict in src/context/static_context_consts.h

lp:~zorba-coders/zorba/caching updated
10540. By Matthias Brantner

merge with trunk

10541. By Matthias Brantner

merge with trunk

10542. By Matthias Brantner

merge

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ChangeLog'
2--- ChangeLog 2011-11-16 04:00:29 +0000
3+++ ChangeLog 2011-11-19 17:32:30 +0000
4@@ -54,6 +54,8 @@
5 xs:anyAtomicType or xs:untypedAtomic.
6 * Added undo for node revalidation
7 * Optimization for count(collection()) expressions
8+ * Caching of results for recursive functions with atomic parameter and return types.
9+ * Added %ann:cache and %ann:no-cache to enable or disable caching of results of functions with atomic parameter and return types.
10 * Fixed bug #867133 (SWIG PHP build failure on Mac OSX)
11 * Fixed bug #872796 (validate-in-place can interfere with other update primitives)
12 * Fixed bug #872799 (validate-in-place can set incorrect types)
13
14=== modified file 'doc/zorba/options.dox'
15--- doc/zorba/options.dox 2011-09-14 06:15:19 +0000
16+++ doc/zorba/options.dox 2011-11-19 17:32:30 +0000
17@@ -278,6 +278,27 @@
18 In order to be able to use the value twice, the <tt>string:materialize</tt> function must be used to materialize the entire contents of the file <tt>myfile.txt</tt> in memory.
19 Otherwise, the error zerr:ZSTR0055 is raised.
20
21+\paragraph caching_annotation Caching Results of Functions
22+Caching of function results might improve the performance if computational expensive functions are invoked multiple times with the same arguments.
23+
24+Zorba automatically caches results of recursive, deterministic, and non-sequential functions whose parameter and return types are subtypes of xs:anyAtomicType if at least optimization level O1 is used.
25+Specifically, if such a function is called twice with the same arguments, the result of the second call will return the same value without re-evaluating the function.
26+
27+For example, in the following recursive function computing a fibonacci number, each result is automatically cached and, hence, dramatically improves the performance.
28+
29+\include zorba/udf/udf-fib-rec.xq
30+
31+Specifically, this optimization reduces the complexity of the function from O(1.6^n) to O(n).
32+
33+In order to explicitly disable function caching, the user can specify the <tt>%ann:no-cache</tt> annotation.
34+
35+In addition, the user can use the <tt>%ann:cache</tt> annotation to cache the results of functions other than the ones that are automatically cached.
36+However, this will only work if the function is not updating and its parameter and return types are subtypes of xs:anyAtomicType.
37+Zorba will raise a warning if caching is explicitly enabled but the function does not meet this criteria (zwarn:ZWST0005).
38+
39+Please note, that explicitly enforcing caching for sequential or nondeterministic functions might not give the intended result.
40+In such cases, Zorba will raise a warning (zwarn:ZWST0006).
41+
42 \paragraph collection_index_annotations Annotations on Collections and Indexes
43
44 The \ref xqddf uses annotations to assign properties to collections and indexes.
45
46=== modified file 'include/zorba/pregenerated/diagnostic_list.h'
47--- include/zorba/pregenerated/diagnostic_list.h 2011-11-15 08:23:20 +0000
48+++ include/zorba/pregenerated/diagnostic_list.h 2011-11-19 17:32:30 +0000
49@@ -600,6 +600,8 @@
50
51 extern ZORBA_DLL_PUBLIC ZorbaErrorCode ZDDY0034_INDEX_RANGE_VALUE_PROBE_BAD_KEY_TYPES;
52
53+extern ZORBA_DLL_PUBLIC ZorbaErrorCode ZDDY0035_INDEX_GENERAL_INSERT;
54+
55 extern ZORBA_DLL_PUBLIC ZorbaErrorCode ZDDY0031_IC_NOT_DECLARED;
56
57 extern ZORBA_DLL_PUBLIC ZorbaErrorCode ZDDY0032_IC_NOT_ACTIVATED;
58@@ -752,6 +754,10 @@
59
60 extern ZORBA_DLL_PUBLIC ZorbaWarningCode ZWST0004_AMBIGUOUS_SEQUENTIAL_FLWOR;
61
62+extern ZORBA_DLL_PUBLIC ZorbaWarningCode ZWST0005_CACHING_NOT_POSSIBLE;
63+
64+extern ZORBA_DLL_PUBLIC ZorbaWarningCode ZWST0006_CACHING_MIGHT_NOT_BE_INTENDED;
65+
66 } // namespace zwarn
67 } // namespace zorba
68 #endif /* ZORBA_DIAGNOSTIC_LIST_API_H */
69
70=== modified file 'modules/com/zorba-xquery/www/modules/pregenerated/errors.xq'
71--- modules/com/zorba-xquery/www/modules/pregenerated/errors.xq 2011-11-15 08:23:20 +0000
72+++ modules/com/zorba-xquery/www/modules/pregenerated/errors.xq 2011-11-19 17:32:30 +0000
73@@ -501,6 +501,10 @@
74
75 (:~
76 :)
77+declare variable $zerr:ZDDY0035 as xs:QName := fn:QName($zerr:NS, "zerr:ZDDY0035");
78+
79+(:~
80+:)
81 declare variable $zerr:ZDDY0031 as xs:QName := fn:QName($zerr:NS, "zerr:ZDDY0031");
82
83 (:~
84
85=== modified file 'modules/com/zorba-xquery/www/modules/pregenerated/warnings.xq'
86--- modules/com/zorba-xquery/www/modules/pregenerated/warnings.xq 2011-11-15 08:10:49 +0000
87+++ modules/com/zorba-xquery/www/modules/pregenerated/warnings.xq 2011-11-19 17:32:30 +0000
88@@ -53,4 +53,23 @@
89
90 (:~
91 :)
92-declare variable $zwarn:ZWST0004 as xs:QName := fn:QName($zwarn:NS, "zwarn:ZWST0004");
93\ No newline at end of file
94+declare variable $zwarn:ZWST0004 as xs:QName := fn:QName($zwarn:NS, "zwarn:ZWST0004");
95+
96+(:~
97+ :
98+ : This warning is raised if the user explicitly enables caching
99+ : of function results (using the %ann:cache annotation) but the function
100+ : is updating or its parameter and return types are not subtypes of
101+ : xs:anyAtomicType.
102+ :
103+:)
104+declare variable $zwarn:ZWST0005 as xs:QName := fn:QName($zwarn:NS, "zwarn:ZWST0005");
105+
106+(:~
107+ :
108+ : This warning is raised if the user explicitly enables caching
109+ : of function results (using the %ann:cache annotation) and the function
110+ : is annotated as sequential or nondeterministic.
111+ :
112+:)
113+declare variable $zwarn:ZWST0006 as xs:QName := fn:QName($zwarn:NS, "zwarn:ZWST0006");
114\ No newline at end of file
115
116=== modified file 'src/annotations/annotations.cpp'
117--- src/annotations/annotations.cpp 2011-11-07 06:32:00 +0000
118+++ src/annotations/annotations.cpp 2011-11-19 17:32:30 +0000
119@@ -100,6 +100,9 @@
120
121 ZANN(streamable, streamable);
122
123+ ZANN(cache, cache);
124+ ZANN(no-cache, nocache);
125+
126 //
127 // Zorba annotations - xqddf
128 //
129@@ -168,6 +171,10 @@
130 ZANN(zann_nonsequential));
131
132 theRuleSet.push_back(
133+ ZANN(zann_cache) |
134+ ZANN(zann_nocache));
135+
136+ theRuleSet.push_back(
137 ZANN(fn_private) |
138 ZANN(fn_public));
139
140@@ -433,6 +440,5 @@
141 }
142
143
144-
145 } /* namespace zorba */
146 /* vim:set et sw=2 ts=2: */
147
148=== modified file 'src/annotations/annotations.h'
149--- src/annotations/annotations.h 2011-11-01 13:47:10 +0000
150+++ src/annotations/annotations.h 2011-11-19 17:32:30 +0000
151@@ -55,6 +55,8 @@
152 zann_nonassignable,
153 zann_sequential,
154 zann_nonsequential,
155+ zann_cache,
156+ zann_nocache,
157 zann_variadic,
158 zann_streamable,
159 zann_unique,
160
161=== modified file 'src/compiler/codegen/plan_visitor.cpp'
162--- src/compiler/codegen/plan_visitor.cpp 2011-10-24 02:42:07 +0000
163+++ src/compiler/codegen/plan_visitor.cpp 2011-11-19 17:32:30 +0000
164@@ -98,6 +98,7 @@
165 #endif
166
167 #include "functions/function.h"
168+#include "functions/udf.h"
169 #include "functions/library.h"
170
171 #include "types/typeops.h"
172@@ -2283,11 +2284,14 @@
173 dynamic_cast<EnclosedIterator*>(iter.getp())->setInUpdateExpr();
174 }
175 }
176-#if 0
177 else if (func->isUdf())
178 {
179+ // need to computeResultCaching here for iterprint to work
180 const user_function* udf = static_cast<const user_function*>(func);
181+ udf->computeResultCaching(theCCB->theXQueryDiagnostics);
182+ }
183
184+#if 0
185 if (udf->isExiting())
186 {
187 TypeManager* tm = v.get_type_manager();
188
189=== modified file 'src/context/static_context_consts.h'
190--- src/context/static_context_consts.h 2011-07-21 23:02:27 +0000
191+++ src/context/static_context_consts.h 2011-11-19 17:32:30 +0000
192@@ -132,6 +132,8 @@
193 zann_nonsequential,
194 zann_variadic,
195 zann_streamable,
196+ zann_cache,
197+ zann_no_cache,
198 zann_unique,
199 zann_nonunique,
200 zann_value_equality,
201
202=== modified file 'src/diagnostics/diagnostic_en.xml'
203--- src/diagnostics/diagnostic_en.xml 2011-11-15 08:23:20 +0000
204+++ src/diagnostics/diagnostic_en.xml 2011-11-19 17:32:30 +0000
205@@ -1997,6 +1997,10 @@
206 <value>"$1": index range-value probe has search keys with incompatible types</value>
207 </diagnostic>
208
209+ <diagnostic code="ZDDY0035" name="INDEX_GENERAL_INSERT">
210+ <value>"$1": index inserting more than one key not allowed for general index</value>
211+ </diagnostic>
212+
213 <diagnostic code="ZDDY0031" name="IC_NOT_DECLARED">
214 <value>"$1": integrity constraint is not declared</value>
215 </diagnostic>
216@@ -2307,6 +2311,37 @@
217 <value>Sequential FLWOR expr may not have the semantics you expect</value>
218 </diagnostic>
219
220+ <diagnostic code="ZWST0005" name="CACHING_NOT_POSSIBLE">
221+ <comment>
222+ This warning is raised if the user explicitly enables caching
223+ of function results (using the %ann:cache annotation) but the function
224+ is updating or its parameter and return types are not subtypes of
225+ xs:anyAtomicType.
226+ </comment>
227+ <value>"$1": function caching not possible; $2</value>
228+ <entry key="RETURN_TYPE">
229+ <value>return type ($3) is not subtype of xs:anyAtomicType</value>
230+ </entry>
231+ <entry key="PARAM_TYPE">
232+ <value>type of parameter $3 is $4 which is not a subtype of xs:anyAtomicType</value>
233+ </entry>
234+ <entry key="UPDATING">
235+ <value>function is updating</value>
236+ </entry>
237+ <entry key="VARIADIC">
238+ <value>function is variadic</value>
239+ </entry>
240+ </diagnostic>
241+
242+ <diagnostic code="ZWST0006" name="CACHING_MIGHT_NOT_BE_INTENDED">
243+ <comment>
244+ This warning is raised if the user explicitly enables caching
245+ of function results (using the %ann:cache annotation) and the function
246+ is annotated as sequential or nondeterministic.
247+ </comment>
248+ <value>"$1": function caching might not give the intended result because the function is declared as $2</value>
249+ </diagnostic>
250+
251 </namespace>
252
253 <!--////////// Subvalues /////////////////////////////////////////////////-->
254
255=== modified file 'src/diagnostics/pregenerated/diagnostic_list.cpp'
256--- src/diagnostics/pregenerated/diagnostic_list.cpp 2011-11-15 08:23:20 +0000
257+++ src/diagnostics/pregenerated/diagnostic_list.cpp 2011-11-19 17:32:30 +0000
258@@ -879,6 +879,9 @@
259 ZorbaErrorCode ZDDY0034_INDEX_RANGE_VALUE_PROBE_BAD_KEY_TYPES( "ZDDY0034" );
260
261
262+ZorbaErrorCode ZDDY0035_INDEX_GENERAL_INSERT( "ZDDY0035" );
263+
264+
265 ZorbaErrorCode ZDDY0031_IC_NOT_DECLARED( "ZDDY0031" );
266
267
268@@ -1104,6 +1107,12 @@
269 ZorbaWarningCode ZWST0004_AMBIGUOUS_SEQUENTIAL_FLWOR( "ZWST0004" );
270
271
272+ZorbaWarningCode ZWST0005_CACHING_NOT_POSSIBLE( "ZWST0005" );
273+
274+
275+ZorbaWarningCode ZWST0006_CACHING_MIGHT_NOT_BE_INTENDED( "ZWST0006" );
276+
277+
278 } // namespace zwarn
279
280 } // namespace zorba
281
282=== modified file 'src/diagnostics/pregenerated/dict_en.cpp'
283--- src/diagnostics/pregenerated/dict_en.cpp 2011-11-15 08:23:20 +0000
284+++ src/diagnostics/pregenerated/dict_en.cpp 2011-11-19 17:32:30 +0000
285@@ -297,6 +297,7 @@
286 { "ZDDY0032", "\"$1\": integrity constraint is not activated" },
287 { "ZDDY0033", "\"$1\": integrity constraint not met for collection \"$2\"" },
288 { "ZDDY0034", "\"$1\": index range-value probe has search keys with incompatible types" },
289+ { "ZDDY0035", "\"$1\": index inserting more than one key not allowed for general index" },
290 { "ZDST0001", "\"$1\": collection already declared" },
291 { "ZDST0002", "\"$1\": collection already imported into module \"$2\"" },
292 { "ZDST0003", "\"$1\": collection declaration not allowed in main module" },
293@@ -359,6 +360,8 @@
294 { "ZWST0002", "\"$1\": unknown or unsupported annotation" },
295 { "ZWST0003", "\"$1\": function declared sequential, but has non-sequential body" },
296 { "ZWST0004", "Sequential FLWOR expr may not have the semantics you expect" },
297+ { "ZWST0005", "\"$1\": function caching not possible; $2" },
298+ { "ZWST0006", "\"$1\": function caching might not give the intended result because the function is declared as $2" },
299 { "ZXQD0001", "\"$1\": prefix not declared when calling function \"$2\" from $3" },
300 { "ZXQD0002", "\"$1\": $2" },
301 { "ZXQD0003", "inconsistent options to the parse-xml-fragment() function: $1" },
302@@ -663,6 +666,10 @@
303 { "~XUST0002_UDF_2", "\"$2\": function declared updating but body is not updating or vacuous" },
304 { "~ZDST0060_unknown_localname", "unknown localname ($3)" },
305 { "~ZDST0060_unknown_namespace", "unknown namespace ($3)" },
306+ { "~ZWST0005_PARAM_TYPE", "type of parameter $3 is $4 which is not a subtype of xs:anyAtomicType" },
307+ { "~ZWST0005_RETURN_TYPE", "return type ($3) is not subtype of xs:anyAtomicType" },
308+ { "~ZWST0005_UPDATING", "function is updating" },
309+ { "~ZWST0005_VARIADIC", "function is variadic" },
310 { "~ZXQD0004_NON_NEGATIVE", "given value must be non-negative ($2)" },
311 { "~ZXQD0004_NOT_WITHIN_RANGE", "not within allowed range ($2)" },
312 { "~ZXQP0004_TypeOps_is_in_scope_ForFunctionItemTypes", "TypeOps::is_in_scope() for function-item types" },
313
314=== modified file 'src/functions/udf.cpp'
315--- src/functions/udf.cpp 2011-10-18 17:37:41 +0000
316+++ src/functions/udf.cpp 2011-11-19 17:32:30 +0000
317@@ -26,10 +26,15 @@
318 #include "compiler/rewriter/framework/rewriter.h"
319
320 #include "functions/udf.h"
321+#include "annotations/annotations.h"
322 #include "functions/function_impl.h"
323
324+#include "diagnostics/xquery_warning.h"
325+
326 #include "types/typeops.h"
327
328+#include "store/api/index.h" // needed for destruction of the cache
329+
330
331 namespace zorba
332 {
333@@ -54,7 +59,10 @@
334 theIsExiting(false),
335 theIsLeaf(true),
336 theIsOptimized(false),
337- thePlanStateSize(0)
338+ thePlanStateSize(0),
339+ theCache(0),
340+ theCacheResults(false),
341+ theCacheComputed(false)
342 {
343 setFlag(FunctionConsts::isUDF);
344 resetFlag(FunctionConsts::isBuiltin);
345@@ -318,7 +326,7 @@
346 /*******************************************************************************
347
348 ********************************************************************************/
349- PlanIter_t user_function::getPlan(CompilerCB* ccb, uint32_t& planStateSize)
350+PlanIter_t user_function::getPlan(CompilerCB* ccb, uint32_t& planStateSize)
351 {
352 if (thePlan == NULL)
353 {
354@@ -372,9 +380,207 @@
355 /*******************************************************************************
356
357 ********************************************************************************/
358-CODEGEN_DEF(user_function)
359-{
360- return new UDFunctionCallIterator(aSctx, aLoc, aArgs, this);
361+store::Index* user_function::getCache() const
362+{
363+ return theCache;
364+}
365+
366+
367+/*******************************************************************************
368+
369+********************************************************************************/
370+void user_function::setCache(store::Index* aCache)
371+{
372+ theCache = aCache;
373+}
374+
375+
376+/*******************************************************************************
377+********************************************************************************/
378+bool user_function::cacheResults() const
379+{
380+ return theCacheResults;
381+}
382+
383+
384+/*******************************************************************************
385+ only cache recursive (non-sequential, non-updating, deterministic)
386+ functions with singleton atomic input and output
387+********************************************************************************/
388+void user_function::computeResultCaching(XQueryDiagnostics* diag) const
389+{
390+ if (theCacheComputed)
391+ {
392+ return;
393+ }
394+
395+ struct OnExit {
396+ private:
397+ bool& theResult;
398+ bool& theCacheComputed;
399+
400+ public:
401+ OnExit(bool& aResult, bool& aCacheComputed)
402+ : theResult(aResult),
403+ theCacheComputed(aCacheComputed) {}
404+
405+ void cache() { theResult = true; }
406+
407+ ~OnExit()
408+ {
409+ theCacheComputed = true;
410+ }
411+ };
412+
413+ // will be destroyed when the function is exited
414+ // set caching to true if cache() is called
415+ OnExit lExit(theCacheResults, theCacheComputed);
416+
417+ // check necessary conditions
418+ // %ann:cache or not %ann:no-cache
419+ if (theAnnotationList && theAnnotationList->contains(AnnotationInternal::zann_nocache))
420+ {
421+ return;
422+ }
423+
424+ // was the %ann:cache annotation given explicitly by the user
425+ bool lExplicitCacheRequest = theAnnotationList
426+ ?theAnnotationList->contains(AnnotationInternal::zann_cache)
427+ :false;
428+
429+ if (isVariadic())
430+ {
431+ if (lExplicitCacheRequest)
432+ {
433+ diag->add_warning(
434+ NEW_XQUERY_WARNING(
435+ zwarn::ZWST0005_CACHING_NOT_POSSIBLE,
436+ WARN_PARAMS(
437+ getName()->getStringValue(),
438+ ZED( ZWST0005_VARIADIC )
439+ ),
440+ WARN_LOC(theLoc)
441+ )
442+ );
443+ }
444+ return;
445+ }
446+
447+ // parameter and return types are subtype of xs:anyAtomicType?
448+ const xqtref_t& lRes = theSignature.returnType();
449+ TypeManager* tm = lRes->get_manager();
450+
451+ if (!TypeOps::is_subtype(tm,
452+ *lRes,
453+ *GENV_TYPESYSTEM.ANY_ATOMIC_TYPE_ONE,
454+ theLoc))
455+ {
456+ if (lExplicitCacheRequest)
457+ {
458+ diag->add_warning(
459+ NEW_XQUERY_WARNING(
460+ zwarn::ZWST0005_CACHING_NOT_POSSIBLE,
461+ WARN_PARAMS(
462+ getName()->getStringValue(),
463+ ZED( ZWST0005_RETURN_TYPE ),
464+ lRes->toString()
465+ ),
466+ WARN_LOC(theLoc)
467+ )
468+ );
469+ }
470+ return;
471+ }
472+
473+ size_t lArity = theSignature.paramCount();
474+ for (size_t i = 0; i < lArity; ++i)
475+ {
476+ const xqtref_t& lArg = theSignature[i];
477+ if (!TypeOps::is_subtype(tm,
478+ *lArg,
479+ *GENV_TYPESYSTEM.ANY_ATOMIC_TYPE_ONE,
480+ theLoc))
481+ {
482+ if (lExplicitCacheRequest)
483+ {
484+ diag->add_warning(
485+ NEW_XQUERY_WARNING(
486+ zwarn::ZWST0005_CACHING_NOT_POSSIBLE,
487+ WARN_PARAMS(
488+ getName()->getStringValue(),
489+ ZED( ZWST0005_PARAM_TYPE ),
490+ i+1,
491+ lArg->toString()
492+ ),
493+ WARN_LOC(theLoc)
494+ )
495+ );
496+ }
497+ return;
498+ }
499+ }
500+
501+ // function updating?
502+ if (isUpdating())
503+ {
504+ if (lExplicitCacheRequest)
505+ {
506+ diag->add_warning(
507+ NEW_XQUERY_WARNING(
508+ zwarn::ZWST0005_CACHING_NOT_POSSIBLE,
509+ WARN_PARAMS(
510+ getName()->getStringValue(),
511+ ZED( ZWST0005_UPDATING )
512+ ),
513+ WARN_LOC(theLoc)
514+ )
515+ );
516+ }
517+ return;
518+ }
519+
520+ if (lExplicitCacheRequest)
521+ {
522+ if (isSequential() || !isDeterministic())
523+ {
524+ if (lExplicitCacheRequest)
525+ {
526+ diag->add_warning(
527+ NEW_XQUERY_WARNING(
528+ zwarn::ZWST0006_CACHING_MIGHT_NOT_BE_INTENDED,
529+ WARN_PARAMS(
530+ getName()->getStringValue(),
531+ (isSequential()?"sequential":"non-deterministic")
532+ ),
533+ WARN_LOC(theLoc)
534+ )
535+ );
536+ }
537+ }
538+ lExit.cache();
539+ return;
540+ }
541+
542+ // optimization is prerequisite before invoking isRecursive
543+ if (!lExplicitCacheRequest && isOptimized() && !isRecursive())
544+ {
545+ return;
546+ }
547+
548+ lExit.cache();
549+}
550+
551+/*******************************************************************************
552+
553+********************************************************************************/
554+PlanIter_t user_function::codegen(
555+ CompilerCB* cb,
556+ static_context* sctx,
557+ const QueryLoc& loc,
558+ std::vector<PlanIter_t>& argv,
559+ AnnotationHolder& ann) const
560+{
561+ return new UDFunctionCallIterator(sctx, loc, argv, this);
562 }
563
564
565
566=== modified file 'src/functions/udf.h'
567--- src/functions/udf.h 2011-10-18 17:37:41 +0000
568+++ src/functions/udf.h 2011-11-19 17:32:30 +0000
569@@ -25,6 +25,12 @@
570 namespace zorba
571 {
572
573+ namespace store
574+ {
575+ class Index;
576+ typedef rchandle<Index> Index_t;
577+ }
578+
579
580 /*******************************************************************************
581 A udf with params $x1, $x2, ..., $xn and a body_expr is translated into a
582@@ -102,6 +108,10 @@
583 uint32_t thePlanStateSize;
584 std::vector<ArgVarRefs> theArgVarsRefs;
585
586+ mutable store::Index_t theCache;
587+ mutable bool theCacheResults;
588+ mutable bool theCacheComputed;
589+
590 public:
591 SERIALIZABLE_CLASS(user_function)
592 user_function(::zorba::serialization::Archiver& ar);
593@@ -160,12 +170,21 @@
594
595 const std::vector<ArgVarRefs>& getArgVarsRefs() const;
596
597+ store::Index* getCache() const;
598+
599+ void setCache(store::Index* aCache);
600+
601+ bool cacheResults() const;
602+
603 PlanIter_t codegen(
604 CompilerCB* cb,
605 static_context* sctx,
606 const QueryLoc& loc,
607 std::vector<PlanIter_t>& argv,
608 AnnotationHolder& ann) const;
609+
610+ void
611+ computeResultCaching(XQueryDiagnostics*) const;
612 };
613
614
615
616=== modified file 'src/runtime/core/fncall_iterator.cpp'
617--- src/runtime/core/fncall_iterator.cpp 2011-10-30 00:18:34 +0000
618+++ src/runtime/core/fncall_iterator.cpp 2011-11-19 17:32:30 +0000
619@@ -48,6 +48,11 @@
620
621 #include "util/string_util.h"
622
623+#include "store/api/index.h"
624+#include "store/api/store.h"
625+#include "store/api/iterator_factory.h"
626+#include "store/api/temp_seq.h"
627+
628 #ifdef ZORBA_WITH_DEBUGGER
629 #include "debugger/debugger_commons.h"
630
631@@ -93,7 +98,8 @@
632 thePlanState(NULL),
633 thePlanStateSize(0),
634 theLocalDCtx(NULL),
635- thePlanOpen(false)
636+ thePlanOpen(false),
637+ theCache(0)
638 {
639 }
640
641@@ -153,6 +159,7 @@
642 {
643 thePlan->reset(*thePlanState);
644 }
645+ theCacheHits.clear();
646 }
647
648
649@@ -198,6 +205,103 @@
650 /*******************************************************************************
651
652 ********************************************************************************/
653+bool UDFunctionCallIterator::isCached() const
654+{
655+ return theUDF->cacheResults();
656+}
657+
658+/*******************************************************************************
659+
660+********************************************************************************/
661+void UDFunctionCallIterator::createCache(
662+ PlanState& planState,
663+ UDFunctionCallIteratorState* state)
664+{
665+ store::Index_t lIndex = theUDF->getCache();
666+ if (!lIndex && theUDF->cacheResults())
667+ {
668+ const signature& sig = theUDF->getSignature();
669+
670+ csize numArgs = theChildren.size();
671+
672+ store::IndexSpecification lSpec;
673+ lSpec.theNumKeyColumns = numArgs;
674+ lSpec.theKeyTypes.resize(numArgs);
675+ lSpec.theCollations.resize(numArgs);
676+ lSpec.theIsTemp = true;
677+ lSpec.theIsUnique = true;
678+ for (csize i = 0; i < numArgs; ++i)
679+ {
680+ lSpec.theKeyTypes[i] = sig[i]->getBaseBuiltinType()->get_qname().getp();
681+ }
682+ lIndex = GENV_STORE.createIndex(theUDF->getName(), lSpec, 0);
683+ theUDF->setCache(lIndex.getp()); // cache the cache in the function itself
684+ state->theCacheHits.reserve(theChildren.size());
685+ }
686+ state->theCache = lIndex.getp();
687+}
688+
689+
690+/*******************************************************************************
691+
692+********************************************************************************/
693+bool UDFunctionCallIterator::probeCache(
694+ PlanState& planState,
695+ UDFunctionCallIteratorState* state,
696+ store::Item_t& result,
697+ std::vector<store::Item_t>& aKey) const
698+{
699+ if (!state->theCache)
700+ return false;
701+
702+ store::IndexCondition_t lCond =
703+ state->theCache->createCondition(store::IndexCondition::POINT_VALUE);
704+
705+ std::vector<store::Iterator_t>::iterator lIter
706+ = state->theArgWrappers.begin();
707+ for (; lIter != state->theArgWrappers.end(); ++lIter)
708+ {
709+ store::Iterator_t& argWrapper = (*lIter);
710+ store::Item_t lArg;
711+ if (argWrapper) // might be 0 if argument is not used
712+ {
713+ argWrapper->next(lArg); // guaranteed to have exactly one result
714+ }
715+ aKey.push_back(lArg);
716+ lCond->pushItem(lArg);
717+ }
718+ store::IndexProbeIterator_t lCacheHit =
719+ GENV_STORE.getIteratorFactory()->createIndexProbeIterator(state->theCache);
720+ lCacheHit->init(lCond);
721+ lCacheHit->open();
722+ return lCacheHit->next(result);
723+}
724+
725+
726+/*******************************************************************************
727+
728+********************************************************************************/
729+void UDFunctionCallIterator::insertCacheEntry(
730+ UDFunctionCallIteratorState* state,
731+ std::vector<store::Item_t>& aKey,
732+ store::Item_t& aValue) const
733+{
734+ if (state->theCache)
735+ {
736+ std::auto_ptr<store::IndexKey> k(new store::IndexKey());
737+ store::IndexKey* k2 = k.get();
738+ k->theItems = aKey;
739+ store::Item_t lTmp = aValue; // insert will eventually transfer the Item_t
740+ if (!state->theCache->insert(k2, lTmp))
741+ {
742+ k.release();
743+ }
744+ }
745+}
746+
747+/*******************************************************************************
748+
749+********************************************************************************/
750 void UDFunctionCallIterator::openImpl(PlanState& planState, uint32_t& offset)
751 {
752 UDFunctionCallIteratorState* state;
753@@ -229,6 +333,11 @@
754 // the plan state (but not the state block) and dynamic context.
755 state->open(planState, theUDF);
756
757+ // if the results of the function should be cached (prereq: atomic in and out)
758+ // this functions stores an index in the dynamic context that contains
759+ // the cached results. The name of the index is the name of the function.
760+ createCache(planState, state);
761+
762 // Create a wrapper over each subplan that computes an argument expr, if the
763 // associated param is actually used anywhere in the function body.
764 csize numArgs = theChildren.size();
765@@ -301,6 +410,9 @@
766 {
767 try
768 {
769+ std::vector<store::Item_t> lKey;
770+ bool lCacheHit;
771+
772 UDFunctionCallIteratorState* state;
773 DEFAULT_STACK_INIT(UDFunctionCallIteratorState, state, planState);
774
775@@ -314,19 +426,29 @@
776 state->thePlanOpen = true;
777 }
778
779- // Bind the args.
780+ // check if there is a cache and the result is already in the cache
781+ lCacheHit = probeCache(planState, state, result, lKey);
782+
783+ // if not in the cache, we bind the arguments to the function
784+ if (!lCacheHit)
785 {
786 const std::vector<ArgVarRefs>& argsRefs = theUDF->getArgVarsRefs();
787- std::vector<ArgVarRefs>::const_iterator argsRefsIte = argsRefs.begin();
788- std::vector<ArgVarRefs>::const_iterator argsRefsEnd = argsRefs.end();
789-
790- std::vector<store::Iterator_t>::iterator argWrapsIte =
791- state->theArgWrappers.begin();
792-
793- for (; argsRefsIte != argsRefsEnd; ++argsRefsIte, ++argWrapsIte)
794+ const std::vector<store::Iterator_t>& argWraps = state->theArgWrappers;
795+ for (size_t i = 0; i < argsRefs.size(); ++i)
796 {
797- store::Iterator_t& argWrapper = (*argWrapsIte);
798- const ArgVarRefs& argVarRefs = (*argsRefsIte);
799+ const ArgVarRefs& argVarRefs = argsRefs[i];
800+ store::Iterator_t argWrapper;
801+ if (state->theCache)
802+ {
803+ std::vector<store::Item_t> lParam(1, lKey[i]);
804+ state->theCacheHits.push_back(GENV_STORE.createTempSeq(lParam));
805+ argWrapper = state->theCacheHits.back()->getIterator();
806+ argWrapper->open();
807+ }
808+ else
809+ {
810+ argWrapper = argWraps[i];
811+ }
812 ArgVarRefs::const_iterator argVarRefsIte = argVarRefs.begin();
813 ArgVarRefs::const_iterator argVarRefsEnd = argVarRefs.end();
814
815@@ -343,25 +465,34 @@
816 }
817 }
818
819-#ifdef ZORBA_WITH_DEBUGGER
820- DEBUGGER_PUSH_FRAME;
821-#endif
822-
823- while (consumeNext(result, state->thePlan, *state->thePlanState))
824- {
825-#ifdef ZORBA_WITH_DEBUGGER
826- DEBUGGER_POP_FRAME;
827-#endif
828- STACK_PUSH(true, state);
829-
830-#ifdef ZORBA_WITH_DEBUGGER
831- DEBUGGER_PUSH_FRAME;
832-#endif
833- }
834-
835-#ifdef ZORBA_WITH_DEBUGGER
836- DEBUGGER_POP_FRAME;
837-#endif
838+ if (lCacheHit)
839+ {
840+ STACK_PUSH(true, state);
841+ }
842+ else
843+ {
844+#ifdef ZORBA_WITH_DEBUGGER
845+ DEBUGGER_PUSH_FRAME;
846+#endif
847+ while (consumeNext(result, state->thePlan, *state->thePlanState))
848+ {
849+#ifdef ZORBA_WITH_DEBUGGER
850+ DEBUGGER_POP_FRAME;
851+#endif
852+
853+ insertCacheEntry(state, lKey, result);
854+ STACK_PUSH(true, state);
855+
856+#ifdef ZORBA_WITH_DEBUGGER
857+ DEBUGGER_PUSH_FRAME;
858+#endif
859+ }
860+
861+#ifdef ZORBA_WITH_DEBUGGER
862+ DEBUGGER_POP_FRAME;
863+#endif
864+
865+ }
866
867 STACK_END(state);
868
869
870=== modified file 'src/runtime/core/fncall_iterator.h'
871--- src/runtime/core/fncall_iterator.h 2011-10-30 00:18:34 +0000
872+++ src/runtime/core/fncall_iterator.h 2011-11-19 17:32:30 +0000
873@@ -69,6 +69,19 @@
874 the body. So, it is never the case that the arg expr will have more than one
875 consumers, and as a result we can bind all those V references to the same arg
876 wrapper.
877+
878+ theCache:
879+ ---------
880+ Is an Index which is set in the state if caching for the invoked function
881+ should be done. The cache is owned by the UDF itself and shared across
882+ all function invocations.
883+
884+ theCacheHits:
885+ -------------
886+ If caching is used, this vector contains the results of all arguments
887+ of the function evaluation. It's used to bind the variables if the
888+ cache didn't give a result in order to avoid duplicate evaluation of
889+ the arguments.
890 ********************************************************************************/
891 class UDFunctionCallIteratorState : public PlanIteratorState
892 {
893@@ -79,6 +92,8 @@
894 dynamic_context * theLocalDCtx;
895 bool thePlanOpen;
896 std::vector<store::Iterator_t> theArgWrappers;
897+ store::Index * theCache;
898+ std::vector<store::TempSeq_t> theCacheHits;
899
900 UDFunctionCallIteratorState();
901
902@@ -130,6 +145,8 @@
903
904 void setDynamic() { theIsDynamic = true; }
905
906+ bool isCached() const;
907+
908 void accept(PlanIterVisitor& v) const;
909
910 void openImpl(PlanState& planState, uint32_t& offset);
911@@ -139,6 +156,22 @@
912 void closeImpl(PlanState& planState);
913
914 bool nextImpl(store::Item_t& result, PlanState& planState) const;
915+
916+protected:
917+ void createCache(
918+ PlanState& planState,
919+ UDFunctionCallIteratorState* state);
920+
921+ bool probeCache(
922+ PlanState& planState,
923+ UDFunctionCallIteratorState* state,
924+ store::Item_t& result,
925+ std::vector<store::Item_t>& aKey) const;
926+
927+ void insertCacheEntry(
928+ UDFunctionCallIteratorState* state,
929+ std::vector<store::Item_t>& aKey,
930+ store::Item_t& aValue) const;
931 };
932
933
934
935=== modified file 'src/runtime/visitors/printer_visitor_impl.cpp'
936--- src/runtime/visitors/printer_visitor_impl.cpp 2011-10-11 01:05:23 +0000
937+++ src/runtime/visitors/printer_visitor_impl.cpp 2011-11-19 17:32:30 +0000
938@@ -1209,7 +1209,23 @@
939
940 #undef TYPED_VAL_CMP
941
942- PRINTER_VISITOR_DEFINITION (UDFunctionCallIterator)
943+ void PrinterVisitor::beginVisit ( const UDFunctionCallIterator& a )
944+ {
945+ thePrinter.startBeginVisit("UDFunctionCallIterator", ++theId);
946+ printCommons( &a, theId );
947+ if (a.isCached())
948+ {
949+ thePrinter.addAttribute("cached", "true");
950+ }
951+ thePrinter.endBeginVisit( theId);
952+ }
953+
954+ void PrinterVisitor::endVisit ( const UDFunctionCallIterator& )
955+ {
956+ thePrinter.startEndVisit();
957+ thePrinter.endEndVisit();
958+ }
959+
960 PRINTER_VISITOR_DEFINITION (ExtFunctionCallIterator)
961 PRINTER_VISITOR_DEFINITION (FnBooleanIterator)
962 PRINTER_VISITOR_DEFINITION (OrIterator)
963
964=== modified file 'src/store/api/index.h'
965--- src/store/api/index.h 2011-10-11 12:11:48 +0000
966+++ src/store/api/index.h 2011-11-19 17:32:30 +0000
967@@ -415,6 +415,18 @@
968 * Returns all keys stored in this index
969 */
970 virtual KeyIterator_t keys() const = 0;
971+
972+ /**
973+ * Insert the given item in the value set of the given key. If the key is not
974+ * in the index already, then the key itself is inserted as well. Return true
975+ * if the key was already in the index, false otherwise
976+ * The index wil take the ownership of the key if it was not already in the
977+ * index.
978+ *
979+ * @error ZDDY0035 if a key with more than one item is inserted into
980+ * a general index
981+ */
982+ virtual bool insert(store::IndexKey*& key, store::Item_t& item) = 0;
983 };
984
985
986
987=== modified file 'src/store/naive/simple_index_general.cpp'
988--- src/store/naive/simple_index_general.cpp 2011-11-15 18:48:54 +0000
989+++ src/store/naive/simple_index_general.cpp 2011-11-19 17:32:30 +0000
990@@ -236,6 +236,20 @@
991 /******************************************************************************
992
993 *******************************************************************************/
994+bool GeneralIndex::insert(store::IndexKey*& key, store::Item_t& value)
995+{
996+ if (key->size() != 1)
997+ {
998+ RAISE_ERROR_NO_LOC(zerr::ZDDY0035_INDEX_GENERAL_INSERT,
999+ ERROR_PARAMS(getName()->getStringValue()));
1000+ }
1001+ return insert((*key)[0], value);
1002+}
1003+
1004+
1005+/******************************************************************************
1006+
1007+*******************************************************************************/
1008 bool GeneralIndex::insert(store::Item_t& key, store::Item_t& node)
1009 {
1010 bool lossy = false;
1011
1012=== modified file 'src/store/naive/simple_index_general.h'
1013--- src/store/naive/simple_index_general.h 2011-11-15 18:48:54 +0000
1014+++ src/store/naive/simple_index_general.h 2011-11-19 17:32:30 +0000
1015@@ -174,6 +174,8 @@
1016
1017 bool insert(store::Item_t& key, store::Item_t& node);
1018
1019+ bool insert(store::IndexKey*& key, store::Item_t& value);
1020+
1021 virtual bool remove(const store::Item_t& key, store::Item_t& item, bool all) = 0;
1022 };
1023
1024
1025=== modified file 'src/store/naive/simple_store.cpp'
1026--- src/store/naive/simple_store.cpp 2011-11-02 17:19:09 +0000
1027+++ src/store/naive/simple_store.cpp 2011-11-19 17:32:30 +0000
1028@@ -555,6 +555,9 @@
1029 store::Iterator* aSourceIter,
1030 ulong aNumColumns)
1031 {
1032+ if (!aSourceIter)
1033+ return;
1034+
1035 store::Item_t domainItem;
1036 store::IndexKey* key = NULL;
1037
1038
1039=== added file 'test/rbkt/ExpCompilerResults/IterPlan/zorba/udf/udf-fib-rec.iter'
1040--- test/rbkt/ExpCompilerResults/IterPlan/zorba/udf/udf-fib-rec.iter 1970-01-01 00:00:00 +0000
1041+++ test/rbkt/ExpCompilerResults/IterPlan/zorba/udf/udf-fib-rec.iter 2011-11-19 17:32:30 +0000
1042@@ -0,0 +1,42 @@
1043+Iterator tree for main query:
1044+<UDFunctionCallIterator cached="true">
1045+ <SingletonIterator value="xs:integer(100)"/>
1046+</UDFunctionCallIterator>
1047+
1048+Iterator tree for local:fib:
1049+<flwor::FLWORIterator>
1050+ <ForVariable name="n">
1051+ <LetVarIterator varname="n"/>
1052+ </ForVariable>
1053+ <ReturnClause>
1054+ <IfThenElseIterator>
1055+ <TypedValueCompareIterator_INTEGER>
1056+ <ForVarIterator varname="n"/>
1057+ <SingletonIterator value="xs:integer(0)"/>
1058+ </TypedValueCompareIterator_INTEGER>
1059+ <SingletonIterator value="xs:integer(0)"/>
1060+ <IfThenElseIterator>
1061+ <TypedValueCompareIterator_INTEGER>
1062+ <ForVarIterator varname="n"/>
1063+ <SingletonIterator value="xs:integer(1)"/>
1064+ </TypedValueCompareIterator_INTEGER>
1065+ <SingletonIterator value="xs:integer(1)"/>
1066+ <SpecificNumArithIterator_AddOperation_INTEGER>
1067+ <UDFunctionCallIterator cached="true">
1068+ <SpecificNumArithIterator_SubtractOperation_INTEGER>
1069+ <ForVarIterator varname="n"/>
1070+ <SingletonIterator value="xs:integer(1)"/>
1071+ </SpecificNumArithIterator_SubtractOperation_INTEGER>
1072+ </UDFunctionCallIterator>
1073+ <UDFunctionCallIterator cached="true">
1074+ <SpecificNumArithIterator_SubtractOperation_INTEGER>
1075+ <ForVarIterator varname="n"/>
1076+ <SingletonIterator value="xs:integer(2)"/>
1077+ </SpecificNumArithIterator_SubtractOperation_INTEGER>
1078+ </UDFunctionCallIterator>
1079+ </SpecificNumArithIterator_AddOperation_INTEGER>
1080+ </IfThenElseIterator>
1081+ </IfThenElseIterator>
1082+ </ReturnClause>
1083+</flwor::FLWORIterator>
1084+
1085
1086=== added file 'test/rbkt/ExpQueryResults/zorba/udf/udf-fib-rec.xml.res'
1087--- test/rbkt/ExpQueryResults/zorba/udf/udf-fib-rec.xml.res 1970-01-01 00:00:00 +0000
1088+++ test/rbkt/ExpQueryResults/zorba/udf/udf-fib-rec.xml.res 2011-11-19 17:32:30 +0000
1089@@ -0,0 +1,1 @@
1090+3736710778780434371
1091
1092=== added file 'test/rbkt/Queries/zorba/udf/udf-fib-rec.xq'
1093--- test/rbkt/Queries/zorba/udf/udf-fib-rec.xq 1970-01-01 00:00:00 +0000
1094+++ test/rbkt/Queries/zorba/udf/udf-fib-rec.xq 2011-11-19 17:32:30 +0000
1095@@ -0,0 +1,8 @@
1096+declare function local:fib($n as xs:integer) as xs:integer
1097+{
1098+ if ($n eq 0) then 0
1099+ else if ($n eq 1) then 1
1100+ else local:fib($n - 1) + local:fib($n - 2)
1101+};
1102+
1103+local:fib(100)

Subscribers

People subscribed via source and target branches