Add support for extended properties in pg proppatch webdav

Mon, 15 Aug 2022 15:45:55 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 15 Aug 2022 15:45:55 +0200
branch
webdav
changeset 377
c011bc2b3143
parent 376
61d481d3c2e4
child 378
0344108db255

Add support for extended properties in pg proppatch

src/server/plugins/postgresql/webdav.c file | annotate | diff | comparison | revisions
src/server/plugins/postgresql/webdav.h file | annotate | diff | comparison | revisions
--- 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 <table> (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 <table> 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, &params, &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, &params, &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;i<proppatch->numext;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;
 }
 
--- 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);

mercurial