Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vMHxR-006ACm-20 for pgsql-hackers@arkaria.postgresql.org; Fri, 21 Nov 2025 03:37:13 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vMHxO-005Tlm-0o for pgsql-hackers@arkaria.postgresql.org; Fri, 21 Nov 2025 03:37:10 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vMHxN-005Tle-2n for pgsql-hackers@lists.postgresql.org; Fri, 21 Nov 2025 03:37:10 +0000 Received: from mail-pj1-x102c.google.com ([2607:f8b0:4864:20::102c]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1vMHxL-000etu-1i for pgsql-hackers@lists.postgresql.org; Fri, 21 Nov 2025 03:37:09 +0000 Received: by mail-pj1-x102c.google.com with SMTP id 98e67ed59e1d1-343dd5aa6e7so1621668a91.0 for ; Thu, 20 Nov 2025 19:37:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1763696225; x=1764301025; darn=lists.postgresql.org; h=to:references:message-id:content-transfer-encoding:cc:date :in-reply-to:from:subject:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=ceHx7egsXs0MKZO38rsGFkZsDqqEF82JFPb4G0G8R1Q=; b=cwYIhZGZEU6koFUQbqaemqqFyMz2Tbk/W1ll4xiNoGJcTDkHz0rEmtiubkmQIsVEdH hGeGmtsIJt0B5t1eRJfyWxHCLloKyOVCswJ9UfEjjs2KRUbUfWJfw0FwO3IQE+NOgJQE v/TmammLjfV29oBGqK5Ij6dCafXd9ZJDTYEWLY6u5fYKlbq/WzeExeIGMbsmA1hM5K6c 2RdONORgICMB170mxgNzHxTvX05zONDTPH5PgbvCXdSP64h0l8eh8e3wrObp9bzS7kjk RUPwjyxDHZwXgzS8j1vAhZ78Qe543U7mcYuQhcmJoG6i7G+E5aNGI4931IJAFLL8eNYP S/FQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1763696225; x=1764301025; h=to:references:message-id:content-transfer-encoding:cc:date :in-reply-to:from:subject:mime-version:x-gm-gg:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=ceHx7egsXs0MKZO38rsGFkZsDqqEF82JFPb4G0G8R1Q=; b=iDYUAt5xija1Mv6s7fEYmCxjtorcGq7GDQ255A1euAgfAAVje0eyCx+TYgaHca9ECb 2mp7BYEWZK9MfwwZhku02h+AEZvSkDgHq54wTSIHud2DAoiNvJ7QyJoSZWmdiYy+CuOC qDChGfFHhxwmmhDEKIfwjI0kMYyLcgmBUw2AX4aVTQXvn5+hR2pfWQ0yI+l3hfkwtdLU FcdUAUHvqDT443a5QJmVGdoq8We90+y4UhunpfwbJYi415nNNUGzXKpmX6coMtiqUBrJ tgdWKRXSdjrhAX7GmwumYoCV7bjNDHcDgK7JSPYWsjsqjTQP3IH/I4Iw97jOpCujgA9Z OE1w== X-Forwarded-Encrypted: i=1; AJvYcCVnHv5TDUWhPKh+ZkouHn8Y4751008YHUuMJ2vbLL8/qjaPioAmF5NWAIu5gEe3aHZsXDDj5RrFQ6OOvOtB@lists.postgresql.org X-Gm-Message-State: AOJu0YypskTvodyJ9gSK9iFX3XtUCrF4OQGHJaHHa1q75GAknTnrJ60U WrX7wC1GIKAgJ8hz+3dDdL4oKtav+VHBsRaY0sX2iU+VP9FJrgp9ZBmX X-Gm-Gg: ASbGnctm2ochdJ47IBX3LleCKne2+bHaRzOEMTdgQAEx7A9peWgiDN/2kpE4L7NmAZE +OHZNFNt/hTMeZ4XjX83aVPowF1eXuM50SYJy83iRHhn3mCZBMUvEkv8r1XstMuz/eJPONn11WV CyGHxaguB28kq54OoPaCMb+6OSHVAo9v53rj9tr/MycbhRqbYWMIUhFxEx3F8aiQU8lvD8ycCwg jMNa0v2U754IJb4fFgDnWIwsSWwOzn0YnO2+j0GcnAX69YR5HfcHuytPpcCzKyWaLTj0/o4bB2v A3VQzcJkSMjB1na6friVL6zyO9nmxHGN995XhBe1OaaG+sZn6JQTUVritB07sinTHn4xJiWQM1D 1mQqmeoOiZ3yVj6rBRQeRp5DAEswyi5rlwDaElvB17+9oerGiZB6byoScZKQWxMaJmQahr1wLP8 rCx9qAYYXq10LGA/oVeAI= X-Google-Smtp-Source: AGHT+IHvEnawZ3NKcZ0uAoDsV+Xr6byKmTuacilr44t16IxW5+W9FOuAxB2E4xiBLf4rraxlPgWFZA== X-Received: by 2002:a05:6a20:3c8e:b0:35b:b97f:7471 with SMTP id adf61e73a8af0-3614eb2787dmr1257151637.4.1763696224691; Thu, 20 Nov 2025 19:37:04 -0800 (PST) Received: from smtpclient.apple ([45.32.121.103]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-bd7604de4cdsm3938888a12.20.2025.11.20.19.37.01 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 20 Nov 2025 19:37:04 -0800 (PST) Content-Type: text/plain; charset=utf-8 Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3826.700.81\)) Subject: Re: Extended Statistics set/restore/clear functions. From: Chao Li In-Reply-To: Date: Fri, 21 Nov 2025 11:36:23 +0800 Cc: jian he , Tomas Vondra , pgsql-hackers@lists.postgresql.org, tgl@sss.pgh.pa.us Content-Transfer-Encoding: quoted-printable Message-Id: <2C8A20C7-4E08-41F4-B30E-AD9F8B007AE5@gmail.com> References: To: Corey Huinker , Michael Paquier X-Mailer: Apple Mail (2.3826.700.81) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk After 16 rounds of revisions, v16 looks solid and robust. Only got a few = small comments: > On Nov 21, 2025, at 07:08, Corey Huinker = wrote: >=20 > = 1 - 0001 ``` + /* + * We need at least two attribute numbers for a ndistinct item, = anything + * less is malformed. + */ + natts =3D list_length(parse->attnum_list); + if ((natts < 2) || (natts > STATS_MAX_DIMENSIONS)) + { + errsave(parse->escontext, + = errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed pg_ndistinct: \"%s\"", = parse->str), + errdetail("The \"%s\" key must contain = an array of at least %d " + "and no more than %d = attributes.", + = PG_NDISTINCT_KEY_ATTRIBUTES, 2, STATS_MAX_DIMENSIONS)); + return JSON_SEM_ACTION_FAILED; + } ``` Do we also need to reset item state vars similar to the success case? 2 - 0001 ``` * + * Arrays can never be empty. + */ +static JsonParseErrorType +ndistinct_array_end(void *state) +{ ``` This function comment is too simple, and doesn=E2=80=99t match to the = function name. Reading the function body, the logic is clear, so I would = suggest either just remove the comment or enhance it. 3 - 0001 ``` static JsonParseErrorType +ndistinct_array_element_start(void *state, bool isnull) +{ + NDistinctParseState *parse =3D state; ``` Nit: NDistinctParseState * can be const. 4 - 0001 ``` +static bool +valid_subsequent_attnum(const AttrNumber prev, const AttrNumber cur) ``` AttrNumber is int16, thus =E2=80=9Cconst=E2=80=9D here is unnecessary. 5 - 0001 ``` + return JSON_SUCCESS; + break; ``` =E2=80=9CBreak=E2=80=9D after =E2=80=9Creturn=E2=80=9D is unnecessary. I = tried to delete the =E2=80=9Cbreak=E2=80=9D and didn=E2=80=99t see a = compile warning, it should be safe to delete the =E2=80=9Cbreak=E2=80=9D. 6 - 0001 ``` +/* + * Compare the attribute arrays of two MVNDistinctItem values, + * looking for duplicate sets. + */ +static bool +has_duplicate_attributes(const MVNDistinctItem *a, + const MVNDistinctItem = *b) ``` The function comment says =E2=80=9Clooking for duplicate sets=E2=80=9D, = seems to imply the function would return the set, but the function = returns a bool, which is a little confusing. 7 - 0002 ``` + for (int i =3D 0; i < natts; i++) + { + dep->attributes[i] =3D (AttrNumber) = list_nth_int(parse->attnum_list, i); + if (dep->attributes[i] =3D=3D parse->dependency) + { + errsave(parse->escontext, + = errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed = pg_dependencies: \"%s\"", parse->str), + errdetail("Item \"%s\" value %d = found in the \"%s\" list.", + = PG_DEPENDENCIES_KEY_DEPENDENCY, parse->dependency, + = PG_DEPENDENCIES_KEY_ATTRIBUTES)); + return JSON_SEM_ACTION_FAILED; + } ``` Similar to comment 1, do we need to reset state vars upon failure? 8 - 0002 ``` + errdetail("The \"%s\" key must contain = an array of at least %d " + " and no than %d = elements.", ``` I believe =E2=80=9Cmore=E2=80=9D is missed between =E2=80=9Cno than=E2=80=9D= . 9 - 0002 ``` +static JsonParseErrorType +dependencies_array_start(void *state) +{ + DependenciesParseState *parse =3D state; ``` Similar to commit 3, DependenciesParseState * can be const. 10 - 0002 ``` static bool valid_subsequent_attnum(const AttrNumber prev, const AttrNumber cur) ``` This function is a dup to the one in 0001, can we put it to a common = place? 11 - 0003 ``` +extern void statatt_get_type(Oid reloid, AttrNumber attnum, + Oid *atttypid, = int32 *atttypmod, + char = *atttyptype, Oid *atttypcoll, + Oid *eq_opr, = Oid *lt_opr); +extern void statatt_init_empty_tuple(Oid reloid, int16 attnum, bool = inherited, + = Datum *values, bool *nulls, bool *replaces); + +extern void statatt_set_slot(Datum *values, bool *nulls, bool = *replaces, + int16 stakind, = Oid staop, Oid stacoll, + Datum = stanumbers, bool stanumbers_isnull, + Datum = stavalues, bool stavalues_isnull); + +extern Datum text_to_stavalues(const char *staname, FmgrInfo *array_in, = Datum d, + Oid typid, = int32 typmod, bool *ok); +extern bool statatt_get_elem_type(Oid atttypid, char atttyptype, + Oid = *elemtypid, Oid *elem_eq_opr); ``` Several functions are made external visible, they are all renamed with = adding a prefix =E2=80=9Cstatatt_=E2=80=9D, why text_to_stavalues is an = exception? 11 - 0004 ``` +bool +pg_dependencies_validate_deps(MVDependencies *dependencies, int2vector = *stxkeys, int numexprs, int elevel) +{ + int attnum_expr_lowbound =3D 0 - numexprs; + + for (int i =3D 0; i < dependencies->ndeps; i++) + { + MVDependency *dep =3D dependencies->deps[i]; ``` This MVDependency * can be const. 12 - 0004 ``` static void upsert_pg_statistic_ext_data(Datum *values, bool *nulls, bool *replaces) { ``` This function pass values, nulls and replaces to heap_modify_tuple() and = heap_form_tuple(), the both functions take all const pointers as = parameters. So, here values, nulls and replaces can all be const. 13 - 0004 ``` + if (!exprs_nulls[offset + AVG_WIDTH_ELEM]) + { + ok =3D text_to_int4(exprs_elems[offset + = AVG_WIDTH_ELEM], + = &values[Anum_pg_statistic_stawidth - 1]); + + if (!ok) + { + char *s =3D = TextDatumGetCString(exprs_elems[offset + NULL_FRAC_ELEM]); + + ereport(WARNING, + = (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Expression %s = element \"%s\" does not match expected input type.", + = extexprarginfo[AVG_WIDTH_ELEM].argname, s))); + pfree(s); + return (Datum) 0; + } + } ``` Here: char *s =3D TextDatumGetCString(exprs_elems[offset + = NULL_FRAC_ELEM]); Should NULL_FRAC_ELEM be AVG_WIDTH_ELEM? 14 - 0004 ``` + if (!exprs_nulls[offset + N_DISTINCT_ELEM]) + { + ok =3D text_to_float4(exprs_elems[offset + = N_DISTINCT_ELEM], + = &values[Anum_pg_statistic_stadistinct - 1]); + + if (!ok) + { + char *s =3D = TextDatumGetCString(exprs_elems[offset + NULL_FRAC_ELEM]); + + ereport(WARNING, + = (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Expression %s = element \"%s\" does not match expected input type.", + = extexprarginfo[N_DISTINCT_ELEM].argname, s))); + pfree(s); + return (Datum) 0; + } + } ``` Similar to comment 13, should NULL_FRAC_ELEM be N_DISTINCT_ELEM? 15 - 0004 ``` +extern Datum import_mcvlist(HeapTuple tup, int elevel, int numattrs, + Oid *atttypids, = int32 *atttypmods, Oid *atttypcolls, + int nitems, = Datum *mcv_elems, bool *mcv_nulls, + bool = *mcv_elem_nulls, float8 *freqs, float8 *base_freqs); + +extern Datum import_mcvlist(HeapTuple tup, int elevel, int numattrs, + Oid *atttypids, = int32 *atttypmods, Oid *atttypcolls, + int nitems, = Datum *mcv_elems, bool *mcv_nulls, + bool = *mcv_elem_nulls, float8 *freqs, float8 *base_freqs); ``` import_mcvlist is declared twice, looks like a copy-paste mistake. 16 - 0005 ``` + PREPQUERY_DUMPEXTSTATSSTATS, ``` =E2=80=9Cstatsstats=E2=80=9D looks weird, I=E2=80=99d suggest = PREPQUERY_DUMPEXTSTATSDATA. Best regards, =E2=80=94 Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/