public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v25 08/15] Add aggregates support in IVM 8+ messages / 5 participants [nested] [flat]
* [PATCH v25 08/15] Add aggregates support in IVM @ 2021-08-02 05:59 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Yugo Nagata @ 2021-08-02 05:59 UTC (permalink / raw) Currently, count, sum, avg, min and max are supported. As a restriction, expressions specified in GROUP BY must appear in the target list because tuples to be updated in IMMV are identified by using this group key. However, in the case of aggregates without GROUP BY, there is only one tuple in the view, so keys are not uses to identify tuples. When creating a IMMV, in addition to __ivm_count column, some hidden columns for each aggregate are added to the target list. For example, names of these hidden columns are ivm_count_avg and ivm_sum_avg for the average function, and so on. In the case of views without aggregate functions, only the number of tuple multiplicities in __ivm_count__ column are updated at incremental maintenance. On the other hand, in the case of view with aggregates, the aggregated values and related hidden columns are also updated. The way of update depends the kind of aggregate function.=E3=80=80Specifically, sum and count are updated by simply adding or subtracting delta value calculated from delta tables. avg is updated by using values of sum and count stored in views as hidden columns and deltas calculated from delta tables. In min or max cases, it becomes more complicated. For an example of min, when tuples are inserted, the smaller value between the current min value in the view and the value calculated from the new delta table is used. When tuples are deleted, if the current min value in the view equals to the min in the old delta table, we need re-computation the latest min value from base tables. Otherwise, the current value in the view remains. As to sum, avg, min, and max (any aggregate functions except count), NULL in input values is ignored, and this returns a null value when no rows are selected. To support this specification, the number of not-NULL input values is counted and stored in views as a hidden column. In the case of count(), count(x) returns zero when no rows are selected, and count(*) doesn't ignore NULL input. These specification are also supported. --- src/backend/commands/createas.c | 299 ++++++++- src/backend/commands/matview.c | 1016 ++++++++++++++++++++++++++++++- src/include/commands/createas.h | 1 + 3 files changed, 1281 insertions(+), 35 deletions(-) diff --git a/src/backend/commands/createas.c b/src/backend/commands/createa= s.c index 4b73965e56..c58cacec05 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -63,6 +63,7 @@ #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/regproc.h" #include "utils/rel.h" #include "utils/rls.h" #include "utils/snapmgr.h" @@ -80,6 +81,11 @@ typedef struct BulkInsertState bistate; /* bulk insert state */ } DR_intorel; =20 +typedef struct +{ + bool has_agg; +} check_ivm_restriction_context; + /* utility functions for CTAS definition creation */ static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into= ); static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into); @@ -94,8 +100,9 @@ static void CreateIvmTriggersOnBaseTablesRecurse(Query *= qry, Node *node, Oid mat Relids *relids, bool ex_lock); static void CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type, int16 ti= ming, bool ex_lock); static void check_ivm_restriction(Node *node); -static bool check_ivm_restriction_walker(Node *node, void *context); +static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction= _context *context); static Bitmapset *get_primary_key_attnos_from_query(Query *qry, List **con= straintList); +static bool check_aggregate_supports_ivm(Oid aggfnoid); =20 /* * create_ctas_internal @@ -431,6 +438,7 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt= *stmt, * rewriteQueryForIMMV -- rewrite view definition query for IMMV * * count(*) is added for counting distinct tuples in views. + * Also, additional hidden columns are added for aggregate values. */ Query * rewriteQueryForIMMV(Query *query, List *colNames) @@ -445,14 +453,46 @@ rewriteQueryForIMMV(Query *query, List *colNames) rewritten =3D copyObject(query); pstate->p_expr_kind =3D EXPR_KIND_SELECT_TARGET; =20 - /* - * Convert DISTINCT to GROUP BY and add count(*) for counting distinct - * tuples in views. - */ - if (rewritten->distinctClause) + /* group keys must be in targetlist */ + if (rewritten->groupClause) { + ListCell *lc; + foreach(lc, rewritten->groupClause) + { + SortGroupClause *scl =3D (SortGroupClause *) lfirst(lc); + TargetEntry *tle =3D get_sortgroupclause_tle(scl, rewritten->targetList= ); + + if (tle->resjunk) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("GROUP BY expression not appearing in select list is not sup= ported on incrementally maintainable materialized view"))); + } + } + /* Convert DISTINCT to GROUP BY. count(*) will be added afterward. */ + else if (!rewritten->hasAggs && rewritten->distinctClause) rewritten->groupClause =3D transformDistinctClause(NULL, &rewritten->tar= getList, rewritten->sortClause, false); =20 + /* Add additional columns for aggregate values */ + if (rewritten->hasAggs) + { + ListCell *lc; + List *aggs =3D NIL; + AttrNumber next_resno =3D list_length(rewritten->targetList) + 1; + + foreach(lc, rewritten->targetList) + { + TargetEntry *tle =3D (TargetEntry *) lfirst(lc); + char *resname =3D (colNames =3D=3D NIL ? tle->resname : strVal(list_nth= (colNames, tle->resno - 1))); + + if (IsA(tle->expr, Aggref)) + makeIvmAggColumn(pstate, (Aggref *)tle->expr, resname, &next_resno, &a= ggs); + } + rewritten->targetList =3D list_concat(rewritten->targetList, aggs); + } + + /* Add count(*) for counting distinct tuples in views */ + if (rewritten->distinctClause || rewritten->hasAggs) + { fn =3D makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICI= T_CALL, -1); fn->agg_star =3D true; =20 @@ -469,6 +509,91 @@ rewriteQueryForIMMV(Query *query, List *colNames) return rewritten; } =20 +/* + * makeIvmAggColumn -- make additional aggregate columns for IVM + * + * For an aggregate column specified by aggref, additional aggregate colum= ns + * are added, which are used to calculate the new aggregate value in IMMV. + * An additional aggregate columns has a name based on resname + * (ex. ivm_count_resname), and resno specified by next_resno. The created + * columns are returned to aggs, and the resno for the next column is also + * returned to next_resno. + * + * Currently, an additional count() is created for aggref other than count. + * In addition, sum() is created for avg aggregate column. + */ +void +makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname, AttrNu= mber *next_resno, List **aggs) +{ + TargetEntry *tle_count; + Node *node; + FuncCall *fn; + Const *dmy_arg =3D makeConst(INT4OID, + -1, + InvalidOid, + sizeof(int32), + Int32GetDatum(1), + false, + true); /* pass by value */ + const char *aggname =3D get_func_name(aggref->aggfnoid); + + /* + * For aggregate functions except count, add count() func with the same a= rg parameters. + * This count result is used for determining if the aggregate value shoul= d be NULL or not. + * Also, add sum() func for avg because we need to calculate an average v= alue as sum/count. + * + * XXX: If there are same expressions explicitly in the target list, we c= an use this instead + * of adding new duplicated one. + */ + if (strcmp(aggname, "count") !=3D 0) + { + fn =3D makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICI= T_CALL, -1); + + /* Make a Func with a dummy arg, and then override this by the original = agg's args. */ + node =3D ParseFuncOrColumn(pstate, fn->funcname, list_make1(dmy_arg), NU= LL, fn, false, -1); + ((Aggref *)node)->args =3D aggref->args; + + tle_count =3D makeTargetEntry((Expr *) node, + *next_resno, + pstrdup(makeObjectName("__ivm_count",resname, "_")), + false); + *aggs =3D lappend(*aggs, tle_count); + (*next_resno)++; + } + if (strcmp(aggname, "avg") =3D=3D 0) + { + List *dmy_args =3D NIL; + ListCell *lc; + foreach(lc, aggref->aggargtypes) + { + Oid typeid =3D lfirst_oid(lc); + Type type =3D typeidType(typeid); + + Const *con =3D makeConst(typeid, + -1, + typeTypeCollation(type), + typeLen(type), + (Datum) 0, + true, + typeByVal(type)); + dmy_args =3D lappend(dmy_args, con); + ReleaseSysCache(type); + } + fn =3D makeFuncCall(list_make1(makeString("sum")), NIL, COERCE_EXPLICIT_= CALL, -1); + + /* Make a Func with dummy args, and then override this by the original a= gg's args. */ + node =3D ParseFuncOrColumn(pstate, fn->funcname, dmy_args, NULL, fn, fal= se, -1); + ((Aggref *)node)->args =3D aggref->args; + + tle_count =3D makeTargetEntry((Expr *) node, + *next_resno, + pstrdup(makeObjectName("__ivm_sum",resname, "_")), + false); + *aggs =3D lappend(*aggs, tle_count); + (*next_resno)++; + } +} + /* * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS * @@ -922,11 +1047,13 @@ CreateIvmTrigger(Oid relOid, Oid viewOid, int16 type= , int16 timing, bool ex_lock static void check_ivm_restriction(Node *node) { - check_ivm_restriction_walker(node, NULL); + check_ivm_restriction_context context =3D {false}; + + check_ivm_restriction_walker(node, &context); } =20 static bool -check_ivm_restriction_walker(Node *node, void *context) +check_ivm_restriction_walker(Node *node, check_ivm_restriction_context *co= ntext) { if (node =3D=3D NULL) return false; @@ -1007,6 +1134,8 @@ check_ivm_restriction_walker(Node *node, void *contex= t) } } =20 + context->has_agg |=3D qry->hasAggs; + /* restrictions for rtable */ foreach(lc, qry->rtable) { @@ -1055,7 +1184,7 @@ check_ivm_restriction_walker(Node *node, void *contex= t) =20 } =20 - query_tree_walker(qry, check_ivm_restriction_walker, NULL, QTW_IGNORE_= RANGE_TABLE); + query_tree_walker(qry, check_ivm_restriction_walker, (void *) context,= QTW_IGNORE_RANGE_TABLE); =20 break; } @@ -1066,8 +1195,12 @@ check_ivm_restriction_walker(Node *node, void *conte= xt) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("column name %s is not supported on incrementally maintain= able materialized view", tle->resname))); + if (context->has_agg && !IsA(tle->expr, Aggref) && contain_aggs_of_lev= el((Node *) tle->expr, 0)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("expression containing an aggregate in it is not supported = on incrementally maintainable materialized view"))); =20 - expression_tree_walker(node, check_ivm_restriction_walker, NULL); + expression_tree_walker(node, check_ivm_restriction_walker, (void *) co= ntext); break; } case T_JoinExpr: @@ -1079,15 +1212,128 @@ check_ivm_restriction_walker(Node *node, void *con= text) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("OUTER JOIN is not supported on incrementally maintainable= materialized view"))); =20 - expression_tree_walker(node, check_ivm_restriction_walker, NULL); + expression_tree_walker(node, check_ivm_restriction_walker, (void *) co= ntext); + break; } - break; - expression_tree_walker(node, check_ivm_restriction_walker, NULL); + case T_Aggref: + { + /* Check if this supports IVM */ + Aggref *aggref =3D (Aggref *) node; + const char *aggname =3D format_procedure(aggref->aggfnoid); + + if (aggref->aggfilter !=3D NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("aggregate function with FILTER clause is not supported on = incrementally maintainable materialized view"))); + + if (aggref->aggdistinct !=3D NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("aggregate function with DISTINCT arguments is not supporte= d on incrementally maintainable materialized view"))); + + if (aggref->aggorder !=3D NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("aggregate function with ORDER clause is not supported on i= ncrementally maintainable materialized view"))); + + if (!check_aggregate_supports_ivm(aggref->aggfnoid)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("aggregate function %s is not supported on incrementally ma= intainable materialized view", aggname))); + break; + } + default: + expression_tree_walker(node, check_ivm_restriction_walker, (void *) con= text); break; } return false; } =20 +/* + * check_aggregate_supports_ivm + * + * Check if the given aggregate function is supporting IVM + */ +static bool +check_aggregate_supports_ivm(Oid aggfnoid) +{ + switch (aggfnoid) + { + /* count */ + case F_COUNT_ANY: + case F_COUNT_: + + /* sum */ + case F_SUM_INT8: + case F_SUM_INT4: + case F_SUM_INT2: + case F_SUM_FLOAT4: + case F_SUM_FLOAT8: + case F_SUM_MONEY: + case F_SUM_INTERVAL: + case F_SUM_NUMERIC: + + /* avg */ + case F_AVG_INT8: + case F_AVG_INT4: + case F_AVG_INT2: + case F_AVG_NUMERIC: + case F_AVG_FLOAT4: + case F_AVG_FLOAT8: + case F_AVG_INTERVAL: + + /* min */ + case F_MIN_ANYARRAY: + case F_MIN_INT8: + case F_MIN_INT4: + case F_MIN_INT2: + case F_MIN_OID: + case F_MIN_FLOAT4: + case F_MIN_FLOAT8: + case F_MIN_DATE: + case F_MIN_TIME: + case F_MIN_TIMETZ: + case F_MIN_MONEY: + case F_MIN_TIMESTAMP: + case F_MIN_TIMESTAMPTZ: + case F_MIN_INTERVAL: + case F_MIN_TEXT: + case F_MIN_NUMERIC: + case F_MIN_BPCHAR: + case F_MIN_TID: + case F_MIN_ANYENUM: + case F_MIN_INET: + case F_MIN_PG_LSN: + + /* max */ + case F_MAX_ANYARRAY: + case F_MAX_INT8: + case F_MAX_INT4: + case F_MAX_INT2: + case F_MAX_OID: + case F_MAX_FLOAT4: + case F_MAX_FLOAT8: + case F_MAX_DATE: + case F_MAX_TIME: + case F_MAX_TIMETZ: + case F_MAX_MONEY: + case F_MAX_TIMESTAMP: + case F_MAX_TIMESTAMPTZ: + case F_MAX_INTERVAL: + case F_MAX_TEXT: + case F_MAX_NUMERIC: + case F_MAX_BPCHAR: + case F_MAX_TID: + case F_MAX_ANYENUM: + case F_MAX_INET: + case F_MAX_PG_LSN: + return true; + + default: + return false; + } +} + /* * CreateIndexOnIMMV * @@ -1139,7 +1385,30 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel) index->concurrent =3D false; index->if_not_exists =3D false; =20 - if (qry->distinctClause) + + if (qry->groupClause) + { + /* create unique constraint on GROUP BY expression columns */ + foreach(lc, qry->groupClause) + { + SortGroupClause *scl =3D (SortGroupClause *) lfirst(lc); + TargetEntry *tle =3D get_sortgroupclause_tle(scl, qry->targetList); + Form_pg_attribute attr =3D TupleDescAttr(matviewRel->rd_att, tle->resno= - 1); + IndexElem *iparam; + + iparam =3D makeNode(IndexElem); + iparam->name =3D pstrdup(NameStr(attr->attname)); + iparam->expr =3D NULL; + iparam->indexcolname =3D NULL; + iparam->collation =3D NIL; + iparam->opclass =3D NIL; + iparam->opclassopts =3D NIL; + iparam->ordering =3D SORTBY_DEFAULT; + iparam->nulls_ordering =3D SORTBY_NULLS_DEFAULT; + index->indexParams =3D lappend(index->indexParams, iparam); + } + } + else if (qry->distinctClause) { /* create unique constraint on all columns */ foreach(lc, qry->targetList) @@ -1198,7 +1467,7 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel) (errmsg("could not create an index on materialized view \"%s\" automa= tically", RelationGetRelationName(matviewRel)), errdetail("This target list does not have all the primary key column= s, " - "or this view does not contain DISTINCT clause."), + "or this view does not contain GROUP BY or DISTINCT clause."), errhint("Create an index on the materialized view for efficient incr= emental maintenance."))); return; } diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 943de5dfba..1f50aaa1b8 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -81,6 +81,32 @@ typedef struct =20 #define MV_INIT_QUERYHASHSIZE 16 =20 +/* MV query type codes */ +#define MV_PLAN_RECALC 1 +#define MV_PLAN_SET_VALUE 2 + +/* + * MI_QueryKey + * + * The key identifying a prepared SPI plan in our query hashtable + */ +typedef struct MV_QueryKey +{ + Oid matview_id; /* OID of materialized view */ + int32 query_type; /* query type ID, see MV_PLAN_XXX above */ +} MV_QueryKey; + +/* + * MV_QueryHashEntry + * + * Hash entry for cached plans used to maintain materialized views. + */ +typedef struct MV_QueryHashEntry +{ + MV_QueryKey key; + SPIPlanPtr plan; +} MV_QueryHashEntry; + /* * MV_TriggerHashEntry * @@ -117,8 +143,16 @@ typedef struct MV_TriggerTable RangeTblEntry *original_rte; /* the original RTE saved before rewriting q= uery */ } MV_TriggerTable; =20 +static HTAB *mv_query_cache =3D NULL; static HTAB *mv_trigger_info =3D NULL; =20 +/* kind of IVM operation for the view */ +typedef enum +{ + IVM_ADD, + IVM_SUB +} IvmOp; + /* ENR name for materialized view delta */ #define NEW_DELTA_ENRNAME "new_delta" #define OLD_DELTA_ENRNAME "old_delta" @@ -152,7 +186,7 @@ static RangeTblEntry *get_prestate_rte(RangeTblEntry *r= te, MV_TriggerTable *tabl QueryEnvironment *queryEnv); static RangeTblEntry *union_ENRs(RangeTblEntry *rte, Oid relid, List *enr_= rtes, const char *prefix, QueryEnvironment *queryEnv); -static Query *rewrite_query_for_distinct(Query *query, ParseState *pstate); +static Query *rewrite_query_for_distinct_and_aggregates(Query *query, Pars= eState *pstate); =20 static void calc_delta(MV_TriggerTable *table, int rte_index, Query *query, DestReceiver *dest_old, DestReceiver *dest_new, @@ -163,19 +197,48 @@ static Query *rewrite_query_for_postupdate_state(Quer= y *query, MV_TriggerTable * static void apply_delta(Oid matviewOid, Tuplestorestate *old_tuplestores, = Tuplestorestate *new_tuplestores, TupleDesc tupdesc_old, TupleDesc tupdesc_new, Query *query, bool use_count, char *count_colname); +static void append_set_clause_for_count(const char *resname, StringInfo bu= f_old, + StringInfo buf_new,StringInfo aggs_list); +static void append_set_clause_for_sum(const char *resname, StringInfo buf_= old, + StringInfo buf_new, StringInfo aggs_list); +static void append_set_clause_for_avg(const char *resname, StringInfo buf_= old, + StringInfo buf_new, StringInfo aggs_list, + const char *aggtype); +static void append_set_clause_for_minmax(const char *resname, StringInfo b= uf_old, + StringInfo buf_new, StringInfo aggs_list, + bool is_min); +static char *get_operation_string(IvmOp op, const char *col, const char *a= rg1, const char *arg2, + const char* count_col, const char *castType); +static char *get_null_condition_string(IvmOp op, const char *arg1, const c= har *arg2, + const char* count_col); static void apply_old_delta(const char *matviewname, const char *deltaname= _old, List *keys); static void apply_old_delta_with_count(const char *matviewname, const char= *deltaname_old, - List *keys, const char *count_colname); + List *keys, StringInfo aggs_list, StringInfo aggs_set, + List *minmax_list, List *is_min_list, + const char *count_colname, + SPITupleTable **tuptable_recalc, uint64 *num_recalc); static void apply_new_delta(const char *matviewname, const char *deltaname= _new, StringInfo target_list); static void apply_new_delta_with_count(const char *matviewname, const char= * deltaname_new, - List *keys, StringInfo target_list, const char* count_colname); + List *keys, StringInfo target_list, StringInfo aggs_set, + const char* count_colname); static char *get_matching_condition_string(List *keys); +static char *get_returning_string(List *minmax_list, List *is_min_list, Li= st *keys); +static char *get_minmax_recalc_condition_string(List *minmax_list, List *i= s_min_list); +static char *get_select_for_recalc_string(List *keys); +static void recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 nu= m_tuples, + List *namelist, List *keys, Relation matviewRel); +static SPIPlanPtr get_plan_for_recalc(Oid matviewOid, List *namelist, List= *keys, Oid *keyTypes); +static SPIPlanPtr get_plan_for_set_values(Oid matviewOid, char *matviewnam= e, List *namelist, + Oid *valTypes); static void generate_equal(StringInfo querybuf, Oid opttype, const char *leftop, const char *rightop); =20 static void mv_InitHashTables(void); +static SPIPlanPtr mv_FetchPreparedPlan(MV_QueryKey *key); +static void mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan); +static void mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query= _type); static void clean_up_IVM_hash_entry(MV_TriggerHashEntry *entry); =20 static List *get_securityQuals(Oid relId, int rt_index, Query *query); @@ -1447,8 +1510,8 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS) rewritten =3D rewrite_query_for_preupdate_state(rewritten, entry->tables, entry->xid, entry->cid, pstate); - /* Rewrite for DISTINCT clause */ - rewritten =3D rewrite_query_for_distinct(rewritten, pstate); + /* Rewrite for DISTINCT clause and aggregates functions */ + rewritten =3D rewrite_query_for_distinct_and_aggregates(rewritten, pstate= ); =20 /* Create tuplestores to store view deltas */ if (entry->has_old) @@ -1499,7 +1562,7 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS) =20 count_colname =3D pstrdup("__ivm_count__"); =20 - if (query->distinctClause) + if (query->hasAggs || query->distinctClause) use_count =3D true; =20 /* calculate delta tables */ @@ -1861,17 +1924,34 @@ union_ENRs(RangeTblEntry *rte, Oid relid, List *enr= _rtes, const char *prefix, } =20 /* - * rewrite_query_for_distinct + * rewrite_query_for_distinct_and_aggregates * - * Rewrite query for counting DISTINCT clause. + * Rewrite query for counting DISTINCT clause and aggregate functions. */ static Query * -rewrite_query_for_distinct(Query *query, ParseState *pstate) +rewrite_query_for_distinct_and_aggregates(Query *query, ParseState *pstate) { TargetEntry *tle_count; FuncCall *fn; Node *node; =20 + /* For aggregate views */ + if (query->hasAggs) + { + ListCell *lc; + List *aggs =3D NIL; + AttrNumber next_resno =3D list_length(query->targetList) + 1; + + foreach(lc, query->targetList) + { + TargetEntry *tle =3D (TargetEntry *) lfirst(lc); + + if (IsA(tle->expr, Aggref)) + makeIvmAggColumn(pstate, (Aggref *)tle->expr, tle->resname, &next_resn= o, &aggs); + } + query->targetList =3D list_concat(query->targetList, aggs); + } + /* Add count(*) for counting distinct tuples in views */ fn =3D makeFuncCall(list_make1(makeString("count")), NIL, COERCE_EXPLICIT= _CALL, -1); fn->agg_star =3D true; @@ -1940,6 +2020,8 @@ rewrite_query_for_postupdate_state(Query *query, MV_T= riggerTable *table, int rte return query; } =20 +#define IVM_colname(type, col) makeObjectName("__ivm_" type, col, "_") + /* * apply_delta * @@ -1953,11 +2035,16 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tu= plestores, Tuplestorestate *n { StringInfoData querybuf; StringInfoData target_list_buf; + StringInfo aggs_list_buf =3D NULL; + StringInfo aggs_set_old =3D NULL; + StringInfo aggs_set_new =3D NULL; Relation matviewRel; char *matviewname; ListCell *lc; int i; List *keys =3D NIL; + List *minmax_list =3D NIL; + List *is_min_list =3D NIL; =20 =20 /* @@ -1975,6 +2062,15 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tup= lestores, Tuplestorestate *n initStringInfo(&querybuf); initStringInfo(&target_list_buf); =20 + if (query->hasAggs) + { + if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0) + aggs_set_old =3D makeStringInfo(); + if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0) + aggs_set_new =3D makeStringInfo(); + aggs_list_buf =3D makeStringInfo(); + } + /* build string of target list */ for (i =3D 0; i < matviewRel->rd_att->natts; i++) { @@ -1998,7 +2094,65 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tup= lestores, Tuplestorestate *n if (tle->resjunk) continue; =20 - keys =3D lappend(keys, resname); + /* + * For views without aggregates, all attributes are used as keys to iden= tify a + * tuple in a view. + */ + if (!query->hasAggs) + keys =3D lappend(keys, attr); + + /* For views with aggregates, we need to build SET clause for updating a= ggregate + * values. */ + if (query->hasAggs && IsA(tle->expr, Aggref)) + { + Aggref *aggref =3D (Aggref *) tle->expr; + const char *aggname =3D get_func_name(aggref->aggfnoid); + + /* + * We can use function names here because it is already checked if these + * can be used in IMMV by its OID at the definition time. + */ + + /* count */ + if (!strcmp(aggname, "count")) + append_set_clause_for_count(resname, aggs_set_old, aggs_set_new, aggs_= list_buf); + + /* sum */ + else if (!strcmp(aggname, "sum")) + append_set_clause_for_sum(resname, aggs_set_old, aggs_set_new, aggs_li= st_buf); + + /* avg */ + else if (!strcmp(aggname, "avg")) + append_set_clause_for_avg(resname, aggs_set_old, aggs_set_new, aggs_li= st_buf, + format_type_be(aggref->aggtype)); + + /* min/max */ + else if (!strcmp(aggname, "min") || !strcmp(aggname, "max")) + { + bool is_min =3D (!strcmp(aggname, "min")); + + append_set_clause_for_minmax(resname, aggs_set_old, aggs_set_new, aggs= _list_buf, is_min); + + /* make a resname list of min and max aggregates */ + minmax_list =3D lappend(minmax_list, resname); + is_min_list =3D lappend_int(is_min_list, is_min); + } + else + elog(ERROR, "unsupported aggregate function: %s", aggname); + } + } + + /* If we have GROUP BY clause, we use its entries as keys. */ + if (query->hasAggs && query->groupClause) + { + foreach (lc, query->groupClause) + { + SortGroupClause *sgcl =3D (SortGroupClause *) lfirst(lc); + TargetEntry *tle =3D get_sortgroupclause_tle(sgcl, query->targetList); + Form_pg_attribute attr =3D TupleDescAttr(matviewRel->rd_att, tle->resno= - 1); + + keys =3D lappend(keys, attr); + } } =20 /* Start maintaining the materialized view. */ @@ -2012,6 +2166,8 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tupl= estores, Tuplestorestate *n if (old_tuplestores && tuplestore_tuple_count(old_tuplestores) > 0) { EphemeralNamedRelation enr =3D palloc(sizeof(EphemeralNamedRelationData)= ); + SPITupleTable *tuptable_recalc =3D NULL; + uint64 num_recalc; int rc; =20 /* convert tuplestores to ENR, and register for SPI */ @@ -2029,10 +2185,19 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tu= plestores, Tuplestorestate *n if (use_count) /* apply old delta and get rows to be recalculated */ apply_old_delta_with_count(matviewname, OLD_DELTA_ENRNAME, - keys, count_colname); + keys, aggs_list_buf, aggs_set_old, + minmax_list, is_min_list, + count_colname, &tuptable_recalc, &num_recalc); else apply_old_delta(matviewname, OLD_DELTA_ENRNAME, keys); =20 + /* + * If we have min or max, we might have to recalculate aggregate values = from base tables + * on some tuples. TIDs and keys such tuples are returned as a result of= the above query. + */ + if (minmax_list && tuptable_recalc) + recalc_and_set_values(tuptable_recalc, num_recalc, minmax_list, keys, m= atviewRel); + } /* For tuple insertion */ if (new_tuplestores && tuplestore_tuple_count(new_tuplestores) > 0) @@ -2055,7 +2220,7 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_tupl= estores, Tuplestorestate *n /* apply new delta */ if (use_count) apply_new_delta_with_count(matviewname, NEW_DELTA_ENRNAME, - keys, &target_list_buf, count_colname); + keys, aggs_set_new, &target_list_buf, count_colname); else apply_new_delta(matviewname, NEW_DELTA_ENRNAME, &target_list_buf); } @@ -2070,49 +2235,410 @@ apply_delta(Oid matviewOid, Tuplestorestate *old_t= uplestores, Tuplestorestate *n elog(ERROR, "SPI_finish failed"); } =20 +/* + * append_set_clause_for_count + * + * Append SET clause string for count aggregation to given buffers. + * Also, append resnames required for calculating the aggregate value. + */ +static void +append_set_clause_for_count(const char *resname, StringInfo buf_old, + StringInfo buf_new,StringInfo aggs_list) +{ + /* For tuple deletion */ + if (buf_old) + { + /* resname =3D mv.resname - t.resname */ + appendStringInfo(buf_old, + ", %s =3D %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_SUB, resname, "mv", "t", NULL, NULL)); + } + /* For tuple insertion */ + if (buf_new) + { + /* resname =3D mv.resname + diff.resname */ + appendStringInfo(buf_new, + ", %s =3D %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_ADD, resname, "mv", "diff", NULL, NULL)); + } + + appendStringInfo(aggs_list, ", %s", + quote_qualified_identifier("diff", resname) + ); +} + +/* + * append_set_clause_for_sum + * + * Append SET clause string for sum aggregation to given buffers. + * Also, append resnames required for calculating the aggregate value. + */ +static void +append_set_clause_for_sum(const char *resname, StringInfo buf_old, + StringInfo buf_new, StringInfo aggs_list) +{ + char *count_col =3D IVM_colname("count", resname); + + /* For tuple deletion */ + if (buf_old) + { + /* sum =3D mv.sum - t.sum */ + appendStringInfo(buf_old, + ", %s =3D %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_SUB, resname, "mv", "t", count_col, NULL) + ); + /* count =3D mv.count - t.count */ + appendStringInfo(buf_old, + ", %s =3D %s", + quote_qualified_identifier(NULL, count_col), + get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL) + ); + } + /* For tuple insertion */ + if (buf_new) + { + /* sum =3D mv.sum + diff.sum */ + appendStringInfo(buf_new, + ", %s =3D %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_ADD, resname, "mv", "diff", count_col, NULL) + ); + /* count =3D mv.count + diff.count */ + appendStringInfo(buf_new, + ", %s =3D %s", + quote_qualified_identifier(NULL, count_col), + get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL) + ); + } + + appendStringInfo(aggs_list, ", %s, %s", + quote_qualified_identifier("diff", resname), + quote_qualified_identifier("diff", IVM_colname("count", resname)) + ); +} + +/* + * append_set_clause_for_avg + * + * Append SET clause string for avg aggregation to given buffers. + * Also, append resnames required for calculating the aggregate value. + */ +static void +append_set_clause_for_avg(const char *resname, StringInfo buf_old, + StringInfo buf_new, StringInfo aggs_list, + const char *aggtype) +{ + char *sum_col =3D IVM_colname("sum", resname); + char *count_col =3D IVM_colname("count", resname); + + /* For tuple deletion */ + if (buf_old) + { + /* avg =3D (mv.sum - t.sum)::aggtype / (mv.count - t.count) */ + appendStringInfo(buf_old, + ", %s =3D %s OPERATOR(pg_catalog./) %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, aggtype), + get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL) + ); + /* sum =3D mv.sum - t.sum */ + appendStringInfo(buf_old, + ", %s =3D %s", + quote_qualified_identifier(NULL, sum_col), + get_operation_string(IVM_SUB, sum_col, "mv", "t", count_col, NULL) + ); + /* count =3D mv.count - t.count */ + appendStringInfo(buf_old, + ", %s =3D %s", + quote_qualified_identifier(NULL, count_col), + get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL) + ); + + } + /* For tuple insertion */ + if (buf_new) + { + /* avg =3D (mv.sum + diff.sum)::aggtype / (mv.count + diff.count) */ + appendStringInfo(buf_new, + ", %s =3D %s OPERATOR(pg_catalog./) %s", + quote_qualified_identifier(NULL, resname), + get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, aggtype= ), + get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL) + ); + /* sum =3D mv.sum + diff.sum */ + appendStringInfo(buf_new, + ", %s =3D %s", + quote_qualified_identifier(NULL, sum_col), + get_operation_string(IVM_ADD, sum_col, "mv", "diff", count_col, NULL) + ); + /* count =3D mv.count + diff.count */ + appendStringInfo(buf_new, + ", %s =3D %s", + quote_qualified_identifier(NULL, count_col), + get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL) + ); + } + + appendStringInfo(aggs_list, ", %s, %s, %s", + quote_qualified_identifier("diff", resname), + quote_qualified_identifier("diff", IVM_colname("sum", resname)), + quote_qualified_identifier("diff", IVM_colname("count", resname)) + ); +} + +/* + * append_set_clause_for_minmax + * + * Append SET clause string for min or max aggregation to given buffers. + * Also, append resnames required for calculating the aggregate value. + * is_min is true if this is min, false if not. + */ +static void +append_set_clause_for_minmax(const char *resname, StringInfo buf_old, + StringInfo buf_new, StringInfo aggs_list, + bool is_min) +{ + char *count_col =3D IVM_colname("count", resname); + + /* For tuple deletion */ + if (buf_old) + { + /* + * If the new value doesn't became NULL then use the value remaining + * in the view although this will be recomputated afterwords. + */ + appendStringInfo(buf_old, + ", %s =3D CASE WHEN %s THEN NULL ELSE %s END", + quote_qualified_identifier(NULL, resname), + get_null_condition_string(IVM_SUB, "mv", "t", count_col), + quote_qualified_identifier("mv", resname) + ); + /* count =3D mv.count - t.count */ + appendStringInfo(buf_old, + ", %s =3D %s", + quote_qualified_identifier(NULL, count_col), + get_operation_string(IVM_SUB, count_col, "mv", "t", NULL, NULL) + ); + } + /* For tuple insertion */ + if (buf_new) + { + /* + * min =3D LEAST(mv.min, diff.min) + * max =3D GREATEST(mv.max, diff.max) + */ + appendStringInfo(buf_new, + ", %s =3D CASE WHEN %s THEN NULL ELSE %s(%s,%s) END", + quote_qualified_identifier(NULL, resname), + get_null_condition_string(IVM_ADD, "mv", "diff", count_col), + + is_min ? "LEAST" : "GREATEST", + quote_qualified_identifier("mv", resname), + quote_qualified_identifier("diff", resname) + ); + /* count =3D mv.count + diff.count */ + appendStringInfo(buf_new, + ", %s =3D %s", + quote_qualified_identifier(NULL, count_col), + get_operation_string(IVM_ADD, count_col, "mv", "diff", NULL, NULL) + ); + } + + appendStringInfo(aggs_list, ", %s, %s", + quote_qualified_identifier("diff", resname), + quote_qualified_identifier("diff", IVM_colname("count", resname)) + ); +} + +/* + * get_operation_string + * + * Build a string to calculate the new aggregate values. + */ +static char * +get_operation_string(IvmOp op, const char *col, const char *arg1, const ch= ar *arg2, + const char* count_col, const char *castType) +{ + StringInfoData buf; + StringInfoData castString; + char *col1 =3D quote_qualified_identifier(arg1, col); + char *col2 =3D quote_qualified_identifier(arg2, col); + char op_char =3D (op =3D=3D IVM_SUB ? '-' : '+'); + + initStringInfo(&buf); + initStringInfo(&castString); + + if (castType) + appendStringInfo(&castString, "::%s", castType); + + if (!count_col) + { + /* + * If the attributes don't have count columns then calc the result + * by using the operator simply. + */ + appendStringInfo(&buf, "(%s OPERATOR(pg_catalog.%c) %s)%s", + col1, op_char, col2, castString.data); + } + else + { + /* + * If the attributes have count columns then consider the condition + * where the result becomes NULL. + */ + char *null_cond =3D get_null_condition_string(op, arg1, arg2, count_col); + + appendStringInfo(&buf, + "(CASE WHEN %s THEN NULL " + "WHEN %s IS NULL THEN %s " + "WHEN %s IS NULL THEN %s " + "ELSE (%s OPERATOR(pg_catalog.%c) %s)%s END)", + null_cond, + col1, col2, + col2, col1, + col1, op_char, col2, castString.data + ); + } + + return buf.data; +} + +/* + * get_null_condition_string + * + * Build a predicate string for CASE clause to check if an aggregate value + * will became NULL after the given operation is applied. + */ +static char * +get_null_condition_string(IvmOp op, const char *arg1, const char *arg2, + const char* count_col) +{ + StringInfoData null_cond; + initStringInfo(&null_cond); + + switch (op) + { + case IVM_ADD: + appendStringInfo(&null_cond, + "%s OPERATOR(pg_catalog.=3D) 0 AND %s OPERATOR(pg_catalog.=3D) 0", + quote_qualified_identifier(arg1, count_col), + quote_qualified_identifier(arg2, count_col) + ); + break; + case IVM_SUB: + appendStringInfo(&null_cond, + "%s OPERATOR(pg_catalog.=3D) %s", + quote_qualified_identifier(arg1, count_col), + quote_qualified_identifier(arg2, count_col) + ); + break; + default: + elog(ERROR,"unknown operation"); + } + + return null_cond.data; +} + + /* * apply_old_delta_with_count * * Execute a query for applying a delta table given by deltname_old * which contains tuples to be deleted from to a materialized view given by * matviewname. This is used when counting is required, that is, the view - * has aggregate or distinct. + * has aggregate or distinct. Also, when a table in EXISTS sub queries + * is modified. + * + * If the view desn't have aggregates or has GROUP BY, this requires a keys + * list to identify a tuple in the view. If the view has aggregates, this + * requires strings representing resnames of aggregates and SET clause for + * updating aggregate values. + * + * If the view has min or max aggregate, this requires a list of resnames = of + * min/max aggregates and a list of boolean which represents which entries= in + * minmax_list is min. These are necessary to check if we need to recalcul= ate + * min or max aggregate values. In this case, this query returns TID and k= eys + * of tuples which need to be recalculated. This result and the number of= rows + * are stored in tuptables and num_recalc repectedly. + * */ static void apply_old_delta_with_count(const char *matviewname, const char *deltaname_= old, - List *keys, const char *count_colname) + List *keys, StringInfo aggs_list, StringInfo aggs_set, + List *minmax_list, List *is_min_list, + const char *count_colname, + SPITupleTable **tuptable_recalc, uint64 *num_recalc) { StringInfoData querybuf; char *match_cond; + char *updt_returning =3D ""; + char *select_for_recalc =3D "SELECT"; + bool agg_without_groupby =3D (list_length(keys) =3D=3D 0); + + Assert(tuptable_recalc !=3D NULL); + Assert(num_recalc !=3D NULL); =20 /* build WHERE condition for searching tuples to be deleted */ match_cond =3D get_matching_condition_string(keys); =20 + /* + * We need a special RETURNING clause and SELECT statement for min/max to + * check which tuple needs re-calculation from base tables. + */ + if (minmax_list) + { + updt_returning =3D get_returning_string(minmax_list, is_min_list, keys); + select_for_recalc =3D get_select_for_recalc_string(keys); + } + /* Search for matching tuples from the view and update or delete if found= . */ initStringInfo(&querybuf); appendStringInfo(&querybuf, "WITH t AS (" /* collecting tid of target tuples in the view */ "SELECT diff.%s, " /* count column */ - "(diff.%s OPERATOR(pg_catalog.=3D) mv.%s) AS for_dlt, " + "(diff.%s OPERATOR(pg_catalog.=3D) mv.%s AND %s) AS for_dlt, " "mv.ctid " + "%s " /* aggregate columns */ "FROM %s AS mv, %s AS diff " "WHERE %s" /* tuple matching condition */ "), updt AS (" /* update a tuple if this is not to be deleted */ "UPDATE %s AS mv SET %s =3D mv.%s OPERATOR(pg_catalog.-) t.%s " + "%s" /* SET clauses for aggregates */ "FROM t WHERE mv.ctid OPERATOR(pg_catalog.=3D) t.ctid AND NOT for_dl= t " + "%s" /* RETURNING clause for recalc infomation */ "), dlt AS (" /* delete a tuple if this is to be deleted */ "DELETE FROM %s AS mv USING t " "WHERE mv.ctid OPERATOR(pg_catalog.=3D) t.ctid AND for_dlt" - ")", + ") %s", /* SELECT returning which tuples need to be recalculate= d */ count_colname, - count_colname, count_colname, + count_colname, count_colname, (agg_without_groupby ? "false" : "true"= ), + (aggs_list !=3D NULL ? aggs_list->data : ""), matviewname, deltaname_old, match_cond, matviewname, count_colname, count_colname, count_colname, - matviewname); + (aggs_set !=3D NULL ? aggs_set->data : ""), + updt_returning, + matviewname, + select_for_recalc); =20 - if (SPI_exec(querybuf.data, 0) !=3D SPI_OK_DELETE) + if (SPI_exec(querybuf.data, 0) !=3D SPI_OK_SELECT) elog(ERROR, "SPI_exec failed: %s", querybuf.data); + + + /* Return tuples to be recalculated. */ + if (minmax_list) + { + *tuptable_recalc =3D SPI_tuptable; + *num_recalc =3D SPI_processed; + } + else + { + *tuptable_recalc =3D NULL; + *num_recalc =3D 0; + } } =20 /* @@ -2172,10 +2698,15 @@ apply_old_delta(const char *matviewname, const char= *deltaname_old, * matviewname. This is used when counting is required, that is, the view * has aggregate or distinct. Also, when a table in EXISTS sub queries * is modified. + * + * If the view desn't have aggregates or has GROUP BY, this requires a keys + * list to identify a tuple in the view. If the view has aggregates, this + * requires strings representing SET clause for updating aggregate values. */ static void apply_new_delta_with_count(const char *matviewname, const char* deltaname_= new, - List *keys, StringInfo target_list, const char* count_colname) + List *keys, StringInfo aggs_set, StringInfo target_list, + const char* count_colname) { StringInfoData querybuf; StringInfoData returning_keys; @@ -2206,6 +2737,7 @@ apply_new_delta_with_count(const char *matviewname, c= onst char* deltaname_new, appendStringInfo(&querybuf, "WITH updt AS (" /* update a tuple if this exists in the view */ "UPDATE %s AS mv SET %s =3D mv.%s OPERATOR(pg_catalog.+) diff.%s " + "%s " /* SET clauses for aggregates */ "FROM %s AS diff " "WHERE %s " /* tuple matching condition */ "RETURNING %s" /* returning keys of updated tuples */ @@ -2213,6 +2745,7 @@ apply_new_delta_with_count(const char *matviewname, c= onst char* deltaname_new, "SELECT %s FROM %s AS diff " "WHERE NOT EXISTS (SELECT 1 FROM updt AS mv WHERE %s);", matviewname, count_colname, count_colname, count_colname, + (aggs_set !=3D NULL ? aggs_set->data : ""), deltaname_new, match_cond, returning_keys.data, @@ -2287,6 +2820,349 @@ get_matching_condition_string(List *keys) return match_cond.data; } =20 +/* + * get_returning_string + * + * Build a string for RETURNING clause of UPDATE used in apply_old_delta_w= ith_count. + * This clause returns ctid and a boolean value that indicates if we need = to + * recalculate min or max value, for each updated row. + */ +static char * +get_returning_string(List *minmax_list, List *is_min_list, List *keys) +{ + StringInfoData returning; + char *recalc_cond; + ListCell *lc; + + Assert(minmax_list !=3D NIL && is_min_list !=3D NIL); + recalc_cond =3D get_minmax_recalc_condition_string(minmax_list, is_min_li= st); + + initStringInfo(&returning); + + appendStringInfo(&returning, "RETURNING mv.ctid AS tid, (%s) AS recalc", = recalc_cond); + foreach (lc, keys) + { + Form_pg_attribute attr =3D (Form_pg_attribute) lfirst(lc); + char *resname =3D NameStr(attr->attname); + appendStringInfo(&returning, ", %s", quote_qualified_identifier("mv", re= sname)); + } + + return returning.data; +} + +/* + * get_minmax_recalc_condition_string + * + * Build a predicate string for checking if any min/max aggregate + * value needs to be recalculated. + */ +static char * +get_minmax_recalc_condition_string(List *minmax_list, List *is_min_list) +{ + StringInfoData recalc_cond; + ListCell *lc1, *lc2; + + initStringInfo(&recalc_cond); + + Assert (list_length(minmax_list) =3D=3D list_length(is_min_list)); + + forboth (lc1, minmax_list, lc2, is_min_list) + { + char *resname =3D (char *) lfirst(lc1); + bool is_min =3D (bool) lfirst_int(lc2); + char *op_str =3D (is_min ? ">=3D" : "<=3D"); + + appendStringInfo(&recalc_cond, "%s OPERATOR(pg_catalog.%s) %s", + quote_qualified_identifier("mv", resname), + op_str, + quote_qualified_identifier("t", resname) + ); + + if (lnext(minmax_list, lc1)) + appendStringInfo(&recalc_cond, " OR "); + } + + return recalc_cond.data; +} + +/* + * get_select_for_recalc_string + * + * Build a query to return tid and keys of tuples which need + * recalculation. This is used as the result of the query + * built by apply_old_delta. + */ +static char * +get_select_for_recalc_string(List *keys) +{ + StringInfoData qry; + ListCell *lc; + + initStringInfo(&qry); + + appendStringInfo(&qry, "SELECT tid"); + foreach (lc, keys) + { + Form_pg_attribute attr =3D (Form_pg_attribute) lfirst(lc); + appendStringInfo(&qry, ", %s", NameStr(attr->attname)); + } + + appendStringInfo(&qry, " FROM updt WHERE recalc"); + + return qry.data; +} + +/* + * recalc_and_set_values + * + * Recalculate tuples in a materialized from base tables and update these. + * The tuples which needs recalculation are specified by keys, and resnames + * of columns to be updated are specified by namelist. TIDs and key values + * are given by tuples in tuptable_recalc. Its first attribute must be TID + * and key values must be following this. + */ +static void +recalc_and_set_values(SPITupleTable *tuptable_recalc, int64 num_tuples, + List *namelist, List *keys, Relation matviewRel) +{ + TupleDesc tupdesc_recalc =3D tuptable_recalc->tupdesc; + Oid *keyTypes =3D NULL, *types =3D NULL; + char *keyNulls =3D NULL, *nulls =3D NULL; + Datum *keyVals =3D NULL, *vals =3D NULL; + int num_vals =3D list_length(namelist); + int num_keys =3D list_length(keys); + uint64 i; + Oid matviewOid; + char *matviewname; + + matviewOid =3D RelationGetRelid(matviewRel); + matviewname =3D quote_qualified_identifier(get_namespace_name(RelationGet= Namespace(matviewRel)), + RelationGetRelationName(matviewRel)); + + /* If we have keys, initialize arrays for them. */ + if (keys) + { + keyTypes =3D palloc(sizeof(Oid) * num_keys); + keyNulls =3D palloc(sizeof(char) * num_keys); + keyVals =3D palloc(sizeof(Datum) * num_keys); + /* a tuple contains keys to be recalculated and ctid to be updated*/ + Assert(tupdesc_recalc->natts =3D=3D num_keys + 1); + + /* Types of key attributes */ + for (i =3D 0; i < num_keys; i++) + keyTypes[i] =3D TupleDescAttr(tupdesc_recalc, i + 1)->atttypid; + } + + /* allocate memory for all attribute names and tid */ + types =3D palloc(sizeof(Oid) * (num_vals + 1)); + nulls =3D palloc(sizeof(char) * (num_vals + 1)); + vals =3D palloc(sizeof(Datum) * (num_vals + 1)); + + /* For each tuple which needs recalculation */ + for (i =3D 0; i < num_tuples; i++) + { + int j; + bool isnull; + SPIPlanPtr plan; + SPITupleTable *tuptable_newvals; + TupleDesc tupdesc_newvals; + + /* Set group key values as parameters if needed. */ + if (keys) + { + for (j =3D 0; j < num_keys; j++) + { + keyVals[j] =3D SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc,= j + 2, &isnull); + if (isnull) + keyNulls[j] =3D 'n'; + else + keyNulls[j] =3D ' '; + } + } + + /* + * Get recalculated values from base tables. The result must be + * only one tuple thich contains the new values for specified keys. + */ + plan =3D get_plan_for_recalc(matviewOid, namelist, keys, keyTypes); + if (SPI_execute_plan(plan, keyVals, keyNulls, false, 0) !=3D SPI_OK_SELE= CT) + elog(ERROR, "SPI_execute_plan"); + if (SPI_processed !=3D 1) + elog(ERROR, "SPI_execute_plan returned zero or more than one rows"); + + tuptable_newvals =3D SPI_tuptable; + tupdesc_newvals =3D tuptable_newvals->tupdesc; + + Assert(tupdesc_newvals->natts =3D=3D num_vals); + + /* Set the new values as parameters */ + for (j =3D 0; j < tupdesc_newvals->natts; j++) + { + if (i =3D=3D 0) + types[j] =3D TupleDescAttr(tupdesc_newvals, j)->atttypid; + + vals[j] =3D SPI_getbinval(tuptable_newvals->vals[0], tupdesc_newvals, j= + 1, &isnull); + if (isnull) + nulls[j] =3D 'n'; + else + nulls[j] =3D ' '; + } + /* Set TID of the view tuple to be updated as a parameter */ + types[j] =3D TIDOID; + vals[j] =3D SPI_getbinval(tuptable_recalc->vals[i], tupdesc_recalc, 1, &= isnull); + nulls[j] =3D ' '; + + /* Update the view tuple to the new values */ + plan =3D get_plan_for_set_values(matviewOid, matviewname, namelist, type= s); + if (SPI_execute_plan(plan, vals, nulls, false, 0) !=3D SPI_OK_UPDATE) + elog(ERROR, "SPI_execute_plan"); + } +} + + +/* + * get_plan_for_recalc + * + * Create or fetch a plan for recalculating value in the view's target list + * from base tables using the definition query of materialized view specif= ied + * by matviewOid. namelist is a list of resnames of values to be recalcula= ted. + * + * keys is a list of keys to identify tuples to be recalculated if this is= not + * empty. KeyTypes is an array of types of keys. + */ +static SPIPlanPtr +get_plan_for_recalc(Oid matviewOid, List *namelist, List *keys, Oid *keyTy= pes) +{ + MV_QueryKey hash_key; + SPIPlanPtr plan; + + /* Fetch or prepare a saved plan for the recalculation */ + mv_BuildQueryKey(&hash_key, matviewOid, MV_PLAN_RECALC); + if ((plan =3D mv_FetchPreparedPlan(&hash_key)) =3D=3D NULL) + { + ListCell *lc; + StringInfoData str; + char *viewdef; + + /* get view definition of matview */ + viewdef =3D text_to_cstring((text *) DatumGetPointer( + DirectFunctionCall1(pg_get_viewdef, ObjectIdGetDatum(matviewOid)))); + /* get rid of trailing semi-colon */ + viewdef[strlen(viewdef)-1] =3D '\0'; + + /* + * Build a query string for recalculating values. This is like + * + * SELECT x1, x2, x3, ... FROM ( ... view definition query ...) mv + * WHERE (key1, key2, ...) =3D ($1, $2, ...); + */ + + initStringInfo(&str); + appendStringInfo(&str, "SELECT "); + foreach (lc, namelist) + { + appendStringInfo(&str, "%s", (char *) lfirst(lc)); + if (lnext(namelist, lc)) + appendStringInfoString(&str, ", "); + } + appendStringInfo(&str, " FROM (%s) mv", viewdef); + + if (keys) + { + int i =3D 1; + char paramname[16]; + + appendStringInfo(&str, " WHERE ("); + foreach (lc, keys) + { + Form_pg_attribute attr =3D (Form_pg_attribute) lfirst(lc); + char *resname =3D NameStr(attr->attname); + Oid typid =3D attr->atttypid; + + sprintf(paramname, "$%d", i); + appendStringInfo(&str, "("); + generate_equal(&str, typid, resname, paramname); + appendStringInfo(&str, " OR (%s IS NULL AND %s IS NULL))", + resname, paramname); + + if (lnext(keys, lc)) + appendStringInfoString(&str, " AND "); + i++; + } + appendStringInfo(&str, ")"); + } + else + keyTypes =3D NULL; + + plan =3D SPI_prepare(str.data, list_length(keys), keyTypes); + if (plan =3D=3D NULL) + elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SP= I_result), str.data); + + SPI_keepplan(plan); + mv_HashPreparedPlan(&hash_key, plan); + } + + return plan; +} + +/* + * get_plan_for_set_values + * + * Create or fetch a plan for applying new values calculated by + * get_plan_for_recalc to a materialized view specified by matviewOid. + * matviewname is the name of the view. namelist is a list of resnames + * of attributes to be updated, and valTypes is an array of types of the + * values. + */ +static SPIPlanPtr +get_plan_for_set_values(Oid matviewOid, char *matviewname, List *namelist, + Oid *valTypes) +{ + MV_QueryKey key; + SPIPlanPtr plan; + + /* Fetch or prepare a saved plan for the real check */ + mv_BuildQueryKey(&key, matviewOid, MV_PLAN_SET_VALUE); + if ((plan =3D mv_FetchPreparedPlan(&key)) =3D=3D NULL) + { + ListCell *lc; + StringInfoData str; + int i; + + /* + * Build a query string for applying min/max values. This is like + * + * UPDATE matviewname AS mv + * SET (x1, x2, x3, x4) =3D ($1, $2, $3, $4) + * WHERE ctid =3D $5; + */ + + initStringInfo(&str); + appendStringInfo(&str, "UPDATE %s AS mv SET (", matviewname); + foreach (lc, namelist) + { + appendStringInfo(&str, "%s", (char *) lfirst(lc)); + if (lnext(namelist, lc)) + appendStringInfoString(&str, ", "); + } + appendStringInfo(&str, ") =3D ROW("); + + for (i =3D 1; i <=3D list_length(namelist); i++) + appendStringInfo(&str, "%s$%d", (i=3D=3D1 ? "" : ", "), i); + + appendStringInfo(&str, ") WHERE ctid OPERATOR(pg_catalog.=3D) $%d", i); + + plan =3D SPI_prepare(str.data, list_length(namelist) + 1, valTypes); + if (plan =3D=3D NULL) + elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SP= I_result), str.data); + + SPI_keepplan(plan); + mv_HashPreparedPlan(&key, plan); + } + + return plan; +} + /* * generate_equals * @@ -2320,6 +3196,13 @@ mv_InitHashTables(void) { HASHCTL ctl; =20 + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize =3D sizeof(MV_QueryKey); + ctl.entrysize =3D sizeof(MV_QueryHashEntry); + mv_query_cache =3D hash_create("MV query cache", + MV_INIT_QUERYHASHSIZE, + &ctl, HASH_ELEM | HASH_BLOBS); + memset(&ctl, 0, sizeof(ctl)); ctl.keysize =3D sizeof(Oid); ctl.entrysize =3D sizeof(MV_TriggerHashEntry); @@ -2328,6 +3211,99 @@ mv_InitHashTables(void) &ctl, HASH_ELEM | HASH_BLOBS); } =20 +/* + * mv_FetchPreparedPlan + */ +static SPIPlanPtr +mv_FetchPreparedPlan(MV_QueryKey *key) +{ + MV_QueryHashEntry *entry; + SPIPlanPtr plan; + + /* + * On the first call initialize the hashtable + */ + if (!mv_query_cache) + mv_InitHashTables(); + + /* + * Lookup for the key + */ + entry =3D (MV_QueryHashEntry *) hash_search(mv_query_cache, + (void *) key, + HASH_FIND, NULL); + if (entry =3D=3D NULL) + return NULL; + + /* + * Check whether the plan is still valid. If it isn't, we don't want to + * simply rely on plancache.c to regenerate it; rather we should start + * from scratch and rebuild the query text too. This is to cover cases + * such as table/column renames. We depend on the plancache machinery to + * detect possible invalidations, though. + * + * CAUTION: this check is only trustworthy if the caller has already + * locked both materialized views and base tables. + */ + plan =3D entry->plan; + if (plan && SPI_plan_is_valid(plan)) + return plan; + + /* + * Otherwise we might as well flush the cached plan now, to free a little + * memory space before we make a new one. + */ + entry->plan =3D NULL; + if (plan) + SPI_freeplan(plan); + + return NULL; +} + +/* + * mv_HashPreparedPlan + * + * Add another plan to our private SPI query plan hashtable. + */ +static void +mv_HashPreparedPlan(MV_QueryKey *key, SPIPlanPtr plan) +{ + MV_QueryHashEntry *entry; + bool found; + + /* + * On the first call initialize the hashtable + */ + if (!mv_query_cache) + mv_InitHashTables(); + + /* + * Add the new plan. We might be overwriting an entry previously found + * invalid by mv_FetchPreparedPlan. + */ + entry =3D (MV_QueryHashEntry *) hash_search(mv_query_cache, + (void *) key, + HASH_ENTER, &found); + Assert(!found || entry->plan =3D=3D NULL); + entry->plan =3D plan; +} + +/* + * mv_BuildQueryKey + * + * Construct a hashtable key for a prepared SPI plan for IVM. + */ +static void +mv_BuildQueryKey(MV_QueryKey *key, Oid matview_id, int32 query_type) +{ + /* + * We assume struct MV_QueryKey contains no padding bytes, else we'd need + * to use memset to clear them. + */ + key->matview_id =3D matview_id; + key->query_type =3D query_type; +} + /* * AtAbort_IVM * diff --git a/src/include/commands/createas.h b/src/include/commands/createa= s.h index bcea9782d3..e36302845f 100644 --- a/src/include/commands/createas.h +++ b/src/include/commands/createas.h @@ -30,6 +30,7 @@ extern void CreateIvmTriggersOnBaseTables(Query *qry, Oid= matviewOid, bool is_cr extern void CreateIndexOnIMMV(Query *query, Relation matviewRel); =20 extern Query *rewriteQueryForIMMV(Query *query, List *colNames); +extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *res= name, AttrNumber *next_resno, List **aggs); =20 extern int GetIntoRelEFlags(IntoClause *intoClause); =20 --=20 2.17.1 --Multipart=_Fri__4_Feb_2022_01_48_06_+0900_N8BNZpfOR27sWrgY Content-Type: text/x-diff; name="v25-0007-Add-Incremental-View-Maintenance-support.patch" Content-Disposition: attachment; filename="v25-0007-Add-Incremental-View-Maintenance-support.patch" Content-Transfer-Encoding: 7bit ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: Using defines for protocol characters @ 2023-08-16 19:29 Nathan Bossart <[email protected]> 0 siblings, 1 reply; 8+ messages in thread From: Nathan Bossart @ 2023-08-16 19:29 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: Michael Paquier <[email protected]>; Tatsuo Ishii <[email protected]>; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; [email protected] On Tue, Aug 15, 2023 at 11:40:07PM +0200, Alvaro Herrera wrote: > On 2023-Aug-16, Michael Paquier wrote: > >> On Wed, Aug 16, 2023 at 06:25:09AM +0900, Tatsuo Ishii wrote: >> > Currently pqcomm.h needs c.h which is not problem for Pgpool-II. But >> > what about other middleware? >> >> Why do you need to include directly c.h? There are definitions in >> there that are not intended to be exposed. > > What this argument says is that these new defines should be in a > separate file, not in pqcomm.h. IMO that makes sense, precisely because > these defines should be usable by third parties. I moved the definitions out to a separate file in v6. -- Nathan Bossart Amazon Web Services: https://aws.amazon.com Attachments: [text/x-diff] v6-0001-Introduce-macros-for-protocol-characters.patch (52.1K, ../../20230816192956.GA2908487@nathanxps13/2-v6-0001-Introduce-macros-for-protocol-characters.patch) download | inline diff: From fc54d1655fe662abe99a4605bdff2d1b65f0dc2a Mon Sep 17 00:00:00 2001 From: Nathan Bossart <[email protected]> Date: Wed, 16 Aug 2023 12:08:29 -0700 Subject: [PATCH v6 1/1] Introduce macros for protocol characters. Author: Dave Cramer Reviewed-by: Alvaro Herrera, Tatsuo Ishii, Peter Smith, Robert Haas, Tom Lane, Peter Eisentraut Discussion: https://postgr.es/m/CADK3HHKbBmK-PKf1bPNFoMC%2BoBt%2BpD9PH8h5nvmBQskEHm-Ehw%40mail.gmail.com --- src/backend/access/common/printsimple.c | 5 +- src/backend/access/transam/parallel.c | 14 ++-- src/backend/backup/basebackup_copy.c | 16 ++--- src/backend/commands/async.c | 2 +- src/backend/commands/copyfromparse.c | 22 +++---- src/backend/commands/copyto.c | 6 +- src/backend/libpq/auth-sasl.c | 2 +- src/backend/libpq/auth.c | 8 +-- src/backend/postmaster/postmaster.c | 2 +- src/backend/replication/walsender.c | 18 +++--- src/backend/tcop/dest.c | 8 +-- src/backend/tcop/fastpath.c | 2 +- src/backend/tcop/postgres.c | 68 ++++++++++---------- src/backend/utils/error/elog.c | 5 +- src/backend/utils/misc/guc.c | 2 +- src/include/Makefile | 3 +- src/include/libpq/pqcomm.h | 23 ++----- src/include/libpq/protocol.h | 85 +++++++++++++++++++++++++ src/include/meson.build | 1 + src/interfaces/libpq/fe-auth.c | 2 +- src/interfaces/libpq/fe-connect.c | 19 ++++-- src/interfaces/libpq/fe-exec.c | 50 +++++++-------- src/interfaces/libpq/fe-protocol3.c | 70 ++++++++++---------- src/interfaces/libpq/fe-trace.c | 70 +++++++++++--------- 24 files changed, 301 insertions(+), 202 deletions(-) create mode 100644 src/include/libpq/protocol.h diff --git a/src/backend/access/common/printsimple.c b/src/backend/access/common/printsimple.c index ef818228ac..675b744db2 100644 --- a/src/backend/access/common/printsimple.c +++ b/src/backend/access/common/printsimple.c @@ -20,6 +20,7 @@ #include "access/printsimple.h" #include "catalog/pg_type.h" +#include "libpq/protocol.h" #include "libpq/pqformat.h" #include "utils/builtins.h" @@ -32,7 +33,7 @@ printsimple_startup(DestReceiver *self, int operation, TupleDesc tupdesc) StringInfoData buf; int i; - pq_beginmessage(&buf, 'T'); /* RowDescription */ + pq_beginmessage(&buf, PqMsg_RowDescription); pq_sendint16(&buf, tupdesc->natts); for (i = 0; i < tupdesc->natts; ++i) @@ -65,7 +66,7 @@ printsimple(TupleTableSlot *slot, DestReceiver *self) slot_getallattrs(slot); /* Prepare and send message */ - pq_beginmessage(&buf, 'D'); + pq_beginmessage(&buf, PqMsg_DataRow); pq_sendint16(&buf, tupdesc->natts); for (i = 0; i < tupdesc->natts; ++i) diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c index 1738aecf1f..194a1207be 100644 --- a/src/backend/access/transam/parallel.c +++ b/src/backend/access/transam/parallel.c @@ -1127,7 +1127,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg) switch (msgtype) { - case 'K': /* BackendKeyData */ + case PqMsg_BackendKeyData: { int32 pid = pq_getmsgint(msg, 4); @@ -1137,8 +1137,8 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg) break; } - case 'E': /* ErrorResponse */ - case 'N': /* NoticeResponse */ + case PqMsg_ErrorResponse: + case PqMsg_NoticeResponse: { ErrorData edata; ErrorContextCallback *save_error_context_stack; @@ -1183,7 +1183,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg) break; } - case 'A': /* NotifyResponse */ + case PqMsg_NotificationResponse: { /* Propagate NotifyResponse. */ int32 pid; @@ -1217,7 +1217,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg) break; } - case 'X': /* Terminate, indicating clean exit */ + case PqMsg_Terminate: { shm_mq_detach(pcxt->worker[i].error_mqh); pcxt->worker[i].error_mqh = NULL; @@ -1372,7 +1372,7 @@ ParallelWorkerMain(Datum main_arg) * protocol message is defined, but it won't actually be used for anything * in this case. */ - pq_beginmessage(&msgbuf, 'K'); + pq_beginmessage(&msgbuf, PqMsg_BackendKeyData); pq_sendint32(&msgbuf, (int32) MyProcPid); pq_sendint32(&msgbuf, (int32) MyCancelKey); pq_endmessage(&msgbuf); @@ -1550,7 +1550,7 @@ ParallelWorkerMain(Datum main_arg) DetachSession(); /* Report success. */ - pq_putmessage('X', NULL, 0); + pq_putmessage(PqMsg_Terminate, NULL, 0); } /* diff --git a/src/backend/backup/basebackup_copy.c b/src/backend/backup/basebackup_copy.c index 1db80cde1b..fee30c21e1 100644 --- a/src/backend/backup/basebackup_copy.c +++ b/src/backend/backup/basebackup_copy.c @@ -152,7 +152,7 @@ bbsink_copystream_begin_backup(bbsink *sink) SendTablespaceList(state->tablespaces); /* Send a CommandComplete message */ - pq_puttextmessage('C', "SELECT"); + pq_puttextmessage(PqMsg_CommandComplete, "SELECT"); /* Begin COPY stream. This will be used for all archives + manifest. */ SendCopyOutResponse(); @@ -169,7 +169,7 @@ bbsink_copystream_begin_archive(bbsink *sink, const char *archive_name) StringInfoData buf; ti = list_nth(state->tablespaces, state->tablespace_num); - pq_beginmessage(&buf, 'd'); /* CopyData */ + pq_beginmessage(&buf, PqMsg_CopyData); pq_sendbyte(&buf, 'n'); /* New archive */ pq_sendstring(&buf, archive_name); pq_sendstring(&buf, ti->path == NULL ? "" : ti->path); @@ -220,7 +220,7 @@ bbsink_copystream_archive_contents(bbsink *sink, size_t len) { mysink->last_progress_report_time = now; - pq_beginmessage(&buf, 'd'); /* CopyData */ + pq_beginmessage(&buf, PqMsg_CopyData); pq_sendbyte(&buf, 'p'); /* Progress report */ pq_sendint64(&buf, state->bytes_done); pq_endmessage(&buf); @@ -246,7 +246,7 @@ bbsink_copystream_end_archive(bbsink *sink) mysink->bytes_done_at_last_time_check = state->bytes_done; mysink->last_progress_report_time = GetCurrentTimestamp(); - pq_beginmessage(&buf, 'd'); /* CopyData */ + pq_beginmessage(&buf, PqMsg_CopyData); pq_sendbyte(&buf, 'p'); /* Progress report */ pq_sendint64(&buf, state->bytes_done); pq_endmessage(&buf); @@ -261,7 +261,7 @@ bbsink_copystream_begin_manifest(bbsink *sink) { StringInfoData buf; - pq_beginmessage(&buf, 'd'); /* CopyData */ + pq_beginmessage(&buf, PqMsg_CopyData); pq_sendbyte(&buf, 'm'); /* Manifest */ pq_endmessage(&buf); } @@ -318,7 +318,7 @@ SendCopyOutResponse(void) { StringInfoData buf; - pq_beginmessage(&buf, 'H'); + pq_beginmessage(&buf, PqMsg_CopyOutResponse); pq_sendbyte(&buf, 0); /* overall format */ pq_sendint16(&buf, 0); /* natts */ pq_endmessage(&buf); @@ -330,7 +330,7 @@ SendCopyOutResponse(void) static void SendCopyDone(void) { - pq_putemptymessage('c'); + pq_putemptymessage(PqMsg_CopyDone); } /* @@ -368,7 +368,7 @@ SendXlogRecPtrResult(XLogRecPtr ptr, TimeLineID tli) end_tup_output(tstate); /* Send a CommandComplete message */ - pq_puttextmessage('C', "SELECT"); + pq_puttextmessage(PqMsg_CommandComplete, "SELECT"); } /* diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c index ef909cf4e0..d148d10850 100644 --- a/src/backend/commands/async.c +++ b/src/backend/commands/async.c @@ -2281,7 +2281,7 @@ NotifyMyFrontEnd(const char *channel, const char *payload, int32 srcPid) { StringInfoData buf; - pq_beginmessage(&buf, 'A'); + pq_beginmessage(&buf, PqMsg_NotificationResponse); pq_sendint32(&buf, srcPid); pq_sendstring(&buf, channel); pq_sendstring(&buf, payload); diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 232768a6e1..f553734582 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -174,7 +174,7 @@ ReceiveCopyBegin(CopyFromState cstate) int16 format = (cstate->opts.binary ? 1 : 0); int i; - pq_beginmessage(&buf, 'G'); + pq_beginmessage(&buf, PqMsg_CopyInResponse); pq_sendbyte(&buf, format); /* overall format */ pq_sendint16(&buf, natts); for (i = 0; i < natts; i++) @@ -279,13 +279,13 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread) /* Validate message type and set packet size limit */ switch (mtype) { - case 'd': /* CopyData */ + case PqMsg_CopyData: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; break; - case 'c': /* CopyDone */ - case 'f': /* CopyFail */ - case 'H': /* Flush */ - case 'S': /* Sync */ + case PqMsg_CopyDone: + case PqMsg_CopyFail: + case PqMsg_Flush: + case PqMsg_Sync: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; break; default: @@ -305,20 +305,20 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread) /* ... and process it */ switch (mtype) { - case 'd': /* CopyData */ + case PqMsg_CopyData: break; - case 'c': /* CopyDone */ + case PqMsg_CopyDone: /* COPY IN correctly terminated by frontend */ cstate->raw_reached_eof = true; return bytesread; - case 'f': /* CopyFail */ + case PqMsg_CopyFail: ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), errmsg("COPY from stdin failed: %s", pq_getmsgstring(cstate->fe_msgbuf)))); break; - case 'H': /* Flush */ - case 'S': /* Sync */ + case PqMsg_Flush: + case PqMsg_Sync: /* * Ignore Flush/Sync for the convenience of client diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 9e4b2437a5..eaa3172793 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -144,7 +144,7 @@ SendCopyBegin(CopyToState cstate) int16 format = (cstate->opts.binary ? 1 : 0); int i; - pq_beginmessage(&buf, 'H'); + pq_beginmessage(&buf, PqMsg_CopyOutResponse); pq_sendbyte(&buf, format); /* overall format */ pq_sendint16(&buf, natts); for (i = 0; i < natts; i++) @@ -159,7 +159,7 @@ SendCopyEnd(CopyToState cstate) /* Shouldn't have any unsent data */ Assert(cstate->fe_msgbuf->len == 0); /* Send Copy Done message */ - pq_putemptymessage('c'); + pq_putemptymessage(PqMsg_CopyDone); } /*---------- @@ -247,7 +247,7 @@ CopySendEndOfRow(CopyToState cstate) CopySendChar(cstate, '\n'); /* Dump the accumulated row as one CopyData message */ - (void) pq_putmessage('d', fe_msgbuf->data, fe_msgbuf->len); + (void) pq_putmessage(PqMsg_CopyData, fe_msgbuf->data, fe_msgbuf->len); break; case COPY_CALLBACK: cstate->data_dest_cb(fe_msgbuf->data, fe_msgbuf->len); diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c index 684680897b..c535bc5383 100644 --- a/src/backend/libpq/auth-sasl.c +++ b/src/backend/libpq/auth-sasl.c @@ -87,7 +87,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass, { pq_startmsgread(); mtype = pq_getbyte(); - if (mtype != 'p') + if (mtype != PqMsg_SASLResponse) { /* Only log error if client didn't disconnect. */ if (mtype != EOF) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 315a24bb3f..0356fe3e45 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -665,7 +665,7 @@ sendAuthRequest(Port *port, AuthRequest areq, const char *extradata, int extrale CHECK_FOR_INTERRUPTS(); - pq_beginmessage(&buf, 'R'); + pq_beginmessage(&buf, PqMsg_AuthenticationRequest); pq_sendint32(&buf, (int32) areq); if (extralen > 0) pq_sendbytes(&buf, extradata, extralen); @@ -698,7 +698,7 @@ recv_password_packet(Port *port) /* Expect 'p' message type */ mtype = pq_getbyte(); - if (mtype != 'p') + if (mtype != PqMsg_PasswordMessage) { /* * If the client just disconnects without offering a password, don't @@ -961,7 +961,7 @@ pg_GSS_recvauth(Port *port) CHECK_FOR_INTERRUPTS(); mtype = pq_getbyte(); - if (mtype != 'p') + if (mtype != PqMsg_GSSResponse) { /* Only log error if client didn't disconnect. */ if (mtype != EOF) @@ -1232,7 +1232,7 @@ pg_SSPI_recvauth(Port *port) { pq_startmsgread(); mtype = pq_getbyte(); - if (mtype != 'p') + if (mtype != PqMsg_GSSResponse) { if (sspictx != NULL) { diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 9c8ec779f9..07d376d77e 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -2357,7 +2357,7 @@ SendNegotiateProtocolVersion(List *unrecognized_protocol_options) StringInfoData buf; ListCell *lc; - pq_beginmessage(&buf, 'v'); /* NegotiateProtocolVersion */ + pq_beginmessage(&buf, PqMsg_NegotiateProtocolVersion); pq_sendint32(&buf, PG_PROTOCOL_LATEST); pq_sendint32(&buf, list_length(unrecognized_protocol_options)); foreach(lc, unrecognized_protocol_options) diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index d27ef2985d..80374c55be 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -603,7 +603,7 @@ SendTimeLineHistory(TimeLineHistoryCmd *cmd) dest->rStartup(dest, CMD_SELECT, tupdesc); /* Send a DataRow message */ - pq_beginmessage(&buf, 'D'); + pq_beginmessage(&buf, PqMsg_DataRow); pq_sendint16(&buf, 2); /* # of columns */ len = strlen(histfname); pq_sendint32(&buf, len); /* col1 len */ @@ -801,7 +801,7 @@ StartReplication(StartReplicationCmd *cmd) WalSndSetState(WALSNDSTATE_CATCHUP); /* Send a CopyBothResponse message, and start streaming */ - pq_beginmessage(&buf, 'W'); + pq_beginmessage(&buf, PqMsg_CopyBothResponse); pq_sendbyte(&buf, 0); pq_sendint16(&buf, 0); pq_endmessage(&buf); @@ -1294,7 +1294,7 @@ StartLogicalReplication(StartReplicationCmd *cmd) WalSndSetState(WALSNDSTATE_CATCHUP); /* Send a CopyBothResponse message, and start streaming */ - pq_beginmessage(&buf, 'W'); + pq_beginmessage(&buf, PqMsg_CopyBothResponse); pq_sendbyte(&buf, 0); pq_sendint16(&buf, 0); pq_endmessage(&buf); @@ -1923,11 +1923,11 @@ ProcessRepliesIfAny(void) /* Validate message type and set packet size limit */ switch (firstchar) { - case 'd': + case PqMsg_CopyData: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; break; - case 'c': - case 'X': + case PqMsg_CopyDone: + case PqMsg_Terminate: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; break; default: @@ -1955,7 +1955,7 @@ ProcessRepliesIfAny(void) /* * 'd' means a standby reply wrapped in a CopyData packet. */ - case 'd': + case PqMsg_CopyData: ProcessStandbyMessage(); received = true; break; @@ -1964,7 +1964,7 @@ ProcessRepliesIfAny(void) * CopyDone means the standby requested to finish streaming. * Reply with CopyDone, if we had not sent that already. */ - case 'c': + case PqMsg_CopyDone: if (!streamingDoneSending) { pq_putmessage_noblock('c', NULL, 0); @@ -1978,7 +1978,7 @@ ProcessRepliesIfAny(void) /* * 'X' means that the standby is closing down the socket. */ - case 'X': + case PqMsg_Terminate: proc_exit(0); default: diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index c0406e2ee5..06d1872b9a 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -176,7 +176,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o len = BuildQueryCompletionString(completionTag, qc, force_undecorated_output); - pq_putmessage('C', completionTag, len + 1); + pq_putmessage(PqMsg_Close, completionTag, len + 1); case DestNone: case DestDebug: @@ -200,7 +200,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o void EndReplicationCommand(const char *commandTag) { - pq_putmessage('C', commandTag, strlen(commandTag) + 1); + pq_putmessage(PqMsg_Close, commandTag, strlen(commandTag) + 1); } /* ---------------- @@ -220,7 +220,7 @@ NullCommand(CommandDest dest) case DestRemoteSimple: /* Tell the FE that we saw an empty query string */ - pq_putemptymessage('I'); + pq_putemptymessage(PqMsg_EmptyQueryResponse); break; case DestNone: @@ -258,7 +258,7 @@ ReadyForQuery(CommandDest dest) { StringInfoData buf; - pq_beginmessage(&buf, 'Z'); + pq_beginmessage(&buf, PqMsg_ReadyForQuery); pq_sendbyte(&buf, TransactionBlockStatusCode()); pq_endmessage(&buf); } diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c index 2f70ebd5fa..71f161dbe2 100644 --- a/src/backend/tcop/fastpath.c +++ b/src/backend/tcop/fastpath.c @@ -69,7 +69,7 @@ SendFunctionResult(Datum retval, bool isnull, Oid rettype, int16 format) { StringInfoData buf; - pq_beginmessage(&buf, 'V'); + pq_beginmessage(&buf, PqMsg_FunctionCallResponse); if (isnull) { diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 36cc99ec9c..e4756f8be2 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -402,37 +402,37 @@ SocketBackend(StringInfo inBuf) */ switch (qtype) { - case 'Q': /* simple query */ + case PqMsg_Query: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; doing_extended_query_message = false; break; - case 'F': /* fastpath function call */ + case PqMsg_FunctionCall: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; doing_extended_query_message = false; break; - case 'X': /* terminate */ + case PqMsg_Terminate: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; doing_extended_query_message = false; ignore_till_sync = false; break; - case 'B': /* bind */ - case 'P': /* parse */ + case PqMsg_Bind: + case PqMsg_Parse: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; doing_extended_query_message = true; break; - case 'C': /* close */ - case 'D': /* describe */ - case 'E': /* execute */ - case 'H': /* flush */ + case PqMsg_Close: + case PqMsg_Describe: + case PqMsg_Execute: + case PqMsg_Flush: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; doing_extended_query_message = true; break; - case 'S': /* sync */ + case PqMsg_Sync: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; /* stop any active skip-till-Sync */ ignore_till_sync = false; @@ -440,13 +440,13 @@ SocketBackend(StringInfo inBuf) doing_extended_query_message = false; break; - case 'd': /* copy data */ + case PqMsg_CopyData: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; doing_extended_query_message = false; break; - case 'c': /* copy done */ - case 'f': /* copy fail */ + case PqMsg_CopyDone: + case PqMsg_CopyFail: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; doing_extended_query_message = false; break; @@ -1589,7 +1589,7 @@ exec_parse_message(const char *query_string, /* string to execute */ * Send ParseComplete. */ if (whereToSendOutput == DestRemote) - pq_putemptymessage('1'); + pq_putemptymessage(PqMsg_ParseComplete); /* * Emit duration logging if appropriate. @@ -2047,7 +2047,7 @@ exec_bind_message(StringInfo input_message) * Send BindComplete. */ if (whereToSendOutput == DestRemote) - pq_putemptymessage('2'); + pq_putemptymessage(PqMsg_BindComplete); /* * Emit duration logging if appropriate. @@ -2290,7 +2290,7 @@ exec_execute_message(const char *portal_name, long max_rows) { /* Portal run not complete, so send PortalSuspended */ if (whereToSendOutput == DestRemote) - pq_putemptymessage('s'); + pq_putemptymessage(PqMsg_PortalSuspended); /* * Set XACT_FLAGS_PIPELINING whenever we suspend an Execute message, @@ -2683,7 +2683,7 @@ exec_describe_statement_message(const char *stmt_name) NULL); } else - pq_putemptymessage('n'); /* NoData */ + pq_putemptymessage(PqMsg_NoData); } /* @@ -2736,7 +2736,7 @@ exec_describe_portal_message(const char *portal_name) FetchPortalTargetList(portal), portal->formats); else - pq_putemptymessage('n'); /* NoData */ + pq_putemptymessage(PqMsg_NoData); } @@ -4239,7 +4239,7 @@ PostgresMain(const char *dbname, const char *username) { StringInfoData buf; - pq_beginmessage(&buf, 'K'); + pq_beginmessage(&buf, PqMsg_BackendKeyData); pq_sendint32(&buf, (int32) MyProcPid); pq_sendint32(&buf, (int32) MyCancelKey); pq_endmessage(&buf); @@ -4618,7 +4618,7 @@ PostgresMain(const char *dbname, const char *username) switch (firstchar) { - case 'Q': /* simple query */ + case PqMsg_Query: { const char *query_string; @@ -4642,7 +4642,7 @@ PostgresMain(const char *dbname, const char *username) } break; - case 'P': /* parse */ + case PqMsg_Parse: { const char *stmt_name; const char *query_string; @@ -4672,7 +4672,7 @@ PostgresMain(const char *dbname, const char *username) } break; - case 'B': /* bind */ + case PqMsg_Bind: forbidden_in_wal_sender(firstchar); /* Set statement_timestamp() */ @@ -4687,7 +4687,7 @@ PostgresMain(const char *dbname, const char *username) /* exec_bind_message does valgrind_report_error_query */ break; - case 'E': /* execute */ + case PqMsg_Execute: { const char *portal_name; int max_rows; @@ -4707,7 +4707,7 @@ PostgresMain(const char *dbname, const char *username) } break; - case 'F': /* fastpath function call */ + case PqMsg_FunctionCall: forbidden_in_wal_sender(firstchar); /* Set statement_timestamp() */ @@ -4742,7 +4742,7 @@ PostgresMain(const char *dbname, const char *username) send_ready_for_query = true; break; - case 'C': /* close */ + case PqMsg_Close: { int close_type; const char *close_target; @@ -4782,13 +4782,13 @@ PostgresMain(const char *dbname, const char *username) } if (whereToSendOutput == DestRemote) - pq_putemptymessage('3'); /* CloseComplete */ + pq_putemptymessage(PqMsg_CloseComplete); valgrind_report_error_query("CLOSE message"); } break; - case 'D': /* describe */ + case PqMsg_Describe: { int describe_type; const char *describe_target; @@ -4822,13 +4822,13 @@ PostgresMain(const char *dbname, const char *username) } break; - case 'H': /* flush */ + case PqMsg_Flush: pq_getmsgend(&input_message); if (whereToSendOutput == DestRemote) pq_flush(); break; - case 'S': /* sync */ + case PqMsg_Sync: pq_getmsgend(&input_message); finish_xact_command(); valgrind_report_error_query("SYNC message"); @@ -4847,7 +4847,7 @@ PostgresMain(const char *dbname, const char *username) /* FALLTHROUGH */ - case 'X': + case PqMsg_Terminate: /* * Reset whereToSendOutput to prevent ereport from attempting @@ -4865,9 +4865,9 @@ PostgresMain(const char *dbname, const char *username) */ proc_exit(0); - case 'd': /* copy data */ - case 'c': /* copy done */ - case 'f': /* copy fail */ + case PqMsg_CopyData: + case PqMsg_CopyDone: + case PqMsg_CopyFail: /* * Accept but ignore these messages, per protocol spec; we @@ -4897,7 +4897,7 @@ forbidden_in_wal_sender(char firstchar) { if (am_walsender) { - if (firstchar == 'F') + if (firstchar == PqMsg_FunctionCall) ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("fastpath function calls not supported in a replication connection"))); diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 5898100acb..8e1f3e8521 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -3465,7 +3465,10 @@ send_message_to_frontend(ErrorData *edata) char tbuf[12]; /* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */ - pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E'); + if (edata->elevel < ERROR) + pq_beginmessage(&msgbuf, PqMsg_NoticeResponse); + else + pq_beginmessage(&msgbuf, PqMsg_ErrorResponse); sev = error_severity(edata->elevel); pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 99bb2fdd19..84e7ad4d90 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -2593,7 +2593,7 @@ ReportGUCOption(struct config_generic *record) { StringInfoData msgbuf; - pq_beginmessage(&msgbuf, 'S'); + pq_beginmessage(&msgbuf, PqMsg_ParameterStatus); pq_sendstring(&msgbuf, record->name); pq_sendstring(&msgbuf, val); pq_endmessage(&msgbuf); diff --git a/src/include/Makefile b/src/include/Makefile index 5d213187e2..2d5242561c 100644 --- a/src/include/Makefile +++ b/src/include/Makefile @@ -40,6 +40,7 @@ install: all installdirs $(INSTALL_DATA) $(srcdir)/port.h '$(DESTDIR)$(includedir_internal)' $(INSTALL_DATA) $(srcdir)/postgres_fe.h '$(DESTDIR)$(includedir_internal)' $(INSTALL_DATA) $(srcdir)/libpq/pqcomm.h '$(DESTDIR)$(includedir_internal)/libpq' + $(INSTALL_DATA) $(srcdir)/libpq/protocol.h '$(DESTDIR)$(includedir_internal)/libpq' # These headers are needed for server-side development $(INSTALL_DATA) pg_config.h '$(DESTDIR)$(includedir_server)' $(INSTALL_DATA) pg_config_ext.h '$(DESTDIR)$(includedir_server)' @@ -65,7 +66,7 @@ installdirs: uninstall: rm -f $(addprefix '$(DESTDIR)$(includedir)'/, pg_config.h pg_config_ext.h pg_config_os.h pg_config_manual.h postgres_ext.h libpq/libpq-fs.h) - rm -f $(addprefix '$(DESTDIR)$(includedir_internal)'/, c.h port.h postgres_fe.h libpq/pqcomm.h) + rm -f $(addprefix '$(DESTDIR)$(includedir_internal)'/, c.h port.h postgres_fe.h libpq/pqcomm.h libpq/protocol.h) # heuristic... rm -rf $(addprefix '$(DESTDIR)$(includedir_server)'/, $(SUBDIRS) *.h) diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index 3da00f7983..46a0946b8b 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -21,6 +21,12 @@ #include <netdb.h> #include <netinet/in.h> +/* + * The definitions for the request/response codes are kept in a separate file + * for ease of use in third party programs. + */ +#include "libpq/protocol.h" + typedef struct { struct sockaddr_storage addr; @@ -112,23 +118,6 @@ typedef uint32 PacketLen; #define MAX_STARTUP_PACKET_LENGTH 10000 -/* These are the authentication request codes sent by the backend. */ - -#define AUTH_REQ_OK 0 /* User is authenticated */ -#define AUTH_REQ_KRB4 1 /* Kerberos V4. Not supported any more. */ -#define AUTH_REQ_KRB5 2 /* Kerberos V5. Not supported any more. */ -#define AUTH_REQ_PASSWORD 3 /* Password */ -#define AUTH_REQ_CRYPT 4 /* crypt password. Not supported any more. */ -#define AUTH_REQ_MD5 5 /* md5 password */ -/* 6 is available. It was used for SCM creds, not supported any more. */ -#define AUTH_REQ_GSS 7 /* GSSAPI without wrap() */ -#define AUTH_REQ_GSS_CONT 8 /* Continue GSS exchanges */ -#define AUTH_REQ_SSPI 9 /* SSPI negotiate without wrap() */ -#define AUTH_REQ_SASL 10 /* Begin SASL authentication */ -#define AUTH_REQ_SASL_CONT 11 /* Continue SASL authentication */ -#define AUTH_REQ_SASL_FIN 12 /* Final SASL message */ -#define AUTH_REQ_MAX AUTH_REQ_SASL_FIN /* maximum AUTH_REQ_* value */ - typedef uint32 AuthRequest; diff --git a/src/include/libpq/protocol.h b/src/include/libpq/protocol.h new file mode 100644 index 0000000000..cc46f4b586 --- /dev/null +++ b/src/include/libpq/protocol.h @@ -0,0 +1,85 @@ +/*------------------------------------------------------------------------- + * + * protocol.h + * Definitions of the request/response codes for the wire protocol. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/libpq/protocol.h + * + *------------------------------------------------------------------------- + */ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +/* These are the request codes sent by the frontend. */ + +#define PqMsg_Bind 'B' +#define PqMsg_Close 'C' +#define PqMsg_Describe 'D' +#define PqMsg_Execute 'E' +#define PqMsg_FunctionCall 'F' +#define PqMsg_Flush 'H' +#define PqMsg_Parse 'P' +#define PqMsg_Query 'Q' +#define PqMsg_Sync 'S' +#define PqMsg_Terminate 'X' +#define PqMsg_CopyFail 'f' +#define PqMsg_GSSResponse 'p' +#define PqMsg_PasswordMessage 'p' +#define PqMsg_SASLInitialResponse 'p' +#define PqMsg_SASLResponse 'p' + + +/* These are the response codes sent by the backend. */ + +#define PqMsg_ParseComplete '1' +#define PqMsg_BindComplete '2' +#define PqMsg_CloseComplete '3' +#define PqMsg_NotificationResponse 'A' +#define PqMsg_CommandComplete 'C' +#define PqMsg_DataRow 'D' +#define PqMsg_ErrorResponse 'E' +#define PqMsg_CopyInResponse 'G' +#define PqMsg_CopyOutResponse 'H' +#define PqMsg_EmptyQueryResponse 'I' +#define PqMsg_BackendKeyData 'K' +#define PqMsg_NoticeResponse 'N' +#define PqMsg_AuthenticationRequest 'R' +#define PqMsg_ParameterStatus 'S' +#define PqMsg_RowDescription 'T' +#define PqMsg_FunctionCallResponse 'V' +#define PqMsg_CopyBothResponse 'W' +#define PqMsg_ReadyForQuery 'Z' +#define PqMsg_NoData 'n' +#define PqMsg_PortalSuspended 's' +#define PqMsg_ParameterDescription 't' +#define PqMsg_NegotiateProtocolVersion 'v' + + +/* These are the codes sent by both the frontend and backend. */ + +#define PqMsg_CopyDone 'c' +#define PqMsg_CopyData 'd' + + +/* These are the authentication request codes sent by the backend. */ + +#define AUTH_REQ_OK 0 /* User is authenticated */ +#define AUTH_REQ_KRB4 1 /* Kerberos V4. Not supported any more. */ +#define AUTH_REQ_KRB5 2 /* Kerberos V5. Not supported any more. */ +#define AUTH_REQ_PASSWORD 3 /* Password */ +#define AUTH_REQ_CRYPT 4 /* crypt password. Not supported any more. */ +#define AUTH_REQ_MD5 5 /* md5 password */ +/* 6 is available. It was used for SCM creds, not supported any more. */ +#define AUTH_REQ_GSS 7 /* GSSAPI without wrap() */ +#define AUTH_REQ_GSS_CONT 8 /* Continue GSS exchanges */ +#define AUTH_REQ_SSPI 9 /* SSPI negotiate without wrap() */ +#define AUTH_REQ_SASL 10 /* Begin SASL authentication */ +#define AUTH_REQ_SASL_CONT 11 /* Continue SASL authentication */ +#define AUTH_REQ_SASL_FIN 12 /* Final SASL message */ +#define AUTH_REQ_MAX AUTH_REQ_SASL_FIN /* maximum AUTH_REQ_* value */ + +#endif /* PROTOCOL_H */ diff --git a/src/include/meson.build b/src/include/meson.build index d7e1ecd4c9..d50897c9fd 100644 --- a/src/include/meson.build +++ b/src/include/meson.build @@ -94,6 +94,7 @@ install_headers( install_headers( 'libpq/pqcomm.h', + 'libpq/protocol.h', install_dir: dir_include_internal / 'libpq', ) diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index 887ca5e9e1..912aa14821 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -586,7 +586,7 @@ pg_SASL_init(PGconn *conn, int payloadlen) /* * Build a SASLInitialResponse message, and send it. */ - if (pqPutMsgStart('p', conn)) + if (pqPutMsgStart(PqMsg_SASLInitialResponse, conn)) goto error; if (pqPuts(selected_mechanism, conn)) goto error; diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 837c5321aa..bf83a9b569 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -3591,7 +3591,9 @@ keep_going: /* We will come back to here until there is * Anything else probably means it's not Postgres on the other * end at all. */ - if (!(beresp == 'R' || beresp == 'v' || beresp == 'E')) + if (beresp != PqMsg_AuthenticationRequest && + beresp != PqMsg_ErrorResponse && + beresp != PqMsg_NegotiateProtocolVersion) { libpq_append_conn_error(conn, "expected authentication request from server, but received %c", beresp); @@ -3618,19 +3620,22 @@ keep_going: /* We will come back to here until there is * version 14, the server also used the old protocol for * errors that happened before processing the startup packet.) */ - if (beresp == 'R' && (msgLength < 8 || msgLength > 2000)) + if (beresp == PqMsg_AuthenticationRequest && + (msgLength < 8 || msgLength > 2000)) { libpq_append_conn_error(conn, "received invalid authentication request"); goto error_return; } - if (beresp == 'v' && (msgLength < 8 || msgLength > 2000)) + if (beresp == PqMsg_NegotiateProtocolVersion && + (msgLength < 8 || msgLength > 2000)) { libpq_append_conn_error(conn, "received invalid protocol negotiation message"); goto error_return; } #define MAX_ERRLEN 30000 - if (beresp == 'E' && (msgLength < 8 || msgLength > MAX_ERRLEN)) + if (beresp == PqMsg_ErrorResponse && + (msgLength < 8 || msgLength > MAX_ERRLEN)) { /* Handle error from a pre-3.0 server */ conn->inCursor = conn->inStart + 1; /* reread data */ @@ -3693,7 +3698,7 @@ keep_going: /* We will come back to here until there is } /* Handle errors. */ - if (beresp == 'E') + if (beresp == PqMsg_ErrorResponse) { if (pqGetErrorNotice3(conn, true)) { @@ -3770,7 +3775,7 @@ keep_going: /* We will come back to here until there is goto error_return; } - else if (beresp == 'v') + else if (beresp == PqMsg_NegotiateProtocolVersion) { if (pqGetNegotiateProtocolVersion3(conn)) { @@ -4540,7 +4545,7 @@ sendTerminateConn(PGconn *conn) * Try to send "close connection" message to backend. Ignore any * error. */ - pqPutMsgStart('X', conn); + pqPutMsgStart(PqMsg_Terminate, conn); pqPutMsgEnd(conn); (void) pqFlush(conn); } diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index a868284ff8..fdb7994779 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -1458,7 +1458,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery) /* Send the query message(s) */ /* construct the outgoing Query message */ - if (pqPutMsgStart('Q', conn) < 0 || + if (pqPutMsgStart(PqMsg_Query, conn) < 0 || pqPuts(query, conn) < 0 || pqPutMsgEnd(conn) < 0) { @@ -1571,7 +1571,7 @@ PQsendPrepare(PGconn *conn, return 0; /* error msg already set */ /* construct the Parse message */ - if (pqPutMsgStart('P', conn) < 0 || + if (pqPutMsgStart(PqMsg_Parse, conn) < 0 || pqPuts(stmtName, conn) < 0 || pqPuts(query, conn) < 0) goto sendFailed; @@ -1599,7 +1599,7 @@ PQsendPrepare(PGconn *conn, /* Add a Sync, unless in pipeline mode. */ if (conn->pipelineStatus == PQ_PIPELINE_OFF) { - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) goto sendFailed; } @@ -1784,7 +1784,7 @@ PQsendQueryGuts(PGconn *conn, if (command) { /* construct the Parse message */ - if (pqPutMsgStart('P', conn) < 0 || + if (pqPutMsgStart(PqMsg_Parse, conn) < 0 || pqPuts(stmtName, conn) < 0 || pqPuts(command, conn) < 0) goto sendFailed; @@ -1808,7 +1808,7 @@ PQsendQueryGuts(PGconn *conn, } /* Construct the Bind message */ - if (pqPutMsgStart('B', conn) < 0 || + if (pqPutMsgStart(PqMsg_Bind, conn) < 0 || pqPuts("", conn) < 0 || pqPuts(stmtName, conn) < 0) goto sendFailed; @@ -1874,14 +1874,14 @@ PQsendQueryGuts(PGconn *conn, goto sendFailed; /* construct the Describe Portal message */ - if (pqPutMsgStart('D', conn) < 0 || + if (pqPutMsgStart(PqMsg_Describe, conn) < 0 || pqPutc('P', conn) < 0 || pqPuts("", conn) < 0 || pqPutMsgEnd(conn) < 0) goto sendFailed; /* construct the Execute message */ - if (pqPutMsgStart('E', conn) < 0 || + if (pqPutMsgStart(PqMsg_Execute, conn) < 0 || pqPuts("", conn) < 0 || pqPutInt(0, 4, conn) < 0 || pqPutMsgEnd(conn) < 0) @@ -1890,7 +1890,7 @@ PQsendQueryGuts(PGconn *conn, /* construct the Sync message if not in pipeline mode */ if (conn->pipelineStatus == PQ_PIPELINE_OFF) { - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) goto sendFailed; } @@ -2422,7 +2422,7 @@ PQdescribePrepared(PGconn *conn, const char *stmt) { if (!PQexecStart(conn)) return NULL; - if (!PQsendTypedCommand(conn, 'D', 'S', stmt)) + if (!PQsendTypedCommand(conn, PqMsg_Describe, 'S', stmt)) return NULL; return PQexecFinish(conn); } @@ -2441,7 +2441,7 @@ PQdescribePortal(PGconn *conn, const char *portal) { if (!PQexecStart(conn)) return NULL; - if (!PQsendTypedCommand(conn, 'D', 'P', portal)) + if (!PQsendTypedCommand(conn, PqMsg_Describe, 'P', portal)) return NULL; return PQexecFinish(conn); } @@ -2456,7 +2456,7 @@ PQdescribePortal(PGconn *conn, const char *portal) int PQsendDescribePrepared(PGconn *conn, const char *stmt) { - return PQsendTypedCommand(conn, 'D', 'S', stmt); + return PQsendTypedCommand(conn, PqMsg_Describe, 'S', stmt); } /* @@ -2469,7 +2469,7 @@ PQsendDescribePrepared(PGconn *conn, const char *stmt) int PQsendDescribePortal(PGconn *conn, const char *portal) { - return PQsendTypedCommand(conn, 'D', 'P', portal); + return PQsendTypedCommand(conn, PqMsg_Describe, 'P', portal); } /* @@ -2488,7 +2488,7 @@ PQclosePrepared(PGconn *conn, const char *stmt) { if (!PQexecStart(conn)) return NULL; - if (!PQsendTypedCommand(conn, 'C', 'S', stmt)) + if (!PQsendTypedCommand(conn, PqMsg_Close, 'S', stmt)) return NULL; return PQexecFinish(conn); } @@ -2506,7 +2506,7 @@ PQclosePortal(PGconn *conn, const char *portal) { if (!PQexecStart(conn)) return NULL; - if (!PQsendTypedCommand(conn, 'C', 'P', portal)) + if (!PQsendTypedCommand(conn, PqMsg_Close, 'P', portal)) return NULL; return PQexecFinish(conn); } @@ -2521,7 +2521,7 @@ PQclosePortal(PGconn *conn, const char *portal) int PQsendClosePrepared(PGconn *conn, const char *stmt) { - return PQsendTypedCommand(conn, 'C', 'S', stmt); + return PQsendTypedCommand(conn, PqMsg_Close, 'S', stmt); } /* @@ -2534,7 +2534,7 @@ PQsendClosePrepared(PGconn *conn, const char *stmt) int PQsendClosePortal(PGconn *conn, const char *portal) { - return PQsendTypedCommand(conn, 'C', 'P', portal); + return PQsendTypedCommand(conn, PqMsg_Close, 'P', portal); } /* @@ -2577,17 +2577,17 @@ PQsendTypedCommand(PGconn *conn, char command, char type, const char *target) /* construct the Sync message */ if (conn->pipelineStatus == PQ_PIPELINE_OFF) { - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) goto sendFailed; } /* remember if we are doing a Close or a Describe */ - if (command == 'C') + if (command == PqMsg_Close) { entry->queryclass = PGQUERY_CLOSE; } - else if (command == 'D') + else if (command == PqMsg_Describe) { entry->queryclass = PGQUERY_DESCRIBE; } @@ -2696,7 +2696,7 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes) return pqIsnonblocking(conn) ? 0 : -1; } /* Send the data (too simple to delegate to fe-protocol files) */ - if (pqPutMsgStart('d', conn) < 0 || + if (pqPutMsgStart(PqMsg_CopyData, conn) < 0 || pqPutnchar(buffer, nbytes, conn) < 0 || pqPutMsgEnd(conn) < 0) return -1; @@ -2731,7 +2731,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg) if (errormsg) { /* Send COPY FAIL */ - if (pqPutMsgStart('f', conn) < 0 || + if (pqPutMsgStart(PqMsg_CopyFail, conn) < 0 || pqPuts(errormsg, conn) < 0 || pqPutMsgEnd(conn) < 0) return -1; @@ -2739,7 +2739,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg) else { /* Send COPY DONE */ - if (pqPutMsgStart('c', conn) < 0 || + if (pqPutMsgStart(PqMsg_CopyDone, conn) < 0 || pqPutMsgEnd(conn) < 0) return -1; } @@ -2751,7 +2751,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg) if (conn->cmd_queue_head && conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE) { - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) return -1; } @@ -3263,7 +3263,7 @@ PQpipelineSync(PGconn *conn) entry->query = NULL; /* construct the Sync message */ - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) goto sendFailed; @@ -3311,7 +3311,7 @@ PQsendFlushRequest(PGconn *conn) return 0; } - if (pqPutMsgStart('H', conn) < 0 || + if (pqPutMsgStart(PqMsg_Flush, conn) < 0 || pqPutMsgEnd(conn) < 0) { return 0; diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 7bc6355d17..5613c56b14 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -34,8 +34,13 @@ * than a couple of kilobytes). */ #define VALID_LONG_MESSAGE_TYPE(id) \ - ((id) == 'T' || (id) == 'D' || (id) == 'd' || (id) == 'V' || \ - (id) == 'E' || (id) == 'N' || (id) == 'A') + ((id) == PqMsg_CopyData || \ + (id) == PqMsg_DataRow || \ + (id) == PqMsg_ErrorResponse || \ + (id) == PqMsg_FunctionCallResponse || \ + (id) == PqMsg_NoticeResponse || \ + (id) == PqMsg_NotificationResponse || \ + (id) == PqMsg_RowDescription) static void handleSyncLoss(PGconn *conn, char id, int msgLength); @@ -140,12 +145,12 @@ pqParseInput3(PGconn *conn) * from config file due to SIGHUP), but otherwise we hold off until * BUSY state. */ - if (id == 'A') + if (id == PqMsg_NotificationResponse) { if (getNotify(conn)) return; } - else if (id == 'N') + else if (id == PqMsg_NoticeResponse) { if (pqGetErrorNotice3(conn, false)) return; @@ -165,12 +170,12 @@ pqParseInput3(PGconn *conn) * it is about to close the connection, so we don't want to just * discard it...) */ - if (id == 'E') + if (id == PqMsg_ErrorResponse) { if (pqGetErrorNotice3(conn, false /* treat as notice */ )) return; } - else if (id == 'S') + else if (id == PqMsg_ParameterStatus) { if (getParameterStatus(conn)) return; @@ -192,7 +197,7 @@ pqParseInput3(PGconn *conn) */ switch (id) { - case 'C': /* command complete */ + case PqMsg_CommandComplete: if (pqGets(&conn->workBuffer, conn)) return; if (!pgHavePendingResult(conn)) @@ -210,13 +215,12 @@ pqParseInput3(PGconn *conn) CMDSTATUS_LEN); conn->asyncStatus = PGASYNC_READY; break; - case 'E': /* error return */ + case PqMsg_ErrorResponse: if (pqGetErrorNotice3(conn, true)) return; conn->asyncStatus = PGASYNC_READY; break; - case 'Z': /* sync response, backend is ready for new - * query */ + case PqMsg_ReadyForQuery: if (getReadyForQuery(conn)) return; if (conn->pipelineStatus != PQ_PIPELINE_OFF) @@ -246,7 +250,7 @@ pqParseInput3(PGconn *conn) conn->asyncStatus = PGASYNC_IDLE; } break; - case 'I': /* empty query */ + case PqMsg_EmptyQueryResponse: if (!pgHavePendingResult(conn)) { conn->result = PQmakeEmptyPGresult(conn, @@ -259,7 +263,7 @@ pqParseInput3(PGconn *conn) } conn->asyncStatus = PGASYNC_READY; break; - case '1': /* Parse Complete */ + case PqMsg_ParseComplete: /* If we're doing PQprepare, we're done; else ignore */ if (conn->cmd_queue_head && conn->cmd_queue_head->queryclass == PGQUERY_PREPARE) @@ -277,10 +281,10 @@ pqParseInput3(PGconn *conn) conn->asyncStatus = PGASYNC_READY; } break; - case '2': /* Bind Complete */ + case PqMsg_BindComplete: /* Nothing to do for this message type */ break; - case '3': /* Close Complete */ + case PqMsg_CloseComplete: /* If we're doing PQsendClose, we're done; else ignore */ if (conn->cmd_queue_head && conn->cmd_queue_head->queryclass == PGQUERY_CLOSE) @@ -298,11 +302,11 @@ pqParseInput3(PGconn *conn) conn->asyncStatus = PGASYNC_READY; } break; - case 'S': /* parameter status */ + case PqMsg_ParameterStatus: if (getParameterStatus(conn)) return; break; - case 'K': /* secret key data from the backend */ + case PqMsg_BackendKeyData: /* * This is expected only during backend startup, but it's @@ -314,7 +318,7 @@ pqParseInput3(PGconn *conn) if (pqGetInt(&(conn->be_key), 4, conn)) return; break; - case 'T': /* Row Description */ + case PqMsg_RowDescription: if (conn->error_result || (conn->result != NULL && conn->result->resultStatus == PGRES_FATAL_ERROR)) @@ -346,7 +350,7 @@ pqParseInput3(PGconn *conn) return; } break; - case 'n': /* No Data */ + case PqMsg_NoData: /* * NoData indicates that we will not be seeing a @@ -374,11 +378,11 @@ pqParseInput3(PGconn *conn) conn->asyncStatus = PGASYNC_READY; } break; - case 't': /* Parameter Description */ + case PqMsg_ParameterDescription: if (getParamDescriptions(conn, msgLength)) return; break; - case 'D': /* Data Row */ + case PqMsg_DataRow: if (conn->result != NULL && conn->result->resultStatus == PGRES_TUPLES_OK) { @@ -405,24 +409,24 @@ pqParseInput3(PGconn *conn) conn->inCursor += msgLength; } break; - case 'G': /* Start Copy In */ + case PqMsg_CopyInResponse: if (getCopyStart(conn, PGRES_COPY_IN)) return; conn->asyncStatus = PGASYNC_COPY_IN; break; - case 'H': /* Start Copy Out */ + case PqMsg_CopyOutResponse: if (getCopyStart(conn, PGRES_COPY_OUT)) return; conn->asyncStatus = PGASYNC_COPY_OUT; conn->copy_already_done = 0; break; - case 'W': /* Start Copy Both */ + case PqMsg_CopyBothResponse: if (getCopyStart(conn, PGRES_COPY_BOTH)) return; conn->asyncStatus = PGASYNC_COPY_BOTH; conn->copy_already_done = 0; break; - case 'd': /* Copy Data */ + case PqMsg_CopyData: /* * If we see Copy Data, just silently drop it. This would @@ -431,7 +435,7 @@ pqParseInput3(PGconn *conn) */ conn->inCursor += msgLength; break; - case 'c': /* Copy Done */ + case PqMsg_CopyDone: /* * If we see Copy Done, just silently drop it. This is @@ -1692,21 +1696,21 @@ getCopyDataMessage(PGconn *conn) */ switch (id) { - case 'A': /* NOTIFY */ + case PqMsg_NotificationResponse: if (getNotify(conn)) return 0; break; - case 'N': /* NOTICE */ + case PqMsg_NoticeResponse: if (pqGetErrorNotice3(conn, false)) return 0; break; - case 'S': /* ParameterStatus */ + case PqMsg_ParameterStatus: if (getParameterStatus(conn)) return 0; break; - case 'd': /* Copy Data, pass it back to caller */ + case PqMsg_CopyData: return msgLength; - case 'c': + case PqMsg_CopyDone: /* * If this is a CopyDone message, exit COPY_OUT mode and let @@ -1929,7 +1933,7 @@ pqEndcopy3(PGconn *conn) if (conn->asyncStatus == PGASYNC_COPY_IN || conn->asyncStatus == PGASYNC_COPY_BOTH) { - if (pqPutMsgStart('c', conn) < 0 || + if (pqPutMsgStart(PqMsg_CopyDone, conn) < 0 || pqPutMsgEnd(conn) < 0) return 1; @@ -1940,7 +1944,7 @@ pqEndcopy3(PGconn *conn) if (conn->cmd_queue_head && conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE) { - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) return 1; } @@ -2023,7 +2027,7 @@ pqFunctionCall3(PGconn *conn, Oid fnid, /* PQfn already validated connection state */ - if (pqPutMsgStart('F', conn) < 0 || /* function call msg */ + if (pqPutMsgStart(PqMsg_FunctionCall, conn) < 0 || pqPutInt(fnid, 4, conn) < 0 || /* function id */ pqPutInt(1, 2, conn) < 0 || /* # of format codes */ pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */ diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c index 402784f40e..b18e3deab6 100644 --- a/src/interfaces/libpq/fe-trace.c +++ b/src/interfaces/libpq/fe-trace.c @@ -562,110 +562,120 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer) switch (id) { - case '1': + case PqMsg_ParseComplete: fprintf(conn->Pfdebug, "ParseComplete"); /* No message content */ break; - case '2': + case PqMsg_BindComplete: fprintf(conn->Pfdebug, "BindComplete"); /* No message content */ break; - case '3': + case PqMsg_CloseComplete: fprintf(conn->Pfdebug, "CloseComplete"); /* No message content */ break; - case 'A': /* Notification Response */ + case PqMsg_NotificationResponse: pqTraceOutputA(conn->Pfdebug, message, &logCursor, regress); break; - case 'B': /* Bind */ + case PqMsg_Bind: pqTraceOutputB(conn->Pfdebug, message, &logCursor); break; - case 'c': + case PqMsg_CopyDone: fprintf(conn->Pfdebug, "CopyDone"); /* No message content */ break; - case 'C': /* Close(F) or Command Complete(B) */ + case PqMsg_CommandComplete: + /* Close(F) and CommandComplete(B) use the same identifier. */ + Assert(PqMsg_Close == PqMsg_CommandComplete); pqTraceOutputC(conn->Pfdebug, toServer, message, &logCursor); break; - case 'd': /* Copy Data */ + case PqMsg_CopyData: /* Drop COPY data to reduce the overhead of logging. */ break; - case 'D': /* Describe(F) or Data Row(B) */ + case PqMsg_Describe: + /* Describe(F) and DataRow(B) use the same identifier. */ + Assert(PqMsg_Describe == PqMsg_DataRow); pqTraceOutputD(conn->Pfdebug, toServer, message, &logCursor); break; - case 'E': /* Execute(F) or Error Response(B) */ + case PqMsg_Execute: + /* Execute(F) and ErrorResponse(B) use the same identifier. */ + Assert(PqMsg_Execute == PqMsg_ErrorResponse); pqTraceOutputE(conn->Pfdebug, toServer, message, &logCursor, regress); break; - case 'f': /* Copy Fail */ + case PqMsg_CopyFail: pqTraceOutputf(conn->Pfdebug, message, &logCursor); break; - case 'F': /* Function Call */ + case PqMsg_FunctionCall: pqTraceOutputF(conn->Pfdebug, message, &logCursor, regress); break; - case 'G': /* Start Copy In */ + case PqMsg_CopyInResponse: pqTraceOutputG(conn->Pfdebug, message, &logCursor); break; - case 'H': /* Flush(F) or Start Copy Out(B) */ + case PqMsg_Flush: + /* Flush(F) and CopyOutResponse(B) use the same identifier */ + Assert(PqMsg_CopyOutResponse == PqMsg_Flush); if (!toServer) pqTraceOutputH(conn->Pfdebug, message, &logCursor); else fprintf(conn->Pfdebug, "Flush"); /* no message content */ break; - case 'I': + case PqMsg_EmptyQueryResponse: fprintf(conn->Pfdebug, "EmptyQueryResponse"); /* No message content */ break; - case 'K': /* secret key data from the backend */ + case PqMsg_BackendKeyData: pqTraceOutputK(conn->Pfdebug, message, &logCursor, regress); break; - case 'n': + case PqMsg_NoData: fprintf(conn->Pfdebug, "NoData"); /* No message content */ break; - case 'N': + case PqMsg_NoticeResponse: pqTraceOutputNR(conn->Pfdebug, "NoticeResponse", message, &logCursor, regress); break; - case 'P': /* Parse */ + case PqMsg_Parse: pqTraceOutputP(conn->Pfdebug, message, &logCursor, regress); break; - case 'Q': /* Query */ + case PqMsg_Query: pqTraceOutputQ(conn->Pfdebug, message, &logCursor); break; - case 'R': /* Authentication */ + case PqMsg_AuthenticationRequest: pqTraceOutputR(conn->Pfdebug, message, &logCursor); break; - case 's': + case PqMsg_PortalSuspended: fprintf(conn->Pfdebug, "PortalSuspended"); /* No message content */ break; - case 'S': /* Parameter Status(B) or Sync(F) */ + case PqMsg_Sync: + /* Parameter Status(B) and Sync(F) use the same identifier */ + Assert(PqMsg_ParameterStatus == PqMsg_Sync); if (!toServer) pqTraceOutputS(conn->Pfdebug, message, &logCursor); else fprintf(conn->Pfdebug, "Sync"); /* no message content */ break; - case 't': /* Parameter Description */ + case PqMsg_ParameterDescription: pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress); break; - case 'T': /* Row Description */ + case PqMsg_RowDescription: pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress); break; - case 'v': /* Negotiate Protocol Version */ + case PqMsg_NegotiateProtocolVersion: pqTraceOutputv(conn->Pfdebug, message, &logCursor); break; - case 'V': /* Function Call response */ + case PqMsg_FunctionCallResponse: pqTraceOutputV(conn->Pfdebug, message, &logCursor); break; - case 'W': /* Start Copy Both */ + case PqMsg_CopyBothResponse: pqTraceOutputW(conn->Pfdebug, message, &logCursor, length); break; - case 'X': + case PqMsg_Terminate: fprintf(conn->Pfdebug, "Terminate"); /* No message content */ break; - case 'Z': /* Ready For Query */ + case PqMsg_ReadyForQuery: pqTraceOutputZ(conn->Pfdebug, message, &logCursor); break; default: -- 2.25.1 ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: Using defines for protocol characters @ 2023-08-17 00:31 Michael Paquier <[email protected]> parent: Nathan Bossart <[email protected]> 0 siblings, 1 reply; 8+ messages in thread From: Michael Paquier @ 2023-08-17 00:31 UTC (permalink / raw) To: Nathan Bossart <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Tatsuo Ishii <[email protected]>; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; [email protected] On Wed, Aug 16, 2023 at 12:29:56PM -0700, Nathan Bossart wrote: > I moved the definitions out to a separate file in v6. Looks sensible seen from here. This patch is missing the installation of protocol.h in src/tools/msvc/Install.pm for MSVC. For pqcomm.h, we are doing that: lcopy('src/include/libpq/pqcomm.h', $target . '/include/internal/libpq/') || croak 'Could not copy pqcomm.h'; So adding two similar lines for protocol.h should be enough (I assume, did not test). In fe-exec.c, we still have a few things for the type of objects to work on: - 'S' for statement. - 'P' for portal. Should these be added to protocol.h? They are part of the extended protocol. The comment at the top of PQsendTypedCommand() mentions 'C' and 'D', but perhaps these should be updated to the object names instead? pqFunctionCall3(), for PQfn(), has a few more hardcoded characters for its status codes. I'm OK to do things incrementally so it's fine by me to not add them now, just noticing on the way what could be added to this new header. -- Michael Attachments: [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc) download ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: Using defines for protocol characters @ 2023-08-17 16:13 Nathan Bossart <[email protected]> parent: Michael Paquier <[email protected]> 0 siblings, 1 reply; 8+ messages in thread From: Nathan Bossart @ 2023-08-17 16:13 UTC (permalink / raw) To: Michael Paquier <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Tatsuo Ishii <[email protected]>; [email protected]; [email protected]; [email protected]; [email protected]; [email protected]; [email protected] On Thu, Aug 17, 2023 at 09:31:55AM +0900, Michael Paquier wrote: > Looks sensible seen from here. Thanks for taking a look. > This patch is missing the installation of protocol.h in > src/tools/msvc/Install.pm for MSVC. For pqcomm.h, we are doing that: > lcopy('src/include/libpq/pqcomm.h', $target . '/include/internal/libpq/') > || croak 'Could not copy pqcomm.h'; > > So adding two similar lines for protocol.h should be enough (I assume, > did not test). I added those lines in v7. > In fe-exec.c, we still have a few things for the type of objects to > work on: > - 'S' for statement. > - 'P' for portal. > Should these be added to protocol.h? They are part of the extended > protocol. IMHO they should be added, but I've intentionally restricted this first patch to only codes with existing names in protocol.sgml. I figured we could work on naming other things in a follow-up discussion. > The comment at the top of PQsendTypedCommand() mentions 'C' and 'D', > but perhaps these should be updated to the object names instead? Done. > pqFunctionCall3(), for PQfn(), has a few more hardcoded characters for > its status codes. I'm OK to do things incrementally so it's fine by > me to not add them now, just noticing on the way what could be added > to this new header. Cool, thanks. -- Nathan Bossart Amazon Web Services: https://aws.amazon.com Attachments: [text/x-diff] v7-0001-Introduce-macros-for-protocol-characters.patch (53.1K, ../../20230817161334.GA3153559@nathanxps13/2-v7-0001-Introduce-macros-for-protocol-characters.patch) download | inline diff: From 69f94689857a469333fecbfd2057868140796516 Mon Sep 17 00:00:00 2001 From: Nathan Bossart <[email protected]> Date: Thu, 17 Aug 2023 09:00:46 -0700 Subject: [PATCH v7 1/1] Introduce macros for protocol characters. Author: Dave Cramer Reviewed-by: Alvaro Herrera, Tatsuo Ishii, Peter Smith, Robert Haas, Tom Lane, Peter Eisentraut, Michael Paquier Discussion: https://postgr.es/m/CADK3HHKbBmK-PKf1bPNFoMC%2BoBt%2BpD9PH8h5nvmBQskEHm-Ehw%40mail.gmail.com --- src/backend/access/common/printsimple.c | 5 +- src/backend/access/transam/parallel.c | 14 ++-- src/backend/backup/basebackup_copy.c | 16 ++--- src/backend/commands/async.c | 2 +- src/backend/commands/copyfromparse.c | 22 +++---- src/backend/commands/copyto.c | 6 +- src/backend/libpq/auth-sasl.c | 2 +- src/backend/libpq/auth.c | 8 +-- src/backend/postmaster/postmaster.c | 2 +- src/backend/replication/walsender.c | 18 +++--- src/backend/tcop/dest.c | 8 +-- src/backend/tcop/fastpath.c | 2 +- src/backend/tcop/postgres.c | 68 ++++++++++---------- src/backend/utils/error/elog.c | 5 +- src/backend/utils/misc/guc.c | 2 +- src/include/Makefile | 3 +- src/include/libpq/pqcomm.h | 23 ++----- src/include/libpq/protocol.h | 85 +++++++++++++++++++++++++ src/include/meson.build | 1 + src/interfaces/libpq/fe-auth.c | 2 +- src/interfaces/libpq/fe-connect.c | 19 ++++-- src/interfaces/libpq/fe-exec.c | 54 ++++++++-------- src/interfaces/libpq/fe-protocol3.c | 70 ++++++++++---------- src/interfaces/libpq/fe-trace.c | 70 +++++++++++--------- src/tools/msvc/Install.pm | 2 + 25 files changed, 305 insertions(+), 204 deletions(-) create mode 100644 src/include/libpq/protocol.h diff --git a/src/backend/access/common/printsimple.c b/src/backend/access/common/printsimple.c index ef818228ac..675b744db2 100644 --- a/src/backend/access/common/printsimple.c +++ b/src/backend/access/common/printsimple.c @@ -20,6 +20,7 @@ #include "access/printsimple.h" #include "catalog/pg_type.h" +#include "libpq/protocol.h" #include "libpq/pqformat.h" #include "utils/builtins.h" @@ -32,7 +33,7 @@ printsimple_startup(DestReceiver *self, int operation, TupleDesc tupdesc) StringInfoData buf; int i; - pq_beginmessage(&buf, 'T'); /* RowDescription */ + pq_beginmessage(&buf, PqMsg_RowDescription); pq_sendint16(&buf, tupdesc->natts); for (i = 0; i < tupdesc->natts; ++i) @@ -65,7 +66,7 @@ printsimple(TupleTableSlot *slot, DestReceiver *self) slot_getallattrs(slot); /* Prepare and send message */ - pq_beginmessage(&buf, 'D'); + pq_beginmessage(&buf, PqMsg_DataRow); pq_sendint16(&buf, tupdesc->natts); for (i = 0; i < tupdesc->natts; ++i) diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c index 1738aecf1f..194a1207be 100644 --- a/src/backend/access/transam/parallel.c +++ b/src/backend/access/transam/parallel.c @@ -1127,7 +1127,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg) switch (msgtype) { - case 'K': /* BackendKeyData */ + case PqMsg_BackendKeyData: { int32 pid = pq_getmsgint(msg, 4); @@ -1137,8 +1137,8 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg) break; } - case 'E': /* ErrorResponse */ - case 'N': /* NoticeResponse */ + case PqMsg_ErrorResponse: + case PqMsg_NoticeResponse: { ErrorData edata; ErrorContextCallback *save_error_context_stack; @@ -1183,7 +1183,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg) break; } - case 'A': /* NotifyResponse */ + case PqMsg_NotificationResponse: { /* Propagate NotifyResponse. */ int32 pid; @@ -1217,7 +1217,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg) break; } - case 'X': /* Terminate, indicating clean exit */ + case PqMsg_Terminate: { shm_mq_detach(pcxt->worker[i].error_mqh); pcxt->worker[i].error_mqh = NULL; @@ -1372,7 +1372,7 @@ ParallelWorkerMain(Datum main_arg) * protocol message is defined, but it won't actually be used for anything * in this case. */ - pq_beginmessage(&msgbuf, 'K'); + pq_beginmessage(&msgbuf, PqMsg_BackendKeyData); pq_sendint32(&msgbuf, (int32) MyProcPid); pq_sendint32(&msgbuf, (int32) MyCancelKey); pq_endmessage(&msgbuf); @@ -1550,7 +1550,7 @@ ParallelWorkerMain(Datum main_arg) DetachSession(); /* Report success. */ - pq_putmessage('X', NULL, 0); + pq_putmessage(PqMsg_Terminate, NULL, 0); } /* diff --git a/src/backend/backup/basebackup_copy.c b/src/backend/backup/basebackup_copy.c index 1db80cde1b..fee30c21e1 100644 --- a/src/backend/backup/basebackup_copy.c +++ b/src/backend/backup/basebackup_copy.c @@ -152,7 +152,7 @@ bbsink_copystream_begin_backup(bbsink *sink) SendTablespaceList(state->tablespaces); /* Send a CommandComplete message */ - pq_puttextmessage('C', "SELECT"); + pq_puttextmessage(PqMsg_CommandComplete, "SELECT"); /* Begin COPY stream. This will be used for all archives + manifest. */ SendCopyOutResponse(); @@ -169,7 +169,7 @@ bbsink_copystream_begin_archive(bbsink *sink, const char *archive_name) StringInfoData buf; ti = list_nth(state->tablespaces, state->tablespace_num); - pq_beginmessage(&buf, 'd'); /* CopyData */ + pq_beginmessage(&buf, PqMsg_CopyData); pq_sendbyte(&buf, 'n'); /* New archive */ pq_sendstring(&buf, archive_name); pq_sendstring(&buf, ti->path == NULL ? "" : ti->path); @@ -220,7 +220,7 @@ bbsink_copystream_archive_contents(bbsink *sink, size_t len) { mysink->last_progress_report_time = now; - pq_beginmessage(&buf, 'd'); /* CopyData */ + pq_beginmessage(&buf, PqMsg_CopyData); pq_sendbyte(&buf, 'p'); /* Progress report */ pq_sendint64(&buf, state->bytes_done); pq_endmessage(&buf); @@ -246,7 +246,7 @@ bbsink_copystream_end_archive(bbsink *sink) mysink->bytes_done_at_last_time_check = state->bytes_done; mysink->last_progress_report_time = GetCurrentTimestamp(); - pq_beginmessage(&buf, 'd'); /* CopyData */ + pq_beginmessage(&buf, PqMsg_CopyData); pq_sendbyte(&buf, 'p'); /* Progress report */ pq_sendint64(&buf, state->bytes_done); pq_endmessage(&buf); @@ -261,7 +261,7 @@ bbsink_copystream_begin_manifest(bbsink *sink) { StringInfoData buf; - pq_beginmessage(&buf, 'd'); /* CopyData */ + pq_beginmessage(&buf, PqMsg_CopyData); pq_sendbyte(&buf, 'm'); /* Manifest */ pq_endmessage(&buf); } @@ -318,7 +318,7 @@ SendCopyOutResponse(void) { StringInfoData buf; - pq_beginmessage(&buf, 'H'); + pq_beginmessage(&buf, PqMsg_CopyOutResponse); pq_sendbyte(&buf, 0); /* overall format */ pq_sendint16(&buf, 0); /* natts */ pq_endmessage(&buf); @@ -330,7 +330,7 @@ SendCopyOutResponse(void) static void SendCopyDone(void) { - pq_putemptymessage('c'); + pq_putemptymessage(PqMsg_CopyDone); } /* @@ -368,7 +368,7 @@ SendXlogRecPtrResult(XLogRecPtr ptr, TimeLineID tli) end_tup_output(tstate); /* Send a CommandComplete message */ - pq_puttextmessage('C', "SELECT"); + pq_puttextmessage(PqMsg_CommandComplete, "SELECT"); } /* diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c index ef909cf4e0..d148d10850 100644 --- a/src/backend/commands/async.c +++ b/src/backend/commands/async.c @@ -2281,7 +2281,7 @@ NotifyMyFrontEnd(const char *channel, const char *payload, int32 srcPid) { StringInfoData buf; - pq_beginmessage(&buf, 'A'); + pq_beginmessage(&buf, PqMsg_NotificationResponse); pq_sendint32(&buf, srcPid); pq_sendstring(&buf, channel); pq_sendstring(&buf, payload); diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 232768a6e1..f553734582 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -174,7 +174,7 @@ ReceiveCopyBegin(CopyFromState cstate) int16 format = (cstate->opts.binary ? 1 : 0); int i; - pq_beginmessage(&buf, 'G'); + pq_beginmessage(&buf, PqMsg_CopyInResponse); pq_sendbyte(&buf, format); /* overall format */ pq_sendint16(&buf, natts); for (i = 0; i < natts; i++) @@ -279,13 +279,13 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread) /* Validate message type and set packet size limit */ switch (mtype) { - case 'd': /* CopyData */ + case PqMsg_CopyData: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; break; - case 'c': /* CopyDone */ - case 'f': /* CopyFail */ - case 'H': /* Flush */ - case 'S': /* Sync */ + case PqMsg_CopyDone: + case PqMsg_CopyFail: + case PqMsg_Flush: + case PqMsg_Sync: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; break; default: @@ -305,20 +305,20 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread) /* ... and process it */ switch (mtype) { - case 'd': /* CopyData */ + case PqMsg_CopyData: break; - case 'c': /* CopyDone */ + case PqMsg_CopyDone: /* COPY IN correctly terminated by frontend */ cstate->raw_reached_eof = true; return bytesread; - case 'f': /* CopyFail */ + case PqMsg_CopyFail: ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), errmsg("COPY from stdin failed: %s", pq_getmsgstring(cstate->fe_msgbuf)))); break; - case 'H': /* Flush */ - case 'S': /* Sync */ + case PqMsg_Flush: + case PqMsg_Sync: /* * Ignore Flush/Sync for the convenience of client diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 9e4b2437a5..eaa3172793 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -144,7 +144,7 @@ SendCopyBegin(CopyToState cstate) int16 format = (cstate->opts.binary ? 1 : 0); int i; - pq_beginmessage(&buf, 'H'); + pq_beginmessage(&buf, PqMsg_CopyOutResponse); pq_sendbyte(&buf, format); /* overall format */ pq_sendint16(&buf, natts); for (i = 0; i < natts; i++) @@ -159,7 +159,7 @@ SendCopyEnd(CopyToState cstate) /* Shouldn't have any unsent data */ Assert(cstate->fe_msgbuf->len == 0); /* Send Copy Done message */ - pq_putemptymessage('c'); + pq_putemptymessage(PqMsg_CopyDone); } /*---------- @@ -247,7 +247,7 @@ CopySendEndOfRow(CopyToState cstate) CopySendChar(cstate, '\n'); /* Dump the accumulated row as one CopyData message */ - (void) pq_putmessage('d', fe_msgbuf->data, fe_msgbuf->len); + (void) pq_putmessage(PqMsg_CopyData, fe_msgbuf->data, fe_msgbuf->len); break; case COPY_CALLBACK: cstate->data_dest_cb(fe_msgbuf->data, fe_msgbuf->len); diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c index 684680897b..c535bc5383 100644 --- a/src/backend/libpq/auth-sasl.c +++ b/src/backend/libpq/auth-sasl.c @@ -87,7 +87,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass, { pq_startmsgread(); mtype = pq_getbyte(); - if (mtype != 'p') + if (mtype != PqMsg_SASLResponse) { /* Only log error if client didn't disconnect. */ if (mtype != EOF) diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 315a24bb3f..0356fe3e45 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -665,7 +665,7 @@ sendAuthRequest(Port *port, AuthRequest areq, const char *extradata, int extrale CHECK_FOR_INTERRUPTS(); - pq_beginmessage(&buf, 'R'); + pq_beginmessage(&buf, PqMsg_AuthenticationRequest); pq_sendint32(&buf, (int32) areq); if (extralen > 0) pq_sendbytes(&buf, extradata, extralen); @@ -698,7 +698,7 @@ recv_password_packet(Port *port) /* Expect 'p' message type */ mtype = pq_getbyte(); - if (mtype != 'p') + if (mtype != PqMsg_PasswordMessage) { /* * If the client just disconnects without offering a password, don't @@ -961,7 +961,7 @@ pg_GSS_recvauth(Port *port) CHECK_FOR_INTERRUPTS(); mtype = pq_getbyte(); - if (mtype != 'p') + if (mtype != PqMsg_GSSResponse) { /* Only log error if client didn't disconnect. */ if (mtype != EOF) @@ -1232,7 +1232,7 @@ pg_SSPI_recvauth(Port *port) { pq_startmsgread(); mtype = pq_getbyte(); - if (mtype != 'p') + if (mtype != PqMsg_GSSResponse) { if (sspictx != NULL) { diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 9c8ec779f9..07d376d77e 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -2357,7 +2357,7 @@ SendNegotiateProtocolVersion(List *unrecognized_protocol_options) StringInfoData buf; ListCell *lc; - pq_beginmessage(&buf, 'v'); /* NegotiateProtocolVersion */ + pq_beginmessage(&buf, PqMsg_NegotiateProtocolVersion); pq_sendint32(&buf, PG_PROTOCOL_LATEST); pq_sendint32(&buf, list_length(unrecognized_protocol_options)); foreach(lc, unrecognized_protocol_options) diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index d27ef2985d..80374c55be 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -603,7 +603,7 @@ SendTimeLineHistory(TimeLineHistoryCmd *cmd) dest->rStartup(dest, CMD_SELECT, tupdesc); /* Send a DataRow message */ - pq_beginmessage(&buf, 'D'); + pq_beginmessage(&buf, PqMsg_DataRow); pq_sendint16(&buf, 2); /* # of columns */ len = strlen(histfname); pq_sendint32(&buf, len); /* col1 len */ @@ -801,7 +801,7 @@ StartReplication(StartReplicationCmd *cmd) WalSndSetState(WALSNDSTATE_CATCHUP); /* Send a CopyBothResponse message, and start streaming */ - pq_beginmessage(&buf, 'W'); + pq_beginmessage(&buf, PqMsg_CopyBothResponse); pq_sendbyte(&buf, 0); pq_sendint16(&buf, 0); pq_endmessage(&buf); @@ -1294,7 +1294,7 @@ StartLogicalReplication(StartReplicationCmd *cmd) WalSndSetState(WALSNDSTATE_CATCHUP); /* Send a CopyBothResponse message, and start streaming */ - pq_beginmessage(&buf, 'W'); + pq_beginmessage(&buf, PqMsg_CopyBothResponse); pq_sendbyte(&buf, 0); pq_sendint16(&buf, 0); pq_endmessage(&buf); @@ -1923,11 +1923,11 @@ ProcessRepliesIfAny(void) /* Validate message type and set packet size limit */ switch (firstchar) { - case 'd': + case PqMsg_CopyData: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; break; - case 'c': - case 'X': + case PqMsg_CopyDone: + case PqMsg_Terminate: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; break; default: @@ -1955,7 +1955,7 @@ ProcessRepliesIfAny(void) /* * 'd' means a standby reply wrapped in a CopyData packet. */ - case 'd': + case PqMsg_CopyData: ProcessStandbyMessage(); received = true; break; @@ -1964,7 +1964,7 @@ ProcessRepliesIfAny(void) * CopyDone means the standby requested to finish streaming. * Reply with CopyDone, if we had not sent that already. */ - case 'c': + case PqMsg_CopyDone: if (!streamingDoneSending) { pq_putmessage_noblock('c', NULL, 0); @@ -1978,7 +1978,7 @@ ProcessRepliesIfAny(void) /* * 'X' means that the standby is closing down the socket. */ - case 'X': + case PqMsg_Terminate: proc_exit(0); default: diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index c0406e2ee5..06d1872b9a 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -176,7 +176,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o len = BuildQueryCompletionString(completionTag, qc, force_undecorated_output); - pq_putmessage('C', completionTag, len + 1); + pq_putmessage(PqMsg_Close, completionTag, len + 1); case DestNone: case DestDebug: @@ -200,7 +200,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o void EndReplicationCommand(const char *commandTag) { - pq_putmessage('C', commandTag, strlen(commandTag) + 1); + pq_putmessage(PqMsg_Close, commandTag, strlen(commandTag) + 1); } /* ---------------- @@ -220,7 +220,7 @@ NullCommand(CommandDest dest) case DestRemoteSimple: /* Tell the FE that we saw an empty query string */ - pq_putemptymessage('I'); + pq_putemptymessage(PqMsg_EmptyQueryResponse); break; case DestNone: @@ -258,7 +258,7 @@ ReadyForQuery(CommandDest dest) { StringInfoData buf; - pq_beginmessage(&buf, 'Z'); + pq_beginmessage(&buf, PqMsg_ReadyForQuery); pq_sendbyte(&buf, TransactionBlockStatusCode()); pq_endmessage(&buf); } diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c index 2f70ebd5fa..71f161dbe2 100644 --- a/src/backend/tcop/fastpath.c +++ b/src/backend/tcop/fastpath.c @@ -69,7 +69,7 @@ SendFunctionResult(Datum retval, bool isnull, Oid rettype, int16 format) { StringInfoData buf; - pq_beginmessage(&buf, 'V'); + pq_beginmessage(&buf, PqMsg_FunctionCallResponse); if (isnull) { diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 36cc99ec9c..e4756f8be2 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -402,37 +402,37 @@ SocketBackend(StringInfo inBuf) */ switch (qtype) { - case 'Q': /* simple query */ + case PqMsg_Query: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; doing_extended_query_message = false; break; - case 'F': /* fastpath function call */ + case PqMsg_FunctionCall: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; doing_extended_query_message = false; break; - case 'X': /* terminate */ + case PqMsg_Terminate: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; doing_extended_query_message = false; ignore_till_sync = false; break; - case 'B': /* bind */ - case 'P': /* parse */ + case PqMsg_Bind: + case PqMsg_Parse: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; doing_extended_query_message = true; break; - case 'C': /* close */ - case 'D': /* describe */ - case 'E': /* execute */ - case 'H': /* flush */ + case PqMsg_Close: + case PqMsg_Describe: + case PqMsg_Execute: + case PqMsg_Flush: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; doing_extended_query_message = true; break; - case 'S': /* sync */ + case PqMsg_Sync: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; /* stop any active skip-till-Sync */ ignore_till_sync = false; @@ -440,13 +440,13 @@ SocketBackend(StringInfo inBuf) doing_extended_query_message = false; break; - case 'd': /* copy data */ + case PqMsg_CopyData: maxmsglen = PQ_LARGE_MESSAGE_LIMIT; doing_extended_query_message = false; break; - case 'c': /* copy done */ - case 'f': /* copy fail */ + case PqMsg_CopyDone: + case PqMsg_CopyFail: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; doing_extended_query_message = false; break; @@ -1589,7 +1589,7 @@ exec_parse_message(const char *query_string, /* string to execute */ * Send ParseComplete. */ if (whereToSendOutput == DestRemote) - pq_putemptymessage('1'); + pq_putemptymessage(PqMsg_ParseComplete); /* * Emit duration logging if appropriate. @@ -2047,7 +2047,7 @@ exec_bind_message(StringInfo input_message) * Send BindComplete. */ if (whereToSendOutput == DestRemote) - pq_putemptymessage('2'); + pq_putemptymessage(PqMsg_BindComplete); /* * Emit duration logging if appropriate. @@ -2290,7 +2290,7 @@ exec_execute_message(const char *portal_name, long max_rows) { /* Portal run not complete, so send PortalSuspended */ if (whereToSendOutput == DestRemote) - pq_putemptymessage('s'); + pq_putemptymessage(PqMsg_PortalSuspended); /* * Set XACT_FLAGS_PIPELINING whenever we suspend an Execute message, @@ -2683,7 +2683,7 @@ exec_describe_statement_message(const char *stmt_name) NULL); } else - pq_putemptymessage('n'); /* NoData */ + pq_putemptymessage(PqMsg_NoData); } /* @@ -2736,7 +2736,7 @@ exec_describe_portal_message(const char *portal_name) FetchPortalTargetList(portal), portal->formats); else - pq_putemptymessage('n'); /* NoData */ + pq_putemptymessage(PqMsg_NoData); } @@ -4239,7 +4239,7 @@ PostgresMain(const char *dbname, const char *username) { StringInfoData buf; - pq_beginmessage(&buf, 'K'); + pq_beginmessage(&buf, PqMsg_BackendKeyData); pq_sendint32(&buf, (int32) MyProcPid); pq_sendint32(&buf, (int32) MyCancelKey); pq_endmessage(&buf); @@ -4618,7 +4618,7 @@ PostgresMain(const char *dbname, const char *username) switch (firstchar) { - case 'Q': /* simple query */ + case PqMsg_Query: { const char *query_string; @@ -4642,7 +4642,7 @@ PostgresMain(const char *dbname, const char *username) } break; - case 'P': /* parse */ + case PqMsg_Parse: { const char *stmt_name; const char *query_string; @@ -4672,7 +4672,7 @@ PostgresMain(const char *dbname, const char *username) } break; - case 'B': /* bind */ + case PqMsg_Bind: forbidden_in_wal_sender(firstchar); /* Set statement_timestamp() */ @@ -4687,7 +4687,7 @@ PostgresMain(const char *dbname, const char *username) /* exec_bind_message does valgrind_report_error_query */ break; - case 'E': /* execute */ + case PqMsg_Execute: { const char *portal_name; int max_rows; @@ -4707,7 +4707,7 @@ PostgresMain(const char *dbname, const char *username) } break; - case 'F': /* fastpath function call */ + case PqMsg_FunctionCall: forbidden_in_wal_sender(firstchar); /* Set statement_timestamp() */ @@ -4742,7 +4742,7 @@ PostgresMain(const char *dbname, const char *username) send_ready_for_query = true; break; - case 'C': /* close */ + case PqMsg_Close: { int close_type; const char *close_target; @@ -4782,13 +4782,13 @@ PostgresMain(const char *dbname, const char *username) } if (whereToSendOutput == DestRemote) - pq_putemptymessage('3'); /* CloseComplete */ + pq_putemptymessage(PqMsg_CloseComplete); valgrind_report_error_query("CLOSE message"); } break; - case 'D': /* describe */ + case PqMsg_Describe: { int describe_type; const char *describe_target; @@ -4822,13 +4822,13 @@ PostgresMain(const char *dbname, const char *username) } break; - case 'H': /* flush */ + case PqMsg_Flush: pq_getmsgend(&input_message); if (whereToSendOutput == DestRemote) pq_flush(); break; - case 'S': /* sync */ + case PqMsg_Sync: pq_getmsgend(&input_message); finish_xact_command(); valgrind_report_error_query("SYNC message"); @@ -4847,7 +4847,7 @@ PostgresMain(const char *dbname, const char *username) /* FALLTHROUGH */ - case 'X': + case PqMsg_Terminate: /* * Reset whereToSendOutput to prevent ereport from attempting @@ -4865,9 +4865,9 @@ PostgresMain(const char *dbname, const char *username) */ proc_exit(0); - case 'd': /* copy data */ - case 'c': /* copy done */ - case 'f': /* copy fail */ + case PqMsg_CopyData: + case PqMsg_CopyDone: + case PqMsg_CopyFail: /* * Accept but ignore these messages, per protocol spec; we @@ -4897,7 +4897,7 @@ forbidden_in_wal_sender(char firstchar) { if (am_walsender) { - if (firstchar == 'F') + if (firstchar == PqMsg_FunctionCall) ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("fastpath function calls not supported in a replication connection"))); diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 5898100acb..8e1f3e8521 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -3465,7 +3465,10 @@ send_message_to_frontend(ErrorData *edata) char tbuf[12]; /* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */ - pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E'); + if (edata->elevel < ERROR) + pq_beginmessage(&msgbuf, PqMsg_NoticeResponse); + else + pq_beginmessage(&msgbuf, PqMsg_ErrorResponse); sev = error_severity(edata->elevel); pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 99bb2fdd19..84e7ad4d90 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -2593,7 +2593,7 @@ ReportGUCOption(struct config_generic *record) { StringInfoData msgbuf; - pq_beginmessage(&msgbuf, 'S'); + pq_beginmessage(&msgbuf, PqMsg_ParameterStatus); pq_sendstring(&msgbuf, record->name); pq_sendstring(&msgbuf, val); pq_endmessage(&msgbuf); diff --git a/src/include/Makefile b/src/include/Makefile index 5d213187e2..2d5242561c 100644 --- a/src/include/Makefile +++ b/src/include/Makefile @@ -40,6 +40,7 @@ install: all installdirs $(INSTALL_DATA) $(srcdir)/port.h '$(DESTDIR)$(includedir_internal)' $(INSTALL_DATA) $(srcdir)/postgres_fe.h '$(DESTDIR)$(includedir_internal)' $(INSTALL_DATA) $(srcdir)/libpq/pqcomm.h '$(DESTDIR)$(includedir_internal)/libpq' + $(INSTALL_DATA) $(srcdir)/libpq/protocol.h '$(DESTDIR)$(includedir_internal)/libpq' # These headers are needed for server-side development $(INSTALL_DATA) pg_config.h '$(DESTDIR)$(includedir_server)' $(INSTALL_DATA) pg_config_ext.h '$(DESTDIR)$(includedir_server)' @@ -65,7 +66,7 @@ installdirs: uninstall: rm -f $(addprefix '$(DESTDIR)$(includedir)'/, pg_config.h pg_config_ext.h pg_config_os.h pg_config_manual.h postgres_ext.h libpq/libpq-fs.h) - rm -f $(addprefix '$(DESTDIR)$(includedir_internal)'/, c.h port.h postgres_fe.h libpq/pqcomm.h) + rm -f $(addprefix '$(DESTDIR)$(includedir_internal)'/, c.h port.h postgres_fe.h libpq/pqcomm.h libpq/protocol.h) # heuristic... rm -rf $(addprefix '$(DESTDIR)$(includedir_server)'/, $(SUBDIRS) *.h) diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index 3da00f7983..46a0946b8b 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -21,6 +21,12 @@ #include <netdb.h> #include <netinet/in.h> +/* + * The definitions for the request/response codes are kept in a separate file + * for ease of use in third party programs. + */ +#include "libpq/protocol.h" + typedef struct { struct sockaddr_storage addr; @@ -112,23 +118,6 @@ typedef uint32 PacketLen; #define MAX_STARTUP_PACKET_LENGTH 10000 -/* These are the authentication request codes sent by the backend. */ - -#define AUTH_REQ_OK 0 /* User is authenticated */ -#define AUTH_REQ_KRB4 1 /* Kerberos V4. Not supported any more. */ -#define AUTH_REQ_KRB5 2 /* Kerberos V5. Not supported any more. */ -#define AUTH_REQ_PASSWORD 3 /* Password */ -#define AUTH_REQ_CRYPT 4 /* crypt password. Not supported any more. */ -#define AUTH_REQ_MD5 5 /* md5 password */ -/* 6 is available. It was used for SCM creds, not supported any more. */ -#define AUTH_REQ_GSS 7 /* GSSAPI without wrap() */ -#define AUTH_REQ_GSS_CONT 8 /* Continue GSS exchanges */ -#define AUTH_REQ_SSPI 9 /* SSPI negotiate without wrap() */ -#define AUTH_REQ_SASL 10 /* Begin SASL authentication */ -#define AUTH_REQ_SASL_CONT 11 /* Continue SASL authentication */ -#define AUTH_REQ_SASL_FIN 12 /* Final SASL message */ -#define AUTH_REQ_MAX AUTH_REQ_SASL_FIN /* maximum AUTH_REQ_* value */ - typedef uint32 AuthRequest; diff --git a/src/include/libpq/protocol.h b/src/include/libpq/protocol.h new file mode 100644 index 0000000000..cc46f4b586 --- /dev/null +++ b/src/include/libpq/protocol.h @@ -0,0 +1,85 @@ +/*------------------------------------------------------------------------- + * + * protocol.h + * Definitions of the request/response codes for the wire protocol. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/libpq/protocol.h + * + *------------------------------------------------------------------------- + */ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +/* These are the request codes sent by the frontend. */ + +#define PqMsg_Bind 'B' +#define PqMsg_Close 'C' +#define PqMsg_Describe 'D' +#define PqMsg_Execute 'E' +#define PqMsg_FunctionCall 'F' +#define PqMsg_Flush 'H' +#define PqMsg_Parse 'P' +#define PqMsg_Query 'Q' +#define PqMsg_Sync 'S' +#define PqMsg_Terminate 'X' +#define PqMsg_CopyFail 'f' +#define PqMsg_GSSResponse 'p' +#define PqMsg_PasswordMessage 'p' +#define PqMsg_SASLInitialResponse 'p' +#define PqMsg_SASLResponse 'p' + + +/* These are the response codes sent by the backend. */ + +#define PqMsg_ParseComplete '1' +#define PqMsg_BindComplete '2' +#define PqMsg_CloseComplete '3' +#define PqMsg_NotificationResponse 'A' +#define PqMsg_CommandComplete 'C' +#define PqMsg_DataRow 'D' +#define PqMsg_ErrorResponse 'E' +#define PqMsg_CopyInResponse 'G' +#define PqMsg_CopyOutResponse 'H' +#define PqMsg_EmptyQueryResponse 'I' +#define PqMsg_BackendKeyData 'K' +#define PqMsg_NoticeResponse 'N' +#define PqMsg_AuthenticationRequest 'R' +#define PqMsg_ParameterStatus 'S' +#define PqMsg_RowDescription 'T' +#define PqMsg_FunctionCallResponse 'V' +#define PqMsg_CopyBothResponse 'W' +#define PqMsg_ReadyForQuery 'Z' +#define PqMsg_NoData 'n' +#define PqMsg_PortalSuspended 's' +#define PqMsg_ParameterDescription 't' +#define PqMsg_NegotiateProtocolVersion 'v' + + +/* These are the codes sent by both the frontend and backend. */ + +#define PqMsg_CopyDone 'c' +#define PqMsg_CopyData 'd' + + +/* These are the authentication request codes sent by the backend. */ + +#define AUTH_REQ_OK 0 /* User is authenticated */ +#define AUTH_REQ_KRB4 1 /* Kerberos V4. Not supported any more. */ +#define AUTH_REQ_KRB5 2 /* Kerberos V5. Not supported any more. */ +#define AUTH_REQ_PASSWORD 3 /* Password */ +#define AUTH_REQ_CRYPT 4 /* crypt password. Not supported any more. */ +#define AUTH_REQ_MD5 5 /* md5 password */ +/* 6 is available. It was used for SCM creds, not supported any more. */ +#define AUTH_REQ_GSS 7 /* GSSAPI without wrap() */ +#define AUTH_REQ_GSS_CONT 8 /* Continue GSS exchanges */ +#define AUTH_REQ_SSPI 9 /* SSPI negotiate without wrap() */ +#define AUTH_REQ_SASL 10 /* Begin SASL authentication */ +#define AUTH_REQ_SASL_CONT 11 /* Continue SASL authentication */ +#define AUTH_REQ_SASL_FIN 12 /* Final SASL message */ +#define AUTH_REQ_MAX AUTH_REQ_SASL_FIN /* maximum AUTH_REQ_* value */ + +#endif /* PROTOCOL_H */ diff --git a/src/include/meson.build b/src/include/meson.build index d7e1ecd4c9..d50897c9fd 100644 --- a/src/include/meson.build +++ b/src/include/meson.build @@ -94,6 +94,7 @@ install_headers( install_headers( 'libpq/pqcomm.h', + 'libpq/protocol.h', install_dir: dir_include_internal / 'libpq', ) diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index 887ca5e9e1..912aa14821 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -586,7 +586,7 @@ pg_SASL_init(PGconn *conn, int payloadlen) /* * Build a SASLInitialResponse message, and send it. */ - if (pqPutMsgStart('p', conn)) + if (pqPutMsgStart(PqMsg_SASLInitialResponse, conn)) goto error; if (pqPuts(selected_mechanism, conn)) goto error; diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 837c5321aa..bf83a9b569 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -3591,7 +3591,9 @@ keep_going: /* We will come back to here until there is * Anything else probably means it's not Postgres on the other * end at all. */ - if (!(beresp == 'R' || beresp == 'v' || beresp == 'E')) + if (beresp != PqMsg_AuthenticationRequest && + beresp != PqMsg_ErrorResponse && + beresp != PqMsg_NegotiateProtocolVersion) { libpq_append_conn_error(conn, "expected authentication request from server, but received %c", beresp); @@ -3618,19 +3620,22 @@ keep_going: /* We will come back to here until there is * version 14, the server also used the old protocol for * errors that happened before processing the startup packet.) */ - if (beresp == 'R' && (msgLength < 8 || msgLength > 2000)) + if (beresp == PqMsg_AuthenticationRequest && + (msgLength < 8 || msgLength > 2000)) { libpq_append_conn_error(conn, "received invalid authentication request"); goto error_return; } - if (beresp == 'v' && (msgLength < 8 || msgLength > 2000)) + if (beresp == PqMsg_NegotiateProtocolVersion && + (msgLength < 8 || msgLength > 2000)) { libpq_append_conn_error(conn, "received invalid protocol negotiation message"); goto error_return; } #define MAX_ERRLEN 30000 - if (beresp == 'E' && (msgLength < 8 || msgLength > MAX_ERRLEN)) + if (beresp == PqMsg_ErrorResponse && + (msgLength < 8 || msgLength > MAX_ERRLEN)) { /* Handle error from a pre-3.0 server */ conn->inCursor = conn->inStart + 1; /* reread data */ @@ -3693,7 +3698,7 @@ keep_going: /* We will come back to here until there is } /* Handle errors. */ - if (beresp == 'E') + if (beresp == PqMsg_ErrorResponse) { if (pqGetErrorNotice3(conn, true)) { @@ -3770,7 +3775,7 @@ keep_going: /* We will come back to here until there is goto error_return; } - else if (beresp == 'v') + else if (beresp == PqMsg_NegotiateProtocolVersion) { if (pqGetNegotiateProtocolVersion3(conn)) { @@ -4540,7 +4545,7 @@ sendTerminateConn(PGconn *conn) * Try to send "close connection" message to backend. Ignore any * error. */ - pqPutMsgStart('X', conn); + pqPutMsgStart(PqMsg_Terminate, conn); pqPutMsgEnd(conn); (void) pqFlush(conn); } diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index a868284ff8..974d462d4b 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -1458,7 +1458,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery) /* Send the query message(s) */ /* construct the outgoing Query message */ - if (pqPutMsgStart('Q', conn) < 0 || + if (pqPutMsgStart(PqMsg_Query, conn) < 0 || pqPuts(query, conn) < 0 || pqPutMsgEnd(conn) < 0) { @@ -1571,7 +1571,7 @@ PQsendPrepare(PGconn *conn, return 0; /* error msg already set */ /* construct the Parse message */ - if (pqPutMsgStart('P', conn) < 0 || + if (pqPutMsgStart(PqMsg_Parse, conn) < 0 || pqPuts(stmtName, conn) < 0 || pqPuts(query, conn) < 0) goto sendFailed; @@ -1599,7 +1599,7 @@ PQsendPrepare(PGconn *conn, /* Add a Sync, unless in pipeline mode. */ if (conn->pipelineStatus == PQ_PIPELINE_OFF) { - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) goto sendFailed; } @@ -1784,7 +1784,7 @@ PQsendQueryGuts(PGconn *conn, if (command) { /* construct the Parse message */ - if (pqPutMsgStart('P', conn) < 0 || + if (pqPutMsgStart(PqMsg_Parse, conn) < 0 || pqPuts(stmtName, conn) < 0 || pqPuts(command, conn) < 0) goto sendFailed; @@ -1808,7 +1808,7 @@ PQsendQueryGuts(PGconn *conn, } /* Construct the Bind message */ - if (pqPutMsgStart('B', conn) < 0 || + if (pqPutMsgStart(PqMsg_Bind, conn) < 0 || pqPuts("", conn) < 0 || pqPuts(stmtName, conn) < 0) goto sendFailed; @@ -1874,14 +1874,14 @@ PQsendQueryGuts(PGconn *conn, goto sendFailed; /* construct the Describe Portal message */ - if (pqPutMsgStart('D', conn) < 0 || + if (pqPutMsgStart(PqMsg_Describe, conn) < 0 || pqPutc('P', conn) < 0 || pqPuts("", conn) < 0 || pqPutMsgEnd(conn) < 0) goto sendFailed; /* construct the Execute message */ - if (pqPutMsgStart('E', conn) < 0 || + if (pqPutMsgStart(PqMsg_Execute, conn) < 0 || pqPuts("", conn) < 0 || pqPutInt(0, 4, conn) < 0 || pqPutMsgEnd(conn) < 0) @@ -1890,7 +1890,7 @@ PQsendQueryGuts(PGconn *conn, /* construct the Sync message if not in pipeline mode */ if (conn->pipelineStatus == PQ_PIPELINE_OFF) { - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) goto sendFailed; } @@ -2422,7 +2422,7 @@ PQdescribePrepared(PGconn *conn, const char *stmt) { if (!PQexecStart(conn)) return NULL; - if (!PQsendTypedCommand(conn, 'D', 'S', stmt)) + if (!PQsendTypedCommand(conn, PqMsg_Describe, 'S', stmt)) return NULL; return PQexecFinish(conn); } @@ -2441,7 +2441,7 @@ PQdescribePortal(PGconn *conn, const char *portal) { if (!PQexecStart(conn)) return NULL; - if (!PQsendTypedCommand(conn, 'D', 'P', portal)) + if (!PQsendTypedCommand(conn, PqMsg_Describe, 'P', portal)) return NULL; return PQexecFinish(conn); } @@ -2456,7 +2456,7 @@ PQdescribePortal(PGconn *conn, const char *portal) int PQsendDescribePrepared(PGconn *conn, const char *stmt) { - return PQsendTypedCommand(conn, 'D', 'S', stmt); + return PQsendTypedCommand(conn, PqMsg_Describe, 'S', stmt); } /* @@ -2469,7 +2469,7 @@ PQsendDescribePrepared(PGconn *conn, const char *stmt) int PQsendDescribePortal(PGconn *conn, const char *portal) { - return PQsendTypedCommand(conn, 'D', 'P', portal); + return PQsendTypedCommand(conn, PqMsg_Describe, 'P', portal); } /* @@ -2488,7 +2488,7 @@ PQclosePrepared(PGconn *conn, const char *stmt) { if (!PQexecStart(conn)) return NULL; - if (!PQsendTypedCommand(conn, 'C', 'S', stmt)) + if (!PQsendTypedCommand(conn, PqMsg_Close, 'S', stmt)) return NULL; return PQexecFinish(conn); } @@ -2506,7 +2506,7 @@ PQclosePortal(PGconn *conn, const char *portal) { if (!PQexecStart(conn)) return NULL; - if (!PQsendTypedCommand(conn, 'C', 'P', portal)) + if (!PQsendTypedCommand(conn, PqMsg_Close, 'P', portal)) return NULL; return PQexecFinish(conn); } @@ -2521,7 +2521,7 @@ PQclosePortal(PGconn *conn, const char *portal) int PQsendClosePrepared(PGconn *conn, const char *stmt) { - return PQsendTypedCommand(conn, 'C', 'S', stmt); + return PQsendTypedCommand(conn, PqMsg_Close, 'S', stmt); } /* @@ -2534,7 +2534,7 @@ PQsendClosePrepared(PGconn *conn, const char *stmt) int PQsendClosePortal(PGconn *conn, const char *portal) { - return PQsendTypedCommand(conn, 'C', 'P', portal); + return PQsendTypedCommand(conn, PqMsg_Close, 'P', portal); } /* @@ -2542,8 +2542,8 @@ PQsendClosePortal(PGconn *conn, const char *portal) * Common code to send a Describe or Close command * * Available options for "command" are - * 'C' for Close; or - * 'D' for Describe. + * PqMsg_Close for Close; or + * PqMsg_Describe for Describe. * * Available options for "type" are * 'S' to run a command on a prepared statement; or @@ -2577,17 +2577,17 @@ PQsendTypedCommand(PGconn *conn, char command, char type, const char *target) /* construct the Sync message */ if (conn->pipelineStatus == PQ_PIPELINE_OFF) { - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) goto sendFailed; } /* remember if we are doing a Close or a Describe */ - if (command == 'C') + if (command == PqMsg_Close) { entry->queryclass = PGQUERY_CLOSE; } - else if (command == 'D') + else if (command == PqMsg_Describe) { entry->queryclass = PGQUERY_DESCRIBE; } @@ -2696,7 +2696,7 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes) return pqIsnonblocking(conn) ? 0 : -1; } /* Send the data (too simple to delegate to fe-protocol files) */ - if (pqPutMsgStart('d', conn) < 0 || + if (pqPutMsgStart(PqMsg_CopyData, conn) < 0 || pqPutnchar(buffer, nbytes, conn) < 0 || pqPutMsgEnd(conn) < 0) return -1; @@ -2731,7 +2731,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg) if (errormsg) { /* Send COPY FAIL */ - if (pqPutMsgStart('f', conn) < 0 || + if (pqPutMsgStart(PqMsg_CopyFail, conn) < 0 || pqPuts(errormsg, conn) < 0 || pqPutMsgEnd(conn) < 0) return -1; @@ -2739,7 +2739,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg) else { /* Send COPY DONE */ - if (pqPutMsgStart('c', conn) < 0 || + if (pqPutMsgStart(PqMsg_CopyDone, conn) < 0 || pqPutMsgEnd(conn) < 0) return -1; } @@ -2751,7 +2751,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg) if (conn->cmd_queue_head && conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE) { - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) return -1; } @@ -3263,7 +3263,7 @@ PQpipelineSync(PGconn *conn) entry->query = NULL; /* construct the Sync message */ - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) goto sendFailed; @@ -3311,7 +3311,7 @@ PQsendFlushRequest(PGconn *conn) return 0; } - if (pqPutMsgStart('H', conn) < 0 || + if (pqPutMsgStart(PqMsg_Flush, conn) < 0 || pqPutMsgEnd(conn) < 0) { return 0; diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 7bc6355d17..5613c56b14 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -34,8 +34,13 @@ * than a couple of kilobytes). */ #define VALID_LONG_MESSAGE_TYPE(id) \ - ((id) == 'T' || (id) == 'D' || (id) == 'd' || (id) == 'V' || \ - (id) == 'E' || (id) == 'N' || (id) == 'A') + ((id) == PqMsg_CopyData || \ + (id) == PqMsg_DataRow || \ + (id) == PqMsg_ErrorResponse || \ + (id) == PqMsg_FunctionCallResponse || \ + (id) == PqMsg_NoticeResponse || \ + (id) == PqMsg_NotificationResponse || \ + (id) == PqMsg_RowDescription) static void handleSyncLoss(PGconn *conn, char id, int msgLength); @@ -140,12 +145,12 @@ pqParseInput3(PGconn *conn) * from config file due to SIGHUP), but otherwise we hold off until * BUSY state. */ - if (id == 'A') + if (id == PqMsg_NotificationResponse) { if (getNotify(conn)) return; } - else if (id == 'N') + else if (id == PqMsg_NoticeResponse) { if (pqGetErrorNotice3(conn, false)) return; @@ -165,12 +170,12 @@ pqParseInput3(PGconn *conn) * it is about to close the connection, so we don't want to just * discard it...) */ - if (id == 'E') + if (id == PqMsg_ErrorResponse) { if (pqGetErrorNotice3(conn, false /* treat as notice */ )) return; } - else if (id == 'S') + else if (id == PqMsg_ParameterStatus) { if (getParameterStatus(conn)) return; @@ -192,7 +197,7 @@ pqParseInput3(PGconn *conn) */ switch (id) { - case 'C': /* command complete */ + case PqMsg_CommandComplete: if (pqGets(&conn->workBuffer, conn)) return; if (!pgHavePendingResult(conn)) @@ -210,13 +215,12 @@ pqParseInput3(PGconn *conn) CMDSTATUS_LEN); conn->asyncStatus = PGASYNC_READY; break; - case 'E': /* error return */ + case PqMsg_ErrorResponse: if (pqGetErrorNotice3(conn, true)) return; conn->asyncStatus = PGASYNC_READY; break; - case 'Z': /* sync response, backend is ready for new - * query */ + case PqMsg_ReadyForQuery: if (getReadyForQuery(conn)) return; if (conn->pipelineStatus != PQ_PIPELINE_OFF) @@ -246,7 +250,7 @@ pqParseInput3(PGconn *conn) conn->asyncStatus = PGASYNC_IDLE; } break; - case 'I': /* empty query */ + case PqMsg_EmptyQueryResponse: if (!pgHavePendingResult(conn)) { conn->result = PQmakeEmptyPGresult(conn, @@ -259,7 +263,7 @@ pqParseInput3(PGconn *conn) } conn->asyncStatus = PGASYNC_READY; break; - case '1': /* Parse Complete */ + case PqMsg_ParseComplete: /* If we're doing PQprepare, we're done; else ignore */ if (conn->cmd_queue_head && conn->cmd_queue_head->queryclass == PGQUERY_PREPARE) @@ -277,10 +281,10 @@ pqParseInput3(PGconn *conn) conn->asyncStatus = PGASYNC_READY; } break; - case '2': /* Bind Complete */ + case PqMsg_BindComplete: /* Nothing to do for this message type */ break; - case '3': /* Close Complete */ + case PqMsg_CloseComplete: /* If we're doing PQsendClose, we're done; else ignore */ if (conn->cmd_queue_head && conn->cmd_queue_head->queryclass == PGQUERY_CLOSE) @@ -298,11 +302,11 @@ pqParseInput3(PGconn *conn) conn->asyncStatus = PGASYNC_READY; } break; - case 'S': /* parameter status */ + case PqMsg_ParameterStatus: if (getParameterStatus(conn)) return; break; - case 'K': /* secret key data from the backend */ + case PqMsg_BackendKeyData: /* * This is expected only during backend startup, but it's @@ -314,7 +318,7 @@ pqParseInput3(PGconn *conn) if (pqGetInt(&(conn->be_key), 4, conn)) return; break; - case 'T': /* Row Description */ + case PqMsg_RowDescription: if (conn->error_result || (conn->result != NULL && conn->result->resultStatus == PGRES_FATAL_ERROR)) @@ -346,7 +350,7 @@ pqParseInput3(PGconn *conn) return; } break; - case 'n': /* No Data */ + case PqMsg_NoData: /* * NoData indicates that we will not be seeing a @@ -374,11 +378,11 @@ pqParseInput3(PGconn *conn) conn->asyncStatus = PGASYNC_READY; } break; - case 't': /* Parameter Description */ + case PqMsg_ParameterDescription: if (getParamDescriptions(conn, msgLength)) return; break; - case 'D': /* Data Row */ + case PqMsg_DataRow: if (conn->result != NULL && conn->result->resultStatus == PGRES_TUPLES_OK) { @@ -405,24 +409,24 @@ pqParseInput3(PGconn *conn) conn->inCursor += msgLength; } break; - case 'G': /* Start Copy In */ + case PqMsg_CopyInResponse: if (getCopyStart(conn, PGRES_COPY_IN)) return; conn->asyncStatus = PGASYNC_COPY_IN; break; - case 'H': /* Start Copy Out */ + case PqMsg_CopyOutResponse: if (getCopyStart(conn, PGRES_COPY_OUT)) return; conn->asyncStatus = PGASYNC_COPY_OUT; conn->copy_already_done = 0; break; - case 'W': /* Start Copy Both */ + case PqMsg_CopyBothResponse: if (getCopyStart(conn, PGRES_COPY_BOTH)) return; conn->asyncStatus = PGASYNC_COPY_BOTH; conn->copy_already_done = 0; break; - case 'd': /* Copy Data */ + case PqMsg_CopyData: /* * If we see Copy Data, just silently drop it. This would @@ -431,7 +435,7 @@ pqParseInput3(PGconn *conn) */ conn->inCursor += msgLength; break; - case 'c': /* Copy Done */ + case PqMsg_CopyDone: /* * If we see Copy Done, just silently drop it. This is @@ -1692,21 +1696,21 @@ getCopyDataMessage(PGconn *conn) */ switch (id) { - case 'A': /* NOTIFY */ + case PqMsg_NotificationResponse: if (getNotify(conn)) return 0; break; - case 'N': /* NOTICE */ + case PqMsg_NoticeResponse: if (pqGetErrorNotice3(conn, false)) return 0; break; - case 'S': /* ParameterStatus */ + case PqMsg_ParameterStatus: if (getParameterStatus(conn)) return 0; break; - case 'd': /* Copy Data, pass it back to caller */ + case PqMsg_CopyData: return msgLength; - case 'c': + case PqMsg_CopyDone: /* * If this is a CopyDone message, exit COPY_OUT mode and let @@ -1929,7 +1933,7 @@ pqEndcopy3(PGconn *conn) if (conn->asyncStatus == PGASYNC_COPY_IN || conn->asyncStatus == PGASYNC_COPY_BOTH) { - if (pqPutMsgStart('c', conn) < 0 || + if (pqPutMsgStart(PqMsg_CopyDone, conn) < 0 || pqPutMsgEnd(conn) < 0) return 1; @@ -1940,7 +1944,7 @@ pqEndcopy3(PGconn *conn) if (conn->cmd_queue_head && conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE) { - if (pqPutMsgStart('S', conn) < 0 || + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || pqPutMsgEnd(conn) < 0) return 1; } @@ -2023,7 +2027,7 @@ pqFunctionCall3(PGconn *conn, Oid fnid, /* PQfn already validated connection state */ - if (pqPutMsgStart('F', conn) < 0 || /* function call msg */ + if (pqPutMsgStart(PqMsg_FunctionCall, conn) < 0 || pqPutInt(fnid, 4, conn) < 0 || /* function id */ pqPutInt(1, 2, conn) < 0 || /* # of format codes */ pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */ diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c index 402784f40e..b18e3deab6 100644 --- a/src/interfaces/libpq/fe-trace.c +++ b/src/interfaces/libpq/fe-trace.c @@ -562,110 +562,120 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer) switch (id) { - case '1': + case PqMsg_ParseComplete: fprintf(conn->Pfdebug, "ParseComplete"); /* No message content */ break; - case '2': + case PqMsg_BindComplete: fprintf(conn->Pfdebug, "BindComplete"); /* No message content */ break; - case '3': + case PqMsg_CloseComplete: fprintf(conn->Pfdebug, "CloseComplete"); /* No message content */ break; - case 'A': /* Notification Response */ + case PqMsg_NotificationResponse: pqTraceOutputA(conn->Pfdebug, message, &logCursor, regress); break; - case 'B': /* Bind */ + case PqMsg_Bind: pqTraceOutputB(conn->Pfdebug, message, &logCursor); break; - case 'c': + case PqMsg_CopyDone: fprintf(conn->Pfdebug, "CopyDone"); /* No message content */ break; - case 'C': /* Close(F) or Command Complete(B) */ + case PqMsg_CommandComplete: + /* Close(F) and CommandComplete(B) use the same identifier. */ + Assert(PqMsg_Close == PqMsg_CommandComplete); pqTraceOutputC(conn->Pfdebug, toServer, message, &logCursor); break; - case 'd': /* Copy Data */ + case PqMsg_CopyData: /* Drop COPY data to reduce the overhead of logging. */ break; - case 'D': /* Describe(F) or Data Row(B) */ + case PqMsg_Describe: + /* Describe(F) and DataRow(B) use the same identifier. */ + Assert(PqMsg_Describe == PqMsg_DataRow); pqTraceOutputD(conn->Pfdebug, toServer, message, &logCursor); break; - case 'E': /* Execute(F) or Error Response(B) */ + case PqMsg_Execute: + /* Execute(F) and ErrorResponse(B) use the same identifier. */ + Assert(PqMsg_Execute == PqMsg_ErrorResponse); pqTraceOutputE(conn->Pfdebug, toServer, message, &logCursor, regress); break; - case 'f': /* Copy Fail */ + case PqMsg_CopyFail: pqTraceOutputf(conn->Pfdebug, message, &logCursor); break; - case 'F': /* Function Call */ + case PqMsg_FunctionCall: pqTraceOutputF(conn->Pfdebug, message, &logCursor, regress); break; - case 'G': /* Start Copy In */ + case PqMsg_CopyInResponse: pqTraceOutputG(conn->Pfdebug, message, &logCursor); break; - case 'H': /* Flush(F) or Start Copy Out(B) */ + case PqMsg_Flush: + /* Flush(F) and CopyOutResponse(B) use the same identifier */ + Assert(PqMsg_CopyOutResponse == PqMsg_Flush); if (!toServer) pqTraceOutputH(conn->Pfdebug, message, &logCursor); else fprintf(conn->Pfdebug, "Flush"); /* no message content */ break; - case 'I': + case PqMsg_EmptyQueryResponse: fprintf(conn->Pfdebug, "EmptyQueryResponse"); /* No message content */ break; - case 'K': /* secret key data from the backend */ + case PqMsg_BackendKeyData: pqTraceOutputK(conn->Pfdebug, message, &logCursor, regress); break; - case 'n': + case PqMsg_NoData: fprintf(conn->Pfdebug, "NoData"); /* No message content */ break; - case 'N': + case PqMsg_NoticeResponse: pqTraceOutputNR(conn->Pfdebug, "NoticeResponse", message, &logCursor, regress); break; - case 'P': /* Parse */ + case PqMsg_Parse: pqTraceOutputP(conn->Pfdebug, message, &logCursor, regress); break; - case 'Q': /* Query */ + case PqMsg_Query: pqTraceOutputQ(conn->Pfdebug, message, &logCursor); break; - case 'R': /* Authentication */ + case PqMsg_AuthenticationRequest: pqTraceOutputR(conn->Pfdebug, message, &logCursor); break; - case 's': + case PqMsg_PortalSuspended: fprintf(conn->Pfdebug, "PortalSuspended"); /* No message content */ break; - case 'S': /* Parameter Status(B) or Sync(F) */ + case PqMsg_Sync: + /* Parameter Status(B) and Sync(F) use the same identifier */ + Assert(PqMsg_ParameterStatus == PqMsg_Sync); if (!toServer) pqTraceOutputS(conn->Pfdebug, message, &logCursor); else fprintf(conn->Pfdebug, "Sync"); /* no message content */ break; - case 't': /* Parameter Description */ + case PqMsg_ParameterDescription: pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress); break; - case 'T': /* Row Description */ + case PqMsg_RowDescription: pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress); break; - case 'v': /* Negotiate Protocol Version */ + case PqMsg_NegotiateProtocolVersion: pqTraceOutputv(conn->Pfdebug, message, &logCursor); break; - case 'V': /* Function Call response */ + case PqMsg_FunctionCallResponse: pqTraceOutputV(conn->Pfdebug, message, &logCursor); break; - case 'W': /* Start Copy Both */ + case PqMsg_CopyBothResponse: pqTraceOutputW(conn->Pfdebug, message, &logCursor, length); break; - case 'X': + case PqMsg_Terminate: fprintf(conn->Pfdebug, "Terminate"); /* No message content */ break; - case 'Z': /* Ready For Query */ + case PqMsg_ReadyForQuery: pqTraceOutputZ(conn->Pfdebug, message, &logCursor); break; default: diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm index 05548d7c0a..b6dd2c3bba 100644 --- a/src/tools/msvc/Install.pm +++ b/src/tools/msvc/Install.pm @@ -614,6 +614,8 @@ sub CopyIncludeFiles 'src/include/', 'c.h', 'port.h', 'postgres_fe.h'); lcopy('src/include/libpq/pqcomm.h', $target . '/include/internal/libpq/') || croak 'Could not copy pqcomm.h'; + lcopy('src/include/libpq/protocol.h', $target . '/include/internal/libpq/') + || croak 'Could not copy protocol.h'; CopyFiles( 'Server headers', -- 2.25.1 ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: Using defines for protocol characters @ 2023-08-17 16:22 Robert Haas <[email protected]> parent: Nathan Bossart <[email protected]> 0 siblings, 2 replies; 8+ messages in thread From: Robert Haas @ 2023-08-17 16:22 UTC (permalink / raw) To: Nathan Bossart <[email protected]>; +Cc: Michael Paquier <[email protected]>; Alvaro Herrera <[email protected]>; Tatsuo Ishii <[email protected]>; [email protected]; [email protected]; [email protected]; [email protected]; [email protected] On Thu, Aug 17, 2023 at 12:13 PM Nathan Bossart <[email protected]> wrote: > On Thu, Aug 17, 2023 at 09:31:55AM +0900, Michael Paquier wrote: > > Looks sensible seen from here. > > Thanks for taking a look. I think this is going to be a major improvement in terms of readability! I'm pretty happy with this version, too. We may be running out of things to argue about -- and no, I don't want to argue about underscores more! -- Robert Haas EDB: http://www.enterprisedb.com ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: Using defines for protocol characters @ 2023-08-17 16:34 Dave Cramer <[email protected]> parent: Robert Haas <[email protected]> 1 sibling, 0 replies; 8+ messages in thread From: Dave Cramer @ 2023-08-17 16:34 UTC (permalink / raw) To: Robert Haas <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Tatsuo Ishii <[email protected]>; [email protected]; [email protected]; [email protected]; [email protected] On Thu, Aug 17, 2023 at 9:22 AM Robert Haas <[email protected]> wrote: > On Thu, Aug 17, 2023 at 12:13 PM Nathan Bossart > <[email protected]> wrote: > > On Thu, Aug 17, 2023 at 09:31:55AM +0900, Michael Paquier wrote: > > > Looks sensible seen from here. > > > > Thanks for taking a look. > > I think this is going to be a major improvement in terms of readability! That was the primary goal > Dave > > -- Dave Cramer ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: Using defines for protocol characters @ 2023-08-17 16:52 Nathan Bossart <[email protected]> parent: Robert Haas <[email protected]> 1 sibling, 1 reply; 8+ messages in thread From: Nathan Bossart @ 2023-08-17 16:52 UTC (permalink / raw) To: Robert Haas <[email protected]>; +Cc: Michael Paquier <[email protected]>; Alvaro Herrera <[email protected]>; Tatsuo Ishii <[email protected]>; [email protected]; [email protected]; [email protected]; [email protected]; [email protected] On Thu, Aug 17, 2023 at 12:22:24PM -0400, Robert Haas wrote: > I think this is going to be a major improvement in terms of readability! > > I'm pretty happy with this version, too. We may be running out of > things to argue about -- and no, I don't want to argue about > underscores more! Awesome. If we are indeed out of things to argue about, I'll plan on committing this sometime next week. -- Nathan Bossart Amazon Web Services: https://aws.amazon.com ^ permalink raw reply [nested|flat] 8+ messages in thread
* Re: Using defines for protocol characters @ 2023-08-23 02:24 Nathan Bossart <[email protected]> parent: Nathan Bossart <[email protected]> 0 siblings, 0 replies; 8+ messages in thread From: Nathan Bossart @ 2023-08-23 02:24 UTC (permalink / raw) To: Robert Haas <[email protected]>; +Cc: Michael Paquier <[email protected]>; Alvaro Herrera <[email protected]>; Tatsuo Ishii <[email protected]>; [email protected]; [email protected]; [email protected]; [email protected]; [email protected] On Thu, Aug 17, 2023 at 09:52:03AM -0700, Nathan Bossart wrote: > On Thu, Aug 17, 2023 at 12:22:24PM -0400, Robert Haas wrote: >> I think this is going to be a major improvement in terms of readability! >> >> I'm pretty happy with this version, too. We may be running out of >> things to argue about -- and no, I don't want to argue about >> underscores more! > > Awesome. If we are indeed out of things to argue about, I'll plan on > committing this sometime next week. Committed. -- Nathan Bossart Amazon Web Services: https://aws.amazon.com ^ permalink raw reply [nested|flat] 8+ messages in thread
end of thread, other threads:[~2023-08-23 02:24 UTC | newest] Thread overview: 8+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2021-08-02 05:59 [PATCH v25 08/15] Add aggregates support in IVM Yugo Nagata <[email protected]> 2023-08-16 19:29 Re: Using defines for protocol characters Nathan Bossart <[email protected]> 2023-08-17 00:31 ` Re: Using defines for protocol characters Michael Paquier <[email protected]> 2023-08-17 16:13 ` Re: Using defines for protocol characters Nathan Bossart <[email protected]> 2023-08-17 16:22 ` Re: Using defines for protocol characters Robert Haas <[email protected]> 2023-08-17 16:34 ` Re: Using defines for protocol characters Dave Cramer <[email protected]> 2023-08-17 16:52 ` Re: Using defines for protocol characters Nathan Bossart <[email protected]> 2023-08-23 02:24 ` Re: Using defines for protocol characters Nathan Bossart <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox