NCBI C++ ToolKit
blk.c
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* FreeTDS - Library of routines accessing Sybase and Microsoft databases
2  * Copyright (C) 1998-2004, 2005, 2010 Brian Bruns, Bill Thompson
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 
20 #include <config.h>
21 
22 #include <stdarg.h>
23 #include <stdio.h>
24 
25 #if HAVE_STDLIB_H
26 #include <stdlib.h>
27 #endif /* HAVE_STDLIB_H */
28 
29 #if HAVE_STRING_H
30 #include <string.h>
31 #endif /* HAVE_STRING_H */
32 
33 #include <freetds/utils.h>
34 #include <freetds/replacements.h>
35 
36 #include "bkpublic.h"
37 
38 #include "ctpublic.h"
39 #include "ctlib.h"
40 
41 static void _blk_null_error(TDSBCPINFO *bcpinfo, int index, int offset);
42 static TDSRET _blk_get_col_data(TDSBCPINFO *bulk, TDSCOLUMN *bcpcol, int offset);
43 static CS_RETCODE _blk_rowxfer_in(CS_BLKDESC * blkdesc, CS_INT rows_to_xfer, CS_INT * rows_xferred);
44 static CS_RETCODE _blk_rowxfer_out(CS_BLKDESC * blkdesc, CS_INT rows_to_xfer, CS_INT * rows_xferred);
45 static void _blk_clean_desc(CS_BLKDESC * blkdesc);
46 
47 #define CONN(bulk) ((CS_CONNECTION *) (bulk)->bcpinfo.parent)
48 
49 TDS_COMPILE_CHECK(same_size, sizeof(CS_BLKDESC) == sizeof(TDSBCPINFO));
50 TDS_COMPILE_CHECK(nested_type, TDS_OFFSET(CS_BLKDESC, bcpinfo) == 0);
51 
53 blk_alloc(CS_CONNECTION * connection, CS_INT version, CS_BLKDESC ** blk_pointer)
54 {
55  CS_BLKDESC *blkdesc;
56 
57  tdsdump_log(TDS_DBG_FUNC, "blk_alloc(%p, %d, %p)\n", connection, version, blk_pointer);
58 
59  if (!connection || !connection->tds_socket)
60  return CS_FAIL;
61 
62  if (connection->tds_socket->conn->tds_version < 0x500)
63  return CS_FAIL;
64 
65  blkdesc = (CS_BLKDESC *) tds_alloc_bcpinfo();
66  if (!blkdesc)
67  return CS_FAIL;
68 
69  /* so we know who we belong to */
70  blkdesc->bcpinfo.parent = connection;
71 
72  *blk_pointer = blkdesc;
73  return CS_SUCCEED;
74 }
75 
76 
78 blk_bind(CS_BLKDESC * blkdesc, CS_INT item, CS_DATAFMT * datafmt_arg, CS_VOID * buffer, CS_INT * datalen, CS_SMALLINT * indicator)
79 {
80  TDSCOLUMN *colinfo;
81  CS_CONNECTION *con;
82  CS_INT bind_count;
83  const CS_DATAFMT_COMMON * datafmt;
84  int i;
85 
86  tdsdump_log(TDS_DBG_FUNC, "blk_bind(%p, %d, %p, %p, %p, %p)\n", blkdesc, item, datafmt_arg, buffer, datalen, indicator);
87 
88  if (!blkdesc)
89  return CS_FAIL;
90 
91  con = CONN(blkdesc);
92 
93  datafmt = _ct_datafmt_common(con->ctx, datafmt_arg);
94 
95  if (item == CS_UNUSED) {
96  /* clear all bindings */
97  if (datafmt == NULL && buffer == NULL && datalen == NULL && indicator == NULL ) {
98  blkdesc->bcpinfo.bind_count = CS_UNUSED;
99  for (i = 0; i < blkdesc->bcpinfo.bindinfo->num_cols; i++ ) {
100  colinfo = blkdesc->bcpinfo.bindinfo->columns[i];
101  colinfo->column_varaddr = NULL;
102  colinfo->column_bindtype = 0;
103  colinfo->column_bindfmt = 0;
104  colinfo->column_bindlen = 0;
105  colinfo->column_nullbind = NULL;
106  colinfo->column_lenbind = NULL;
107  }
108  }
109  return CS_SUCCEED;
110  }
111 
112  /* check item value */
113 
114  if (item < 1 || item > blkdesc->bcpinfo.bindinfo->num_cols) {
115  _ctclient_msg(con, "blk_bind", 2, 5, 1, 141, "%s, %d", "colnum", item);
116  return CS_FAIL;
117  }
118 
119  /* clear bindings for this column */
120 
121  if (datafmt == NULL && buffer == NULL && datalen == NULL && indicator == NULL ) {
122 
123  colinfo = blkdesc->bcpinfo.bindinfo->columns[item - 1];
124  colinfo->column_varaddr = NULL;
125  colinfo->column_bindtype = 0;
126  colinfo->column_bindfmt = 0;
127  colinfo->column_bindlen = 0;
128  colinfo->column_nullbind = NULL;
129  colinfo->column_lenbind = NULL;
130 
131  return CS_SUCCEED;
132  }
133 
134  if (datafmt == NULL)
135  return CS_FAIL;
136 
137  /*
138  * check whether the request is for array binding and ensure that user
139  * supplies the same datafmt->count to the subsequent ct_bind calls
140  */
141 
142  bind_count = (datafmt->count == 0) ? 1 : datafmt->count;
143 
144  /* first bind for this result set */
145 
146  if (blkdesc->bcpinfo.bind_count == CS_UNUSED) {
147  blkdesc->bcpinfo.bind_count = bind_count;
148  } else {
149  /* all subsequent binds for this result set - the bind counts must be the same */
150  if (blkdesc->bcpinfo.bind_count != bind_count) {
151  _ctclient_msg(con, "blk_bind", 1, 1, 1, 137, "%d, %d", bind_count, blkdesc->bcpinfo.bind_count);
152  return CS_FAIL;
153  }
154  }
155 
156  /* bind the column_varaddr to the address of the buffer */
157 
158  colinfo = blkdesc->bcpinfo.bindinfo->columns[item - 1];
159 
160  colinfo->column_varaddr = (char *) buffer;
161  colinfo->column_bindtype = datafmt->datatype;
162  colinfo->column_bindfmt = datafmt->format;
163  colinfo->column_bindlen = datafmt->maxlength;
164  if (indicator) {
165  colinfo->column_nullbind = indicator;
166  }
167  if (datalen) {
168  colinfo->column_lenbind = datalen;
169  }
170  return CS_SUCCEED;
171 }
172 
174 blk_colval(SRV_PROC * srvproc, CS_BLKDESC * blkdescp, CS_BLK_ROW * rowp, CS_INT colnum, CS_VOID * valuep, CS_INT valuelen,
175  CS_INT * outlenp)
176 {
177 
178  tdsdump_log(TDS_DBG_FUNC, "blk_colval(%p, %p, %p, %d, %p, %d, %p)\n",
179  srvproc, blkdescp, rowp, colnum, valuep, valuelen, outlenp);
180 
181  tdsdump_log(TDS_DBG_FUNC, "UNIMPLEMENTED blk_colval()\n");
182  return CS_FAIL;
183 }
184 
186 blk_default(CS_BLKDESC * blkdesc, CS_INT colnum, CS_VOID * buffer, CS_INT buflen, CS_INT * outlen)
187 {
188 
189  tdsdump_log(TDS_DBG_FUNC, "blk_default(%p, %d, %p, %d, %p)\n", blkdesc, colnum, buffer, buflen, outlen);
190 
191  tdsdump_log(TDS_DBG_FUNC, "UNIMPLEMENTED blk_default()\n");
192  return CS_FAIL;
193 }
194 
196 blk_describe(CS_BLKDESC * blkdesc, CS_INT item, CS_DATAFMT * datafmt_arg)
197 {
198  TDSCOLUMN *curcol;
199  CS_INT status, datatype;
200  CS_DATAFMT_LARGE *datafmt;
201  CS_DATAFMT_LARGE datafmt_buf;
202 
203  tdsdump_log(TDS_DBG_FUNC, "blk_describe(%p, %d, %p)\n", blkdesc, item, datafmt_arg);
204 
205  if (!blkdesc)
206  return CS_FAIL;
207 
208  datafmt = _ct_datafmt_conv_prepare(CONN(blkdesc)->ctx, datafmt_arg, &datafmt_buf);
209 
210  if (item < 1 || item > blkdesc->bcpinfo.bindinfo->num_cols) {
211  _ctclient_msg(CONN(blkdesc), "blk_describe", 2, 5, 1, 141, "%s, %d", "colnum", item);
212  return CS_FAIL;
213  }
214 
215  curcol = blkdesc->bcpinfo.bindinfo->columns[item - 1];
216  /* name is always null terminated */
217  strlcpy(datafmt->name, tds_dstr_cstr(&curcol->column_name), sizeof(datafmt->name));
218  datafmt->namelen = (CS_INT) strlen(datafmt->name);
219  /* need to turn the SYBxxx into a CS_xxx_TYPE */
220  datatype = _ct_get_client_type(curcol, true);
221  if (datatype == CS_ILLEGAL_TYPE)
222  return CS_FAIL;
223  datafmt->datatype = datatype;
224  tdsdump_log(TDS_DBG_INFO1, "blk_describe() datafmt->datatype = %d server type %d\n", datatype,
225  curcol->column_type);
226  /* FIXME is ok this value for numeric/decimal? */
227  datafmt->maxlength = curcol->column_size;
228  datafmt->usertype = curcol->column_usertype;
229  datafmt->precision = curcol->column_prec;
230  datafmt->scale = curcol->column_scale;
231 
232  /*
233  * There are other options that can be returned, but these are the
234  * only two being noted via the TDS layer.
235  */
236  status = 0;
237  if (curcol->column_nullable)
238  status |= CS_CANBENULL;
239  if (curcol->column_identity)
240  status |= CS_IDENTITY;
241  datafmt->status = status;
242 
243  datafmt->count = 1;
244  datafmt->locale = NULL;
245 
246  _ct_datafmt_conv_back(datafmt_arg, datafmt);
247  return CS_SUCCEED;
248 }
249 
251 blk_done(CS_BLKDESC * blkdesc, CS_INT type, CS_INT * outrow)
252 {
253  TDSSOCKET *tds;
254  int rows_copied;
255 
256  tdsdump_log(TDS_DBG_FUNC, "blk_done(%p, %d, %p)\n", blkdesc, type, outrow);
257 
258  tds = CONN(blkdesc)->tds_socket;
259 
260  switch (type) {
261  case CS_BLK_BATCH:
262  if (TDS_FAILED(tds_bcp_done(tds, &rows_copied))) {
263  _ctclient_msg(CONN(blkdesc), "blk_done", 2, 5, 1, 140, "");
264  return CS_FAIL;
265  }
266 
267  if (outrow)
268  *outrow = rows_copied;
269 
270  if (TDS_FAILED(tds_bcp_start(tds, &blkdesc->bcpinfo))) {
271  _ctclient_msg(CONN(blkdesc), "blk_done", 2, 5, 1, 140, "");
272  return CS_FAIL;
273  }
274  break;
275 
276  case CS_BLK_ALL:
277  if (TDS_FAILED(tds_bcp_done(tds, &rows_copied))) {
278  _ctclient_msg(CONN(blkdesc), "blk_done", 2, 5, 1, 140, "");
279  return CS_FAIL;
280  }
281 
282  if (outrow)
283  *outrow = rows_copied;
284 
285  /* free allocated storage in blkdesc & initialise flags, etc. */
286  _blk_clean_desc(blkdesc);
287  break;
288 
289  case CS_BLK_CANCEL:
290  tds->out_pos = 8; /* discard staged query data */
291  /* Can't transition directly from SENDING to PENDING. */
294 
296 
298  _ctclient_msg(CONN(blkdesc), "blk_done", 2, 5, 1, 140,
299  "");
300  return CS_FAIL;
301  }
302 
303  if (outrow)
304  *outrow = 0;
305 
306  /* free allocated storage in blkdesc & initialise flags, etc. */
307  _blk_clean_desc(blkdesc);
308  break;
309  }
310 
311  return CS_SUCCEED;
312 }
313 
314 static void _blk_clean_desc (CS_BLKDESC * blkdesc)
315 {
316  if (blkdesc->bcpinfo.hint) {
317  /*
318  * hint is formally const, so TDS_ZERO_FREE would yield
319  * a warning.
320  */
321  free((char*)blkdesc->bcpinfo.hint);
322  blkdesc->bcpinfo.hint = NULL;
323  }
324 
325  tds_deinit_bcpinfo(&blkdesc->bcpinfo);
326 
327  blkdesc->bcpinfo.direction = 0;
328  blkdesc->bcpinfo.bind_count = CS_UNUSED;
329  blkdesc->bcpinfo.xfer_init = 0;
330  blkdesc->bcpinfo.text_sent = 0;
331  blkdesc->bcpinfo.next_col = 0;
332  blkdesc->bcpinfo.blob_cols = 0;
333 }
334 
337 {
338  tdsdump_log(TDS_DBG_FUNC, "blk_drop(%p)\n", blkdesc);
339 
340  /* this is possible as CS_BLKDESC contains just bcpinfo field */
341  tds_free_bcpinfo(&blkdesc->bcpinfo);
342 
343  return CS_SUCCEED;
344 }
345 
347 blk_getrow(SRV_PROC * srvproc, CS_BLKDESC * blkdescp, CS_BLK_ROW * rowp)
348 {
349  tdsdump_log(TDS_DBG_FUNC, "blk_getrow(%p, %p, %p)\n", srvproc, blkdescp, rowp);
350 
351  tdsdump_log(TDS_DBG_FUNC, "UNIMPLEMENTED blk_getrow()\n");
352  return CS_FAIL;
353 }
354 
356 blk_gettext(SRV_PROC * srvproc, CS_BLKDESC * blkdescp, CS_BLK_ROW * rowp, CS_INT bufsize, CS_INT * outlenp)
357 {
358 
359  tdsdump_log(TDS_DBG_FUNC, "blk_gettext(%p, %p, %p, %d, %p)\n", srvproc, blkdescp, rowp, bufsize, outlenp);
360 
361  tdsdump_log(TDS_DBG_FUNC, "UNIMPLEMENTED blk_gettext()\n");
362  return CS_FAIL;
363 }
364 
366 blk_init(CS_BLKDESC * blkdesc, CS_INT direction, CS_CHAR * tablename, CS_INT tnamelen)
367 {
368  tdsdump_log(TDS_DBG_FUNC, "blk_init(%p, %d, %p, %d)\n", blkdesc, direction, tablename, tnamelen);
369 
370  if (!blkdesc) {
371  return CS_FAIL;
372  }
373 
374  if (direction != CS_BLK_IN && direction != CS_BLK_OUT ) {
375  _ctclient_msg(CONN(blkdesc), "blk_init", 2, 6, 1, 138, "");
376  return CS_FAIL;
377  }
378 
379  if (!tablename) {
380  _ctclient_msg(CONN(blkdesc), "blk_init", 2, 6, 1, 139, "");
381  return CS_FAIL;
382  }
383  if (tnamelen == CS_NULLTERM)
384  tnamelen = (CS_INT) strlen(tablename);
385 
386  /* free allocated storage in blkdesc & initialise flags, etc. */
387  tds_deinit_bcpinfo(&blkdesc->bcpinfo);
388 
389  /* string can be no-nul terminated so copy with memcpy */
390  if (!tds_dstr_copyn(&blkdesc->bcpinfo.tablename, tablename, tnamelen)) {
391  return CS_FAIL;
392  }
393 
394  blkdesc->bcpinfo.direction = direction;
395  blkdesc->bcpinfo.bind_count = CS_UNUSED;
396  blkdesc->bcpinfo.xfer_init = 0;
397 
398  if (TDS_FAILED(tds_bcp_init(CONN(blkdesc)->tds_socket, &blkdesc->bcpinfo))) {
399  _ctclient_msg(CONN(blkdesc), "blk_init", 2, 5, 1, 140, "");
400  return CS_FAIL;
401  }
402  blkdesc->bcpinfo.bind_count = CS_UNUSED;
403 
404  return CS_SUCCEED;
405 }
406 
408 blk_props(CS_BLKDESC * blkdesc, CS_INT action, CS_INT property, CS_VOID * buffer, CS_INT buflen, CS_INT * outlen)
409 {
410  int retval, intval;
411 
412  tdsdump_log(TDS_DBG_FUNC, "blk_props(%p, %d, %d, %p, %d, %p)\n", blkdesc, action, property, buffer, buflen, outlen);
413 
414  switch (property) {
415  case BLK_IDENTITY:
416  switch (action) {
417  case CS_SET:
418  if (buffer) {
419  memcpy(&intval, buffer, sizeof(intval));
420  if (intval == CS_TRUE)
421  blkdesc->bcpinfo.identity_insert_on = 1;
422  if (intval == CS_FALSE)
423  blkdesc->bcpinfo.identity_insert_on = 0;
424  }
425  return CS_SUCCEED;
426  break;
427  case CS_GET:
428  retval = blkdesc->bcpinfo.identity_insert_on == 1 ? CS_TRUE : CS_FALSE ;
429  if (buffer) {
430  memcpy (buffer, &retval, sizeof(retval));
431  if (outlen)
432  *outlen = sizeof(retval);
433  }
434  return CS_SUCCEED;
435  break;
436  default:
437  _ctclient_msg(CONN(blkdesc), "blk_props", 2, 5, 1, 141, "%s, %d", "action", action);
438  break;
439  }
440  break;
441 
442  default:
443  _ctclient_msg(CONN(blkdesc), "blk_props", 2, 5, 1, 141, "%s, %d", "property", property);
444  break;
445  }
446  return CS_FAIL;
447 }
448 
451 {
452  tdsdump_log(TDS_DBG_FUNC, "blk_rowalloc(%p, %p)\n", srvproc, row);
453 
454  tdsdump_log(TDS_DBG_FUNC, "UNIMPLEMENTED blk_rowalloc()\n");
455  return CS_FAIL;
456 }
457 
460 {
461  tdsdump_log(TDS_DBG_FUNC, "blk_rowdrop(%p, %p)\n", srvproc, row);
462 
463  tdsdump_log(TDS_DBG_FUNC, "UNIMPLEMENTED blk_rowdrop()\n");
464  return CS_FAIL;
465 }
466 
469 {
470  tdsdump_log(TDS_DBG_FUNC, "blk_rowxfer(%p)\n", blkdesc);
471 
472  return blk_rowxfer_mult(blkdesc, NULL);
473 }
474 
476 blk_rowxfer_mult(CS_BLKDESC * blkdesc, CS_INT * row_count)
477 {
478  CS_INT rows_to_xfer = 0;
479  CS_INT rows_xferred = 0;
480  CS_RETCODE ret;
481 
482  tdsdump_log(TDS_DBG_FUNC, "blk_rowxfer_mult(%p, %p)\n", blkdesc, row_count);
483 
484  if (!row_count || *row_count == 0 )
485  rows_to_xfer = blkdesc->bcpinfo.bind_count;
486  else
487  rows_to_xfer = *row_count;
488 
489  if (blkdesc->bcpinfo.direction == CS_BLK_IN) {
490  ret = _blk_rowxfer_in(blkdesc, rows_to_xfer, &rows_xferred);
491  } else {
492  ret = _blk_rowxfer_out(blkdesc, rows_to_xfer, &rows_xferred);
493  }
494  if (row_count)
495  *row_count = rows_xferred;
496  return ret;
497 
498 }
499 
502 {
503 
504  tdsdump_log(TDS_DBG_FUNC, "blk_sendrow(%p, %p)\n", blkdesc, row);
505 
506  tdsdump_log(TDS_DBG_FUNC, "UNIMPLEMENTED blk_sendrow()\n");
507  return CS_FAIL;
508 }
509 
512 {
513  tdsdump_log(TDS_DBG_FUNC, "blk_sendtext(%p, %p, %p, %d)\n", blkdesc, row, buffer, buflen);
514 
515  tdsdump_log(TDS_DBG_FUNC, "UNIMPLEMENTED blk_sendtext()\n");
516  return CS_FAIL;
517 }
518 
520 blk_sethints(CS_BLKDESC* blkdesc, CS_CHAR* hints, CS_INT hintslen)
521 {
522  char * h;
523 
524  if (blkdesc == NULL || (h = tds_new(char, hintslen + 1)) == NULL) {
525  return CS_FAIL;
526  }
527 
528  strlcpy(h, hints, hintslen + 1);
529  blkdesc->bcpinfo.hint = h;
530 
531  return CS_SUCCEED;
532 }
533 
535 blk_srvinit(SRV_PROC * srvproc, CS_BLKDESC * blkdescp)
536 {
537  tdsdump_log(TDS_DBG_FUNC, "blk_srvinit(%p, %p)\n", srvproc, blkdescp);
538 
539  tdsdump_log(TDS_DBG_FUNC, "UNIMPLEMENTED blk_srvinit()\n");
540  return CS_FAIL;
541 }
542 
544 blk_textxfer(CS_BLKDESC * blkdesc, CS_BYTE * buffer, CS_INT buflen, CS_INT * outlen)
545 {
546  TDSSOCKET *tds;
547  TDSCOLUMN *bindcol;
548 
549  if (blkdesc == NULL || buffer == NULL) {
550  return CS_FAIL;
551  }
552 
553  tds = CONN(blkdesc)->tds_socket;
554 
555  bindcol = blkdesc->bcpinfo.bindinfo->columns
556  [blkdesc->bcpinfo.next_col-1];
557 
558  if (bindcol->column_varaddr != NULL) {
559  return CS_FAIL;
560  }
561 
562  bindcol->column_cur_size = buflen;
563  bindcol->column_lenbind = &bindcol->column_cur_size;
564  bindcol->column_varaddr = (TDS_CHAR*) buffer;
565 
566  if (TDS_FAILED(tds_bcp_send_record(tds, &blkdesc->bcpinfo,
568  0))) {
569  return CS_FAIL;
570  } else if (blkdesc->bcpinfo.next_col == 0) {
571  return CS_END_DATA; /* all done */
572  } else {
573  bindcol->column_varaddr = NULL;
574  return CS_SUCCEED; /* still need more data */
575  }
576 }
577 
578 static CS_RETCODE
579 _blk_rowxfer_out(CS_BLKDESC * blkdesc, CS_INT rows_to_xfer, CS_INT * rows_xferred)
580 {
581  TDSSOCKET *tds;
582  TDS_INT result_type;
583  TDSRET ret;
584  TDS_INT temp_count;
585 
586  tdsdump_log(TDS_DBG_FUNC, "_blk_rowxfer_out(%p, %d, %p)\n", blkdesc, rows_to_xfer, rows_xferred);
587 
588  if (!blkdesc || !CONN(blkdesc))
589  return CS_FAIL;
590 
591  tds = CONN(blkdesc)->tds_socket;
592 
593  /*
594  * the first time blk_xfer called after blk_init()
595  * do the query and get to the row data...
596  */
597 
598  if (blkdesc->bcpinfo.xfer_init == 0) {
599 
600  if (TDS_FAILED(tds_submit_queryf(tds, "select * from %s", tds_dstr_cstr(&blkdesc->bcpinfo.tablename)))) {
601  _ctclient_msg(CONN(blkdesc), "blk_rowxfer", 2, 5, 1, 140, "");
602  return CS_FAIL;
603  }
604 
605  while ((ret = tds_process_tokens(tds, &result_type, NULL, TDS_TOKEN_RESULTS)) == TDS_SUCCESS) {
606  if (result_type == TDS_ROW_RESULT)
607  break;
608  }
609 
610  if (ret != TDS_SUCCESS || result_type != TDS_ROW_RESULT) {
611  _ctclient_msg(CONN(blkdesc), "blk_rowxfer", 2, 5, 1, 140, "");
612  return CS_FAIL;
613  }
614 
615  blkdesc->bcpinfo.xfer_init = 1;
616  }
617 
618  if (rows_xferred)
619  *rows_xferred = 0;
620 
621  for (temp_count = 0; temp_count < rows_to_xfer; temp_count++) {
622 
624 
625  tdsdump_log(TDS_DBG_FUNC, "blk_rowxfer_out() process_row_tokens returned %d\n", ret);
626 
627  switch (ret) {
628  case TDS_SUCCESS:
629  if (result_type == TDS_ROW_RESULT || result_type == TDS_COMPUTE_RESULT) {
630  if (result_type == TDS_ROW_RESULT) {
631  if (_ct_bind_data( CONN(blkdesc)->ctx, tds->current_results, blkdesc->bcpinfo.bindinfo, temp_count))
632  return CS_ROW_FAIL;
633  if (rows_xferred)
634  *rows_xferred = *rows_xferred + 1;
635  }
636  break;
637  }
638  case TDS_NO_MORE_RESULTS:
639  return CS_END_DATA;
640  break;
641 
642  default:
643  _ctclient_msg(CONN(blkdesc), "blk_rowxfer", 2, 5, 1, 140, "");
644  return CS_FAIL;
645  break;
646  }
647  }
648 
649  return CS_SUCCEED;
650 }
651 
652 static CS_RETCODE
653 _blk_rowxfer_in(CS_BLKDESC * blkdesc, CS_INT rows_to_xfer, CS_INT * rows_xferred)
654 {
655  TDSSOCKET *tds;
656  TDS_INT each_row;
657 
658  tdsdump_log(TDS_DBG_FUNC, "_blk_rowxfer_in(%p, %d, %p)\n", blkdesc, rows_to_xfer, rows_xferred);
659 
660  if (!blkdesc)
661  return CS_FAIL;
662 
663  tds = CONN(blkdesc)->tds_socket;
664 
665  /*
666  * the first time blk_xfer called after blk_init()
667  * do the query and get to the row data...
668  */
669 
670  if (blkdesc->bcpinfo.xfer_init == 0) {
671 
672  blkdesc->bcpinfo.xfer_init = 1;
673 
674  /*
675  * first call the start_copy function, which will
676  * retrieve details of the database table columns
677  */
678 
679  if (TDS_FAILED(tds_bcp_start_copy_in(tds, &blkdesc->bcpinfo))) {
680  _ctclient_msg(CONN(blkdesc), "blk_rowxfer", 2, 5, 1, 140, "");
681  blkdesc->bcpinfo.xfer_init = 0;
682  return CS_FAIL;
683  }
684  }
685 
686  for (each_row = 0; each_row < rows_to_xfer; each_row++ ) {
687 
689  if (blkdesc->bcpinfo.next_col > 0) {
690  return CS_BLK_HAS_TEXT;
691  }
692  } else {
693  return CS_FAIL;
694  }
695  }
696 
697  return CS_SUCCEED;
698 }
699 
700 static void
701 _blk_null_error(TDSBCPINFO *bcpinfo, int index, int offset)
702 {
703  CS_BLKDESC *blkdesc = (CS_BLKDESC *) bcpinfo;
704 
705  tdsdump_log(TDS_DBG_FUNC, "_blk_null_error(%p, %d, %d)\n", bcpinfo, index, offset);
706 
707  _ctclient_msg(CONN(blkdesc), "blk_rowxfer", 2, 7, 1, 142, "%d, %d", index + 1, offset + 1);
708 }
709 
710 static TDSRET
712 {
713  int result = 0;
714  bool null_column = false;
715  unsigned char *src = NULL;
716 
717  CS_INT srctype = 0;
718  CS_INT srclen = 0;
719  CS_INT destlen = 0;
720  CS_SMALLINT *nullind = NULL;
721  CS_INT *datalen = NULL;
722  CS_BLKDESC *blkdesc = (CS_BLKDESC *) bulk;
723  CS_CONTEXT *ctx = CONN(blkdesc)->ctx;
724  BCPCOLDATA *coldata = bindcol->bcp_column_data;
725 
726  tdsdump_log(TDS_DBG_FUNC, "_blk_get_col_data(%p, %p, %d)\n", bulk, bindcol, offset);
727 
728  if (bindcol->column_nullbind) {
729  nullind = bindcol->column_nullbind;
730  nullind += offset;
731  }
732  if (bindcol->column_lenbind) {
733  datalen = bindcol->column_lenbind;
734  datalen += offset;
735  }
736 
737  /*
738  * Retrieve the initial bound column_varaddress
739  * and increment it if offset specified
740  */
741 
742  src = (unsigned char *) bindcol->column_varaddr;
743  if (!src) {
744  if (nullind && *nullind == -1) {
745  null_column = true;
746  }
747 
748  bindcol->bcp_column_data->datalen = *datalen;
749  bindcol->bcp_column_data->is_null = null_column;
750 
751  if (null_column) {
752  return TDS_SUCCESS;
753  } else if (is_blob_col(bindcol)
754  && bindcol->column_varaddr == NULL) {
755  /* Data will come piecemeal, via blk_textxfer. */
756  return TDS_NO_MORE_RESULTS;
757  }
758 
759  tdsdump_log(TDS_DBG_ERROR, "error source field not addressable\n");
760  return TDS_FAIL;
761  }
762 
763  src += offset * bindcol->column_bindlen;
764  srctype = bindcol->column_bindtype; /* passes to cs_convert */
765 
766  tdsdump_log(TDS_DBG_INFO1, "blk_get_col_data srctype = %d\n", srctype);
767  tdsdump_log(TDS_DBG_INFO1, "blk_get_col_data datalen = %d\n", datalen ? *datalen : -1);
768 
769  if (datalen) {
770  if (*datalen == CS_UNUSED) {
771  switch (srctype) {
772  case CS_LONG_TYPE: srclen = 8; break;
773  case CS_FLOAT_TYPE: srclen = 8; break;
774  case CS_MONEY_TYPE: srclen = 8; break;
775  case CS_DATETIME_TYPE: srclen = 8; break;
776  case CS_INT_TYPE: srclen = 4; break;
777  case CS_UINT_TYPE: srclen = 4; break;
778  case CS_REAL_TYPE: srclen = 4; break;
779  case CS_MONEY4_TYPE: srclen = 4; break;
780  case CS_DATETIME4_TYPE: srclen = 4; break;
781  case CS_SMALLINT_TYPE: srclen = 2; break;
782  case CS_USMALLINT_TYPE: srclen = 2; break;
783  case CS_TINYINT_TYPE: srclen = 1; break;
784  case CS_BIT_TYPE: srclen = 1; break;
785  case CS_BIGINT_TYPE: srclen = 8; break;
786  case CS_UBIGINT_TYPE: srclen = 8; break;
787  case CS_UNIQUE_TYPE: srclen = 16; break;
788  default:
790  "Not fixed length type (%d)"
791  " and datalen not specified\n",
792  bindcol->column_bindtype);
793  return TDS_FAIL;
794  }
795 
796  } else {
797  srclen = *datalen;
798  }
799  }
800  if (srclen == 0) {
801  if (nullind && *nullind == -1)
802  null_column = true;
803  }
804 
805  if (!null_column && !is_blob_type(bindcol->column_type)) {
806  CS_DATAFMT_COMMON srcfmt, destfmt;
807  CS_INT desttype;
808  TDS_SERVER_TYPE tds_desttype = TDS_INVALID_TYPE;
809  TDSSOCKET * tds = CONN(blkdesc)->tds_socket;
810 
811  srcfmt.datatype = srctype;
812  srcfmt.maxlength = srclen;
813 
814  desttype = _cs_convert_not_client(NULL, bindcol, NULL, NULL);
815  if (desttype == CS_ILLEGAL_TYPE) {
816  desttype = _ct_get_client_type(bindcol, false);
817  } else {
818  tds_desttype = bindcol->column_type;
819  }
820  if (desttype == CS_ILLEGAL_TYPE)
821  return TDS_FAIL;
822  destfmt.datatype = desttype;
823  destfmt.maxlength = bindcol->on_server.column_size;
824  destfmt.precision = bindcol->column_prec;
825  destfmt.scale = bindcol->column_scale;
826  destfmt.format = CS_FMT_UNUSED;
827 
828  /* if convert return FAIL mark error but process other columns */
829  if ((result = _cs_convert(ctx, &srcfmt, (CS_VOID *) src,
830  &destfmt, (CS_VOID *) coldata->data,
831  &destlen, tds_desttype,
832  (CS_VOID **) &coldata->data))
833  != CS_SUCCEED) {
834  tdsdump_log(TDS_DBG_INFO1, "convert failed for %d \n", srctype);
835  return TDS_FAIL;
836  }
837  if (destfmt.maxlength != bindcol->column_size
838  && destfmt.datatype == CS_CHAR_TYPE
839  && is_fixed_type(_ct_get_server_type(tds, srctype))) {
840  size_t out_len;
841  TDS_UCHAR *buf
843  (tds, bindcol->char_conv,
844  (char *) coldata->data, destlen,
845  &out_len);
846  if (buf != NULL && buf != coldata->data) {
847  free(coldata->data);
848  coldata->data = buf;
849  destlen = (CS_INT) out_len;
850  }
851  }
852  }
853 
854  bindcol->bcp_column_data->datalen = destlen;
855  bindcol->bcp_column_data->is_null = null_column;
856 
857  return TDS_SUCCESS;
858 }
#define BLK_IDENTITY
Definition: bkpublic.h:38
#define CS_BIGINT_TYPE
Definition: cspublic.h:580
#define CS_SMALLINT_TYPE
Definition: cspublic.h:557
#define CS_FAIL
Definition: cspublic.h:41
#define SRV_PROC
Definition: cspublic.h:724
#define CS_BLK_BATCH
Definition: cspublic.h:518
#define CS_FMT_UNUSED
Definition: cspublic.h:398
#define CS_DATETIME4_TYPE
Definition: cspublic.h:563
#define CS_FLOAT_TYPE
Definition: cspublic.h:560
#define CS_BLK_ALL
Definition: cspublic.h:519
#define CS_BIT_TYPE
Definition: cspublic.h:561
#define CS_UINT_TYPE
Definition: cspublic.h:582
#define CS_REAL_TYPE
Definition: cspublic.h:559
#define CS_BLK_IN
Definition: cspublic.h:515
#define CS_FALSE
Definition: cspublic.h:721
#define CS_CHAR_TYPE
Definition: cspublic.h:550
#define CS_MONEY4_TYPE
Definition: cspublic.h:565
#define CS_USMALLINT_TYPE
Definition: cspublic.h:581
#define CS_DATETIME_TYPE
Definition: cspublic.h:562
#define CS_INT_TYPE
Definition: cspublic.h:558
#define CS_BLK_OUT
Definition: cspublic.h:516
#define CS_TINYINT_TYPE
Definition: cspublic.h:556
#define CS_UNUSED
Definition: cspublic.h:425
#define CS_END_DATA
Definition: cspublic.h:55
#define CS_MONEY_TYPE
Definition: cspublic.h:564
#define CS_SET
Definition: cspublic.h:429
#define CS_LONG_TYPE
Definition: cspublic.h:570
#define CS_TRUE
Definition: cspublic.h:722
#define CS_ROW_FAIL
Definition: cspublic.h:54
#define CS_SUCCEED
Definition: cspublic.h:40
#define CS_BLK_HAS_TEXT
Definition: cspublic.h:47
#define CS_BLK_CANCEL
Definition: cspublic.h:520
#define CS_NULLTERM
Definition: cspublic.h:422
#define CS_CANBENULL
Definition: cspublic.h:290
#define CS_UNIQUE_TYPE
Definition: cspublic.h:587
#define CS_ILLEGAL_TYPE
Definition: cspublic.h:549
#define CS_IDENTITY
Definition: cspublic.h:298
#define CS_UBIGINT_TYPE
Definition: cspublic.h:583
#define CS_GET
Definition: cspublic.h:428
struct _cs_blk_row CS_BLK_ROW
Definition: cstypes.h:102
Int4 CS_INT
Definition: cstypes.h:41
Int2 CS_SMALLINT
Definition: cstypes.h:45
void CS_VOID
Definition: cstypes.h:53
unsigned char CS_BYTE
Definition: cstypes.h:49
CS_INT CS_RETCODE
Definition: cstypes.h:63
char CS_CHAR
Definition: cstypes.h:48
CS_RETCODE blk_colval(SRV_PROC *srvproc, CS_BLKDESC *blkdescp, CS_BLK_ROW *rowp, CS_INT colnum, CS_VOID *valuep, CS_INT valuelen, CS_INT *outlenp)
Definition: blk.c:162
CS_RETCODE blk_describe(CS_BLKDESC *blkdesc, CS_INT item, CS_DATAFMT *datafmt)
Definition: blk.c:184
CS_RETCODE blk_default(CS_BLKDESC *blkdesc, CS_INT colnum, CS_VOID *buffer, CS_INT buflen, CS_INT *outlen)
Definition: blk.c:174
CS_RETCODE blk_init(CS_BLKDESC *blkdesc, CS_INT direction, CS_CHAR *tablename, CS_INT tnamelen)
Definition: blk.c:340
CS_RETCODE blk_drop(CS_BLKDESC *blkdesc)
Definition: blk.c:310
CS_RETCODE blk_sendtext(CS_BLKDESC *blkdesc, CS_BLK_ROW *row, CS_BYTE *buffer, CS_INT buflen)
Definition: blk.c:485
CS_RETCODE blk_rowxfer(CS_BLKDESC *blkdesc)
Definition: blk.c:442
CS_RETCODE blk_textxfer(CS_BLKDESC *blkdesc, CS_BYTE *buffer, CS_INT buflen, CS_INT *outlen)
Definition: blk.c:518
CS_RETCODE blk_srvinit(SRV_PROC *srvproc, CS_BLKDESC *blkdescp)
Definition: blk.c:509
CS_RETCODE blk_sendrow(CS_BLKDESC *blkdesc, CS_BLK_ROW *row)
Definition: blk.c:475
CS_RETCODE blk_done(CS_BLKDESC *blkdesc, CS_INT type, CS_INT *outrow)
Definition: blk.c:228
CS_RETCODE blk_getrow(SRV_PROC *srvproc, CS_BLKDESC *blkdescp, CS_BLK_ROW *rowp)
Definition: blk.c:321
CS_RETCODE blk_bind(CS_BLKDESC *blkdesc, CS_INT item, CS_DATAFMT *datafmt, CS_VOID *buffer, CS_INT *datalen, CS_SMALLINT *indicator)
Definition: blk.c:70
CS_RETCODE blk_props(CS_BLKDESC *blkdesc, CS_INT action, CS_INT property, CS_VOID *buffer, CS_INT buflen, CS_INT *outlen)
Definition: blk.c:382
TDS_COMPILE_CHECK(same_size, sizeof(CS_BLKDESC)==sizeof(TDSBCPINFO))
CS_RETCODE blk_rowdrop(SRV_PROC *srvproc, CS_BLK_ROW *row)
Definition: blk.c:433
CS_RETCODE blk_sethints(CS_BLKDESC *blkdesc, CS_CHAR *hints, CS_INT hintslen)
Definition: blk.c:494
CS_RETCODE blk_gettext(SRV_PROC *srvproc, CS_BLKDESC *blkdescp, CS_BLK_ROW *rowp, CS_INT bufsize, CS_INT *outlenp)
Definition: blk.c:330
CS_RETCODE blk_alloc(CS_CONNECTION *connection, CS_INT version, CS_BLKDESC **blk_pointer)
Definition: blk.c:51
CS_RETCODE blk_rowxfer_mult(CS_BLKDESC *blkdesc, CS_INT *row_count)
Definition: blk.c:450
CS_RETCODE blk_rowalloc(SRV_PROC *srvproc, CS_BLK_ROW **row)
Definition: blk.c:424
CS_CONTEXT * ctx
Definition: t0006.c:12
TDS_SERVER_TYPE _ct_get_server_type(TDSSOCKET *tds, int datatype)
Definition: ct.c:2128
int _ct_bind_data(CS_CONTEXT *ctx, TDSRESULTINFO *resinfo, TDSRESULTINFO *bindinfo, CS_INT offset)
Definition: ct.c:1794
void _ctclient_msg(CS_CONNECTION *con, const char *funcname, int layer, int origin, int severity, int number, const char *fmt,...)
Definition: ct.c:188
int _ct_get_client_type(CS_CONTEXT *ctx, TDSCOLUMN *col)
Definition: ct.c:1964
int _cs_convert_not_client(CS_CONTEXT *ctx, TDSCOLUMN *curcol, CONV_RESULT *convert_buffer, unsigned char **p_src)
Try to convert to a type we can handle.
Definition: cs.c:1508
TDS_SERVER_TYPE
Definition: proto.h:161
#define TDS_FAIL
Definition: tds.h:204
#define tds_new(type, n)
Definition: tds.h:1392
#define TDS_FAILED(rc)
Definition: tds.h:206
#define TDS_OFFSET(str, field)
Definition: tds.h:364
tds_sysdep_int32_type TDS_INT
Definition: tds.h:149
@ TDS_STOPAT_DONE
Definition: tds.h:255
@ TDS_RETURN_ROW
Definition: tds.h:256
@ TDS_STOPAT_ROWFMT
Definition: tds.h:252
@ TDS_TOKEN_RESULTS
Definition: tds.h:261
@ TDS_RETURN_COMPUTE
Definition: tds.h:257
#define is_fixed_type(x)
Definition: tds.h:438
#define tdsdump_log
Definition: tds.h:1561
#define TDS_INVALID_TYPE
Definition: tds.h:160
#define TDS_DBG_INFO1
Definition: tds.h:900
#define TDS_NO_MORE_RESULTS
Definition: tds.h:202
#define is_blob_type(x)
Definition: tds.h:443
unsigned char TDS_UCHAR
Definition: tds.h:145
#define is_blob_col(x)
Definition: tds.h:445
#define TDS_COMPUTE_RESULT
Definition: tds.h:220
@ TDS_PENDING
cilent is waiting for data
Definition: tds.h:866
@ TDS_WRITING
client is writing data
Definition: tds.h:864
char TDS_CHAR
Definition: tds.h:144
#define TDS_ROW_RESULT
Definition: tds.h:216
int TDSRET
Definition: tds.h:201
#define TDS_DBG_ERROR
Definition: tds.h:903
#define TDS_SUCCESS
Definition: tds.h:203
#define TDS_DBG_FUNC
Definition: tds.h:898
#define strlcpy(d, s, l)
Definition: replacements.h:80
int offset
Definition: replacements.h:160
static TDSSOCKET * tds
Definition: collations.c:37
#define tds_send_cancel
#define tds_bcp_start
#define tds_bcp_start_copy_in
#define tds_bcp_init
#define tds_convert_string
#define tds_free_bcpinfo
#define tds_set_state
#define tds_deinit_bcpinfo
#define tds_process_cancel
#define tds_submit_queryf
#define tds_bcp_done
#define tds_process_tokens
#define tds_bcp_send_record
#define CONN(bulk)
Definition: blk.c:47
static CS_RETCODE _blk_rowxfer_in(CS_BLKDESC *blkdesc, CS_INT rows_to_xfer, CS_INT *rows_xferred)
Definition: blk.c:653
static void _blk_clean_desc(CS_BLKDESC *blkdesc)
Definition: blk.c:314
static TDSRET _blk_get_col_data(TDSBCPINFO *bulk, TDSCOLUMN *bcpcol, int offset)
Definition: blk.c:711
static void _blk_null_error(TDSBCPINFO *bcpinfo, int index, int offset)
Definition: blk.c:701
static CS_RETCODE _blk_rowxfer_out(CS_BLKDESC *blkdesc, CS_INT rows_to_xfer, CS_INT *rows_xferred)
Definition: blk.c:579
CS_DATAFMT_LARGE * _ct_datafmt_conv_prepare(CS_CONTEXT *ctx, CS_DATAFMT *datafmt, CS_DATAFMT_LARGE *fmtbuf)
Prepares to Convert CS_DATAFMT output parameter to CS_DATAFMT_LARGE.
Definition: ctutil.c:289
CS_RETCODE _cs_convert(CS_CONTEXT *ctx, const CS_DATAFMT_COMMON *srcfmt, CS_VOID *srcdata, const CS_DATAFMT_COMMON *destfmt, CS_VOID *destdata, CS_INT *resultlen, TDS_SERVER_TYPE desttype, CS_VOID **handle)
Definition: cs.c:488
const CS_DATAFMT_COMMON * _ct_datafmt_common(CS_CONTEXT *ctx, const CS_DATAFMT *datafmt)
Definition: ctutil.c:244
void _ct_datafmt_conv_back(CS_DATAFMT *datafmt, CS_DATAFMT_LARGE *fmtbuf)
Converts CS_DATAFMT output parameter to CS_DATAFMT_LARGE after setting it.
Definition: ctutil.c:308
#define NULL
Definition: ncbistd.hpp:225
static const char * tds_dstr_cstr(DSTR *s)
Returns a C version (NUL terminated string) of dstr.
Definition: string.h:66
DSTR * tds_dstr_copyn(DSTR *s, const char *src, size_t length) TDS_WUR
Set string to a given buffer of characters.
Definition: tdsstring.c:78
#define tds_alloc_bcpinfo
char * buf
int i
if(yy_accept[yy_current_state])
static int version
Definition: mdb_load.c:29
static int bufsize
Definition: pcregrep.c:162
static pcre_uint8 * buffer
Definition: pcretest.c:1051
#define row(bind, expected)
Definition: string_bind.c:73
CS_INT maxlength
Definition: ctlib.h:373
CS_INT format
Definition: ctlib.h:372
CS_INT precision
Definition: ctlib.h:375
CS_INT datatype
Definition: ctlib.h:371
CS_INT count
Definition: ctlib.h:377
CS_INT scale
Definition: ctlib.h:374
CS_INT maxlength
Definition: ctlib.h:361
CS_INT precision
Definition: ctlib.h:363
CS_CHAR name[256]
Definition: ctlib.h:357
CS_INT usertype
Definition: ctlib.h:366
CS_INT status
Definition: ctlib.h:364
CS_INT datatype
Definition: ctlib.h:359
CS_INT namelen
Definition: ctlib.h:358
CS_INT scale
Definition: ctlib.h:362
CS_INT count
Definition: ctlib.h:365
CS_LOCALE * locale
Definition: ctlib.h:367
TDSBCPINFO bcpinfo
Definition: ctlib.h:246
TDSSOCKET * tds_socket
Definition: ctlib.h:127
CS_CONTEXT * ctx
Definition: ctlib.h:125
TDS_UCHAR * data
Definition: tds.h:694
TDS_INT is_null
Definition: tds.h:696
TDS_INT datalen
Definition: tds.h:695
const char * hint
Definition: tds.h:1661
TDSRESULTINFO * bindinfo
Definition: tds.h:1669
TDS_INT blob_cols
Definition: tds.h:1672
void * parent
Definition: tds.h:1662
TDS_INT text_sent
Definition: tds.h:1670
TDS_INT identity_insert_on
Definition: tds.h:1666
TDS_INT xfer_init
Definition: tds.h:1667
TDS_INT next_col
Definition: tds.h:1671
DSTR tablename
Definition: tds.h:1663
TDS_INT direction
Definition: tds.h:1665
TDS_INT bind_count
Definition: tds.h:1668
Metadata about columns in regular and compute rows.
Definition: tds.h:761
TDS_INT column_size
maximun size of data.
Definition: tds.h:766
TDS_SMALLINT * column_nullbind
Definition: tds.h:818
DSTR column_name
Definition: tds.h:787
TDS_UINT column_bindlen
Definition: tds.h:817
BCPCOLDATA * bcp_column_data
Definition: tds.h:825
TDS_SMALLINT column_bindtype
Definition: tds.h:815
TDS_TINYINT column_prec
precision for decimal/numeric
Definition: tds.h:775
TDS_SERVER_TYPE column_type
This type can be different from wire type because conversion (e.g.
Definition: tds.h:768
unsigned int column_identity
Definition: tds.h:797
TDSICONV * char_conv
refers to previously allocated iconv information
Definition: tds.h:784
TDS_SMALLINT column_bindfmt
Definition: tds.h:816
unsigned int column_nullable
Definition: tds.h:795
TDS_TINYINT column_scale
scale for decimal/numeric
Definition: tds.h:776
TDS_INT * column_lenbind
Definition: tds.h:820
struct tds_column::@124 on_server
TDS_CHAR * column_varaddr
Definition: tds.h:819
TDS_INT column_cur_size
size written in variable (ie: char, text, binary).
Definition: tds.h:811
TDS_INT column_usertype
Definition: tds.h:763
TDS_USMALLINT tds_version
Definition: tds.h:1138
TDSCOLUMN ** columns
Definition: tds.h:844
TDS_USMALLINT num_cols
Definition: tds.h:845
Information for a server connection.
Definition: tds.h:1211
unsigned out_pos
current position in out_buf
Definition: tds.h:1238
TDSCONNECTION conn[1]
Definition: tds.h:1215
TDSRESULTINFO * current_results
Current query information.
Definition: tds.h:1263
Definition: type.c:6
else result
Definition: token2.c:20
void free(voidpf ptr)
Modified on Thu Jun 13 17:29:41 2024 by modify_doxy.py rev. 669887