tdbc::postgres

Artifact [9e9bf4d5ff]
Login

Artifact 9e9bf4d5ffddf091bc9327fc0145a0815e6d549035f39e35a0dd6d60dcd7128a:

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);