From d83fa7a8428ec5e068fcdd06ffe8a8ac47b97401 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Tue, 1 Feb 2022 22:53:56 +0000 Subject: [PATCH 01/46] Print vars --- .gitignore | 4 + plpgsql_debugger.c | 626 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 578 insertions(+), 52 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..44b4a95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.bc +*.so +.vscode \ No newline at end of file diff --git a/plpgsql_debugger.c b/plpgsql_debugger.c index 6812592..a134888 100644 --- a/plpgsql_debugger.c +++ b/plpgsql_debugger.c @@ -25,7 +25,13 @@ #include "utils/array.h" #include "utils/builtins.h" #include "utils/syscache.h" +#include "utils/lsyscache.h" +#include "utils/datum.h" +#include "utils/rel.h" #include "miscadmin.h" +#include "funcapi.h" +#include "parser/parse_type.h" +#include "access/detoast.h" #if INCLUDE_PACKAGE_SUPPORT #include "spl.h" @@ -111,6 +117,15 @@ static bool plpgsql_do_deposit(ErrorContextCallback *frame, const char *var_name static Oid plpgsql_get_func_oid(ErrorContextCallback *frame); static void plpgsql_send_cur_line(ErrorContextCallback *frame); +static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PLpgSQL_var *var); +static void exec_eval_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeid, int32 *typetypmod, Datum *value, bool *isnull); +static char *convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype); +static void revalidate_rectypeid(PLpgSQL_rec *rec); +static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec); +static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc); +static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str); +static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, Datum newvalue, bool isnull, bool freeable); + #if INCLUDE_PACKAGE_SUPPORT debugger_language_t spl_debugger_lang = #else @@ -298,72 +313,36 @@ varIsArgument(const PLpgSQL_execstate *estate, PLpgSQL_function *func, static void plpgsql_send_vars(ErrorContextCallback *frame) { + + PLpgSQL_datum *datum; + PLpgSQL_variable *variable; + + Datum value; + Oid typeid; + int32 typetypmod; + bool isNull; + bool isArg; + int i; + PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; dbg_ctx * dbg_info = (dbg_ctx *) estate->plugin_info; - int i; for( i = 0; i < estate->ndatums; i++ ) { if( is_var_visible( estate, i )) { + datum = (PLpgSQL_datum*) estate->datums[i]; + variable = (PLpgSQL_variable*) estate->datums[i]; + isArg = dbg_info->func->fn_nargs > 0 && i < dbg_info->func->fn_nargs; switch( estate->datums[i]->dtype ) { #if (PG_VERSION_NUM >= 110000) case PLPGSQL_DTYPE_PROMISE: #endif case PLPGSQL_DTYPE_VAR: - { - PLpgSQL_var * var = (PLpgSQL_var *) estate->datums[i]; - char * val; - char * name = var->refname; - bool isArg; - - isArg = varIsArgument(estate, dbg_info->func, i, &name); - - if( datumIsNull((PLpgSQL_datum *)var )) - val = "NULL"; - else - val = get_text_val( var, NULL, NULL ); - - dbg_send( "%s:%c:%d:%c:%c:%c:%d:%s", - name, - isArg ? 'A' : 'L', - var->lineno, - dbg_info->symbols[i].duplicate_name ? 'f' : 't', - var->isconst ? 't':'f', - var->notnull ? 't':'f', - var->datatype ? var->datatype->typoid : InvalidOid, - val ); - - break; - } #if 0 - FIXME: implement other types - - case PLPGSQL_DTYPE_REC: - { - PLpgSQL_rec * rec = (PLpgSQL_rec *) estate->datums[i]; - int att; - char * typeName; - - if (rec->tupdesc != NULL) - { - for( att = 0; att < rec->tupdesc->natts; ++att ) - { - typeName = SPI_gettype( rec->tupdesc, att + 1 ); - - dbg_send( "o:%s.%s:%d:%d:%d:%d:%s\n", - rec->refname, NameStr( rec->tupdesc->attrs[att]->attname ), - 0, rec->lineno, 0, rec->tupdesc->attrs[att]->attnotnull, typeName ? typeName : "" ); - - if( typeName ) - pfree( typeName ); - } - } - break; - } -#endif case PLPGSQL_DTYPE_ROW: +#endif case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_RECFIELD: #if (PG_VERSION_NUM < 140000) @@ -373,7 +352,17 @@ plpgsql_send_vars(ErrorContextCallback *frame) case PLPGSQL_DTYPE_EXPR: #endif { - /* FIXME: implement other types */ + exec_eval_datum(estate, datum, &typeid, &typetypmod, &value, &isNull); + + dbg_send("%s:%c:%d:%c:%c:%c:%d:%s", + variable->refname, + isArg ? 'A' : 'L', + variable->lineno, + dbg_info->symbols[i].duplicate_name ? 'f' : 't', + variable->isconst ? 't':'f', + variable->notnull ? 't':'f', + typeid, + isNull? "NULL" : convert_value_to_string(estate, value, typeid)); break; } } @@ -1531,3 +1520,536 @@ datumIsNull(PLpgSQL_datum *datum) return false; } + +void exec_eval_datum(PLpgSQL_execstate *estate, + PLpgSQL_datum *datum, + Oid *typeid, + int32 *typetypmod, + Datum *value, + bool *isnull) +{ + MemoryContext oldcontext; + + switch (datum->dtype) + { + case PLPGSQL_DTYPE_PROMISE: + /* fulfill promise if needed, then handle like regular var */ + plpgsql_fulfill_promise(estate, (PLpgSQL_var *) datum); + + /* FALL THRU */ + + case PLPGSQL_DTYPE_VAR: + { + PLpgSQL_var *var = (PLpgSQL_var *) datum; + + *typeid = var->datatype->typoid; + *typetypmod = var->datatype->atttypmod; + *value = var->value; + *isnull = var->isnull; + break; + } + + case PLPGSQL_DTYPE_ROW: + { + PLpgSQL_row *row = (PLpgSQL_row *) datum; + HeapTuple tup; + + /* We get here if there are multiple OUT parameters */ + if (!row->rowtupdesc) /* should not happen */ + elog(ERROR, "row variable has no tupdesc"); + /* Make sure we have a valid type/typmod setting */ + BlessTupleDesc(row->rowtupdesc); + + + oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); + tup = make_tuple_from_row(estate, row, row->rowtupdesc); + if (tup == NULL) /* should not happen */ + elog(ERROR, "row not compatible with its own tupdesc"); + *typeid = row->rowtupdesc->tdtypeid; + *typetypmod = row->rowtupdesc->tdtypmod; + *value = HeapTupleGetDatum(tup); + *isnull = false; + MemoryContextSwitchTo(oldcontext); + break; + } + + case PLPGSQL_DTYPE_REC: + { + PLpgSQL_rec *rec = (PLpgSQL_rec *) datum; + + if (rec->erh == NULL) + { + /* Treat uninstantiated record as a simple NULL */ + *value = (Datum) 0; + *isnull = true; + /* Report variable's declared type */ + *typeid = rec->rectypeid; + *typetypmod = -1; + } + else + { + if (ExpandedRecordIsEmpty(rec->erh)) + { + /* Empty record is also a NULL */ + *value = (Datum) 0; + *isnull = true; + } + else + { + *value = ExpandedRecordGetDatum(rec->erh); + *isnull = false; + } + if (rec->rectypeid != RECORDOID) + { + /* Report variable's declared type, if not RECORD */ + *typeid = rec->rectypeid; + *typetypmod = -1; + } + else + { + /* Report record's actual type if declared RECORD */ + *typeid = rec->erh->er_typeid; + *typetypmod = rec->erh->er_typmod; + } + } + break; + } + + case PLPGSQL_DTYPE_RECFIELD: + { + PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum; + PLpgSQL_rec *rec; + ExpandedRecordHeader *erh; + + rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]); + erh = rec->erh; + + /* + * If record variable is NULL, instantiate it if it has a + * named composite type, else complain. (This won't change + * the logical state of the record: it's still NULL.) + */ + if (erh == NULL) + { + instantiate_empty_record_variable(estate, rec); + erh = rec->erh; + } + + /* + * Look up the field's properties if we have not already, or + * if the tuple descriptor ID changed since last time. + */ + if (unlikely(recfield->rectupledescid != erh->er_tupdesc_id)) + { + if (!expanded_record_lookup_field(erh, + recfield->fieldname, + &recfield->finfo)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("record \"%s\" has no field \"%s\"", + rec->refname, recfield->fieldname))); + recfield->rectupledescid = erh->er_tupdesc_id; + } + + /* Report type data. */ + *typeid = recfield->finfo.ftypeid; + *typetypmod = recfield->finfo.ftypmod; + + /* And fetch the field value. */ + *value = expanded_record_get_field(erh, + recfield->finfo.fnumber, + isnull); + break; + } + + default: + elog(ERROR, "unrecognized dtype: %d", datum->dtype); + } +} + +char * convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) +{ + char *result; + MemoryContext oldcontext; + Oid typoutput; + bool typIsVarlena; + + oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); + getTypeOutputInfo(valtype, &typoutput, &typIsVarlena); + result = OidOutputFunctionCall(typoutput, value); + MemoryContextSwitchTo(oldcontext); + + return result; +} + +/* + * If the variable has an armed "promise", compute the promised value + * and assign it to the variable. + * The assignment automatically disarms the promise. + */ +static void +plpgsql_fulfill_promise(PLpgSQL_execstate *estate, + PLpgSQL_var *var) +{ + MemoryContext oldcontext; + + if (var->promise == PLPGSQL_PROMISE_NONE) + return; /* nothing to do */ + + /* + * This will typically be invoked in a short-lived context such as the + * mcontext. We must create variable values in the estate's datum + * context. This quick-and-dirty solution risks leaking some additional + * cruft there, but since any one promise is honored at most once per + * function call, it's probably not worth being more careful. + */ + oldcontext = MemoryContextSwitchTo(estate->datum_context); + + switch (var->promise) + { + case PLPGSQL_PROMISE_TG_NAME: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(estate->trigdata->tg_trigger->tgname)), + false, true); + break; + + case PLPGSQL_PROMISE_TG_WHEN: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_BEFORE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "BEFORE"); + else if (TRIGGER_FIRED_AFTER(estate->trigdata->tg_event)) + assign_text_var(estate, var, "AFTER"); + else if (TRIGGER_FIRED_INSTEAD(estate->trigdata->tg_event)) + assign_text_var(estate, var, "INSTEAD OF"); + else + elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF"); + break; + + case PLPGSQL_PROMISE_TG_LEVEL: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_FOR_ROW(estate->trigdata->tg_event)) + assign_text_var(estate, var, "ROW"); + else if (TRIGGER_FIRED_FOR_STATEMENT(estate->trigdata->tg_event)) + assign_text_var(estate, var, "STATEMENT"); + else + elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT"); + break; + + case PLPGSQL_PROMISE_TG_OP: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_BY_INSERT(estate->trigdata->tg_event)) + assign_text_var(estate, var, "INSERT"); + else if (TRIGGER_FIRED_BY_UPDATE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "UPDATE"); + else if (TRIGGER_FIRED_BY_DELETE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "DELETE"); + else if (TRIGGER_FIRED_BY_TRUNCATE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "TRUNCATE"); + else + elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE"); + break; + + case PLPGSQL_PROMISE_TG_RELID: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + ObjectIdGetDatum(estate->trigdata->tg_relation->rd_id), + false, false); + break; + + case PLPGSQL_PROMISE_TG_TABLE_NAME: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(RelationGetRelationName(estate->trigdata->tg_relation))), + false, true); + break; + + case PLPGSQL_PROMISE_TG_TABLE_SCHEMA: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(get_namespace_name(RelationGetNamespace(estate->trigdata->tg_relation)))), + false, true); + break; + + case PLPGSQL_PROMISE_TG_NARGS: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + Int16GetDatum(estate->trigdata->tg_trigger->tgnargs), + false, false); + break; + + case PLPGSQL_PROMISE_TG_ARGV: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (estate->trigdata->tg_trigger->tgnargs > 0) + { + /* + * For historical reasons, tg_argv[] subscripts start at zero + * not one. So we can't use construct_array(). + */ + int nelems = estate->trigdata->tg_trigger->tgnargs; + Datum *elems; + int dims[1]; + int lbs[1]; + int i; + + elems = palloc(sizeof(Datum) * nelems); + for (i = 0; i < nelems; i++) + elems[i] = CStringGetTextDatum(estate->trigdata->tg_trigger->tgargs[i]); + dims[0] = nelems; + lbs[0] = 0; + + assign_simple_var(estate, var, + PointerGetDatum(construct_md_array(elems, NULL, + 1, dims, lbs, + TEXTOID, + -1, false, TYPALIGN_INT)), + false, true); + } + else + { + assign_simple_var(estate, var, (Datum) 0, true, false); + } + break; + + case PLPGSQL_PROMISE_TG_EVENT: + if (estate->evtrigdata == NULL) + elog(ERROR, "event trigger promise is not in an event trigger function"); + assign_text_var(estate, var, estate->evtrigdata->event); + break; + + case PLPGSQL_PROMISE_TG_TAG: + if (estate->evtrigdata == NULL) + elog(ERROR, "event trigger promise is not in an event trigger function"); + assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag)); + break; + + default: + elog(ERROR, "unrecognized promise type: %d", var->promise); + } + + MemoryContextSwitchTo(oldcontext); +} + +/* + * Create a memory context for statement-lifespan variables, if we don't + * have one already. It will be a child of stmt_mcontext_parent, which is + * either the function's main context or a pushed-down outer stmt_mcontext. + */ +static MemoryContext +get_stmt_mcontext(PLpgSQL_execstate *estate) +{ + if (estate->stmt_mcontext == NULL) + { + estate->stmt_mcontext = + AllocSetContextCreate(estate->stmt_mcontext_parent, + "PLpgSQL per-statement data", + ALLOCSET_DEFAULT_SIZES); + } + return estate->stmt_mcontext; +} + +static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) +{ + Assert(rec->erh == NULL); /* else caller error */ + + /* If declared type is RECORD, we can't instantiate */ + if (rec->rectypeid == RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("record \"%s\" is not assigned yet", rec->refname), + errdetail("The tuple structure of a not-yet-assigned record is indeterminate."))); + + /* Make sure rec->rectypeid is up-to-date before using it */ + revalidate_rectypeid(rec); + + /* OK, do it */ + rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1, + estate->datum_context); +} + +static void revalidate_rectypeid(PLpgSQL_rec *rec) +{ + PLpgSQL_type *typ = rec->datatype; + TypeCacheEntry *typentry; + + if (rec->rectypeid == RECORDOID) + return; /* it's RECORD, so nothing to do */ + Assert(typ != NULL); + if (typ->tcache && + typ->tcache->tupDesc_identifier == typ->tupdesc_id) + { + /* + * Although *typ is known up-to-date, it's possible that rectypeid + * isn't, because *rec is cloned during each function startup from a + * copy that we don't have a good way to update. Hence, forcibly fix + * rectypeid before returning. + */ + rec->rectypeid = typ->typoid; + return; + } + + /* + * typcache entry has suffered invalidation, so re-look-up the type name + * if possible, and then recheck the type OID. If we don't have a + * TypeName, then we just have to soldier on with the OID we've got. + */ + if (typ->origtypname != NULL) + { + /* this bit should match parse_datatype() in pl_gram.y */ + typenameTypeIdAndMod(NULL, typ->origtypname, + &typ->typoid, + &typ->atttypmod); + } + + /* this bit should match build_datatype() in pl_comp.c */ + typentry = lookup_type_cache(typ->typoid, + TYPECACHE_TUPDESC | + TYPECACHE_DOMAIN_BASE_INFO); + if (typentry->typtype == TYPTYPE_DOMAIN) + typentry = lookup_type_cache(typentry->domainBaseType, + TYPECACHE_TUPDESC); + if (typentry->tupDesc == NULL) + { + /* + * If we get here, user tried to replace a composite type with a + * non-composite one. We're not gonna support that. + */ + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(typ->typoid)))); + } + + /* + * Update tcache and tupdesc_id. Since we don't support changing to a + * non-composite type, none of the rest of *typ needs to change. + */ + typ->tcache = typentry; + typ->tupdesc_id = typentry->tupDesc_identifier; + + /* + * Update *rec, too. (We'll deal with subsidiary RECFIELDs as needed.) + */ + rec->rectypeid = typ->typoid; +} + + +static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc) +{ + int natts = tupdesc->natts; + HeapTuple tuple; + Datum *dvalues; + bool *nulls; + int i; + + if (natts != row->nfields) + return NULL; + + dvalues = (Datum *) eval_mcontext_alloc0(estate, natts * sizeof(Datum)); + nulls = (bool *) eval_mcontext_alloc(estate, natts * sizeof(bool)); + + for (i = 0; i < natts; i++) + { + Oid fieldtypeid; + int32 fieldtypmod; + + if (TupleDescAttr(tupdesc, i)->attisdropped) + { + nulls[i] = true; /* leave the column as null */ + continue; + } + + exec_eval_datum(estate, estate->datums[row->varnos[i]], + &fieldtypeid, &fieldtypmod, + &dvalues[i], &nulls[i]); + if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid) + return NULL; + /* XXX should we insist on typmod match, too? */ + } + + tuple = heap_form_tuple(tupdesc, dvalues, nulls); + + return tuple; +} + +static void +assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) +{ + assign_simple_var(estate, var, CStringGetTextDatum(str), false, true); +} + +static void +assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, + Datum newvalue, bool isnull, bool freeable) +{ + Assert(var->dtype == PLPGSQL_DTYPE_VAR || + var->dtype == PLPGSQL_DTYPE_PROMISE); + + /* + * In non-atomic contexts, we do not want to store TOAST pointers in + * variables, because such pointers might become stale after a commit. + * Forcibly detoast in such cases. We don't want to detoast (flatten) + * expanded objects, however; those should be OK across a transaction + * boundary since they're just memory-resident objects. (Elsewhere in + * this module, operations on expanded records likewise need to request + * detoasting of record fields when !estate->atomic. Expanded arrays are + * not a problem since all array entries are always detoasted.) + */ + if (!estate->atomic && !isnull && var->datatype->typlen == -1 && + VARATT_IS_EXTERNAL_NON_EXPANDED(DatumGetPointer(newvalue))) + { + MemoryContext oldcxt; + Datum detoasted; + + /* + * Do the detoasting in the eval_mcontext to avoid long-term leakage + * of whatever memory toast fetching might leak. Then we have to copy + * the detoasted datum to the function's main context, which is a + * pain, but there's little choice. + */ + oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate)); + detoasted = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newvalue))); + MemoryContextSwitchTo(oldcxt); + /* Now's a good time to not leak the input value if it's freeable */ + if (freeable) + pfree(DatumGetPointer(newvalue)); + /* Once we copy the value, it's definitely freeable */ + newvalue = datumCopy(detoasted, false, -1); + freeable = true; + /* Can't clean up eval_mcontext here, but it'll happen before long */ + } + + /* Free the old value if needed */ + if (var->freeval) + { + if (DatumIsReadWriteExpandedObject(var->value, + var->isnull, + var->datatype->typlen)) + DeleteExpandedObject(var->value); + else + pfree(DatumGetPointer(var->value)); + } + /* Assign new value to datum */ + var->value = newvalue; + var->isnull = isnull; + var->freeval = freeable; + + /* + * If it's a promise variable, then either we just assigned the promised + * value, or the user explicitly assigned an overriding value. Either + * way, cancel the promise. + */ + var->promise = PLPGSQL_PROMISE_NONE; +} From 3d6006472baf1097543926d54625e22c3cbf432f Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Tue, 1 Feb 2022 23:20:58 +0000 Subject: [PATCH 02/46] Add missing macros --- plpgsql_debugger.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plpgsql_debugger.c b/plpgsql_debugger.c index a134888..74e5bc8 100644 --- a/plpgsql_debugger.c +++ b/plpgsql_debugger.c @@ -83,6 +83,15 @@ typedef struct #endif } dbg_ctx; +#define get_eval_mcontext(estate) \ + ((estate)->eval_econtext->ecxt_per_tuple_memory) +#define eval_mcontext_alloc(estate, sz) \ + MemoryContextAlloc(get_eval_mcontext(estate), sz) +#define eval_mcontext_alloc0(estate, sz) \ + MemoryContextAllocZero(get_eval_mcontext(estate), sz) + +#define NULL_DATUM "NULL" + static void dbg_startup( PLpgSQL_execstate * estate, PLpgSQL_function * func ); static void dbg_newstmt( PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt ); static void initialize_plugin_info( PLpgSQL_execstate * estate, PLpgSQL_function * func ); @@ -362,7 +371,7 @@ plpgsql_send_vars(ErrorContextCallback *frame) variable->isconst ? 't':'f', variable->notnull ? 't':'f', typeid, - isNull? "NULL" : convert_value_to_string(estate, value, typeid)); + isNull? NULL_DATUM : convert_value_to_string(estate, value, typeid)); break; } } From 87b09aa2299b91225f666e614f2b93f22a52e88e Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Wed, 2 Feb 2022 01:04:04 +0000 Subject: [PATCH 03/46] Install instructions --- INSTALL.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 INSTALL.md diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..08b4e6b --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,27 @@ +# Install with docker container + +Tested with succes with PG 13 & 14 +Replace X with your version + +## Run docker container +> docker run --name postgres-14-idea-debugger -p 5442:5432 -e POSTGRES_PASSWORD=postgres -d postgres:1X + +## Install Postgres sources + + +> apt update && apt upgrade +> apt install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex postgresql-server-dev-1X +> cd /usr/src/ + +> git clone https://github.com/postgres/postgres.git +> cd postgres +> git checkout REL_1X_STABLE +> ./configure +> cd contrib +> git clone https://github.com/ng-galien/pldebugger.git +> cd pldebugger/ +> git checkout print-vars-14 + +## Install +> make clean && make USE_PGXS=1 && make USE_PGXS=1 install + From 7a6579b77e24c0959b2e72a15870a56498fb28c9 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Wed, 2 Feb 2022 01:18:03 +0000 Subject: [PATCH 04/46] Install formatting --- INSTALL.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 08b4e6b..cf2c3f1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -6,22 +6,31 @@ Replace X with your version ## Run docker container > docker run --name postgres-14-idea-debugger -p 5442:5432 -e POSTGRES_PASSWORD=postgres -d postgres:1X -## Install Postgres sources +## Install Postgres and debugger sources +> apt update && apt upgrade -> apt update && apt upgrade > apt install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex postgresql-server-dev-1X + > cd /usr/src/ > git clone https://github.com/postgres/postgres.git -> cd postgres + +> cd postgres + > git checkout REL_1X_STABLE + > ./configure + > cd contrib + > git clone https://github.com/ng-galien/pldebugger.git + > cd pldebugger/ + > git checkout print-vars-14 -## Install +## Install debugger + > make clean && make USE_PGXS=1 && make USE_PGXS=1 install From fe84731c0e4785de107a7f8d318fbd564c862557 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 17 Apr 2022 10:55:42 +0200 Subject: [PATCH 05/46] Update INSTALL.md Update branch name --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index cf2c3f1..ee3b32d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -28,7 +28,7 @@ Replace X with your version > cd pldebugger/ -> git checkout print-vars-14 +> git checkout print-vars ## Install debugger From b9f94a6e34909a5974ed790f09af10ccb8c63f38 Mon Sep 17 00:00:00 2001 From: Constantine Grischenko <98530452+cvas71@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:44:59 +0300 Subject: [PATCH 06/46] Update plpgsql_debugger.c Now, when debugging, you can see variable values with the RECORD type. --- plpgsql_debugger.c | 212 ++++++++++++++++++++++++--------------------- 1 file changed, 111 insertions(+), 101 deletions(-) diff --git a/plpgsql_debugger.c b/plpgsql_debugger.c index 74e5bc8..856b2b1 100644 --- a/plpgsql_debugger.c +++ b/plpgsql_debugger.c @@ -129,8 +129,8 @@ static void plpgsql_send_cur_line(ErrorContextCallback *frame); static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PLpgSQL_var *var); static void exec_eval_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeid, int32 *typetypmod, Datum *value, bool *isnull); static char *convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype); -static void revalidate_rectypeid(PLpgSQL_rec *rec); -static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec); +//static void revalidate_rectypeid(PLpgSQL_rec *rec); +//static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec); static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc); static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str); static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, Datum newvalue, bool isnull, bool freeable); @@ -340,6 +340,9 @@ plpgsql_send_vars(ErrorContextCallback *frame) { if( is_var_visible( estate, i )) { + //if((estate->datums[i]->dtype==PLPGSQL_DTYPE_RECFIELD)||(estate->datums[i]->dtype==PLPGSQL_DTYPE_REC)) + // continue; + datum = (PLpgSQL_datum*) estate->datums[i]; variable = (PLpgSQL_variable*) estate->datums[i]; isArg = dbg_info->func->fn_nargs > 0 && i < dbg_info->func->fn_nargs; @@ -1640,8 +1643,15 @@ void exec_eval_datum(PLpgSQL_execstate *estate, */ if (erh == NULL) { - instantiate_empty_record_variable(estate, rec); - erh = rec->erh; + /* Treat uninstantiated record as a simple NULL */ + *value = (Datum) 0; + *isnull = true; + /* Report variable's declared type */ + *typeid = (Oid) 0; //rec->rectypeid; + *typetypmod = -1; + break; + //instantiate_empty_record_variable(estate, rec); + //erh = rec->erh; } /* @@ -1856,103 +1866,103 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate, * have one already. It will be a child of stmt_mcontext_parent, which is * either the function's main context or a pushed-down outer stmt_mcontext. */ -static MemoryContext -get_stmt_mcontext(PLpgSQL_execstate *estate) -{ - if (estate->stmt_mcontext == NULL) - { - estate->stmt_mcontext = - AllocSetContextCreate(estate->stmt_mcontext_parent, - "PLpgSQL per-statement data", - ALLOCSET_DEFAULT_SIZES); - } - return estate->stmt_mcontext; -} - -static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) -{ - Assert(rec->erh == NULL); /* else caller error */ - - /* If declared type is RECORD, we can't instantiate */ - if (rec->rectypeid == RECORDOID) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("record \"%s\" is not assigned yet", rec->refname), - errdetail("The tuple structure of a not-yet-assigned record is indeterminate."))); - - /* Make sure rec->rectypeid is up-to-date before using it */ - revalidate_rectypeid(rec); - - /* OK, do it */ - rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1, - estate->datum_context); -} - -static void revalidate_rectypeid(PLpgSQL_rec *rec) -{ - PLpgSQL_type *typ = rec->datatype; - TypeCacheEntry *typentry; - - if (rec->rectypeid == RECORDOID) - return; /* it's RECORD, so nothing to do */ - Assert(typ != NULL); - if (typ->tcache && - typ->tcache->tupDesc_identifier == typ->tupdesc_id) - { - /* - * Although *typ is known up-to-date, it's possible that rectypeid - * isn't, because *rec is cloned during each function startup from a - * copy that we don't have a good way to update. Hence, forcibly fix - * rectypeid before returning. - */ - rec->rectypeid = typ->typoid; - return; - } - - /* - * typcache entry has suffered invalidation, so re-look-up the type name - * if possible, and then recheck the type OID. If we don't have a - * TypeName, then we just have to soldier on with the OID we've got. - */ - if (typ->origtypname != NULL) - { - /* this bit should match parse_datatype() in pl_gram.y */ - typenameTypeIdAndMod(NULL, typ->origtypname, - &typ->typoid, - &typ->atttypmod); - } - - /* this bit should match build_datatype() in pl_comp.c */ - typentry = lookup_type_cache(typ->typoid, - TYPECACHE_TUPDESC | - TYPECACHE_DOMAIN_BASE_INFO); - if (typentry->typtype == TYPTYPE_DOMAIN) - typentry = lookup_type_cache(typentry->domainBaseType, - TYPECACHE_TUPDESC); - if (typentry->tupDesc == NULL) - { - /* - * If we get here, user tried to replace a composite type with a - * non-composite one. We're not gonna support that. - */ - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("type %s is not composite", - format_type_be(typ->typoid)))); - } - - /* - * Update tcache and tupdesc_id. Since we don't support changing to a - * non-composite type, none of the rest of *typ needs to change. - */ - typ->tcache = typentry; - typ->tupdesc_id = typentry->tupDesc_identifier; - - /* - * Update *rec, too. (We'll deal with subsidiary RECFIELDs as needed.) - */ - rec->rectypeid = typ->typoid; -} +//static MemoryContext +//get_stmt_mcontext(PLpgSQL_execstate *estate) +//{ +// if (estate->stmt_mcontext == NULL) +// { +// estate->stmt_mcontext = +// AllocSetContextCreate(estate->stmt_mcontext_parent, +// "PLpgSQL per-statement data", +// ALLOCSET_DEFAULT_SIZES); +// } +// return estate->stmt_mcontext; +//} + +//static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) +//{ +// Assert(rec->erh == NULL); /* else caller error */ +// +// /* If declared type is RECORD, we can't instantiate */ +// if (rec->rectypeid == RECORDOID) +// ereport(ERROR, +// (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), +// errmsg("record \"%s\" is not assigned yet", rec->refname), +// errdetail("The tuple structure of a not-yet-assigned record is indeterminate. :("))); +// +// /* Make sure rec->rectypeid is up-to-date before using it */ +// revalidate_rectypeid(rec); +// +// /* OK, do it */ +// rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1, +// estate->datum_context); +//} + +//static void revalidate_rectypeid(PLpgSQL_rec *rec) +//{ +// PLpgSQL_type *typ = rec->datatype; +// TypeCacheEntry *typentry; +// +// if (rec->rectypeid == RECORDOID) +// return; /* it's RECORD, so nothing to do */ +// Assert(typ != NULL); +// if (typ->tcache && +// typ->tcache->tupDesc_identifier == typ->tupdesc_id) +// { +// /* +// * Although *typ is known up-to-date, it's possible that rectypeid +// * isn't, because *rec is cloned during each function startup from a +// * copy that we don't have a good way to update. Hence, forcibly fix +// * rectypeid before returning. +// */ +// rec->rectypeid = typ->typoid; +// return; +// } +// +// /* +// * typcache entry has suffered invalidation, so re-look-up the type name +// * if possible, and then recheck the type OID. If we don't have a +// * TypeName, then we just have to soldier on with the OID we've got. +// */ +// if (typ->origtypname != NULL) +// { +// /* this bit should match parse_datatype() in pl_gram.y */ +// typenameTypeIdAndMod(NULL, typ->origtypname, +// &typ->typoid, +// &typ->atttypmod); +// } +// +// /* this bit should match build_datatype() in pl_comp.c */ +// typentry = lookup_type_cache(typ->typoid, +// TYPECACHE_TUPDESC | +// TYPECACHE_DOMAIN_BASE_INFO); +// if (typentry->typtype == TYPTYPE_DOMAIN) +// typentry = lookup_type_cache(typentry->domainBaseType, +// TYPECACHE_TUPDESC); +// if (typentry->tupDesc == NULL) +// { +// /* +// * If we get here, user tried to replace a composite type with a +// * non-composite one. We're not gonna support that. +// */ +// ereport(ERROR, +// (errcode(ERRCODE_WRONG_OBJECT_TYPE), +// errmsg("type %s is not composite", +// format_type_be(typ->typoid)))); +// } +// +// /* +// * Update tcache and tupdesc_id. Since we don't support changing to a +// * non-composite type, none of the rest of *typ needs to change. +// */ +// typ->tcache = typentry; +// typ->tupdesc_id = typentry->tupDesc_identifier; +// +// /* +// * Update *rec, too. (We'll deal with subsidiary RECFIELDs as needed.) +// */ +// rec->rectypeid = typ->typoid; +//} static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc) From 631d30f96d7c8c13cfc8fb72f5feacf8decf325c Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Thu, 29 Sep 2022 19:25:01 +0000 Subject: [PATCH 07/46] Move files --- Makefile | 2 +- plpgsql_debugger.c | 559 +-------------------------------------------- plpgsql_var.c | 443 +++++++++++++++++++++++++++++++++++ plpgsql_var.h | 39 ++++ 4 files changed, 484 insertions(+), 559 deletions(-) create mode 100644 plpgsql_var.c create mode 100644 plpgsql_var.h diff --git a/Makefile b/Makefile index 0d0d82e..c00592a 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ EXTENSION = pldbgapi MODULE_big = plugin_debugger -OBJS = plpgsql_debugger.o plugin_debugger.o dbgcomm.o pldbgapi.o +OBJS = plpgsql_var.o plpgsql_debugger.o plugin_debugger.o dbgcomm.o pldbgapi.o ifdef INCLUDE_PACKAGE_SUPPORT OBJS += spl_debugger.o endif diff --git a/plpgsql_debugger.c b/plpgsql_debugger.c index 856b2b1..666aaf4 100644 --- a/plpgsql_debugger.c +++ b/plpgsql_debugger.c @@ -41,6 +41,7 @@ #endif #include "pldebugger.h" +#include "plpgsql_var.h" /* Include header for GETSTRUCT */ #if (PG_VERSION_NUM >= 90300) @@ -83,13 +84,6 @@ typedef struct #endif } dbg_ctx; -#define get_eval_mcontext(estate) \ - ((estate)->eval_econtext->ecxt_per_tuple_memory) -#define eval_mcontext_alloc(estate, sz) \ - MemoryContextAlloc(get_eval_mcontext(estate), sz) -#define eval_mcontext_alloc0(estate, sz) \ - MemoryContextAllocZero(get_eval_mcontext(estate), sz) - #define NULL_DATUM "NULL" static void dbg_startup( PLpgSQL_execstate * estate, PLpgSQL_function * func ); @@ -126,15 +120,6 @@ static bool plpgsql_do_deposit(ErrorContextCallback *frame, const char *var_name static Oid plpgsql_get_func_oid(ErrorContextCallback *frame); static void plpgsql_send_cur_line(ErrorContextCallback *frame); -static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PLpgSQL_var *var); -static void exec_eval_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeid, int32 *typetypmod, Datum *value, bool *isnull); -static char *convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype); -//static void revalidate_rectypeid(PLpgSQL_rec *rec); -//static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec); -static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc); -static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str); -static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, Datum newvalue, bool isnull, bool freeable); - #if INCLUDE_PACKAGE_SUPPORT debugger_language_t spl_debugger_lang = #else @@ -340,9 +325,6 @@ plpgsql_send_vars(ErrorContextCallback *frame) { if( is_var_visible( estate, i )) { - //if((estate->datums[i]->dtype==PLPGSQL_DTYPE_RECFIELD)||(estate->datums[i]->dtype==PLPGSQL_DTYPE_REC)) - // continue; - datum = (PLpgSQL_datum*) estate->datums[i]; variable = (PLpgSQL_variable*) estate->datums[i]; isArg = dbg_info->func->fn_nargs > 0 && i < dbg_info->func->fn_nargs; @@ -1533,542 +1515,3 @@ datumIsNull(PLpgSQL_datum *datum) return false; } -void exec_eval_datum(PLpgSQL_execstate *estate, - PLpgSQL_datum *datum, - Oid *typeid, - int32 *typetypmod, - Datum *value, - bool *isnull) -{ - MemoryContext oldcontext; - - switch (datum->dtype) - { - case PLPGSQL_DTYPE_PROMISE: - /* fulfill promise if needed, then handle like regular var */ - plpgsql_fulfill_promise(estate, (PLpgSQL_var *) datum); - - /* FALL THRU */ - - case PLPGSQL_DTYPE_VAR: - { - PLpgSQL_var *var = (PLpgSQL_var *) datum; - - *typeid = var->datatype->typoid; - *typetypmod = var->datatype->atttypmod; - *value = var->value; - *isnull = var->isnull; - break; - } - - case PLPGSQL_DTYPE_ROW: - { - PLpgSQL_row *row = (PLpgSQL_row *) datum; - HeapTuple tup; - - /* We get here if there are multiple OUT parameters */ - if (!row->rowtupdesc) /* should not happen */ - elog(ERROR, "row variable has no tupdesc"); - /* Make sure we have a valid type/typmod setting */ - BlessTupleDesc(row->rowtupdesc); - - - oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); - tup = make_tuple_from_row(estate, row, row->rowtupdesc); - if (tup == NULL) /* should not happen */ - elog(ERROR, "row not compatible with its own tupdesc"); - *typeid = row->rowtupdesc->tdtypeid; - *typetypmod = row->rowtupdesc->tdtypmod; - *value = HeapTupleGetDatum(tup); - *isnull = false; - MemoryContextSwitchTo(oldcontext); - break; - } - - case PLPGSQL_DTYPE_REC: - { - PLpgSQL_rec *rec = (PLpgSQL_rec *) datum; - - if (rec->erh == NULL) - { - /* Treat uninstantiated record as a simple NULL */ - *value = (Datum) 0; - *isnull = true; - /* Report variable's declared type */ - *typeid = rec->rectypeid; - *typetypmod = -1; - } - else - { - if (ExpandedRecordIsEmpty(rec->erh)) - { - /* Empty record is also a NULL */ - *value = (Datum) 0; - *isnull = true; - } - else - { - *value = ExpandedRecordGetDatum(rec->erh); - *isnull = false; - } - if (rec->rectypeid != RECORDOID) - { - /* Report variable's declared type, if not RECORD */ - *typeid = rec->rectypeid; - *typetypmod = -1; - } - else - { - /* Report record's actual type if declared RECORD */ - *typeid = rec->erh->er_typeid; - *typetypmod = rec->erh->er_typmod; - } - } - break; - } - - case PLPGSQL_DTYPE_RECFIELD: - { - PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum; - PLpgSQL_rec *rec; - ExpandedRecordHeader *erh; - - rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]); - erh = rec->erh; - - /* - * If record variable is NULL, instantiate it if it has a - * named composite type, else complain. (This won't change - * the logical state of the record: it's still NULL.) - */ - if (erh == NULL) - { - /* Treat uninstantiated record as a simple NULL */ - *value = (Datum) 0; - *isnull = true; - /* Report variable's declared type */ - *typeid = (Oid) 0; //rec->rectypeid; - *typetypmod = -1; - break; - //instantiate_empty_record_variable(estate, rec); - //erh = rec->erh; - } - - /* - * Look up the field's properties if we have not already, or - * if the tuple descriptor ID changed since last time. - */ - if (unlikely(recfield->rectupledescid != erh->er_tupdesc_id)) - { - if (!expanded_record_lookup_field(erh, - recfield->fieldname, - &recfield->finfo)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("record \"%s\" has no field \"%s\"", - rec->refname, recfield->fieldname))); - recfield->rectupledescid = erh->er_tupdesc_id; - } - - /* Report type data. */ - *typeid = recfield->finfo.ftypeid; - *typetypmod = recfield->finfo.ftypmod; - - /* And fetch the field value. */ - *value = expanded_record_get_field(erh, - recfield->finfo.fnumber, - isnull); - break; - } - - default: - elog(ERROR, "unrecognized dtype: %d", datum->dtype); - } -} - -char * convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) -{ - char *result; - MemoryContext oldcontext; - Oid typoutput; - bool typIsVarlena; - - oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); - getTypeOutputInfo(valtype, &typoutput, &typIsVarlena); - result = OidOutputFunctionCall(typoutput, value); - MemoryContextSwitchTo(oldcontext); - - return result; -} - -/* - * If the variable has an armed "promise", compute the promised value - * and assign it to the variable. - * The assignment automatically disarms the promise. - */ -static void -plpgsql_fulfill_promise(PLpgSQL_execstate *estate, - PLpgSQL_var *var) -{ - MemoryContext oldcontext; - - if (var->promise == PLPGSQL_PROMISE_NONE) - return; /* nothing to do */ - - /* - * This will typically be invoked in a short-lived context such as the - * mcontext. We must create variable values in the estate's datum - * context. This quick-and-dirty solution risks leaking some additional - * cruft there, but since any one promise is honored at most once per - * function call, it's probably not worth being more careful. - */ - oldcontext = MemoryContextSwitchTo(estate->datum_context); - - switch (var->promise) - { - case PLPGSQL_PROMISE_TG_NAME: - if (estate->trigdata == NULL) - elog(ERROR, "trigger promise is not in a trigger function"); - assign_simple_var(estate, var, - DirectFunctionCall1(namein, - CStringGetDatum(estate->trigdata->tg_trigger->tgname)), - false, true); - break; - - case PLPGSQL_PROMISE_TG_WHEN: - if (estate->trigdata == NULL) - elog(ERROR, "trigger promise is not in a trigger function"); - if (TRIGGER_FIRED_BEFORE(estate->trigdata->tg_event)) - assign_text_var(estate, var, "BEFORE"); - else if (TRIGGER_FIRED_AFTER(estate->trigdata->tg_event)) - assign_text_var(estate, var, "AFTER"); - else if (TRIGGER_FIRED_INSTEAD(estate->trigdata->tg_event)) - assign_text_var(estate, var, "INSTEAD OF"); - else - elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF"); - break; - - case PLPGSQL_PROMISE_TG_LEVEL: - if (estate->trigdata == NULL) - elog(ERROR, "trigger promise is not in a trigger function"); - if (TRIGGER_FIRED_FOR_ROW(estate->trigdata->tg_event)) - assign_text_var(estate, var, "ROW"); - else if (TRIGGER_FIRED_FOR_STATEMENT(estate->trigdata->tg_event)) - assign_text_var(estate, var, "STATEMENT"); - else - elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT"); - break; - - case PLPGSQL_PROMISE_TG_OP: - if (estate->trigdata == NULL) - elog(ERROR, "trigger promise is not in a trigger function"); - if (TRIGGER_FIRED_BY_INSERT(estate->trigdata->tg_event)) - assign_text_var(estate, var, "INSERT"); - else if (TRIGGER_FIRED_BY_UPDATE(estate->trigdata->tg_event)) - assign_text_var(estate, var, "UPDATE"); - else if (TRIGGER_FIRED_BY_DELETE(estate->trigdata->tg_event)) - assign_text_var(estate, var, "DELETE"); - else if (TRIGGER_FIRED_BY_TRUNCATE(estate->trigdata->tg_event)) - assign_text_var(estate, var, "TRUNCATE"); - else - elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE"); - break; - - case PLPGSQL_PROMISE_TG_RELID: - if (estate->trigdata == NULL) - elog(ERROR, "trigger promise is not in a trigger function"); - assign_simple_var(estate, var, - ObjectIdGetDatum(estate->trigdata->tg_relation->rd_id), - false, false); - break; - - case PLPGSQL_PROMISE_TG_TABLE_NAME: - if (estate->trigdata == NULL) - elog(ERROR, "trigger promise is not in a trigger function"); - assign_simple_var(estate, var, - DirectFunctionCall1(namein, - CStringGetDatum(RelationGetRelationName(estate->trigdata->tg_relation))), - false, true); - break; - - case PLPGSQL_PROMISE_TG_TABLE_SCHEMA: - if (estate->trigdata == NULL) - elog(ERROR, "trigger promise is not in a trigger function"); - assign_simple_var(estate, var, - DirectFunctionCall1(namein, - CStringGetDatum(get_namespace_name(RelationGetNamespace(estate->trigdata->tg_relation)))), - false, true); - break; - - case PLPGSQL_PROMISE_TG_NARGS: - if (estate->trigdata == NULL) - elog(ERROR, "trigger promise is not in a trigger function"); - assign_simple_var(estate, var, - Int16GetDatum(estate->trigdata->tg_trigger->tgnargs), - false, false); - break; - - case PLPGSQL_PROMISE_TG_ARGV: - if (estate->trigdata == NULL) - elog(ERROR, "trigger promise is not in a trigger function"); - if (estate->trigdata->tg_trigger->tgnargs > 0) - { - /* - * For historical reasons, tg_argv[] subscripts start at zero - * not one. So we can't use construct_array(). - */ - int nelems = estate->trigdata->tg_trigger->tgnargs; - Datum *elems; - int dims[1]; - int lbs[1]; - int i; - - elems = palloc(sizeof(Datum) * nelems); - for (i = 0; i < nelems; i++) - elems[i] = CStringGetTextDatum(estate->trigdata->tg_trigger->tgargs[i]); - dims[0] = nelems; - lbs[0] = 0; - - assign_simple_var(estate, var, - PointerGetDatum(construct_md_array(elems, NULL, - 1, dims, lbs, - TEXTOID, - -1, false, TYPALIGN_INT)), - false, true); - } - else - { - assign_simple_var(estate, var, (Datum) 0, true, false); - } - break; - - case PLPGSQL_PROMISE_TG_EVENT: - if (estate->evtrigdata == NULL) - elog(ERROR, "event trigger promise is not in an event trigger function"); - assign_text_var(estate, var, estate->evtrigdata->event); - break; - - case PLPGSQL_PROMISE_TG_TAG: - if (estate->evtrigdata == NULL) - elog(ERROR, "event trigger promise is not in an event trigger function"); - assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag)); - break; - - default: - elog(ERROR, "unrecognized promise type: %d", var->promise); - } - - MemoryContextSwitchTo(oldcontext); -} - -/* - * Create a memory context for statement-lifespan variables, if we don't - * have one already. It will be a child of stmt_mcontext_parent, which is - * either the function's main context or a pushed-down outer stmt_mcontext. - */ -//static MemoryContext -//get_stmt_mcontext(PLpgSQL_execstate *estate) -//{ -// if (estate->stmt_mcontext == NULL) -// { -// estate->stmt_mcontext = -// AllocSetContextCreate(estate->stmt_mcontext_parent, -// "PLpgSQL per-statement data", -// ALLOCSET_DEFAULT_SIZES); -// } -// return estate->stmt_mcontext; -//} - -//static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) -//{ -// Assert(rec->erh == NULL); /* else caller error */ -// -// /* If declared type is RECORD, we can't instantiate */ -// if (rec->rectypeid == RECORDOID) -// ereport(ERROR, -// (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), -// errmsg("record \"%s\" is not assigned yet", rec->refname), -// errdetail("The tuple structure of a not-yet-assigned record is indeterminate. :("))); -// -// /* Make sure rec->rectypeid is up-to-date before using it */ -// revalidate_rectypeid(rec); -// -// /* OK, do it */ -// rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1, -// estate->datum_context); -//} - -//static void revalidate_rectypeid(PLpgSQL_rec *rec) -//{ -// PLpgSQL_type *typ = rec->datatype; -// TypeCacheEntry *typentry; -// -// if (rec->rectypeid == RECORDOID) -// return; /* it's RECORD, so nothing to do */ -// Assert(typ != NULL); -// if (typ->tcache && -// typ->tcache->tupDesc_identifier == typ->tupdesc_id) -// { -// /* -// * Although *typ is known up-to-date, it's possible that rectypeid -// * isn't, because *rec is cloned during each function startup from a -// * copy that we don't have a good way to update. Hence, forcibly fix -// * rectypeid before returning. -// */ -// rec->rectypeid = typ->typoid; -// return; -// } -// -// /* -// * typcache entry has suffered invalidation, so re-look-up the type name -// * if possible, and then recheck the type OID. If we don't have a -// * TypeName, then we just have to soldier on with the OID we've got. -// */ -// if (typ->origtypname != NULL) -// { -// /* this bit should match parse_datatype() in pl_gram.y */ -// typenameTypeIdAndMod(NULL, typ->origtypname, -// &typ->typoid, -// &typ->atttypmod); -// } -// -// /* this bit should match build_datatype() in pl_comp.c */ -// typentry = lookup_type_cache(typ->typoid, -// TYPECACHE_TUPDESC | -// TYPECACHE_DOMAIN_BASE_INFO); -// if (typentry->typtype == TYPTYPE_DOMAIN) -// typentry = lookup_type_cache(typentry->domainBaseType, -// TYPECACHE_TUPDESC); -// if (typentry->tupDesc == NULL) -// { -// /* -// * If we get here, user tried to replace a composite type with a -// * non-composite one. We're not gonna support that. -// */ -// ereport(ERROR, -// (errcode(ERRCODE_WRONG_OBJECT_TYPE), -// errmsg("type %s is not composite", -// format_type_be(typ->typoid)))); -// } -// -// /* -// * Update tcache and tupdesc_id. Since we don't support changing to a -// * non-composite type, none of the rest of *typ needs to change. -// */ -// typ->tcache = typentry; -// typ->tupdesc_id = typentry->tupDesc_identifier; -// -// /* -// * Update *rec, too. (We'll deal with subsidiary RECFIELDs as needed.) -// */ -// rec->rectypeid = typ->typoid; -//} - - -static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc) -{ - int natts = tupdesc->natts; - HeapTuple tuple; - Datum *dvalues; - bool *nulls; - int i; - - if (natts != row->nfields) - return NULL; - - dvalues = (Datum *) eval_mcontext_alloc0(estate, natts * sizeof(Datum)); - nulls = (bool *) eval_mcontext_alloc(estate, natts * sizeof(bool)); - - for (i = 0; i < natts; i++) - { - Oid fieldtypeid; - int32 fieldtypmod; - - if (TupleDescAttr(tupdesc, i)->attisdropped) - { - nulls[i] = true; /* leave the column as null */ - continue; - } - - exec_eval_datum(estate, estate->datums[row->varnos[i]], - &fieldtypeid, &fieldtypmod, - &dvalues[i], &nulls[i]); - if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid) - return NULL; - /* XXX should we insist on typmod match, too? */ - } - - tuple = heap_form_tuple(tupdesc, dvalues, nulls); - - return tuple; -} - -static void -assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) -{ - assign_simple_var(estate, var, CStringGetTextDatum(str), false, true); -} - -static void -assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, - Datum newvalue, bool isnull, bool freeable) -{ - Assert(var->dtype == PLPGSQL_DTYPE_VAR || - var->dtype == PLPGSQL_DTYPE_PROMISE); - - /* - * In non-atomic contexts, we do not want to store TOAST pointers in - * variables, because such pointers might become stale after a commit. - * Forcibly detoast in such cases. We don't want to detoast (flatten) - * expanded objects, however; those should be OK across a transaction - * boundary since they're just memory-resident objects. (Elsewhere in - * this module, operations on expanded records likewise need to request - * detoasting of record fields when !estate->atomic. Expanded arrays are - * not a problem since all array entries are always detoasted.) - */ - if (!estate->atomic && !isnull && var->datatype->typlen == -1 && - VARATT_IS_EXTERNAL_NON_EXPANDED(DatumGetPointer(newvalue))) - { - MemoryContext oldcxt; - Datum detoasted; - - /* - * Do the detoasting in the eval_mcontext to avoid long-term leakage - * of whatever memory toast fetching might leak. Then we have to copy - * the detoasted datum to the function's main context, which is a - * pain, but there's little choice. - */ - oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate)); - detoasted = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newvalue))); - MemoryContextSwitchTo(oldcxt); - /* Now's a good time to not leak the input value if it's freeable */ - if (freeable) - pfree(DatumGetPointer(newvalue)); - /* Once we copy the value, it's definitely freeable */ - newvalue = datumCopy(detoasted, false, -1); - freeable = true; - /* Can't clean up eval_mcontext here, but it'll happen before long */ - } - - /* Free the old value if needed */ - if (var->freeval) - { - if (DatumIsReadWriteExpandedObject(var->value, - var->isnull, - var->datatype->typlen)) - DeleteExpandedObject(var->value); - else - pfree(DatumGetPointer(var->value)); - } - /* Assign new value to datum */ - var->value = newvalue; - var->isnull = isnull; - var->freeval = freeable; - - /* - * If it's a promise variable, then either we just assigned the promised - * value, or the user explicitly assigned an overriding value. Either - * way, cancel the promise. - */ - var->promise = PLPGSQL_PROMISE_NONE; -} diff --git a/plpgsql_var.c b/plpgsql_var.c new file mode 100644 index 0000000..fca38ec --- /dev/null +++ b/plpgsql_var.c @@ -0,0 +1,443 @@ +#include "plpgsql_var.h" + +static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PLpgSQL_var *var); +static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc); + +static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str); + +static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, Datum newvalue, bool isnull, bool freeable); + +void exec_eval_datum(PLpgSQL_execstate *estate, + PLpgSQL_datum *datum, + Oid *typeid, + int32 *typetypmod, + Datum *value, + bool *isnull) +{ + MemoryContext oldcontext; + + switch (datum->dtype) + { + case PLPGSQL_DTYPE_PROMISE: + /* fulfill promise if needed, then handle like regular var */ + plpgsql_fulfill_promise(estate, (PLpgSQL_var *) datum); + + /* FALL THRU */ + + case PLPGSQL_DTYPE_VAR: + { + PLpgSQL_var *var = (PLpgSQL_var *) datum; + + *typeid = var->datatype->typoid; + *typetypmod = var->datatype->atttypmod; + *value = var->value; + *isnull = var->isnull; + break; + } + + case PLPGSQL_DTYPE_ROW: + { + PLpgSQL_row *row = (PLpgSQL_row *) datum; + HeapTuple tup; + + /* We get here if there are multiple OUT parameters */ + if (!row->rowtupdesc) /* should not happen */ + elog(ERROR, "row variable has no tupdesc"); + /* Make sure we have a valid type/typmod setting */ + BlessTupleDesc(row->rowtupdesc); + + + oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); + tup = make_tuple_from_row(estate, row, row->rowtupdesc); + if (tup == NULL) /* should not happen */ + elog(ERROR, "row not compatible with its own tupdesc"); + *typeid = row->rowtupdesc->tdtypeid; + *typetypmod = row->rowtupdesc->tdtypmod; + *value = HeapTupleGetDatum(tup); + *isnull = false; + MemoryContextSwitchTo(oldcontext); + break; + } + + case PLPGSQL_DTYPE_REC: + { + PLpgSQL_rec *rec = (PLpgSQL_rec *) datum; + + if (rec->erh == NULL) + { + /* Treat uninstantiated record as a simple NULL */ + *value = (Datum) 0; + *isnull = true; + /* Report variable's declared type */ + *typeid = rec->rectypeid; + *typetypmod = -1; + } + else + { + if (ExpandedRecordIsEmpty(rec->erh)) + { + /* Empty record is also a NULL */ + *value = (Datum) 0; + *isnull = true; + } + else + { + *value = ExpandedRecordGetDatum(rec->erh); + *isnull = false; + } + if (rec->rectypeid != RECORDOID) + { + /* Report variable's declared type, if not RECORD */ + *typeid = rec->rectypeid; + *typetypmod = -1; + } + else + { + /* Report record's actual type if declared RECORD */ + *typeid = rec->erh->er_typeid; + *typetypmod = rec->erh->er_typmod; + } + } + break; + } + + case PLPGSQL_DTYPE_RECFIELD: + { + PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum; + PLpgSQL_rec *rec; + ExpandedRecordHeader *erh; + + rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]); + erh = rec->erh; + + /* + * If record variable is NULL, instantiate it if it has a + * named composite type, else complain. (This won't change + * the logical state of the record: it's still NULL.) + */ + if (erh == NULL) + { + /* Treat uninstantiated record as a simple NULL */ + *value = (Datum) 0; + *isnull = true; + /* Report variable's declared type */ + *typeid = (Oid) 0; //rec->rectypeid; + *typetypmod = -1; + break; + //instantiate_empty_record_variable(estate, rec); + //erh = rec->erh; + } + + /* + * Look up the field's properties if we have not already, or + * if the tuple descriptor ID changed since last time. + */ + if (unlikely(recfield->rectupledescid != erh->er_tupdesc_id)) + { + if (!expanded_record_lookup_field(erh, + recfield->fieldname, + &recfield->finfo)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("record \"%s\" has no field \"%s\"", + rec->refname, recfield->fieldname))); + recfield->rectupledescid = erh->er_tupdesc_id; + } + + /* Report type data. */ + *typeid = recfield->finfo.ftypeid; + *typetypmod = recfield->finfo.ftypmod; + + /* And fetch the field value. */ + *value = expanded_record_get_field(erh, + recfield->finfo.fnumber, + isnull); + break; + } + + default: + elog(ERROR, "unrecognized dtype: %d", datum->dtype); + } +} + +char * convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) +{ + char *result; + MemoryContext oldcontext; + Oid typoutput; + bool typIsVarlena; + + oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); + getTypeOutputInfo(valtype, &typoutput, &typIsVarlena); + result = OidOutputFunctionCall(typoutput, value); + MemoryContextSwitchTo(oldcontext); + + return result; +} + +/* + * If the variable has an armed "promise", compute the promised value + * and assign it to the variable. + * The assignment automatically disarms the promise. + */ +static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, + PLpgSQL_var *var) +{ + MemoryContext oldcontext; + + if (var->promise == PLPGSQL_PROMISE_NONE) + return; /* nothing to do */ + + /* + * This will typically be invoked in a short-lived context such as the + * mcontext. We must create variable values in the estate's datum + * context. This quick-and-dirty solution risks leaking some additional + * cruft there, but since any one promise is honored at most once per + * function call, it's probably not worth being more careful. + */ + oldcontext = MemoryContextSwitchTo(estate->datum_context); + + switch (var->promise) + { + case PLPGSQL_PROMISE_TG_NAME: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(estate->trigdata->tg_trigger->tgname)), + false, true); + break; + + case PLPGSQL_PROMISE_TG_WHEN: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_BEFORE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "BEFORE"); + else if (TRIGGER_FIRED_AFTER(estate->trigdata->tg_event)) + assign_text_var(estate, var, "AFTER"); + else if (TRIGGER_FIRED_INSTEAD(estate->trigdata->tg_event)) + assign_text_var(estate, var, "INSTEAD OF"); + else + elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF"); + break; + + case PLPGSQL_PROMISE_TG_LEVEL: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_FOR_ROW(estate->trigdata->tg_event)) + assign_text_var(estate, var, "ROW"); + else if (TRIGGER_FIRED_FOR_STATEMENT(estate->trigdata->tg_event)) + assign_text_var(estate, var, "STATEMENT"); + else + elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT"); + break; + + case PLPGSQL_PROMISE_TG_OP: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_BY_INSERT(estate->trigdata->tg_event)) + assign_text_var(estate, var, "INSERT"); + else if (TRIGGER_FIRED_BY_UPDATE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "UPDATE"); + else if (TRIGGER_FIRED_BY_DELETE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "DELETE"); + else if (TRIGGER_FIRED_BY_TRUNCATE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "TRUNCATE"); + else + elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE"); + break; + + case PLPGSQL_PROMISE_TG_RELID: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + ObjectIdGetDatum(estate->trigdata->tg_relation->rd_id), + false, false); + break; + + case PLPGSQL_PROMISE_TG_TABLE_NAME: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(RelationGetRelationName(estate->trigdata->tg_relation))), + false, true); + break; + + case PLPGSQL_PROMISE_TG_TABLE_SCHEMA: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(get_namespace_name(RelationGetNamespace(estate->trigdata->tg_relation)))), + false, true); + break; + + case PLPGSQL_PROMISE_TG_NARGS: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + Int16GetDatum(estate->trigdata->tg_trigger->tgnargs), + false, false); + break; + + case PLPGSQL_PROMISE_TG_ARGV: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (estate->trigdata->tg_trigger->tgnargs > 0) + { + /* + * For historical reasons, tg_argv[] subscripts start at zero + * not one. So we can't use construct_array(). + */ + int nelems = estate->trigdata->tg_trigger->tgnargs; + Datum *elems; + int dims[1]; + int lbs[1]; + int i; + + elems = palloc(sizeof(Datum) * nelems); + for (i = 0; i < nelems; i++) + elems[i] = CStringGetTextDatum(estate->trigdata->tg_trigger->tgargs[i]); + dims[0] = nelems; + lbs[0] = 0; + + assign_simple_var(estate, var, + PointerGetDatum(construct_md_array(elems, NULL, + 1, dims, lbs, + TEXTOID, + -1, false, TYPALIGN_INT)), + false, true); + } + else + { + assign_simple_var(estate, var, (Datum) 0, true, false); + } + break; + + case PLPGSQL_PROMISE_TG_EVENT: + if (estate->evtrigdata == NULL) + elog(ERROR, "event trigger promise is not in an event trigger function"); + assign_text_var(estate, var, estate->evtrigdata->event); + break; + + case PLPGSQL_PROMISE_TG_TAG: + if (estate->evtrigdata == NULL) + elog(ERROR, "event trigger promise is not in an event trigger function"); + assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag)); + break; + + default: + elog(ERROR, "unrecognized promise type: %d", var->promise); + } + + MemoryContextSwitchTo(oldcontext); +} + + + +static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc) +{ + int natts = tupdesc->natts; + HeapTuple tuple; + Datum *dvalues; + bool *nulls; + int i; + + if (natts != row->nfields) + return NULL; + + dvalues = (Datum *) eval_mcontext_alloc0(estate, natts * sizeof(Datum)); + nulls = (bool *) eval_mcontext_alloc(estate, natts * sizeof(bool)); + + for (i = 0; i < natts; i++) + { + Oid fieldtypeid; + int32 fieldtypmod; + + if (TupleDescAttr(tupdesc, i)->attisdropped) + { + nulls[i] = true; /* leave the column as null */ + continue; + } + + exec_eval_datum(estate, estate->datums[row->varnos[i]], + &fieldtypeid, &fieldtypmod, + &dvalues[i], &nulls[i]); + if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid) + return NULL; + /* XXX should we insist on typmod match, too? */ + } + + tuple = heap_form_tuple(tupdesc, dvalues, nulls); + + return tuple; +} + +static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) +{ + assign_simple_var(estate, var, CStringGetTextDatum(str), false, true); +} + +static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, + Datum newvalue, bool isnull, bool freeable) +{ + Assert(var->dtype == PLPGSQL_DTYPE_VAR || + var->dtype == PLPGSQL_DTYPE_PROMISE); + + /* + * In non-atomic contexts, we do not want to store TOAST pointers in + * variables, because such pointers might become stale after a commit. + * Forcibly detoast in such cases. We don't want to detoast (flatten) + * expanded objects, however; those should be OK across a transaction + * boundary since they're just memory-resident objects. (Elsewhere in + * this module, operations on expanded records likewise need to request + * detoasting of record fields when !estate->atomic. Expanded arrays are + * not a problem since all array entries are always detoasted.) + */ + if (!estate->atomic && !isnull && var->datatype->typlen == -1 && + VARATT_IS_EXTERNAL_NON_EXPANDED(DatumGetPointer(newvalue))) + { + MemoryContext oldcxt; + Datum detoasted; + + /* + * Do the detoasting in the eval_mcontext to avoid long-term leakage + * of whatever memory toast fetching might leak. Then we have to copy + * the detoasted datum to the function's main context, which is a + * pain, but there's little choice. + */ + oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate)); + detoasted = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newvalue))); + MemoryContextSwitchTo(oldcxt); + /* Now's a good time to not leak the input value if it's freeable */ + if (freeable) + pfree(DatumGetPointer(newvalue)); + /* Once we copy the value, it's definitely freeable */ + newvalue = datumCopy(detoasted, false, -1); + freeable = true; + /* Can't clean up eval_mcontext here, but it'll happen before long */ + } + + /* Free the old value if needed */ + if (var->freeval) + { + if (DatumIsReadWriteExpandedObject(var->value, + var->isnull, + var->datatype->typlen)) + DeleteExpandedObject(var->value); + else + pfree(DatumGetPointer(var->value)); + } + /* Assign new value to datum */ + var->value = newvalue; + var->isnull = isnull; + var->freeval = freeable; + + /* + * If it's a promise variable, then either we just assigned the promised + * value, or the user explicitly assigned an overriding value. Either + * way, cancel the promise. + */ + var->promise = PLPGSQL_PROMISE_NONE; +} diff --git a/plpgsql_var.h b/plpgsql_var.h new file mode 100644 index 0000000..0f925a8 --- /dev/null +++ b/plpgsql_var.h @@ -0,0 +1,39 @@ +#ifndef PLVAR_H +#define PLVAR_H + +#include "postgres.h" + +#include "lib/stringinfo.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "globalbp.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/syscache.h" +#include "utils/lsyscache.h" +#include "utils/datum.h" +#include "utils/rel.h" +#include "miscadmin.h" +#include "funcapi.h" +#include "parser/parse_type.h" +#include "access/detoast.h" + +#define get_eval_mcontext(estate) \ + ((estate)->eval_econtext->ecxt_per_tuple_memory) +#define eval_mcontext_alloc(estate, sz) \ + MemoryContextAlloc(get_eval_mcontext(estate), sz) +#define eval_mcontext_alloc0(estate, sz) \ + MemoryContextAllocZero(get_eval_mcontext(estate), sz) + +#if INCLUDE_PACKAGE_SUPPORT +#include "spl.h" +#include "catalog/edb_variable.h" +#else +#include "plpgsql.h" +#endif + +extern void exec_eval_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeid, int32 *typetypmod, Datum *value, bool *isnull); + +extern char *convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype); + +#endif \ No newline at end of file From 977db8afc4d1ca1e3dd275022d6d28f6b1856819 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Thu, 29 Sep 2022 20:30:02 +0000 Subject: [PATCH 08/46] handle version for var output --- plpgsql_var.c | 1183 ++++++++++++++++++++++++++++++++++++++++++++++++- plpgsql_var.h | 4 + 2 files changed, 1185 insertions(+), 2 deletions(-) diff --git a/plpgsql_var.c b/plpgsql_var.c index fca38ec..738caed 100644 --- a/plpgsql_var.c +++ b/plpgsql_var.c @@ -1,10 +1,10 @@ #include "plpgsql_var.h" +#if (PG_VERSION_NUM >= 130000) + static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PLpgSQL_var *var); static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc); - static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str); - static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, Datum newvalue, bool isnull, bool freeable); void exec_eval_datum(PLpgSQL_execstate *estate, @@ -441,3 +441,1182 @@ static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, */ var->promise = PLPGSQL_PROMISE_NONE; } + +#elif (PG_VERSION_NUM >= 120000) + +//================================================================================ +// pl_exec.c functions +//================================================================================ + +/* + * exec_eval_datum Get current value of a PLpgSQL_datum + * + * The type oid, typmod, value in Datum format, and null flag are returned. + * + * At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums; + * that's not needed because we never pass references to such datums to SPI. + * + * NOTE: the returned Datum points right at the stored value in the case of + * pass-by-reference datatypes. Generally callers should take care not to + * modify the stored value. Some callers intentionally manipulate variables + * referenced by R/W expanded pointers, though; it is those callers' + * responsibility that the results are semantically OK. + * + * In some cases we have to palloc a return value, and in such cases we put + * it into the estate's eval_mcontext. + */ +static void +exec_eval_datum(PLpgSQL_execstate *estate, + PLpgSQL_datum *datum, + Oid *typeid, + int32 *typetypmod, + Datum *value, + bool *isnull) +{ + MemoryContext oldcontext; + + switch (datum->dtype) + { + case PLPGSQL_DTYPE_PROMISE: + /* fulfill promise if needed, then handle like regular var */ + plpgsql_fulfill_promise(estate, (PLpgSQL_var *) datum); + + /* FALL THRU */ + + case PLPGSQL_DTYPE_VAR: + { + PLpgSQL_var *var = (PLpgSQL_var *) datum; + + *typeid = var->datatype->typoid; + *typetypmod = var->datatype->atttypmod; + *value = var->value; + *isnull = var->isnull; + break; + } + + case PLPGSQL_DTYPE_ROW: + { + PLpgSQL_row *row = (PLpgSQL_row *) datum; + HeapTuple tup; + + /* We get here if there are multiple OUT parameters */ + if (!row->rowtupdesc) /* should not happen */ + elog(ERROR, "row variable has no tupdesc"); + /* Make sure we have a valid type/typmod setting */ + BlessTupleDesc(row->rowtupdesc); + oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); + tup = make_tuple_from_row(estate, row, row->rowtupdesc); + if (tup == NULL) /* should not happen */ + elog(ERROR, "row not compatible with its own tupdesc"); + *typeid = row->rowtupdesc->tdtypeid; + *typetypmod = row->rowtupdesc->tdtypmod; + *value = HeapTupleGetDatum(tup); + *isnull = false; + MemoryContextSwitchTo(oldcontext); + break; + } + + case PLPGSQL_DTYPE_REC: + { + PLpgSQL_rec *rec = (PLpgSQL_rec *) datum; + + if (rec->erh == NULL) + { + /* Treat uninstantiated record as a simple NULL */ + *value = (Datum) 0; + *isnull = true; + /* Report variable's declared type */ + *typeid = rec->rectypeid; + *typetypmod = -1; + } + else + { + if (ExpandedRecordIsEmpty(rec->erh)) + { + /* Empty record is also a NULL */ + *value = (Datum) 0; + *isnull = true; + } + else + { + *value = ExpandedRecordGetDatum(rec->erh); + *isnull = false; + } + if (rec->rectypeid != RECORDOID) + { + /* Report variable's declared type, if not RECORD */ + *typeid = rec->rectypeid; + *typetypmod = -1; + } + else + { + /* Report record's actual type if declared RECORD */ + *typeid = rec->erh->er_typeid; + *typetypmod = rec->erh->er_typmod; + } + } + break; + } + + case PLPGSQL_DTYPE_RECFIELD: + { + PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum; + PLpgSQL_rec *rec; + ExpandedRecordHeader *erh; + + rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]); + erh = rec->erh; + + /* + * If record variable is NULL, instantiate it if it has a + * named composite type, else complain. (This won't change + * the logical state of the record: it's still NULL.) + */ + if (erh == NULL) + { + instantiate_empty_record_variable(estate, rec); + erh = rec->erh; + } + + /* + * Look up the field's properties if we have not already, or + * if the tuple descriptor ID changed since last time. + */ + if (unlikely(recfield->rectupledescid != erh->er_tupdesc_id)) + { + if (!expanded_record_lookup_field(erh, + recfield->fieldname, + &recfield->finfo)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("record \"%s\" has no field \"%s\"", + rec->refname, recfield->fieldname))); + recfield->rectupledescid = erh->er_tupdesc_id; + } + + /* Report type data. */ + *typeid = recfield->finfo.ftypeid; + *typetypmod = recfield->finfo.ftypmod; + + /* And fetch the field value. */ + *value = expanded_record_get_field(erh, + recfield->finfo.fnumber, + isnull); + break; + } + + default: + elog(ERROR, "unrecognized dtype: %d", datum->dtype); + } +} + +/* ---------- + * convert_value_to_string Convert a non-null Datum to C string + * + * Note: the result is in the estate's eval_mcontext, and will be cleared + * by the next exec_eval_cleanup() call. The invoked output function might + * leave additional cruft there as well, so just pfree'ing the result string + * would not be enough to avoid memory leaks if we did not do it like this. + * In most usages the Datum being passed in is also in that context (if + * pass-by-reference) and so an exec_eval_cleanup() call is needed anyway. + * + * Note: not caching the conversion function lookup is bad for performance. + * However, this function isn't currently used in any places where an extra + * catalog lookup or two seems like a big deal. + * ---------- + */ +static char * +convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) +{ + char *result; + MemoryContext oldcontext; + Oid typoutput; + bool typIsVarlena; + + oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); + getTypeOutputInfo(valtype, &typoutput, &typIsVarlena); + result = OidOutputFunctionCall(typoutput, value); + MemoryContextSwitchTo(oldcontext); + + return result; +} + + +/* + * If the variable has an armed "promise", compute the promised value + * and assign it to the variable. + * The assignment automatically disarms the promise. + */ +static void +plpgsql_fulfill_promise(PLpgSQL_execstate *estate, + PLpgSQL_var *var) +{ + MemoryContext oldcontext; + + if (var->promise == PLPGSQL_PROMISE_NONE) + return; /* nothing to do */ + + /* + * This will typically be invoked in a short-lived context such as the + * mcontext. We must create variable values in the estate's datum + * context. This quick-and-dirty solution risks leaking some additional + * cruft there, but since any one promise is honored at most once per + * function call, it's probably not worth being more careful. + */ + oldcontext = MemoryContextSwitchTo(estate->datum_context); + + switch (var->promise) + { + case PLPGSQL_PROMISE_TG_NAME: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(estate->trigdata->tg_trigger->tgname)), + false, true); + break; + + case PLPGSQL_PROMISE_TG_WHEN: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_BEFORE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "BEFORE"); + else if (TRIGGER_FIRED_AFTER(estate->trigdata->tg_event)) + assign_text_var(estate, var, "AFTER"); + else if (TRIGGER_FIRED_INSTEAD(estate->trigdata->tg_event)) + assign_text_var(estate, var, "INSTEAD OF"); + else + elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF"); + break; + + case PLPGSQL_PROMISE_TG_LEVEL: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_FOR_ROW(estate->trigdata->tg_event)) + assign_text_var(estate, var, "ROW"); + else if (TRIGGER_FIRED_FOR_STATEMENT(estate->trigdata->tg_event)) + assign_text_var(estate, var, "STATEMENT"); + else + elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT"); + break; + + case PLPGSQL_PROMISE_TG_OP: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_BY_INSERT(estate->trigdata->tg_event)) + assign_text_var(estate, var, "INSERT"); + else if (TRIGGER_FIRED_BY_UPDATE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "UPDATE"); + else if (TRIGGER_FIRED_BY_DELETE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "DELETE"); + else if (TRIGGER_FIRED_BY_TRUNCATE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "TRUNCATE"); + else + elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE"); + break; + + case PLPGSQL_PROMISE_TG_RELID: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + ObjectIdGetDatum(estate->trigdata->tg_relation->rd_id), + false, false); + break; + + case PLPGSQL_PROMISE_TG_TABLE_NAME: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(RelationGetRelationName(estate->trigdata->tg_relation))), + false, true); + break; + + case PLPGSQL_PROMISE_TG_TABLE_SCHEMA: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(get_namespace_name(RelationGetNamespace(estate->trigdata->tg_relation)))), + false, true); + break; + + case PLPGSQL_PROMISE_TG_NARGS: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + Int16GetDatum(estate->trigdata->tg_trigger->tgnargs), + false, false); + break; + + case PLPGSQL_PROMISE_TG_ARGV: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (estate->trigdata->tg_trigger->tgnargs > 0) + { + /* + * For historical reasons, tg_argv[] subscripts start at zero + * not one. So we can't use construct_array(). + */ + int nelems = estate->trigdata->tg_trigger->tgnargs; + Datum *elems; + int dims[1]; + int lbs[1]; + int i; + + elems = palloc(sizeof(Datum) * nelems); + for (i = 0; i < nelems; i++) + elems[i] = CStringGetTextDatum(estate->trigdata->tg_trigger->tgargs[i]); + dims[0] = nelems; + lbs[0] = 0; + + assign_simple_var(estate, var, + PointerGetDatum(construct_md_array(elems, NULL, + 1, dims, lbs, + TEXTOID, + -1, false, 'i')), + false, true); + } + else + { + assign_simple_var(estate, var, (Datum) 0, true, false); + } + break; + + case PLPGSQL_PROMISE_TG_EVENT: + if (estate->evtrigdata == NULL) + elog(ERROR, "event trigger promise is not in an event trigger function"); + assign_text_var(estate, var, estate->evtrigdata->event); + break; + + case PLPGSQL_PROMISE_TG_TAG: + if (estate->evtrigdata == NULL) + elog(ERROR, "event trigger promise is not in an event trigger function"); + assign_text_var(estate, var, estate->evtrigdata->tag); + break; + + default: + elog(ERROR, "unrecognized promise type: %d", var->promise); + } + + MemoryContextSwitchTo(oldcontext); +} + + +/* ---------- + * make_tuple_from_row Make a tuple from the values of a row object + * + * A NULL return indicates rowtype mismatch; caller must raise suitable error + * + * The result tuple is freshly palloc'd in caller's context. Some junk + * may be left behind in eval_mcontext, too. + * ---------- + */ +static HeapTuple +make_tuple_from_row(PLpgSQL_execstate *estate, + PLpgSQL_row *row, + TupleDesc tupdesc) +{ + int natts = tupdesc->natts; + HeapTuple tuple; + Datum *dvalues; + bool *nulls; + int i; + + if (natts != row->nfields) + return NULL; + + dvalues = (Datum *) eval_mcontext_alloc0(estate, natts * sizeof(Datum)); + nulls = (bool *) eval_mcontext_alloc(estate, natts * sizeof(bool)); + + for (i = 0; i < natts; i++) + { + Oid fieldtypeid; + int32 fieldtypmod; + + if (TupleDescAttr(tupdesc, i)->attisdropped) + { + nulls[i] = true; /* leave the column as null */ + continue; + } + + exec_eval_datum(estate, estate->datums[row->varnos[i]], + &fieldtypeid, &fieldtypmod, + &dvalues[i], &nulls[i]); + if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid) + return NULL; + /* XXX should we insist on typmod match, too? */ + } + + tuple = heap_form_tuple(tupdesc, dvalues, nulls); + + return tuple; +} + +/* + * If we have not created an expanded record to hold the record variable's + * value, do so. The expanded record will be "empty", so this does not + * change the logical state of the record variable: it's still NULL. + * However, now we'll have a tupdesc with which we can e.g. look up fields. + */ +static void +instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) +{ + Assert(rec->erh == NULL); /* else caller error */ + + /* If declared type is RECORD, we can't instantiate */ + if (rec->rectypeid == RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("record \"%s\" is not assigned yet", rec->refname), + errdetail("The tuple structure of a not-yet-assigned record is indeterminate."))); + + /* Make sure rec->rectypeid is up-to-date before using it */ + revalidate_rectypeid(rec); + + /* OK, do it */ + rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1, + estate->datum_context); +} + + +/* + * Verify that a PLpgSQL_rec's rectypeid is up-to-date. + */ +static void +revalidate_rectypeid(PLpgSQL_rec *rec) +{ + PLpgSQL_type *typ = rec->datatype; + TypeCacheEntry *typentry; + + if (rec->rectypeid == RECORDOID) + return; /* it's RECORD, so nothing to do */ + Assert(typ != NULL); + if (typ->tcache && + typ->tcache->tupDesc_identifier == typ->tupdesc_id) + { + /* + * Although *typ is known up-to-date, it's possible that rectypeid + * isn't, because *rec is cloned during each function startup from a + * copy that we don't have a good way to update. Hence, forcibly fix + * rectypeid before returning. + */ + rec->rectypeid = typ->typoid; + return; + } + + /* + * typcache entry has suffered invalidation, so re-look-up the type name + * if possible, and then recheck the type OID. If we don't have a + * TypeName, then we just have to soldier on with the OID we've got. + */ + if (typ->origtypname != NULL) + { + /* this bit should match parse_datatype() in pl_gram.y */ + typenameTypeIdAndMod(NULL, typ->origtypname, + &typ->typoid, + &typ->atttypmod); + } + + /* this bit should match build_datatype() in pl_comp.c */ + typentry = lookup_type_cache(typ->typoid, + TYPECACHE_TUPDESC | + TYPECACHE_DOMAIN_BASE_INFO); + if (typentry->typtype == TYPTYPE_DOMAIN) + typentry = lookup_type_cache(typentry->domainBaseType, + TYPECACHE_TUPDESC); + if (typentry->tupDesc == NULL) + { + /* + * If we get here, user tried to replace a composite type with a + * non-composite one. We're not gonna support that. + */ + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(typ->typoid)))); + } + + /* + * Update tcache and tupdesc_id. Since we don't support changing to a + * non-composite type, none of the rest of *typ needs to change. + */ + typ->tcache = typentry; + typ->tupdesc_id = typentry->tupDesc_identifier; + + /* + * Update *rec, too. (We'll deal with subsidiary RECFIELDs as needed.) + */ + rec->rectypeid = typ->typoid; +} + +/* + * free old value of a text variable and assign new value from C string + */ +static void +assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) +{ + assign_simple_var(estate, var, CStringGetTextDatum(str), false, true); +} + + +/* + * assign_simple_var --- assign a new value to any VAR datum. + * + * This should be the only mechanism for assignment to simple variables, + * lest we do the release of the old value incorrectly (not to mention + * the detoasting business). + */ +static void +assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, + Datum newvalue, bool isnull, bool freeable) +{ + Assert(var->dtype == PLPGSQL_DTYPE_VAR || + var->dtype == PLPGSQL_DTYPE_PROMISE); + + /* + * In non-atomic contexts, we do not want to store TOAST pointers in + * variables, because such pointers might become stale after a commit. + * Forcibly detoast in such cases. We don't want to detoast (flatten) + * expanded objects, however; those should be OK across a transaction + * boundary since they're just memory-resident objects. (Elsewhere in + * this module, operations on expanded records likewise need to request + * detoasting of record fields when !estate->atomic. Expanded arrays are + * not a problem since all array entries are always detoasted.) + */ + if (!estate->atomic && !isnull && var->datatype->typlen == -1 && + VARATT_IS_EXTERNAL_NON_EXPANDED(DatumGetPointer(newvalue))) + { + MemoryContext oldcxt; + Datum detoasted; + + /* + * Do the detoasting in the eval_mcontext to avoid long-term leakage + * of whatever memory toast fetching might leak. Then we have to copy + * the detoasted datum to the function's main context, which is a + * pain, but there's little choice. + */ + oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate)); + detoasted = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newvalue))); + MemoryContextSwitchTo(oldcxt); + /* Now's a good time to not leak the input value if it's freeable */ + if (freeable) + pfree(DatumGetPointer(newvalue)); + /* Once we copy the value, it's definitely freeable */ + newvalue = datumCopy(detoasted, false, -1); + freeable = true; + /* Can't clean up eval_mcontext here, but it'll happen before long */ + } + + /* Free the old value if needed */ + if (var->freeval) + { + if (DatumIsReadWriteExpandedObject(var->value, + var->isnull, + var->datatype->typlen)) + DeleteExpandedObject(var->value); + else + pfree(DatumGetPointer(var->value)); + } + /* Assign new value to datum */ + var->value = newvalue; + var->isnull = isnull; + var->freeval = freeable; + + /* + * If it's a promise variable, then either we just assigned the promised + * value, or the user explicitly assigned an overriding value. Either + * way, cancel the promise. + */ + var->promise = PLPGSQL_PROMISE_NONE; +} + +#else + +//=========================================================================== +//Starts +//=========================================================================== + +/* + * exec_eval_datum Get current value of a PLpgSQL_datum + * + * The type oid, typmod, value in Datum format, and null flag are returned. + * + * At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums; + * that's not needed because we never pass references to such datums to SPI. + * + * NOTE: the returned Datum points right at the stored value in the case of + * pass-by-reference datatypes. Generally callers should take care not to + * modify the stored value. Some callers intentionally manipulate variables + * referenced by R/W expanded pointers, though; it is those callers' + * responsibility that the results are semantically OK. + * + * In some cases we have to palloc a return value, and in such cases we put + * it into the estate's eval_mcontext. + */ +static void +exec_eval_datum(PLpgSQL_execstate *estate, + PLpgSQL_datum *datum, + Oid *typeid, + int32 *typetypmod, + Datum *value, + bool *isnull) +{ + MemoryContext oldcontext; + + switch (datum->dtype) + { + case PLPGSQL_DTYPE_PROMISE: + /* fulfill promise if needed, then handle like regular var */ + plpgsql_fulfill_promise(estate, (PLpgSQL_var *) datum); + + /* FALL THRU */ + + case PLPGSQL_DTYPE_VAR: + { + PLpgSQL_var *var = (PLpgSQL_var *) datum; + + *typeid = var->datatype->typoid; + *typetypmod = var->datatype->atttypmod; + *value = var->value; + *isnull = var->isnull; + break; + } + + case PLPGSQL_DTYPE_ROW: + { + PLpgSQL_row *row = (PLpgSQL_row *) datum; + HeapTuple tup; + + /* We get here if there are multiple OUT parameters */ + if (!row->rowtupdesc) /* should not happen */ + elog(ERROR, "row variable has no tupdesc"); + /* Make sure we have a valid type/typmod setting */ + BlessTupleDesc(row->rowtupdesc); + oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); + tup = make_tuple_from_row(estate, row, row->rowtupdesc); + if (tup == NULL) /* should not happen */ + elog(ERROR, "row not compatible with its own tupdesc"); + *typeid = row->rowtupdesc->tdtypeid; + *typetypmod = row->rowtupdesc->tdtypmod; + *value = HeapTupleGetDatum(tup); + *isnull = false; + MemoryContextSwitchTo(oldcontext); + break; + } + + case PLPGSQL_DTYPE_REC: + { + PLpgSQL_rec *rec = (PLpgSQL_rec *) datum; + + if (rec->erh == NULL) + { + /* Treat uninstantiated record as a simple NULL */ + *value = (Datum) 0; + *isnull = true; + /* Report variable's declared type */ + *typeid = rec->rectypeid; + *typetypmod = -1; + } + else + { + if (ExpandedRecordIsEmpty(rec->erh)) + { + /* Empty record is also a NULL */ + *value = (Datum) 0; + *isnull = true; + } + else + { + *value = ExpandedRecordGetDatum(rec->erh); + *isnull = false; + } + if (rec->rectypeid != RECORDOID) + { + /* Report variable's declared type, if not RECORD */ + *typeid = rec->rectypeid; + *typetypmod = -1; + } + else + { + /* Report record's actual type if declared RECORD */ + *typeid = rec->erh->er_typeid; + *typetypmod = rec->erh->er_typmod; + } + } + break; + } + + case PLPGSQL_DTYPE_RECFIELD: + { + PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum; + PLpgSQL_rec *rec; + ExpandedRecordHeader *erh; + + rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]); + erh = rec->erh; + + /* + * If record variable is NULL, instantiate it if it has a + * named composite type, else complain. (This won't change + * the logical state of the record: it's still NULL.) + */ + if (erh == NULL) + { + instantiate_empty_record_variable(estate, rec); + erh = rec->erh; + } + + /* + * Look up the field's properties if we have not already, or + * if the tuple descriptor ID changed since last time. + */ + if (unlikely(recfield->rectupledescid != erh->er_tupdesc_id)) + { + if (!expanded_record_lookup_field(erh, + recfield->fieldname, + &recfield->finfo)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("record \"%s\" has no field \"%s\"", + rec->refname, recfield->fieldname))); + recfield->rectupledescid = erh->er_tupdesc_id; + } + + /* Report type data. */ + *typeid = recfield->finfo.ftypeid; + *typetypmod = recfield->finfo.ftypmod; + + /* And fetch the field value. */ + *value = expanded_record_get_field(erh, + recfield->finfo.fnumber, + isnull); + break; + } + + default: + elog(ERROR, "unrecognized dtype: %d", datum->dtype); + } +} + +/* + * If the variable has an armed "promise", compute the promised value + * and assign it to the variable. + * The assignment automatically disarms the promise. + */ +static void +plpgsql_fulfill_promise(PLpgSQL_execstate *estate, + PLpgSQL_var *var) +{ + MemoryContext oldcontext; + + if (var->promise == PLPGSQL_PROMISE_NONE) + return; /* nothing to do */ + + /* + * This will typically be invoked in a short-lived context such as the + * mcontext. We must create variable values in the estate's datum + * context. This quick-and-dirty solution risks leaking some additional + * cruft there, but since any one promise is honored at most once per + * function call, it's probably not worth being more careful. + */ + oldcontext = MemoryContextSwitchTo(estate->datum_context); + + switch (var->promise) + { + case PLPGSQL_PROMISE_TG_NAME: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(estate->trigdata->tg_trigger->tgname)), + false, true); + break; + + case PLPGSQL_PROMISE_TG_WHEN: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_BEFORE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "BEFORE"); + else if (TRIGGER_FIRED_AFTER(estate->trigdata->tg_event)) + assign_text_var(estate, var, "AFTER"); + else if (TRIGGER_FIRED_INSTEAD(estate->trigdata->tg_event)) + assign_text_var(estate, var, "INSTEAD OF"); + else + elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF"); + break; + + case PLPGSQL_PROMISE_TG_LEVEL: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_FOR_ROW(estate->trigdata->tg_event)) + assign_text_var(estate, var, "ROW"); + else if (TRIGGER_FIRED_FOR_STATEMENT(estate->trigdata->tg_event)) + assign_text_var(estate, var, "STATEMENT"); + else + elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT"); + break; + + case PLPGSQL_PROMISE_TG_OP: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (TRIGGER_FIRED_BY_INSERT(estate->trigdata->tg_event)) + assign_text_var(estate, var, "INSERT"); + else if (TRIGGER_FIRED_BY_UPDATE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "UPDATE"); + else if (TRIGGER_FIRED_BY_DELETE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "DELETE"); + else if (TRIGGER_FIRED_BY_TRUNCATE(estate->trigdata->tg_event)) + assign_text_var(estate, var, "TRUNCATE"); + else + elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE"); + break; + + case PLPGSQL_PROMISE_TG_RELID: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + ObjectIdGetDatum(estate->trigdata->tg_relation->rd_id), + false, false); + break; + + case PLPGSQL_PROMISE_TG_TABLE_NAME: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(RelationGetRelationName(estate->trigdata->tg_relation))), + false, true); + break; + + case PLPGSQL_PROMISE_TG_TABLE_SCHEMA: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + DirectFunctionCall1(namein, + CStringGetDatum(get_namespace_name(RelationGetNamespace(estate->trigdata->tg_relation)))), + false, true); + break; + + case PLPGSQL_PROMISE_TG_NARGS: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + assign_simple_var(estate, var, + Int16GetDatum(estate->trigdata->tg_trigger->tgnargs), + false, false); + break; + + case PLPGSQL_PROMISE_TG_ARGV: + if (estate->trigdata == NULL) + elog(ERROR, "trigger promise is not in a trigger function"); + if (estate->trigdata->tg_trigger->tgnargs > 0) + { + /* + * For historical reasons, tg_argv[] subscripts start at zero + * not one. So we can't use construct_array(). + */ + int nelems = estate->trigdata->tg_trigger->tgnargs; + Datum *elems; + int dims[1]; + int lbs[1]; + int i; + + elems = palloc(sizeof(Datum) * nelems); + for (i = 0; i < nelems; i++) + elems[i] = CStringGetTextDatum(estate->trigdata->tg_trigger->tgargs[i]); + dims[0] = nelems; + lbs[0] = 0; + + assign_simple_var(estate, var, + PointerGetDatum(construct_md_array(elems, NULL, + 1, dims, lbs, + TEXTOID, + -1, false, 'i')), + false, true); + } + else + { + assign_simple_var(estate, var, (Datum) 0, true, false); + } + break; + + case PLPGSQL_PROMISE_TG_EVENT: + if (estate->evtrigdata == NULL) + elog(ERROR, "event trigger promise is not in an event trigger function"); + assign_text_var(estate, var, estate->evtrigdata->event); + break; + + case PLPGSQL_PROMISE_TG_TAG: + if (estate->evtrigdata == NULL) + elog(ERROR, "event trigger promise is not in an event trigger function"); + assign_text_var(estate, var, estate->evtrigdata->tag); + break; + + default: + elog(ERROR, "unrecognized promise type: %d", var->promise); + } + + MemoryContextSwitchTo(oldcontext); +} + +/* ---------- + * convert_value_to_string Convert a non-null Datum to C string + * + * Note: the result is in the estate's eval_mcontext, and will be cleared + * by the next exec_eval_cleanup() call. The invoked output function might + * leave additional cruft there as well, so just pfree'ing the result string + * would not be enough to avoid memory leaks if we did not do it like this. + * In most usages the Datum being passed in is also in that context (if + * pass-by-reference) and so an exec_eval_cleanup() call is needed anyway. + * + * Note: not caching the conversion function lookup is bad for performance. + * However, this function isn't currently used in any places where an extra + * catalog lookup or two seems like a big deal. + * ---------- + */ +static char * +convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) +{ + char *result; + MemoryContext oldcontext; + Oid typoutput; + bool typIsVarlena; + + oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); + getTypeOutputInfo(valtype, &typoutput, &typIsVarlena); + result = OidOutputFunctionCall(typoutput, value); + MemoryContextSwitchTo(oldcontext); + + return result; +} + +/* + * Verify that a PLpgSQL_rec's rectypeid is up-to-date. + */ +static void +revalidate_rectypeid(PLpgSQL_rec *rec) +{ + PLpgSQL_type *typ = rec->datatype; + TypeCacheEntry *typentry; + + if (rec->rectypeid == RECORDOID) + return; /* it's RECORD, so nothing to do */ + Assert(typ != NULL); + if (typ->tcache && + typ->tcache->tupDesc_identifier == typ->tupdesc_id) + { + /* + * Although *typ is known up-to-date, it's possible that rectypeid + * isn't, because *rec is cloned during each function startup from a + * copy that we don't have a good way to update. Hence, forcibly fix + * rectypeid before returning. + */ + rec->rectypeid = typ->typoid; + return; + } + + /* + * typcache entry has suffered invalidation, so re-look-up the type name + * if possible, and then recheck the type OID. If we don't have a + * TypeName, then we just have to soldier on with the OID we've got. + */ + if (typ->origtypname != NULL) + { + /* this bit should match parse_datatype() in pl_gram.y */ + typenameTypeIdAndMod(NULL, typ->origtypname, + &typ->typoid, + &typ->atttypmod); + } + + /* this bit should match build_datatype() in pl_comp.c */ + typentry = lookup_type_cache(typ->typoid, + TYPECACHE_TUPDESC | + TYPECACHE_DOMAIN_BASE_INFO); + if (typentry->typtype == TYPTYPE_DOMAIN) + typentry = lookup_type_cache(typentry->domainBaseType, + TYPECACHE_TUPDESC); + if (typentry->tupDesc == NULL) + { + /* + * If we get here, user tried to replace a composite type with a + * non-composite one. We're not gonna support that. + */ + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("type %s is not composite", + format_type_be(typ->typoid)))); + } + + /* + * Update tcache and tupdesc_id. Since we don't support changing to a + * non-composite type, none of the rest of *typ needs to change. + */ + typ->tcache = typentry; + typ->tupdesc_id = typentry->tupDesc_identifier; + + /* + * Update *rec, too. (We'll deal with subsidiary RECFIELDs as needed.) + */ + rec->rectypeid = typ->typoid; +} + +/* + * If we have not created an expanded record to hold the record variable's + * value, do so. The expanded record will be "empty", so this does not + * change the logical state of the record variable: it's still NULL. + * However, now we'll have a tupdesc with which we can e.g. look up fields. + */ +static void +instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) +{ + Assert(rec->erh == NULL); /* else caller error */ + + /* If declared type is RECORD, we can't instantiate */ + if (rec->rectypeid == RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("record \"%s\" is not assigned yet", rec->refname), + errdetail("The tuple structure of a not-yet-assigned record is indeterminate."))); + + /* Make sure rec->rectypeid is up-to-date before using it */ + revalidate_rectypeid(rec); + + /* OK, do it */ + rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1, + estate->datum_context); +} + + +/* ---------- + * make_tuple_from_row Make a tuple from the values of a row object + * + * A NULL return indicates rowtype mismatch; caller must raise suitable error + * + * The result tuple is freshly palloc'd in caller's context. Some junk + * may be left behind in eval_mcontext, too. + * ---------- + */ +static HeapTuple +make_tuple_from_row(PLpgSQL_execstate *estate, + PLpgSQL_row *row, + TupleDesc tupdesc) +{ + int natts = tupdesc->natts; + HeapTuple tuple; + Datum *dvalues; + bool *nulls; + int i; + + if (natts != row->nfields) + return NULL; + + dvalues = (Datum *) eval_mcontext_alloc0(estate, natts * sizeof(Datum)); + nulls = (bool *) eval_mcontext_alloc(estate, natts * sizeof(bool)); + + for (i = 0; i < natts; i++) + { + Oid fieldtypeid; + int32 fieldtypmod; + + if (TupleDescAttr(tupdesc, i)->attisdropped) + { + nulls[i] = true; /* leave the column as null */ + continue; + } + + exec_eval_datum(estate, estate->datums[row->varnos[i]], + &fieldtypeid, &fieldtypmod, + &dvalues[i], &nulls[i]); + if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid) + return NULL; + /* XXX should we insist on typmod match, too? */ + } + + tuple = heap_form_tuple(tupdesc, dvalues, nulls); + + return tuple; +} + +/* + * free old value of a text variable and assign new value from C string + */ +static void +assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) +{ + assign_simple_var(estate, var, CStringGetTextDatum(str), false, true); +} + + +/* + * assign_simple_var --- assign a new value to any VAR datum. + * + * This should be the only mechanism for assignment to simple variables, + * lest we do the release of the old value incorrectly (not to mention + * the detoasting business). + */ +static void +assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, + Datum newvalue, bool isnull, bool freeable) +{ + Assert(var->dtype == PLPGSQL_DTYPE_VAR || + var->dtype == PLPGSQL_DTYPE_PROMISE); + + /* + * In non-atomic contexts, we do not want to store TOAST pointers in + * variables, because such pointers might become stale after a commit. + * Forcibly detoast in such cases. We don't want to detoast (flatten) + * expanded objects, however; those should be OK across a transaction + * boundary since they're just memory-resident objects. (Elsewhere in + * this module, operations on expanded records likewise need to request + * detoasting of record fields when !estate->atomic. Expanded arrays are + * not a problem since all array entries are always detoasted.) + */ + if (!estate->atomic && !isnull && var->datatype->typlen == -1 && + VARATT_IS_EXTERNAL_NON_EXPANDED(DatumGetPointer(newvalue))) + { + MemoryContext oldcxt; + Datum detoasted; + + /* + * Do the detoasting in the eval_mcontext to avoid long-term leakage + * of whatever memory toast fetching might leak. Then we have to copy + * the detoasted datum to the function's main context, which is a + * pain, but there's little choice. + */ + oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate)); + detoasted = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newvalue))); + MemoryContextSwitchTo(oldcxt); + /* Now's a good time to not leak the input value if it's freeable */ + if (freeable) + pfree(DatumGetPointer(newvalue)); + /* Once we copy the value, it's definitely freeable */ + newvalue = datumCopy(detoasted, false, -1); + freeable = true; + /* Can't clean up eval_mcontext here, but it'll happen before long */ + } + + /* Free the old value if needed */ + if (var->freeval) + { + if (DatumIsReadWriteExpandedObject(var->value, + var->isnull, + var->datatype->typlen)) + DeleteExpandedObject(var->value); + else + pfree(DatumGetPointer(var->value)); + } + /* Assign new value to datum */ + var->value = newvalue; + var->isnull = isnull; + var->freeval = freeable; + + /* + * If it's a promise variable, then either we just assigned the promised + * value, or the user explicitly assigned an overriding value. Either + * way, cancel the promise. + */ + var->promise = PLPGSQL_PROMISE_NONE; +} + +#endif + diff --git a/plpgsql_var.h b/plpgsql_var.h index 0f925a8..82336f8 100644 --- a/plpgsql_var.h +++ b/plpgsql_var.h @@ -16,7 +16,11 @@ #include "miscadmin.h" #include "funcapi.h" #include "parser/parse_type.h" +#if (PG_VERSION_NUM >= 130000) #include "access/detoast.h" +#elif (PG_VERSION_NUM >= 120000) +#include "access/tuptoaster.h" +#endif #define get_eval_mcontext(estate) \ ((estate)->eval_econtext->ecxt_per_tuple_memory) From 40f24a5ad010d62d56d92f7066bb9db321e57a1b Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Thu, 29 Sep 2022 21:52:04 +0000 Subject: [PATCH 09/46] v11 support --- plpgsql_debugger.c | 7 ++++++- plpgsql_var.c | 35 +++++++++++++++-------------------- plpgsql_var.h | 3 +++ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/plpgsql_debugger.c b/plpgsql_debugger.c index 666aaf4..66fea61 100644 --- a/plpgsql_debugger.c +++ b/plpgsql_debugger.c @@ -31,7 +31,6 @@ #include "miscadmin.h" #include "funcapi.h" #include "parser/parse_type.h" -#include "access/detoast.h" #if INCLUDE_PACKAGE_SUPPORT #include "spl.h" @@ -337,8 +336,11 @@ plpgsql_send_vars(ErrorContextCallback *frame) #if 0 case PLPGSQL_DTYPE_ROW: #endif +#if (PG_VERSION_NUM >= 120000) case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_RECFIELD: +#endif + #if (PG_VERSION_NUM < 140000) case PLPGSQL_DTYPE_ARRAYELEM: #endif @@ -359,6 +361,9 @@ plpgsql_send_vars(ErrorContextCallback *frame) isNull? NULL_DATUM : convert_value_to_string(estate, value, typeid)); break; } + default: { + break; + } } } } diff --git a/plpgsql_var.c b/plpgsql_var.c index 738caed..548d635 100644 --- a/plpgsql_var.c +++ b/plpgsql_var.c @@ -1,5 +1,6 @@ #include "plpgsql_var.h" + #if (PG_VERSION_NUM >= 130000) static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PLpgSQL_var *var); @@ -1033,9 +1034,12 @@ assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, #else -//=========================================================================== -//Starts -//=========================================================================== +static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PLpgSQL_var *var); +static void revalidate_rectypeid(PLpgSQL_rec *rec); +static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec); +static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc); +static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str); +static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, Datum newvalue, bool isnull, bool freeable); /* * exec_eval_datum Get current value of a PLpgSQL_datum @@ -1054,8 +1058,7 @@ assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, * In some cases we have to palloc a return value, and in such cases we put * it into the estate's eval_mcontext. */ -static void -exec_eval_datum(PLpgSQL_execstate *estate, +void exec_eval_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeid, int32 *typetypmod, @@ -1204,8 +1207,7 @@ exec_eval_datum(PLpgSQL_execstate *estate, * and assign it to the variable. * The assignment automatically disarms the promise. */ -static void -plpgsql_fulfill_promise(PLpgSQL_execstate *estate, +static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PLpgSQL_var *var) { MemoryContext oldcontext; @@ -1374,8 +1376,7 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate, * catalog lookup or two seems like a big deal. * ---------- */ -static char * -convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) +char * convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) { char *result; MemoryContext oldcontext; @@ -1393,8 +1394,7 @@ convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) /* * Verify that a PLpgSQL_rec's rectypeid is up-to-date. */ -static void -revalidate_rectypeid(PLpgSQL_rec *rec) +static void revalidate_rectypeid(PLpgSQL_rec *rec) { PLpgSQL_type *typ = rec->datatype; TypeCacheEntry *typentry; @@ -1466,8 +1466,7 @@ revalidate_rectypeid(PLpgSQL_rec *rec) * change the logical state of the record variable: it's still NULL. * However, now we'll have a tupdesc with which we can e.g. look up fields. */ -static void -instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) +static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) { Assert(rec->erh == NULL); /* else caller error */ @@ -1486,7 +1485,6 @@ instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) estate->datum_context); } - /* ---------- * make_tuple_from_row Make a tuple from the values of a row object * @@ -1496,8 +1494,7 @@ instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) * may be left behind in eval_mcontext, too. * ---------- */ -static HeapTuple -make_tuple_from_row(PLpgSQL_execstate *estate, +static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc) { @@ -1540,8 +1537,7 @@ make_tuple_from_row(PLpgSQL_execstate *estate, /* * free old value of a text variable and assign new value from C string */ -static void -assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) +static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) { assign_simple_var(estate, var, CStringGetTextDatum(str), false, true); } @@ -1554,8 +1550,7 @@ assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) * lest we do the release of the old value incorrectly (not to mention * the detoasting business). */ -static void -assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, +static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, Datum newvalue, bool isnull, bool freeable) { Assert(var->dtype == PLPGSQL_DTYPE_VAR || diff --git a/plpgsql_var.h b/plpgsql_var.h index 82336f8..83fa9c4 100644 --- a/plpgsql_var.h +++ b/plpgsql_var.h @@ -20,6 +20,9 @@ #include "access/detoast.h" #elif (PG_VERSION_NUM >= 120000) #include "access/tuptoaster.h" +#else +#include "access/tuptoaster.h" +#include "access/htup_details.h" #endif #define get_eval_mcontext(estate) \ From 159fc4056cc63aa01d9f9bc8b3277b3f328fa595 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Thu, 29 Sep 2022 23:32:10 +0000 Subject: [PATCH 10/46] Disable RECORD for pg 12 --- plpgsql_debugger.c | 2 +- plpgsql_var.c | 33 +++++++++++++++------------------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/plpgsql_debugger.c b/plpgsql_debugger.c index 66fea61..e4eede1 100644 --- a/plpgsql_debugger.c +++ b/plpgsql_debugger.c @@ -336,7 +336,7 @@ plpgsql_send_vars(ErrorContextCallback *frame) #if 0 case PLPGSQL_DTYPE_ROW: #endif -#if (PG_VERSION_NUM >= 120000) +#if (PG_VERSION_NUM >= 130000) case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_RECFIELD: #endif diff --git a/plpgsql_var.c b/plpgsql_var.c index 548d635..0701a0b 100644 --- a/plpgsql_var.c +++ b/plpgsql_var.c @@ -445,9 +445,14 @@ static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, #elif (PG_VERSION_NUM >= 120000) -//================================================================================ -// pl_exec.c functions -//================================================================================ +static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PLpgSQL_var *var); +static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, PLpgSQL_var *var); +static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc); +static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec); +static void revalidate_rectypeid(PLpgSQL_rec *rec); +static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str); +static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, Datum newvalue, bool isnull, bool freeable); + /* * exec_eval_datum Get current value of a PLpgSQL_datum @@ -466,8 +471,7 @@ static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, * In some cases we have to palloc a return value, and in such cases we put * it into the estate's eval_mcontext. */ -static void -exec_eval_datum(PLpgSQL_execstate *estate, +void exec_eval_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeid, int32 *typetypmod, @@ -626,8 +630,7 @@ exec_eval_datum(PLpgSQL_execstate *estate, * catalog lookup or two seems like a big deal. * ---------- */ -static char * -convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) +char * convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) { char *result; MemoryContext oldcontext; @@ -803,7 +806,6 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate, MemoryContextSwitchTo(oldcontext); } - /* ---------- * make_tuple_from_row Make a tuple from the values of a row object * @@ -813,8 +815,7 @@ plpgsql_fulfill_promise(PLpgSQL_execstate *estate, * may be left behind in eval_mcontext, too. * ---------- */ -static HeapTuple -make_tuple_from_row(PLpgSQL_execstate *estate, +static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc) { @@ -860,8 +861,7 @@ make_tuple_from_row(PLpgSQL_execstate *estate, * change the logical state of the record variable: it's still NULL. * However, now we'll have a tupdesc with which we can e.g. look up fields. */ -static void -instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) +static void instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) { Assert(rec->erh == NULL); /* else caller error */ @@ -884,8 +884,7 @@ instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec) /* * Verify that a PLpgSQL_rec's rectypeid is up-to-date. */ -static void -revalidate_rectypeid(PLpgSQL_rec *rec) +static void revalidate_rectypeid(PLpgSQL_rec *rec) { PLpgSQL_type *typ = rec->datatype; TypeCacheEntry *typentry; @@ -954,8 +953,7 @@ revalidate_rectypeid(PLpgSQL_rec *rec) /* * free old value of a text variable and assign new value from C string */ -static void -assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) +static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) { assign_simple_var(estate, var, CStringGetTextDatum(str), false, true); } @@ -968,8 +966,7 @@ assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) * lest we do the release of the old value incorrectly (not to mention * the detoasting business). */ -static void -assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, +static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, Datum newvalue, bool isnull, bool freeable) { Assert(var->dtype == PLPGSQL_DTYPE_VAR || From 9a49b3b12740dee7993feeede125a39b2b6d5f10 Mon Sep 17 00:00:00 2001 From: Constantine Grischenko <98530452+cvas71@users.noreply.github.com> Date: Fri, 30 Sep 2022 16:31:29 +0300 Subject: [PATCH 11/46] Update plpgsql_debugger.c If is field of record then output full name with name variable of the record. --- plpgsql_debugger.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plpgsql_debugger.c b/plpgsql_debugger.c index e4eede1..b237cab 100644 --- a/plpgsql_debugger.c +++ b/plpgsql_debugger.c @@ -351,7 +351,11 @@ plpgsql_send_vars(ErrorContextCallback *frame) exec_eval_datum(estate, datum, &typeid, &typetypmod, &value, &isNull); dbg_send("%s:%c:%d:%c:%c:%c:%d:%s", - variable->refname, + //variable->refname, + //If is field of record then output full name with name variable of the record. + (estate->datums[i]->dtype == PLPGSQL_DTYPE_RECFIELD) ? + psprintf("%s.%s", ((PLpgSQL_rec *) (estate->datums[((PLpgSQL_recfield *) datum)->recparentno]))->refname, variable->refname) : + variable->refname, isArg ? 'A' : 'L', variable->lineno, dbg_info->symbols[i].duplicate_name ? 'f' : 't', From 4cce12423aecc1eaa190b36c4e3ed4ea0d0a0308 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Sun, 2 Oct 2022 15:56:54 +0000 Subject: [PATCH 12/46] print json on record types --- plpgsql_debugger.c | 32 ++++++++---- plpgsql_var.c | 123 +++++++++++++++++---------------------------- plpgsql_var.h | 7 +-- 3 files changed, 73 insertions(+), 89 deletions(-) diff --git a/plpgsql_debugger.c b/plpgsql_debugger.c index e4eede1..f92f08b 100644 --- a/plpgsql_debugger.c +++ b/plpgsql_debugger.c @@ -310,24 +310,28 @@ plpgsql_send_vars(ErrorContextCallback *frame) PLpgSQL_datum *datum; PLpgSQL_variable *variable; - Datum value; + Datum datum_value; Oid typeid; int32 typetypmod; bool isNull; bool isArg; int i; + StringInfo stringInfo; + PLpgSQL_datum_type datumType; - PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; - dbg_ctx * dbg_info = (dbg_ctx *) estate->plugin_info; + PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; + dbg_ctx *dbg_info = (dbg_ctx *) estate->plugin_info; for( i = 0; i < estate->ndatums; i++ ) { if( is_var_visible( estate, i )) { - datum = (PLpgSQL_datum*) estate->datums[i]; - variable = (PLpgSQL_variable*) estate->datums[i]; - isArg = dbg_info->func->fn_nargs > 0 && i < dbg_info->func->fn_nargs; - switch( estate->datums[i]->dtype ) + isNull = true; + datum = (PLpgSQL_datum*) estate->datums[i]; + variable = (PLpgSQL_variable*) estate->datums[i]; + isArg = dbg_info->func->fn_nargs > 0 && i < dbg_info->func->fn_nargs; + datumType = estate->datums[i]->dtype; + switch( datumType ) { #if (PG_VERSION_NUM >= 110000) case PLPGSQL_DTYPE_PROMISE: @@ -348,8 +352,17 @@ plpgsql_send_vars(ErrorContextCallback *frame) case PLPGSQL_DTYPE_EXPR: #endif { - exec_eval_datum(estate, datum, &typeid, &typetypmod, &value, &isNull); + exec_eval_datum(estate, datum, &typeid, &typetypmod, &datum_value, &isNull); + stringInfo = makeStringInfo(); + if (isNull) + { + appendStringInfoString(stringInfo, NULL_DATUM); + } + else + { + print_datum(stringInfo, estate, datum_value, typeid); + } dbg_send("%s:%c:%d:%c:%c:%c:%d:%s", variable->refname, isArg ? 'A' : 'L', @@ -358,7 +371,8 @@ plpgsql_send_vars(ErrorContextCallback *frame) variable->isconst ? 't':'f', variable->notnull ? 't':'f', typeid, - isNull? NULL_DATUM : convert_value_to_string(estate, value, typeid)); + stringInfo->data); + pfree(stringInfo); break; } default: { diff --git a/plpgsql_var.c b/plpgsql_var.c index 0701a0b..f46f2f8 100644 --- a/plpgsql_var.c +++ b/plpgsql_var.c @@ -1,5 +1,51 @@ #include "plpgsql_var.h" +//static void print_other(StringInfo stringInfo, PLpgSQL_execstate *estate, Datum value, Oid oid); + +static void spi_call(const char* call, StringInfo stringInfo, Datum value, Oid oid); + +static bool mustConvertToJSONB(Oid oid); + +void print_datum(StringInfo stringInfo, PLpgSQL_execstate *estate, Datum datumValue, Oid oid) +{ + if ( mustConvertToJSONB(oid) ) + { + spi_call("SELECT to_jsonb($1)::TEXT;", stringInfo, datumValue, oid); + } + else + { + spi_call("SELECT $1::TEXT;", stringInfo, datumValue, oid); + } +} + +static bool mustConvertToJSONB(Oid oid) +{ + HeapTuple typeTup; + Form_pg_type typeStruct; + typeTup = SearchSysCache(TYPEOID, ObjectIdGetDatum( oid ), 0, 0, 0); + if(!HeapTupleIsValid(typeTup)) + { + return false; + } + typeStruct = (Form_pg_type)GETSTRUCT( typeTup ); + return strcmp(NameStr(typeStruct->typname), "record") == 0; +} + +static void spi_call(const char* call, StringInfo stringInfo, Datum value, Oid oid) { + int ret; + Oid argsTypes[1] = {oid}; + Datum argsValues[1] = {value}; + SPI_connect(); + ret = SPI_execute_with_args(call, 1, argsTypes, argsValues, NULL, true, 1); + if (ret > 0 && SPI_tuptable != NULL) + { + SPITupleTable *tuptable = SPI_tuptable; + TupleDesc tupdesc = tuptable->tupdesc; + HeapTuple tuple = tuptable->vals[0]; + appendStringInfoString(stringInfo, SPI_getvalue(tuple, tupdesc, 1)); + } + SPI_finish(); +} #if (PG_VERSION_NUM >= 130000) @@ -161,20 +207,6 @@ void exec_eval_datum(PLpgSQL_execstate *estate, } } -char * convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) -{ - char *result; - MemoryContext oldcontext; - Oid typoutput; - bool typIsVarlena; - - oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); - getTypeOutputInfo(valtype, &typoutput, &typIsVarlena); - result = OidOutputFunctionCall(typoutput, value); - MemoryContextSwitchTo(oldcontext); - - return result; -} /* * If the variable has an armed "promise", compute the promised value @@ -335,8 +367,6 @@ static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, MemoryContextSwitchTo(oldcontext); } - - static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate, PLpgSQL_row *row, TupleDesc tupdesc) { int natts = tupdesc->natts; @@ -615,37 +645,6 @@ void exec_eval_datum(PLpgSQL_execstate *estate, } } -/* ---------- - * convert_value_to_string Convert a non-null Datum to C string - * - * Note: the result is in the estate's eval_mcontext, and will be cleared - * by the next exec_eval_cleanup() call. The invoked output function might - * leave additional cruft there as well, so just pfree'ing the result string - * would not be enough to avoid memory leaks if we did not do it like this. - * In most usages the Datum being passed in is also in that context (if - * pass-by-reference) and so an exec_eval_cleanup() call is needed anyway. - * - * Note: not caching the conversion function lookup is bad for performance. - * However, this function isn't currently used in any places where an extra - * catalog lookup or two seems like a big deal. - * ---------- - */ -char * convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) -{ - char *result; - MemoryContext oldcontext; - Oid typoutput; - bool typIsVarlena; - - oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); - getTypeOutputInfo(valtype, &typoutput, &typIsVarlena); - result = OidOutputFunctionCall(typoutput, value); - MemoryContextSwitchTo(oldcontext); - - return result; -} - - /* * If the variable has an armed "promise", compute the promised value * and assign it to the variable. @@ -1358,36 +1357,6 @@ static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate, MemoryContextSwitchTo(oldcontext); } -/* ---------- - * convert_value_to_string Convert a non-null Datum to C string - * - * Note: the result is in the estate's eval_mcontext, and will be cleared - * by the next exec_eval_cleanup() call. The invoked output function might - * leave additional cruft there as well, so just pfree'ing the result string - * would not be enough to avoid memory leaks if we did not do it like this. - * In most usages the Datum being passed in is also in that context (if - * pass-by-reference) and so an exec_eval_cleanup() call is needed anyway. - * - * Note: not caching the conversion function lookup is bad for performance. - * However, this function isn't currently used in any places where an extra - * catalog lookup or two seems like a big deal. - * ---------- - */ -char * convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) -{ - char *result; - MemoryContext oldcontext; - Oid typoutput; - bool typIsVarlena; - - oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); - getTypeOutputInfo(valtype, &typoutput, &typIsVarlena); - result = OidOutputFunctionCall(typoutput, value); - MemoryContextSwitchTo(oldcontext); - - return result; -} - /* * Verify that a PLpgSQL_rec's rectypeid is up-to-date. */ diff --git a/plpgsql_var.h b/plpgsql_var.h index 83fa9c4..dab1856 100644 --- a/plpgsql_var.h +++ b/plpgsql_var.h @@ -15,7 +15,8 @@ #include "utils/rel.h" #include "miscadmin.h" #include "funcapi.h" -#include "parser/parse_type.h" +#include "executor/spi.h" + #if (PG_VERSION_NUM >= 130000) #include "access/detoast.h" #elif (PG_VERSION_NUM >= 120000) @@ -39,8 +40,8 @@ #include "plpgsql.h" #endif -extern void exec_eval_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeid, int32 *typetypmod, Datum *value, bool *isnull); +extern void exec_eval_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeid, int32 *typetypmod, Datum *value, bool *isnull); -extern char *convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype); +extern void print_datum(StringInfo stringInfo, PLpgSQL_execstate *estate, Datum datumValue, Oid oid); #endif \ No newline at end of file From ccecddc385d386d265cce60df78c1afab9412c51 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Sun, 2 Oct 2022 19:36:25 +0000 Subject: [PATCH 13/46] missing include --- plpgsql_var.h | 1 + 1 file changed, 1 insertion(+) diff --git a/plpgsql_var.h b/plpgsql_var.h index dab1856..ebc7eff 100644 --- a/plpgsql_var.h +++ b/plpgsql_var.h @@ -16,6 +16,7 @@ #include "miscadmin.h" #include "funcapi.h" #include "executor/spi.h" +#include "parser/parse_type.h" #if (PG_VERSION_NUM >= 130000) #include "access/detoast.h" From 697879b0f50391feddab2b461cfd4489718f17e1 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer Date: Mon, 3 Oct 2022 21:00:03 +0000 Subject: [PATCH 14/46] print recfield name --- .gitignore | 3 ++- plpgsql_debugger.c | 39 +++++++++++++++++++++++++++------------ plpgsql_var.c | 2 -- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 44b4a95..05eb4e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.o *.bc *.so -.vscode \ No newline at end of file +.vscode +vcpkg diff --git a/plpgsql_debugger.c b/plpgsql_debugger.c index 0094c58..c8b4664 100644 --- a/plpgsql_debugger.c +++ b/plpgsql_debugger.c @@ -316,7 +316,8 @@ plpgsql_send_vars(ErrorContextCallback *frame) bool isNull; bool isArg; int i; - StringInfo stringInfo; + StringInfo nameString; + StringInfo valueString; PLpgSQL_datum_type datumType; PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; @@ -353,30 +354,44 @@ plpgsql_send_vars(ErrorContextCallback *frame) #endif { - exec_eval_datum(estate, datum, &typeid, &typetypmod, &datum_value, &isNull); - stringInfo = makeStringInfo(); + exec_eval_datum(estate, datum, &typeid, &typetypmod, &datum_value, &isNull); + + valueString = makeStringInfo(); + nameString = makeStringInfo(); + if (isNull) { - appendStringInfoString(stringInfo, NULL_DATUM); + appendStringInfoString(valueString, NULL_DATUM); } else { - print_datum(stringInfo, estate, datum_value, typeid); + print_datum(valueString, estate, datum_value, typeid); } + + if (datumType == PLPGSQL_DTYPE_RECFIELD) + { + appendStringInfo(nameString, "%s.%s", + ((PLpgSQL_rec *) (estate->datums[((PLpgSQL_recfield *) datum)->recparentno]))->refname, + variable->refname); + } + else + { + appendStringInfoString(nameString, variable->refname); + } + dbg_send("%s:%c:%d:%c:%c:%c:%d:%s", - //variable->refname, - //If is field of record then output full name with name variable of the record. - (estate->datums[i]->dtype == PLPGSQL_DTYPE_RECFIELD) ? - psprintf("%s.%s", ((PLpgSQL_rec *) (estate->datums[((PLpgSQL_recfield *) datum)->recparentno]))->refname, variable->refname) : - variable->refname, + nameString->data, isArg ? 'A' : 'L', variable->lineno, dbg_info->symbols[i].duplicate_name ? 'f' : 't', variable->isconst ? 't':'f', variable->notnull ? 't':'f', typeid, - stringInfo->data); - pfree(stringInfo); + valueString->data); + + pfree(valueString); + pfree(nameString); + break; } default: { diff --git a/plpgsql_var.c b/plpgsql_var.c index f46f2f8..a827fde 100644 --- a/plpgsql_var.c +++ b/plpgsql_var.c @@ -1,7 +1,5 @@ #include "plpgsql_var.h" -//static void print_other(StringInfo stringInfo, PLpgSQL_execstate *estate, Datum value, Oid oid); - static void spi_call(const char* call, StringInfo stringInfo, Datum value, Oid oid); static bool mustConvertToJSONB(Oid oid); From 6fba1397f15788fbb68fd376fd9025cf7a32ed75 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 14:49:40 +0200 Subject: [PATCH 15/46] Add Action to build images --- .github/workflows/docker-publish.yaml | 42 +++++++++++++++++++++++ docker/Dockerfile | 33 ++++++++++++++++++ docker/README.md | 49 +++++++++++++++++++++++++++ docker/config.sh | 3 ++ 4 files changed, 127 insertions(+) create mode 100644 .github/workflows/docker-publish.yaml create mode 100644 docker/Dockerfile create mode 100644 docker/README.md create mode 100644 docker/config.sh diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml new file mode 100644 index 0000000..ed153ec --- /dev/null +++ b/.github/workflows/docker-publish.yaml @@ -0,0 +1,42 @@ +name: Build and Push Multiple Docker Images + +on: + workflow_dispatch: + inputs: + PG_VERSION: + description: 'PostgreSQL version (13, 14, 15, etc.)' + required: true + PG_PLATFORM: + description: 'PostgreSQL platform (amd64 or arm64)' + required: true + +env: + PG_IMAGE: postgres-debugger + +jobs: + build-and-push: + runs-on: ubuntu-latest + strategy: + matrix: + platform: [amd64, arm64] + version: [13, 14, 15, 16] + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Go to Dockerfile directory + run: cd docker + + - name: Build Docker image + run: docker build --platform "linux/${{ matrix.platform }}" \ + --build-arg "TAG=${{ matrix.version }}" \ + --tag ${{ secrets.DOCKER_USERNAME }}/$PG_IMAGE:${{ matrix.version }}-${{ matrix.platform }} . + + - name: Push Docker image to docker hub + run: docker push ${{ secrets.DOCKER_USERNAME }}/$PG_IMAGE:${{ matrix.version }}-${{ matrix.platform }} diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..311f77c --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,33 @@ +# Filename: Dockerfile + +#POSTGRES IMAGE +ARG TAG=latest +FROM postgres:${TAG} + +# SET ENV +ARG TAG +ENV PG_LIB=postgresql-server-dev-${TAG} +ENV PG_BRANCH=REL_${TAG}_STABLE +ENV PLUGIN_BRANCH=print-vars + +# APT +RUN apt --yes update \ + && apt --yes upgrade \ + && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex $PG_LIB \ + && cd /usr/src/ \ + && git clone -b $PG_BRANCH --single-branch https://github.com/postgres/postgres.git \ + && cd postgres \ + && ./configure \ + && cd /usr/src/postgres/contrib \ + && git clone -b $PLUGIN_BRANCH --single-branch https://github.com/ng-galien/pldebugger.git \ + && cd pldebugger \ + && make clean && make USE_PGXS=1 && make USE_PGXS=1 install \ + && rm -r /usr/src/postgres \ + && apt --yes remove --purge git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex $PG_LIB \ + && apt --yes autoremove \ + && apt --yes clean + +# CONFIG +COPY *.sql /docker-entrypoint-initdb.d/ +COPY *.sh /docker-entrypoint-initdb.d/ +RUN chmod a+r /docker-entrypoint-initdb.d/* diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..2103854 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,49 @@ +# Docker Image + +## Build the image + +Leave empty if you don't want to push the image to docker hub + +### Build the image for a specific platform + +> change PG_PLATFORM to amd64 or arm64 + +```bash +export PG_PLATFORM=arm64 && PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ +&& docker build --platform "linux/$PG_PLATFORM" --build-arg "TAG=$PG_VERSION" -t "$DOCKER_USER$PG_IMAGE:$PG_VERSION-$PG_PLATFORM" . +``` + +### Push the image to docker hub + +```bash +export PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ +&& docker push "$DOCKER_USER$PG_IMAGE:$PG_VERSION-amd64" \ +&& docker push "$DOCKER_USER$PG_IMAGE:$PG_VERSION-arm64" +``` + +## Manifest + +### Create the manifest + +```bash +export PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ +&& docker manifest create "$DOCKER_USER$PG_IMAGE:$PG_VERSION" \ +--amend "$DOCKER_USER$PG_IMAGE:$PG_VERSION-amd64" \ +--amend "$DOCKER_USER$PG_IMAGE:$PG_VERSION-arm64" +``` + +### Push the manifest + +```bash +export PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ +&& docker manifest push "$DOCKER_USER$PG_IMAGE:$PG_VERSION" +``` + + +## Run the image + +```bash +export PG_VERSION=14 \ +&& export PG_IMAGE=postgres-with-debugger \ +&& docker run -p 55$PG_VERSION:5432 --name "PostgresSQL-$PG_VERSION-debug" -e POSTGRES_PASSWORD=postgres -d "$PG_IMAGE:$PG_VERSION" +``` diff --git a/docker/config.sh b/docker/config.sh new file mode 100644 index 0000000..ff9604a --- /dev/null +++ b/docker/config.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +sed -i "s/#shared_preload_libraries = ''/shared_preload_libraries = 'plugin_debugger'/g" /var/lib/postgresql/data/postgresql.conf \ No newline at end of file From 6f2f92bf0b177243826d70babc14235657d47581 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 14:52:21 +0200 Subject: [PATCH 16/46] Checkout print-vars --- .github/workflows/docker-publish.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index ed153ec..2211e83 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -23,6 +23,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + with: + ref: print-vars - name: Log in to Docker Hub uses: docker/login-action@v2 From 4f2d55c3a5cbeb18f06dc988a703f43689380e31 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 15:27:03 +0200 Subject: [PATCH 17/46] Remove inputs --- .github/workflows/docker-publish.yaml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 2211e83..2cae8ce 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -2,13 +2,6 @@ name: Build and Push Multiple Docker Images on: workflow_dispatch: - inputs: - PG_VERSION: - description: 'PostgreSQL version (13, 14, 15, etc.)' - required: true - PG_PLATFORM: - description: 'PostgreSQL platform (amd64 or arm64)' - required: true env: PG_IMAGE: postgres-debugger @@ -19,7 +12,7 @@ jobs: strategy: matrix: platform: [amd64, arm64] - version: [13, 14, 15, 16] + version: [13, 14, 15] steps: - name: Checkout code uses: actions/checkout@v3 From 8170d8d09cefe3ae2df0d85f5f05400e4a79cde3 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 17:31:43 +0200 Subject: [PATCH 18/46] Use of buildx --- .github/workflows/docker-publish.yaml | 43 ++++++++++++++------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 2cae8ce..80f3739 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -11,27 +11,30 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - platform: [amd64, arm64] - version: [13, 14, 15] + version: [11, 12, 13, 14, 15] + platform: + - linux/amd64 + - linux/arm64 steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - ref: print-vars + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: print-vars - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 - - name: Go to Dockerfile directory - run: cd docker + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build Docker image - run: docker build --platform "linux/${{ matrix.platform }}" \ - --build-arg "TAG=${{ matrix.version }}" \ - --tag ${{ secrets.DOCKER_USERNAME }}/$PG_IMAGE:${{ matrix.version }}-${{ matrix.platform }} . - - - name: Push Docker image to docker hub - run: docker push ${{ secrets.DOCKER_USERNAME }}/$PG_IMAGE:${{ matrix.version }}-${{ matrix.platform }} + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: docker + platforms: ${{ matrix.platform }} + build-args: PG_VERSION=${{ matrix.version }} + push: true + tags: ${{ env.PG_IMAGE }}:${{ matrix.version }} \ No newline at end of file From 0c92a19c703d25045e7cf7cef83ead50784f78e2 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 17:35:28 +0200 Subject: [PATCH 19/46] Fix TAG --- .github/workflows/docker-publish.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 80f3739..13ec9a3 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -35,6 +35,6 @@ jobs: with: context: docker platforms: ${{ matrix.platform }} - build-args: PG_VERSION=${{ matrix.version }} + build-args: TAG=${{ matrix.version }} push: true - tags: ${{ env.PG_IMAGE }}:${{ matrix.version }} \ No newline at end of file + tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} \ No newline at end of file From b4868c8d8662009676cb111355812e0b486e057b Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 17:47:37 +0200 Subject: [PATCH 20/46] Add setup step --- .github/workflows/docker-publish.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 13ec9a3..3a4b3ce 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: [11, 12, 13, 14, 15] + version: [15] platform: - linux/amd64 - linux/arm64 @@ -24,17 +24,20 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Log in to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push Docker image + - name: Build and push to Docker Hub uses: docker/build-push-action@v4 with: context: docker platforms: ${{ matrix.platform }} build-args: TAG=${{ matrix.version }} push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} \ No newline at end of file + tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} From 9f020ba518d1bf156c2b00fa5248c9e838d70ccb Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 18:00:36 +0200 Subject: [PATCH 21/46] Puts version in inputs --- .github/workflows/docker-publish.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 3a4b3ce..8524fe6 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -2,6 +2,12 @@ name: Build and Push Multiple Docker Images on: workflow_dispatch: + inputs: + version: + description: 'PostgreSQL version' + required: true + default: 15 + type: number env: PG_IMAGE: postgres-debugger @@ -11,7 +17,6 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: [15] platform: - linux/amd64 - linux/arm64 @@ -38,6 +43,7 @@ jobs: with: context: docker platforms: ${{ matrix.platform }} - build-args: TAG=${{ matrix.version }} + build-args: TAG=${{ inputs.version }} push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} + tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-${{ matrix.platform }} + From 800119274d453b2fd678d517ec26356bd1de6f57 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 18:01:49 +0200 Subject: [PATCH 22/46] Change action title --- .github/workflows/docker-publish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 8524fe6..b0420be 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -1,4 +1,4 @@ -name: Build and Push Multiple Docker Images +name: Docker image on: workflow_dispatch: From 560b1a972871a392a520ec1ee8c276804cfee8f4 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 18:04:15 +0200 Subject: [PATCH 23/46] Fix platform name --- .github/workflows/docker-publish.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index b0420be..85211dc 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -18,8 +18,8 @@ jobs: strategy: matrix: platform: - - linux/amd64 - - linux/arm64 + - amd64 + - arm64 steps: - name: Checkout code uses: actions/checkout@v3 @@ -42,7 +42,7 @@ jobs: uses: docker/build-push-action@v4 with: context: docker - platforms: ${{ matrix.platform }} + platforms: linux/${{ matrix.platform }} build-args: TAG=${{ inputs.version }} push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-${{ matrix.platform }} From a5881a9297b9fc571b2f11009cbe6b4f30658619 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 19:37:51 +0200 Subject: [PATCH 24/46] Simplified conditions --- .github/workflows/docker-publish.yaml | 51 +++++++++++++++++++++------ 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 85211dc..ecfc764 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -1,4 +1,4 @@ -name: Docker image +name: Docker image for multi-arch on: workflow_dispatch: @@ -6,8 +6,13 @@ on: version: description: 'PostgreSQL version' required: true - default: 15 - type: number + type: choice + options: + - 11 + - 12 + - 13 + - 14 + - 15 env: PG_IMAGE: postgres-debugger @@ -15,11 +20,6 @@ env: jobs: build-and-push: runs-on: ubuntu-latest - strategy: - matrix: - platform: - - amd64 - - arm64 steps: - name: Checkout code uses: actions/checkout@v3 @@ -38,12 +38,41 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push to Docker Hub + - name: Build amd64 and push to Docker Hub uses: docker/build-push-action@v4 with: context: docker - platforms: linux/${{ matrix.platform }} + platforms: linux/amd64 build-args: TAG=${{ inputs.version }} push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-${{ matrix.platform }} + tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-amd64 + + - name: Build arm64 and push to Docker Hub + if: inputs.version == '14' || inputs.version == '15' + uses: docker/build-push-action@v4 + with: + context: docker + platforms: linux/arm64 + build-args: TAG=${{ inputs.version }} + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-arm64 + + - name: Create manifest for multi-arch + if: inputs.version == '14' || inputs.version == '15' + run: | + docker manifest creae ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }} \ + --amend ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-amd64 \ + --amend ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-arm64 + + - name: Ceate manifest for single arch + if: inputs.version == '11' || inputs.version == '12' || inputs.version == '13' + run: | + docker manifest create ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }} \ + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-amd64 + + - name: Push manifest + run: | + docker manifest push ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }} + + From a4337e9f5d7b2c593ec1e1c54a4005f5d51dfed6 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 19:39:27 +0200 Subject: [PATCH 25/46] README --- docker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index 2103854..61d462c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -26,7 +26,7 @@ export PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER= ### Create the manifest ```bash -export PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ +export PG_VERSION=14 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ && docker manifest create "$DOCKER_USER$PG_IMAGE:$PG_VERSION" \ --amend "$DOCKER_USER$PG_IMAGE:$PG_VERSION-amd64" \ --amend "$DOCKER_USER$PG_IMAGE:$PG_VERSION-arm64" From 2a877e29aeaf546f7a0961c60c5b3239c926e99c Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 20:06:48 +0200 Subject: [PATCH 26/46] Fix typo --- .github/workflows/docker-publish.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index ecfc764..dc4bc3e 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -60,16 +60,16 @@ jobs: - name: Create manifest for multi-arch if: inputs.version == '14' || inputs.version == '15' run: | - docker manifest creae ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }} \ + docker manifest create ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }} \ --amend ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-amd64 \ --amend ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-arm64 - - name: Ceate manifest for single arch + - name: Ceate manifest for amd64 if: inputs.version == '11' || inputs.version == '12' || inputs.version == '13' run: | docker manifest create ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }} \ - ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-amd64 - + --amend ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-amd64 + - name: Push manifest run: | docker manifest push ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }} From 701e6a4c40a519472c6af80a4f853452a6a11924 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 20:45:49 +0200 Subject: [PATCH 27/46] Don't care about manifest --- .github/workflows/docker-publish.yaml | 61 +++++++++------------------ docker/Dockerfile | 2 +- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index dc4bc3e..05d2ea5 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -2,17 +2,6 @@ name: Docker image for multi-arch on: workflow_dispatch: - inputs: - version: - description: 'PostgreSQL version' - required: true - type: choice - options: - - 11 - - 12 - - 13 - - 14 - - 15 env: PG_IMAGE: postgres-debugger @@ -20,6 +9,24 @@ env: jobs: build-and-push: runs-on: ubuntu-latest + strategy: + matrix: + platform: + - amd64 + - arm64 + version: + - 11 + - 12 + - 13 + - 14 + - 15 + exclude: + - platform: arm64 + version: 11 + - platform: arm64 + version: 12 + - platform: arm64 + version: 13 steps: - name: Checkout code uses: actions/checkout@v3 @@ -42,37 +49,11 @@ jobs: uses: docker/build-push-action@v4 with: context: docker - platforms: linux/amd64 - build-args: TAG=${{ inputs.version }} - push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-amd64 - - - name: Build arm64 and push to Docker Hub - if: inputs.version == '14' || inputs.version == '15' - uses: docker/build-push-action@v4 - with: - context: docker - platforms: linux/arm64 - build-args: TAG=${{ inputs.version }} + platforms: ${{ matrix.platform }} + build-args: TAG=${{ matrix.version }} push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-arm64 - - - name: Create manifest for multi-arch - if: inputs.version == '14' || inputs.version == '15' - run: | - docker manifest create ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }} \ - --amend ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-amd64 \ - --amend ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-arm64 - - - name: Ceate manifest for amd64 - if: inputs.version == '11' || inputs.version == '12' || inputs.version == '13' - run: | - docker manifest create ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }} \ - --amend ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-amd64 + tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} - - name: Push manifest - run: | - docker manifest push ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }} diff --git a/docker/Dockerfile b/docker/Dockerfile index 311f77c..2d18be2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -15,7 +15,7 @@ RUN apt --yes update \ && apt --yes upgrade \ && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex $PG_LIB \ && cd /usr/src/ \ - && git clone -b $PG_BRANCH --single-branch https://github.com/postgres/postgres.git \ + && git clone --progress -b $PG_BRANCH --single-branch https://github.com/postgres/postgres.git \ && cd postgres \ && ./configure \ && cd /usr/src/postgres/contrib \ From 1d0cf981ecf4200689be74e235c87ab3594be24b Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 20:49:23 +0200 Subject: [PATCH 28/46] exlude pg 11 --- .github/workflows/docker-publish.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 05d2ea5..d1db2de 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -15,14 +15,11 @@ jobs: - amd64 - arm64 version: - - 11 - 12 - 13 - 14 - 15 exclude: - - platform: arm64 - version: 11 - platform: arm64 version: 12 - platform: arm64 From 0de7992ef4084df9fbc726081d5db2258352d285 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 21:18:57 +0200 Subject: [PATCH 29/46] Fix multiplatform name --- .github/workflows/docker-publish.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index d1db2de..e70e7b2 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -12,17 +12,17 @@ jobs: strategy: matrix: platform: - - amd64 - - arm64 + - linux/amd64 + - linux/arm64 version: - 12 - 13 - 14 - 15 exclude: - - platform: arm64 + - platform: linux/amd64 version: 12 - - platform: arm64 + - platform: linux/arm64 version: 13 steps: - name: Checkout code From dbbe31f39fb12214945037245189a54eb4800ade Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Mon, 28 Aug 2023 21:58:38 +0200 Subject: [PATCH 30/46] Fix multi platform --- .github/workflows/docker-publish.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index e70e7b2..b4ca3b2 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -13,16 +13,16 @@ jobs: matrix: platform: - linux/amd64 - - linux/arm64 + - linux/amd64,linux/arm64 version: - 12 - 13 - 14 - 15 exclude: - - platform: linux/amd64 + - platform: linux/amd64,linux/arm64 version: 12 - - platform: linux/arm64 + - platform: linux/amd64,linux/arm64 version: 13 steps: - name: Checkout code From 1f75f8a2d91602d2be3fadf8790e31081415da4c Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Mon, 28 Aug 2023 22:01:26 +0200 Subject: [PATCH 31/46] Fix exclusion --- .github/workflows/docker-publish.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index b4ca3b2..e098963 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -24,6 +24,10 @@ jobs: version: 12 - platform: linux/amd64,linux/arm64 version: 13 + - platform: linux/amd64 + version: 14 + - platform: linux/amd64 + version: 15 steps: - name: Checkout code uses: actions/checkout@v3 From af48347583645b107e376a12f49128b1ec0b857a Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Mon, 28 Aug 2023 22:25:40 +0200 Subject: [PATCH 32/46] Step nane --- .github/workflows/docker-publish.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index e098963..0ba23fe 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -46,7 +46,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build amd64 and push to Docker Hub + - name: Build and push to Docker Hub uses: docker/build-push-action@v4 with: context: docker @@ -54,7 +54,4 @@ jobs: build-args: TAG=${{ matrix.version }} push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} - - - - + \ No newline at end of file From ac00852546aea252a7334c2058447c6ba8d6b0a7 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Mon, 28 Aug 2023 22:57:06 +0200 Subject: [PATCH 33/46] Use debian 12 --- .github/workflows/docker-publish.yaml | 3 +- docker/Dockerfile | 4 +-- docker/README.md | 44 ++++++--------------------- 3 files changed, 14 insertions(+), 37 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 0ba23fe..4221a0a 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -15,6 +15,7 @@ jobs: - linux/amd64 - linux/amd64,linux/arm64 version: + - 11 - 12 - 13 - 14 @@ -51,7 +52,7 @@ jobs: with: context: docker platforms: ${{ matrix.platform }} - build-args: TAG=${{ matrix.version }} + build-args: TAG=${{ matrix.version }} BASE_IMAGE=${{ matrix.version }}-bookworm push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 2d18be2..0bd0795 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,8 +1,8 @@ # Filename: Dockerfile #POSTGRES IMAGE -ARG TAG=latest -FROM postgres:${TAG} +ARG BASE_IMAGE=latest +FROM postgres:${BASE_IMAGE} # SET ENV ARG TAG diff --git a/docker/README.md b/docker/README.md index 61d462c..3206bf2 100644 --- a/docker/README.md +++ b/docker/README.md @@ -4,46 +4,22 @@ Leave empty if you don't want to push the image to docker hub -### Build the image for a specific platform +### Environment variables -> change PG_PLATFORM to amd64 or arm64 -```bash -export PG_PLATFORM=arm64 && PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ -&& docker build --platform "linux/$PG_PLATFORM" --build-arg "TAG=$PG_VERSION" -t "$DOCKER_USER$PG_IMAGE:$PG_VERSION-$PG_PLATFORM" . -``` - -### Push the image to docker hub - -```bash -export PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ -&& docker push "$DOCKER_USER$PG_IMAGE:$PG_VERSION-amd64" \ -&& docker push "$DOCKER_USER$PG_IMAGE:$PG_VERSION-arm64" -``` - -## Manifest +* PG_VERSION: PostgreSQL version (from 11 to 15) +* PG_PLATFORM: Target platform, tested on linux/amd64 and linux/arm64 (arm64 is supported from PostgreSQL 14) +* PG_IMAGE: Image name (11.21-bullseye for postgres:11.21-bullseye) -### Create the manifest ```bash -export PG_VERSION=14 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ -&& docker manifest create "$DOCKER_USER$PG_IMAGE:$PG_VERSION" \ ---amend "$DOCKER_USER$PG_IMAGE:$PG_VERSION-amd64" \ ---amend "$DOCKER_USER$PG_IMAGE:$PG_VERSION-arm64" +export PG_PLATFORM=linux/amd64 \ +&& PG_VERSION=11 \ +&& export PG_IMAGE=postgres-debugger \ +&& export DOCKER_USER=galien0xffffff \ +&& export BASE_IMAGE="11-bookworm" \ ``` -### Push the manifest - -```bash -export PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ -&& docker manifest push "$DOCKER_USER$PG_IMAGE:$PG_VERSION" -``` - - -## Run the image - ```bash -export PG_VERSION=14 \ -&& export PG_IMAGE=postgres-with-debugger \ -&& docker run -p 55$PG_VERSION:5432 --name "PostgresSQL-$PG_VERSION-debug" -e POSTGRES_PASSWORD=postgres -d "$PG_IMAGE:$PG_VERSION" +docker buildx build --platform $PG_PLATFORM --build-arg "TAG=$PG_VERSION" --build-arg "BASE_IMAGE=$BASE_IMAGE" -t "$DOCKER_USER/$PG_IMAGE:$PG_VERSION" . ``` From 6717cd3e08c48f4c111bd3798a9d89b582f79985 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Mon, 28 Aug 2023 23:06:45 +0200 Subject: [PATCH 34/46] Typo --- .github/workflows/docker-publish.yaml | 6 +++++- docker/README.md | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 4221a0a..eb24d4e 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -21,6 +21,8 @@ jobs: - 14 - 15 exclude: + - platform: linux/amd64,linux/arm64 + version: 11 - platform: linux/amd64,linux/arm64 version: 12 - platform: linux/amd64,linux/arm64 @@ -52,7 +54,9 @@ jobs: with: context: docker platforms: ${{ matrix.platform }} - build-args: TAG=${{ matrix.version }} BASE_IMAGE=${{ matrix.version }}-bookworm + build-args: | + TAG=${{ matrix.version }} + BASE_IMAGE=${{ matrix.version }}-bookworm push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} \ No newline at end of file diff --git a/docker/README.md b/docker/README.md index 3206bf2..8f47be3 100644 --- a/docker/README.md +++ b/docker/README.md @@ -9,7 +9,7 @@ Leave empty if you don't want to push the image to docker hub * PG_VERSION: PostgreSQL version (from 11 to 15) * PG_PLATFORM: Target platform, tested on linux/amd64 and linux/arm64 (arm64 is supported from PostgreSQL 14) -* PG_IMAGE: Image name (11.21-bullseye for postgres:11.21-bullseye) +* BASE_IMAGE: Image name (11.21-bullseye for postgres:11.21-bullseye) ```bash @@ -17,7 +17,7 @@ export PG_PLATFORM=linux/amd64 \ && PG_VERSION=11 \ && export PG_IMAGE=postgres-debugger \ && export DOCKER_USER=galien0xffffff \ -&& export BASE_IMAGE="11-bookworm" \ +&& export BASE_IMAGE="11-bookworm" ``` ```bash From 679a828665d9f6a87ca4c38bd5732dacc0ab4025 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Mon, 28 Aug 2023 23:28:37 +0200 Subject: [PATCH 35/46] Use buildx --- docker/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/README.md b/docker/README.md index 8f47be3..841a323 100644 --- a/docker/README.md +++ b/docker/README.md @@ -2,8 +2,6 @@ ## Build the image -Leave empty if you don't want to push the image to docker hub - ### Environment variables @@ -20,6 +18,8 @@ export PG_PLATFORM=linux/amd64 \ && export BASE_IMAGE="11-bookworm" ``` +### With buildx + ```bash docker buildx build --platform $PG_PLATFORM --build-arg "TAG=$PG_VERSION" --build-arg "BASE_IMAGE=$BASE_IMAGE" -t "$DOCKER_USER/$PG_IMAGE:$PG_VERSION" . ``` From 92b35b68f7efd66eaf810059cda1d57762f35c7b Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Mon, 28 Aug 2023 23:42:53 +0200 Subject: [PATCH 36/46] Multi image build & Cron --- .github/workflows/docker-publish.yaml | 12 ++++++++++-- docker/Dockerfile | 4 ++-- docker/README.md | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index eb24d4e..ca2867a 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -2,6 +2,9 @@ name: Docker image for multi-arch on: workflow_dispatch: + schedule: + #every month the 23rd at 6:23am + - cron: '23 6 23 * *' env: PG_IMAGE: postgres-debugger @@ -11,6 +14,9 @@ jobs: runs-on: ubuntu-latest strategy: matrix: + image: + - bullseye + - bookworm platform: - linux/amd64 - linux/amd64,linux/arm64 @@ -56,7 +62,9 @@ jobs: platforms: ${{ matrix.platform }} build-args: | TAG=${{ matrix.version }} - BASE_IMAGE=${{ matrix.version }}-bookworm + BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 0bd0795..2a6f6cc 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,6 @@ # Filename: Dockerfile -#POSTGRES IMAGE +#POSTGRES IMAGE ARG BASE_IMAGE=latest FROM postgres:${BASE_IMAGE} @@ -10,7 +10,7 @@ ENV PG_LIB=postgresql-server-dev-${TAG} ENV PG_BRANCH=REL_${TAG}_STABLE ENV PLUGIN_BRANCH=print-vars -# APT +# APT & BUILD RUN apt --yes update \ && apt --yes upgrade \ && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex $PG_LIB \ diff --git a/docker/README.md b/docker/README.md index 841a323..74ab394 100644 --- a/docker/README.md +++ b/docker/README.md @@ -7,7 +7,7 @@ * PG_VERSION: PostgreSQL version (from 11 to 15) * PG_PLATFORM: Target platform, tested on linux/amd64 and linux/arm64 (arm64 is supported from PostgreSQL 14) -* BASE_IMAGE: Image name (11.21-bullseye for postgres:11.21-bullseye) +* BASE_IMAGE: Image name (11-bookworm, ...) ```bash From 4c053d3704859804f50a219e74276880cc4dc681 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Tue, 29 Aug 2023 00:25:06 +0200 Subject: [PATCH 37/46] Tags handling --- .github/workflows/docker-publish.yaml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index ca2867a..06fbb92 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -8,6 +8,7 @@ on: env: PG_IMAGE: postgres-debugger + TAGS: jobs: build-and-push: @@ -37,7 +38,26 @@ jobs: version: 14 - platform: linux/amd64 version: 15 + include: + - image: bullseye + tag: 11 + - platform: linux/amd64 + version: 15 steps: + + - name: Setup default tags + run: | + echo "Setting up tags for bullseye" + env: + TAGS: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} + + - name: Add global tags for bookworm + if: matrix.image == 'bookworm' + run: | + echo "Setting up tags for bookworm and adding to global tags" + env: + TAGS: ${{ env.TAGS }},${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} + - name: Checkout code uses: actions/checkout@v3 with: @@ -55,7 +75,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push to Docker Hub + - name: Build and push uses: docker/build-push-action@v4 with: context: docker @@ -64,7 +84,5 @@ jobs: TAG=${{ matrix.version }} BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} push: true - tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} - ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} + tags: ${{ env.TAGS }} \ No newline at end of file From 984f01ad4a194ba1ce33eefc18e2c28832337760 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Tue, 29 Aug 2023 00:27:53 +0200 Subject: [PATCH 38/46] Tags --- .github/workflows/docker-publish.yaml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 06fbb92..9058098 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -8,7 +8,6 @@ on: env: PG_IMAGE: postgres-debugger - TAGS: jobs: build-and-push: @@ -49,14 +48,14 @@ jobs: run: | echo "Setting up tags for bullseye" env: - TAGS: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} - + Hub_Tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} + - name: Add global tags for bookworm if: matrix.image == 'bookworm' run: | echo "Setting up tags for bookworm and adding to global tags" env: - TAGS: ${{ env.TAGS }},${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} + Hub_Tags: ${{ env.Hub_Tags }},${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} - name: Checkout code uses: actions/checkout@v3 @@ -84,5 +83,5 @@ jobs: TAG=${{ matrix.version }} BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} push: true - tags: ${{ env.TAGS }} + tags: ${{ env.Hub_Tags }} \ No newline at end of file From 0e332ff5c21a3939bed90b16491a31eec33c11b0 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Tue, 29 Aug 2023 00:32:40 +0200 Subject: [PATCH 39/46] Image tags --- .github/workflows/docker-publish.yaml | 33 +++++++++++++++------------ 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 9058098..1c271f8 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -44,19 +44,6 @@ jobs: version: 15 steps: - - name: Setup default tags - run: | - echo "Setting up tags for bullseye" - env: - Hub_Tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} - - - name: Add global tags for bookworm - if: matrix.image == 'bookworm' - run: | - echo "Setting up tags for bookworm and adding to global tags" - env: - Hub_Tags: ${{ env.Hub_Tags }},${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} - - name: Checkout code uses: actions/checkout@v3 with: @@ -74,7 +61,8 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push + - name: Build and push bullseye + if: matrix.image == 'bullseye' uses: docker/build-push-action@v4 with: context: docker @@ -83,5 +71,20 @@ jobs: TAG=${{ matrix.version }} BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} push: true - tags: ${{ env.Hub_Tags }} + tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} + + - name: Build and push bookworm + if: matrix.image == 'bookworm' + uses: docker/build-push-action@v4 + with: + context: docker + platforms: ${{ matrix.platform }} + build-args: | + TAG=${{ matrix.version }} + BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} + \ No newline at end of file From f111467d2a94f4cc6b7418a84535611380788fbf Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Tue, 29 Aug 2023 00:34:54 +0200 Subject: [PATCH 40/46] typo --- .github/workflows/docker-publish.yaml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 1c271f8..bbf69ad 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -37,18 +37,13 @@ jobs: version: 14 - platform: linux/amd64 version: 15 - include: - - image: bullseye - tag: 11 - - platform: linux/amd64 - version: 15 - steps: + steps: - name: Checkout code uses: actions/checkout@v3 with: ref: print-vars - + - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -72,7 +67,7 @@ jobs: BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} - + - name: Build and push bookworm if: matrix.image == 'bookworm' uses: docker/build-push-action@v4 From db4a6a874067ba0732a35220424377faec54d01e Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sun, 27 Aug 2023 19:37:51 +0200 Subject: [PATCH 41/46] Github actions --- .github/workflows/docker-publish.yaml | 64 +++++++++++++++++++++------ docker/Dockerfile | 10 ++--- docker/README.md | 46 +++++-------------- 3 files changed, 66 insertions(+), 54 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index 85211dc..bbf69ad 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -1,13 +1,10 @@ -name: Docker image +name: Docker image for multi-arch on: workflow_dispatch: - inputs: - version: - description: 'PostgreSQL version' - required: true - default: 15 - type: number + schedule: + #every month the 23rd at 6:23am + - cron: '23 6 23 * *' env: PG_IMAGE: postgres-debugger @@ -17,15 +14,36 @@ jobs: runs-on: ubuntu-latest strategy: matrix: + image: + - bullseye + - bookworm platform: - - amd64 - - arm64 + - linux/amd64 + - linux/amd64,linux/arm64 + version: + - 11 + - 12 + - 13 + - 14 + - 15 + exclude: + - platform: linux/amd64,linux/arm64 + version: 11 + - platform: linux/amd64,linux/arm64 + version: 12 + - platform: linux/amd64,linux/arm64 + version: 13 + - platform: linux/amd64 + version: 14 + - platform: linux/amd64 + version: 15 + steps: - name: Checkout code uses: actions/checkout@v3 with: ref: print-vars - + - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -38,12 +56,30 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push to Docker Hub + - name: Build and push bullseye + if: matrix.image == 'bullseye' + uses: docker/build-push-action@v4 + with: + context: docker + platforms: ${{ matrix.platform }} + build-args: | + TAG=${{ matrix.version }} + BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} + + - name: Build and push bookworm + if: matrix.image == 'bookworm' uses: docker/build-push-action@v4 with: context: docker - platforms: linux/${{ matrix.platform }} - build-args: TAG=${{ inputs.version }} + platforms: ${{ matrix.platform }} + build-args: | + TAG=${{ matrix.version }} + BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ inputs.version }}-${{ matrix.platform }} + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }} + \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 311f77c..2a6f6cc 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,8 +1,8 @@ # Filename: Dockerfile -#POSTGRES IMAGE -ARG TAG=latest -FROM postgres:${TAG} +#POSTGRES IMAGE +ARG BASE_IMAGE=latest +FROM postgres:${BASE_IMAGE} # SET ENV ARG TAG @@ -10,12 +10,12 @@ ENV PG_LIB=postgresql-server-dev-${TAG} ENV PG_BRANCH=REL_${TAG}_STABLE ENV PLUGIN_BRANCH=print-vars -# APT +# APT & BUILD RUN apt --yes update \ && apt --yes upgrade \ && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex $PG_LIB \ && cd /usr/src/ \ - && git clone -b $PG_BRANCH --single-branch https://github.com/postgres/postgres.git \ + && git clone --progress -b $PG_BRANCH --single-branch https://github.com/postgres/postgres.git \ && cd postgres \ && ./configure \ && cd /usr/src/postgres/contrib \ diff --git a/docker/README.md b/docker/README.md index 2103854..74ab394 100644 --- a/docker/README.md +++ b/docker/README.md @@ -2,48 +2,24 @@ ## Build the image -Leave empty if you don't want to push the image to docker hub +### Environment variables -### Build the image for a specific platform -> change PG_PLATFORM to amd64 or arm64 +* PG_VERSION: PostgreSQL version (from 11 to 15) +* PG_PLATFORM: Target platform, tested on linux/amd64 and linux/arm64 (arm64 is supported from PostgreSQL 14) +* BASE_IMAGE: Image name (11-bookworm, ...) -```bash -export PG_PLATFORM=arm64 && PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ -&& docker build --platform "linux/$PG_PLATFORM" --build-arg "TAG=$PG_VERSION" -t "$DOCKER_USER$PG_IMAGE:$PG_VERSION-$PG_PLATFORM" . -``` - -### Push the image to docker hub ```bash -export PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ -&& docker push "$DOCKER_USER$PG_IMAGE:$PG_VERSION-amd64" \ -&& docker push "$DOCKER_USER$PG_IMAGE:$PG_VERSION-arm64" +export PG_PLATFORM=linux/amd64 \ +&& PG_VERSION=11 \ +&& export PG_IMAGE=postgres-debugger \ +&& export DOCKER_USER=galien0xffffff \ +&& export BASE_IMAGE="11-bookworm" ``` -## Manifest - -### Create the manifest - -```bash -export PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ -&& docker manifest create "$DOCKER_USER$PG_IMAGE:$PG_VERSION" \ ---amend "$DOCKER_USER$PG_IMAGE:$PG_VERSION-amd64" \ ---amend "$DOCKER_USER$PG_IMAGE:$PG_VERSION-arm64" -``` - -### Push the manifest - -```bash -export PG_VERSION=15 && export PG_IMAGE=postgres-debugger && export DOCKER_USER=galien0xffffff/ \ -&& docker manifest push "$DOCKER_USER$PG_IMAGE:$PG_VERSION" -``` - - -## Run the image +### With buildx ```bash -export PG_VERSION=14 \ -&& export PG_IMAGE=postgres-with-debugger \ -&& docker run -p 55$PG_VERSION:5432 --name "PostgresSQL-$PG_VERSION-debug" -e POSTGRES_PASSWORD=postgres -d "$PG_IMAGE:$PG_VERSION" +docker buildx build --platform $PG_PLATFORM --build-arg "TAG=$PG_VERSION" --build-arg "BASE_IMAGE=$BASE_IMAGE" -t "$DOCKER_USER/$PG_IMAGE:$PG_VERSION" . ``` From 13326e0c2e78f15c860070ba94dc09c2a62412f0 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sat, 18 Nov 2023 10:36:11 +0100 Subject: [PATCH 42/46] Fix PG 16 compilation --- .github/workflows/docker-publish.yaml | 3 +++ docker/Dockerfile | 2 +- docker/README.md | 8 ++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index bbf69ad..d422775 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -26,6 +26,7 @@ jobs: - 13 - 14 - 15 + - 16 exclude: - platform: linux/amd64,linux/arm64 version: 11 @@ -37,6 +38,8 @@ jobs: version: 14 - platform: linux/amd64 version: 15 + - platform: linux/amd64 + version: 16 steps: - name: Checkout code diff --git a/docker/Dockerfile b/docker/Dockerfile index 2a6f6cc..c810540 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,7 +13,7 @@ ENV PLUGIN_BRANCH=print-vars # APT & BUILD RUN apt --yes update \ && apt --yes upgrade \ - && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex $PG_LIB \ + && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex pkg-config $PG_LIB \ && cd /usr/src/ \ && git clone --progress -b $PG_BRANCH --single-branch https://github.com/postgres/postgres.git \ && cd postgres \ diff --git a/docker/README.md b/docker/README.md index 74ab394..47d01eb 100644 --- a/docker/README.md +++ b/docker/README.md @@ -5,17 +5,17 @@ ### Environment variables -* PG_VERSION: PostgreSQL version (from 11 to 15) +* PG_VERSION: PostgreSQL version (from 11 to 16) * PG_PLATFORM: Target platform, tested on linux/amd64 and linux/arm64 (arm64 is supported from PostgreSQL 14) * BASE_IMAGE: Image name (11-bookworm, ...) ```bash -export PG_PLATFORM=linux/amd64 \ -&& PG_VERSION=11 \ +export PG_PLATFORM=linux/arm64 \ +&& PG_VERSION=16 \ && export PG_IMAGE=postgres-debugger \ && export DOCKER_USER=galien0xffffff \ -&& export BASE_IMAGE="11-bookworm" +&& export BASE_IMAGE="16-bookworm" ``` ### With buildx From 313cf73d75116d179ca875bc1faa2f241c923dbe Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sat, 18 Nov 2023 10:56:31 +0100 Subject: [PATCH 43/46] Decompose build --- .github/workflows/docker-publish.yaml | 8 ++++---- docker/Dockerfile | 20 +++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/docker-publish.yaml b/.github/workflows/docker-publish.yaml index d422775..99f2598 100644 --- a/.github/workflows/docker-publish.yaml +++ b/.github/workflows/docker-publish.yaml @@ -66,8 +66,8 @@ jobs: context: docker platforms: ${{ matrix.platform }} build-args: | - TAG=${{ matrix.version }} - BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} + TAG=${{ matrix.version }} + BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} push: true tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} @@ -78,8 +78,8 @@ jobs: context: docker platforms: ${{ matrix.platform }} build-args: | - TAG=${{ matrix.version }} - BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} + TAG=${{ matrix.version }} + BASE_IMAGE=${{ matrix.version }}-${{ matrix.image }} push: true tags: | ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.PG_IMAGE }}:${{ matrix.version }}-${{ matrix.image }} diff --git a/docker/Dockerfile b/docker/Dockerfile index c810540..82c4a85 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -10,20 +10,26 @@ ENV PG_LIB=postgresql-server-dev-${TAG} ENV PG_BRANCH=REL_${TAG}_STABLE ENV PLUGIN_BRANCH=print-vars -# APT & BUILD +# APT RUN apt --yes update \ && apt --yes upgrade \ - && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex pkg-config $PG_LIB \ - && cd /usr/src/ \ + && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex pkg-config $PG_LIB + +# CONFIGURE POSTGRES +RUN cd /usr/src/ \ && git clone --progress -b $PG_BRANCH --single-branch https://github.com/postgres/postgres.git \ && cd postgres \ && ./configure \ - && cd /usr/src/postgres/contrib \ - && git clone -b $PLUGIN_BRANCH --single-branch https://github.com/ng-galien/pldebugger.git \ + && cd /usr/src/postgres/contrib + +# INSTALL PLDEBUGGER +RUN git clone -b $PLUGIN_BRANCH --single-branch https://github.com/ng-galien/pldebugger.git \ && cd pldebugger \ && make clean && make USE_PGXS=1 && make USE_PGXS=1 install \ - && rm -r /usr/src/postgres \ - && apt --yes remove --purge git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex $PG_LIB \ + && rm -r /usr/src/postgres + +# CLEAN +RUN apt --yes remove --purge git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex pkg-config $PG_LIB \ && apt --yes autoremove \ && apt --yes clean From f300b46a4020c45d600c347d8ced3b27094b7bb6 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sat, 18 Nov 2023 11:01:31 +0100 Subject: [PATCH 44/46] Fix Debugger install --- docker/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 82c4a85..5509078 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -19,11 +19,11 @@ RUN apt --yes update \ RUN cd /usr/src/ \ && git clone --progress -b $PG_BRANCH --single-branch https://github.com/postgres/postgres.git \ && cd postgres \ - && ./configure \ - && cd /usr/src/postgres/contrib + && ./configure # INSTALL PLDEBUGGER -RUN git clone -b $PLUGIN_BRANCH --single-branch https://github.com/ng-galien/pldebugger.git \ +RUN cd /usr/src/postgres/contrib \ + && git clone -b $PLUGIN_BRANCH --single-branch https://github.com/ng-galien/pldebugger.git \ && cd pldebugger \ && make clean && make USE_PGXS=1 && make USE_PGXS=1 install \ && rm -r /usr/src/postgres From 262482f01bf7e9a096d6b5417f90125cee250e60 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sat, 18 Nov 2023 12:25:29 +0100 Subject: [PATCH 45/46] Fix missing package --- docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 5509078..63a9161 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,7 +13,7 @@ ENV PLUGIN_BRANCH=print-vars # APT RUN apt --yes update \ && apt --yes upgrade \ - && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex pkg-config $PG_LIB + && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex libicu-dev pkg-config $PG_LIB # CONFIGURE POSTGRES RUN cd /usr/src/ \ @@ -29,7 +29,7 @@ RUN cd /usr/src/postgres/contrib \ && rm -r /usr/src/postgres # CLEAN -RUN apt --yes remove --purge git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex pkg-config $PG_LIB \ +RUN apt --yes remove --purge git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex libicu-dev pkg-config $PG_LIB \ && apt --yes autoremove \ && apt --yes clean From e51c71715f70aa9cbaed2f4286be1eada50a8d27 Mon Sep 17 00:00:00 2001 From: Alexandre Boyer <33391039+ng-galien@users.noreply.github.com> Date: Sat, 18 Nov 2023 12:32:22 +0100 Subject: [PATCH 46/46] Fix image cleaning --- docker/Dockerfile | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 63a9161..278b2ba 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,23 +13,17 @@ ENV PLUGIN_BRANCH=print-vars # APT RUN apt --yes update \ && apt --yes upgrade \ - && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex libicu-dev pkg-config $PG_LIB - -# CONFIGURE POSTGRES -RUN cd /usr/src/ \ + && apt --yes install git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex libicu-dev pkg-config $PG_LIB \ + && cd /usr/src/ \ && git clone --progress -b $PG_BRANCH --single-branch https://github.com/postgres/postgres.git \ && cd postgres \ - && ./configure - -# INSTALL PLDEBUGGER -RUN cd /usr/src/postgres/contrib \ + && ./configure \ + && cd /usr/src/postgres/contrib \ && git clone -b $PLUGIN_BRANCH --single-branch https://github.com/ng-galien/pldebugger.git \ && cd pldebugger \ && make clean && make USE_PGXS=1 && make USE_PGXS=1 install \ - && rm -r /usr/src/postgres - -# CLEAN -RUN apt --yes remove --purge git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex libicu-dev pkg-config $PG_LIB \ + && rm -r /usr/src/postgres \ + && apt --yes remove --purge git build-essential libreadline-dev zlib1g-dev bison libkrb5-dev flex libicu-dev pkg-config $PG_LIB \ && apt --yes autoremove \ && apt --yes clean