1 /*
2 * Copyright 2006-2008 The FLWOR Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "stdafx.h"
17
18 #include <algorithm>
19
20 #include "types/typeops.h"
21
22 #include "functions/function.h"
23 #include "functions/library.h"
24
25 #include "compiler/xqddf/value_index.h"
26 #include "compiler/api/compilercb.h"
27 #include "compiler/expression/flwor_expr.h"
28 #include "compiler/expression/fo_expr.h"
29 #include "compiler/expression/script_exprs.h"
30 #include "compiler/expression/expr.h"
31 #include "compiler/expression/expr_iter.h"
32 #include "compiler/codegen/plan_visitor.h"
33
34 #include "runtime/base/plan_iterator.h"
35 #include "runtime/indexing/doc_indexer.h"
36
37 #include "store/api/item_factory.h"
38
39 #include "diagnostics/util_macros.h"
40
41
42 namespace zorba
43 {
44
45 SERIALIZABLE_CLASS_VERSIONS(OrderModifier)
46
SERIALIZABLE_CLASS_VERSIONS(IndexDecl)47 SERIALIZABLE_CLASS_VERSIONS(IndexDecl)
48
49
50 /*******************************************************************************
51
52 ********************************************************************************/
53 IndexDecl::IndexDecl(
54 static_context* sctx,
55 CompilerCB* ccb,
56 const QueryLoc& loc,
57 const store::Item_t& name)
58 :
59 theCCB(ccb),
60 theLocation(loc),
61 theSctx(sctx),
62 theName(name),
63 theIsGeneral(false),
64 theIsUnique(false),
65 theIsTemp(false),
66 theMaintenanceMode(MANUAL),
67 theContainerKind(HASH),
68 theDomainClause(NULL),
69 theDomainExpr(NULL),
70 theDomainVar(NULL),
71 theDomainPosVar(NULL),
72 theBuildExpr(NULL),
73 theDocIndexerExpr(NULL)
74 {
75 }
76
77
78 /*******************************************************************************
79
80 ********************************************************************************/
IndexDecl(::zorba::serialization::Archiver & ar)81 IndexDecl::IndexDecl(::zorba::serialization::Archiver& ar)
82 :
83 SimpleRCObject(ar)
84 {
85 }
86
87
88 /*******************************************************************************
89
90 ********************************************************************************/
serialize(::zorba::serialization::Archiver & ar)91 void IndexDecl::serialize(::zorba::serialization::Archiver& ar)
92 {
93 ar & theSctx;
94 ar & theName;
95 ar & theIsGeneral;
96 ar & theIsUnique;
97 ar & theIsTemp;
98 SERIALIZE_ENUM(MaintenanceMode, theMaintenanceMode);
99 SERIALIZE_ENUM(ContainerKind, theContainerKind);
100 //ar & theDomainClause;
101 //ar & theKeyExprs;
102 ar & theNumKeyExprs;
103 ar & theKeyTypes;
104 ar & theOrderModifiers;
105
106 ar & theSourceNames;
107 //ar & theDomainSourceExprs;
108
109 ar & theBuildPlan;
110 ar & theDocIndexerPlan;
111 }
112
113
114 /*******************************************************************************
115
116 ********************************************************************************/
~IndexDecl()117 IndexDecl::~IndexDecl()
118 {
119 }
120
121
122 /*******************************************************************************
123
124 ********************************************************************************/
getName() const125 store::Item* IndexDecl::getName() const
126 {
127 return theName.getp();
128 }
129
130
131 /*******************************************************************************
132
133 ********************************************************************************/
getDomainExpr() const134 expr* IndexDecl::getDomainExpr() const
135 {
136 return theDomainExpr;
137 }
138
139
140 /*******************************************************************************
141
142 ********************************************************************************/
setDomainExpr(expr * domainExpr)143 void IndexDecl::setDomainExpr(expr* domainExpr)
144 {
145 theDomainExpr = domainExpr;
146
147 if (theDomainClause == NULL)
148 theDomainClause = theCCB->theEM->create_for_clause(domainExpr->get_sctx(),
149 domainExpr->get_loc(),
150 NULL,
151 NULL);
152
153 theDomainClause->set_expr(domainExpr);
154 }
155
156
157 /*******************************************************************************
158
159 ********************************************************************************/
getDomainVariable() const160 var_expr* IndexDecl::getDomainVariable() const
161 {
162 return theDomainVar;
163 }
164
165
166 /*******************************************************************************
167
168 ********************************************************************************/
setDomainVariable(var_expr * domainVar)169 void IndexDecl::setDomainVariable(var_expr* domainVar)
170 {
171 theDomainVar = domainVar;
172
173 if (theDomainClause == NULL)
174 theDomainClause = theCCB->theEM->create_for_clause(domainVar->get_sctx(),
175 domainVar->get_loc(),
176 NULL,
177 NULL);
178
179 theDomainClause->set_var(domainVar);
180 }
181
182
183 /*******************************************************************************
184
185 ********************************************************************************/
getDomainPositionVariable() const186 var_expr* IndexDecl::getDomainPositionVariable() const
187 {
188 return theDomainPosVar;
189 }
190
191
192 /*******************************************************************************
193
194 ********************************************************************************/
setDomainPositionVariable(var_expr * domainPosVar)195 void IndexDecl::setDomainPositionVariable(var_expr* domainPosVar)
196 {
197 theDomainPosVar = domainPosVar;
198
199 theDomainClause->set_pos_var(domainPosVar);
200 }
201
202
203 /*******************************************************************************
204
205 ********************************************************************************/
setKeyExpressions(const std::vector<expr * > & keyExprs)206 void IndexDecl::setKeyExpressions(const std::vector<expr*>& keyExprs)
207 {
208 theKeyExprs = keyExprs;
209 theNumKeyExprs = theKeyExprs.size();
210 }
211
212
213 /*******************************************************************************
214
215 ********************************************************************************/
getKeyTypes() const216 const std::vector<xqtref_t>& IndexDecl::getKeyTypes() const
217 {
218 return theKeyTypes;
219 }
220
221
222 /*******************************************************************************
223
224 ********************************************************************************/
setKeyTypes(const std::vector<xqtref_t> & keyTypes)225 void IndexDecl::setKeyTypes(const std::vector<xqtref_t>& keyTypes)
226 {
227 assert(!keyTypes.empty());
228
229 theKeyTypes = keyTypes;
230
231 if (theIsGeneral && theIsUnique)
232 {
233 TypeManager* tm = theSctx->get_typemanager();
234
235 xqtref_t type = theKeyTypes[0];
236 xqtref_t ptype;
237
238 if (type != NULL)
239 ptype = TypeOps::prime_type(tm, *type);
240
241 if (ptype == NULL ||
242 TypeOps::is_equal(tm, *ptype, *GENV_TYPESYSTEM.ANY_ATOMIC_TYPE_ONE) ||
243 TypeOps::is_subtype(tm, *ptype, *GENV_TYPESYSTEM.UNTYPED_ATOMIC_TYPE_ONE))
244 {
245 RAISE_ERROR(zerr::ZDST0025_INDEX_BAD_UNIQUE_PROPERTY, theLocation,
246 ERROR_PARAMS(theName->getStringValue()));
247 }
248 }
249 }
250
251
252 /*******************************************************************************
253
254 ********************************************************************************/
getOrderModifiers() const255 const std::vector<OrderModifier>& IndexDecl::getOrderModifiers() const
256 {
257 return theOrderModifiers;
258 }
259
260
261 /*******************************************************************************
262
263 ********************************************************************************/
setOrderModifiers(const std::vector<OrderModifier> & modifiers)264 void IndexDecl::setOrderModifiers(const std::vector<OrderModifier>& modifiers)
265 {
266 theOrderModifiers = modifiers;
267 }
268
269
270 /******************************************************************************
271 Check that the domain and key exprs satisfy the constraints specified by the
272 XQDDF spec. This method is called from the translator, after the domain and
273 key exprs have been translated and optimized.
274 *******************************************************************************/
analyze(CompilerCB * ccb)275 void IndexDecl::analyze(CompilerCB* ccb)
276 {
277 store::Item_t dotQName;
278 GENV_ITEMFACTORY->createQName(dotQName, "", "", "$$dot");
279 expr* dotVar = NULL;
280
281 // Get the var_expr representing the context item, if it is defined
282 VarInfo* var = theSctx->lookup_var(dotQName);
283
284 if (var)
285 dotVar = var->getVar();
286
287 std::vector<var_expr*> varExprs;
288
289 // Check constraints on the domain expr
290 analyzeExprInternal(getDomainExpr(),
291 theSourceNames,
292 theDomainSourceExprs,
293 varExprs,
294 dotVar);
295
296 varExprs.clear();
297
298 std::vector<expr*> keySources;
299
300 csize numKeys = theKeyExprs.size();
301
302 if (theIsGeneral && numKeys > 1)
303 {
304 RAISE_ERROR(zerr::ZDST0035_INDEX_GENERAL_MULTIKEY, theKeyExprs[1]->get_loc(),
305 ERROR_PARAMS(theName->getStringValue()));
306 }
307
308 // Check constraints on the key exprs
309 for (csize i = 0; i < numKeys; ++i)
310 {
311 analyzeExprInternal(theKeyExprs[i],
312 theSourceNames,
313 keySources,
314 varExprs,
315 dotVar);
316 }
317
318 // If the index is declared as "automatically maintained", check whether
319 // automatic maintence can be done efficiently, and if so, set the appropriate
320 // maintenance mode.
321 if (keySources.empty() &&
322 theDomainSourceExprs.size() == 1 &&
323 theMaintenanceMode != MANUAL)
324 {
325 if (getDomainExpr()->is_map(theDomainSourceExprs[0], theSctx))
326 theMaintenanceMode = DOC_MAP;
327 }
328
329 if (theMaintenanceMode == REBUILD)
330 {
331 // If the index is declared as "automatically maintained", then
332 // theMaintenanceMode is initially set to REBUILD. If theMaintenanceMode
333 // is not changed above (to DOC_MAP), then we throw an error because we
334 // don't want to automatically rebuild the full index with every update.
335 RAISE_ERROR(zerr::ZDST0034_INDEX_CANNOT_DO_AUTOMATIC_MAINTENANCE,
336 getDomainExpr()->get_loc(),
337 ERROR_PARAMS(theName->getStringValue()));
338 }
339 else if (theMaintenanceMode == DOC_MAP)
340 {
341 // Have to do this here (rather than during runtime) so that we don't have to
342 // serialize the index exprs.
343 (void)getDocIndexer(ccb, theLocation);
344 }
345
346 // Have to do this here (rather than during runtime) so that we don't have to
347 // serialize the index exprs.
348 (void)getBuildPlan(ccb, theLocation);
349 }
350
351
352 /*******************************************************************************
353 Check that the given expr
354 (a) is deterministic,
355 (b) does not reference any variables other than those in varExprs
356 (c) does not reference the given dotVar
357 (c) does not reference any input functions other than xqddf:collection(qname)
358 (d) the arg to each xqddf:collection is a const qname
359
360 If the above conditions are met, the method will return the qnames of all the
361 accessed collections and the fo exprs representing the xqddf:collection
362 invocations.
363 ********************************************************************************/
analyzeExprInternal(expr * e,std::vector<store::Item * > & sourceNames,std::vector<expr * > & sourceExprs,std::vector<var_expr * > & varExprs,expr * dotVar)364 void IndexDecl::analyzeExprInternal(
365 expr* e,
366 std::vector<store::Item*>& sourceNames,
367 std::vector<expr*>& sourceExprs,
368 std::vector<var_expr*>& varExprs,
369 expr* dotVar)
370 {
371 if (e->get_expr_kind() == fo_expr_kind)
372 {
373 fo_expr* foExpr = static_cast<fo_expr*>(e);
374 const function* func = foExpr->get_func();
375
376 if (!func->isDeterministic())
377 {
378 RAISE_ERROR(zerr::ZDST0028_INDEX_NOT_DETERMINISTIC, e->get_loc(),
379 ERROR_PARAMS(theName->getStringValue()));
380 }
381
382 if (func->isSource())
383 {
384 if (func->getKind() == FunctionConsts::STATIC_COLLECTIONS_DML_COLLECTION_1)
385 {
386 const expr* argExpr = foExpr->get_arg(0);
387
388 const store::Item* qname = argExpr->getQName(theSctx);
389
390 if (qname != NULL)
391 {
392 sourceNames.push_back((store::Item*)qname);
393 sourceExprs.push_back(foExpr);
394 }
395 else
396 {
397 RAISE_ERROR(zerr::ZDST0030_INDEX_NON_CONST_DATA_SOURCE, e->get_loc(),
398 ERROR_PARAMS(theName->getStringValue()));
399 }
400 }
401 else
402 {
403 RAISE_ERROR(zerr::ZDST0029_INDEX_INVALID_DATA_SOURCE, e->get_loc(),
404 ERROR_PARAMS(theName->getStringValue()));
405 }
406 }
407 }
408 else if (e->get_expr_kind() == var_decl_expr_kind)
409 {
410 var_expr* varExpr = static_cast<var_decl_expr*>(e)->get_var_expr();
411
412 ZORBA_ASSERT(varExpr->get_kind() == var_expr::local_var);
413
414 varExprs.push_back(varExpr);
415 }
416 else if (e->get_expr_kind() == flwor_expr_kind ||
417 e->get_expr_kind() == gflwor_expr_kind)
418 {
419 static_cast<const flwor_expr*>(e)->get_vars_defined(varExprs);
420 }
421 else if (e->get_expr_kind() == var_expr_kind)
422 {
423 if (e == dotVar)
424 {
425 RAISE_ERROR(zerr::ZDST0032_INDEX_REFERENCES_CTX_ITEM, e->get_loc(),
426 ERROR_PARAMS(theName->getStringValue()));
427 }
428
429 if (e != getDomainVariable() &&
430 std::find(varExprs.begin(), varExprs.end(), e) == varExprs.end())
431 {
432 RAISE_ERROR(zerr::ZDST0031_INDEX_HAS_FREE_VARS, e->get_loc(),
433 ERROR_PARAMS(theName->getStringValue()));
434 }
435 }
436
437 ExprIterator iter(e);
438 while (!iter.done())
439 {
440 analyzeExprInternal((**iter), sourceNames, sourceExprs, varExprs, dotVar);
441 iter.next();
442 }
443 }
444
445
446 /******************************************************************************
447 Create the expression that "builds" the index, if not done already. The expr
448 to build is the following, for value and general indexes, respectively:
449
450 for $newdot at $newpos in cloned_domain_expr
451 return value-index-entry-builder($$newdot, cloned_key1_expr, ..., cloned_keyN_expr)
452
453 for $$newdot at $$newpos in cloned_domain_expr
454 return general-index-entry-builder($$newdot, cloned_key_expr);
455
456 The runtime plan corresponding to this expr is given to the store, which
457 then populates the index by creating entries out of the items returned by
458 this expr.
459 *******************************************************************************/
getBuildExpr(CompilerCB * ccb,const QueryLoc & loc)460 expr* IndexDecl::getBuildExpr(CompilerCB* ccb, const QueryLoc& loc)
461 {
462 if (theBuildExpr != NULL)
463 return theBuildExpr;
464
465 theDomainClause = NULL;
466
467 expr* domainExpr = getDomainExpr();
468 var_expr* dot = getDomainVariable();
469 var_expr* pos = getDomainPositionVariable();
470 static_context* sctx = domainExpr->get_sctx();
471 const QueryLoc& dotloc = dot->get_loc();
472
473 csize numKeys = theKeyExprs.size();
474 std::vector<expr*> clonedExprs(numKeys + 1);
475
476 //
477 // Clone the domain expr.
478 //
479 expr::substitution_t subst;
480 expr* newdom = domainExpr->clone(subst);
481
482 //
483 // Clone the domain variable and the domain pos variable. These vars are
484 // referenced by the key exprs.
485 //
486 var_expr* newdot = theCCB->theEM->
487 create_var_expr(sctx, dotloc, dot->get_kind(), dot->get_name());
488
489 var_expr* newpos = theCCB->theEM->
490 create_var_expr(sctx, dotloc, pos->get_kind(), pos->get_name());
491
492 //
493 // Create for clause (this has to be done here so that the cloned dot var gets
494 // associated with the cloned domain expr; this is needed before cloning the
495 // key expr) :
496 //
497 // for $newdot at $newpos in new_domain_expr
498 //
499 for_clause* fc =
500 theCCB->theEM->create_for_clause(sctx, dotloc, newdot, newdom, newpos);
501
502 //
503 // Clone the key exprs, replacing their references to the 2 domain variables
504 // with the clones of these variables.
505 //
506 for (csize i = 0; i < numKeys; ++i)
507 {
508 subst.clear();
509 subst[dot] = newdot;
510 subst[pos] = newpos;
511
512 clonedExprs[i+1] = theKeyExprs[i]->clone(subst);
513 }
514
515 //
516 // Create flwor expr:
517 //
518 // for $newdot at $newpos in new_domain_expr
519 // return index-entry-builder($$newdot, new_key1_expr, ..., new_keyN_expr)
520 //
521
522 expr* domainVarExpr(theCCB->theEM->create_wrapper_expr(sctx, loc, newdot));
523
524 clonedExprs[0] = domainVarExpr;
525
526 function* f = NULL;
527
528 if (theIsGeneral)
529 f = GET_BUILTIN_FUNCTION(OP_GENERAL_INDEX_ENTRY_BUILDER_N);
530 else
531 f = GET_BUILTIN_FUNCTION(OP_VALUE_INDEX_ENTRY_BUILDER_N);
532
533 ZORBA_ASSERT(f != NULL);
534
535 fo_expr* returnExpr = theCCB->theEM->create_fo_expr(sctx, loc, f, clonedExprs);
536
537 flwor_expr* flworExpr = theCCB->theEM->create_flwor_expr(sctx, loc, false);
538 flworExpr->set_return_expr(returnExpr);
539 flworExpr->add_clause(fc);
540
541 theBuildExpr = flworExpr;
542
543 if (ccb->theConfig.optimize_cb != NULL)
544 {
545 std::string msg = "build expr for index " + theName->getStringValue().str();
546
547 ccb->theConfig.optimize_cb(theBuildExpr, msg);
548 }
549
550 return theBuildExpr;
551 }
552
553
554 /*******************************************************************************
555
556 ********************************************************************************/
getBuildPlan(CompilerCB * ccb,const QueryLoc & loc)557 PlanIterator* IndexDecl::getBuildPlan(CompilerCB* ccb, const QueryLoc& loc)
558 {
559 if (theBuildPlan != NULL)
560 return theBuildPlan.getp();
561
562 expr* buildExpr = getBuildExpr(ccb, loc);
563
564 ulong nextVarId = 1;
565 theBuildPlan = codegen("index", buildExpr, ccb, nextVarId);
566
567 return theBuildPlan.getp();
568 }
569
570
571 /*******************************************************************************
572 Called from ApplyIterator::nextImpl before it actually starts applying the
573 updates.
574 ********************************************************************************/
getDocIndexer(CompilerCB * ccb,const QueryLoc & loc)575 DocIndexer* IndexDecl::getDocIndexer(
576 CompilerCB* ccb,
577 const QueryLoc& loc)
578 {
579 if (theDocIndexer != NULL)
580 return theDocIndexer.getp();
581
582 if (theMaintenanceMode != DOC_MAP)
583 return NULL;
584
585 std::stringstream ss;
586 ss << "$$idx_doc_var_" << this;
587 std::string varname = ss.str();
588 store::Item_t docVarName;
589 GENV_ITEMFACTORY->createQName(docVarName, "", "", varname.c_str());
590
591 csize numKeys = theNumKeyExprs;
592
593 if (theDocIndexerPlan != NULL)
594 {
595 theDocIndexer = new DocIndexer(isGeneral(), numKeys, theDocIndexerPlan, docVarName);
596
597 return theDocIndexer.getp();
598 }
599
600 expr* domainExpr = getDomainExpr();
601 var_expr* dot = getDomainVariable();
602 var_expr* pos = getDomainPositionVariable();
603
604 static_context* sctx = domainExpr->get_sctx();
605
606 const QueryLoc& dotloc = dot->get_loc();
607
608 std::vector<expr*> clonedExprs(numKeys + 1);
609
610 //
611 // Clone the domain expr and replace the reference to the collection with a
612 // reference to a new prolog variable that will be bound to a set of docs
613 // during the apply-updates.
614 //
615
616 var_expr* docVar = theCCB->theEM->create_var_expr(sctx,
617 dot->get_loc(),
618 var_expr::prolog_var,
619 docVarName);
620 docVar->set_unique_id(1);
621 ulong nextVarId = 2;
622
623 expr* wrapperExpr = theCCB->theEM->create_wrapper_expr(sctx, dot->get_loc(), docVar);
624
625 docVar->set_type(domainExpr->get_return_type());
626
627 expr::substitution_t subst;
628
629 subst[theDomainSourceExprs[0]] = wrapperExpr;
630
631 expr* newdom = domainExpr->clone(subst);
632
633 //
634 // Clone the domain variable and the domain pos variable. These vars are
635 // referenced by the key exprs.
636 //
637 var_expr* newdot = theCCB->theEM->
638 create_var_expr(sctx, dotloc, dot->get_kind(), dot->get_name());
639
640 var_expr* newpos = theCCB->theEM->
641 create_var_expr(sctx, dotloc, pos->get_kind(), pos->get_name());
642
643 //
644 // Create for clause (this has to be done here so that the cloned dot var gets
645 // associated with the cloned domain expr; this is needed before cloning the
646 // key expr) :
647 //
648 // for $newdot at $newpos in new_domain_expr
649 //
650 for_clause* fc =
651 theCCB->theEM->create_for_clause(sctx, dotloc, newdot, newdom, newpos);
652
653 //
654 // Clone the key exprs, replacing their references to the 2 domain variables
655 // with the clones of these variables.
656 //
657 for (csize i = 0; i < numKeys; ++i)
658 {
659 subst.clear();
660 subst[dot] = newdot;
661 subst[pos] = newpos;
662
663 clonedExprs[i+1] = theKeyExprs[i]->clone(subst);
664 }
665
666 //
667 // Create flwor expr:
668 //
669 // for $newdot at $newpos in new_domain_expr
670 // return index-entry-builder($$newdot, new_key1_expr, ..., new_keyN_expr)
671 //
672
673 expr* domainVarExpr = theCCB->theEM->create_wrapper_expr(sctx, loc, newdot);
674
675 clonedExprs[0] = domainVarExpr;
676
677 function* f = NULL;
678
679 if (theIsGeneral)
680 f = GET_BUILTIN_FUNCTION(OP_GENERAL_INDEX_ENTRY_BUILDER_N);
681 else
682 f = GET_BUILTIN_FUNCTION(OP_VALUE_INDEX_ENTRY_BUILDER_N);
683
684 ZORBA_ASSERT(f != NULL);
685
686 fo_expr* returnExpr = theCCB->theEM->create_fo_expr(sctx, loc, f, clonedExprs);
687
688 flwor_expr* flworExpr = theCCB->theEM->create_flwor_expr(sctx, loc, false);
689 flworExpr->set_return_expr(returnExpr);
690 flworExpr->add_clause(fc);
691
692 if (ccb->theConfig.optimize_cb != NULL)
693 {
694 std::string msg = "entry-creator expr for index " + theName->getStringValue().str();
695
696 ccb->theConfig.optimize_cb(flworExpr, msg);
697 }
698
699 theDocIndexerExpr = flworExpr;
700
701 //
702 // Generate the runtime plan for theDocIndexerExpr
703 //
704 theDocIndexerPlan = codegen("doc indexer", flworExpr, ccb, nextVarId);
705
706 //
707 // Create theDocIndexer obj
708 //
709 theDocIndexer = new DocIndexer(isGeneral(), numKeys, theDocIndexerPlan, docVarName);
710
711 return theDocIndexer.getp();
712 }
713
714
715 /*******************************************************************************
716
717 ********************************************************************************/
toString()718 std::string IndexDecl::toString()
719 {
720 std::ostringstream os;
721
722 os << "Index : " << theName->getStringValue() << std::endl;
723
724 os << "Domain Expr : " << std::endl;
725
726 getDomainExpr()->put(os) << std::endl;
727
728 os << "Domain Variable : ";
729 getDomainVariable()->put(os);
730
731 csize numColumns = theKeyExprs.size();
732
733 for (csize i = 0; i < numColumns; ++i)
734 {
735 os << std::endl << "Key Expr " << i << " : " << std::endl;
736 theKeyExprs[i]->put(os);
737 }
738
739 return os.str();
740 }
741
742
743 } // namespace zorba
744 /* vim:set et sw=2 ts=2: */
745