diff options
author | jsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68> | 2013-01-03 07:41:14 +0000 |
---|---|---|
committer | jsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68> | 2013-01-03 07:41:14 +0000 |
commit | 9e1b9e73145e00ea591bd1e0e9777625bad66dc9 (patch) | |
tree | c6563988970251cba763364bcddd33ac7fe23970 | |
parent | 157ca678dee75e7881a0198425d0c8328f0bee04 (diff) |
Add support for HTTP patch and application of patch scripts to server and data store components.
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1428192 13f79535-47bb-0310-9956-ffa450edef68
46 files changed, 847 insertions, 59 deletions
diff --git a/sca-cpp/trunk/.gitignore b/sca-cpp/trunk/.gitignore index fc7f1cb1c9..42539a1488 100644 --- a/sca-cpp/trunk/.gitignore +++ b/sca-cpp/trunk/.gitignore @@ -152,4 +152,5 @@ hosting/server/data/authn chat-send opencl-shell opencl-test +patch-test diff --git a/sca-cpp/trunk/components/cache/client-test.cpp b/sca-cpp/trunk/components/cache/client-test.cpp index 5e9be6c14a..3c8a261f84 100644 --- a/sca-cpp/trunk/components/cache/client-test.cpp +++ b/sca-cpp/trunk/components/cache/client-test.cpp @@ -81,6 +81,32 @@ const bool testCache(const string& uri) { 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/sca-cpp/trunk/components/cache/datacache.cpp b/sca-cpp/trunk/components/cache/datacache.cpp index 975ca43dce..4fafd9e345 100644 --- a/sca-cpp/trunk/components/cache/datacache.cpp +++ b/sca-cpp/trunk/components/cache/datacache.cpp @@ -95,6 +95,20 @@ const failable<value> put(const value& key, const value& val, unused const lvvla } /** + * 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 @@ const tuscany::value apply(const tuscany::list<tuscany::value>& params) { 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/sca-cpp/trunk/components/cache/memcache-test.cpp b/sca-cpp/trunk/components/cache/memcache-test.cpp index 6c6adb0541..10eda45eae 100644 --- a/sca-cpp/trunk/components/cache/memcache-test.cpp +++ b/sca-cpp/trunk/components/cache/memcache-test.cpp @@ -40,6 +40,8 @@ bool testMemCached() { 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/sca-cpp/trunk/components/cache/memcache.cpp b/sca-cpp/trunk/components/cache/memcache.cpp index 2e4597efd3..e1a3c7e9af 100644 --- a/sca-cpp/trunk/components/cache/memcache.cpp +++ b/sca-cpp/trunk/components/cache/memcache.cpp @@ -63,6 +63,39 @@ const failable<value> put(const list<value>& params, const memcache::MemCached& } /** + * 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 @@ const failable<value> start(const list<value>& params) { 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/sca-cpp/trunk/components/cache/memcache.hpp b/sca-cpp/trunk/components/cache/memcache.hpp index 00ee9c6291..7962d1caa0 100644 --- a/sca-cpp/trunk/components/cache/memcache.hpp +++ b/sca-cpp/trunk/components/cache/memcache.hpp @@ -73,6 +73,7 @@ private: 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 @@ const failable<bool> put(const value& key, const value& val, const MemCached& ca } /** + * 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 @@ const failable<bool> del(const value& key, const MemCached& cache) { 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/sca-cpp/trunk/components/cache/partitioner.cpp b/sca-cpp/trunk/components/cache/partitioner.cpp index a38c053358..8a56a7f932 100644 --- a/sca-cpp/trunk/components/cache/partitioner.cpp +++ b/sca-cpp/trunk/components/cache/partitioner.cpp @@ -143,6 +143,23 @@ const failable<value> put(const value& key, const value& val, const lvvlambda& s } /** + * 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 @@ const tuscany::value apply(const tuscany::list<tuscany::value>& params) { 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/sca-cpp/trunk/components/constdb/client-test.cpp b/sca-cpp/trunk/components/constdb/client-test.cpp index b796ef01dd..165e3d8836 100644 --- a/sca-cpp/trunk/components/constdb/client-test.cpp +++ b/sca-cpp/trunk/components/constdb/client-test.cpp @@ -77,6 +77,32 @@ const bool testConstDb() { 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/sca-cpp/trunk/components/constdb/constdb.cpp b/sca-cpp/trunk/components/constdb/constdb.cpp index a9a5bc5817..2d34ed2b73 100644 --- a/sca-cpp/trunk/components/constdb/constdb.cpp +++ b/sca-cpp/trunk/components/constdb/constdb.cpp @@ -63,6 +63,39 @@ const failable<value> put(const list<value>& params, const tinycdb::TinyCDB& cdb } /** + * 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 @@ const failable<value> start(unused const list<value>& params) { 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/sca-cpp/trunk/components/constdb/tinycdb-test.cpp b/sca-cpp/trunk/components/constdb/tinycdb-test.cpp index 6cc9e9eabb..41bfe12772 100644 --- a/sca-cpp/trunk/components/constdb/tinycdb-test.cpp +++ b/sca-cpp/trunk/components/constdb/tinycdb-test.cpp @@ -40,6 +40,8 @@ const bool testTinyCDB() { 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/sca-cpp/trunk/components/constdb/tinycdb.hpp b/sca-cpp/trunk/components/constdb/tinycdb.hpp index ce1dcbb011..3da5f3c216 100644 --- a/sca-cpp/trunk/components/constdb/tinycdb.hpp +++ b/sca-cpp/trunk/components/constdb/tinycdb.hpp @@ -389,6 +389,37 @@ const failable<bool> put(const value& key, const value& val, const TinyCDB& cdb) } /** + * 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 @@ const failable<bool> del(const value& key, const TinyCDB& cdb) { 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 @@ const failable<bool> del(const value& key, const TinyCDB& cdb) { // 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/sca-cpp/trunk/components/filedb/client-test.cpp b/sca-cpp/trunk/components/filedb/client-test.cpp index 5694d97522..96c0212096 100644 --- a/sca-cpp/trunk/components/filedb/client-test.cpp +++ b/sca-cpp/trunk/components/filedb/client-test.cpp @@ -77,6 +77,32 @@ const bool testFileDB() { 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/sca-cpp/trunk/components/filedb/file-test.cpp b/sca-cpp/trunk/components/filedb/file-test.cpp index 5270967ccb..55272800f0 100644 --- a/sca-cpp/trunk/components/filedb/file-test.cpp +++ b/sca-cpp/trunk/components/filedb/file-test.cpp @@ -38,11 +38,14 @@ const bool testFileDB(const string& dbname, const string& format) { 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/sca-cpp/trunk/components/filedb/filedb.cpp b/sca-cpp/trunk/components/filedb/filedb.cpp index 37cb6c5260..b28cc9bedb 100644 --- a/sca-cpp/trunk/components/filedb/filedb.cpp +++ b/sca-cpp/trunk/components/filedb/filedb.cpp @@ -63,6 +63,39 @@ const failable<value> put(const list<value>& params, const filedb::FileDB& db) { } /** + * 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 @@ const failable<value> start(const list<value>& params) { 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/sca-cpp/trunk/components/filedb/filedb.hpp b/sca-cpp/trunk/components/filedb/filedb.hpp index 2855cebfc6..41dde88bef 100644 --- a/sca-cpp/trunk/components/filedb/filedb.hpp +++ b/sca-cpp/trunk/components/filedb/filedb.hpp @@ -82,6 +82,7 @@ private: 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 @@ const failable<bool> put(const value& key, const value& val, const FileDB& db) { } /** + * 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 @@ const failable<bool> del(const value& key, const FileDB& db) { 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/sca-cpp/trunk/components/sqldb/client-test.cpp b/sca-cpp/trunk/components/sqldb/client-test.cpp index c9fbb7d5bb..fcdb8d3c2a 100644 --- a/sca-cpp/trunk/components/sqldb/client-test.cpp +++ b/sca-cpp/trunk/components/sqldb/client-test.cpp @@ -77,6 +77,32 @@ const bool testSqlDb() { 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/sca-cpp/trunk/components/sqldb/pgsql-conf b/sca-cpp/trunk/components/sqldb/pgsql-conf index 8adbb902c9..020ce129fb 100755 --- a/sca-cpp/trunk/components/sqldb/pgsql-conf +++ b/sca-cpp/trunk/components/sqldb/pgsql-conf @@ -96,6 +96,7 @@ archive_command = '$here/pgsql-archive $root $host $bport %p %f' wal_level = hot_standby max_wal_senders = 5 wal_keep_segments = 32 +#synchronous_standby_names = '*' EOF @@ -166,7 +167,6 @@ cat >$root/sqldb/data/pgbouncer.conf <<EOF 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 @@ pidfile = $root/logs/pgbouncer.pid 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/sca-cpp/trunk/components/sqldb/pgsql-standby-conf b/sca-cpp/trunk/components/sqldb/pgsql-standby-conf index 5f76b5b332..ae2ed8e909 100755 --- a/sca-cpp/trunk/components/sqldb/pgsql-standby-conf +++ b/sca-cpp/trunk/components/sqldb/pgsql-standby-conf @@ -126,7 +126,7 @@ cat >$root/sqldb/data/recovery.conf << EOF # 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 @@ cat >$root/sqldb/data/pgbouncer.conf <<EOF 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 @@ pidfile = $root/logs/pgbouncer.pid 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/sca-cpp/trunk/components/sqldb/pgsql-standby-test.cpp b/sca-cpp/trunk/components/sqldb/pgsql-standby-test.cpp index 5d73b0d877..319194ab41 100644 --- a/sca-cpp/trunk/components/sqldb/pgsql-standby-test.cpp +++ b/sca-cpp/trunk/components/sqldb/pgsql-standby-test.cpp @@ -43,6 +43,9 @@ const bool testPGSql() { 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/sca-cpp/trunk/components/sqldb/pgsql-test.cpp b/sca-cpp/trunk/components/sqldb/pgsql-test.cpp index 5d7bb98cd8..ec5d537b44 100644 --- a/sca-cpp/trunk/components/sqldb/pgsql-test.cpp +++ b/sca-cpp/trunk/components/sqldb/pgsql-test.cpp @@ -49,6 +49,8 @@ const bool testPGSql() { 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/sca-cpp/trunk/components/sqldb/pgsql.hpp b/sca-cpp/trunk/components/sqldb/pgsql.hpp index 620aec4105..5e0004c4e6 100644 --- a/sca-cpp/trunk/components/sqldb/pgsql.hpp +++ b/sca-cpp/trunk/components/sqldb/pgsql.hpp @@ -75,12 +75,15 @@ public: 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 @@ public: 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 @@ private: 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 @@ const failable<bool> setup(const PGSql& pgsql) { } /** + * 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 failable<bool> post(const value& key, const value& val, const PGSql& pgsql 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 failable<bool> put(const value& key, const value& val, const PGSql& pgsql) 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 @@ const failable<bool> put(const value& key, const value& val, const PGSql& pgsql) 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 @@ const failable<bool> put(const value& key, const value& val, const PGSql& pgsql) } /** + * 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 @@ const list<value> keyid(const list<value>& key) { } /** - * 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<list<value> > keyparams(const list<value>& key) { 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 @@ const failable<value> get(const value& key, const PGSql& pgsql) { // 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 failable<value> get(const value& key, const PGSql& pgsql) { 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 @@ const failable<value> get(const value& key, const PGSql& pgsql) { 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 failable<value> get(const value& key, const PGSql& pgsql) { 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 @@ const failable<value> get(const value& key, const PGSql& pgsql) { 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 failable<bool> del(const value& key, const PGSql& pgsql) { 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/sca-cpp/trunk/components/sqldb/sqldb.cpp b/sca-cpp/trunk/components/sqldb/sqldb.cpp index 1288dd553b..75be2c0624 100644 --- a/sca-cpp/trunk/components/sqldb/sqldb.cpp +++ b/sca-cpp/trunk/components/sqldb/sqldb.cpp @@ -64,6 +64,65 @@ const failable<value> put(const list<value>& params, const pgsql::PGSql& pg) { } /** + * 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 @@ const failable<value> start(const list<value>& params) { 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/sca-cpp/trunk/hosting/server/patch-test.cpp b/sca-cpp/trunk/hosting/server/patch-test.cpp new file mode 100644 index 0000000000..f7a631adcf --- /dev/null +++ b/sca-cpp/trunk/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/sca-cpp/trunk/modules/java/test/Client.java b/sca-cpp/trunk/modules/java/test/Client.java index c3bd875fcc..d2b942c860 100644 --- a/sca-cpp/trunk/modules/java/test/Client.java +++ b/sca-cpp/trunk/modules/java/test/Client.java @@ -29,6 +29,8 @@ public interface Client { Boolean put(Iterable<String> id, Iterable<?> item); + Boolean patch(Iterable<String> id, Iterable<?> item); + Boolean delete(Iterable<String> id); } diff --git a/sca-cpp/trunk/modules/java/test/ClientImpl.java b/sca-cpp/trunk/modules/java/test/ClientImpl.java index ade2ba302e..f1ca0ca033 100644 --- a/sca-cpp/trunk/modules/java/test/ClientImpl.java +++ b/sca-cpp/trunk/modules/java/test/ClientImpl.java @@ -37,6 +37,10 @@ public class ClientImpl { 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/sca-cpp/trunk/modules/java/test/Server.java b/sca-cpp/trunk/modules/java/test/Server.java index 3dfe3c84ef..0f16da48fb 100644 --- a/sca-cpp/trunk/modules/java/test/Server.java +++ b/sca-cpp/trunk/modules/java/test/Server.java @@ -29,6 +29,8 @@ public interface Server { Boolean put(Iterable<String> id, Iterable<?> item); + Boolean patch(Iterable<String> id, Iterable<?> item); + Boolean delete(Iterable<String> id); } diff --git a/sca-cpp/trunk/modules/java/test/ServerImpl.java b/sca-cpp/trunk/modules/java/test/ServerImpl.java index ee25cf7bf8..4499681f78 100644 --- a/sca-cpp/trunk/modules/java/test/ServerImpl.java +++ b/sca-cpp/trunk/modules/java/test/ServerImpl.java @@ -49,6 +49,10 @@ public class ServerImpl { 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/sca-cpp/trunk/modules/java/wiring-test b/sca-cpp/trunk/modules/java/wiring-test index dd865c4c66..6f01ecb145 100755 --- a/sca-cpp/trunk/modules/java/wiring-test +++ b/sca-cpp/trunk/modules/java/wiring-test @@ -61,6 +61,10 @@ if [ "$rc" = "0" ]; then 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/sca-cpp/trunk/modules/python/client-test.py b/sca-cpp/trunk/modules/python/client-test.py index 3c7183e865..5d73515ca2 100644 --- a/sca-cpp/trunk/modules/python/client-test.py +++ b/sca-cpp/trunk/modules/python/client-test.py @@ -36,5 +36,8 @@ def post(collection, item, ref): 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/sca-cpp/trunk/modules/python/server-test.py b/sca-cpp/trunk/modules/python/server-test.py index da5d216da5..fe493f671a 100644 --- a/sca-cpp/trunk/modules/python/server-test.py +++ b/sca-cpp/trunk/modules/python/server-test.py @@ -38,5 +38,8 @@ def post(collection, item): def put(id, item): return True +def patch(id, item): + return True + def delete(id): return True diff --git a/sca-cpp/trunk/modules/python/wiring-test b/sca-cpp/trunk/modules/python/wiring-test index 899c3eb5a1..e7bb6c410d 100755 --- a/sca-cpp/trunk/modules/python/wiring-test +++ b/sca-cpp/trunk/modules/python/wiring-test @@ -60,6 +60,10 @@ if [ "$rc" = "0" ]; then 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/sca-cpp/trunk/modules/server/client-test.hpp b/sca-cpp/trunk/modules/server/client-test.hpp index dc9ca299ad..29da9e4798 100644 --- a/sca-cpp/trunk/modules/server/client-test.hpp +++ b/sca-cpp/trunk/modules/server/client-test.hpp @@ -294,6 +294,21 @@ const bool testPut() { 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 @@ const bool testServer() { tuscany::server::testGet(); tuscany::server::testPost(); tuscany::server::testPut(); + tuscany::server::testPatch(); tuscany::server::testDel(); tuscany::server::testEval(); tuscany::server::testGetPerf(); diff --git a/sca-cpp/trunk/modules/server/client-test.scm b/sca-cpp/trunk/modules/server/client-test.scm index 47b799d390..a4db7f1b26 100644 --- a/sca-cpp/trunk/modules/server/client-test.scm +++ b/sca-cpp/trunk/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/sca-cpp/trunk/modules/server/httpd-test b/sca-cpp/trunk/modules/server/httpd-test index 195caa4562..ef30682171 100755 --- a/sca-cpp/trunk/modules/server/httpd-test +++ b/sca-cpp/trunk/modules/server/httpd-test @@ -60,6 +60,10 @@ if [ "$rc" = "0" ]; then 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/sca-cpp/trunk/modules/server/impl-test.cpp b/sca-cpp/trunk/modules/server/impl-test.cpp index a9bd9ad585..0ad1302217 100644 --- a/sca-cpp/trunk/modules/server/impl-test.cpp +++ b/sca-cpp/trunk/modules/server/impl-test.cpp @@ -45,6 +45,10 @@ const failable<value> put(unused const list<value>& params) { 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 @@ const tuscany::value apply(const tuscany::list<tuscany::value>& params) { 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/sca-cpp/trunk/modules/server/mod-eval.hpp b/sca-cpp/trunk/modules/server/mod-eval.hpp index aa13584534..5a8a15bf43 100644 --- a/sca-cpp/trunk/modules/server/mod-eval.hpp +++ b/sca-cpp/trunk/modules/server/mod-eval.hpp @@ -206,7 +206,7 @@ public: 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 @@ const failable<list<value> > applyLifecycleExpr(const list<value>& impls, const * 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 failable<Composite> confComponents(const string& contribPath, const string 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 failable<list<value> > startComponents(const list<value>& impls) { const list<value> simpls = content(fsimpls); debug(impls, "modeval::startComponents::simpls"); - return mkbtree(sort(simpls)); + return mkbrbtree(sort(simpls)); } /** @@ -696,7 +696,7 @@ const failable<int> get(const list<value>& rpath, request_rec* const r, const lv 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 failable<int> post(const list<value>& rpath, request_rec* const r, const l 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 @@ const failable<int> put(const list<value>& rpath, request_rec* const r, const lv } /** + * 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 @@ int translateComponent(request_rec* const r, const list<value>& rpath, const lis // 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 @@ int translateReference(request_rec* const r, const list<value>& rpath, const lis // 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 @@ const int translateRequest(request_rec* const r, const list<value>& rpath, const * 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 @@ const int handleRequest(const list<value>& rpath, request_rec* const r, const li 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 @@ const int handleRequest(const list<value>& rpath, request_rec* const r, const li 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 @@ int handler(request_rec* r) { 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 @@ void childInit(apr_pool_t* p, server_rec* s) { // 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 @@ void childInit(apr_pool_t* p, server_rec* s) { // 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/sca-cpp/trunk/modules/server/server-test.scm b/sca-cpp/trunk/modules/server/server-test.scm index 4bbff6e5c2..b9ed6d584c 100644 --- a/sca-cpp/trunk/modules/server/server-test.scm +++ b/sca-cpp/trunk/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/sca-cpp/trunk/modules/server/wiring-test b/sca-cpp/trunk/modules/server/wiring-test index 7e1aea22b1..a00255e332 100755 --- a/sca-cpp/trunk/modules/server/wiring-test +++ b/sca-cpp/trunk/modules/server/wiring-test @@ -60,6 +60,10 @@ if [ "$rc" = "0" ]; then 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/sca-cpp/trunk/modules/wsgi/atomutil.py b/sca-cpp/trunk/modules/wsgi/atomutil.py index ad6425f062..4b67ef216e 100644 --- a/sca-cpp/trunk/modules/wsgi/atomutil.py +++ b/sca-cpp/trunk/modules/wsgi/atomutil.py @@ -29,7 +29,7 @@ def entryElementValues(e): 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/sca-cpp/trunk/modules/wsgi/client-test.py b/sca-cpp/trunk/modules/wsgi/client-test.py index 867222e792..5c67269885 100644 --- a/sca-cpp/trunk/modules/wsgi/client-test.py +++ b/sca-cpp/trunk/modules/wsgi/client-test.py @@ -36,5 +36,8 @@ def post(collection, item, ref): 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/sca-cpp/trunk/modules/wsgi/composite.py b/sca-cpp/trunk/modules/wsgi/composite.py index baea7aa053..77f2ecdb59 100755 --- a/sca-cpp/trunk/modules/wsgi/composite.py +++ b/sca-cpp/trunk/modules/wsgi/composite.py @@ -237,6 +237,14 @@ def application(e, r): 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/sca-cpp/trunk/modules/wsgi/httputil.py b/sca-cpp/trunk/modules/wsgi/httputil.py index 842460cc6a..f98418d0bc 100644 --- a/sca-cpp/trunk/modules/wsgi/httputil.py +++ b/sca-cpp/trunk/modules/wsgi/httputil.py @@ -124,6 +124,20 @@ class client: 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/sca-cpp/trunk/modules/wsgi/server-test.py b/sca-cpp/trunk/modules/wsgi/server-test.py index 610ec05075..b2bc0efe98 100644 --- a/sca-cpp/trunk/modules/wsgi/server-test.py +++ b/sca-cpp/trunk/modules/wsgi/server-test.py @@ -41,5 +41,8 @@ def post(collection, item): def put(id, item): return True +def patch(id, item): + return True + def delete(id): return True diff --git a/sca-cpp/trunk/modules/wsgi/util.py b/sca-cpp/trunk/modules/wsgi/util.py index 24467fd2cb..f630455901 100644 --- a/sca-cpp/trunk/modules/wsgi/util.py +++ b/sca-cpp/trunk/modules/wsgi/util.py @@ -60,7 +60,7 @@ def reverse(l): 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 cons_stream(car, cdr): 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/sca-cpp/trunk/modules/wsgi/wiring-test b/sca-cpp/trunk/modules/wsgi/wiring-test index cbecc201e8..4a8d15a5bd 100755 --- a/sca-cpp/trunk/modules/wsgi/wiring-test +++ b/sca-cpp/trunk/modules/wsgi/wiring-test @@ -56,6 +56,10 @@ if [ "$rc" = "0" ]; then 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/sca-cpp/trunk/modules/wsgi/wsgi-test b/sca-cpp/trunk/modules/wsgi/wsgi-test index f8334b33ad..8deeb4d0ba 100755 --- a/sca-cpp/trunk/modules/wsgi/wsgi-test +++ b/sca-cpp/trunk/modules/wsgi/wsgi-test @@ -52,6 +52,10 @@ if [ "$rc" = "0" ]; then 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 |