Skip to content

Commit 8e343ab

Browse files
Optimize agtype_build_map argument processing
Add a per-function type cache backed by a hash table to avoid repeated type categorization for previously seen OIDs. Add a fast path for non-variadic calls that processes arguments directly from FunctionCallInfo, avoiding extract_variadic_args() and its temporary allocations in the common case. Add a temporary MemoryContext in agtype_build_map() for agtype_value allocations.
1 parent eaa7540 commit 8e343ab

1 file changed

Lines changed: 258 additions & 29 deletions

File tree

‎src/backend/utils/adt/agtype.c‎

Lines changed: 258 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@
6565
#include "catalog/ag_label.h"
6666
#include "utils/ag_func.h"
6767
#include "utils/agtype_traversal.h"
68+
#include "common/hashfn.h"
69+
70+
6871
/* State structure for Percentile aggregate functions */
6972
typedef struct PercentileGroupAggState
7073
{
@@ -98,6 +101,64 @@ typedef enum /* type categories for datum_to_agtype */
98101
AGT_TYPE_OTHER /* all else */
99102
} agt_type_category;
100103

104+
#include "lib/simplehash.h"
105+
106+
/*
107+
* SQL function argument entry.
108+
*/
109+
typedef struct ArgType
110+
{
111+
Oid type_oid;
112+
Datum value;
113+
bool is_null;
114+
} ArgType;
115+
116+
/*
117+
* Used for caching type information.
118+
*/
119+
typedef struct TypeInfo
120+
{
121+
Oid oid;
122+
agt_type_category category;
123+
Oid outfuncoid;
124+
uint32 hash_value;
125+
char status;
126+
} TypeInfo;
127+
128+
#define SH_PREFIX type_cache
129+
#define SH_ELEMENT_TYPE TypeInfo
130+
#define SH_KEY_TYPE Oid
131+
#define SH_KEY oid
132+
#define SH_EQUAL(tb, a, b) (a == b)
133+
#define SH_HASH_KEY(tb, key) hash_uint32((uint32) key)
134+
#define SH_STORE_HASH
135+
#define SH_GET_HASH(tb, a) (a)->hash_value
136+
#define SH_SCOPE static __attribute__((unused))
137+
138+
#define SH_DECLARE
139+
#define SH_DEFINE
140+
#include "lib/simplehash.h"
141+
142+
/*
143+
* Initial size of type cache hash table.
144+
*/
145+
#define INIT_TYPE_CACHE_SIZE 8
146+
147+
/*
148+
* Cache for the agtype_build_map function.
149+
* Stores type information cache such as type category and outfuncoid.
150+
* Also stores a temporary memory context which is used for agtype_value allocations.
151+
*/
152+
typedef struct
153+
{
154+
type_cache_hash* type_cache;
155+
156+
MemoryContext tmp_ctx;
157+
158+
} agtype_build_map_cache;
159+
160+
161+
101162
static inline Datum agtype_from_cstring(char *str, int len);
102163
size_t check_string_length(size_t len);
103164
static void agtype_in_agtype_annotation(void *pstate, char *annotation);
@@ -2860,6 +2921,114 @@ Datum edge_to_jsonb(PG_FUNCTION_ARGS)
28602921
PG_RETURN_DATUM(result);
28612922
}
28622923

2924+
/*
2925+
* Init temporary memory context and empty type cache.
2926+
*/
2927+
static agtype_build_map_cache * init_agtype_type_cache(FunctionCallInfo fcinfo)
2928+
{
2929+
MemoryContext oldctx;
2930+
agtype_build_map_cache *cache;
2931+
2932+
oldctx = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
2933+
2934+
cache = palloc(sizeof(agtype_build_map_cache));
2935+
2936+
cache->type_cache =
2937+
type_cache_create(fcinfo->flinfo->fn_mcxt, 8, NULL);
2938+
2939+
cache->tmp_ctx =
2940+
AllocSetContextCreate(fcinfo->flinfo->fn_mcxt,
2941+
"agtype_build_map tmp",
2942+
ALLOCSET_DEFAULT_SIZES);
2943+
2944+
fcinfo->flinfo->fn_extra = cache;
2945+
2946+
MemoryContextSwitchTo(oldctx);
2947+
2948+
return cache;
2949+
}
2950+
2951+
/*
2952+
* Get TypeInfo from the cache if present.
2953+
* If not, get type information from agtype_categorize_type function and cache the result.
2954+
*/
2955+
static TypeInfo * get_type_cache(FunctionCallInfo fcinfo, Oid type)
2956+
{
2957+
MemoryContext oldctx;
2958+
agtype_build_map_cache *cache;
2959+
TypeInfo *entry;
2960+
bool found;
2961+
2962+
cache = (agtype_build_map_cache *) fcinfo->flinfo->fn_extra;
2963+
2964+
entry =
2965+
type_cache_lookup(cache->type_cache,
2966+
type);
2967+
2968+
if (entry != NULL)
2969+
{
2970+
return entry;
2971+
}
2972+
2973+
2974+
/*
2975+
* Type was not cached.
2976+
* Insert new entry.
2977+
*/
2978+
oldctx = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
2979+
2980+
entry =
2981+
type_cache_insert(cache->type_cache,
2982+
type,
2983+
&found);
2984+
2985+
MemoryContextSwitchTo(oldctx);
2986+
Assert(!found);
2987+
2988+
entry->oid = type;
2989+
2990+
2991+
if (type == InvalidOid)
2992+
{
2993+
entry->category = AGT_TYPE_NULL;
2994+
entry->outfuncoid = InvalidOid;
2995+
}
2996+
else
2997+
{
2998+
agtype_categorize_type(type,
2999+
&entry->category,
3000+
&entry->outfuncoid);
3001+
}
3002+
3003+
return entry;
3004+
}
3005+
3006+
/*
3007+
* Helper function for getting argument info. Writes the result into ArgType *
3008+
*/
3009+
static inline void get_arg_info(ArgType *arg,
3010+
FunctionCallInfo fcinfo,
3011+
Datum* args,
3012+
bool* nulls,
3013+
Oid* types,
3014+
int idx,
3015+
bool is_variadic)
3016+
{
3017+
if (!is_variadic)
3018+
{
3019+
arg->value = fcinfo->args[idx].value;
3020+
arg->is_null = fcinfo->args[idx].isnull;
3021+
arg->type_oid = get_fn_expr_argtype(fcinfo->flinfo, idx);
3022+
}
3023+
else
3024+
{
3025+
arg->value = args[idx];
3026+
arg->is_null = nulls[idx];
3027+
arg->type_oid = types[idx];
3028+
}
3029+
3030+
}
3031+
28633032
static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo)
28643033
{
28653034
int nargs;
@@ -2868,55 +3037,71 @@ static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo)
28683037
Datum *args;
28693038
bool *nulls;
28703039
Oid *types;
3040+
3041+
bool is_expr_variadic = get_fn_expr_variadic(fcinfo->flinfo);
28713042

2872-
/* build argument values to build the object */
2873-
nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
3043+
if (!is_expr_variadic)
3044+
{
3045+
nargs = fcinfo->nargs;
3046+
}
3047+
else
3048+
{
3049+
/* build argument values to build the object */
3050+
nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls);
3051+
}
28743052

28753053
if (nargs < 0)
28763054
{
28773055
return NULL;
28783056
}
3057+
28793058

28803059
if (nargs % 2 != 0)
28813060
{
28823061
ereport(
28833062
ERROR,
28843063
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2885-
errmsg("argument list must have been even number of elements"),
2886-
errhint("The arguments of agtype_build_map() must consist of alternating keys and values.")));
3064+
errmsg("argument list must have been even number of elements"),
3065+
errhint("The arguments of agtype_build_map() must consist of alternating keys and values.")));
28873066
}
28883067

28893068
memset(&result, 0, sizeof(agtype_in_state));
28903069

28913070
result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_OBJECT,
2892-
NULL);
3071+
NULL);
28933072

28943073
/* iterate through the arguments and build the object */
28953074
for (i = 0; i < nargs; i += 2)
28963075
{
3076+
TypeInfo* type_info;
3077+
ArgType arg_info;
3078+
3079+
get_arg_info(&arg_info, fcinfo, args, nulls, types, i, is_expr_variadic);
3080+
type_info = get_type_cache(fcinfo, arg_info.type_oid);
3081+
28973082
/* process key */
2898-
if (nulls[i])
3083+
if (arg_info.is_null)
28993084
{
29003085
ereport(ERROR,
29013086
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2902-
errmsg("argument %d: key must not be null", i + 1)));
3087+
errmsg("argument %d: key must not be null", i + 1)));
29033088
}
29043089

29053090
/*
2906-
* If the key is agtype, we need to extract it as an agtype string and
2907-
* push the value.
2908-
*/
2909-
if (types[i] == AGTYPEOID)
3091+
* If the key is agtype, we need to extract it as an agtype string and
3092+
* push the value.
3093+
*/
3094+
if (arg_info.type_oid == AGTYPEOID)
29103095
{
29113096
agtype_value *agtv = NULL;
29123097

2913-
agtv = tostring_helper(args[i], types[i],
2914-
"agtype_build_map_as_agtype_value");
3098+
agtv = tostring_helper(arg_info.value, arg_info.type_oid,
3099+
"agtype_build_map_as_agtype_value");
29153100
if (agtv == NULL)
29163101
{
29173102
ereport(ERROR,
29183103
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2919-
errmsg("argument %d: key must not be null", i + 1)));
3104+
errmsg("argument %d: key must not be null", i + 1)));
29203105

29213106
}
29223107

@@ -2927,11 +3112,26 @@ static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo)
29273112
}
29283113
else
29293114
{
2930-
add_agtype(args[i], false, &result, types[i], true);
3115+
datum_to_agtype(arg_info.value,
3116+
false,
3117+
&result,
3118+
type_info->category,
3119+
type_info->outfuncoid,
3120+
true);
29313121
}
2932-
2933-
/* process value */
2934-
add_agtype(args[i + 1], nulls[i + 1], &result, types[i + 1], false);
3122+
get_arg_info(&arg_info, fcinfo, args, nulls, types, i + 1, is_expr_variadic);
3123+
type_info = get_type_cache(fcinfo, arg_info.type_oid);
3124+
3125+
datum_to_agtype(arg_info.value,
3126+
arg_info.is_null,
3127+
&result,
3128+
arg_info.is_null
3129+
? AGT_TYPE_NULL
3130+
: type_info->category,
3131+
arg_info.is_null
3132+
? InvalidOid
3133+
: type_info->outfuncoid,
3134+
false);
29353135
}
29363136

29373137
result.res = push_agtype_value(&result.parse_state, WAGT_END_OBJECT, NULL);
@@ -2945,21 +3145,36 @@ PG_FUNCTION_INFO_V1(agtype_build_map);
29453145
*/
29463146
Datum agtype_build_map(PG_FUNCTION_ARGS)
29473147
{
3148+
agtype_build_map_cache *cache;
3149+
MemoryContext oldctx;
29483150
agtype_value *result = NULL;
29493151
agtype *agt_result = NULL;
29503152

3153+
cache = (agtype_build_map_cache *) fcinfo->flinfo->fn_extra;
3154+
3155+
if (cache == NULL)
3156+
{
3157+
cache = init_agtype_type_cache(fcinfo);
3158+
}
3159+
3160+
MemoryContextReset(cache->tmp_ctx);
3161+
3162+
/*
3163+
* Use temporary memory context for agtype_value allocations.
3164+
*/
3165+
oldctx = MemoryContextSwitchTo(cache->tmp_ctx);
3166+
29513167
result = agtype_build_map_as_agtype_value(fcinfo);
3168+
3169+
MemoryContextSwitchTo(oldctx);
3170+
29523171
if (result == NULL)
2953-
{
29543172
PG_RETURN_NULL();
2955-
}
29563173

29573174
agt_result = agtype_value_to_agtype(result);
2958-
pfree_agtype_value(result);
29593175

29603176
PG_RETURN_POINTER(agt_result);
29613177
}
2962-
29633178
PG_FUNCTION_INFO_V1(agtype_build_map_noargs);
29643179

29653180
/*
@@ -2989,20 +3204,34 @@ PG_FUNCTION_INFO_V1(agtype_build_map_nonull);
29893204
*/
29903205
Datum agtype_build_map_nonull(PG_FUNCTION_ARGS)
29913206
{
3207+
agtype_build_map_cache *cache;
3208+
MemoryContext oldctx;
29923209
agtype_value *result = NULL;
29933210
agtype *agt_result;
29943211

2995-
result = agtype_build_map_as_agtype_value(fcinfo);
2996-
if (result == NULL)
3212+
cache = (agtype_build_map_cache *) fcinfo->flinfo->fn_extra;
3213+
3214+
if (cache == NULL)
29973215
{
2998-
PG_RETURN_NULL();
3216+
cache = init_agtype_type_cache(fcinfo);
29993217
}
30003218

3001-
remove_null_from_agtype_object(result);
3002-
agt_result = agtype_value_to_agtype(result);
3003-
3004-
pfree_agtype_value(result);
3219+
MemoryContextReset(cache->tmp_ctx);
30053220

3221+
oldctx = MemoryContextSwitchTo(cache->tmp_ctx);
3222+
3223+
result = agtype_build_map_as_agtype_value(fcinfo);
3224+
3225+
if (result != NULL)
3226+
remove_null_from_agtype_object(result);
3227+
3228+
MemoryContextSwitchTo(oldctx);
3229+
3230+
if (result == NULL)
3231+
PG_RETURN_NULL();
3232+
3233+
agt_result = agtype_value_to_agtype(result);
3234+
30063235
PG_RETURN_POINTER(agt_result);
30073236
}
30083237

0 commit comments

Comments
 (0)