Add support for HTTP patch and application of patch scripts to server and data store components.

git-svn-id: https://svn.apache.org/repos/asf/tuscany/sca-cpp/trunk@1428192 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/.gitignore b/.gitignore
index fc7f1cb..42539a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -152,4 +152,5 @@
 chat-send
 opencl-shell
 opencl-test
+patch-test
 
diff --git a/components/cache/client-test.cpp b/components/cache/client-test.cpp
index 5e9be6c..3c8a261 100644
--- a/components/cache/client-test.cpp
+++ b/components/cache/client-test.cpp
@@ -81,6 +81,32 @@
         assert(hasContent(val));
         assert(content(val) == b);
     }
+
+    const list<value> k = nilListValue + "content" + (nilListValue + "item" 
+            + (nilListValue + "name" + string("Apple"))
+            + (nilListValue + "price" + string("$3.99")));
+    const list<value> c = nilListValue + (nilListValue + "entry" 
+            + (nilListValue + "title" + string("item"))
+            + (nilListValue + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))
+            + k);
+
+    {
+        const list<value> s = nilListValue + "content" +
+            (nilListValue + "patch" + string("(define (patch id e) (tree-subst-assoc '(price) '(price \"$3.99\") e))"));
+        const list<value> ps = nilListValue + (nilListValue + "entry" 
+                + (nilListValue + "title" + string("item"))
+                + (nilListValue + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))
+                + s);
+
+        const failable<value> r = http::patch(ps, uri + p, cs);
+        assert(hasContent(r));
+        assert(content(r) == trueValue);
+    }
+    {
+        const failable<value> val = http::get(uri + p, cs);
+        assert(hasContent(val));
+        assert(content(val) == c);
+    }
     {
         const failable<value> r = http::del(uri + p, cs);
         assert(hasContent(r));
diff --git a/components/cache/datacache.cpp b/components/cache/datacache.cpp
index 975ca43..4fafd9e 100644
--- a/components/cache/datacache.cpp
+++ b/components/cache/datacache.cpp
@@ -95,6 +95,20 @@
 }
 
 /**
+ * Patch an item in the cache.
+ */
+const failable<value> patch(const value& key, const value& val, unused const lvvlambda& rcache1, const lvvlambda& wcache1, unused const lvvlambda& rcache2, const lvvlambda& wcache2) {
+
+    // Update level1 cache
+    wcache1(mklist<value>("patch", key, val));
+
+    // Update level2 cache
+    wcache2(mklist<value>("patch", key, val));
+
+    return trueValue;
+}
+
+/**
  * Delete an item from the cache.
  */
 const failable<value> del(const value& key, unused const lvvlambda& rcache1, const lvvlambda& wcache1, unused const lvvlambda& rcache2, const lvvlambda& wcache2) {
@@ -121,6 +135,8 @@
         return tuscany::datacache::post(cadr(params), caddr(params), cadddr(params), caddddr(params), cadddddr(params), caddddddr(params));
     if (func == "put")
         return tuscany::datacache::put(cadr(params), caddr(params), cadddr(params), caddddr(params), cadddddr(params), caddddddr(params));
+    if (func == "patch")
+        return tuscany::datacache::patch(cadr(params), caddr(params), cadddr(params), caddddr(params), cadddddr(params), caddddddr(params));
     if (func == "delete")
         return tuscany::datacache::del(cadr(params), caddr(params), cadddr(params), caddddr(params), cadddddr(params));
     return tuscany::mkfailure<tuscany::value>();
diff --git a/components/cache/memcache-test.cpp b/components/cache/memcache-test.cpp
index 6c6adb0..10eda45 100644
--- a/components/cache/memcache-test.cpp
+++ b/components/cache/memcache-test.cpp
@@ -40,6 +40,8 @@
     assert(get(k, ch) == value(string("AAA")));
     assert(hasContent(put(k, string("aaa"), ch)));
     assert(get(k, ch) == value(string("aaa")));
+    assert(hasContent(patch(k, string("bbb"), ch)));
+    assert(get(k, ch) == value(string("bbb")));
     assert(hasContent(del(k, ch)));
     assert(!hasContent(get(k, ch)));
 
diff --git a/components/cache/memcache.cpp b/components/cache/memcache.cpp
index 2e4597e..e1a3c7e 100644
--- a/components/cache/memcache.cpp
+++ b/components/cache/memcache.cpp
@@ -63,6 +63,39 @@
 }
 
 /**
+ * Patch an item in the cache.
+ */
+const failable<value> patch(const list<value>& params, const memcache::MemCached& ch) {
+    // Read patch
+    value p = assoc<value>("patch", assoc<value>("content", car<value>(cadr(params))));
+    if (isNil(p))
+        return mkfailure<value>("Couldn't read patch script");
+    const string script = cadr<value>(p);
+    debug(script, "memcache::patch::script");
+    istringstream is(script);
+
+    // Get existing value from cache
+    const failable<value> ival = memcache::get(car(params), ch);
+    if (!hasContent(ival) && rcode(ival) != 404)
+        return mkfailure<value>(ival);
+
+    // Apply patch
+    scheme::Env env = scheme::setupEnvironment();
+    const value pval = scheme::evalScript(cons<value>("patch", scheme::quotedParameters(mklist<value>(car(params), hasContent(ival)? content(ival) : (value)list<value>()))), is, env);
+    if (isNil(pval)) {
+        ostringstream os;
+        os << "Couldn't patch memcached entry: " << car(params);
+        return mkfailure<value>(str(os), 404, false);
+    }
+
+    // Push patched value to cache
+    const failable<bool> val = memcache::patch(car(params), pval, ch);
+    if (!hasContent(val))
+        return mkfailure<value>(val);
+    return value(content(val));
+}
+
+/**
  * Delete an item from the cache.
  */
 const failable<value> del(const list<value>& params, const memcache::MemCached& ch) {
@@ -98,6 +131,8 @@
             return post(cdr(params), ch);
         if (func == "put")
             return put(cdr(params), ch);
+        if (func == "patch")
+            return patch(cdr(params), ch);
         if (func == "delete")
             return del(cdr(params), ch);
         return mkfailure<value>();
diff --git a/components/cache/memcache.hpp b/components/cache/memcache.hpp
index 00ee9c6..7962d1c 100644
--- a/components/cache/memcache.hpp
+++ b/components/cache/memcache.hpp
@@ -73,6 +73,7 @@
 
     friend const failable<bool> post(const value& key, const value& val, const MemCached& cache);
     friend const failable<bool> put(const value& key, const value& val, const MemCached& cache);
+    friend const failable<bool> patch(const value& key, const value& val, const MemCached& cache);
     friend const failable<value> get(const value& key, const MemCached& cache);
     friend const failable<bool> del(const value& key, const MemCached& cache);
 
@@ -177,6 +178,26 @@
 }
 
 /**
+ * Patch an item in the cache. If the item doesn't exist it is added.
+ */
+const failable<bool> patch(const value& key, const value& val, const MemCached& cache) {
+    debug(key, "memcache::patch::key");
+    debug(val, "memcache::patch::value");
+
+    const string ks(write(content(scheme::writeValue(key))));
+    const string vs(write(content(scheme::writeValue(val))));
+    const apr_status_t rc = apr_memcache_set(cache.mc, nospaces(c_str(ks)), const_cast<char*>(c_str(vs)), length(vs), 0, 27);
+    if (rc != APR_SUCCESS) {
+        ostringstream os;
+        os << "Couldn't set memcached entry: " << key;
+        return mkfailure<bool>(str(os));
+    }
+
+    debug(true, "memcache::patch::result");
+    return true;
+}
+
+/**
  * Get an item from the cache.
  */
 const failable<value> get(const value& key, const MemCached& cache) {
@@ -209,7 +230,7 @@
     if (rc != APR_SUCCESS) {
         ostringstream os;
         os << "Couldn't delete memcached entry: " << key;
-        return mkfailure<bool>(str(os));
+        return rc == APR_NOTFOUND? mkfailure<bool>(str(os), 404, false) : mkfailure<bool>(str(os));
     }
 
     debug(true, "memcache::delete::result");
diff --git a/components/cache/partitioner.cpp b/components/cache/partitioner.cpp
index a38c053..8a56a7f 100644
--- a/components/cache/partitioner.cpp
+++ b/components/cache/partitioner.cpp
@@ -143,6 +143,23 @@
 }
 
 /**
+ * Patch an item in a partition.
+ */
+const failable<value> patch(const value& key, const value& val, const lvvlambda& selector, const list<value>& partitions) {
+
+    // Select partition
+    const failable<list<value> > p = partition(key, selector, partitions);
+    if (!hasContent(p))
+        return mkfailure<value>(p);
+
+    // Path item in selected partition
+    const lvvlambda l = car(content(p));
+    l(mklist<value>("patch", key, val));
+
+    return trueValue;
+}
+
+/**
  * Delete an item from a partition.
  */
 const failable<value> del(const value& key, const lvvlambda& selector, const list<value>& partitions) {
@@ -172,6 +189,8 @@
         return tuscany::partitioner::post(cadr(params), caddr(params), cadddr(params), cddddr(params));
     if (func == "put")
         return tuscany::partitioner::put(cadr(params), caddr(params), cadddr(params), cddddr(params));
+    if (func == "patch")
+        return tuscany::partitioner::patch(cadr(params), caddr(params), cadddr(params), cddddr(params));
     if (func == "delete")
         return tuscany::partitioner::del(cadr(params), caddr(params), cdddr(params));
     return tuscany::mkfailure<tuscany::value>();
diff --git a/components/constdb/client-test.cpp b/components/constdb/client-test.cpp
index b796ef0..165e3d8 100644
--- a/components/constdb/client-test.cpp
+++ b/components/constdb/client-test.cpp
@@ -77,6 +77,32 @@
         assert(hasContent(val));
         assert(content(val) == b);
     }
+
+    const list<value> k = nilListValue + "content" + (nilListValue + "item" 
+            + (nilListValue + "name" + string("Apple"))
+            + (nilListValue + "price" + string("$3.99")));
+    const list<value> c = nilListValue + (nilListValue + "entry" 
+            + (nilListValue + "title" + string("item"))
+            + (nilListValue + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))
+            + k);
+
+    {
+        const list<value> s = nilListValue + "content" +
+            (nilListValue + "patch" + string("(define (patch id e) (tree-subst-assoc '(price) '(price \"$3.99\") e))"));
+        const list<value> ps = nilListValue + (nilListValue + "entry" 
+                + (nilListValue + "title" + string("item"))
+                + (nilListValue + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))
+                + s);
+
+        const failable<value> r = http::patch(ps, uri + p, cs);
+        assert(hasContent(r));
+        assert(content(r) == trueValue);
+    }
+    {
+        const failable<value> val = http::get(uri + p, cs);
+        assert(hasContent(val));
+        assert(content(val) == c);
+    }
     {
         const failable<value> r = http::del(uri + p, cs);
         assert(hasContent(r));
diff --git a/components/constdb/constdb.cpp b/components/constdb/constdb.cpp
index a9a5bc5..2d34ed2 100644
--- a/components/constdb/constdb.cpp
+++ b/components/constdb/constdb.cpp
@@ -63,6 +63,39 @@
 }
 
 /**
+ * Patch an item in the database.
+ */
+const failable<value> patch(const list<value>& params, const tinycdb::TinyCDB& cdb) {
+    // Read patch
+    value p = assoc<value>("patch", assoc<value>("content", car<value>(cadr(params))));
+    if (isNil(p))
+        return mkfailure<value>("Couldn't read patch script");
+    const string script = cadr<value>(p);
+    debug(script, "tinycdb::patch::script");
+    istringstream is(script);
+
+    // Get existing value from database
+    const failable<value> ival = tinycdb::get(car(params), cdb);
+    if (!hasContent(ival) && rcode(ival) != 404)
+        return mkfailure<value>(ival);
+
+    // Apply patch
+    scheme::Env env = scheme::setupEnvironment();
+    const value pval = scheme::evalScript(cons<value>("patch", scheme::quotedParameters(mklist<value>(car(params), hasContent(ival)? content(ival) : (value)list<value>()))), is, env);
+    if (isNil(pval)) {
+        ostringstream os;
+        os << "Couldn't patch tinycdb entry: " << car(params);
+        return mkfailure<value>(str(os), 404, false);
+    }
+
+    // Push patched value to database
+    const failable<bool> val = tinycdb::patch(car(params), pval, cdb);
+    if (!hasContent(val))
+        return mkfailure<value>(val);
+    return value(content(val));
+}
+
+/**
  * Delete an item from the database.
  */
 const failable<value> del(const list<value>& params, const tinycdb::TinyCDB& cdb) {
@@ -89,6 +122,8 @@
             return post(cdr(params), cdb);
         if (func == "put")
             return put(cdr(params), cdb);
+        if (func == "patch")
+            return patch(cdr(params), cdb);
         if (func == "delete")
             return del(cdr(params), cdb);
         return mkfailure<value>();
diff --git a/components/constdb/tinycdb-test.cpp b/components/constdb/tinycdb-test.cpp
index 6cc9e9e..41bfe12 100644
--- a/components/constdb/tinycdb-test.cpp
+++ b/components/constdb/tinycdb-test.cpp
@@ -40,6 +40,8 @@
     assert((get(k, cdb)) == value(string("AAA")));
     assert(hasContent(put(k, string("aaa"), cdb)));
     assert((get(k, cdb)) == value(string("aaa")));
+    assert(hasContent(patch(k, string("bbb"), cdb)));
+    assert((get(k, cdb)) == value(string("bbb")));
     assert(hasContent(del(k, cdb)));
     assert(!hasContent(get(k, cdb)));
 
diff --git a/components/constdb/tinycdb.hpp b/components/constdb/tinycdb.hpp
index ce1dcbb..3da5f3c 100644
--- a/components/constdb/tinycdb.hpp
+++ b/components/constdb/tinycdb.hpp
@@ -389,6 +389,37 @@
 }
 
 /**
+ * Patch an item in the database. If the item doesn't exist it is added.
+ */
+const failable<bool> patch(const value& key, const value& val, const TinyCDB& cdb) {
+    debug(key, "tinycdb::patch::key");
+    debug(val, "tinycdb::patch::value");
+    debug(dbname(cdb), "tinycdb::patch::dbname");
+
+    const string ks(write(content(scheme::writeValue(key))));
+    const string vs(write(content(scheme::writeValue(val))));
+
+    // Process each entry and skip existing key
+    const lambda<const failable<bool>(buffer&, const unsigned int, const unsigned int)> update = [ks](buffer& buf, const unsigned int klen, unused const unsigned int vlen) -> const failable<bool> {
+        if (ks == string((char*)buf, klen))
+            return false;
+        return true;
+    };
+
+    // Add the new entry to the db
+    const lambda<const failable<bool>(struct cdb_make&)> finish = [ks, vs](struct cdb_make& cdbm) -> const failable<bool> {
+        if (cdb_make_add(&cdbm, c_str(ks), (unsigned int)length(ks), c_str(vs), (unsigned int)length(vs)) == -1)
+            return mkfailure<bool>(string("Couldn't add tinycdb entry: ") + ks);
+        return true;
+    };
+
+    // Rewrite the db
+    const failable<bool> r = rewrite(update, finish, cdb);
+    debug(r, "tinycdb::patch::result");
+    return r;
+}
+
+/**
  * Get an item from the database.
  */
 const failable<value> get(const value& key, const TinyCDB& cdb) {
@@ -425,11 +456,14 @@
     debug(dbname(cdb), "tinycdb::delete::dbname");
 
     const string ks(write(content(scheme::writeValue(key))));
+    bool found = false;
 
     // Process each entry and skip existing key
-    const lambda<const failable<bool>(buffer&, const unsigned int, const unsigned int)> update = [ks](buffer& buf, const unsigned int klen, unused const unsigned int vlen) -> const failable<bool> {
-        if (ks == string((char*)buf, klen))
+    const lambda<const failable<bool>(buffer&, const unsigned int, const unsigned int)> update = [ks, &found](buffer& buf, const unsigned int klen, unused const unsigned int vlen) -> const failable<bool> {
+        if (ks == string((char*)buf, klen)) {
+            found = true;
             return false;
+        }
         return true;
     };
 
@@ -440,6 +474,11 @@
 
     // Rewrite the db
     const failable<bool> r = rewrite(update, finish, cdb);
+    if (!hasContent(r) || !found) {
+        ostringstream os;
+        os << "Couldn't delete tinycdb entry: " << key;
+        return hasContent(r)? mkfailure<bool>(str(os), 404, false) : r;
+    }
     debug(r, "tinycdb::delete::result");
     return r;
 }
diff --git a/components/filedb/client-test.cpp b/components/filedb/client-test.cpp
index 5694d97..96c0212 100644
--- a/components/filedb/client-test.cpp
+++ b/components/filedb/client-test.cpp
@@ -77,6 +77,32 @@
         assert(hasContent(val));
         assert(content(val) == b);
     }
+
+    const list<value> k = nilListValue + "content" + (nilListValue + "item" 
+            + (nilListValue + "name" + string("Apple"))
+            + (nilListValue + "price" + string("$3.99")));
+    const list<value> c = nilListValue + (nilListValue + "entry" 
+            + (nilListValue + "title" + string("item"))
+            + (nilListValue + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))
+            + k);
+
+    {
+        const list<value> s = nilListValue + "content" +
+            (nilListValue + "patch" + string("(define (patch id e) (tree-subst-assoc '(price) '(price \"$3.99\") e))"));
+        const list<value> ps = nilListValue + (nilListValue + "entry" 
+                + (nilListValue + "title" + string("item"))
+                + (nilListValue + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))
+                + s);
+
+        const failable<value> r = http::patch(ps, uri + p, cs);
+        assert(hasContent(r));
+        assert(content(r) == trueValue);
+    }
+    {
+        const failable<value> val = http::get(uri + p, cs);
+        assert(hasContent(val));
+        assert(content(val) == c);
+    }
     {
         const failable<value> r = http::del(uri + p, cs);
         assert(hasContent(r));
diff --git a/components/filedb/file-test.cpp b/components/filedb/file-test.cpp
index 5270967..5527280 100644
--- a/components/filedb/file-test.cpp
+++ b/components/filedb/file-test.cpp
@@ -38,11 +38,14 @@
 
     const list<value> a = mklist<value>(nilListValue + "ns1:a" + (nilListValue + "@xmlns:ns1" + string("http://aaa")) + (nilListValue + "text" + string("Hey!")));
     const list<value> b = mklist<value>(nilListValue + "ns1:b" + (nilListValue + "@xmlns:ns1" + string("http://bbb")) + (nilListValue + "text" + string("Hey!")));
+    const list<value> c = mklist<value>(nilListValue + "ns1:c" + (nilListValue + "@xmlns:ns1" + string("http://ccc")) + (nilListValue + "text" + string("Hey!")));
 
     assert(hasContent(post(k, a, db)));
     assert((get(k, db)) == value(a));
     assert(hasContent(put(k, b, db)));
     assert((get(k, db)) == value(b));
+    assert(hasContent(patch(k, c, db)));
+    assert((get(k, db)) == value(c));
     assert(hasContent(del(k, db)));
     assert(!hasContent(get(k, db)));
     assert(hasContent(post(k, a, db)));
diff --git a/components/filedb/filedb.cpp b/components/filedb/filedb.cpp
index 37cb6c5..b28cc9b 100644
--- a/components/filedb/filedb.cpp
+++ b/components/filedb/filedb.cpp
@@ -63,6 +63,39 @@
 }
 
 /**
+ * Patch an item in the database.
+ */
+const failable<value> patch(const list<value>& params, const filedb::FileDB& db) {
+    // Read patch
+    value p = assoc<value>("patch", assoc<value>("content", car<value>(cadr(params))));
+    if (isNil(p))
+        return mkfailure<value>("Couldn't read patch script");
+    const string script = cadr<value>(p);
+    debug(script, "filedb::patch::script");
+    istringstream is(script);
+
+    // Get existing value from database
+    const failable<value> ival = filedb::get(car(params), db);
+    if (!hasContent(ival) && rcode(ival) != 404)
+        return mkfailure<value>(ival);
+
+    // Apply patch
+    scheme::Env env = scheme::setupEnvironment();
+    const value pval = scheme::evalScript(cons<value>("patch", scheme::quotedParameters(mklist<value>(car(params), hasContent(ival)? content(ival) : (value)list<value>()))), is, env);
+    if (isNil(pval)) {
+        ostringstream os;
+        os << "Couldn't patch file database entry: " << car(params);
+        return mkfailure<value>(str(os), 404, false);
+    }
+
+    // Push patched value to database
+    const failable<bool> val = filedb::patch(car(params), pval, db);
+    if (!hasContent(val))
+        return mkfailure<value>(val);
+    return value(content(val));
+}
+
+/**
  * Delete an item from the database.
  */
 const failable<value> del(const list<value>& params, const filedb::FileDB& db) {
@@ -91,6 +124,8 @@
             return post(cdr(params), db);
         if (func == "put")
             return put(cdr(params), db);
+        if (func == "patch")
+            return patch(cdr(params), db);
         if (func == "delete")
             return del(cdr(params), db);
         return mkfailure<value>();
diff --git a/components/filedb/filedb.hpp b/components/filedb/filedb.hpp
index 2855ceb..41dde88 100644
--- a/components/filedb/filedb.hpp
+++ b/components/filedb/filedb.hpp
@@ -82,6 +82,7 @@
     friend const failable<value> read(istream& is, const string& format);
     friend const failable<bool> post(const value& key, const value& val, const FileDB& db);
     friend const failable<bool> put(const value& key, const value& val, const FileDB& db);
+    friend const failable<bool> patch(const value& key, const value& val, const FileDB& db);
     friend const failable<value> get(const value& key, const FileDB& db);
     friend const failable<bool> del(const value& key, const FileDB& db);
 };
@@ -208,6 +209,30 @@
 }
 
 /**
+ * Patch an item in the database. If the item doesn't exist it is added.
+ */
+const failable<bool> patch(const value& key, const value& val, const FileDB& db) {
+    debug(key, "filedb::patch::key");
+    debug(val, "filedb::patch::value");
+    debug(db.name, "filedb::patch::dbname");
+
+    if (isList(key))
+        mkdirs(key, db.name);
+    const string fn = filename(key, db.name);
+    debug(fn, "filedb::patch::filename");
+    ofstream os(fn);
+    if (os.fail()) {
+        ostringstream os;
+        os << "Couldn't patch file database entry: " << key;
+        return mkfailure<bool>(str(os));
+    }
+    const failable<bool> r = write(val, os, db.format);
+
+    debug(r, "filedb::patch::result");
+    return r;
+}
+
+/**
  * Get an item from the database.
  */
 const failable<value> get(const value& key, const FileDB& db) {
@@ -241,7 +266,7 @@
     if (rc == -1) {
         ostringstream os;
         os << "Couldn't delete file database entry: " << key;
-        return mkfailure<bool>(str(os));
+        return errno == ENOENT? mkfailure<bool>(str(os), 404, false) : mkfailure<bool>(str(os));
     }
 
     debug(true, "filedb::delete::result");
diff --git a/components/sqldb/client-test.cpp b/components/sqldb/client-test.cpp
index c9fbb7d..fcdb8d3 100644
--- a/components/sqldb/client-test.cpp
+++ b/components/sqldb/client-test.cpp
@@ -77,6 +77,32 @@
         assert(hasContent(val));
         assert(content(val) == b);
     }
+
+    const list<value> k = nilListValue + "content" + (nilListValue + "item" 
+            + (nilListValue + "name" + string("Apple"))
+            + (nilListValue + "price" + string("$3.99")));
+    const list<value> c = nilListValue + (nilListValue + "entry" 
+            + (nilListValue + "title" + string("item"))
+            + (nilListValue + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))
+            + k);
+
+    {
+        const list<value> s = nilListValue + "content" +
+            (nilListValue + "patch" + string("(define (patch id e) (tree-subst-assoc '(price) '(price \"$3.99\") e))"));
+        const list<value> ps = nilListValue + (nilListValue + "entry" 
+                + (nilListValue + "title" + string("item"))
+                + (nilListValue + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))
+                + s);
+
+        const failable<value> r = http::patch(ps, uri + p, cs);
+        assert(hasContent(r));
+        assert(content(r) == trueValue);
+    }
+    {
+        const failable<value> val = http::get(uri + p, cs);
+        assert(hasContent(val));
+        assert(content(val) == c);
+    }
     {
         const failable<value> r = http::del(uri + p, cs);
         assert(hasContent(r));
diff --git a/components/sqldb/pgsql-conf b/components/sqldb/pgsql-conf
index 8adbb90..020ce12 100755
--- a/components/sqldb/pgsql-conf
+++ b/components/sqldb/pgsql-conf
@@ -96,6 +96,7 @@
 wal_level = hot_standby
 max_wal_senders = 5
 wal_keep_segments = 32
+#synchronous_standby_names = '*'
 
 EOF
 
@@ -166,7 +167,6 @@
 db = host=$host port=$port dbname=db user=bouncer
 
 [pgbouncer]
-pool_mode = session
 listen_addr = $listen
 listen_port = $bport
 unix_socket_dir =
@@ -177,9 +177,9 @@
 max_client_conn = 1000
 pool_mode = transaction
 server_reset_query =
-default_pool_size = 500
+default_pool_size = 50
 min_pool_size = 5
-reserve_pool_size = 50
+reserve_pool_size = 5
 log_connections = 0
 log_disconnections = 0
 stats_period = 3600
diff --git a/components/sqldb/pgsql-standby-conf b/components/sqldb/pgsql-standby-conf
index 5f76b5b..ae2ed8e 100755
--- a/components/sqldb/pgsql-standby-conf
+++ b/components/sqldb/pgsql-standby-conf
@@ -126,7 +126,7 @@
 
 # Start in standby mode
 standby_mode = 'on'
-primary_conninfo = 'host=$mhost port=$mport user=standby'
+primary_conninfo = 'host=$mhost port=$mport user=standby application_name=$host:$port'
 
 # Failover
 trigger_file = '$root/sqldb/failover'
@@ -165,7 +165,6 @@
 db = host=$host port=$port dbname=db user=bouncer
 
 [pgbouncer]
-pool_mode = session
 listen_addr = $listen
 listen_port = $bport
 unix_socket_dir =
@@ -176,9 +175,9 @@
 max_client_conn = 1000
 pool_mode = transaction
 server_reset_query =
-default_pool_size = 500
+default_pool_size = 50
 min_pool_size = 5
-reserve_pool_size = 50
+reserve_pool_size = 5
 log_connections = 0
 log_disconnections = 0
 stats_period = 3600
diff --git a/components/sqldb/pgsql-standby-test.cpp b/components/sqldb/pgsql-standby-test.cpp
index 5d73b0d..319194a 100644
--- a/components/sqldb/pgsql-standby-test.cpp
+++ b/components/sqldb/pgsql-standby-test.cpp
@@ -43,6 +43,9 @@
     assert(hasContent(put(k, string("aaa"), wpg)));
     sleep(1);
     assert((get(k, rpg)) == value(string("aaa")));
+    assert(hasContent(patch(k, string("bbb"), wpg)));
+    sleep(1);
+    assert((get(k, rpg)) == value(string("bbb")));
     assert(hasContent(del(k, wpg)));
     sleep(1);
     assert(!hasContent(get(k, rpg)));
diff --git a/components/sqldb/pgsql-test.cpp b/components/sqldb/pgsql-test.cpp
index 5d7bb98..ec5d537 100644
--- a/components/sqldb/pgsql-test.cpp
+++ b/components/sqldb/pgsql-test.cpp
@@ -49,6 +49,8 @@
 
     assert(hasContent(put(k, string("aaa"), pg)));
     assert(content(get(k, pg)) == value(string("aaa")));
+    assert(hasContent(patch(k, string("bbb"), pg)));
+    assert(content(get(k, pg)) == value(string("bbb")));
     assert(hasContent(del(k, pg)));
     assert(!hasContent(get(k, pg)));
 
diff --git a/components/sqldb/pgsql.hpp b/components/sqldb/pgsql.hpp
index 620aec4..5e0004c 100644
--- a/components/sqldb/pgsql.hpp
+++ b/components/sqldb/pgsql.hpp
@@ -75,12 +75,15 @@
         string ks = string("select a.attname from pg_attribute a, pg_class c where a.attrelid = c.relfilenode and c.relname = '") + table + string("' and a.attnum in (1, 2) order by a.attnum;");
         PGresult* const kr = PQexec(conn, c_str(ks));
         if (PQresultStatus(kr) != PGRES_TUPLES_OK) {
-            mkfailure<bool>(string("Couldn't execute postgresql column select statement: ") + pgfailure(kr, conn));
+            const string rs = string("Couldn't execute postgresql column select statement: ") + pgfailure(kr, conn);
+            PQclear(kr);
+            mkfailure<bool>(rs);
             return;
         }
         if (PQntuples(kr) != 2) {
+            const string rs = "Couldn't find postgresql table key and value column names";
             PQclear(kr);
-            mkfailure<bool>(string("Couldn't find postgresql table key and value column names"));
+            mkfailure<bool>(rs);
             return;
         }
         kname = c_str(string(PQgetvalue(kr, 0, 0)));
@@ -95,12 +98,10 @@
     PGSql& operator=(const PGSql& c) = delete;
 
     ~PGSql() {
-        debug("pgsql::~pgsql");
         if (!owner)
             return;
         if (conn == NULL)
             return;
-        debug(conn, "pgsql::~pgsql::conn");
         PQfinish(conn);
     }
 
@@ -113,8 +114,12 @@
     const char* vname;
 
     friend const failable<bool> setup(const PGSql& pgsql);
+    friend const failable<bool> begin(const PGSql& pgsql);
+    friend const failable<bool> commit(const PGSql& pgsql);
+    friend const failable<bool> rollback(const PGSql& pgsql);
     friend const failable<bool> post(const value& key, const value& val, const PGSql& pgsql);
     friend const failable<bool> put(const value& key, const value& val, const PGSql& pgsql);
+    friend const failable<bool> patch(const value& key, const value& val, const PGSql& pgsql);
     friend const failable<value> get(const value& key, const PGSql& pgsql);
     friend const failable<bool> del(const value& key, const PGSql& pgsql);
 };
@@ -134,6 +139,69 @@
 }
 
 /**
+ * Begin a database transaction.
+ */
+const failable<bool> begin(const PGSql& pgsql) {
+    debug("pgsql::begin");
+    debug(pgsql.conninfo, "pgsql::begin::conninfo");
+    debug(pgsql.table, "pgsql::begin::table");
+    setup(pgsql);
+
+    PGresult* const r = PQexec(pgsql.conn, "begin transaction isolation level repeatable read");
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+        const string rs = string("Couldn't execute begin SQL statement: ") + pgfailure(r, pgsql.conn);
+        PQclear(r);
+        return mkfailure<bool>(rs);
+    }
+    PQclear(r);
+
+    debug(true, "pgsql::begin::result");
+    return true;
+}
+
+/**
+ * Commit a database transaction.
+ */
+const failable<bool> commit(const PGSql& pgsql) {
+    debug("pgsql::commit");
+    debug(pgsql.conninfo, "pgsql::commit::conninfo");
+    debug(pgsql.table, "pgsql::commit::table");
+    setup(pgsql);
+
+    PGresult* const r = PQexec(pgsql.conn, "commit");
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+        const string rs = string("Couldn't execute commit SQL statement: ") + pgfailure(r, pgsql.conn);
+        PQclear(r);
+        return mkfailure<bool>(rs);
+    }
+    PQclear(r);
+
+    debug(true, "pgsql::commit::result");
+    return true;
+}
+
+/**
+ * Rollback a database transaction.
+ */
+const failable<bool> rollback(const PGSql& pgsql) {
+    debug("pgsql::rollback");
+    debug(pgsql.conninfo, "pgsql::rollback::conninfo");
+    debug(pgsql.table, "pgsql::rollback::table");
+    setup(pgsql);
+
+    PGresult* const r = PQexec(pgsql.conn, "rollback");
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+        const string rs = string("Couldn't execute rollback SQL statement: ") + pgfailure(r, pgsql.conn);
+        PQclear(r);
+        return mkfailure<bool>(rs);
+    }
+    PQclear(r);
+
+    debug(true, "pgsql::rollback::result");
+    return true;
+}
+
+/**
  * Post a new item to the database.
  */
 const failable<bool> post(const value& key, const value& val, const PGSql& pgsql) {
@@ -147,8 +215,11 @@
     const string vs(write(content(scheme::writeValue(val))));
     const char* const params[2] = { c_str(ks), c_str(vs) };
     PGresult* const r = PQexecParams(pgsql.conn, c_str(string("insert into ") + pgsql.table + string(" values($1, $2);")), 2, NULL, params, NULL, NULL, 0);
-    if (PQresultStatus(r) != PGRES_COMMAND_OK)
-        return mkfailure<bool>(string("Couldn't execute insert postgresql SQL statement: ") + pgfailure(r, pgsql.conn));
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+        const string rs = string("Couldn't execute insert postgresql SQL statement: ") + pgfailure(r, pgsql.conn);
+        PQclear(r);
+        return mkfailure<bool>(rs);
+    }
     PQclear(r);
 
     debug(true, "pgsql::post::result");
@@ -169,10 +240,13 @@
     const string vs(write(content(scheme::writeValue(val))));
     const char* const params[2] = { c_str(ks), c_str(vs) };
     PGresult* const r = PQexecParams(pgsql.conn, c_str(string("update ") + pgsql.table + string(" set ") + pgsql.vname + string(" = $2 where ") + pgsql.kname + string(" = $1;")), 2, NULL, params, NULL, NULL, 0);
-    if (PQresultStatus(r) != PGRES_COMMAND_OK)
-        return mkfailure<bool>(string("Couldn't execute update postgresql SQL statement: ") + pgfailure(r, pgsql.conn));
-    const string t = PQcmdTuples(r);
-    if (t != "0") {
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+        const string rs = string("Couldn't execute update postgresql SQL statement: ") + pgfailure(r, pgsql.conn);
+        PQclear(r);
+        return mkfailure<bool>(rs);
+    }
+    const char* const t = PQcmdTuples(r);
+    if (t != NULL && strcmp(t, "0")) {
         PQclear(r);
         debug(true, "pgsql::put::result");
         return true;
@@ -180,8 +254,11 @@
     PQclear(r);
 
     PGresult* const pr = PQexecParams(pgsql.conn, c_str(string("insert into ") + pgsql.table + string(" values($1, $2);")), 2, NULL, params, NULL, NULL, 0);
-    if (PQresultStatus(pr) != PGRES_COMMAND_OK)
-        return mkfailure<bool>(string("Couldn't execute insert postgresql SQL statement: ") + pgfailure(pr, pgsql.conn));
+    if (PQresultStatus(pr) != PGRES_COMMAND_OK) {
+        const string rs = string("Couldn't execute insert postgresql SQL statement: ") + pgfailure(pr, pgsql.conn);
+        PQclear(pr);
+        return mkfailure<bool>(rs);
+    }
     PQclear(pr);
 
     debug(true, "pgsql::put::result");
@@ -189,6 +266,57 @@
 }
 
 /**
+ * Patch an item in the database. If the item doesn't exist it is added.
+ */
+const failable<bool> patch(const value& key, const value& val, const PGSql& pgsql) {
+    debug(key, "pgsql::patch::key");
+    debug(val, "pgsql::patch::value");
+    debug(pgsql.conninfo, "pgsql::patch::conninfo");
+    debug(pgsql.table, "pgsql::patch::table");
+    setup(pgsql);
+
+    const string ks(write(content(scheme::writeValue(key))));
+    const string vs(write(content(scheme::writeValue(val))));
+    const char* const params[2] = { c_str(ks), c_str(vs) };
+    PGresult* const r = PQexecParams(pgsql.conn, c_str(string("update ") + pgsql.table + string(" set ") + pgsql.vname + string(" = $2 where ") + pgsql.kname + string(" = $1;")), 2, NULL, params, NULL, NULL, 0);
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+        const string rs = string("Couldn't execute update postgresql SQL statement: ") + pgfailure(r, pgsql.conn);
+        const char* const st = PQresultErrorField(r, PG_DIAG_SQLSTATE);
+        if (st != NULL && !strncmp(st, "40", 2)) {
+
+            // Report a transaction serialization conflict
+            PQclear(r);
+            return mkfailure<bool>(rs, 409);
+        }
+        PQclear(r);
+        return mkfailure<bool>(rs);
+    }
+    const char* const t = PQcmdTuples(r);
+    if (t != NULL && strcmp(t, "0")) {
+        PQclear(r);
+        debug(true, "pgsql::patch::result");
+        return true;
+    }
+    PQclear(r);
+
+    PGresult* const pr = PQexecParams(pgsql.conn, c_str(string("insert into ") + pgsql.table + string(" values($1, $2);")), 2, NULL, params, NULL, NULL, 0);
+    if (PQresultStatus(pr) != PGRES_COMMAND_OK) {
+        const string rs = string("Couldn't execute insert postgresql SQL statement: ") + pgfailure(pr, pgsql.conn);
+        const char* const st = PQresultErrorField(pr, PG_DIAG_SQLSTATE);
+        if (st != NULL && !strncmp(st, "40", 2)) {
+            PQclear(pr);
+            return mkfailure<bool>(rs, 40);
+        }
+        PQclear(pr);
+        return mkfailure<bool>(rs);
+    }
+    PQclear(pr);
+
+    debug(true, "pgsql::patch::result");
+    return true;
+}
+
+/**
  * Convert a key to an item id.
  */
 const list<value> keyid(const list<value>& key) {
@@ -200,14 +328,14 @@
 }
 
 /**
- * Convert a key to an param name / value assoc.
+ * Convert a key to a (param name, value) assoc.
  */
-const list<list<value> > keyparams(const list<value>& key) {
+const list<value> keyparams(const list<value>& key) {
     if (isNil(key))
         return nilListValue;
     if (!isList(car(key)))
         return keyparams(cdr(key));
-    return cons<list<value> >((list<value>)car(key), keyparams(cdr(key)));
+    return cons<value>(car(key), keyparams(cdr(key)));
 }
 
 /**
@@ -216,9 +344,8 @@
 const list<value> getitems(PGresult* const r, const int i, const int n) {
     if (i == n)
         return nilListValue;
-    const value key(content(scheme::readValue(string(PQgetvalue(r, i, 0)))));
     const value val(content(scheme::readValue(string(PQgetvalue(r, i, 1)))));
-    return cons<value>(mklist<value>(key, val), getitems(r, i + 1, n));
+    return cons<value>(val, getitems(r, i + 1, n));
 }
 
 /**
@@ -250,12 +377,13 @@
 
     // Get item and id and get parameters from the key
     const bool lk = isList(key);
-    const list<list<value> > kparams = lk? keyparams(key) : list<list<value> >();
+    const list<value> kparams = lk? keyparams(key) : nilListValue;
     const list<value> regex = assoc<value>("regex", kparams);
     const list<value> like = assoc<value>("like", kparams);
     const list<value> textsearch = assoc<value>("textsearch", kparams);
     const list<value> limit = assoc<value>("limit", kparams);
     const list<value> offset = assoc<value>("offset", kparams);
+    const list<value> rank = assoc<value>("rank", kparams);
     const list<value> id = lk? keyid(key) : nilListValue;
     const list<value> atable = assoc<value>("table", kparams);
     const string table = isNil(atable)? pgsql.table : (string)cadr(atable);
@@ -265,14 +393,20 @@
     const string vname = isNil(avname)? pgsql.vname : (string)cadr(avname);
 
     // Build the SQL query
-    const char* sqlparams[5];
+    const char* sqlparams[6];
     int p = 0;
     int w = 0;
     ostringstream sqlos;
-    sqlos << "select data.*";
+    sqlos << "select data." << kname << ", data." << vname;
     if (!isNil(textsearch)) {
-        // Text search, setup result ranking
-        sqlos << ", ts_rank_cd(to_tsvector(data." << vname << "), tsquery, 32) as rank";
+        // Text search, setup text result ranking
+        sqlos << ", ts_rank_cd(to_tsvector(data." << vname << "), tsquery, 32) as tsrank";
+    }
+    if (!isNil(rank)) {
+        // Ranking, setup rank expression
+        const string rs = (string)cadr(rank);
+        sqlparams[p++] = c_str(rs);
+        sqlos << ", $" << p << " as rank";
     }
     sqlos << " from " << table << " data";
     if (!isNil(textsearch)) {
@@ -305,9 +439,13 @@
     if (!isNil(textsearch)) {
         // Text search, apply the query
         sqlos << (w == 0? " where" : " and");
-        sqlos << " tsquery @@ to_tsvector(data." << vname << ") order by rank desc";
+        sqlos << " tsquery @@ to_tsvector(data." << vname << ")";
         w++;
     }
+    if (!isNil(textsearch) || !isNil(rank)) {
+        // Result ordering
+        sqlos << " order by" << (isNil(rank)? "" : " rank desc") << ((isNil(rank) || isNil(textsearch))? "" : ",") << (isNil(textsearch)? "" : " tsrank desc");
+    }
     if (!isNil(offset)) {
         // Result pagination offset
         sqlos << " offset " << atoi(c_str((string)cadr(offset)));
@@ -320,8 +458,11 @@
     const string sqls = str(sqlos);
     debug(sqls, "pgsql::get::sqls");
     PGresult* r = PQexecParams(pgsql.conn, c_str(sqls), p, NULL, sqlparams, NULL, NULL, 0);
-    if (PQresultStatus(r) != PGRES_TUPLES_OK)
-        return mkfailure<value>(string("Couldn't execute select postgresql SQL statement: ") + pgfailure(r, pgsql.conn));
+    if (PQresultStatus(r) != PGRES_TUPLES_OK) {
+        const string rs = string("Couldn't execute select postgresql SQL statement: ") + pgfailure(r, pgsql.conn);
+        PQclear(r);
+        return mkfailure<value>(rs);
+    }
     const int n = PQntuples(r);
     if (n < 1) {
         PQclear(r);
@@ -330,7 +471,7 @@
         return mkfailure<value>(str(os), 404, false);
     }
 
-    // Return a collection of key / item pairs
+    // Return a collection of items
     if (l != 1) {
         const list<value> lval = getitems(r, 0, n);
         PQclear(r);
@@ -357,8 +498,18 @@
     const string ks(write(content(scheme::writeValue(key))));
     const char* const params[1] = { c_str(ks) };
     PGresult* const r = PQexecParams(pgsql.conn, c_str(string("delete from ") + pgsql.table + string(" where ") + pgsql.kname + string(" = $1;")), 1, NULL, params, NULL, NULL, 0);
-    if (PQresultStatus(r) != PGRES_COMMAND_OK)
-        return mkfailure<bool>(string("Couldn't execute delete postgresql SQL statement: ") + pgfailure(r, pgsql.conn));
+    if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+        const string rs = string("Couldn't execute delete postgresql SQL statement: ") + pgfailure(r, pgsql.conn);
+        PQclear(r);
+        return mkfailure<bool>(rs);
+    }
+    const char* const t = PQcmdTuples(r);
+    if (t != NULL && !strcmp(t, "0")) {
+        PQclear(r);
+        ostringstream os;
+        os << "Couldn't delete postgresql entry: " << key;
+        return mkfailure<bool>(str(os), 404, false);
+    }
     PQclear(r);
 
     debug(true, "pgsql::delete::result");
diff --git a/components/sqldb/sqldb.cpp b/components/sqldb/sqldb.cpp
index 1288dd5..75be2c0 100644
--- a/components/sqldb/sqldb.cpp
+++ b/components/sqldb/sqldb.cpp
@@ -64,6 +64,65 @@
 }
 
 /**
+ * Patch an item in the database.
+ */
+const failable<value> patch(const list<value>& params, const pgsql::PGSql& pg) {
+    // Read patch
+    value p = assoc<value>("patch", assoc<value>("content", car<value>(cadr(params))));
+    if (isNil(p))
+        return mkfailure<value>("Couldn't read patch script");
+    const string script = cadr<value>(p);
+    debug(script, "pgsql::patch::script");
+
+    const lambda<const failable<value>(const value&, const pgsql::PGSql&, const string&, const int)> tryPatch = [&tryPatch](const value& key, const pgsql::PGSql& pg, const string& script, const int count) -> const failable<value> {
+
+        // Begin database transaction
+        const failable<bool> brc = pgsql::begin(pg);
+        if (!hasContent(brc))
+            return mkfailure<value>(brc);
+
+        // Get existing value from database
+        const failable<value> ival = pgsql::get(key, pg);
+        if (!hasContent(ival) && rcode(ival) != 404) {
+            pgsql::rollback(pg);
+            return mkfailure<value>(ival);
+        }
+
+        // Apply patch
+        istringstream is(script);
+        scheme::Env env = scheme::setupEnvironment();
+        const value pval = scheme::evalScript(cons<value>("patch", scheme::quotedParameters(mklist<value>(key, hasContent(ival)? content(ival) : (value)list<value>()))), is, env);
+        if (isNil(pval)) {
+            ostringstream os;
+            os << "Couldn't patch postgresql entry: " << key;
+            return mkfailure<value>(str(os), 404, false);
+        }
+
+        // Push patched value to database
+        const failable<bool> val = pgsql::patch(key, pval, pg);
+        if (!hasContent(val)) {
+            pgsql::rollback(pg);
+
+            // Retry on a transaction serialization error
+            if (rcode(val) == 409 && count > 0)
+                return tryPatch(key, pg, script, count - 1);
+            return mkfailure<value>(val);
+        }
+
+        // Commit database transaction
+        const failable<bool> crc = pgsql::commit(pg);
+        if (!hasContent(crc))
+            return mkfailure<value>(crc);
+
+        return value(content(val));
+    };
+
+    // Try patching the entry and automatically retry a few times on transaction
+    // serialization errors
+    return tryPatch(car(params), pg, script, 5);
+}
+
+/**
  * Delete an item from the database.
  */
 const failable<value> del(const list<value>& params, const pgsql::PGSql& pg) {
@@ -98,6 +157,8 @@
             return post(cdr(params), *pg);
         if (func == "put")
             return put(cdr(params), *pg);
+        if (func == "patch")
+            return patch(cdr(params), *pg);
         if (func == "delete")
             return del(cdr(params), *pg);
         return mkfailure<value>();
diff --git a/hosting/server/patch-test.cpp b/hosting/server/patch-test.cpp
new file mode 100644
index 0000000..f7a631a
--- /dev/null
+++ b/hosting/server/patch-test.cpp
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* $Rev$ $Date$ */
+
+/**
+ * Test patch evaluation.
+ */
+
+#include <assert.h>
+#include "stream.hpp"
+#include "string.hpp"
+#include "perf.hpp"
+#include "../../modules/scheme/eval.hpp"
+
+namespace tuscany {
+namespace server {
+
+const string ratingScript = 
+"(define author \"admin\")\n"
+"(define updated \"Jan 01, 2012\")\n"
+"(define orating 1)\n"
+"(define nrating 4)\n"
+
+"(define (patch id e)\n"
+    "(define (rating x e)\n"
+        "(define a (tree-select-assoc (list x) e))\n"
+        "(if (null? a) (list x \"0\") (car a))\n"
+    ")\n"
+
+    "(define cratings (list (rating 'rating1 e) (rating 'rating2 e) (rating 'rating3 e) (rating 'rating4 e)))\n"
+
+    "(define (calcrating v i)\n"
+        "(define nv (+ v (if (= i orating) (- 1) (if (= i nrating) 1 0))))\n"
+        "(if (< nv 0) 0 nv)\n"
+    ")\n"
+
+    "(define (calcratings r i)\n"
+        "(if (null? r)\n"
+            "r\n"
+            "(cons (list (car (car r)) (calcrating (cadr (car r)) i)) (calcratings (cdr r) (+ 1 i)))\n"
+        ")\n"
+    ")\n"
+
+    "(define nratings (calcratings cratings 1))\n"
+
+    "(define neg (+ 1 (+ (* (cadr (assoc 'rating1 nratings)) 2) (cadr (assoc 'rating2 nratings)))))\n"
+    "(define pos (+ 1 (+ (* (cadr (assoc 'rating4 nratings)) 2) (cadr (assoc 'rating3 nratings)))))\n"
+    "(define arating (* 4 (/ (- (/ (+ pos 1.9208) (+ pos neg)) (/ (* 1.96 (sqrt (+ (/ (* pos neg) (+ pos neg)) 0.9604))) (+ pos neg))) (+ 1 (/ 3.8416 (+ pos neg))))))\n"
+
+    "(list (list 'entry (list 'title (car id)) (list 'id (car id)) (list 'author author) (list 'updated updated) (list 'content (cons 'ratings (cons (list 'rating arating) nratings)))))\n"
+")\n";
+
+const bool testRating() {
+    const gc_scoped_pool pool;
+    scheme::Env env = scheme::setupEnvironment();
+    istringstream script(ratingScript);
+    const list<value> v = mklist<value>(mklist<value>("entry", mklist<value>("title", string("test")), mklist<value>("id", string("test")), mklist<value>("content", mklist<value>("ratings", mklist<value>("rating", 2.5), mklist<value>("rating1", 10), mklist<value>("rating2", 20), mklist<value>("rating3", 30), mklist<value>("rating4", 40)))));
+    const list<value> rv = mklist<value>(mklist<value>("entry", mklist<value>("title", string("test")), mklist<value>("id", string("test")), mklist<value>("author", string("admin")), mklist<value>("updated", string("Jan 01, 2012")), mklist<value>("content", mklist<value>("ratings", mklist<value>("rating", 2.674348916258323), mklist<value>("rating1", 9), mklist<value>("rating2", 20), mklist<value>("rating3", 30), mklist<value>("rating4", 41)))));
+    const value pval = scheme::evalScript(cons<value>("patch", scheme::quotedParameters(mklist<value>(mklist<value>(string("test")), v))), script, env);
+    assert(pval == rv);
+    return true;
+}
+
+}
+}
+
+int main() {
+    const tuscany::gc_scoped_pool p;
+    tuscany::cout << "Testing..." << tuscany::endl;
+
+    tuscany::server::testRating();
+
+    tuscany::cout << "OK" << tuscany::endl;
+    return 0;
+}
diff --git a/modules/java/test/Client.java b/modules/java/test/Client.java
index c3bd875..d2b942c 100644
--- a/modules/java/test/Client.java
+++ b/modules/java/test/Client.java
@@ -29,6 +29,8 @@
     
     Boolean put(Iterable<String> id, Iterable<?> item);
     
+    Boolean patch(Iterable<String> id, Iterable<?> item);
+    
     Boolean delete(Iterable<String> id);
     
 }
diff --git a/modules/java/test/ClientImpl.java b/modules/java/test/ClientImpl.java
index ade2ba3..f1ca0ca 100644
--- a/modules/java/test/ClientImpl.java
+++ b/modules/java/test/ClientImpl.java
@@ -37,6 +37,10 @@
         return server.put(id, item);
     }
     
+    public Boolean patch(Iterable<String> id, Iterable<?> item, Server server) {
+        return server.patch(id, item);
+    }
+    
     public Boolean delete(Iterable<String> id, Server server) {
         return server.delete(id);
     }
diff --git a/modules/java/test/Server.java b/modules/java/test/Server.java
index 3dfe3c8..0f16da4 100644
--- a/modules/java/test/Server.java
+++ b/modules/java/test/Server.java
@@ -29,6 +29,8 @@
     
     Boolean put(Iterable<String> id, Iterable<?> item);
     
+    Boolean patch(Iterable<String> id, Iterable<?> item);
+    
     Boolean delete(Iterable<String> id);
     
 }
diff --git a/modules/java/test/ServerImpl.java b/modules/java/test/ServerImpl.java
index ee25cf7..4499681 100644
--- a/modules/java/test/ServerImpl.java
+++ b/modules/java/test/ServerImpl.java
@@ -49,6 +49,10 @@
         return true;
     }
 
+    public Boolean patch(final Iterable<String> id, final Iterable<?> item) {
+        return true;
+    }
+
     public Boolean delete(final Iterable<String> id) {
         return true;
     }
diff --git a/modules/java/wiring-test b/modules/java/wiring-test
index dd865c4..6f01ecb 100755
--- a/modules/java/wiring-test
+++ b/modules/java/wiring-test
@@ -61,6 +61,10 @@
     rc=$?
 fi
 if [ "$rc" = "0" ]; then
+    $curl_prefix/bin/curl http://localhost:8090/client/111 -X PATCH -H "Content-type: application/atom+xml" --data @../server/htdocs/test/entry.xml 2>/dev/null
+    rc=$?
+fi
+if [ "$rc" = "0" ]; then
     $curl_prefix/bin/curl http://localhost:8090/client/111 -X DELETE 2>/dev/null
     rc=$?
 fi
diff --git a/modules/python/client-test.py b/modules/python/client-test.py
index 3c7183e..5d73515 100644
--- a/modules/python/client-test.py
+++ b/modules/python/client-test.py
@@ -36,5 +36,8 @@
 def put(id, item, ref):
     return ref.put(id, item)
 
+def patch(id, item, ref):
+    return ref.patch(id, item)
+
 def delete(id, ref):
     return ref.delete(id)
diff --git a/modules/python/server-test.py b/modules/python/server-test.py
index da5d216..fe493f6 100644
--- a/modules/python/server-test.py
+++ b/modules/python/server-test.py
@@ -38,5 +38,8 @@
 def put(id, item):
     return True
 
+def patch(id, item):
+    return True
+
 def delete(id):
     return True
diff --git a/modules/python/wiring-test b/modules/python/wiring-test
index 899c3eb..e7bb6c4 100755
--- a/modules/python/wiring-test
+++ b/modules/python/wiring-test
@@ -60,6 +60,10 @@
     rc=$?
 fi
 if [ "$rc" = "0" ]; then
+    $curl_prefix/bin/curl http://localhost:8090/client/111 -X PATCH -H "Content-type: application/atom+xml" --data @../server/htdocs/test/entry.xml 2>/dev/null
+    rc=$?
+fi
+if [ "$rc" = "0" ]; then
     $curl_prefix/bin/curl http://localhost:8090/client/111 -X DELETE 2>/dev/null
     rc=$?
 fi
diff --git a/modules/server/client-test.hpp b/modules/server/client-test.hpp
index dc9ca29..29da9e4 100644
--- a/modules/server/client-test.hpp
+++ b/modules/server/client-test.hpp
@@ -294,6 +294,21 @@
     return true;
 }
 
+const bool testPatch() {
+    const gc_scoped_pool pool;
+    const list<value> i = nilListValue + "content" + (nilListValue + "item"
+            + (nilListValue + "name" + string("Apple"))
+            + (nilListValue + "price" + string("$2.99")));
+    const list<value> a = nilListValue + (nilListValue + "entry" 
+            + (nilListValue + "title" + string("item"))
+            + (nilListValue + "id" + string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))
+            + i);
+    const http::CURLSession ch("", "", "", "", 0);
+    const value rc = content(http::patch(a, testURI + "/111", ch));
+    assert(rc == trueValue);
+    return true;
+}
+
 const bool testDel() {
     const gc_scoped_pool pool;
     const http::CURLSession ch("", "", "", "", 0);
@@ -306,6 +321,7 @@
     tuscany::server::testGet();
     tuscany::server::testPost();
     tuscany::server::testPut();
+    tuscany::server::testPatch();
     tuscany::server::testDel();
     tuscany::server::testEval();
     tuscany::server::testGetPerf();
diff --git a/modules/server/client-test.scm b/modules/server/client-test.scm
index 47b799d..a4db7f1 100644
--- a/modules/server/client-test.scm
+++ b/modules/server/client-test.scm
@@ -33,6 +33,10 @@
   (ref "put" id entry)
 )
 
+(define (patch id entry ref)
+  (ref "patch" id entry)
+)
+
 (define (delete id ref)
   (ref "delete" id)
 )
diff --git a/modules/server/httpd-test b/modules/server/httpd-test
index 195caa4..ef30682 100755
--- a/modules/server/httpd-test
+++ b/modules/server/httpd-test
@@ -60,6 +60,10 @@
     rc=$?
 fi
 if [ "$rc" = "0" ]; then
+    $curl_prefix/bin/curl http://localhost:8090/scheme/111 -X PATCH -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null
+    rc=$?
+fi
+if [ "$rc" = "0" ]; then
     $curl_prefix/bin/curl http://localhost:8090/scheme/111 -X DELETE 2>/dev/null
     rc=$?
 fi
diff --git a/modules/server/impl-test.cpp b/modules/server/impl-test.cpp
index a9bd9ad..0ad1302 100644
--- a/modules/server/impl-test.cpp
+++ b/modules/server/impl-test.cpp
@@ -45,6 +45,10 @@
     return trueValue;
 }
 
+const failable<value> patch(unused const list<value>& params) {
+    return trueValue;
+}
+
 const failable<value> del(unused const list<value>& params) {
     return trueValue;
 }
@@ -66,6 +70,8 @@
         return tuscany::server::post(cdr(params));
     if (func == "put")
         return tuscany::server::put(cdr(params));
+    if (func == "patch")
+        return tuscany::server::patch(cdr(params));
     if (func == "delete")
         return tuscany::server::del(cdr(params));
     if (func == "echo")
diff --git a/modules/server/mod-eval.hpp b/modules/server/mod-eval.hpp
index aa13584..5a8a15b 100644
--- a/modules/server/mod-eval.hpp
+++ b/modules/server/mod-eval.hpp
@@ -206,7 +206,7 @@
         debug(impls, "modeval::implProxy::callImpl::impls");
 
         // Lookup the component implementation
-        const list<value> impl(assoctree<value>(cname, (list<value>)impls));
+        const list<value> impl(rbtreeAssoc<value>(cname, (list<value>)impls));
         if (isNil(impl))
             return mkfailure<value>(string("Couldn't find component implementation: ") + (string)cname);
 
@@ -573,7 +573,7 @@
  * arranged in trees of reference-name + reference-target pairs.
  */
 const list<value> componentReferenceToTargetTree(const value& c) {
-    return mklist<value>(scdl::name(c), mkbtree(sort(scdl::referenceToTargetAssoc(scdl::references(c)))));
+    return mklist<value>(scdl::name(c), mkbrbtree(sort(scdl::referenceToTargetAssoc(scdl::references(c)))));
 }
 
 const list<value> componentReferenceToTargetAssoc(const list<value>& c) {
@@ -634,13 +634,13 @@
     const list<value> comps = content(fcomps);
     debug(comps, "modeval::confComponents::comps");
 
-    const list<value> refs = mkbtree(sort(componentReferenceToTargetAssoc(comps)));
+    const list<value> refs = mkbrbtree(sort(componentReferenceToTargetAssoc(comps)));
     debug(flatten(refs), "modeval::confComponents::refs");
 
-    const list<value> svcs = mkbtree(sort(uriToComponentAssoc(comps)));
+    const list<value> svcs = mkbrbtree(sort(uriToComponentAssoc(comps)));
     debug(flatten(svcs), "modeval::confComponents::svcs");
 
-    const list<value> cimpls = mkbtree(sort(componentToImplementationAssoc(comps,
+    const list<value> cimpls = mkbrbtree(sort(componentToImplementationAssoc(comps,
                     isNil(contributor)? length(vhost) != 0? contribPath + vhost + "/" : contribPath : contribPath,
                     impls, lifecycle, sslc, timeout)));
     debug(flatten(cimpls), "modeval::confComponents::impls");
@@ -659,7 +659,7 @@
 
     const list<value> simpls = content(fsimpls);
     debug(impls, "modeval::startComponents::simpls");
-    return mkbtree(sort(simpls));
+    return mkbrbtree(sort(simpls));
 }
 
 /**
@@ -696,7 +696,7 @@
     debug(r->uri, "modeval::get::uri");
 
     // Inspect the query string
-    const list<list<value> > args = httpd::unescapeArgs(httpd::queryArgs(r));
+    const list<value> args = httpd::unescapeArgs(httpd::queryArgs(r));
     const list<value> ia = assoc(value("id"), args);
     const list<value> ma = assoc(value("method"), args);
 
@@ -795,7 +795,7 @@
         const list<string> ls = httpd::read(r);
         debug(ls, "modeval::post::input");
         const value jsreq = content(json::readValue(ls));
-        const list<list<value> > args = httpd::postArgs(jsreq);
+        const list<value> args = httpd::postArgs(jsreq);
 
         // Extract the request id, method and params
         const value id = cadr(assoc(value("id"), args));
@@ -878,6 +878,34 @@
 }
 
 /**
+ * Handle an HTTP PATCH.
+ */
+const failable<int> patch(const list<value>& rpath, request_rec* const r, const lvvlambda& impl) {
+    debug(r->uri, "modeval::put::patch");
+
+    // Read the ATOM entry
+    const int rc = httpd::setupReadPolicy(r);
+    if(rc != OK)
+        return rc;
+    const list<string> ls = httpd::read(r);
+    debug(ls, "modeval::patch::input");
+    const value aval = elementsToValues(content(atom::isATOMEntry(ls)? atom::readATOMEntry(ls) : atom::readATOMFeed(ls)));
+
+    // Evaluate the PATCH expression and update the corresponding resource
+    const failable<value> val = failableResult(impl(cons<value>("patch", mklist<value>(cddr(rpath), aval))));
+    if (!hasContent(val))
+        return mkfailure<int>(val);
+
+    // Report HTTP status
+    const value rval = content(val);
+    if (isNil(rval) || rval == falseValue)
+        return HTTP_NOT_FOUND;
+    if (isNumber(rval))
+        return (int)rval;
+    return OK;
+}
+
+/**
  * Handle an HTTP DELETE.
  */
 const failable<int> del(const list<value>& rpath, request_rec* const r, const lvvlambda& impl) {
@@ -928,7 +956,7 @@
     // Find the requested component
     if (isNil(cdr(rpath)))
         return HTTP_NOT_FOUND;
-    const list<value> impl(assoctree(cadr(rpath), impls));
+    const list<value> impl(rbtreeAssoc(cadr(rpath), impls));
     if (isNil(impl))
         return HTTP_NOT_FOUND;
     debug(impl, "modeval::translateComponent::impl");
@@ -947,13 +975,13 @@
     // Find the requested component
     if (isNil(cdr(rpath)))
         return HTTP_NOT_FOUND;
-    const list<value> comp(assoctree(cadr(rpath), refs));
+    const list<value> comp(rbtreeAssoc(cadr(rpath), refs));
     if (isNil(comp))
         return HTTP_NOT_FOUND;
     debug(comp, "modeval::translateReference::comp");
 
     // Find the requested reference and target configuration
-    const list<value> ref(assoctree<value>(caddr(rpath), cadr(comp)));
+    const list<value> ref(rbtreeAssoc<value>(caddr(rpath), cadr(comp)));
     if (isNil(ref))
         return HTTP_NOT_FOUND;
     debug(ref, "modeval::translateReference::ref");
@@ -1087,7 +1115,7 @@
  * Translate a request.
  */
 int translate(request_rec *r) {
-    if(r->method_number != M_GET && r->method_number != M_POST && r->method_number != M_PUT && r->method_number != M_DELETE)
+    if(r->method_number != M_GET && r->method_number != M_POST && r->method_number != M_PUT && r->method_number != M_PATCH && r->method_number != M_DELETE)
         return DECLINED;
 
     const gc_scoped_pool sp(r->pool);
@@ -1151,7 +1179,7 @@
     debug(rpath, "modeval::handleRequest::path");
 
     // Get the component implementation lambda
-    const list<value> impl(assoctree<value>(cadr(rpath), impls));
+    const list<value> impl(rbtreeAssoc<value>(cadr(rpath), impls));
     if (isNil(impl)) {
         mkfailure<int>(string("Couldn't find component implementation: ") + (string)cadr(rpath));
         return HTTP_NOT_FOUND;
@@ -1167,6 +1195,8 @@
         return httpd::reportStatus(post(rpath, r, l));
     if(r->method_number == M_PUT)
         return httpd::reportStatus(put(rpath, r, l));
+    if(r->method_number == M_PATCH)
+        return httpd::reportStatus(patch(rpath, r, l));
     if(r->method_number == M_DELETE)
         return httpd::reportStatus(del(rpath, r, l));
     return HTTP_NOT_IMPLEMENTED;
@@ -1219,7 +1249,7 @@
         const list<value> simpls = content(fsimpls);
 
         // Merge the components in the virtual host with the components in the main host
-        reqc.impls = mkbtree(sort(append(flatten((const list<value>)sc.compos.impls), flatten(simpls))));
+        reqc.impls = mkbrbtree(sort(append(flatten((const list<value>)sc.compos.impls), flatten(simpls))));
 
         // Handle the request against the running components
         const int rc = handleRequest(reqc.rpath, r, reqc.impls);
@@ -1418,7 +1448,7 @@
     
     // Get the vhost contributor component implementation lambda
     if (length(sc.vhostc.contributorName) != 0) {
-        const list<value> impl(assoctree<value>((string)sc.vhostc.contributorName, (const list<value>)sc.compos.impls));
+        const list<value> impl(rbtreeAssoc<value>((string)sc.vhostc.contributorName, (const list<value>)sc.compos.impls));
         if (isNil(impl)) {
             mkfailure<int>(string("Couldn't find contributor component implementation: ") + sc.vhostc.contributorName);
             failureExitChild();
@@ -1428,7 +1458,7 @@
 
     // Get the vhost authenticator component implementation lambda
     if (length(sc.vhostc.authenticatorName) != 0) {
-        const list<value> impl(assoctree<value>((string)sc.vhostc.authenticatorName, (const list<value>)sc.compos.impls));
+        const list<value> impl(rbtreeAssoc<value>((string)sc.vhostc.authenticatorName, (const list<value>)sc.compos.impls));
         if (isNil(impl)) {
             mkfailure<int>(string("Couldn't find authenticator component implementation: ") + sc.vhostc.authenticatorName);
             failureExitChild();
diff --git a/modules/server/server-test.scm b/modules/server/server-test.scm
index 4bbff6e..b9ed6d5 100644
--- a/modules/server/server-test.scm
+++ b/modules/server/server-test.scm
@@ -22,7 +22,7 @@
 ; ATOMPub test case
 
 (define (get id)
-  (if (nul id)
+  (if (null? id)
     '((feed (title "Sample Feed") (id "123456789") (entry
        (((title "Item") (id "111") (content (item (name "Apple") (currencyCode "USD") (currencySymbol "$") (price 2.99))))
         ((title "Item") (id "222") (content (item (name "Orange") (currencyCode "USD") (currencySymbol "$") (price 3.55))))
@@ -39,6 +39,10 @@
   true
 )
 
+(define (patch id item)
+  true
+)
+
 (define (delete id)
   true
 )
diff --git a/modules/server/wiring-test b/modules/server/wiring-test
index 7e1aea2..a00255e 100755
--- a/modules/server/wiring-test
+++ b/modules/server/wiring-test
@@ -60,6 +60,10 @@
     rc=$?
 fi
 if [ "$rc" = "0" ]; then
+    $curl_prefix/bin/curl http://localhost:8090/client/111 -X PATCH -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null
+    rc=$?
+fi
+if [ "$rc" = "0" ]; then
     $curl_prefix/bin/curl http://localhost:8090/client/111 -X DELETE 2>/dev/null
     rc=$?
 fi
diff --git a/modules/wsgi/atomutil.py b/modules/wsgi/atomutil.py
index ad6425f..4b67ef2 100644
--- a/modules/wsgi/atomutil.py
+++ b/modules/wsgi/atomutil.py
@@ -29,7 +29,7 @@
     i = "" if isNil(li) else elementValue(car(li))
     lc = filter(selector((element, "'content")), e)
     return append((element, "'entry", (element, "'title", t), (element, "'id", i)),
-            () if isNil(lc) else ((element, "'content", elementValue(car(lc))),))
+            () if isNil(lc) else () if isAttribute(elementValue(car(lc))) else ((element, "'content", elementValue(car(lc))),))
 
 # Convert a list of elements to a list of values representing ATOM entries
 def entriesElementValues(e):
diff --git a/modules/wsgi/client-test.py b/modules/wsgi/client-test.py
index 867222e..5c67269 100644
--- a/modules/wsgi/client-test.py
+++ b/modules/wsgi/client-test.py
@@ -36,5 +36,8 @@
 def put(id, item, ref):
     return ref.put(id, item)
 
+def patch(id, item, ref):
+    return ref.patch(id, item)
+
 def delete(id, ref):
     return ref.delete(id)
diff --git a/modules/wsgi/composite.py b/modules/wsgi/composite.py
index baea7aa..77f2ecd 100755
--- a/modules/wsgi/composite.py
+++ b/modules/wsgi/composite.py
@@ -237,6 +237,14 @@
             return failure(e, r, 404)
         return result(e, r, 200)
     
+    if m == "PATCH":
+        # Handle an ATOM entry PATCH
+        ae = elementsToValues(readATOMEntry(requestBody(e)))
+        v = comp("patch", id, ae)
+        if v == False:
+            return failure(e, r, 404)
+        return result(e, r, 200)
+    
     if m == "DELETE":
         v = comp("delete", id)
         if v == False:
diff --git a/modules/wsgi/httputil.py b/modules/wsgi/httputil.py
index 842460c..f98418d 100644
--- a/modules/wsgi/httputil.py
+++ b/modules/wsgi/httputil.py
@@ -124,6 +124,20 @@
                 return None
             return True
 
+        # handle a PATCH request
+        if func == "patch":
+            u = requesturi(self.url, car(args))
+            print >> stderr, "Client PATCH request", u
+            req = StringIO()
+            writeStrings(atomutil.writeATOMEntry(atomutil.entryValuesToElements(cadr(args))), req)
+            headers["Content-type"] = "application/atom+xml"
+            c.request("PATCH", u, req.getvalue(), headers)
+            res = c.getresponse()
+            print >> stderr, "Client status", res.status
+            if res.status != 200:
+                return None
+            return True
+
         # handle a DELETE request
         if func == "delete":
             u = requesturi(self.url, car(args))
diff --git a/modules/wsgi/server-test.py b/modules/wsgi/server-test.py
index 610ec05..b2bc0ef 100644
--- a/modules/wsgi/server-test.py
+++ b/modules/wsgi/server-test.py
@@ -41,5 +41,8 @@
 def put(id, item):
     return True
 
+def patch(id, item):
+    return True
+
 def delete(id):
     return True
diff --git a/modules/wsgi/util.py b/modules/wsgi/util.py
index 24467fd..f630455 100644
--- a/modules/wsgi/util.py
+++ b/modules/wsgi/util.py
@@ -60,7 +60,7 @@
 def isNil(l):
     if isinstance(l, streampair):
         return l.isNil()
-    return l == ()
+    return l is None or l == ()
 
 def isSymbol(v):
     return isinstance(v, basestring) and v[0:1] == "'"
@@ -132,11 +132,24 @@
 def assoc(k, l):
     if l == ():
         return None
-
     if k == car(car(l)):
         return car(l)
     return assoc(k, cdr(l))
 
+def delAssoc(k, l):
+    if l == ():
+        return ()
+    if k == car(car(l)):
+        return delAssoc(k, cdr(l))
+    return cons(car(l), delAssoc(k, cdr(l)))
+
+def putAssoc(a, l):
+    if l == ():
+        return (a,)
+    if car(a) == car(car(l)):
+        return cons(a, cdr(l))
+    return cons(car(l), putAssoc(a, cdr(l)))
+
 # Currying / partial function application
 def curry(f, *args):
     return lambda *a: f(*(args + a))
diff --git a/modules/wsgi/wiring-test b/modules/wsgi/wiring-test
index cbecc20..4a8d15a 100755
--- a/modules/wsgi/wiring-test
+++ b/modules/wsgi/wiring-test
@@ -56,6 +56,10 @@
     rc=$?
 fi
 if [ "$rc" = "0" ]; then
+    $curl_prefix/bin/curl $uri/client/111 -X PATCH -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null
+    rc=$?
+fi
+if [ "$rc" = "0" ]; then
     $curl_prefix/bin/curl $uri/client/111 -X DELETE 2>/dev/null
     rc=$?
 fi
diff --git a/modules/wsgi/wsgi-test b/modules/wsgi/wsgi-test
index f8334b3..8deeb4d 100755
--- a/modules/wsgi/wsgi-test
+++ b/modules/wsgi/wsgi-test
@@ -52,6 +52,10 @@
     rc=$?
 fi
 if [ "$rc" = "0" ]; then
+    $curl_prefix/bin/curl http://localhost:8090/wsgi/111 -X PATCH -H "Content-type: application/atom+xml" --data @htdocs/test/entry.xml 2>/dev/null
+    rc=$?
+fi
+if [ "$rc" = "0" ]; then
     $curl_prefix/bin/curl http://localhost:8090/wsgi/111 -X DELETE 2>/dev/null
     rc=$?
 fi