Skip to content

Commit 535bf1a

Browse files
committed
feat: show TupleTableSlot attributes in 'value: type' form as members
Show values and their types of TupleTableSlot. Values are taken from 'tts_values' and nulls from 'tts_isnull'. For now only PG 18 is supported.
1 parent e4f1c1d commit 535bf1a

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

src/variables.ts

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)