File: _kiconversion_field_precision.c

package info (click to toggle)
python-kinterbasdb 3.3.0-2
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 2,432 kB
  • ctags: 1,830
  • sloc: ansic: 16,803; python: 3,514; makefile: 63; sh: 22
file content (425 lines) | stat: -rw-r--r-- 14,799 bytes parent folder | download | duplicates (2)
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
/* KInterbasDB Python Package - Implementation of Field Precision Determination
 *
 * Version 3.3
 *
 * 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-2007 [dsr]   David Rushby          <woodsplitter@rocketmail.com>
 * [Contributors:]
 *   2001      [eac]   Evgeny A. Cherkashin  <eugeneai@icc.ru>
 *   2001-2002 [janez] Janez Jere            <janez.jere@void.si>
 */

/* This source file is designed to be directly included in _kiconversion.c,
 * without the involvement of a header file. */

#define ENTITY_TYPE_UNKNOWN 0
#define ENTITY_TYPE_TABLE 1
#define ENTITY_TYPE_STORED_PROCEDURE 2

#define ENTITY_TYPE_LAST ENTITY_TYPE_STORED_PROCEDURE

static PyObject *determine_field_precision(
    short entity_type_code,
    char *entity_name, short entity_name_length,
    char *field_name, short field_name_length,
    Cursor *cursor
  )
{
  /* Returns:
   * - a new reference to a PyObject * containing the precision figure on
   *   success (may be the PyInt zero)
   * - a new reference to a PyObject * containing the PyInt zero on routine
   *   inability to determine precision (as in the case of dynamic fields)
   * - NULL on error (will already have set an exception) */
  int status = -1;
  PyObject *precision = NULL;
  PyObject *result_cache = NULL;
  PyObject *result_cache_this_entity = NULL;

  const char *sql_statement_table =
       "SELECT FIELD_SPEC.RDB$FIELD_PRECISION"
      " FROM RDB$FIELDS FIELD_SPEC, RDB$RELATION_FIELDS REL_FIELDS"
      " WHERE"
            " FIELD_SPEC.RDB$FIELD_NAME = REL_FIELDS.RDB$FIELD_SOURCE"
        " AND REL_FIELDS.RDB$RELATION_NAME = ?"
        " AND REL_FIELDS.RDB$FIELD_NAME = ?"
    ;
  const unsigned short sql_statement_table_length = (unsigned short)
    strlen(sql_statement_table);

  const char *sql_statement_stored_procedure =
       "SELECT FIELD_SPEC.RDB$FIELD_PRECISION"
      " FROM RDB$FIELDS FIELD_SPEC, RDB$PROCEDURE_PARAMETERS REL_FIELDS"
      " WHERE"
            " FIELD_SPEC.RDB$FIELD_NAME = REL_FIELDS.RDB$FIELD_SOURCE"
        " AND RDB$PROCEDURE_NAME = ?"
        " AND RDB$PARAMETER_NAME = ?"
        " AND RDB$PARAMETER_TYPE = 1" /* 1 is the parameter type of output
                                       * parameters */
    ;
  const unsigned short sql_statement_stored_procedure_length = (unsigned short)
    strlen(sql_statement_stored_procedure);

  /* The following variables are all just shortcuts to members of
   * result_cache: */
  XSQLDA *in_da = NULL;
  XSQLVAR *in_var = NULL;
  XSQLDA *out_da = NULL;
  XSQLVAR *out_var = NULL;

  CursorDescriptionCache *cache =
    Transaction_get_con(cursor->trans)->desc_cache;
  PyObject *exception_type = NULL;

  /* Default to normal table. */
  if (entity_type_code == ENTITY_TYPE_UNKNOWN) {
    entity_type_code = ENTITY_TYPE_TABLE;
  }

  if (entity_name_length == 0 || field_name_length == 0) {
    /* Either or both of the entity name and the field name are not supplied,
     * so we cannot determine this output field's precision.  This is not
     * an exceptional situation; it occurs routinely in queries with
     * dynamically computed fields (e.g., select count(*) from some_table). */
    return PyInt_FromLong(0);
  }

  /* Special case for the automagic RDB$DB_KEY field, which the engine isn't
   * able to find the precision of.  The engine mangles the field name to
   * "DB_KEY" instead of "RDB$DB_KEY", but I'm testing for either here in the
   * interest of future-proofing. */
  if (
         (field_name_length ==  6 && strncmp(field_name, "DB_KEY",      6) == 0)
      || (field_name_length == 10 && strncmp(field_name, "RDB$DB_KEY", 10) == 0)
     )
  { return PyInt_FromLong(0); }

  /* If the cache has not yet been allocated and prepared, do so now.
   * If it has already been allocated, just set some local "shortcut pointers"
   * and proceed directly to the query execution. */

  if (cache != NULL) {
    /* If the precison figure for this entity.field is already cached, just
     * retrieve it from the cache dictionary and return. */
    result_cache = cache->result_cache;
    assert (result_cache != NULL);
    result_cache_this_entity =
      PyDict_GetItemString(result_cache, entity_name); /* borrowed ref */

    if (result_cache_this_entity != NULL) {
      precision = PyDict_GetItemString(result_cache_this_entity, field_name);

      if (precision != NULL) {
        /* Cache hit.
         * PyDict_GetItemString returned borrowed ref, so we need to INCREF. */
        Py_INCREF(precision);
        return precision;
      }
    } else {
      /* There is not even a cache for this entity, so there cannot possibly be
       * one for this entity.field.  Create a new dictionary to hold the cached
       * precision figures for this entity. */
      result_cache_this_entity = PyDict_New();
      if (result_cache_this_entity == NULL) { goto fail; }

      status = PyDict_SetItemString(result_cache,
          entity_name, result_cache_this_entity
        );
      /* PyDict_SetItemString established its own ref. */
      Py_DECREF(result_cache_this_entity);
      if (status == -1) { goto fail; }
    }
    /* The precision figure was not cached; fall through and query the system
     * tables. */

    in_da = cache->in_da;
    out_da = cache->out_da;
    out_var = cache->out_da->sqlvar;
  } else {
    /* cache itself was NULL, so we're starting from scratch by allocating the
     * cache structure.  Won't need to explicitly deallocate it on error,
     * because CConnection's field cleanup code will take care of that. */
    cache = Transaction_get_con(cursor->trans)->desc_cache = kimem_main_malloc(
        sizeof(CursorDescriptionCache)
      );
    if (cache == NULL) { goto fail; }

    cache->in_da = (XSQLDA *) cache->in_da_mem;

    cache->out_da = (XSQLDA *) cache->out_da_mem;
    cache->out_var_sqldata = -1;
    cache->out_var_sqlind = SQLIND_NULL;
    out_var = cache->out_da->sqlvar;
    out_var->sqldata = (char *) &cache->out_var_sqldata;
    out_var->sqlind = &cache->out_var_sqlind;

    /* The dictionary result_cache will cache entity-specific dictionaries that
     * will contain the field precision figures that have been determined via
     * queries to system tables.
     * Notice that we attach result_cache to the CursorDescriptionCache object,
     * which is tracked by the CConnection, so on error, deallocation of
     * result_cache will be handled implicitly by the CConnection's
     * field cleanup code. */
    result_cache = cache->result_cache = PyDict_New();
    if (result_cache == NULL) { goto fail; }

    /* There was no cache at all, so there could not have been a cache for this
     * entity.  Create one. */
    result_cache_this_entity = PyDict_New();
    if (result_cache_this_entity == NULL) { goto fail; }

    status = PyDict_SetItemString(result_cache,
        entity_name, result_cache_this_entity
      );
    Py_DECREF(result_cache_this_entity);
    if (status == -1) { goto fail; }

    /* Set up the query output structures.  We know at design time exactly how
     * they should be configured, so there's no convoluted dynamism here, as
     * there is in servicing an arbitrary query that originated in Python
     * client code. */
    out_da = cache->out_da;
    out_da->version = SQLDA_VERSION_KIDB;
    out_da->sqln = 1;

    /* Set up the input structures (again, their configuration is mostly
     * static). */
    in_da = cache->in_da;
    in_da->version = SQLDA_VERSION_KIDB;
    in_da->sqln = 2;
    in_da->sqld = 2;

    /* Set the type flags of the input variables (they never change): */
    in_da->sqlvar      ->sqltype = SQL_TEXT;
    (in_da->sqlvar + 1)->sqltype = SQL_TEXT;

    /* Allocate the statement structures.  MUST set statement handles to NULL
     * before calls to isc_dsql_allocate_statement. */
    ENTER_GDAL

    cache->stmt_handle_table = NULL_STMT_HANDLE;
    cache->stmt_handle_stored_procedure = NULL_STMT_HANDLE;

    isc_dsql_allocate_statement(cursor->status_vector,
        Transaction_get_db_handle_p(cursor->trans),
        &cache->stmt_handle_table
      );
    if (DB_API_ERROR(cursor->status_vector)) {
      LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
      goto fail_with_operationalerror;
    }

    isc_dsql_allocate_statement(cursor->status_vector,
        Transaction_get_db_handle_p(cursor->trans),
        &cache->stmt_handle_stored_procedure
      );
    LEAVE_GDAL

    if (DB_API_ERROR(cursor->status_vector)) {
      goto fail_with_operationalerror;
    }

    /* Prepare the statements. */
    {
      isc_tr_handle *trans_handle_addr =
        Transaction_get_handle_p(cursor->trans);

      ENTER_GDAL
      isc_dsql_prepare(cursor->status_vector,
          trans_handle_addr,
          &cache->stmt_handle_table,
          sql_statement_table_length,
          (char *) sql_statement_table,
          Transaction_get_dialect(cursor->trans),
          out_da
        );

      if (DB_API_ERROR(cursor->status_vector)) {
        LEAVE_GDAL_WITHOUT_ENDING_CODE_BLOCK
        goto fail_with_operationalerror;
      }

      isc_dsql_prepare(cursor->status_vector,
          trans_handle_addr,
          &cache->stmt_handle_stored_procedure,
          sql_statement_stored_procedure_length,
          (char *) sql_statement_stored_procedure,
          Transaction_get_dialect(cursor->trans),
          out_da
        );
      LEAVE_GDAL

      if (DB_API_ERROR(cursor->status_vector)) {
        goto fail_with_operationalerror;
      }
    }
  }

  /* We are now done (allocating and preparing new)/(loading references to
   * existing) description cache structures. */
  assert (in_da != NULL);
  assert (out_da != NULL);
  assert (out_var != NULL);
  assert (out_var == out_da->sqlvar);

  /* Set the names of the relation and field for which we're determining
   * precision. */
  in_var = in_da->sqlvar; /* First input variable. */
  assert (in_var->sqltype == SQL_TEXT);
  in_var->sqllen = entity_name_length;
  in_var->sqldata = entity_name;

  in_var++; /* Second input variable. */
  assert (in_var->sqltype == SQL_TEXT);
  in_var->sqllen = field_name_length;
  in_var->sqldata = field_name;

  /* Execute the prepared statement. */
  switch (entity_type_code) {
    case ENTITY_TYPE_TABLE:
      {
        isc_tr_handle *trans_handle_addr =
          Transaction_get_handle_p(cursor->trans);

        ENTER_GDAL
        isc_dsql_execute2(cursor->status_vector,
            trans_handle_addr,
            &cache->stmt_handle_table,
            Transaction_get_dialect(cursor->trans),
            in_da,
            out_da
          );
        LEAVE_GDAL
      }
      break;

    case ENTITY_TYPE_STORED_PROCEDURE:
      {
        isc_tr_handle *trans_handle_addr =
          Transaction_get_handle_p(cursor->trans);

        ENTER_GDAL
        isc_dsql_execute2(cursor->status_vector,
            trans_handle_addr,
            &cache->stmt_handle_stored_procedure,
            Transaction_get_dialect(cursor->trans),
            in_da,
            out_da
          );
        LEAVE_GDAL
      }
      break;

    default:
      raise_exception(InternalError, "determine_field_precision called with"
        " invalid entity type directive.");
      goto fail;
  }

  if (DB_API_ERROR(cursor->status_vector)) {
    /* If we've recursed as far as possible, yet have run out of entity types
     * to try, we must give up and raise an error. */
    if (entity_type_code == ENTITY_TYPE_LAST) {
      exception_type = InternalError;
      goto fail;
    } else {
      /* Recursively try the next alternative entity type. */
      precision = determine_field_precision(
          (short) (entity_type_code + 1),
          entity_name, entity_name_length,
          field_name, field_name_length,
          cursor
        );
      if (precision == NULL) { goto fail; }
    }
  } else {
    Transaction_stats_record_ps_executed(cursor->trans);

    /* Both PyInt_FromLong and PyDict_SetItemString create new references, so
     * although we want to store one reference to the int object and return
     * another reference to the same object, there's no need to
     *   INCREF(precision)
     * here. */
    precision = PyInt_FromLong(
          cache->out_var_sqlind == SQLIND_NULL ? 0 : cache->out_var_sqldata
      );
    if (precision == NULL) { goto fail; }

    /* Cache the precision figure. */
    if (PyDict_SetItemString(result_cache_this_entity, field_name, precision)
        == -1
       )
    {
      Py_DECREF(precision);
      goto fail;
    }
  }

  assert (precision != NULL);
  assert (PyInt_CheckExact(precision));
  return precision;

  fail_with_operationalerror:
    exception_type = OperationalError;
    /* Fall through to fail: */
  fail:
    /* If no exception_type was specified, there should already be a Python
     * exception set. */
    if (exception_type == NULL) {
      assert (PyErr_Occurred());
    } else {
      raise_sql_exception(exception_type,
          "Unable to determine field precison from system tables: ",
          cursor->status_vector
        );
    }

    return NULL;
} /* determine_field_precision */

static void free_field_precision_cache(
    CursorDescriptionCache *cache, boolean should_try_to_free_stmt_handles,
    ISC_STATUS *status_vector
  )
{
  if (cache == NULL) { return; }

  /* Added should_try_to_free_stmt_handles flag so that the connection can
   * prevent this function from calling isc_dsql_free_statement if the
   * connection knows that it has lost its database handle (in some versions of
   * the Firebird client library, isc_dsql_free_statement causes a segfault
   * when the connection under which the handles were established is no longer
   * valid). */
  if (!should_try_to_free_stmt_handles) {
    cache->stmt_handle_table = NULL_STMT_HANDLE;
    cache->stmt_handle_stored_procedure = NULL_STMT_HANDLE;
  } else {
    assert (cache->stmt_handle_table != NULL_STMT_HANDLE);
    assert (cache->stmt_handle_stored_procedure != NULL_STMT_HANDLE);

    ENTER_GDAL
    isc_dsql_free_statement(status_vector,
        &cache->stmt_handle_table,
        DSQL_drop
      );

    isc_dsql_free_statement(status_vector,
        &cache->stmt_handle_stored_procedure,
        DSQL_drop
      );
    LEAVE_GDAL
  }

  /* Free the master cache dictionary, which will of course free its
   * subordinate (entity-specific) dictionaries: */
  Py_XDECREF(cache->result_cache);

  /* Free the passed cache object itself: */
  kimem_main_free(cache);
} /* free_field_precision_cache */