@@ -1468,6 +1468,81 @@ export abstract class Variable {
14681468 return result ;
14691469 }
14701470
1471+ private async legacyOidOutputFunctionCall ( funcOid : string | number , datum : string ) {
1472+ /*
1473+ * Older systems do not have OidOutputFunctionCall(), so use
1474+ * FunctionCall1() instead.
1475+ */
1476+
1477+ /* Call function */
1478+ const result = await this . functionCall ( funcOid , '(char *)' , datum ) ;
1479+
1480+ /*
1481+ * Now process the result.
1482+ * Note that function can return NULL and it's up to caller check
1483+ * if this is correct value.
1484+ */
1485+ if ( this . debug . isNull ( result ) ) {
1486+ return null ;
1487+ }
1488+
1489+ /* Free allocated string */
1490+ const ptr = this . debug . extractPtrFromString ( result ) ;
1491+
1492+ /*
1493+ * Function (funcOid) can return either NULL or a valid string.
1494+ * NULL checked previously, so now if we fail to extract value, then
1495+ * it is not valid string output.
1496+ */
1497+ if ( ptr === null ) {
1498+ throw new EvaluationError ( `could not extract pointer from result: ${ result . result } ` ) ;
1499+ }
1500+
1501+ await this . pfree ( ptr ) ;
1502+ const str = this . debug . extractString ( result ) ;
1503+ if ( str === null ) {
1504+ throw new EvaluationError ( `could not extract string from result: ${ result . result } ` ) ;
1505+ }
1506+
1507+ return str ;
1508+ }
1509+
1510+ async oidOutputFunctionCall ( funcOid : string | number , datum : string ) {
1511+ this . checkCanAlloc ( ) ;
1512+
1513+ if ( this . context . hasOidOutputFunctionCall ) {
1514+ try {
1515+ const result = await this . evaluate ( `OidOutputFunctionCall(${ funcOid } , ${ datum } )` ) ;
1516+ if ( this . debug . isNull ( result ) ) {
1517+ return null ;
1518+ }
1519+
1520+ const ptr = this . debug . extractPtrFromString ( result ) ;
1521+ if ( ! ptr ) {
1522+ throw new EvaluationError ( `could not extract pointer from result: ${ result . result } ` ) ;
1523+ }
1524+
1525+ await this . pfree ( ptr ) ;
1526+ const str = this . debug . extractString ( result ) ;
1527+ if ( ! str ) {
1528+ throw new EvaluationError ( `could not extract string from result: ${ result . result } ` ) ;
1529+ }
1530+ return str ;
1531+ } catch ( err ) {
1532+ if ( ! isEvaluationError ( err ) ) {
1533+ throw err ;
1534+ }
1535+
1536+ logger . error ( err , 'could not invoke OidOutputFunctionCall - switching to legacy code' ) ;
1537+ const result = await this . legacyOidOutputFunctionCall ( funcOid , datum ) ;
1538+ this . context . hasOidOutputFunctionCall = false ;
1539+ return result ;
1540+ }
1541+ }
1542+
1543+ return await this . legacyOidOutputFunctionCall ( funcOid , datum ) ;
1544+ }
1545+
14711546 private haveSysCatCacheBreakpoint ( ) {
14721547 return ! ! vscode . debug . breakpoints . find ( b => {
14731548 if ( ! b . enabled ) {
@@ -2605,42 +2680,6 @@ class ExprNodeVariable extends NodeVariable {
26052680 return oid ;
26062681 } ;
26072682
2608- const evalStrWithPtr = async ( expr : string ) => {
2609- const result = await this . debug . evaluate ( expr , this . frameId ) ;
2610- const str = this . debug . extractString ( result ) ;
2611- if ( str === null ) {
2612- throw new EvaluationError ( `failed to get string from expr: ${ expr } ` ) ;
2613- }
2614-
2615- const ptr = this . debug . extractPtrFromString ( result ) ;
2616- if ( ptr === null ) {
2617- throw new EvaluationError ( `failed to get pointer from expr: ${ expr } ` ) ;
2618- }
2619- return [ str , ptr ] ;
2620- } ;
2621-
2622- const legacyOidOutputFunctionCall = async ( funcOid : number ) => {
2623- /*
2624- * Older systems do not have OidOutputFunctionCall(), so use
2625- * FunctionCall1() instead.
2626- */
2627- /* Call function */
2628- const result = await this . functionCall ( funcOid , '(char *)' , `((Const *)${ this . getPointer ( ) } )->constvalue)` ) ;
2629-
2630- /* Free allocated string */
2631- const ptr = this . debug . extractPtrFromString ( result ) ;
2632- if ( ptr === null ) {
2633- throw new EvaluationError ( `failed to extract pointer from result: "${ result . result } "` ) ;
2634- }
2635- await this . pfree ( ptr ) ;
2636-
2637- const str = this . debug . extractString ( result ) ;
2638- if ( str === null ) {
2639- throw new EvaluationError ( `failed to extract string from result: "${ result . result } "` ) ;
2640- }
2641- return str ;
2642- } ;
2643-
26442683 if ( await this . getMemberValueBool ( 'constisnull' ) ) {
26452684 return 'NULL' ;
26462685 }
@@ -2744,32 +2783,22 @@ class ExprNodeVariable extends NodeVariable {
27442783 }
27452784
27462785 const funcOid = await evalOid ( `*((Oid *)${ tupOutput } )` ) ;
2747- let repr ;
2748-
2749-
2750- if ( this . context . hasOidOutputFunctionCall ) {
2751- try {
2752- const [ str , ptr ] = await evalStrWithPtr ( `OidOutputFunctionCall(${ funcOid } , ((Const *)${ this . getPointer ( ) } )->constvalue)` ) ;
2753- await this . pfree ( ptr ) ;
2754- repr = str ;
2755- } catch ( e ) {
2756- if ( ! isEvaluationError ( e ) ) {
2757- throw e ;
2758- }
2759-
2760- repr = await legacyOidOutputFunctionCall ( funcOid ) ;
2761- this . context . hasOidOutputFunctionCall = false ;
2762- }
2763- } else {
2764- repr = await legacyOidOutputFunctionCall ( funcOid ) ;
2765- }
2786+ const constvalue = `((Const *)${ this . getPointer ( ) } )->constvalue` ;
2787+ const repr = await this . oidOutputFunctionCall ( funcOid , constvalue ) ;
27662788
27672789 await this . pfree ( tupOutput ) ;
27682790 await this . pfree ( tupIsVarlena ) ;
2769- if ( tupIOParam )
2770- { await this . pfree ( tupIOParam ) ; }
2791+ if ( tupIOParam ) {
2792+ await this . pfree ( tupIOParam ) ;
2793+ }
27712794
2772- return repr ;
2795+ /*
2796+ * We must not get NULL here, because even if string is empty it
2797+ * still be rendered as empty string, not NULL. This should be
2798+ * considered as error, but follow Postel's law - "be liberal
2799+ * in what you accept".
2800+ */
2801+ return repr ?? 'NULL' ;
27732802 }
27742803
27752804 private async formatOpExpr ( ) {
@@ -5639,7 +5668,7 @@ class TupleTableSlotAttributesVariable extends Variable {
56395668 const datums = await this . debug . getArrayVariables ( expr , nvalid , this . frameId ) ;
56405669 return datums . map ( dv => this . debug . extractBool ( dv ) ) ;
56415670 }
5642-
5671+
56435672 async getAttribute ( tupdesc : RealVariable , i : number ) {
56445673 const expr = `TupleDescAttr((TupleDesc)${ tupdesc . getPointer ( ) } , ${ i } )` ;
56455674 try {
@@ -5727,29 +5756,20 @@ class TupleTableSlotAttributesVariable extends Variable {
57275756 let type ;
57285757 if ( typeInfo ) {
57295758 const { typoutput, typname} = typeInfo ;
5730- type = typname ?? '' ;
57315759
5760+ type = typname ;
57325761 if ( isnull ) {
57335762 value = 'NULL' ;
5734- } else {
5735- const result = await this . evaluate ( `OidOutputFunctionCall(${ typoutput } , ${ datums [ i ] } )` ) ;
5736- value = this . debug . extractString ( result ) ?? '???' ;
5737-
5738- const ptr = this . debug . extractPtrFromString ( result ) ;
5739- if ( ptr === null ) {
5740- logger . warn ( 'OidOutputFunctionCall returned valid string, but failed to extract pointer from' , result . result ) ;
5741- } else {
5742- await this . pfree ( ptr ) ;
5743- }
5744- }
5745- } else {
5746- type = '' ;
5747- if ( isnull ) {
5748- value = 'NULL' ;
5749- } else {
5750- value = '???' ;
5763+ } else if ( typoutput ) {
5764+ value = await this . oidOutputFunctionCall ( typoutput , datums [ i ] ) ;
57515765 }
5766+ } else if ( isnull ) {
5767+ value = 'NULL' ;
57525768 }
5769+
5770+ /* fallback with default values */
5771+ value ??= '???' ;
5772+ type ??= '' ;
57535773
57545774 /* value: type */
57555775 attrs . push ( new ScalarVariable ( value , type , '' , this . context , this ) ) ;
0 commit comments