home *** CD-ROM | disk | FTP | other *** search
/ Complete Linux / Complete Linux.iso / docs / apps / database / postgres / postgre4.z / postgre4 / src / rewrite / RewriteHandl.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-03-18  |  17.7 KB  |  627 lines

  1.  
  2.  
  3. /* ----------------------------------------------------------------
  4.  * $Header: /private/postgres/src/rewrite/RCS/RewriteHandler.c,v 2.32 1992/08/16 21:03:54 clarsen Exp $
  5.  * ----------------------------------------------------------------
  6.  */
  7. /****************************************************/
  8.  
  9. #include "tmp/postgres.h"
  10. #include "utils/log.h"
  11. #include "utils/rel.h"
  12. #include "nodes/pg_lisp.h"        /* for LispValue and lisp support */
  13. #include "nodes/primnodes.h"
  14. #include "nodes/primnodes.a.h"        /* for accessors to varnodes etc */
  15.  
  16. #include "rules/prs2.h"            /* XXX - temporarily */
  17. /* #include "rules/prs2locks.h"         XXX - temporarily */
  18.  
  19. #include "parser/parse.h"        /* for RETRIEVE,REPLACE,APPEND ... */
  20. #include "parser/parsetree.h"        /* for parsetree manipulation */
  21.  
  22. #include "./RewriteSuppo.h"    
  23. #include "./RewriteDebug.h"
  24. #include "./RewriteHandl.h"
  25. #include "./RewriteManip.h"
  26. #include "./locks.h"
  27.  
  28. /*
  29.  * Gather meta information about parsetree, and rule
  30.  * Fix rule body and qualifier so that they can be mixed
  31.  * with the parsetree and maintain semantic validity
  32.  */
  33.  
  34. extern List lispCopyList();
  35. extern List lispCopy();
  36.  
  37.  
  38. extern int DebugLvl;
  39. RewriteInfo *GatherRewriteMeta(parsetree, rule_action, rule_qual, rt_index, event, instead_flag)
  40.      List parsetree,rule_action, rule_qual;
  41.      int rt_index, event, *instead_flag;
  42. {
  43.     RewriteInfo *info;    
  44.     int rt_length;
  45.     List result_reln;
  46.  
  47.     info = (RewriteInfo *) palloc(sizeof(RewriteInfo));
  48.     info->rt_index = rt_index;
  49.     info->event = event;
  50.     info->instead_flag = *instead_flag;
  51.     info->rule_action = rule_action;
  52.     info->rule_qual = (List) CopyObject(rule_qual);
  53.     info->nothing = FALSE;
  54.     info->action = root_command_type(parse_root(info->rule_action));
  55.     if (info->rule_action == LispNil) info->nothing = TRUE;
  56.     if (info->nothing) return info;
  57.     info->current_varno = rt_index;
  58.     info->rt = lispCopyList(root_rangetable(parse_root(parsetree)));
  59.     rt_length = length(info->rt);
  60.     info->rt = append(info->rt,
  61.               root_rangetable(parse_root(info->rule_action)));
  62.     info->new_varno = PRS2_NEW_VARNO + rt_length;
  63.     OffsetVarNodes(info->rule_action, rt_length);
  64.     OffsetVarNodes(info->rule_qual, rt_length);
  65.     ChangeVarNodes(info->rule_action, PRS2_CURRENT_VARNO+rt_length, rt_index);
  66.     ChangeVarNodes(info->rule_qual, PRS2_CURRENT_VARNO+rt_length, rt_index);
  67.     
  68.     /*
  69.      * bug here about replace CURRENT  -- sort of
  70.      * replace current is deprecated now so this code shouldn't really
  71.      * need to be so clutzy but.....
  72.      */
  73.    if (info->action != RETRIEVE) {    /* i.e update XXXXX */
  74.        int new_result_reln = 0;
  75.        result_reln = root_result_relation(parse_root(info->rule_action));
  76.        switch (CInteger(result_reln)) {
  77.        case PRS2_CURRENT_VARNO: new_result_reln = rt_index;
  78.        break; 
  79.        case PRS2_NEW_VARNO:    /* XXX */
  80.        default            :
  81.        new_result_reln = CInteger(result_reln) + rt_length;
  82.        break;
  83.        }
  84.        root_result_relation(parse_root(info->rule_action)) =
  85.        lispInteger (new_result_reln);
  86.    }
  87.     
  88.     return info;
  89. }
  90.  
  91.  
  92.  
  93. List OptimizeRIRRules(locks)
  94.      List locks;
  95. {
  96.     List attr_level = LispNil,i;
  97.     List relation_level = LispNil;
  98.  
  99.     foreach (i, locks) {
  100.     Prs2OneLock rule_lock     = (Prs2OneLock)CAR(i);
  101.     if ( prs2OneLockGetAttributeNumber(rule_lock) == -1) 
  102.         relation_level = nappend1(relation_level, (LispValue)rule_lock);
  103.     else attr_level = nappend1(attr_level, (LispValue)rule_lock);
  104.     }
  105.     return append(relation_level, attr_level);
  106. }
  107. /*
  108.  * idea is to put instead rules before regular rules so that
  109.  * excess semantically queasy queries aren't processed
  110.  */
  111.  
  112. List OrderRules(locks)
  113.      List locks;
  114. {
  115.     List regular = LispNil,i;
  116.     List instead_rules = LispNil;
  117.  
  118.     foreach (i, locks) {
  119.     Prs2OneLock rule_lock     = (Prs2OneLock)CAR(i);
  120.     if (!(IsActive(rule_lock) && IsRewrite(rule_lock))) continue;
  121.     if (IsInstead(rule_lock))
  122.         instead_rules = nappend1(instead_rules, (LispValue)rule_lock);
  123.     else regular = nappend1(regular, (LispValue)rule_lock);
  124.     }
  125.     return append(regular, instead_rules);
  126. }
  127. int AllRetrieve(actions)
  128.      List actions;
  129. {
  130.     List n;
  131.  
  132.     foreach(n, actions) {
  133.         List pt = CAR(n);
  134.     int event;
  135.     List command_type;
  136.     List root = parse_root(pt);
  137.  
  138.     command_type = root_command_type_atom(root);
  139.     if ((consp(command_type)) && (CInteger(CAR(command_type)) == '*'))
  140.         event = (int) CAtom(CDR(command_type));
  141.     else
  142.         event = root_command_type(root);
  143.     if (event != RETRIEVE) return false;
  144.     }
  145.     return true;
  146. }
  147. List StupidUnionRetrieveHack(parsetree, actions)
  148.      List parsetree, actions;
  149. {
  150.     return actions;
  151. }
  152. List FireRetrieveRulesAtQuery(parsetree, rt_index, relation,instead_flag,
  153.                   rule_flag)
  154.      List parsetree;
  155.      Relation relation;
  156.      int rt_index, *instead_flag, rule_flag;
  157. {
  158.     List i;
  159.     List work   =  LispNil;
  160.     List results  = LispNil;
  161.     RuleLock rt_entry_locks = NULL;
  162.     List locks = LispNil;
  163.     rt_entry_locks = prs2GetLocksFromRelation(
  164.                 RelationGetRelationName(relation));
  165.     locks = MatchRetrieveLocks(rt_entry_locks, rt_index, parsetree);
  166.     foreach (i, locks) {
  167.     Prs2OneLock rule_lock     = (Prs2OneLock)CAR(i);
  168.     List rule;
  169.     List rule_action;
  170.     List rule_qual;
  171.     List qual;
  172.     int instead;
  173.  
  174.     if (!IsInstead(rule_lock)) continue;
  175.     work = nappend1(work, (LispValue)rule_lock);
  176.     }
  177.     if (work != LispNil) {
  178.     work = OptimizeRIRRules(locks);
  179.     foreach (i, work) {
  180.         Prs2OneLock rule_lock     = (Prs2OneLock)CAR(i);
  181.         List rule;
  182.         int foo;
  183.         int relation_level;
  184.         int modified = FALSE;
  185.         rule = RuleIdGetActionInfo (prs2OneLockGetRuleId(rule_lock),
  186.                     &foo);
  187.         if (null(rule)) continue;
  188.         relation_level = (prs2OneLockGetAttributeNumber(rule_lock) == -1);
  189.         if (!CDR(rule)) {
  190.         *instead_flag = TRUE;
  191.         return LispNil;
  192.         }
  193.         if (!rule_flag &&
  194.         length(CDR(rule)) >= 2
  195.         && AllRetrieve(CDR(rule))) {
  196.         *instead_flag = TRUE;
  197.         return StupidUnionRetrieveHack(parsetree, CDR(rule));
  198.         }
  199.         ApplyRetrieveRule(parsetree, rule,rt_index,relation_level,
  200.                   prs2OneLockGetAttributeNumber(rule_lock),
  201.                   &modified);
  202.         if (modified) {
  203.         *instead_flag = TRUE;
  204.         FixResdomTypes(parse_targetlist(parsetree));
  205.         return lispCons(parsetree,LispNil);
  206.         }
  207.     }
  208.     }
  209.     return LispNil;
  210. }    
  211.     
  212.     
  213. /* Idea is like this:
  214.  *
  215.  * retrieve-instead-retrieve rules have different semantics than update nodes
  216.  * Separate RIR rules from others.  Pass others to FireRules.
  217.  * Order RIR rules and process.
  218.  *
  219.  */
  220.  
  221. ApplyRetrieveRule(parsetree, rule, rt_index,relation_level, attr_num,modified)
  222.      List parsetree,rule;
  223.      int relation_level, attr_num, rt_index, *modified;
  224. {
  225.     List rule_action;
  226.     List rule_qual, rt;
  227.     int nothing,rt_length;
  228.     int badpostquel= FALSE;
  229.     if (!null(rule) && CDR(rule)) {
  230.     if (length(CDR(rule)) > 1)
  231.         return;
  232.     rule_action = CAR(CDR(rule));
  233.     rule_qual = CAR(rule);
  234.     nothing = FALSE;
  235.     }
  236.     else nothing = TRUE;
  237.     if (rule_action == LispNil) nothing = TRUE;
  238.     rt = lispCopyList(root_rangetable(parse_root(parsetree)));
  239.     rt_length = length(rt);
  240.     rt = append(rt, root_rangetable(parse_root(rule_action)));
  241.     root_rangetable(parse_root(parsetree)) = rt;
  242.     root_rangetable(parse_root(rule_action)) = rt;
  243.     OffsetVarNodes(rule_action, rt_length);
  244.     OffsetVarNodes(rule_qual, rt_length);
  245.     ChangeVarNodes(rule_action, PRS2_CURRENT_VARNO+rt_length, rt_index);
  246.     ChangeVarNodes(rule_qual, PRS2_CURRENT_VARNO+rt_length, rt_index);
  247.     if (relation_level) {
  248.     HandleViewRule(parsetree, rt, parse_targetlist(rule_action),rt_index
  249.                ,modified);
  250.     }
  251.     else {
  252.     HandleRIRAttributeRule(parsetree, rt,
  253.                    parse_targetlist(rule_action),
  254.                    rt_index, attr_num,modified,&badpostquel);
  255.     }
  256.     if (*modified && !badpostquel)
  257.     AddQual(parsetree,parse_qualification(rule_action));
  258. }
  259. List ProcessRetrieveQuery(parsetree, rt,instead_flag,rule)
  260.      List parsetree,rt;
  261.      int *instead_flag,rule;
  262. {
  263.     List rt_entry_ptr;
  264.     int rt_index = 0;
  265.     List product_queries = LispNil;
  266.  
  267.     foreach (rt_entry_ptr, rt) {
  268.     List rt_entry = CAR(rt_entry_ptr);
  269.     Name rt_entry_relname = NULL;
  270.     Relation rt_entry_relation = NULL;
  271.     RuleLock rt_entry_locks = NULL;
  272.     ObjectId rt_entry_relid = NULL;
  273.     int just_rir_rules = FALSE;
  274.      List new_rewritten  = NULL;
  275.     List result = LispNil;
  276.  
  277.     rt_entry_relname = (Name) CString(CADR(rt_entry));
  278.     rt_index++;
  279.     rt_entry_relation = amopenr(rt_entry_relname);
  280.     result = 
  281.         FireRetrieveRulesAtQuery(parsetree, rt_index, rt_entry_relation,
  282.                      instead_flag,rule);
  283.     amclose(rt_entry_relation);
  284.     if (*instead_flag && result) return result;
  285.     if (*instead_flag) return LispNil;
  286.     }
  287.     if (rule) return LispNil;
  288.     foreach (rt_entry_ptr, rt) {
  289.     List rt_entry = CAR(rt_entry_ptr);
  290.     Name rt_entry_relname = NULL;
  291.     Relation rt_entry_relation = NULL;
  292.     RuleLock rt_entry_locks = NULL;
  293.     ObjectId rt_entry_relid = NULL;
  294.     int just_rir_rules = FALSE;
  295.      List new_rewritten  = NULL;
  296.     List result = LispNil;
  297.     List locks = LispNil;
  298.     List dummy_products;
  299.     rt_index++;
  300.     rt_entry_relname = (Name) CString(CADR(rt_entry));
  301.     rt_entry_relation = amopenr(rt_entry_relname);
  302.     rt_entry_locks = prs2GetLocksFromRelation(
  303.               RelationGetRelationName(rt_entry_relation));
  304.     
  305.     amclose(rt_entry_relation);
  306.     locks = MatchLocks(EventIsRetrieve,rt_entry_locks,rt_index,parsetree);
  307.     if (locks == LispNil) continue;
  308.     result = FireRules(parsetree, rt_index, RETRIEVE,
  309.                instead_flag, locks,&dummy_products);
  310.     if (*instead_flag) return nappend1(LispNil, result);
  311.     if (result != LispNil)
  312.         product_queries = append(product_queries, result);
  313.     }
  314.     return product_queries;
  315. }
  316.  
  317. List CopyAndAddQual(parsetree, actions, rule_qual,
  318.             rt_index,event)
  319.      List parsetree,rule_qual,actions;
  320.      int event, rt_index;
  321. {
  322.     List new_pt = (List) CopyObject(parsetree);
  323.     List new_qual = LispNil;
  324.     List rule_action = LispNil;
  325.  
  326.     if (actions)
  327.     rule_action = CAR(actions);
  328.     if (rule_qual != LispNil)
  329.     new_qual = (List) CopyObject(rule_qual);
  330.     if (rule_action != LispNil) {
  331.     List rt;
  332.     int rt_length;
  333.  
  334.     rt = root_rangetable(parse_root(new_pt));
  335.     rt_length = length(rt);
  336.     rt = append(rt,
  337.             lispCopyList(root_rangetable(parse_root(rule_action))));
  338.     root_rangetable(parse_root(new_pt)) = rt;
  339.     OffsetVarNodes(new_qual, rt_length);
  340.     ChangeVarNodes(new_qual, PRS2_CURRENT_VARNO+rt_length, rt_index);
  341.     }
  342.     /* XXX -- where current doesn't work for instead nothing.... yet*/
  343.     AddNotQual(new_pt, new_qual);
  344.     return new_pt;
  345. }
  346.  
  347.  
  348. /*
  349.  *  FireRules(parsetree, rt_index, relation, rt, event,
  350.  *             instead_flag, locks,qual_products)
  351.  *  
  352.  *  Iterate through rule locks applying rules.  After an instead rule
  353.  *  rule has been applied, return just new parsetree and let RewriteQuery
  354.  *  start the process all over again.  The locks are reordered to maintain
  355.  *  sensible semantics.  remember: reality is for dead birds -- glass
  356.  *
  357.  */
  358.  
  359. List FireRules(parsetree, rt_index, event,  instead_flag, locks,qual_products)
  360.      List parsetree;
  361.      int event, *instead_flag, rt_index;
  362.      List locks;
  363.      List *qual_products;
  364. {
  365.     RewriteInfo *info;
  366.     List results = LispNil;
  367.     List i;
  368.  
  369. /* choose rule to fire from list of rules */
  370.  
  371.     if (locks == LispNil) {
  372.        (void) ProcessRetrieveQuery(parsetree,
  373.                    root_rangetable(parse_root(parsetree)),
  374.                    instead_flag,TRUE);
  375.        if (*instead_flag) return nappend1(LispNil,parsetree);
  376.        else return LispNil;
  377.    }
  378.                     
  379.     locks = OrderRules(locks);    /* instead rules first */
  380.     foreach ( i , locks ) {
  381.        Prs2OneLock rule_lock     = (Prs2OneLock)CAR(i);
  382.        List saved_query = LispNil;
  383.        List rule;
  384.        List qual;
  385.        List event_qual, actions;
  386.        List r;
  387.        int foo = *instead_flag;
  388.        *instead_flag = FALSE;
  389.        rule = RuleIdGetActionInfo (prs2OneLockGetRuleId(rule_lock),
  390.                    instead_flag);
  391.        
  392.        /* multiple rule action time */
  393.        if (null(rule)) return LispNil;
  394.        event_qual = CAR(rule);
  395.        actions = CDR(rule);
  396.        if (event_qual != LispNil && *instead_flag)
  397.        *qual_products =
  398.            nappend1(*qual_products,
  399.             CopyAndAddQual(parsetree,actions,event_qual,
  400.                        rt_index,event));
  401.        foreach (r, actions) {
  402.        List rule_action  = CAR(r);
  403.        List rule_qual = lispCopy(event_qual);
  404.        
  405.        /* save range table information 
  406.         * possibly shift current/new to end of rangetable 
  407.         * Step 1
  408.         * Rewrite current.attribute or current to tuple variable
  409.         * this appears to be done in parser?
  410.         */
  411.  
  412.        info = GatherRewriteMeta(parsetree, rule_action,
  413.                     rule_qual,rt_index,event,instead_flag);
  414.        
  415.        /* handle escapable cases, or those handled by other code */
  416.        
  417.        if (info->nothing && *instead_flag) return LispNil;
  418.        if (info->nothing) continue;
  419.        if (info->action == info->event == RETRIEVE) continue;
  420.        /*
  421.         * Event Qualification forces copying of parsetree --- XXX
  422.         * and splitting into two queries one w/rule_qual, one
  423.         * w/NOT rule_qual
  424.         */
  425.        /*
  426.         * Also add user query qual onto rule action
  427.         *
  428.         */
  429.        qual = parse_qualification(parsetree);
  430.        AddQual(info->rule_action, qual);
  431.        
  432.        if (info->rule_qual != LispNil) 
  433.            AddQual(info->rule_action, info->rule_qual);
  434.  
  435.        /* Step 2
  436.         * Rewrite new.attribute w/ right hand side of
  437.         * target-list entry for appropriate field name in append/replace
  438.         *
  439.         */  
  440.        if ((info->event == APPEND) || (info->event == REPLACE)) {
  441.            FixNew(info, parsetree);
  442.        }
  443.        
  444.        /* Step 3
  445.         *
  446.         *  rewriting due to retrieve rules 
  447.         */
  448.        root_rangetable(parse_root(info->rule_action)) = info->rt;              
  449.        (void) ProcessRetrieveQuery(info->rule_action, info->rt, &foo,TRUE);
  450.        
  451.        /* Step 4
  452.         *  
  453.         * Simplify?
  454.         * hey, no algorithm for simplification...let the planner do it.
  455.         */
  456.  
  457.        results = nappend1(results,info->rule_action);
  458.        }
  459.        if (*instead_flag) break;
  460.    }
  461.     return results;
  462. }
  463. List ProcessUpdateNode(parsetree, rt_index, event,
  464.              instead_flag,relation_locks,qual_products)
  465.      List parsetree;
  466.      int event, *instead_flag, rt_index;
  467.      RuleLock relation_locks;
  468.      List *qual_products;
  469. {
  470.     List locks = LispNil;
  471.     Prs2LockType locktype;
  472.     /* First step is to determine which locks actually apply */
  473.  
  474.     switch(event) {
  475.     case APPEND: locktype = EventIsAppend;
  476.     break;
  477.     case DELETE: locktype = EventIsDelete;
  478.     break;
  479.     case RETRIEVE: locktype = EventIsRetrieve;
  480.     break;
  481.     case REPLACE: locktype = EventIsReplace;
  482.     break;
  483.     default: elog (WARN, "really damm unlikely event");
  484.         break;
  485.     }
  486.  
  487.     if (relation_locks != NULL)
  488.     locks = MatchLocks(locktype,relation_locks,rt_index,parsetree);
  489.     return FireRules(parsetree, rt_index, event,
  490.             instead_flag, locks,qual_products);
  491. }
  492.  
  493.     
  494.     
  495. List RewriteQuery(parsetree,instead_flag,qual_products)
  496.      List parsetree;
  497.      int *instead_flag;
  498.      List *qual_products;
  499. {
  500.     List root              = NULL;
  501.     List command_type      = NULL;
  502.     List rt                = NULL;
  503.     List rt_entry_ptr      = NULL;
  504.     int event              = -1;
  505.     List tl                = NULL;
  506.     List qual              = NULL;
  507.     List something_happened = FALSE;
  508.     List qr_output            = LispNil;
  509.     int rt_index           = 1;
  510.     List product_queries = LispNil;
  511.     List result_relation = LispNil;
  512.  
  513.  
  514.     Assert ( parsetree != NULL );
  515.     root = parse_root(parsetree);
  516.     Assert ( root != NULL );
  517.     command_type = root_command_type_atom(root);
  518.  
  519.     rt    = root_rangetable(root);
  520.  
  521.     /* if this is a recursive query, command_type node is funny */
  522.     if ((consp(command_type)) && (CInteger(CAR(command_type)) == '*'))
  523.     event = (int) CAtom(CDR(command_type));
  524.     else
  525.         event = root_command_type(root);
  526.  
  527.     if (DebugLvl) {
  528.     printf("\nRewriteQuery being called with :\n");
  529.     Print_parse ( parsetree );
  530.     }
  531.  
  532.     /*
  533.      * only for a delete may the targetlist be NULL
  534.      */
  535.     if ( event != DELETE && event != NOTIFY ) {
  536.      tl = parse_targetlist(parsetree);
  537.      Assert ( tl != NULL );
  538.     }
  539.     if (event == NOTIFY)
  540.       return LispNil;
  541.  
  542.     result_relation = root_result_relation(parse_root(parsetree));
  543.     if (event != RETRIEVE) {
  544.     List rt_entry;
  545.     Name rt_entry_relname = NULL;
  546.     Relation rt_entry_relation = NULL;
  547.     RuleLock rt_entry_locks = NULL;
  548.  
  549.     rt_entry = rt_fetch(CInteger(result_relation),
  550.                 root_rangetable(parse_root(parsetree)));
  551.     rt_entry_relname = (Name) CString(CADR(rt_entry));
  552.     rt_entry_relation = amopenr(rt_entry_relname);
  553.     rt_entry_locks = prs2GetLocksFromRelation(
  554.                  RelationGetRelationName(rt_entry_relation));
  555.     amclose(rt_entry_relation);
  556.     product_queries =
  557.         ProcessUpdateNode(parsetree,
  558.                   CInteger(result_relation),
  559.                   event,
  560.                   instead_flag,
  561.                   rt_entry_locks,qual_products);
  562.     return product_queries;
  563.     }
  564.     else {            /* XXX */
  565.     char *temp;
  566.     List new_pt;
  567.     List other;
  568.     /*
  569.      * This looks a little foolish.  Something must have been removed
  570.      * in between these two lines of code.... ?  Commenting these lines
  571.      * out gives us about an 8% speed up on the wisconsin benchmark.
  572.      *
  573.      * temp = PlanToString(parsetree);
  574.      *
  575.      * new_pt = (List)StringToPlan(temp);
  576.      */
  577.     other = (List) CopyObject(parsetree);
  578.     return ProcessRetrieveQuery(other, rt, instead_flag,FALSE);
  579.     }
  580. }
  581.  
  582. /* rewrite one query via QueryRewrite system, possibly returning 0, or many
  583.  * queries
  584.  */  
  585.  
  586. List
  587. QueryRewrite ( parsetree )
  588.      List parsetree;
  589. {
  590.     List n;
  591.     List rewritten = LispNil;
  592.     List result = LispNil;
  593.     int instead;
  594.     List qual_products = LispNil;
  595.  
  596.     instead = FALSE;
  597.     result = RewriteQuery(parsetree, &instead,&qual_products);
  598.     if (!instead) rewritten = nappend1(rewritten, parsetree);
  599.     foreach(n, result) {
  600.         List pt = CAR(n);
  601.         List newstuff = LispNil;
  602.  
  603.         newstuff = QueryRewrite(pt);
  604.         if (newstuff != LispNil) rewritten = append(rewritten, newstuff);
  605.     }
  606.     if (qual_products != LispNil)
  607.     rewritten = append(rewritten,qual_products);
  608.     if (DebugLvl) {
  609.     puts("printing resulting queries:");
  610.     foreach(n, rewritten) {
  611.         List pt = CAR(n);
  612.         Print_parse(pt);
  613.     }
  614.     }
  615.     return rewritten;
  616. }
  617.  
  618.  
  619.  
  620.  
  621.  
  622.  
  623.  
  624.  
  625.  
  626.  
  627.