LCOV - code coverage report
Current view: top level - src/backend/utils/cache - partcache.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 96.6 % 176 170
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 52.7 % 74 39

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * partcache.c
       4                 :             :  *              Support routines for manipulating partition information cached in
       5                 :             :  *              relcache
       6                 :             :  *
       7                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       8                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       9                 :             :  *
      10                 :             :  * IDENTIFICATION
      11                 :             :  *                src/backend/utils/cache/partcache.c
      12                 :             :  *
      13                 :             :  *-------------------------------------------------------------------------
      14                 :             : */
      15                 :             : #include "postgres.h"
      16                 :             : 
      17                 :             : #include "access/hash.h"
      18                 :             : #include "access/htup_details.h"
      19                 :             : #include "access/nbtree.h"
      20                 :             : #include "access/relation.h"
      21                 :             : #include "catalog/partition.h"
      22                 :             : #include "catalog/pg_opclass.h"
      23                 :             : #include "catalog/pg_partitioned_table.h"
      24                 :             : #include "miscadmin.h"
      25                 :             : #include "nodes/makefuncs.h"
      26                 :             : #include "nodes/nodeFuncs.h"
      27                 :             : #include "optimizer/optimizer.h"
      28                 :             : #include "partitioning/partbounds.h"
      29                 :             : #include "utils/builtins.h"
      30                 :             : #include "utils/lsyscache.h"
      31                 :             : #include "utils/memutils.h"
      32                 :             : #include "utils/partcache.h"
      33                 :             : #include "utils/rel.h"
      34                 :             : #include "utils/syscache.h"
      35                 :             : 
      36                 :             : 
      37                 :             : static void RelationBuildPartitionKey(Relation relation);
      38                 :             : static List *generate_partition_qual(Relation rel);
      39                 :             : 
      40                 :             : /*
      41                 :             :  * RelationGetPartitionKey -- get partition key, if relation is partitioned
      42                 :             :  *
      43                 :             :  * Note: partition keys are not allowed to change after the partitioned rel
      44                 :             :  * is created.  RelationClearRelation knows this and preserves rd_partkey
      45                 :             :  * across relcache rebuilds, as long as the relation is open.  Therefore,
      46                 :             :  * even though we hand back a direct pointer into the relcache entry, it's
      47                 :             :  * safe for callers to continue to use that pointer as long as they hold
      48                 :             :  * the relation open.
      49                 :             :  */
      50                 :             : PartitionKey
      51                 :       17016 : RelationGetPartitionKey(Relation rel)
      52                 :             : {
      53         [ +  + ]:       17016 :         if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
      54                 :           2 :                 return NULL;
      55                 :             : 
      56         [ +  + ]:       17014 :         if (unlikely(rel->rd_partkey == NULL))
      57                 :        2776 :                 RelationBuildPartitionKey(rel);
      58                 :             : 
      59                 :       17014 :         return rel->rd_partkey;
      60                 :       17016 : }
      61                 :             : 
      62                 :             : /*
      63                 :             :  * RelationBuildPartitionKey
      64                 :             :  *              Build partition key data of relation, and attach to relcache
      65                 :             :  *
      66                 :             :  * Partitioning key data is a complex structure; to avoid complicated logic to
      67                 :             :  * free individual elements whenever the relcache entry is flushed, we give it
      68                 :             :  * its own memory context, a child of CacheMemoryContext, which can easily be
      69                 :             :  * deleted on its own.  To avoid leaking memory in that context in case of an
      70                 :             :  * error partway through this function, the context is initially created as a
      71                 :             :  * child of CurTransactionContext and only re-parented to CacheMemoryContext
      72                 :             :  * at the end, when no further errors are possible.  Also, we don't make this
      73                 :             :  * context the current context except in very brief code sections, out of fear
      74                 :             :  * that some of our callees allocate memory on their own which would be leaked
      75                 :             :  * permanently.
      76                 :             :  */
      77                 :             : static void
      78                 :        2776 : RelationBuildPartitionKey(Relation relation)
      79                 :             : {
      80                 :        2776 :         Form_pg_partitioned_table form;
      81                 :        2776 :         HeapTuple       tuple;
      82                 :        2776 :         bool            isnull;
      83                 :        2776 :         int                     i;
      84                 :        2776 :         PartitionKey key;
      85                 :        2776 :         AttrNumber *attrs;
      86                 :        2776 :         oidvector  *opclass;
      87                 :        2776 :         oidvector  *collation;
      88                 :        2776 :         ListCell   *partexprs_item;
      89                 :        2776 :         Datum           datum;
      90                 :        2776 :         MemoryContext partkeycxt,
      91                 :             :                                 oldcxt;
      92                 :        2776 :         int16           procnum;
      93                 :             : 
      94                 :        2776 :         tuple = SearchSysCache1(PARTRELID,
      95                 :        2776 :                                                         ObjectIdGetDatum(RelationGetRelid(relation)));
      96                 :             : 
      97         [ +  - ]:        2776 :         if (!HeapTupleIsValid(tuple))
      98   [ #  #  #  # ]:           0 :                 elog(ERROR, "cache lookup failed for partition key of relation %u",
      99                 :             :                          RelationGetRelid(relation));
     100                 :             : 
     101                 :        2776 :         partkeycxt = AllocSetContextCreate(CurTransactionContext,
     102                 :             :                                                                            "partition key",
     103                 :             :                                                                            ALLOCSET_SMALL_SIZES);
     104                 :        2776 :         MemoryContextCopyAndSetIdentifier(partkeycxt,
     105                 :             :                                                                           RelationGetRelationName(relation));
     106                 :             : 
     107                 :        2776 :         key = (PartitionKey) MemoryContextAllocZero(partkeycxt,
     108                 :             :                                                                                                 sizeof(PartitionKeyData));
     109                 :             : 
     110                 :             :         /* Fixed-length attributes */
     111                 :        2776 :         form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
     112                 :        2776 :         key->strategy = form->partstrat;
     113                 :        2776 :         key->partnatts = form->partnatts;
     114                 :             : 
     115                 :             :         /* Validate partition strategy code */
     116         [ +  + ]:        2776 :         if (key->strategy != PARTITION_STRATEGY_LIST &&
     117   [ +  +  +  - ]:        1651 :                 key->strategy != PARTITION_STRATEGY_RANGE &&
     118                 :         139 :                 key->strategy != PARTITION_STRATEGY_HASH)
     119   [ #  #  #  # ]:           0 :                 elog(ERROR, "invalid partition strategy \"%c\"", key->strategy);
     120                 :             : 
     121                 :             :         /*
     122                 :             :          * We can rely on the first variable-length attribute being mapped to the
     123                 :             :          * relevant field of the catalog's C struct, because all previous
     124                 :             :          * attributes are non-nullable and fixed-length.
     125                 :             :          */
     126                 :        2776 :         attrs = form->partattrs.values;
     127                 :             : 
     128                 :             :         /* But use the hard way to retrieve further variable-length attributes */
     129                 :             :         /* Operator class */
     130                 :        2776 :         datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
     131                 :             :                                                                    Anum_pg_partitioned_table_partclass);
     132                 :        2776 :         opclass = (oidvector *) DatumGetPointer(datum);
     133                 :             : 
     134                 :             :         /* Collation */
     135                 :        2776 :         datum = SysCacheGetAttrNotNull(PARTRELID, tuple,
     136                 :             :                                                                    Anum_pg_partitioned_table_partcollation);
     137                 :        2776 :         collation = (oidvector *) DatumGetPointer(datum);
     138                 :             : 
     139                 :             :         /* Expressions */
     140                 :        2776 :         datum = SysCacheGetAttr(PARTRELID, tuple,
     141                 :             :                                                         Anum_pg_partitioned_table_partexprs, &isnull);
     142         [ +  + ]:        2776 :         if (!isnull)
     143                 :             :         {
     144                 :         163 :                 char       *exprString;
     145                 :         163 :                 Node       *expr;
     146                 :             : 
     147                 :         163 :                 exprString = TextDatumGetCString(datum);
     148                 :         163 :                 expr = stringToNode(exprString);
     149                 :         163 :                 pfree(exprString);
     150                 :             : 
     151                 :             :                 /*
     152                 :             :                  * Run the expressions through const-simplification since the planner
     153                 :             :                  * will be comparing them to similarly-processed qual clause operands,
     154                 :             :                  * and may fail to detect valid matches without this step; fix
     155                 :             :                  * opfuncids while at it.  We don't need to bother with
     156                 :             :                  * canonicalize_qual() though, because partition expressions should be
     157                 :             :                  * in canonical form already (ie, no need for OR-merging or constant
     158                 :             :                  * elimination).
     159                 :             :                  */
     160                 :         163 :                 expr = eval_const_expressions(NULL, expr);
     161                 :         163 :                 fix_opfuncids(expr);
     162                 :             : 
     163                 :         163 :                 oldcxt = MemoryContextSwitchTo(partkeycxt);
     164                 :         163 :                 key->partexprs = (List *) copyObject(expr);
     165                 :         163 :                 MemoryContextSwitchTo(oldcxt);
     166                 :         163 :         }
     167                 :             : 
     168                 :             :         /* Allocate assorted arrays in the partkeycxt, which we'll fill below */
     169                 :        2776 :         oldcxt = MemoryContextSwitchTo(partkeycxt);
     170                 :        2776 :         key->partattrs = palloc0_array(AttrNumber, key->partnatts);
     171                 :        2776 :         key->partopfamily = palloc0_array(Oid, key->partnatts);
     172                 :        2776 :         key->partopcintype = palloc0_array(Oid, key->partnatts);
     173                 :        2776 :         key->partsupfunc = palloc0_array(FmgrInfo, key->partnatts);
     174                 :             : 
     175                 :        2776 :         key->partcollation = palloc0_array(Oid, key->partnatts);
     176                 :        2776 :         key->parttypid = palloc0_array(Oid, key->partnatts);
     177                 :        2776 :         key->parttypmod = palloc0_array(int32, key->partnatts);
     178                 :        2776 :         key->parttyplen = palloc0_array(int16, key->partnatts);
     179                 :        2776 :         key->parttypbyval = palloc0_array(bool, key->partnatts);
     180                 :        2776 :         key->parttypalign = palloc0_array(char, key->partnatts);
     181                 :        2776 :         key->parttypcoll = palloc0_array(Oid, key->partnatts);
     182                 :        2776 :         MemoryContextSwitchTo(oldcxt);
     183                 :             : 
     184                 :             :         /* determine support function number to search for */
     185                 :        2776 :         procnum = (key->strategy == PARTITION_STRATEGY_HASH) ?
     186                 :             :                 HASHEXTENDED_PROC : BTORDER_PROC;
     187                 :             : 
     188                 :             :         /* Copy partattrs and fill other per-attribute info */
     189                 :        2776 :         memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16));
     190                 :        2776 :         partexprs_item = list_head(key->partexprs);
     191         [ +  + ]:        5896 :         for (i = 0; i < key->partnatts; i++)
     192                 :             :         {
     193                 :        3120 :                 AttrNumber      attno = key->partattrs[i];
     194                 :        3120 :                 HeapTuple       opclasstup;
     195                 :        3120 :                 Form_pg_opclass opclassform;
     196                 :        3120 :                 Oid                     funcid;
     197                 :             : 
     198                 :             :                 /* Collect opfamily information */
     199                 :        3120 :                 opclasstup = SearchSysCache1(CLAOID,
     200                 :        3120 :                                                                          ObjectIdGetDatum(opclass->values[i]));
     201         [ +  - ]:        3120 :                 if (!HeapTupleIsValid(opclasstup))
     202   [ #  #  #  # ]:           0 :                         elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
     203                 :             : 
     204                 :        3120 :                 opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup);
     205                 :        3120 :                 key->partopfamily[i] = opclassform->opcfamily;
     206                 :        3120 :                 key->partopcintype[i] = opclassform->opcintype;
     207                 :             : 
     208                 :             :                 /* Get a support function for the specified opfamily and datatypes */
     209                 :        6240 :                 funcid = get_opfamily_proc(opclassform->opcfamily,
     210                 :        3120 :                                                                    opclassform->opcintype,
     211                 :        3120 :                                                                    opclassform->opcintype,
     212                 :        3120 :                                                                    procnum);
     213         [ +  - ]:        3120 :                 if (!OidIsValid(funcid))
     214   [ #  #  #  # ]:           0 :                         ereport(ERROR,
     215                 :             :                                         (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     216                 :             :                                          errmsg("operator class \"%s\" of access method %s is missing support function %d for type %s",
     217                 :             :                                                         NameStr(opclassform->opcname),
     218                 :             :                                                         (key->strategy == PARTITION_STRATEGY_HASH) ?
     219                 :             :                                                         "hash" : "btree",
     220                 :             :                                                         procnum,
     221                 :             :                                                         format_type_be(opclassform->opcintype))));
     222                 :             : 
     223                 :        3120 :                 fmgr_info_cxt(funcid, &key->partsupfunc[i], partkeycxt);
     224                 :             : 
     225                 :             :                 /* Collation */
     226                 :        3120 :                 key->partcollation[i] = collation->values[i];
     227                 :             : 
     228                 :             :                 /* Collect type information */
     229         [ +  + ]:        3120 :                 if (attno != 0)
     230                 :             :                 {
     231                 :        2945 :                         Form_pg_attribute att = TupleDescAttr(relation->rd_att, attno - 1);
     232                 :             : 
     233                 :        2945 :                         key->parttypid[i] = att->atttypid;
     234                 :        2945 :                         key->parttypmod[i] = att->atttypmod;
     235                 :        2945 :                         key->parttypcoll[i] = att->attcollation;
     236                 :        2945 :                 }
     237                 :             :                 else
     238                 :             :                 {
     239         [ +  - ]:         175 :                         if (partexprs_item == NULL)
     240   [ #  #  #  # ]:           0 :                                 elog(ERROR, "wrong number of partition key expressions");
     241                 :             : 
     242                 :         175 :                         key->parttypid[i] = exprType(lfirst(partexprs_item));
     243                 :         175 :                         key->parttypmod[i] = exprTypmod(lfirst(partexprs_item));
     244                 :         175 :                         key->parttypcoll[i] = exprCollation(lfirst(partexprs_item));
     245                 :             : 
     246                 :         175 :                         partexprs_item = lnext(key->partexprs, partexprs_item);
     247                 :             :                 }
     248                 :        6240 :                 get_typlenbyvalalign(key->parttypid[i],
     249                 :        3120 :                                                          &key->parttyplen[i],
     250                 :        3120 :                                                          &key->parttypbyval[i],
     251                 :        3120 :                                                          &key->parttypalign[i]);
     252                 :             : 
     253                 :        3120 :                 ReleaseSysCache(opclasstup);
     254                 :        3120 :         }
     255                 :             : 
     256                 :        2776 :         ReleaseSysCache(tuple);
     257                 :             : 
     258                 :             :         /* Assert that we're not leaking any old data during assignments below */
     259         [ +  - ]:        2776 :         Assert(relation->rd_partkeycxt == NULL);
     260         [ +  - ]:        2776 :         Assert(relation->rd_partkey == NULL);
     261                 :             : 
     262                 :             :         /*
     263                 :             :          * Success --- reparent our context and make the relcache point to the
     264                 :             :          * newly constructed key
     265                 :             :          */
     266                 :        2776 :         MemoryContextSetParent(partkeycxt, CacheMemoryContext);
     267                 :        2776 :         relation->rd_partkeycxt = partkeycxt;
     268                 :        2776 :         relation->rd_partkey = key;
     269                 :        2776 : }
     270                 :             : 
     271                 :             : /*
     272                 :             :  * RelationGetPartitionQual
     273                 :             :  *
     274                 :             :  * Returns a list of partition quals
     275                 :             :  */
     276                 :             : List *
     277                 :        3110 : RelationGetPartitionQual(Relation rel)
     278                 :             : {
     279                 :             :         /* Quick exit */
     280         [ +  + ]:        3110 :         if (!rel->rd_rel->relispartition)
     281                 :        1975 :                 return NIL;
     282                 :             : 
     283                 :        1135 :         return generate_partition_qual(rel);
     284                 :        3110 : }
     285                 :             : 
     286                 :             : /*
     287                 :             :  * get_partition_qual_relid
     288                 :             :  *
     289                 :             :  * Returns an expression tree describing the passed-in relation's partition
     290                 :             :  * constraint.
     291                 :             :  *
     292                 :             :  * If the relation is not found, or is not a partition, or there is no
     293                 :             :  * partition constraint, return NULL.  We must guard against the first two
     294                 :             :  * cases because this supports a SQL function that could be passed any OID.
     295                 :             :  * The last case can happen even if relispartition is true, when a default
     296                 :             :  * partition is the only partition.
     297                 :             :  */
     298                 :             : Expr *
     299                 :          48 : get_partition_qual_relid(Oid relid)
     300                 :             : {
     301                 :          48 :         Expr       *result = NULL;
     302                 :             : 
     303                 :             :         /* Do the work only if this relation exists and is a partition. */
     304         [ -  + ]:          48 :         if (get_rel_relispartition(relid))
     305                 :             :         {
     306                 :          48 :                 Relation        rel = relation_open(relid, AccessShareLock);
     307                 :          48 :                 List       *and_args;
     308                 :             : 
     309                 :          48 :                 and_args = generate_partition_qual(rel);
     310                 :             : 
     311                 :             :                 /* Convert implicit-AND list format to boolean expression */
     312         [ +  + ]:          48 :                 if (and_args == NIL)
     313                 :           4 :                         result = NULL;
     314         [ +  + ]:          44 :                 else if (list_length(and_args) > 1)
     315                 :          41 :                         result = makeBoolExpr(AND_EXPR, and_args, -1);
     316                 :             :                 else
     317                 :           3 :                         result = linitial(and_args);
     318                 :             : 
     319                 :             :                 /* Keep the lock, to allow safe deparsing against the rel by caller. */
     320                 :          48 :                 relation_close(rel, NoLock);
     321                 :          48 :         }
     322                 :             : 
     323                 :          96 :         return result;
     324                 :          48 : }
     325                 :             : 
     326                 :             : /*
     327                 :             :  * generate_partition_qual
     328                 :             :  *
     329                 :             :  * Generate partition predicate from rel's partition bound expression. The
     330                 :             :  * function returns a NIL list if there is no predicate.
     331                 :             :  *
     332                 :             :  * We cache a copy of the result in the relcache entry, after constructing
     333                 :             :  * it using the caller's context.  This approach avoids leaking any data
     334                 :             :  * into long-lived cache contexts, especially if we fail partway through.
     335                 :             :  */
     336                 :             : static List *
     337                 :        1278 : generate_partition_qual(Relation rel)
     338                 :             : {
     339                 :        1278 :         HeapTuple       tuple;
     340                 :        1278 :         MemoryContext oldcxt;
     341                 :        1278 :         Datum           boundDatum;
     342                 :        1278 :         bool            isnull;
     343                 :        1278 :         List       *my_qual = NIL,
     344                 :        1278 :                            *result = NIL;
     345                 :        1278 :         Oid                     parentrelid;
     346                 :        1278 :         Relation        parent;
     347                 :             : 
     348                 :             :         /* Guard against stack overflow due to overly deep partition tree */
     349                 :        1278 :         check_stack_depth();
     350                 :             : 
     351                 :             :         /* If we already cached the result, just return a copy */
     352         [ +  + ]:        1278 :         if (rel->rd_partcheckvalid)
     353                 :         784 :                 return copyObject(rel->rd_partcheck);
     354                 :             : 
     355                 :             :         /*
     356                 :             :          * Grab at least an AccessShareLock on the parent table.  Must do this
     357                 :             :          * even if the partition has been partially detached, because transactions
     358                 :             :          * concurrent with the detach might still be trying to use a partition
     359                 :             :          * descriptor that includes it.
     360                 :             :          */
     361                 :         494 :         parentrelid = get_partition_parent(RelationGetRelid(rel), true);
     362                 :         494 :         parent = relation_open(parentrelid, AccessShareLock);
     363                 :             : 
     364                 :             :         /* Get pg_class.relpartbound */
     365                 :         494 :         tuple = SearchSysCache1(RELOID,
     366                 :         494 :                                                         ObjectIdGetDatum(RelationGetRelid(rel)));
     367         [ +  - ]:         494 :         if (!HeapTupleIsValid(tuple))
     368   [ #  #  #  # ]:           0 :                 elog(ERROR, "cache lookup failed for relation %u",
     369                 :             :                          RelationGetRelid(rel));
     370                 :             : 
     371                 :         494 :         boundDatum = SysCacheGetAttr(RELOID, tuple,
     372                 :             :                                                                  Anum_pg_class_relpartbound,
     373                 :             :                                                                  &isnull);
     374         [ +  + ]:         494 :         if (!isnull)
     375                 :             :         {
     376                 :         492 :                 PartitionBoundSpec *bound;
     377                 :             : 
     378                 :         492 :                 bound = castNode(PartitionBoundSpec,
     379                 :             :                                                  stringToNode(TextDatumGetCString(boundDatum)));
     380                 :             : 
     381                 :         492 :                 my_qual = get_qual_from_partbound(parent, bound);
     382                 :         492 :         }
     383                 :             : 
     384                 :         494 :         ReleaseSysCache(tuple);
     385                 :             : 
     386                 :             :         /* Add the parent's quals to the list (if any) */
     387         [ +  + ]:         494 :         if (parent->rd_rel->relispartition)
     388                 :          95 :                 result = list_concat(generate_partition_qual(parent), my_qual);
     389                 :             :         else
     390                 :         399 :                 result = my_qual;
     391                 :             : 
     392                 :             :         /*
     393                 :             :          * Change Vars to have partition's attnos instead of the parent's. We do
     394                 :             :          * this after we concatenate the parent's quals, because we want every Var
     395                 :             :          * in it to bear this relation's attnos. It's safe to assume varno = 1
     396                 :             :          * here.
     397                 :             :          */
     398                 :         494 :         result = map_partition_varattnos(result, 1, rel, parent);
     399                 :             : 
     400                 :             :         /* Assert that we're not leaking any old data during assignments below */
     401         [ +  - ]:         494 :         Assert(rel->rd_partcheckcxt == NULL);
     402         [ +  - ]:         494 :         Assert(rel->rd_partcheck == NIL);
     403                 :             : 
     404                 :             :         /*
     405                 :             :          * Save a copy in the relcache.  The order of these operations is fairly
     406                 :             :          * critical to avoid memory leaks and ensure that we don't leave a corrupt
     407                 :             :          * relcache entry if we fail partway through copyObject.
     408                 :             :          *
     409                 :             :          * If, as is definitely possible, the partcheck list is NIL, then we do
     410                 :             :          * not need to make a context to hold it.
     411                 :             :          */
     412         [ +  + ]:         494 :         if (result != NIL)
     413                 :             :         {
     414                 :         485 :                 rel->rd_partcheckcxt = AllocSetContextCreate(CacheMemoryContext,
     415                 :             :                                                                                                          "partition constraint",
     416                 :             :                                                                                                          ALLOCSET_SMALL_SIZES);
     417                 :         485 :                 MemoryContextCopyAndSetIdentifier(rel->rd_partcheckcxt,
     418                 :             :                                                                                   RelationGetRelationName(rel));
     419                 :         485 :                 oldcxt = MemoryContextSwitchTo(rel->rd_partcheckcxt);
     420                 :         485 :                 rel->rd_partcheck = copyObject(result);
     421                 :         485 :                 MemoryContextSwitchTo(oldcxt);
     422                 :         485 :         }
     423                 :             :         else
     424                 :           9 :                 rel->rd_partcheck = NIL;
     425                 :         494 :         rel->rd_partcheckvalid = true;
     426                 :             : 
     427                 :             :         /* Keep the parent locked until commit */
     428                 :         494 :         relation_close(parent, NoLock);
     429                 :             : 
     430                 :             :         /* Return the working copy to the caller */
     431                 :         494 :         return result;
     432                 :        1278 : }
        

Generated by: LCOV version 2.3.2-1