File: _kiconversion_type_translation.c

package info (click to toggle)
python-kinterbasdb 3.1-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 1,044 kB
  • ctags: 1,157
  • sloc: ansic: 6,879; python: 2,517; makefile: 77
file content (607 lines) | stat: -rw-r--r-- 21,594 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
/* KInterbasDB Python Package - Implementation of Dynamic Type Translation
**
** Version 3.1
**
** The following contributors hold Copyright (C) over their respective
** portions of code (see license.txt for details):
**
** [Original Author (maintained through version 2.0-0.3.1):]
**   1998-2001 [alex]  Alexander Kuznetsov   <alexan@users.sourceforge.net>
** [Maintainers (after version 2.0-0.3.1):]
**   2001-2002 [maz]   Marek Isalski         <kinterbasdb@maz.nu>
**   2002-2004 [dsr]   David Rushby          <woodsplitter@rocketmail.com>
** [Contributors:]
**   2001      [eac]   Evgeny A. Cherkashin  <eugeneai@icc.ru>
**   2001-2002 [janez] Janez Jere            <janez.jere@void.si>
*/

static PyObject *dynamically_type_convert_input_obj_if_necessary(
    PyObject *py_input,
    boolean is_array_element,
    short data_type, short data_subtype, short scale,
    PyObject *converter
  );


/* Infinitely peristent global variables that are "private" to this source file: */
PyObject *_type_names_all_supported;

PyObject *_cached_type_name_TEXT;
PyObject *_cached_type_name_TEXT_UNICODE;
PyObject *_cached_type_name_BLOB;

PyObject *_cached_type_name_INTEGER;
PyObject *_cached_type_name_FIXED;
PyObject *_cached_type_name_FLOATING;

PyObject *_cached_type_name_TIMESTAMP;
PyObject *_cached_type_name_DATE;
PyObject *_cached_type_name_TIME;


#define IS_UNICODE_CHAR_OR_VARCHAR(data_type, data_subtype) \
  (((data_type) == SQL_VARYING || (data_type) == SQL_TEXT) && (data_subtype) > 2)

#define CACHED_TYPE_NAME_TEXT_OR_TEXT_UNICODE(data_subtype) \
  (((data_subtype) <= 2) ? _cached_type_name_TEXT : _cached_type_name_TEXT_UNICODE)


/* INIT_CONST_TYPE_NAME: */
#define _ICTN(ptr, name) \
  if ( ( ptr = PyString_FromString(name) ) == NULL ) { \
    goto INIT_TYPE_TRANSLATION_FAILED; \
  } \
  if ( PyList_Append(_type_names_all_supported, ptr) != 0 ) { \
    goto INIT_TYPE_TRANSLATION_FAILED; \
  }

static int init_kidb_type_translation(void) {
  _type_names_all_supported = PyList_New(0);
  if (_type_names_all_supported == NULL) {
    goto INIT_TYPE_TRANSLATION_FAILED;
  }

  _ICTN( _cached_type_name_TEXT,           "TEXT"                            );
  _ICTN( _cached_type_name_TEXT_UNICODE,   "TEXT_UNICODE"                    );
  _ICTN( _cached_type_name_BLOB,           "BLOB"                            );

  _ICTN( _cached_type_name_INTEGER,        "INTEGER"                         );
  _ICTN( _cached_type_name_FIXED,          "FIXED"                           );
  _ICTN( _cached_type_name_FLOATING,       "FLOATING"                        );

  _ICTN( _cached_type_name_TIMESTAMP,      "TIMESTAMP"                       );
  _ICTN( _cached_type_name_DATE,           "DATE"                            );
  _ICTN( _cached_type_name_TIME,           "TIME"                            );

  return 0;

 INIT_TYPE_TRANSLATION_FAILED:
  return -1;
} /* init_kidb_type_translation */


static PyObject *_get_cached_type_name_for_conventional_code(
    short data_type, short data_subtype, short scale
  )
{
  switch (data_type) {
    case SQL_TEXT:
    case SQL_VARYING:
      return CACHED_TYPE_NAME_TEXT_OR_TEXT_UNICODE(data_subtype);

    case SQL_BLOB:
      return _cached_type_name_BLOB;

    case SQL_SHORT:
    case SQL_LONG:
    #ifdef INTERBASE6_OR_LATER
    case SQL_INT64:
    #endif /* INTERBASE6_OR_LATER */
      return
            IS_FIXED_POINT__CONVENTIONAL(data_type, data_subtype, scale)
          ? _cached_type_name_FIXED
          : _cached_type_name_INTEGER
        ;

    case SQL_FLOAT:
    case SQL_DOUBLE:
    case SQL_D_FLOAT:
      return _cached_type_name_FLOATING;

    case SQL_TIMESTAMP:
      return _cached_type_name_TIMESTAMP;
    #ifdef INTERBASE6_OR_LATER
    case SQL_TYPE_DATE:
      return _cached_type_name_DATE;
    case SQL_TYPE_TIME:
      return _cached_type_name_TIME;
    #endif /* INTERBASE6_OR_LATER */

    default:
      raise_exception(InternalError,
          "_get_cached_type_name_for_conventional_code: unknown type"
        );
      return NULL;
  }
} /* _get_cached_type_name_for_conventional_code */


static PyObject *_get_cached_type_name_for_array_code(
    short data_type, short data_subtype, short scale
  )
{
  switch (data_type) {
    case blr_text:
    case blr_text2:
    case blr_varying:
    case blr_varying2:
    case blr_cstring:
    case blr_cstring2:
      return CACHED_TYPE_NAME_TEXT_OR_TEXT_UNICODE(data_subtype);

    case blr_quad:
      /* ISC_QUAD structure; since the DB engine doesn't support arrays of
      ** arrays, assume that this item refers to a blob id. */
    case blr_blob:
    case blr_blob_id:
      return _cached_type_name_BLOB;

    case blr_short:
    case blr_long:
    #ifdef INTERBASE6_OR_LATER
    case blr_int64:
    #endif /* INTERBASE6_OR_LATER */
      return
            IS_FIXED_POINT__ARRAY_EL(data_type, data_subtype, scale)
          ? _cached_type_name_FIXED
          : _cached_type_name_INTEGER
        ;

    case blr_float:
    case blr_double:
    case blr_d_float:
      return _cached_type_name_FLOATING;

    case blr_timestamp:
      return _cached_type_name_TIMESTAMP;
    #ifdef INTERBASE6_OR_LATER
    case blr_sql_date:
      return _cached_type_name_DATE;
    case blr_sql_time:
      return _cached_type_name_TIME;
    #endif /* INTERBASE6_OR_LATER */

    default:
      raise_exception(InternalError,
          "_get_cached_type_name_for_array_code: unknown type"
        );
      return NULL;
  }
} /* _get_cached_type_name_for_array_code */


static PyObject *_get_converter(
    PyObject *trans_dict, short data_type, short data_subtype, short scale,
    boolean is_array_field
  )
{
  /* Returns a borrowed reference to the converter callable if one is
  ** registered for the specified type in $trans_dict, or a borrowed reference
  ** to Py_None if no such converter is registered.
  ** Returns NULL on error. */
  if (trans_dict == NULL) {
    return Py_None; /* Note the lack of the conventional PY_INCREF(Py_None). */
  } else {
    PyObject *type_name = (
        is_array_field ?
            _get_cached_type_name_for_array_code(data_type, data_subtype, scale)
          : _get_cached_type_name_for_conventional_code(data_type, data_subtype, scale)
      );
    if (type_name == NULL) {
      return NULL;
    }
    {
      PyObject *converter = PyDict_GetItem(trans_dict, type_name);
      if (converter != NULL) {
        /* PyDict_GetItem returned a borrowed reference. */
        return converter;
      } else {
        /* Note the lack of the conventional PY_INCREF(Py_None), for symmetry
        ** with the lack of a new reference from PyDict_GetItem. */
        return Py_None;
      }
    }
  }
} /* _get_converter */


static PyObject *connection_get_out_converter(
    ConnectionObject *conn, short data_type, short data_subtype, short scale,
    boolean is_array_field
  )
{
  return _get_converter(conn->type_trans_out, data_type, data_subtype, scale, is_array_field);
} /* connection_get_out_converter */


static PyObject *connection_get_in_converter(
    ConnectionObject *conn, short data_type, short data_subtype, short scale,
    boolean is_array_field
  )
{
  return _get_converter(conn->type_trans_in, data_type, data_subtype, scale, is_array_field);
} /* connection_get_in_converter */


static PyObject *cursor_get_out_converter(
    CursorObject *cursor, short data_type, short data_subtype, short scale,
    boolean is_array_field
  )
{
  PyObject *trans_dict = cursor->type_trans_out;
  PyObject *converter = _get_converter(trans_dict, data_type, data_subtype, scale, is_array_field);

  if (converter != Py_None) {
    return converter;
  }

  /* Fall back on the connection's translation dictionary, if any. */
  return connection_get_out_converter(cursor->connection,
      data_type, data_subtype, scale, is_array_field
    );
} /* cursor_get_out_converter */


static PyObject *cursor_get_in_converter(
    CursorObject *cursor, short data_type, short data_subtype, short scale,
    boolean is_array_field
  )
{
  PyObject *trans_dict = cursor->type_trans_in;
  PyObject *converter = _get_converter(trans_dict, data_type, data_subtype, scale, is_array_field);

  if (converter != Py_None) {
    return converter;
  }

  /* Fall back on the connection's translation dictionary, if any. */
  return connection_get_in_converter(cursor->connection,
      data_type, data_subtype, scale, is_array_field
    );
} /* cursor_get_in_converter */


/* 2003.10.16: */
static PyObject *connection_get_translator_output_type(
    ConnectionObject *con, PyObject *translator_key
  )
{
  /* Helper function for cursor_get_translator_output_type. */
  assert (PyString_Check(translator_key));
{
  PyObject *output_type_dict = con->output_type_trans_return_type_dict;
  if (output_type_dict != NULL) {
    return PyDict_GetItem(output_type_dict, translator_key);
  }
  return NULL;
}
} /* connection_get_translator_output_type */


/* cursor_get_translator_output_type's search might "bubble" to its connection
** in a manner similar to the "bubble" in cursor_get_out_converter. */
static PyObject *cursor_get_translator_output_type(
    CursorObject *cursor, PyObject *translator_key
  )
{
  /* If a record of the return type of the output translator registered under
  ** $translator_key is found, returns a borrowed reference to that type.
  ** Otherwise, returns NULL (which simply means "not found"--it doesn't mean
  ** there was an error). */
  assert (PyString_Check(translator_key));
{
  PyObject *output_type_dict = cursor->output_type_trans_return_type_dict;
  if (output_type_dict != NULL) {
    PyObject *output_type = PyDict_GetItem(output_type_dict, translator_key);
    if (output_type != NULL) {
      return output_type; /* Return borrowed reference. */
    } /* Else, fall through and search cursor->connection's output type dict. */
  }

  return connection_get_translator_output_type(cursor->connection, translator_key);
}
} /* cursor_get_translator_output_type */


#define TYPE_NAMES_ALL_VALID             1
#define TYPE_NAMES_INVALID               0
#define TYPE_NAMES_VALIDATION_PROBLEM   -1

static int _validate_type_names(PyObject *trans_dict) {
  /* Returns:
  ** TYPE_NAMES_ALL_VALID if all type names are valid,
  ** TYPE_NAMES_INVALID if at least one type name is invalid,
  ** TYPE_NAMES_VALIDATION_PROBLEM upon error in validation process. */
  int status = TYPE_NAMES_VALIDATION_PROBLEM; /* Guilty until proven innocent. */
  PyObject *keys = PyDict_Keys(trans_dict);
  int key_count;
  int i;

  if (keys == NULL) {
    goto _VALIDATE_TYPE_NAMES_CLEANUP;
  }

  key_count = PyList_GET_SIZE(keys);
  for (i = 0; i < key_count; i++) {
    /* PyList_GET_ITEM "returns" a borrowed ref, and can't fail as long as keys
    ** is of the correct type, which it certainly is here. */
    PyObject *k = PyList_GET_ITEM(keys, i);

    int contains = PySequence_Contains(_type_names_all_supported, k);
    if (contains == -1) { /* error in PySequence_Contains */
      goto _VALIDATE_TYPE_NAMES_CLEANUP;
    } else if (contains == 0) {
      /* k was not in the master list of supported type names. */
      #if PYTHON_2_2_OR_LATER
        PyObject *str_all_supported = PyObject_Str(_type_names_all_supported);
        if (str_all_supported == NULL) goto _VALIDATE_TYPE_NAMES_CLEANUP;
        {
        PyObject *form_msg = PyString_FromFormat(
            "Cannot translate type '%s'. Type must be one of %s.",
            PyString_AsString(k), PyString_AsString(str_all_supported)
          );
        Py_DECREF(str_all_supported);
        if (form_msg == NULL) goto _VALIDATE_TYPE_NAMES_CLEANUP;
        raise_exception(ProgrammingError, PyString_AsString(form_msg));
        Py_DECREF(form_msg);
        }
      #else /* not PYTHON_2_2_OR_LATER */
        raise_exception(ProgrammingError, "Cannot translate this type.");
      #endif /* PYTHON_2_2_OR_LATER */
      status = TYPE_NAMES_INVALID;
      goto _VALIDATE_TYPE_NAMES_CLEANUP;
    } /* else, contains == 1, which is the 'success' condition. */
  }
  status = TYPE_NAMES_ALL_VALID;

 _VALIDATE_TYPE_NAMES_CLEANUP:
  Py_XDECREF(keys);
  return status;
} /* _validate_type_names */


#define DICT_IS_NONE_OR_EMPTY(d) \
  ((d) == Py_None || PyDict_Size(d) == 0)

/* Generic programming the ugly way: */
#define _CREATE_TYPE_TRANS_SETTER(func_name, member_name, type_name, type_infra_name) \
  static PyObject *func_name( PyObject *self, PyObject *args ) { \
    type_name *target; \
    PyObject *trans_dict; \
    PyObject *output_type_trans_return_type_dict = NULL; \
    \
    if ( !PyArg_ParseTuple( args, "O!O!|O!", \
            &type_infra_name, &target, &PyDict_Type, &trans_dict, \
            &PyDict_Type, &output_type_trans_return_type_dict \
       )) \
    { return NULL; } \
    if (_validate_type_names(trans_dict) != TYPE_NAMES_ALL_VALID) \
      { return NULL; } \
    \
    /* If the target's output_type_trans_return_type_dict is to be replaced, \
    ** the caller will have supplied a non-NULL value for it (NULL indicates \
    ** that the optional argument was not specified). */ \
    if (output_type_trans_return_type_dict != NULL) { \
      Py_XDECREF(target->output_type_trans_return_type_dict); \
      /* If the new output_type_trans_return_type_dict is None or empty, \
      ** set the target's corresponding member to NULL rather than recording \
      ** the useless incoming value. \
      ** Note that output_type_trans_return_type_dict might be empty when \
      ** trans_dict is *not* empty, because when a translator is set to None \
      ** (which indicates that it should return the kinterbasdb's internal \
      ** representation of the value), output_type_trans_return_type_dict \
      ** will not contain an entry for that translation key (instead, \
      ** XSQLDA2Description supplies a default type). */ \
      if ( DICT_IS_NONE_OR_EMPTY(output_type_trans_return_type_dict) ) { \
        target->output_type_trans_return_type_dict = NULL; \
      } else { \
        Py_INCREF(output_type_trans_return_type_dict); \
        target->output_type_trans_return_type_dict = output_type_trans_return_type_dict; \
      } \
    } \
    \
    Py_XDECREF(target->member_name); /* Free old translation dict, if any. */ \
    if ( DICT_IS_NONE_OR_EMPTY(trans_dict) ) { \
      target->member_name = NULL; \
    } else { \
      /* Corresponding DECREF is in delete_[type] or the XDECREF above. */ \
      Py_INCREF(trans_dict); \
      target->member_name = trans_dict; \
    } \
    \
    Py_INCREF(Py_None); \
    return Py_None; \
  }
/* end of _CREATE_TYPE_TRANS_SETTER */

#define _CREATE_TYPE_TRANS_GETTER(func_name, member_name, type_name, type_infra_name) \
  static PyObject *func_name( PyObject *self, PyObject *args ) { \
    type_name *target; \
    \
    if ( !PyArg_ParseTuple( args, "O!", &type_infra_name, &target ) ) { return NULL; } \
    \
    if (target->member_name != NULL) { \
      /* Copy the dict so that the type translation settings can't be modified \
      ** except via a set_type_trans_* method. */ \
      return PyDict_Copy(target->member_name); \
    } else { \
      Py_INCREF(Py_None); \
      return Py_None; \
    } \
  }
/* end of _CREATE_TYPE_TRANS_GETTER */

/* Getters/setters for ConnectionObject: */
/* Out: */
_CREATE_TYPE_TRANS_SETTER(
    pyob_con_set_type_trans_out, type_trans_out, ConnectionObject, ConnectionType
  )
_CREATE_TYPE_TRANS_GETTER(
    pyob_con_get_type_trans_out, type_trans_out, ConnectionObject, ConnectionType
  )
/* In: */
_CREATE_TYPE_TRANS_SETTER(
    pyob_con_set_type_trans_in, type_trans_in, ConnectionObject, ConnectionType
  )
_CREATE_TYPE_TRANS_GETTER(
    pyob_con_get_type_trans_in, type_trans_in, ConnectionObject, ConnectionType
  )

/* Getters/setters for CursorObject: */
/* Out: */
_CREATE_TYPE_TRANS_SETTER(
    pyob_cur_set_type_trans_out, type_trans_out, CursorObject, CursorType
  )
_CREATE_TYPE_TRANS_GETTER(
    pyob_cur_get_type_trans_out, type_trans_out, CursorObject, CursorType
  )
/* In: */
_CREATE_TYPE_TRANS_SETTER(
    pyob_cur_set_type_trans_in, type_trans_in, CursorObject, CursorType
  )
_CREATE_TYPE_TRANS_GETTER(
    pyob_cur_get_type_trans_in, type_trans_in, CursorObject, CursorType
  )


/* 2003.03.30: */
static PyObject *dynamically_type_convert_input_obj_if_necessary(
    PyObject *py_input,
    boolean is_array_element,
    short data_type, short data_subtype, short scale,
    PyObject *converter
  )
{
  /* if $converter is None, returns:
  **   a new reference to the original value
  ** else:
  **   the return value of the converter (which is a new reference) */
  if (converter == Py_None) {
    Py_INCREF(py_input);
    return py_input;
  }{
  PyObject *py_converted;
  PyObject *py_argument_to_converter;
  boolean is_fixed_point;

  PyObject *argz = PyTuple_New(1);
  if (argz == NULL) { return PyErr_NoMemory(); }

  /* Next, set py_argument_to_converter, the single argument that the converter
  ** will receive (though it's only one argument, it might be a sequence). */

  /* Special case for fixed point fields:  pass the original input object
  ** and the scale figure in a 2-tuple, rather than just the original input
  ** object, as with all other field types. */
  is_fixed_point = (
        is_array_element
      ? IS_FIXED_POINT__ARRAY_EL(data_type, data_subtype, scale)
      : IS_FIXED_POINT__CONVENTIONAL(data_type, data_subtype, scale)
    );
  if (is_fixed_point) {
    /* Reference ownership of this new 2-tuple is passed to argz via
    ** PyTuple_SET_ITEM.  argz will then delete this new 2-tuple when argz
    ** itself is deleted.  The refcount of py_input is INCd when it enters
    ** the new 2-tuple; DECd when the 2-tuple is deleted. */
    py_argument_to_converter = Py_BuildValue("(Oi)", py_input, scale);
  } else if (IS_UNICODE_CHAR_OR_VARCHAR(data_type, data_subtype)) {
    py_argument_to_converter = Py_BuildValue("(Oi)", py_input, data_subtype);
  } else {
    /* We currently hold only a borrowed reference to py_input, since it's
    ** an input parameter rather than a newly created object.
    ** Therefore, we must now artificially INCREF py_input so that
    ** PyTuple_SET_ITEM(argz, ...) can "steal" ownership of a reference to
    ** py_input and then discard that reference when argz is destroyed. */
    Py_INCREF(py_input);
    py_argument_to_converter = py_input;
  }
  if (py_argument_to_converter == NULL) {
    Py_DECREF(argz);
    return PyErr_NoMemory();
  }
  PyTuple_SET_ITEM(argz, 0, py_argument_to_converter);

  py_converted = PyObject_CallObject(converter, argz); /* The MEAT. */

  Py_DECREF(argz); /* This also releases py_argument_to_converter. */

  /* py_converted is a new reference. */
  return py_converted;
}} /* dynamically_type_convert_input_obj_if_necessary */


static PyObject *dynamically_type_convert_output_obj_if_necessary(
    PyObject *db_plain_output, PyObject *converter,
    short data_type, short data_subtype
  )
{
  /* Unlike dynamically_type_convert_input_obj_if_necessary, this function
  ** does NOT return a new reference.
  ** if $converter is None:
  **   returns the passed reference to the original value db_plain_output
  ** else:
  **   returns the return value of the converter (which is a new reference),
  **   BUT ALSO deletes the passed reference to db_plain_output, in effect
  **   "replacing" db_plain_output with py_converted.
  **   The passed reference to db_plain_output is deleted EVEN if this function
  **   encounters an error.
  */
  if (converter == Py_None) {
    return db_plain_output;
  }{
  PyObject *py_converted;
  boolean is_unicode_char_or_varchar = IS_UNICODE_CHAR_OR_VARCHAR(data_type, data_subtype);
  PyObject *argz = PyTuple_New(1);
  if (argz == NULL) {
    /* Yes, DECing db_plain_output here is correct (see comment at start): */
    Py_DECREF(db_plain_output);
    return PyErr_NoMemory();
  }

  if (!is_unicode_char_or_varchar) {
    /* The following statement "steals" the ref to db_plain_output, which is
    ** appropriate behavior in this situation. */
    PyTuple_SET_ITEM(argz, 0, db_plain_output);
  } else {
    /* If it's a unicode CHAR or VARCHAR, create a 2-tuple containing:
    ** (
    **    the raw (encoded) string,
    **    the database engine's internal character set code
    ** ). */
    PyObject *tuple_of_raw_string_and_charset_code = PyTuple_New(2);
    PyObject *db_charset_code = PyInt_FromLong(data_subtype);
    if (tuple_of_raw_string_and_charset_code == NULL || db_charset_code == NULL) {
      /* Yes, DECing db_plain_output here is correct (see comment at start): */
      Py_DECREF(db_plain_output);
      Py_DECREF(argz);
      return PyErr_NoMemory();
    }
    /* The following statements "steal" the refs to the element values, which
    ** is appropriate behavior in this situation.  Reference ownership of
    ** db_plain_output and db_charset_code is handed off to the container
    ** tuple_of_raw_string_and_charset_code; in turn, reference ownership of
    ** tuple_of_raw_string_and_charset_code is handed off to the container argz.
    ** When argz is released at the end of this function, the release
    ** "cascades", releasing the three other references mentioned above. */
    PyTuple_SET_ITEM(tuple_of_raw_string_and_charset_code, 0, db_plain_output);
    PyTuple_SET_ITEM(tuple_of_raw_string_and_charset_code, 1, db_charset_code);

    PyTuple_SET_ITEM(argz, 0, tuple_of_raw_string_and_charset_code);
  }

  py_converted = PyObject_CallObject(converter, argz); /* The MEAT. */

  Py_DECREF(argz);

  return py_converted;
}} /* dynamically_type_convert_output_obj_if_necessary */