File: _kiconversion_field_precision.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 (409 lines) | stat: -rw-r--r-- 14,693 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
/* KInterbasDB Python Package - Implementation of Field Precision Determination
**
** 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>
*/

/* 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,
    CursorObject *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 up exception)
  */

  /* The relname and sqlname strings in an IB API XSQLVAR are not
  ** NULL-terminated (see IB 6 API Guide page 88), so the lengths are provided
  ** as parameters to this function rather than determined locally by strlen. */

  PyObject *precision;
  PyObject *result_cache;
  PyObject *result_cache_this_entity;

  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 = ?"
    ;
  unsigned short sql_statement_table_length = 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 */
    ;
  unsigned short sql_statement_stored_procedure_length =
    strlen(sql_statement_stored_procedure);

  /* The following variables are just local "excessive dereference reducers". */
  XSQLDA *in_da, *out_da;
  XSQLVAR *in_var;

  CursorDescriptionCache *cache = cursor->connection->desc_cache;
  /* 2003.02.17c: */
  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);
  }

  /* 2003.10.06: */
  /* 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 "dereference cache
  ** variables" 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;
    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) {
        /* PyDict_GetItemString borrows its reference, 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) {
        return PyErr_NoMemory();
      }

      if (PyDict_SetItemString(result_cache, entity_name, result_cache_this_entity)
          == -1
         )
      {
        return NULL;
      }
      /* 2003.02.17: Fix ref-count leak (PyDict_SetItemString INCREFs
      ** result_cache_this_entity, so we need to discard our reference to it.) */
      Py_DECREF(result_cache_this_entity);
    }
    /* The precision figure was not cached; fall through and query the system
    ** tables. */

    in_da = cache->in_da;
    out_da = cache->out_da;
  } else {
    /* Allocate the cache structure. */
    cache = cursor->connection->desc_cache = kimem_main_malloc(sizeof(CursorDescriptionCache));

    /* This dictionary will cache the precision figures that have been
    ** determined via queries to system tables. */
    result_cache = cache->result_cache = PyDict_New();
    if ( result_cache == NULL ) {
      return PyErr_NoMemory();
    }
    /* 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 ) {
      return PyErr_NoMemory();
    }

    if (PyDict_SetItemString(result_cache, entity_name, result_cache_this_entity)
        == -1
       )
    {
      return NULL;
    }
    /* 2003.02.17: Fix ref-count leak (PyDict_SetItemString INCREFs
    ** result_cache_this_entity, so we need to discard our reference to it.) */
    Py_DECREF(result_cache_this_entity);

    /* Set up the output structures.  We know at design time exactly how they
    ** should be configured; there's no convoluted dance of dynamism here, as
    ** there is in servicing a generic Python-level query. */
    /* 2003.03.31: */
    cache->out_da = (XSQLDA *) kimem_xsqlda_malloc(XSQLDA_LENGTH(1));
    out_da = cache->out_da;
    out_da->version = SQLDA_VERSION1;
    out_da->sqln = 1;

    /* Set up the input structures (again, their configuration is mostly
    ** static). */
    /* 2003.03.31: */
    cache->in_da = (XSQLDA *) kimem_xsqlda_malloc(XSQLDA_LENGTH(2));
    in_da = cache->in_da;
    in_da->version = SQLDA_VERSION1;
    in_da->sqln = 2;
    in_da->sqld = 2;

    in_da->sqlvar      ->sqltype = SQL_TEXT;
    (in_da->sqlvar + 1)->sqltype = SQL_TEXT;

    /* Allocate the statement structures. */
    /* MUST set statement handles to NULL before isc_dsql_allocate_statement
    ** calls. */
    ENTER_DB
    cache->stmt_handle_table = NULL;
    isc_dsql_allocate_statement( cursor->status_vector,
        &(cursor->connection->db_handle),
        &(cache->stmt_handle_table)
      );

    cache->stmt_handle_stored_procedure = NULL;
    isc_dsql_allocate_statement( cursor->status_vector,
        &(cursor->connection->db_handle),
        &(cache->stmt_handle_stored_procedure)
      );
    LEAVE_DB

    if ( DB_API_ERROR(cursor->status_vector) ) {
      /* 2003.02.17c: */
      exception_type = OperationalError;
      goto DETERMINE_FIELD_PRECISION_ERROR;
    }

    /* Prepare the statements. */
    {
      /* 2003.10.15a:  Don't call CON_GET_TRANS_HANDLE[_ADDR] without GIL. */
      isc_tr_handle *trans_handle_addr = CON_GET_TRANS_HANDLE_ADDR(cursor->connection);

      ENTER_DB
      isc_dsql_prepare( cursor->status_vector,
          trans_handle_addr,
          &(cache->stmt_handle_table),
          sql_statement_table_length,
          (char *) sql_statement_table,
          cursor->connection->dialect,
          out_da
        );

      isc_dsql_prepare( cursor->status_vector,
          trans_handle_addr,
          &(cache->stmt_handle_stored_procedure),
          sql_statement_stored_procedure_length,
          (char *) sql_statement_stored_procedure,
          cursor->connection->dialect,
          out_da
        );
      LEAVE_DB
    }

    if ( DB_API_ERROR(cursor->status_vector) ) {
      /* 2003.02.17c: */
      exception_type = OperationalError;
      goto DETERMINE_FIELD_PRECISION_ERROR;
    }

    /* cache->out_var points to the first (and only) element of
    ** out_da->sqlvar ; cache->out_var is nothing more than a convenient
    ** reference. */
    cache->out_var = out_da->sqlvar;
    /* These next two are freed in _kicore_connection.c/delete_connection. */
    cache->out_var->sqldata = (char *) kimem_main_malloc(sizeof(short));
    /* We won't actually use the null status of the output field (it will never
    ** be NULL), but the API requires that space be allocated anyway. */
    cache->out_var->sqlind = (short *) kimem_main_malloc(sizeof(short));
  } /* We are now done (allocating and preparing new)/(loading references to
    ** existing) description cache structure. */

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

  in_var++; /* Second input variable. */
  in_var->sqllen = field_name_length;
  in_var->sqldata = field_name;

  /* Execute the prepared statement. */
  switch (entity_type_code) {
    case ENTITY_TYPE_TABLE:
      {
        /* 2003.10.15a:  Don't call CON_GET_TRANS_HANDLE[_ADDR] without GIL. */
        isc_tr_handle *trans_handle_addr = CON_GET_TRANS_HANDLE_ADDR(cursor->connection);

        ENTER_DB
        isc_dsql_execute2( cursor->status_vector,
            trans_handle_addr,
            &(cache->stmt_handle_table),
            cursor->connection->dialect,
            in_da,
            out_da
          );
        LEAVE_DB
      }
      break;
    case ENTITY_TYPE_STORED_PROCEDURE:
      {
        /* 2003.10.15a:  Don't call CON_GET_TRANS_HANDLE[_ADDR] without GIL. */
        isc_tr_handle *trans_handle_addr = CON_GET_TRANS_HANDLE_ADDR(cursor->connection);

        ENTER_DB
        isc_dsql_execute2( cursor->status_vector,
            trans_handle_addr,
            &(cache->stmt_handle_stored_procedure),
            cursor->connection->dialect,
            in_da,
            out_da
          );
        LEAVE_DB
      }
      break;
    default:
      raise_exception(InternalError, "determine_field_precision called with"
        " invalid entity type directive.");
  }

  if ( DB_API_ERROR(cursor->status_vector) ) {
    /* If we've run out of entity types to try, we must give up and raise an
    ** error. */
    if (entity_type_code == ENTITY_TYPE_LAST) {
      /* 2003.02.17c: */
      exception_type = InternalError;
      goto DETERMINE_FIELD_PRECISION_ERROR;
    } 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
        );
    }
  } else {
    /* Both PyInt_FromLong and PyDict_SetItemString create new references, so
    ** there's no need to increment the reference count of precision here. */
    precision = PyInt_FromLong( (long) *( (short *) cache->out_var->sqldata ) );

    /* Cache the precision figure. */
    if (PyDict_SetItemString(result_cache_this_entity, field_name, precision)
        == -1
       )
    {
      return NULL;
    }
    /* 2003.02.17: No need to 'Py_DECREF(precision)' here, because we pass
    ** owernship of the reference we hold upward to the caller. */
  }

  return precision;

/* 2003.02.17c: */
DETERMINE_FIELD_PRECISION_ERROR:
  assert (exception_type != NULL);
  raise_sql_exception( exception_type,
      "Unable to determine field precison from system tables: ",
      cursor->status_vector
    );
  /* 2003.02.17c: Closing the cursor here is acceptable because this is known
  ** to be a debilitating internal error. */
  close_cursor(cursor); /* 2003.02.17c: No change needed. */
  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;

  /* 2003.10.06: Added should_try_to_free_stmt_handles 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 becomes unsafe 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;
    cache->stmt_handle_stored_procedure = NULL;
  } else {
    ENTER_DB
    assert (cache->stmt_handle_table != NULL);
    isc_dsql_free_statement( status_vector,
        &(cache->stmt_handle_table),
        DSQL_drop
      );

    assert (cache->stmt_handle_stored_procedure != NULL);
    isc_dsql_free_statement( status_vector,
        &(cache->stmt_handle_stored_procedure),
        DSQL_drop
      );
    LEAVE_DB
  }

  assert (cache->in_da != NULL);
  assert (cache->out_da != NULL);

  /* These next two were allocated in _kiconversion_field_precision.c .
  ** Note that cache->out_var itself is a member of cache->out_da; it is
  ** not allocated separately (but cache->out_var's components sqldata and
  ** sqlind are allocated separately). */
  kimem_main_free(cache->out_var->sqldata);
  kimem_main_free(cache->out_var->sqlind);

  /* 2003.03.31: */
  kimem_xsqlda_free(cache->in_da);
  kimem_xsqlda_free(cache->out_da);

  assert (cache->result_cache != NULL);
  Py_DECREF(cache->result_cache);

  kimem_main_free(cache);
} /* free_field_precision_cache */