如何在PostgreSQL的C函数中操作Tuple及嵌套复合类型?
嘿,刚好之前折腾过PostgreSQL的C扩展开发,Tuple操作这块确实官方文档例子少得可怜,我给你直接上实操代码和核心要点,包你能快速上手!
一、读取Tuple(含嵌套复合类型)
PostgreSQL里的行数据都是以HeapTuple结构体传递的,读取字段主要靠heap_getattr函数。如果是嵌套复合类型,它的字段值本质是一个打包后的HeapTupleHeader,提取出来后就能当成普通Tuple处理。
#include "postgres.h" #include "fmgr.h" #include "executor/executor.h" #include "utils/heaptuple.h" #include "utils/tuple.h" #include "utils/typcache.h" PG_MODULE_MAGIC; // 触发器中读取Tuple(含嵌套复合类型)的示例函数 PG_FUNCTION_INFO_V1(read_nested_tuple); Datum read_nested_tuple(PG_FUNCTION_ARGS) { // 从触发器上下文获取Tuple和元数据 TriggerData *trigdata = (TriggerData *) fcinfo->context; HeapTuple target_tuple = trigdata->tg_new; TupleDesc tup_desc = trigdata->tg_relation->rd_att; // 1. 读取普通text字段:比如表中的name字段 Datum name_datum = heap_getattr(target_tuple, Anum_mytable_name, tup_desc, NULL); char *name_str = TextDatumGetCString(name_datum); elog(INFO, "读取到普通字段name: %s", name_str); // 2. 读取嵌套复合类型字段:比如address(自定义类型address_type,包含street、city) Datum addr_datum = heap_getattr(target_tuple, Anum_mytable_address, tup_desc, NULL); HeapTupleHeader addr_header = DatumGetHeapTupleHeader(addr_datum); // 通过类型缓存获取嵌套类型的元数据 TypeCacheEntry *addr_typcache = lookup_type_cache(get_typid_by_name("address_type"), TYPECACHE_TUPLE_DESC); TupleDesc addr_tup_desc = addr_typcache->tupDesc; // 读取嵌套类型的street字段 Datum street_datum = heap_getattr(HeapTupleFromHeader(addr_header), Anum_address_type_street, addr_tup_desc, NULL); char *street_str = TextDatumGetCString(street_datum); elog(INFO, "读取到嵌套字段street: %s", street_str); // 读取嵌套类型的city字段 Datum city_datum = heap_getattr(HeapTupleFromHeader(addr_header), Anum_address_type_city, addr_tup_desc, NULL); char *city_str = TextDatumGetCString(city_datum); elog(INFO, "读取到嵌套字段city: %s", city_str); PG_RETURN_VOID(); }
二、更新Tuple(含嵌套字段修改)
更新Tuple不能直接修改原结构体,得先把原Tuple的字段值提取出来,修改后再重新构建新Tuple。嵌套字段的修改逻辑和主Tuple一致,只是多了一层“拆包-修改-打包”的过程。
// 触发器中更新Tuple(含嵌套字段)的示例函数 PG_FUNCTION_INFO_V1(update_nested_tuple); Datum update_nested_tuple(PG_FUNCTION_ARGS) { TriggerData *trigdata = (TriggerData *) fcinfo->context; HeapTuple old_tuple = trigdata->tg_old; TupleDesc tup_desc = trigdata->tg_relation->rd_att; Datum new_values[Natts_mytable]; bool is_null[Natts_mytable]; // 先把原Tuple的所有字段值和null状态复制到数组中 heap_deform_tuple(old_tuple, tup_desc, new_values, is_null); // 1. 修改普通字段:更新name的值 new_values[Anum_mytable_name - 1] = CStringGetTextDatum("更新后的名字"); is_null[Anum_mytable_name - 1] = false; // 2. 修改嵌套复合类型字段:更新address的city // 先拆包原嵌套Tuple HeapTupleHeader old_addr_header = DatumGetHeapTupleHeader(new_values[Anum_mytable_address - 1]); HeapTuple old_addr_tuple = HeapTupleFromHeader(old_addr_header); TypeCacheEntry *addr_typcache = lookup_type_cache(get_typid_by_name("address_type"), TYPECACHE_TUPLE_DESC); TupleDesc addr_tup_desc = addr_typcache->tupDesc; Datum addr_new_values[Natts_address_type]; bool addr_is_null[Natts_address_type]; // 提取嵌套Tuple的字段值 heap_deform_tuple(old_addr_tuple, addr_tup_desc, addr_new_values, addr_is_null); // 修改city字段 addr_new_values[Anum_address_type_city - 1] = CStringGetTextDatum("新城市"); addr_is_null[Anum_address_type_city - 1] = false; // 重新打包嵌套Tuple HeapTuple new_addr_tuple = heap_form_tuple(addr_tup_desc, addr_new_values, addr_is_null); new_values[Anum_mytable_address - 1] = HeapTupleGetDatum(new_addr_tuple); is_null[Anum_mytable_address - 1] = false; // 构建新的主Tuple并返回 HeapTuple new_tuple = heap_form_tuple(tup_desc, new_values, is_null); trigdata->tg_new = new_tuple; PG_RETURN_DATUM(HeapTupleGetDatum(new_tuple)); }
三、创建全新的Tuple(含嵌套复合类型)
创建Tuple需要先构建每个字段的Datum值,嵌套类型要先创建好对应的Tuple,再把它打包成Datum传入主Tuple的字段数组。
// 创建含嵌套复合类型的新Tuple示例函数 PG_FUNCTION_INFO_V1(create_nested_tuple); Datum create_nested_tuple(PG_FUNCTION_ARGS) { // 1. 先创建嵌套的address Tuple TypeCacheEntry *addr_typcache = lookup_type_cache(get_typid_by_name("address_type"), TYPECACHE_TUPLE_DESC); TupleDesc addr_tup_desc = addr_typcache->tupDesc; Datum addr_values[Natts_address_type]; bool addr_is_null[Natts_address_type] = {false}; // 所有字段非空 addr_values[Anum_address_type_street - 1] = CStringGetTextDatum("456 Oak Ave"); addr_values[Anum_address_type_city - 1] = CStringGetTextDatum("Los Angeles"); HeapTuple addr_tuple = heap_form_tuple(addr_tup_desc, addr_values, addr_is_null); Datum addr_datum = HeapTupleGetDatum(addr_tuple); // 2. 创建主Tuple(假设表mytable有name和address两个字段) Relation target_rel = heap_open(get_relname_relid("mytable"), AccessShareLock); TupleDesc main_tup_desc = target_rel->rd_att; Datum main_values[Natts_mytable]; bool main_is_null[Natts_mytable] = {false}; main_values[Anum_mytable_name - 1] = CStringGetTextDatum("Alice"); main_values[Anum_mytable_address - 1] = addr_datum; HeapTuple main_tuple = heap_form_tuple(main_tup_desc, main_values, main_is_null); // 可选:将新Tuple插入到表中 heap_insert(target_rel, main_tuple); heap_close(target_rel, AccessShareLock); PG_RETURN_DATUM(HeapTupleGetDatum(main_tuple)); }
关键注意事项
- 必须包含核心头文件:
postgres.h、fmgr.h、utils/heaptuple.h、utils/typcache.h这些是操作Tuple的基础 - 用
lookup_type_cache获取复合类型的元数据,比手动构建更安全,还能避免重复计算 - Datum类型转换要对应:比如text类型用
TextDatumGetCString和CStringGetTextDatum,其他类型有对应的转换宏 - 触发器函数要先检查
fcinfo->context是否为TriggerData类型,避免非法访问崩溃
内容的提问来源于stack exchange,提问作者Shore
相关产品推荐
相关产品推荐

