Attachment "typesupport-silence.diff" to
ticket [14dee3be9a]
added by
anonymous
2022-07-20 14:26:02.
Index: generic/tdbcpostgres.c
==================================================================
--- generic/tdbcpostgres.c
+++ generic/tdbcpostgres.c
@@ -80,10 +80,11 @@
"nullable",
"out",
"precision",
"scale",
"type",
+ "inttype",
NULL
};
enum LiteralIndex {
LIT_EMPTY,
LIT_0,
@@ -95,14 +96,19 @@
LIT_NULLABLE,
LIT_OUT,
LIT_PRECISION,
LIT_SCALE,
LIT_TYPE,
+ LIT_INTTYPE,
LIT__END
};
-/* Object IDs for the Postgres data types */
+/*
+ * Object IDs for Postgres data types with supported Tcl_Obj intrep
+ * conversions in ResultSetConstructor(), or special TDBC-standard alternate
+ * names.
+ */
#define UNTYPEDOID 0
#define BYTEAOID 17
#define INT8OID 20
#define INT2OID 21
@@ -120,11 +126,11 @@
typedef struct PostgresDataType {
const char* name; /* Type name */
Oid oid; /* Type number */
} PostgresDataType;
-static const PostgresDataType dataTypes[] = {
+static const PostgresDataType specialTypes[] = {
{ "NULL", UNTYPEDOID},
{ "smallint", INT2OID },
{ "integer", INT4OID },
{ "tinyint", INT2OID },
{ "float", FLOAT8OID },
@@ -133,11 +139,10 @@
{ "timestamp", TIMESTAMPOID },
{ "bigint", INT8OID },
{ "date", DATEOID },
{ "time", TIMEOID },
{ "bit", BITOID },
- { "numeric", NUMERICOID },
{ "decimal", NUMERICOID },
{ "text", TEXTOID },
{ "varbinary", BYTEAOID },
{ "varchar", VARCHAROID } ,
{ "char", BPCHAROID },
@@ -231,11 +236,10 @@
*/
typedef struct PerInterpData {
size_t refCount; /* Reference count */
Tcl_Obj* literals[LIT__END]; /* Literal pool */
- Tcl_HashTable typeNumHash; /* Lookup table for type numbers */
} PerInterpData;
#define IncrPerInterpRefCount(x) \
do { \
++((x)->refCount); \
} while(0)
@@ -262,10 +266,11 @@
int stmtCounter; /* Counter for naming statements */
int flags;
int isolation; /* Current isolation level */
int readOnly; /* Read only connection indicator */
char * savedOpts[INDX_MAX]; /* Saved configuration options */
+ Tcl_HashTable typeHash; /* Lookup table for types (name -> oid) */
} ConnectionData;
/*
* Flags for the state of an POSTGRES connection
*/
@@ -699,12 +704,71 @@
const PGresult* message)
{
(void)clientData;
(void)message;
}
-
+
+/*
+ *-----------------------------------------------------------------------------
+ * SpecialType --
+ *
+ * Match a PostgreSQL type OID against the specialTypes[] array.
+ *
+ * Results:
+ * A valid index into specialTypes[], corresponding either to the element
+ * with a matching OID or the final pseudo-element (with a NULL name).
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static int
+SpecialType(Oid oid) {
+ /* TODO: bsearch or sthing */
+ int i = 0;
+ while (specialTypes[i].name != NULL
+ && specialTypes[i].oid != oid) {
+ i += 1;
+ }
+ return i;
+}
+
/*
+ *-----------------------------------------------------------------------------
+ * TypeEntryByOid --
+ *
+ * Given a pointer to the type lookup table from ConnectionData, look up a
+ * type by OID (value in the hash table).
+ *
+ * Results:
+ * A pointer to the matching Tcl_HashEntry, or NULL for a failed search.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+static Tcl_HashEntry *
+TypeEntryByOid(Tcl_HashTable *typeHash, Oid oid) {
+ Tcl_HashSearch search;
+ Tcl_HashEntry *curEntry;
+
+ for(curEntry = Tcl_FirstHashEntry(typeHash, &search);
+ curEntry != NULL;
+ curEntry = Tcl_NextHashEntry(&search)) {
+
+ if (PTR2UINT(Tcl_GetHashValue(curEntry)) == oid) {
+ break;
+ }
+ }
+
+ return curEntry;
+}
+
+/*
*-----------------------------------------------------------------------------
*
* ExecSimpleQuery --
*
* Executes given query.
@@ -1029,27 +1093,30 @@
*-----------------------------------------------------------------------------
*/
static int
ConfigureConnection(
- ConnectionData* cdata, /* Connection data */
- Tcl_Interp* interp, /* Tcl interpreter */
- int objc, /* Parameter count */
- Tcl_Obj* const objv[], /* Parameter data */
- int skip /* Number of parameters to skip */
+ ConnectionData* cdata, /* Connection data */
+ Tcl_Interp* interp, /* Tcl interpreter */
+ int objc, /* Parameter count */
+ Tcl_Obj* const objv[], /* Parameter data */
+ int skip /* Number of parameters to skip */
) {
- int optionIndex; /* Index of the current option in
- * ConnOptions */
- int optionValue; /* Integer value of the current option */
+ int optionIndex; /* Index of the current option in
+ * ConnOptions */
+ int optionValue; /* Integer value of the current option */
int i;
size_t j;
- char portval[10]; /* String representation of port number */
- char * encoding = NULL; /* Selected encoding name */
- int isolation = ISOL_NONE; /* Isolation level */
- int readOnly = -1; /* Read only indicator */
+ char portval[10]; /* String representation of port number */
+ char * encoding = NULL; /* Selected encoding name */
+ int isolation = ISOL_NONE; /* Isolation level */
+ int readOnly = -1; /* Read only indicator */
#define CONNINFO_LEN 1000
- char connInfo[CONNINFO_LEN]; /* Configuration string for PQconnectdb() */
+ char connInfo[CONNINFO_LEN]; /* Configuration string for PQconnectdb() */
+ PGresult *res; /* libpq result set of type oids and names */
+ int ntypes; /* Number of type tuples in res */
+ const PostgresDataType *curType; /* Pointer into specialTypes */
Tcl_Obj* retval;
Tcl_Obj* optval;
int vers; /* PostgreSQL major version */
@@ -1207,10 +1274,60 @@
if (PQsetClientEncoding(cdata->pgPtr, encoding) != 0) {
TransferPostgresError(interp, cdata->pgPtr);
return TCL_ERROR;
}
}
+
+ /*
+ * Type definitions
+ */
+ if (ExecSimpleQuery(interp, cdata->pgPtr,
+ "SELECT oid, typname FROM pg_type", &res) != TCL_OK) {
+ return TCL_ERROR;
+ } else {
+ for (i = 0, ntypes = PQntuples(res); i < ntypes; ++i) {
+ int isNew;
+ /*
+ * PostgreSQL documents OIDs as "unsigned four-byte integers", which
+ * could be outside the range of a (signed) long.
+ */
+ Tcl_Obj *oidObj;
+ Tcl_WideInt oid;
+ char *oidString, *typname;
+
+ oidString = PQgetvalue(res, i, 0);
+ typname = PQgetvalue(res, i, 1);
+
+ oidObj = Tcl_NewStringObj(oidString, -1);
+ if (Tcl_GetWideIntFromObj(interp, oidObj, &oid) != TCL_OK) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("PostgreSQL returned "
+ "the following type ID "
+ "(for a type named "
+ "\"%s\"), which is not "
+ "an integer value: %s",
+ typname, oidString));
+ Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY001",
+ "POSTGRES", "NULL", NULL);
+ Tcl_DecrRefCount(oidObj);
+ return TCL_ERROR;
+ }
+ Tcl_DecrRefCount(oidObj);
+
+ Tcl_HashEntry *entry =
+ Tcl_CreateHashEntry(&(cdata->typeHash),
+ typname,
+ &isNew);
+ Tcl_SetHashValue(entry, (ClientData)UINT2PTR(oid));
+ }
+ }
+ for (curType = specialTypes; curType->name != NULL; curType++) {
+ int isNew;
+ Tcl_HashEntry *entry = Tcl_CreateHashEntry(&(cdata->typeHash),
+ curType->name,
+ &isNew);
+ Tcl_SetHashValue(entry, (ClientData)UINT2PTR(curType->oid));
+ }
/* Transaction isolation level */
if (isolation != ISOL_NONE) {
if (ExecSimpleQuery(interp, cdata->pgPtr,
@@ -1302,10 +1419,11 @@
cdata->pgPtr = NULL;
cdata->stmtCounter = 0;
cdata->flags = 0;
cdata->isolation = ISOL_NONE;
cdata->readOnly = 0;
+ Tcl_InitHashTable(&(cdata->typeHash), TCL_STRING_KEYS);
IncrPerInterpRefCount(pidata);
Tcl_ObjectSetMetadata(thisObject, &connectionDataType, cdata);
/* Configure the connection */
@@ -1312,11 +1430,10 @@
if (ConfigureConnection(cdata, interp, objc, objv, skip) != TCL_OK) {
return TCL_ERROR;
}
return TCL_OK;
-
}
/*
*-----------------------------------------------------------------------------
*
@@ -1436,21 +1553,22 @@
/*
*-----------------------------------------------------------------------------
*
* ConnectionColumnsMethod --
*
- * Method that asks for the names of columns in a table
- * in the database (optionally matching a given pattern)
+ * Method that asks for the names of and certain information
+ * about columns in a table in the database (optionally matching
+ * a given pattern)
*
* Usage:
* $connection columns table ?pattern?
*
* Parameters:
* None.
*
* Results:
- * Returns the list of tables
+ * Returns the list of column dicts.
*
*-----------------------------------------------------------------------------
*/
static int
@@ -1524,11 +1642,11 @@
Tcl_GetString(sqlQuery), &res) != TCL_OK) {
Tcl_DecrRefCount(sqlQuery);
PQclear(resType);
return TCL_ERROR;
} else {
- int i, j;
+ int i, j, typeIndex;
retval = Tcl_NewObj();
Tcl_IncrRefCount(retval);
for (i = 0; i < PQntuples(res); i += 1) {
attrs = Tcl_NewObj();
@@ -1540,22 +1658,53 @@
/* Get the type name, by retrieving type oid */
j = PQfnumber(resType, columnName);
if (j >= 0) {
+ Tcl_Obj *typeNameObj;
+ Tcl_HashEntry *typeEntry;
+
typeOid = PQftype(resType, j);
-
- /* TODO: bsearch or sthing */
- j = 0 ;
- while (dataTypes[j].name != NULL
- && dataTypes[j].oid != typeOid) {
- j+=1;
- }
- if ( dataTypes[j].name != NULL) {
- Tcl_DictObjPut(NULL, attrs, literals[LIT_TYPE],
- Tcl_NewStringObj(dataTypes[j].name, -1));
- }
+ typeEntry = TypeEntryByOid(&(cdata->typeHash), typeOid);
+
+ if (typeEntry != NULL) {
+ typeNameObj =
+ Tcl_NewStringObj((char *)Tcl_GetHashKey(&(cdata->typeHash),
+ typeEntry), -1);
+ /*
+ * Record the server's internal type name under the
+ * non-TDBC-standard "inttype" field, even if we're also
+ * going to end up resorting to using that as the standard
+ * "type" value too.
+ */
+ Tcl_DictObjPut(NULL, attrs, literals[LIT_INTTYPE],
+ typeNameObj);
+ } else {
+ /*
+ * XXX: Use a stringification of the bogus OID instead?
+ */
+ Tcl_SetObjResult(interp,
+ Tcl_ObjPrintf("PostgreSQL returned an "
+ "unknown type ID (%u) for "
+ "column \"%s\"",
+ typeOid, columnName));
+ PQclear(resType);
+ PQclear(res);
+ return TCL_ERROR;
+ }
+
+ /*
+ * If we have an alias in specialTypes, use that for the TDBC
+ * standard type; otherwise, fall back to the server's internal
+ * type name.
+ */
+ typeIndex = SpecialType(typeOid);
+ Tcl_DictObjPut(NULL, attrs, literals[LIT_TYPE],
+ (specialTypes[typeIndex].name != NULL)
+ ? Tcl_NewStringObj(specialTypes[typeIndex].name,
+ -1)
+ : typeNameObj);
}
/* 1 is numeric_precision column number */
if (!PQgetisnull(res, i, 1)) {
@@ -1830,10 +1979,13 @@
ConnectionData* cdata /* Instance data for the connection */
) {
if (cdata->pgPtr != NULL) {
PQfinish(cdata->pgPtr);
}
+
+ Tcl_DeleteHashTable(&(cdata->typeHash));
+
DecrPerInterpRefCount(cdata->pidata);
ckfree(cdata);
}
/*
@@ -2357,11 +2509,11 @@
Tcl_Obj* paramName; /* Name of a parameter */
Tcl_Obj* paramDesc; /* Description of one parameter */
Tcl_Obj* dataTypeName; /* Name of a parameter's data type */
Tcl_Obj* retVal; /* Return value from this command */
Tcl_HashEntry* typeHashEntry;
- int i;
+ int i, typeIndex;
(void)dummy;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "");
return TCL_ERROR;
@@ -2386,17 +2538,44 @@
literals[LIT_INOUT]);
break;
default:
break;
}
- typeHashEntry =
- Tcl_FindHashEntry(&(pidata->typeNumHash),
- INT2PTR(sdata->paramDataTypes[i]));
- if (typeHashEntry != NULL) {
- dataTypeName = (Tcl_Obj*) Tcl_GetHashValue(typeHashEntry);
- Tcl_DictObjPut(NULL, paramDesc, literals[LIT_TYPE], dataTypeName);
+
+ /*
+ * Use the TDBC-standard type name if there is one...
+ */
+ typeIndex = SpecialType(sdata->paramDataTypes[i]);
+ if (specialTypes[typeIndex].name != NULL) {
+ dataTypeName = Tcl_NewStringObj(specialTypes[typeIndex].name, -1);
+ } else {
+ /*
+ * ...and fall back to the server's internal name if there isn't.
+ */
+ typeHashEntry = TypeEntryByOid(&cdata->typeHash,
+ sdata->paramDataTypes[i]);
+ if (typeHashEntry != NULL) {
+ dataTypeName =
+ Tcl_NewStringObj((char *)Tcl_GetHashKey(&(cdata->typeHash),
+ typeHashEntry), -1);
+ } else {
+ /*
+ * We should never be assigning an OID that isn't in typeHash,
+ * nor is the server likely to accept one.
+ */
+ Tcl_Panic("corrupt data type OID %u\n",
+ sdata->paramDataTypes[i]);
+
+ /*
+ * Silence compiler warning (compilers can't necessarily figure
+ * out that Tcl_Panic doesn't return).
+ */
+ return TCL_ERROR;
+ }
}
+
+ Tcl_DictObjPut(NULL, paramDesc, literals[LIT_TYPE], dataTypeName);
Tcl_DictObjPut(NULL, paramDesc, literals[LIT_PRECISION],
Tcl_NewWideIntObj(sdata->params[i].precision));
Tcl_DictObjPut(NULL, paramDesc, literals[LIT_SCALE],
Tcl_NewWideIntObj(sdata->params[i].scale));
Tcl_DictObjPut(NULL, retVal, paramName, paramDesc);
@@ -2436,10 +2615,12 @@
Tcl_Object thisObject = Tcl_ObjectContextObject(context);
/* The current statement object */
StatementData* sdata /* The current statement */
= (StatementData*) Tcl_ObjectGetMetadata(thisObject,
&statementDataType);
+ ConnectionData *cdata /* The current connection's metadata */
+ = sdata->cdata;
static const struct {
const char* name;
int flags;
} directions[] = {
{ "in", PARAM_IN },
@@ -2446,20 +2627,21 @@
{ "out", PARAM_OUT },
{ "inout", PARAM_IN | PARAM_OUT },
{ NULL, 0 }
};
int direction;
- int typeNum; /* Data type number of a parameter */
+ Oid typeOid; /* Data type OID of a parameter */
int precision; /* Data precision */
int scale; /* Data scale */
const char* paramName; /* Name of the parameter being set */
Tcl_Obj* targetNameObj; /* Name of the ith parameter in the statement */
const char* targetName; /* Name of a candidate parameter in the
* statement */
int matchCount = 0; /* Number of parameters matching the name */
Tcl_Obj* errorObj; /* Error message */
+ Tcl_HashEntry *typeEntry; /* Entry in the connection's type table */
int i;
(void)dummy;
/* Check parameters */
@@ -2476,17 +2658,42 @@
Tcl_ResetResult(interp);
} else {
++i;
}
if (i >= objc) goto wrongNumArgs;
- if (Tcl_GetIndexFromObjStruct(interp, objv[i], dataTypes,
- sizeof(dataTypes[0]), "SQL data type",
- TCL_EXACT, &typeNum) == TCL_OK) {
+
+ typeEntry = Tcl_FindHashEntry(&(cdata->typeHash),
+ Tcl_GetString(objv[i]));
+
+ if (typeEntry != NULL) {
+ /*
+ * XXX: Should there be a PTR2OID?
+ */
+ typeOid = PTR2UINT(Tcl_GetHashValue(typeEntry));
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("", 0));
++i;
} else {
+ const PostgresDataType *curType;
+ Tcl_Obj *specialTypesObj = Tcl_NewStringObj(specialTypes[0].name, -1);
+
+ for (curType = specialTypes + 1; curType->name != NULL; curType++) {
+ Tcl_AppendStringsToObj(specialTypesObj,
+ (curType + 1)->name == NULL ? ", or " : ", ",
+ curType->name,
+ NULL);
+ }
+
+ Tcl_SetObjResult(interp,
+ Tcl_ObjPrintf("bad SQL data type \"%s\": must be in "
+ "your PostgreSQL database's pg_type "
+ "table or be one of %s",
+ Tcl_GetString(objv[i]),
+ Tcl_GetString(specialTypesObj)));
+ Tcl_DecrRefCount(specialTypesObj);
return TCL_ERROR;
}
+
if (i < objc) {
if (Tcl_GetIntFromObj(interp, objv[i], &precision) == TCL_OK) {
++i;
} else {
return TCL_ERROR;
@@ -2510,14 +2717,14 @@
Tcl_ListObjIndex(NULL, sdata->subVars, i, &targetNameObj);
targetName = Tcl_GetString(targetNameObj);
if (!strcmp(paramName, targetName)) {
++matchCount;
sdata->params[i].flags = direction;
- if (sdata->paramDataTypes[i] != dataTypes[typeNum].oid) {
+ if (sdata->paramDataTypes[i] != typeOid) {
sdata->paramTypesChanged = 1;
}
- sdata->paramDataTypes[i] = dataTypes[typeNum].oid;
+ sdata->paramDataTypes[i] = typeOid;
sdata->params[i].precision = precision;
sdata->params[i].scale = scale;
}
}
if (matchCount == 0) {
@@ -3342,22 +3549,10 @@
pidata->refCount = 1;
for (i = 0; i < LIT__END; ++i) {
pidata->literals[i] = Tcl_NewStringObj(LiteralValues[i], -1);
Tcl_IncrRefCount(pidata->literals[i]);
}
- Tcl_InitHashTable(&(pidata->typeNumHash), TCL_ONE_WORD_KEYS);
- for (i = 0; dataTypes[i].name != NULL; ++i) {
- int isNew;
- Tcl_HashEntry* entry =
- Tcl_CreateHashEntry(&(pidata->typeNumHash),
- INT2PTR(dataTypes[i].oid),
- &isNew);
- Tcl_Obj* nameObj = Tcl_NewStringObj(dataTypes[i].name, -1);
- Tcl_IncrRefCount(nameObj);
- Tcl_SetHashValue(entry, (ClientData) nameObj);
- }
-
/*
* Find the connection class, and attach an 'init' method to it.
*/
@@ -3489,20 +3684,10 @@
DeletePerInterpData(
PerInterpData* pidata /* Data structure to clean up */
) {
int i;
- Tcl_HashSearch search;
- Tcl_HashEntry *entry;
- for (entry = Tcl_FirstHashEntry(&(pidata->typeNumHash), &search);
- entry != NULL;
- entry = Tcl_NextHashEntry(&search)) {
- Tcl_Obj* nameObj = (Tcl_Obj*) Tcl_GetHashValue(entry);
- Tcl_DecrRefCount(nameObj);
- }
- Tcl_DeleteHashTable(&(pidata->typeNumHash));
-
for (i = 0; i < LIT__END; ++i) {
Tcl_DecrRefCount(pidata->literals[i]);
}
ckfree(pidata);