@@ -2244,6 +2244,10 @@ export class NodeVariable extends RealVariable {
22442244 realTag === 'BitString' ) {
22452245 return new ValueVariable ( realTag , args ) ;
22462246 }
2247+
2248+ if ( realTag === 'TupleTableSlot' ) {
2249+ return new TupleTableSlotVariable ( args ) ;
2250+ }
22472251
22482252 return new NodeVariable ( realTag , args ) ;
22492253 }
@@ -5604,6 +5608,175 @@ class SimplehashElementsMember extends Variable {
56045608 }
56055609}
56065610
5611+ /*
5612+ * Represents values of attributes in TupleTableSlot with tupoutput applied.
5613+ */
5614+ class TupleTableSlotAttributesVariable extends Variable {
5615+ /* TupleTableSlot variable which we are rendering */
5616+ slot : TupleTableSlotVariable ;
5617+ constructor ( slot : TupleTableSlotVariable ) {
5618+ super ( '$attributes$' , '' , '' , '' , slot . context , slot . frameId , slot ) ;
5619+ this . slot = slot ;
5620+ }
5621+
5622+ isExpandable ( ) {
5623+ return true ;
5624+ }
5625+
5626+ async getTupleDesc ( ) {
5627+ const tupdesc = await this . slot . getMember ( 'tts_tupleDescriptor' ) ;
5628+ return tupdesc as RealVariable ;
5629+ }
5630+
5631+ async getDatums ( nvalid : number ) {
5632+ const expr = `((TupleTableSlot *)${ this . slot . getPointer ( ) } )->tts_values` ;
5633+ const datums = await this . debug . getArrayVariables ( expr , nvalid , this . frameId ) ;
5634+ return datums . map ( dv => dv . value ) ;
5635+ }
5636+
5637+ async getNulls ( nvalid : number ) {
5638+ const expr = `((TupleTableSlot *)${ this . slot . getPointer ( ) } )->tts_isnull` ;
5639+ const datums = await this . debug . getArrayVariables ( expr , nvalid , this . frameId ) ;
5640+ return datums . map ( dv => this . debug . extractBool ( dv ) ) ;
5641+ }
5642+
5643+ async getAttribute ( tupdesc : RealVariable , i : number ) {
5644+ const expr = `TupleDescAttr((TupleDesc)${ tupdesc . getPointer ( ) } , ${ i } )` ;
5645+ try {
5646+ const result = await this . evaluate ( expr ) ;
5647+ /*
5648+ * Pass simple pointer, because Variable.create is too heavy for
5649+ * such short operation. Anyway we know what we want to get (name
5650+ * of member).
5651+ */
5652+ return result . memoryReference ;
5653+ } catch ( err ) {
5654+ if ( ! ( isEvaluationError ( err ) && this . debug . type == dbg . DebuggerType . CodeLLDB ) ) {
5655+ throw err ;
5656+ }
5657+ }
5658+
5659+ /*
5660+ * TupleDescAttr declared 'static inline', so CodeLLDB complains
5661+ * with 'is ambiguous' error. All we have to do is just to retry.
5662+ */
5663+ const result = await this . evaluate ( expr ) ;
5664+ return result . memoryReference ;
5665+ }
5666+
5667+ async getTypeInfo ( formpgattribute : string , needoutput : boolean ) {
5668+ let result = await this . evaluate ( `((Form_pg_attribute)${ formpgattribute } )->atttypid` ) ;
5669+ const atttyp = Number ( result . result ) ;
5670+ if ( ! oidIsValid ( atttyp ) ) {
5671+ return ;
5672+ }
5673+
5674+ /*
5675+ * Now we must get typoutput Oid to get output representation of value.
5676+ * But know it is not safe to use 'getTypeOutputInfo', because if
5677+ * it's value is not valid Oid, then it throws ERROR, which is not
5678+ * good here. So we must search syscache directly.
5679+ */
5680+ const typeoid = this . debug . formatEnumValue ( 'SysCacheIdentifier' , 'TYPEOID' ) ;
5681+ result = await this . evaluateSysCache ( `SearchSysCache1(${ typeoid } , (Datum)${ atttyp } )` ) ;
5682+ if ( this . debug . isNull ( result ) ) {
5683+ return ;
5684+ }
5685+
5686+ const syscacheTuple = result . memoryReference ;
5687+
5688+ const typeform = await this . evaluate ( `GETSTRUCT((const HeapTupleData *)${ syscacheTuple } )` ) ;
5689+ result = await this . evaluate ( `(char *)((Form_pg_type)${ typeform . memoryReference } )->typname.data` ) ;
5690+ const typname = this . debug . extractString ( result ) ;
5691+
5692+ /* such early filter saves some us */
5693+ let typoutput : number | undefined ;
5694+ if ( needoutput ) {
5695+ const typoutputResult = await this . evaluate ( `((Form_pg_type)${ typeform . memoryReference } )->typoutput` ) ;
5696+ typoutput = Number ( typoutputResult . result ) ;
5697+ if ( ! oidIsValid ( typoutput ) ) {
5698+ typoutput = undefined ;
5699+ }
5700+ }
5701+
5702+ await this . evaluateVoid ( `ReleaseSysCache((HeapTuple)${ syscacheTuple } )` ) ;
5703+ return {
5704+ typoutput,
5705+ typname,
5706+ } ;
5707+ }
5708+
5709+ async doGetChildren ( ) {
5710+ const nvalid = await this . slot . getMemberValueNumber ( 'tts_nvalid' ) ;
5711+ if ( nvalid == 0 ) {
5712+ return [ ] ;
5713+ }
5714+
5715+ const tupdesc = await this . getTupleDesc ( ) ;
5716+ const datums = await this . getDatums ( nvalid ) ;
5717+ const nulls = await this . getNulls ( nvalid ) ;
5718+
5719+ const attrs = [ ] ;
5720+ for ( let i = 0 ; i < nvalid ; ++ i ) {
5721+ const isnull = nulls [ i ] ;
5722+
5723+ const attr = await this . getAttribute ( tupdesc , i ) ;
5724+ const typeInfo = await this . getTypeInfo ( attr , ! isnull ) ;
5725+
5726+ let value ;
5727+ let type ;
5728+ if ( typeInfo ) {
5729+ const { typoutput, typname} = typeInfo ;
5730+ type = typname ?? '' ;
5731+
5732+ if ( isnull ) {
5733+ 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 = '???' ;
5751+ }
5752+ }
5753+
5754+ /* value: type */
5755+ attrs . push ( new ScalarVariable ( value , type , '' , this . context , this ) ) ;
5756+ }
5757+
5758+ return attrs ;
5759+ }
5760+ }
5761+
5762+ class TupleTableSlotVariable extends NodeVariable {
5763+ constructor ( args : RealVariableArgs ) {
5764+ super ( 'TupleTableSlot' , args ) ;
5765+ }
5766+
5767+ async doGetChildren ( ) {
5768+ const children = await super . doGetChildren ( ) ;
5769+ if ( ! children ?. length ) {
5770+ return children ;
5771+ }
5772+
5773+ return [
5774+ ...children ,
5775+ new TupleTableSlotAttributesVariable ( this ) ,
5776+ ] ;
5777+ }
5778+ }
5779+
56075780/*
56085781 * Represents scalar integer field, that acts like value of enumeration
56095782 * (maybe bitmask), but defined using macros instead of enum values.
0 commit comments