## modules/ud/ud_comrol.c

/* [<][>][^][v][top][bottom][index][help] */

## FUNCTIONS

This source file includes following functions.

1. rollback
2. commit
3. delete

       1 /***************************************
       2   $Revision: 1.13 $
       3
       4   rollback(), commit(), delete() - rollback, commit update transaction, delete an object
       5
       6   Status: NOT REVUED, NOT TESTED
       7
       8  Author(s):       Andrei Robachevsky
       9
      10   ******************/ /******************
      11   Modification History:
      12         andrei (17/01/2000) Created.
      13   ******************/ /******************
      14   Copyright (c) 2000                              RIPE NCC
      15
      16   All Rights Reserved
      17
      18   Permission to use, copy, modify, and distribute this software and its
      19   documentation for any purpose and without fee is hereby granted,
      20   provided that the above copyright notice appear in all copies and that
      21   both that copyright notice and this permission notice appear in
      22   supporting documentation, and that the name of the author not be
      23   used in advertising or publicity pertaining to distribution of the
      24   software without specific, written prior permission.
      25
      26   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
      27   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
      28   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
      29   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
      30   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
      31   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
      32  ***************************************/
      33 #include "ud.h"
      34 #include "ud_int.h"
      35 #include "ud_comrol.h"
      36 #include "rp.h"
      37
      38 #define RIPE_REG 17
      39
      40 /************************************************************
      41 * int rollback(Transaction_t *tr)
      42 * Rollback the transaction
      43 *
      44 * **********************************************************/
      45 int rollback(Transaction_t *tr) {
         /* [<][>][^][v][top][bottom][index][help] */
      46 GString *query;
      47 long sequence_id;
      48 int i, j;
      49 int sql_err;
      50
      51  if(ACT_DELETE(tr->action)) return(0);
      52
      53  if ((query = g_string_sized_new(STR_XXL)) == NULL){
      54    fprintf(stderr, "E: cannot allocate gstring\n");
      55    tr->succeeded=0;
      56    tr->error |= ERROR_U_MEM;
      57    return(ERROR_U_MEM); }
      58
      59 /* Lock all relevant tables */
      60    g_string_sprintf(query, "LOCK TABLES %s WRITE,",  DF_get_class_sql_table(tr->class_type));
      61
      62     for (i=0; tables[tr->class_type][i] != NULL; i++)
      63       g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
      64
      65     for (i=TAB_START; tables[tr->class_type][i] != NULL; i++)
      66       g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
      67
      68     g_string_sprintfa(query, " last WRITE, history WRITE ");
      69
      70     sql_err=SQ_execute_query(tr->sql_connection, query->str, NULL);
      71
      72     /*fprintf(stderr,"%s\n", query->str);*/
      73
      74
      75 /* Process AUX and LEAF tables */
      76     for (i=TAB_START; tables[tr->class_type][i] != NULL; i++) {
      77     /* Delete what has been inserted */
      78     g_string_sprintf(query, "DELETE FROM %s WHERE object_id=%ld AND thread_id=%d", tables[tr->class_type][i], tr->object_id, tr->thread_ins);
      79     sql_err=SQ_execute_query(tr->sql_connection, query->str, NULL);
      80
      81     /* Normalize what has been updated/touched */
      82     g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE object_id=%ld AND thread_id=%d", tables[tr->class_type][i], tr->object_id, tr->thread_upd);
      83     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
      84   }
      85
      86 /* Process MAIN tables */
      87     g_string_sprintf(query, "DELETE FROM %s WHERE  object_id=%ld AND thread_id=%d",
      88                              DF_get_class_sql_table(tr->class_type), tr->object_id, tr->thread_ins);
      89     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
      90
      91     /* This is needed only for objects with dummies, as they are updated with TR_UPDATE */
      92     /* We use this tag when commiting the update to set dummy==0 */
      93     /* XXX may be later this should be reconsidered */
      94     g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE  object_id=%ld AND thread_id=%d",
      95                              DF_get_class_sql_table(tr->class_type), tr->object_id, tr->thread_upd);
      96     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
      97
      98 /* Now tables  that might be affected by dummies */
      99     for(j=0; j < tr->ndummy; j++)
     100     for (i=0; tables[tr->class_type][i] != NULL; i++) {
     101         g_string_sprintf(query, "DELETE FROM %s WHERE object_id=%ld ", tables[tr->class_type][i], tr->dummy_id[j]);
     102         sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     103     }
     104
     105 /* Rollback last and history tables */
     106     if(ACT_UPDATE(tr->action)) { /* so we are updating an object */
     107    g_string_sprintf(query, "DELETE FROM history WHERE object_id=%ld AND sequence_id=%ld", tr->object_id, tr->sequence_id-1);
     108    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     109    /* we do not need to delete a row in the last for updates    */
     110   }
     111     else { /* we failed to create an object */
     112       sequence_id=1; /* sequence start == 1 */
     113    g_string_sprintf(query, "DELETE FROM last WHERE object_id=%ld AND sequence_id=%ld", tr->object_id, sequence_id);
     114    sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     115   }
     116
     117
     118     for(j=0; j < tr->ndummy; j++){/* if dummies have been created */
     119          g_string_sprintf(query, "DELETE FROM last WHERE object_id=%ld ", tr->dummy_id[j]);
     120          sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     121   }
     122
     123   /* Unlock all tables */
     124   g_string_sprintf(query, "UNLOCK TABLES ");
     125   sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     126
     127
     128   g_string_free(query, TRUE);
     129   return(0);
     130 } /* rollback() */
     131
     132 /************************************************************
     133 * int commit(Transaction_t *tr)
     134 * Commit the transaction
     135 *
     136 * **********************************************************/
     137 int commit(Transaction_t *tr) {
         /* [<][>][^][v][top][bottom][index][help] */
     138 GString *query;
     139 int err=0;
     140 int i,j;
     141 A_Type_t attr_type;
     142 int sql_err;
     143
     144 if(ACT_DELETE(tr->action)) return(0);
     145
     146  if ((query = g_string_sized_new(STR_XXL)) == NULL){
     147    fprintf(stderr, "E: cannot allocate gstring\n");
     148    tr->succeeded=0;
     149    tr->error|=ERROR_U_MEM;
     150    return(ERROR_U_MEM);
     151  }
     152
     153 /* Lock all relevant tables */
     154     g_string_sprintf(query, "LOCK TABLES %s WRITE,", DF_get_class_sql_table(tr->class_type));
     155
     156     for (i=0; tables[tr->class_type][i] != NULL; i++)
     157       g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
     158
     159     for (i=TAB_START; tables[tr->class_type][i] != NULL; i++)
     160       g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
     161
     162     g_string_sprintfa(query, " last WRITE, history WRITE ");
     163
     164     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     165
     166 /* fprintf(stderr,"%s\n", query->str); */
     167
     168 /* Commit the transaction for AUX and LEAF tables that may be affected (taken from object template) */
     169   for (i=TAB_START; tables[tr->class_type][i] != NULL; i++) {
     170  /* Delete old records from the tables */
     171     g_string_sprintf(query, "DELETE FROM %s WHERE object_id=%ld AND thread_id=0 ", tables[tr->class_type][i], tr->object_id);
     172     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     173     /*    fprintf(stderr, "D: query (del old): %s\n", query->str);  */
     174
     175  /* Set thread_id to 0 to commit the transaction */
     176     g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE object_id=%ld", tables[tr->class_type][i], tr->object_id);
     177     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     178     /*    fprintf(stderr, "D: query (com new): %s\n", query->str); */
     179   }
     180
     181 /* Commit the transaction for the MAIN tables */
     182
     183 /* Commit the transaction for person_role, mntner, as_set, route_set tables */
     184 /* They require different handling because of dummies */
     185 /* The rule is: Update: dummy->0, Insert: preserve dummy value */
     186 /* These tables do not require deletions since we cannot have such condition (object_id==0 AND thread_id==0) */
     187  if((tr->class_type==C_PN) || (tr->class_type==C_RO) ||
     188    (tr->class_type==C_AS) || (tr->class_type==C_RS) ||
     189    (tr->class_type==C_MT)){
     190
     191  /* Process the rows updated/touched */
     192     g_string_sprintf(query, "UPDATE %s SET thread_id=0, dummy=0 WHERE object_id=%ld AND thread_id=%d ",  DF_get_class_sql_table(tr->class_type), tr->object_id, tr->thread_upd);
     193     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     194  }
     195
     196  switch (tr->class_type) {
     197    case C_IR:
     198    case C_IN:
     199    case C_I6:
     200    case C_FS:
     201     if((tr->save)){ /* Some special processing for tables with the second attribute */
     202      /* Update the second field of the table with query like one below */
     203      /* UPDATE %s SET thread_id=%d, local_as='%s' WHERE object_id=%ld */
     204
     205      switch(tr->class_type) {
     206       /* Local-as for inet-rtr */
     207       case C_IR: attr_type=A_LA;
     208                  break;
     209       /* netname for inetnum and inet6num */
     210       case C_IN:
     211       case C_I6: attr_type=A_NA;
     212                  break;
     213       /* filter for filter-set */
     214       case C_FS: attr_type=A_FI;
     215                  break;
     216       default:
     217                  die;
     218                  break;
     219      }
     220      g_string_sprintf(query, DF_get_update_query(attr_type), DF_get_class_sql_table(tr->class_type), 0, (char *)tr->save, tr->object_id);
     221      sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     222     }
     223     else die;
     224     break;
     225
     226    default:
     227  /* Process all other MAIN tables for updates/inserts and person_role, mntner, as_set, route_set tables for rows inserts */
     228     g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE object_id=%ld AND thread_id>0", DF_get_class_sql_table(tr->class_type), tr->object_id);
     229     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     230     break;
     231  }
     232
     233
     234 /* for tables that might be affected by dummies */
     235  for(j=0; j < tr->ndummy; j++)/* if dummies have been created */
     236    for (i=0; tables[tr->class_type][i] != NULL; i++) {
     237     g_string_sprintf(query, "UPDATE %s SET thread_id=0 WHERE object_id=%ld ", tables[tr->class_type][i], tr->dummy_id[j]);
     238     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     239  }
     240
     241
     242    for(j=0; j < tr->ndummy; j++){/* if dummies have been created*/
     243          g_string_sprintf(query, "UPDATE last SET thread_id=0 WHERE object_id=%ld ", tr->dummy_id[j]);
     244          sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     245   }
     246
     247  /* Unlock all tables */
     248  g_string_sprintf(query, "UNLOCK TABLES ");
     249  sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     250
     251  /* Update radix tree for route, inetnum and inaddr-arpa domain*/
     252  if(tr->standalone==0) { /* only if server*/
     253
     254  /* Create a radix node for the object */
     255    if( (   (tr->class_type==C_RT)
     256         || (tr->class_type==C_IN)
     257         || (tr->class_type==C_I6)
     258         || (tr->class_type==C_DN))
     259        && (tr->packptr)) {
     260      rp_upd_pack_t *packptr = tr->packptr;
     261
     262      packptr->key = tr->object_id;
     263
     264      if( RP_pack_node(RX_OPER_CRE, packptr, RIPE_REG) == RX_OK ) {
     265        err = 0;
     266      } else {
     267        err = (-1) ;
     268      }
     269    }
     270    /* XXX Check for errors */
     271  }
     272
     273   g_string_free(query, TRUE);
     274   return(err);
     275 } /* commit() */
     276
     277
     278
     279 /******************************************************
     280 *int delete(Transaction_t *tr)
     281 * Delete the object
     282 *
     283 *****************************************************/
     284 int delete(Transaction_t *tr)
         /* [<][>][^][v][top][bottom][index][help] */
     285 {
     286 GString *query;
     287 int err=0;
     288 int i;
     289 int num;
     290 long ref_id;
     291 long num_rec;
     292 long timestamp;
     293
     294 char sobject_id[STR_M];
     295 char *sql_str;
     296 int sql_err;
     297
     298
     299  /* Try to allocate g_string. Return on error */
     300  if ((query = g_string_sized_new(STR_XXL)) == NULL){
     301    fprintf(stderr, "E: cannot allocate gstring\n");
     302    tr->succeeded=0;
     303    tr->error|=ERROR_U_MEM;
     304    return(ERROR_U_MEM);
     305  }
     306
     307
     308 /* Check for referential integrity of deletion */
     309
     310    sprintf(sobject_id, "%ld", tr->object_id);
     311
     312    switch(tr->class_type){
     313     case C_PN:
     314     case C_RO:
     315
     316        if(tr->dummy==1)break; /* This is if the override exists */
     317
     318        /* Check that this person/role object is not referenced */
     319
     320        for (i=0; t_ipn[i] != NULL; i++) {
     321         /* Calculate number of references */
     322         sql_str= get_field_str(tr->sql_connection, "COUNT(*)", t_ipn[i], "pe_ro_id", sobject_id, NULL);
     323         if(sql_str) {
     324          num_rec = atol(sql_str);  free(sql_str);
     325          ref_id=tr->object_id;
     326          /* Check if it is a self reference (for role objects) */
     327          if(num_rec==1) {
     328           sql_str= get_field_str(tr->sql_connection, "object_id", t_ipn[i], "pe_ro_id", sobject_id, NULL);
     329           if(sql_str) {
     330            ref_id = atol(sql_str);  free(sql_str);
     331           } else {
     332            tr->succeeded=0; tr->error |= ERROR_U_DBS; break;
     333           }
     334          }
     335          /* If there are references (and not the only self reference) we cannot delete */
     336          if((num_rec>1) || (ref_id!=tr->object_id)) {
     337            g_string_sprintfa(tr->error_script,"E[%d][%ld]:ref integrity: %s\n" ,ERROR_U_OBJ, num_rec, t_ipn[i]);
     338            tr->succeeded=0; tr->error |= ERROR_U_OBJ;
     339          }
     340         } else {
     341         /* SQL error occured */
     342          tr->succeeded=0; tr->error |= ERROR_U_DBS;
     343          g_string_sprintfa(tr->error_script,"E[%d][%s]:%s\n", ERROR_U_DBS, t_ipn[i], SQ_error(tr->sql_connection));
     344         }
     345        }
     346
     347        /* Check that this person/role object is not referenced by name (legacy stuff) */
     348
     349        for (i=0; t_ipn[i] != NULL; i++) {
     350         /* Calculate number of references */
     351
     352         g_string_sprintf(query, "SELECT COUNT(*) FROM %s, person_role "
     353                                 "WHERE person_role.object_id=%s.pe_ro_id "
     354                                 "AND person_role.nic_hdl='%s' ", t_ipn[i], t_ipn[i], tr->save);
     355
     356         sql_str= get_qresult_str(tr->sql_connection, query->str);
     357         if(sql_str) {
     358          num_rec = atol(sql_str);  free(sql_str);
     359          /* If there are references (no self reference is possible in this case) we cannot delete */
     360          if(num_rec>0) {
     361            g_string_sprintfa(tr->error_script,"E[%d][%ld]:ref integrity: %s\n" ,ERROR_U_OBJ, num_rec, t_ipn[i]);
     362            tr->succeeded=0; tr->error |= ERROR_U_OBJ;
     363          }
     364         } else {
     365         /* SQL error occured */
     366          tr->succeeded=0; tr->error |= ERROR_U_DBS;
     367          g_string_sprintfa(tr->error_script,"E[%d][%s]:%s\n", ERROR_U_DBS, t_ipn[i], SQ_error(tr->sql_connection));
     368         }
     369        }
     370
     371        break;
     372
     373     case C_MT:
     374
     375         if(tr->dummy==1)break; /* This is if the override exists */
     376
     377         /* Check that this mntner object is not referenced */
     378
     379        for (i=0; t_imt[i] != NULL; i++) {
     380        /* Calculate number of references */
     381         sql_str= get_field_str(tr->sql_connection, "COUNT(*)", t_imt[i], "mnt_id", sobject_id, NULL);
     382         if(sql_str) {
     383          num_rec = atol(sql_str);  free(sql_str);
     384          ref_id=tr->object_id;
     385          /* Check if it is a self reference  */
     386          if(num_rec==1) {
     387             sql_str= get_field_str(tr->sql_connection, "object_id", t_imt[i], "mnt_id", sobject_id, NULL);
     388             if(sql_str) {
     389               ref_id = atol(sql_str);  free(sql_str);
     390             } else {
     391               tr->succeeded=0; tr->error |= ERROR_U_DBS; break;
     392             }
     393          }
     394          /* If there are references (and not the only self reference) we cannot delete */
     395          if((num_rec>1) || (ref_id!=tr->object_id)) {
     396            g_string_sprintfa(tr->error_script,"E[%d][%ld]:ref integrity: %s\n" ,ERROR_U_OBJ, num_rec, t_imt[i]);
     397            tr->succeeded=0; tr->error |= ERROR_U_OBJ;
     398          }
     399         } else {
     400          tr->succeeded=0; tr->error |= ERROR_U_DBS;
     401         }
     402        }
     403        break;
     404
     405     case C_RS:
     406     case C_AS:
     407         /* Check that this set object is not referenced */
     408         /* Calculate number of references */
     409         sql_str= get_field_str(tr->sql_connection, "COUNT(*)", "member_of", "set_id", sobject_id, NULL);
     410         if(sql_str) {
     411          num_rec = atol(sql_str);  free(sql_str);
     412          /* XXX though set may contain other sets as memebers, */
     413          /* there is no member-of attribute in these objects. */
     414          /* So no self-reference is possible */
     415          if(num_rec!=0) {
     416            g_string_sprintfa(tr->error_script,"E[%d][%ld]:ref integrity: %s\n" ,ERROR_U_OBJ, num_rec, "member_of");
     417            tr->succeeded=0; tr->error |= ERROR_U_OBJ;
     418           /* XXX Do not refuse the transaction but change the object to dummy */
     419          }
     420         } else {
     421          tr->succeeded=0; tr->error |= ERROR_U_DBS;
     422         }
     423         break;
     424
     425     default:
     426         break;
     427    }
     428
     429  /* Check if we have passed referential integrity check */
     430  if(tr->succeeded==0){
     431        return(-1);
     432  }
     433
     434
     435 /* Lock all relevant tables */
     436     g_string_sprintf(query, "LOCK TABLES %s WRITE,", DF_get_class_sql_table(tr->class_type));
     437
     438     for (i=0; tables[tr->class_type][i] != NULL; i++)
     439       g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
     440
     441     for (i=TAB_START; tables[tr->class_type][i] != NULL; i++)
     442       g_string_sprintfa(query, " %s WRITE,", tables[tr->class_type][i]);
     443
     444     g_string_sprintfa(query, " last WRITE, history WRITE ");
     445
     446     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     447
     448     for (i=TAB_START; tables[tr->class_type][i] != NULL; i++) {
     449     g_string_sprintf(query, "DELETE FROM %s WHERE object_id=%ld ", tables[tr->class_type][i], tr->object_id);
     450     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     451     /*    fprintf(stderr, "D: query (delete): %s\n", query->str);*/
     452   }
     453
     454 /* Process the MAIN table  */
     455     g_string_sprintf(query, "DELETE FROM %s WHERE object_id=%ld ", DF_get_class_sql_table(tr->class_type), tr->object_id);
     456     sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     457
     458 /* Update the history table */
     459     g_string_sprintf(query,     "INSERT history "
     460                                 "SELECT 0, object_id, sequence_id, timestamp, object_type, object "
     461                                 "FROM last "
     462                                 "WHERE object_id=%ld ", tr->object_id);
     463
     464
     465   sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     466   if (sql_err) {
     467          fprintf(stderr, "E ERROR!<perform_update>: INSERT history failed:[%d][%s]\n", num, query->str);
     468          tr->succeeded=0;
     469          tr->error |=ERROR_U_DBS;
     470   }
     471
     472   /* get sequence number */
     473   tr->sequence_id = get_sequence_id(tr);
     474   tr->sequence_id++;
     475
     476   /* insert new version into the last */
     477   timestamp=time(NULL);
     478
     479  /* empty the contents, but leave in the table to restrict re-use of object_id */
     480   g_string_sprintf(query, "UPDATE last SET object='', timestamp=%ld  WHERE object_id=%ld ", timestamp, tr->object_id);
     481
     482   sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     483   if (sql_err) {
     484         fprintf(stderr, "E ERROR!<perform_update>: UPDATE last failed: [%d][%s]\n", num, query->str);
     485          tr->succeeded=0;
     486          tr->error |= ERROR_U_DBS;
     487   }
     488
     489
     490   /* Do more in the forest
     491    * Update radix tree for route and inetnum
     492    */
     493   if(tr->standalone==0) { /* only if server */
     494   /* Collect some data for radix tree and NH repository update */
     495     g_slist_foreach((tr->object)->attributes, get_rx_data, tr);
     496
     497     /* Only for these types of objects and only if we have collected data (tr->save != NULL) */
     498     if( (   (tr->class_type==C_RT)
     499          || (tr->class_type==C_IN)
     500          || (tr->class_type==C_I6)
     501          || (tr->class_type==C_DN))
     502         && (tr->packptr)) {
     503       rp_upd_pack_t *packptr = tr->packptr;
     504
     505       packptr->key = tr->object_id;
     506       if( RP_pack_node(RX_OPER_DEL, packptr, RIPE_REG) == RX_OK ) {
     507         err = 0;
     508       } else {
     509         err = (-1) ;
     510       }
     511     }
     512   }
     513
     514  /* Unlock all tables */
     515   g_string_sprintf(query, "UNLOCK TABLES ");
     516   sql_err = SQ_execute_query(tr->sql_connection, query->str, (SQ_result_set_t **)NULL);
     517
     518   g_string_free(query, TRUE);
     519
     520   return(err);
     521
     522 } /* delete() */

/* [<][>][^][v][top][bottom][index][help] */