# HG changeset patch # User Olaf Wintermann # Date 1660571155 -7200 # Node ID c011bc2b314316ed1713507333aabc4e4f3b0675 # Parent 61d481d3c2e4a4767d995fccacc8b55b0dce1a60 Add support for extended properties in pg proppatch diff -r 61d481d3c2e4 -r c011bc2b3143 src/server/plugins/postgresql/webdav.c --- a/src/server/plugins/postgresql/webdav.c Sun Aug 14 16:46:52 2022 +0200 +++ b/src/server/plugins/postgresql/webdav.c Mon Aug 15 15:45:55 2022 +0200 @@ -460,7 +460,6 @@ if(ns) { UcxKey pkey = webdav_property_key((const char*)ns->href, cur->property->name); PgPropertyStoreExt *cfg_ext = ucx_map_get(pgdav->repository->prop_ext, pkey); - char *pkey_data = pkey.data; free((void*)pkey.data); if(cfg_ext) { PgPropfindExtCol extcol; @@ -889,6 +888,41 @@ return result; } +#define PG_PROPPATCH_EXT_SET 0 +#define PG_PROPPATCH_EXT_REMOVE 1 + +static int pg_proppatch_add_ext_prop( + pool_handle_t *pool, + PgWebdavBackend *pgdav, + PgProppatch *proppatch, + WebdavProperty *property, + int proppatch_op) +{ + UcxKey pkey = webdav_property_key((const char*)property->namespace->href, property->name); + PgPropertyStoreExt *ext = ucx_map_get(pgdav->repository->prop_ext, pkey); + free((void*)pkey.data); + if(ext) { + PgProppatchExtProp *ext_prop = pool_malloc(pool, sizeof(PgProppatchExtProp)); + if(!ext_prop) { + return 1; + } + ext_prop->column = ext; + ext_prop->property = property; + + UcxAllocator a = util_pool_allocator(pool); + proppatch->ext[ext->tableindex].isused = TRUE; + + UcxList **list = proppatch_op == PG_PROPPATCH_EXT_REMOVE + ? &proppatch->ext[ext->tableindex].remove + : &proppatch->ext[ext->tableindex].set; + *list = ucx_list_append_a(&a, *list, ext_prop); + + proppatch->extensions_used = TRUE; + } + + return 0; +} + static int pg_dav_set_property( PgWebdavBackend *pgdav, WebdavProppatchRequest *request, @@ -897,7 +931,14 @@ void *userdata) { pool_handle_t *pool = request->sn->pool; + PgProppatch *proppatch = request->userdata; WSNamespace *ns = property->namespace; + + // check if the property belongs to an extension + if(proppatch->ext && ns) { + return pg_proppatch_add_ext_prop(pool, pgdav, proppatch, property, PG_PROPPATCH_EXT_SET); + } + char *resource_id_str = userdata; int ret = 0; @@ -951,7 +992,14 @@ void *userdata) { pool_handle_t *pool = request->sn->pool; + PgProppatch *proppatch = request->userdata; WSNamespace *ns = property->namespace; + + // check if the property belongs to an extension + if(proppatch->ext && ns) { + return pg_proppatch_add_ext_prop(pool, pgdav, proppatch, property, PG_PROPPATCH_EXT_REMOVE); + } + char *resource_id_str = userdata; int ret = 0; @@ -978,6 +1026,274 @@ return ret; } + +/* + * Creates an SQL query for inserting a new row to an extension table + * A parameter list for PQexecParams will also be generated, however + * params[0] will be empty (resource_id str) + * + * Query: insert into (resource_id, col1, ...) values ($1, $2 ...); + */ +static UcxBuffer* ext_row_create_insert_query(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table, char *** params, size_t *nparams) { + pool_handle_t *pool = request->sn->pool; + + UcxBuffer *sql = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND); + if(!sql) { + return NULL; + } + + size_t pg_nparams = ucx_list_size(ext->set) + 1; + char** pg_params = pool_calloc(pool, pg_nparams, sizeof(char*)); + if(!pg_params) { + ucx_buffer_free(sql); + return NULL; + } + + ucx_buffer_puts(sql, "insert into "); + ucx_buffer_puts(sql, table->table); + ucx_buffer_puts(sql, "(resource_id"); + UCX_FOREACH(elm, ext->set) { + PgProppatchExtProp *prop = elm->data; + ucx_bprintf(sql, ",%s", prop->column->name); + } + + ucx_buffer_puts(sql, ") values ($1\n"); + int i = 1; + UCX_FOREACH(elm, ext->set) { + PgProppatchExtProp *prop = elm->data; + WebdavProperty *property = prop->property; + // convert the property value to WSXmlData + // property->vtype == WS_VALUE_XML_NODE should always be true + WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL; + char *value_str = NULL; + //char *nsdef_str = NULL; + if(property_value) { + value_str = property_value->data; + if(property_value->namespaces) { + // currently only text data is supported + pool_free(pool, params); + ucx_buffer_free(sql); + return NULL; + } + } + + pg_params[i] = value_str; + ucx_bprintf(sql, ",$%d", ++i); + } + ucx_buffer_puts(sql, ");"); + + + //printf("\n\n%.*s\n\n", (int)sql->size, sql->space); + //fflush(stdout); + + *params = pg_params; + *nparams = pg_nparams; + + return sql; +} + +/* + * Creates an SQL query for updating an extension table row + * A parameter list for PQexecParams will also be generated, however + * params[0] will be empty (resource_id str) + * + * Query: update
set + * col1 = $2, + * col2 = $3, + * ... + * where resource_id = $1 ; + */ +static UcxBuffer* ext_row_create_update_query(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table, char *** params, size_t *nparams) { + pool_handle_t *pool = request->sn->pool; + + UcxBuffer *sql = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND); + if(!sql) { + return NULL; + } + + ucx_buffer_puts(sql, "update "); + ucx_buffer_puts(sql, table->table); + ucx_buffer_puts(sql, " set\n"); + + size_t pg_nparams = ucx_list_size(ext->set) + 1; + char** pg_params = pool_calloc(pool, pg_nparams, sizeof(char*)); + if(!pg_params) { + ucx_buffer_free(sql); + return NULL; + } + + int i = 1; + UCX_FOREACH(elm, ext->set) { + PgProppatchExtProp *prop = elm->data; + WebdavProperty *property = prop->property; + // convert the property value to WSXmlData + // property->vtype == WS_VALUE_XML_NODE should always be true + WSXmlData *property_value = property->vtype == WS_VALUE_XML_NODE ? wsxml_node2data(pool, property->value.node) : NULL; + char *value_str = NULL; + //char *nsdef_str = NULL; + if(property_value) { + value_str = property_value->data; + if(property_value->namespaces) { + // currently only text data is supported + pool_free(pool, params); + ucx_buffer_free(sql); + return NULL; + } + } + + pg_params[i] = value_str; + ucx_bprintf(sql, " %s = $%d,\n", prop->column->name, ++i); + } + + UCX_FOREACH(elm, ext->remove) { + PgProppatchExtProp *prop = elm->data; + ucx_bprintf(sql, " %s = NULL,\n", prop->column->name); + } + + // check if any write worked + if(sql->pos == 0) { + ucx_buffer_free(sql); + pool_free(pool, pg_params); + return NULL; + } + + // last line should end with ',' '\n' + // replace ',' with space + if(sql->space[sql->pos-2] == ',') { + sql->space[sql->pos-2] = ' '; + } + + ucx_bprintf(sql, "where resource_id = $1 ;\0"); + + //printf("\n\n%.*s\n\n", (int)sql->size, sql->space); + //fflush(stdout); + + *params = pg_params; + *nparams = pg_nparams; + + return sql; +} + +/* + * Executes an SQL insert for the extension table + */ +int ext_row_insert(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table) { + PgWebdavBackend *pgdav = request->dav->instance; + PgProppatch *proppatch = request->userdata; + pool_handle_t *pool = request->sn->pool; + + char **params; + size_t nparam; + UcxBuffer *sql = ext_row_create_insert_query(request, ext, table, ¶ms, &nparam); + if(!sql) { + return 1; + } + + char resource_id_str[32]; + snprintf(resource_id_str, 32, "%" PRId64, proppatch->resource_id); + params[0] = resource_id_str; + + PGresult *result = PQexecParams( + pgdav->connection, + sql->space, + nparam, // number of parameters + NULL, + ( const char *const *)params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + ucx_buffer_free(sql); + + int ret = 1; + if(PQresultStatus(result) == PGRES_COMMAND_OK) { + // command ok, check if any row was updated + char *nrows_affected = PQcmdTuples(result); + if(nrows_affected[0] == '1') { + ret = 0; + } else { + log_ereport(LOG_FAILURE, "pg: extension row insert failed"); + } + } else { + log_ereport(LOG_FAILURE, "pg: extension row insert failed: %s", PQresultErrorMessage(result)); + } + + + PQclear(result); + + return ret; +} + +/* + * Executes an SQL update for the extension table + */ +int ext_row_update(WebdavProppatchRequest *request, PgProppatchExt *ext, PgExtTable *table) { + PgWebdavBackend *pgdav = request->dav->instance; + PgProppatch *proppatch = request->userdata; + pool_handle_t *pool = request->sn->pool; + + char **params; + size_t nparam; + UcxBuffer *sql = ext_row_create_update_query(request, ext, table, ¶ms, &nparam); + if(!sql) { + return 1; + } + + char resource_id_str[32]; + snprintf(resource_id_str, 32, "%" PRId64, proppatch->resource_id); + params[0] = resource_id_str; + + PGresult *result = PQexecParams( + pgdav->connection, + sql->space, + nparam, // number of parameters + NULL, + ( const char *const *)params, // parameter value + NULL, + NULL, + 0); // 0: result in text format + + ucx_buffer_free(sql); + + int ret = 1; + if(PQresultStatus(result) == PGRES_COMMAND_OK) { + // command ok, check if any row was updated + char *nrows_affected = PQcmdTuples(result); + if(nrows_affected[0] == '0') { + // no rows affected, that means we have to insert a new row + // in the extension table for this resource + + // TODO: cleanup params + + ret = ext_row_insert(request, ext, table); + } + + } else { + log_ereport(LOG_FAILURE, "pg: extension row update failed: %s", PQresultErrorMessage(result)); + } + + + PQclear(result); + + return ret; +} + +static int pg_dav_update_extension_tables(WebdavProppatchRequest *request) { + PgWebdavBackend *pgdav = request->dav->instance; + PgProppatch *proppatch = request->userdata; + + for(int i=0;inumext;i++) { + if(proppatch->ext[i].isused) { + if(ext_row_update(request, &proppatch->ext[i], &pgdav->repository->tables[i])) { + // extension proppatch failed + return 1; + } + } + } + + return 0; +} + int pg_dav_proppatch_do( WebdavProppatchRequest *request, WebdavResource *response, @@ -989,6 +1305,25 @@ pool_handle_t *pool = request->sn->pool; char *path = pblock_findkeyval(pb_key_path, request->rq->vars); + PgProppatch proppatch; + if(pgdav->repository->ntables == 0) { + proppatch.ext = NULL; + proppatch.numext = 0; + } else { + // some properties are stored in additional tables + // for each table we create a PgProppatchExt record + // which stores data about, which tables are used + // and which properties (columns) should be updated + // + // proppatch.ext[i] should contain the data for repository->tables[i] + proppatch.numext = pgdav->repository->ntables; + proppatch.ext = pool_calloc(request->sn->pool, proppatch.numext, sizeof(PgProppatchExt)); + if(!proppatch.ext) { + return 1; // OOM + } + } + request->userdata = &proppatch; + // check if the resource exists, we also need the resource_id int64_t parent_id; int64_t resource_id; @@ -1012,6 +1347,8 @@ return 1; } + proppatch.resource_id = resource_id; + // because proppatch must be atomic and we have multiple sql // queries and other backends that do stuff that could fail // we need the possibility to reverse all changes @@ -1048,8 +1385,16 @@ resource_id_str); if(rm_res.error) { return 1; - } + } + // if extensions are in use and pg_proppatch_op found any + // properties, that should be stored in extension tables + // we do the update/insert now + if(proppatch.extensions_used) { + ret = pg_dav_update_extension_tables(request); + } + + return ret; } diff -r 61d481d3c2e4 -r c011bc2b3143 src/server/plugins/postgresql/webdav.h --- a/src/server/plugins/postgresql/webdav.h Sun Aug 14 16:46:52 2022 +0200 +++ b/src/server/plugins/postgresql/webdav.h Mon Aug 15 15:45:55 2022 +0200 @@ -71,6 +71,24 @@ int nrows; } PgPropfind; +typedef struct { + PgPropertyStoreExt *column; + WebdavProperty *property; +} PgProppatchExtProp; + +typedef struct { + UcxList *set; /* list of PgProppatchExtProp* */ + UcxList *remove; /* list of PgProppatchExtProp* */ + WSBool isused; +} PgProppatchExt; + +typedef struct { + int64_t resource_id; + PgProppatchExt *ext; + size_t numext; + WSBool extensions_used; +} PgProppatch; + void* pg_webdav_init(ServerConfiguration *cfg, pool_handle_t *pool, WSConfigNode *config); WebdavBackend* pg_webdav_create(Session *sn, Request *rq, pblock *pb, void *initData); WebdavBackend* pg_webdav_create_from_resdata(Session *sn, Request *rq, PgRepository *repo, ResourceData *resdata);