Browse Source

end of the tunnel

stefanocasazza 11 years ago
parent
commit
0095633b29
15 changed files with 1346 additions and 0 deletions
  1. 107 0
      ULib/README.md
  2. 0 0
      ULib/__init__.py
  3. 26 0
      ULib/bash_profile.sh
  4. 28 0
      ULib/benchmark_config
  5. 80 0
      ULib/install.sh
  6. 120 0
      ULib/setup.py
  7. 8 0
      ULib/source_code
  8. 120 0
      ULib/src/db.usp
  9. 123 0
      ULib/src/fortune.h
  10. 153 0
      ULib/src/fortunes.usp
  11. 80 0
      ULib/src/json.usp
  12. 51 0
      ULib/src/plaintext.usp
  13. 160 0
      ULib/src/queries.usp
  14. 184 0
      ULib/src/updates.usp
  15. 106 0
      ULib/src/world.h

+ 107 - 0
ULib/README.md

@@ -0,0 +1,107 @@
+#ULib Benchmarking Test
+
+This is the ULib portion of a [benchmarking test suite](https://github.com/TechEmpower/FrameworkBenchmarks) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+
+* [JSON test source](src/json.usp)
+
+### Data-Store/Database Mapping Test
+
+* [Database test source](src/db.usp)
+
+### Variable Query Test
+
+* [Variable Query test source](src/queries.usp)
+
+### Fortune Query Test
+
+* [Fortune Query test source](src/fortunes.usp)
+
+### Variable Query (update) Test
+
+* [Variable Query (update) test source](src/updates.usp)
+
+### Plaintext Test
+
+* [Plaintext test source](src/plaintext.usp)
+
+## Infrastructure Software Versions
+The tests were run with:
+
+* [ULib Version 1.4.1](https://github.com/stefanocasazza/ULib/archive/v1.4.1.tar.gz)
+
+Output
+======
+
+[/json](http://www.techempower.com/benchmarks/#section=json)
+-----
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:11:10 GMT
+Server: ULib 
+Content-Length: 27
+Content-Type: application/json; charset=UTF-8
+
+{"message":"Hello, World!"}
+```
+
+[/db](http://www.techempower.com/benchmarks/#section=db)
+---
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:14:51 GMT
+Server: ULib 
+Content-Length: 31
+Content-Type: application/json; charset=UTF-8
+
+{"id":6227,"randomNumber":8489}
+```
+
+[/queries?queries=10](http://www.techempower.com/benchmarks/#section=query)
+-------------------
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:14:51 GMT
+Server: ULib 
+Content-Length: 320
+Content-Type: application/json; charset=UTF-8
+
+[{"id":6851,"randomNumber":7598},{"id":3968,"randomNumber":7325},{"id":8159,"randomNumber":348},{"id":9560,"randomNumber":7333},{"id":9938,"randomNumber":9080},{"id":1598,"randomNumber":1623},{"id":3280,"randomNumber":8707},{"id":4521,"randomNumber":6063},{"id":8173,"randomNumber":3690},{"id":3648,"randomNumber":8803}]
+```
+
+[/fortunes](http://www.techempower.com/benchmarks/#section=fortune)
+---------
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:14:51 GMT
+Server: ULib 
+Content-Type: text/html; charset=UTF-8
+Content-Length: 1227
+
+<!doctype html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><tr><td>11</td><td>&lt;script&gt;alert(&quot;This should not be displayed in a browser alert box.&quot;);&lt;/script&gt;</td></tr><tr><td>4</td><td>A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1</td></tr><tr><td>5</td><td>A computer program does what you tell it to do, not what you want it to do.</td></tr><tr><td>2</td><td>A computer scientist is someone who fixes things that aren&apos;t broken.</td></tr><tr><td>8</td><td>A list is only as strong as its weakest link. — Donald Knuth</td></tr><tr><td>0</td><td>Additional fortune added at request time.</td></tr><tr><td>3</td><td>After enough decimal places, nobody gives a damn.</td></tr><tr><td>7</td><td>Any program that runs right is obsolete.</td></tr><tr><td>10</td><td>Computers make very fast, very accurate mistakes.</td></tr><tr><td>6</td><td>Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen</td></tr><tr><td>9</td><td>Feature: A bug with seniority.</td></tr><tr><td>1</td><td>fortune: No such file or directory</td></tr><tr><td>12</td><td>フレームワークのベンチマーク</td></tr></table></body></html>
+```
+
+[/updates?queries=10](http://www.techempower.com/benchmarks/#section=update)
+-------------------
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:14:51 GMT
+Server: ULib 
+Content-Length: 319
+Content-Type: application/json; charset=UTF-8
+
+[{"id":7171,"randomNumber":351},{"id":6019,"randomNumber":9725},{"id":8118,"randomNumber":4023},{"id":7965,"randomNumber":1388},{"id":7797,"randomNumber":2249},{"id":112,"randomNumber":1108},{"id":6127,"randomNumber":4323},{"id":2597,"randomNumber":7509},{"id":2978,"randomNumber":7883},{"id":1111,"randomNumber":2228}]
+```
+
+[/plaintext](http://www.techempower.com/benchmarks/#section=plaintext)
+----------
+```
+HTTP/1.1 200 OK
+Date: Thu, 03 Jul 2014 10:14:51 GMT
+Server: ULib 
+Content-Type: text/plain; charset=UTF-8
+Content-Length: 13
+
+Hello, World!
+```

+ 0 - 0
ULib/__init__.py


+ 26 - 0
ULib/bash_profile.sh

@@ -0,0 +1,26 @@
+if [ -z "${FWROOT}" ]; then
+  export FWROOT=${HOME}/FrameworkBenchmarks
+fi
+#--------------------------------------
+# TROOT - Path of this test's directory
+#--------------------------------------
+if [ -z "${TROOT}" ]; then
+  export TROOT=${FWROOT}/ULib
+fi
+#------------------------------------------------------------ --------------------------------------------
+# IROOT - Path of this test's install directory ($FWROOT/installs or $FWROOT/installs/pertest/<test-name>)
+#------------------------------------------------------------ --------------------------------------------
+if [ -z "${IROOT}" ]; then
+  export IROOT=${FWROOT}/installs
+fi
+
+# Set the root of our ULib installation
+export ULIB_ROOT=${IROOT}/ULib
+
+# Where to find the userver_tcp executable
+export PATH="${ULIB_ROOT}/bin:$PATH"
+
+export ULIB_VERSION=1.4.1
+export ULIB_DOCUMENT_ROOT=${ULIB_ROOT}/ULIB_DOCUMENT_ROOT
+export ULIB_BUILD_OUTPUT=${ULIB_ROOT}/ULIB_BUILD_OUTPUT.txt
+export ULIB_SERVER_OUTPUT=${ULIB_ROOT}/ULIB_SERVER_OUTPUT.txt

+ 28 - 0
ULib/benchmark_config

@@ -0,0 +1,28 @@
+{
+  "framework": "ULib",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "MySQL",
+      "framework": "ULib",
+      "language": "C++",
+      "orm": "Micro",
+      "platform": "ULib",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "ULib",
+      "notes": "",
+      "versus": ""
+    }
+  }]
+}

+ 80 - 0
ULib/install.sh

@@ -0,0 +1,80 @@
+#!/bin/bash
+
+# ---------------------------------------------------------------------------------------------------------------------------------
+# toolset/run-tests.py --install server --install-strategy pertest --install-error-action continue --test ULib --type all --verbose
+# ---------------------------------------------------------------------------------------------------------------------------------
+# INFO:root:Running installation for ULib
+# INSTALL: 
+#    export TROOT=$FWROOT/ULib && 
+#    export IROOT=$FWROOT/installs && 
+#    . $FWROOT/toolset/setup/linux/bash_functions.sh && 
+#    . $FWROOT/ULib/install.sh (cwd=$FWROOT//installs)
+# ---------------------------------------------------------------------------------------------------------------------------------
+
+if [ -z "$TROOT" ]; then
+   return 1
+fi
+
+. ${TROOT}/bash_profile.sh
+
+if [ ! -d "$ULIB_ROOT" ]; then
+  mkdir -p $ULIB_ROOT
+fi
+
+cd ${ULIB_ROOT}
+
+if [ ! -f "benchmark.cfg" ]; then
+  cat <<EOF >benchmark.cfg
+userver {
+ PORT 8080
+ PREFORK_CHILD 8
+ LISTEN_BACKLOG 8192
+ MAX_KEEP_ALIVE 8192
+ DOCUMENT_ROOT ${ULIB_DOCUMENT_ROOT}
+ PID_FILE ${ULIB_ROOT}/userver_tcp.pid
+}
+EOF
+fi
+
+if [ -x "bin/userver_tcp" ] && [ -x "${ULIB_DOCUMENT_ROOT}/db.so" ]; then
+  return 0
+fi
+
+# 1. Download ULib
+if [ ! -f "v${ULIB_VERSION}.tar.gz" ]; then
+	wget -nc --no-check-certificate --trust-server-names -O v${ULIB_VERSION}.tar.gz \
+        https://github.com/stefanocasazza/ULib/archive/v${ULIB_VERSION}.tar.gz 2>/dev/null
+
+	tar xvf v${ULIB_VERSION}.tar.gz 2>/dev/null
+fi
+
+cd ULib-$ULIB_VERSION
+
+# 2. Compile application (userver_tcp)
+# ======================================================================================================
+# TO AVOID configure: error: newly created file is older than distributed files! Check your system clock
+# ======================================================================================================
+find . -exec touch {} \;
+# ======================================================================================================
+
+#           --enable-debug \
+LIBS="-lssl -lcrypto -lz" \
+./configure --prefix=$ULIB_ROOT \
+            --disable-static \
+            --with-mysql \
+            --without-ssl --without-pcre --without-expat \
+            --without-libz --without-libuuid --without-magic \
+            --enable-static-orm-driver=mysql --enable-static-server-plugin=http >$ULIB_BUILD_OUTPUT 2>&1
+
+make -j1 install >>$ULIB_BUILD_OUTPUT 2>&1
+
+# 3. Compile usp pages for benchmark
+cd src/ulib/net/server/plugin/usp
+make -j1 db.la fortunes.la json.la plaintext.la queries.la updates.la >>$ULIB_BUILD_OUTPUT 2>&1
+
+if [ ! -e .libs/db.so ]; then
+   return 1
+fi
+
+mkdir -p $ULIB_DOCUMENT_ROOT
+cp .libs/db.so .libs/fortunes.so .libs/json.so .libs/plaintext.so .libs/queries.so .libs/updates.so $ULIB_DOCUMENT_ROOT

+ 120 - 0
ULib/setup.py

@@ -0,0 +1,120 @@
+# ---------------------------------------------------------------------------------------------------------------------------------
+# toolset/run-tests.py --install server --install-strategy pertest --install-error-action continue --test ULib --type all --verbose
+# ---------------------------------------------------------------------------------------------------------------------------------
+import os
+import sys
+import time
+#import logging
+import setup_util
+import subprocess
+import multiprocessing
+
+#log = logging.getLogger('framework_test')
+
+# --------------------------------------------------------------------------------------------------------
+# TROOT - Path of this test's directory
+# IROOT - Path of this test's install directory ($FWROOT/installs or $FWROOT/installs/pertest/<test-name>)
+# --------------------------------------------------------------------------------------------------------
+script = """
+if [ -n "${TROOT}" ] && [ -n "${IROOT}" ]; then
+  return 0
+fi
+return 1
+"""
+
+p = subprocess.Popen(['sh'], stdin=subprocess.PIPE)
+p.communicate(script)
+if p.returncode != 0:
+  print('ULib: TROOT and IROOT are EMPTY...')
+# log.critical('ULib: TROOT and IROOT are EMPTY...')
+else:
+  script = """
+. ${TROOT}/bash_profile.sh
+if [ -x "${ULIB_ROOT}/bin/userver_tcp" ] && [ -e "${ULIB_DOCUMENT_ROOT}/db.so" ] && [ -f "${ULIB_ROOT}/benchmark.cfg" ]; then
+  return 0
+fi
+return 1
+"""
+
+  p1 = subprocess.Popen(['sh'], stdin=subprocess.PIPE)
+  p1.communicate(script)
+  if p1.returncode != 0:
+    print('ULib install script is NOT BEEN CALLED, I call it now...')
+#   log.critical('ULib install script is NOT BEEN CALLED, I call it now...')
+    subprocess.call("${TROOT}/install.sh", shell=True, executable='/bin/bash')
+    p2 = subprocess.Popen(['sh'], stdin=subprocess.PIPE)
+    p2.communicate(script)
+    if p2.returncode != 0:
+      print('ULib install script FAILED')
+#     log.critical('ULib install script FAILED')
+      ulib_root = subprocess.check_output('. ${TROOT}/bash_profile.sh; printf "$ULIB_ROOT" 2>/dev/null', shell=True, executable='/bin/bash')
+      fprg = ulib_root + "/bin/userver_tcp"
+      if not os.path.exists(fprg):
+        print("ULib server program " + fprg + " NOT EXIST")
+#       log.critical("ULib server program " + fprg + " NOT EXIST")
+      fcfg = ulib_root + "/benchmark.cfg"
+      if not os.path.exists(fcfg):
+        print("ULib configuration file " + fcfg + " NOT EXIST")
+#       log.critical("ULib configuration file " + fcfg + " NOT EXIST")
+      fusp = subprocess.check_output('. ${TROOT}/bash_profile.sh; printf "$ULIB_DOCUMENT_ROOT" 2>/dev/null', shell=True, executable='/bin/bash') + "/db.so"
+      if not os.path.exists(fusp):
+        print("ULib usp page " + fusp + " NOT EXIST")
+#       log.critical("ULib usp page " + fusp + " NOT EXIST")
+      print('Aborting')
+#     log.critical('Aborting')
+      exit(1)
+
+##############
+# start(args)
+##############
+def start(args, logfile, errfile):
+  try:
+    ulib_root = subprocess.check_output('. ${TROOT}/bash_profile.sh; printf "$ULIB_ROOT" 2>/dev/null', shell=True, executable='/bin/bash')
+    fprg = ulib_root + "/bin/userver_tcp"
+    fcfg = ulib_root + "/benchmark.cfg"
+    # 1. Change ULib Server configuration
+    # I don't understand if the two approach are different...
+    #threads = str(args.max_threads)
+    PROCS = str(multiprocessing.cpu_count())
+    setup_util.replace_text(fcfg, "PREFORK_CHILD .*", "PREFORK_CHILD " + PROCS)
+
+    ulib_server_output = subprocess.check_output('. ${TROOT}/bash_profile.sh; printf "$ULIB_SERVER_OUTPUT" 2>/dev/null', shell=True, executable='/bin/bash')
+
+    # 2. Start ULib Server (userver_tcp)
+    print("trying to start ULib server " + fprg + " -c " + fcfg)
+#   log.info("trying to start ULib server " + fprg + " -c " + fcfg)
+
+    # sudo mysqlcheck -v -r -A -u benchmarkdbuser -p
+    os.putenv("ORM_DRIVER","mysql")
+    os.putenv("ORM_OPTION","host=" + args.database_host + " user=benchmarkdbuser password=benchmarkdbpass dbname=hello_world")
+    os.putenv("UMEMPOOL", "1583,1507,-19,45,16458,523,-27,-14,27")
+
+    # Run in the background, but keep stdout/stderr for easy debugging
+    subprocess.Popen(                      fprg + " -c " + fcfg + " >" + ulib_server_output + " 2>&1", shell=True, stdout=logfile, stderr=errfile)
+  # subprocess.Popen("UTRACE=\"0 50M\" " + fprg + " -c " + fcfg + " >" + ulib_server_output + " 2>&1", shell=True, stdout=logfile, stderr=errfile)
+
+    print("ULib server STARTED")
+#   log.info("ULib server STARTED")
+    return 0
+  except subprocess.CalledProcessError:
+    return 1
+
+##############
+# stop()
+##############
+def stop(logfile, errfile):
+  try:
+    ulib_root = subprocess.check_output('. ${TROOT}/bash_profile.sh; printf "$ULIB_ROOT" 2>/dev/null', shell=True, executable='/bin/bash')
+    # Stop ULib Server (userver_tcp)
+    subprocess.check_call("kill -TERM $( cat " + ulib_root + "/userver_tcp.pid )", shell=True, stderr=errfile, stdout=logfile)
+    time.sleep(2);
+    p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
+    out, err = p.communicate()
+    for line in out.splitlines():
+       if 'userver_tcp' in line:
+         pid = int(line.split(None, 2)[1])
+         os.kill(pid, 9)
+    subprocess.call("rm -f " + ulib_root + "/userver_tcp.pid", shell=True, stderr=errfile, stdout=logfile)
+    return 0
+  except subprocess.CalledProcessError:
+    return 1

+ 8 - 0
ULib/source_code

@@ -0,0 +1,8 @@
+./src/db.usp
+./src/world.h
+./src/json.usp
+./src/fortune.h
+./src/queries.usp
+./src/updates.usp
+./src/fortunes.usp
+./src/plaintext.usp

+ 120 - 0
ULib/src/db.usp

@@ -0,0 +1,120 @@
+<!--#
+Test 2: Single database query
+
+This test exercises the framework's object-relational mapper (ORM), random number generator, database driver, and database connection pool.
+
+Requirements
+
+1.  For every request, a single row from a World table must be retrieved from a database table. 
+
+2.  The recommended URI is /db.
+
+3.  The schema for World is id (int, primary key) and randomNumber (int).
+
+4.  The World table is known to contain 10,000 rows.
+
+5.  The row retrieved must be selected by its id using a random number generator (ids range from 1 to 10,000).
+
+6.  The row should be converted to an object using an object-relational mapping (ORM) tool.
+    Tests that do not use an ORM will be identified with the suffix "raw," meaning they use the platform's raw database connectivity.
+
+7.  The object (or database row, if an ORM is not used) must be serialized to JSON.
+
+8.  The response content length should be approximately 32 bytes.
+
+9.  The response content type must be set to application/json.
+
+10. The response headers must include either Content-Length or Transfer-Encoding.
+
+11. The response headers must include Server and Date.
+
+12. Use of an in-memory cache of World objects or rows by the application is not permitted.
+
+13. Use of prepared statements for SQL database tests (e.g., for MySQL) is encouraged but not required.
+
+14. gzip compression is not permitted.
+
+15. Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+16. If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+17. The request handler will be exercised at concurrency levels ranging from 8 to 256.
+
+18. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /db HTTP/1.1
+Host: server
+User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Type: application/json; charset=UTF-8
+Content-Length: 32
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+{"id":3217,"randomNumber":2149}
+-->
+<!--#declaration
+#include "world.h"
+
+#define AS_cpoll_cppsp_DO
+
+#ifndef AS_cpoll_cppsp_DO
+static UValue*	pvalue;
+#endif
+static World*			 pworld_db;
+static UOrmSession*	 psql_db;
+static UOrmStatement* pstmt_db;
+
+static void usp_init_db()
+{
+	U_TRACE(5, "::usp_init_db()")
+
+	psql_db	= U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
+	pstmt_db	= U_NEW(UOrmStatement(*psql_db, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
+
+	if (pstmt_db == 0) U_ERROR("usp_init_db(): we cound't connect to db, exiting...");
+
+	pworld_db = U_NEW(World);
+
+	pstmt_db->use( pworld_db->id);
+	pstmt_db->into(pworld_db->randomNumber);
+
+#ifndef AS_cpoll_cppsp_DO
+	pvalue = U_NEW(UValue(OBJECT_VALUE));
+#endif
+}
+
+static void usp_end_db()
+{
+	U_TRACE(5, "::usp_end_db()")
+
+	delete pstmt_db;
+	delete psql_db;
+	delete pworld_db;
+#ifndef AS_cpoll_cppsp_DO
+	delete pvalue;
+#endif
+}
+-->
+<!--#header
+Content-Type: application/json; charset=UTF-8
+-->
+<!--#code
+pworld_db->id = u_get_num_random(10000);
+
+pstmt_db->execute();
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PRINTF_ADD("{\"id\":%d,\"randomNumber\":%d}", pworld_db->id, pworld_db->randomNumber);
+#else
+USP_JSON_stringify(*pvalue, World, *pworld_db);
+#endif
+-->

+ 123 - 0
ULib/src/fortune.h

@@ -0,0 +1,123 @@
+// fortune.h
+
+#ifndef FORTUNE_H
+#define FORTUNE_H 1
+
+#include <ulib/orm/orm.h>
+#include <ulib/json/value.h>
+
+class Fortune {
+public:
+	// Check for memory error
+	U_MEMORY_TEST
+
+	// Allocator e Deallocator
+	U_MEMORY_ALLOCATOR
+	U_MEMORY_DEALLOCATOR
+
+	int id;
+	UString message;
+
+	// CONSTRUCTOR
+
+	Fortune()
+		{
+		U_TRACE_REGISTER_OBJECT(5, Fortune, "")
+		}
+
+	Fortune(int _id, const UString& _message) : id(_id), message(_message)
+		{
+		U_TRACE_REGISTER_OBJECT(5, Fortune, "%d,%.*S", _id, U_STRING_TO_TRACE(_message))
+		}
+
+	Fortune(const Fortune& f) : id(f.id), message((void*)U_STRING_TO_PARAM(f.message))
+		{
+		U_TRACE_REGISTER_OBJECT(5, Fortune, "%p", &f)
+
+		U_MEMORY_TEST_COPY(f)
+		}
+
+	~Fortune()
+		{
+		U_TRACE_UNREGISTER_OBJECT(5, Fortune)
+		}
+
+	// SERVICE
+
+	bool operator<(const Fortune& other) const { return cmp_obj(&message, &other.message); }
+
+	static int cmp_obj(const void* a, const void* b)
+		{
+		U_TRACE(5, "Fortune::cmp_obj(%p,%p)", a, b)
+
+		return (*(const Fortune**)a)->message.compare((*(const Fortune**)b)->message);
+		}
+
+#ifdef DEBUG
+	const char* dump(bool breset) const
+		{
+		*UObjectIO::os << "id               " << id				   << '\n'
+							<< "message (UString " << (void*)&message << ')';
+
+		if (breset)
+			{
+			UObjectIO::output();
+
+			return UObjectIO::buffer_output;
+			}
+
+		return 0;
+		}
+#endif
+
+private:
+	Fortune& operator=(const Fortune&) { return *this; }
+};
+
+// ORM TEMPLATE SPECIALIZATIONS
+
+template <> class U_EXPORT UOrmTypeHandler<Fortune> : public UOrmTypeHandler_Base {
+public:
+	explicit UOrmTypeHandler(Fortune& val) : UOrmTypeHandler_Base(&val) {}
+
+	void bindParam(UOrmStatement* stmt) const
+		{
+		U_TRACE(0, "UOrmTypeHandler<Fortune>::bindParam(%p)", stmt)
+
+		stmt->bindParam(U_ORM_TYPE_HANDLER(Fortune, id,		  int));
+		stmt->bindParam(U_ORM_TYPE_HANDLER(Fortune, message, UString));
+		}
+
+	void bindResult(UOrmStatement* stmt)
+		{
+		U_TRACE(0, "UOrmTypeHandler<Fortune>::bindResult(%p)", stmt)
+
+		stmt->bindResult(U_ORM_TYPE_HANDLER(Fortune, id,		int));
+		stmt->bindResult(U_ORM_TYPE_HANDLER(Fortune, message, UString));
+		}
+};
+
+// JSON TEMPLATE SPECIALIZATIONS
+
+template <> class U_EXPORT UJsonTypeHandler<Fortune> : public UJsonTypeHandler_Base {
+public:
+	explicit UJsonTypeHandler(Fortune& val) : UJsonTypeHandler_Base(&val) {}
+
+	void toJSON(UValue& json)
+		{
+		U_TRACE(0, "UJsonTypeHandler<Fortune>::toJSON(%p)", &json)
+
+		json.toJSON(U_JSON_TYPE_HANDLER(Fortune, id,		  int));
+		json.toJSON(U_JSON_TYPE_HANDLER(Fortune, message, UString));
+		}
+
+	void fromJSON(UValue& json)
+		{
+		U_TRACE(0, "UJsonTypeHandler<Fortune>::fromJSON(%p)", &json)
+
+		json.fromJSON(U_JSON_TYPE_HANDLER(Fortune, id,		 int));
+		json.fromJSON(U_JSON_TYPE_HANDLER(Fortune, message, UString));
+		}
+};
+
+#endif

+ 153 - 0
ULib/src/fortunes.usp

@@ -0,0 +1,153 @@
+<!--#
+Test 4: Fortunes
+
+This test exercises the ORM, database connectivity, dynamic-size collections,
+sorting, server-side templates, XSS countermeasures, and character encoding.
+
+Requirements
+
+1.  The recommended URI is /fortunes.
+
+2.  A Fortune database table contains a dozen Unix-style fortune-cookie messages.
+
+3.  The schema for Fortune is id (int, primary key) and message (varchar).
+
+4.  Using an ORM, all Fortune objects must be fetched from the Fortune table, and placed into a list data structure.
+    Tests that do not use an ORM will be classified as "raw" meaning they use the platform's raw database connectivity.
+
+5.  The list data structure must be a dynamic-size or equivalent and should not be dimensioned using foreknowledge of
+    the row-count of the database table.
+
+6.  Within the scope of the request, a new Fortune object must be constructed and added to the list. This confirms that
+    the data structure is dynamic-sized. The new fortune is not persisted to the database; it is ephemeral for the scope of the request.
+
+7.  The new Fortune's message must be "Additional fortune added at request time."
+
+8.  The list of Fortune objects must be sorted by the order of the message field. No ORDER BY clause is permitted in the
+    database query (ordering within the query would be of negligible value anyway since a newly instantiated Fortune is
+	 added to the list prior to sorting).
+
+9.  The sorted list must be provided to a server-side template and rendered to simple HTML (see below for minimum template).
+    The resulting HTML table displays each Fortune's id number and message text.
+
+10. This test does not include external assets (CSS, JavaScript); a later test type will include assets.
+
+11. The HTML generated by the template must be sent as a response.
+
+12. Be aware that the message text fields are stored as UTF-8 and one of the fortune cookie messages is in Japanese.
+
+13. The resulting HTML must be delivered using UTF-8 encoding.
+
+14. The Japanese fortune cookie message must be displayed correctly.
+
+15. Be aware that at least one of the message text fields includes a <script> tag.
+
+16. The server-side template must assume the message text cannot be trusted and must escape the message text properly.
+
+17. The implementation is encouraged to use best practices for templates such as layout inheritence, separate header and
+    footer files, and so on. However, this is not required. We request that implementations do not manage assets (JavaScript,
+	 CSS, images). We are deferring asset management until we can craft a more suitable test.
+
+18. The response content type must be set to text/html.
+
+19. The response headers must include either Content-Length or Transfer-Encoding.
+
+20. The response headers must include Server and Date.
+
+21. Use of an in-memory cache of Fortune objects or rows by the application is not permitted.
+
+22. Use of prepared statements for SQL database tests (e.g., for MySQL) is encouraged but not required.
+
+23. gzip compression is not permitted.
+
+24. Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+25. If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+26. The request handler will be exercised at concurrency levels ranging from 8 to 256.
+
+27. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /fortunes HTTP/1.1
+Host: server
+User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Length: 1190
+Content-Type: text/html; charset=UTF-8
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+
+<!doctype html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><tr><td>11</td><td>&lt;script&gt;alert(&quot;This should not be displayed in a browser alert box.&quot;);&lt;/script&gt;</td></tr><tr><td>4</td><td>A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1</td></tr><tr><td>5</td><td>A computer program does what you tell it to do, not what you want it to do.</td></tr><tr><td>2</td><td>A computer scientist is someone who fixes things that aren't broken.</td></tr><tr><td>8</td><td>A list is only as strong as its weakest link. â Donald Knuth</td></tr><tr><td>0</td><td>Additional fortune added at request time.</td></tr><tr><td>3</td><td>After enough decimal places, nobody gives a damn.</td></tr><tr><td>7</td><td>Any program that runs right is obsolete.</td></tr><tr><td>10</td><td>Computers make very fast, very accurate mistakes.</td></tr><tr><td>6</td><td>Emacs is a nice operating system, but I prefer UNIX. â Tom Christaensen</td></tr><tr><td>9</td><td>Feature: A bug with seniority.</td></tr><tr><td>1</td><td>fortune: No such file or directory</td></tr><tr><td>12</td><td>ã¬ã¼ãã¯ã¼ã¯ã®ãã³ããã¼ã¯</td></tr></table></body></html>
+-->
+<!--#declaration
+#include "fortune.h"
+
+static UOrmSession*		  psql_fortune;
+static UOrmStatement*	  pstmt_fortune;
+static Fortune*			  pfortune;
+static UString*           pmessage;
+static UVector<Fortune*>* pvfortune;
+
+static void usp_init_fortunes()
+{
+	U_TRACE(5, "::usp_init_fortunes()")
+
+	psql_fortune  = U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("fortune")));
+	pstmt_fortune = U_NEW(UOrmStatement(*psql_fortune, U_CONSTANT_TO_PARAM("SELECT id, message FROM Fortune")));
+
+	if (pstmt_fortune == 0) U_ERROR("usp_init_fortunes(): we cound't connect to db, exiting...");
+
+	pfortune  = U_NEW(Fortune);
+	pvfortune = U_NEW(UVector<Fortune*>);
+	pmessage  = U_NEW(U_STRING_FROM_CONSTANT("Additional fortune added at request time."));
+
+	pstmt_fortune->into(*pfortune);
+}
+
+static void usp_end_fortunes()
+{
+	U_TRACE(5, "::usp_end_fortunes()")
+
+	delete pstmt_fortune;
+	delete psql_fortune;
+	delete pvfortune;
+	delete pfortune;
+	delete pmessage;
+}
+-->
+<!doctype html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr><!--#code
+Fortune* elem;
+unsigned char encoded[1024];
+
+pstmt_fortune->execute();
+
+	  pvfortune->push_back(U_NEW(Fortune(0, *pmessage)));
+do	{ pvfortune->push_back(U_NEW(Fortune(*pfortune))); } while (pstmt_fortune->nextRow());
+
+pvfortune->sort(Fortune::cmp_obj);
+
+for (uint32_t i = 0, n = pvfortune->size(); i < n; ++i)
+	{
+	elem = (*pvfortune)[i];
+
+	(void) u_xml_encode((const unsigned char*)U_STRING_TO_PARAM(elem->message), encoded);
+
+	USP_PRINTF_ADD(
+		"<tr>"
+		"<td>%d</td>"
+		"<td>%s</td>"
+		"</tr>",
+		elem->id, encoded);
+	}
+
+pvfortune->clear();
+--></table></body></html>

+ 80 - 0
ULib/src/json.usp

@@ -0,0 +1,80 @@
+<!--#
+Test 1: JSON serialization
+
+This test exercises the framework fundamentals including keep-alive support, request routing, request header
+parsing, object instantiation, JSON serialization, response header generation, and request count throughput.
+
+Requirements
+
+1.  For each request, an object mapping the key message to Hello, World! must be instantiated.
+
+2.  The recommended URI is /json.
+
+3.  A JSON serializer must be used to convert the object to JSON.
+
+4.  The response text must be {"message":"Hello, World!"}, but white-space variations are acceptable.
+
+5.  The response content length should be approximately 28 bytes.
+
+6.  The response content type must be set to application/json.
+
+7.  The response headers must include either Content-Length or Transfer-Encoding.
+
+8.  The response headers must include Server and Date.
+
+9.  gzip compression is not permitted.
+
+10. Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+11. If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+12. The request handler will be exercised at concurrency levels ranging from 8 to 256.
+
+13. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /json HTTP/1.1
+Host: server
+User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Type: application/json; charset=UTF-8
+Content-Length: 28
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+{"message":"Hello, World!"}
+-->
+<!--#declaration
+static UString* pkey;
+static UString* pvalue;
+
+static void usp_init_json()
+{
+	U_TRACE(5, "::usp_init_json()")
+
+	pkey   = U_NEW(U_STRING_FROM_CONSTANT("message"));
+	pvalue = U_NEW(U_STRING_FROM_CONSTANT("Hello, World!"));
+}
+
+static void usp_end_json()
+{
+	U_TRACE(5, "::usp_end_json()")
+
+	delete pkey;
+	delete pvalue;
+}
+-->
+<!--#header
+Content-Type: application/json; charset=UTF-8
+-->
+<!--#code
+UValue json(*pkey, *pvalue);
+USP_JSON_PUTS(json);
+-->

+ 51 - 0
ULib/src/plaintext.usp

@@ -0,0 +1,51 @@
+<!--#
+Test 6: Plaintext
+
+This test is an exercise of the request-routing fundamentals only, designed to demonstrate the capacity of
+high-performance platforms in particular. The response payload is still small, meaning good performance is
+still necessary in order to saturate the gigabit Ethernet of the test environment.
+
+Requirements
+
+1.  The recommended URI is /plaintext.
+
+2.  The response content type must be set to text/plain.
+
+3.  The response body must be Hello, World!.
+
+4.  The response headers must include either Content-Length or Transfer-Encoding.
+
+5.  The response headers must include Server and Date.
+
+6.  gzip compression is not permitted.
+
+7.  Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+8.  Server support for HTTP pipelining is strongly encouraged but not required.
+
+9.  If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+10. The request handler will be exercised at 256, 1024, 4096, and 16,384 concurrency.
+
+11. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /plaintext HTTP/1.1
+Host: server
+User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Length: 15
+Content-Type: text/plain; charset=UTF-8
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+Hello, World!
+-->
+Hello, World!

+ 160 - 0
ULib/src/queries.usp

@@ -0,0 +1,160 @@
+<!--#
+Test 3: Multiple database queries
+
+This test is a variation of Test #2 and also uses the World table. Multiple rows are fetched to more dramatically
+punish the database driver and connection pool. At the highest queries-per-request tested (20), this test demonstrates
+all frameworks' convergence toward zero requests-per-second as database activity increases.
+
+Requirements
+
+1.  For every request, an integer query string parameter named queries must be retrieved from the request.
+	 The parameter specifies the number of database queries to execute in preparing the HTTP response (see below).
+
+2.  The recommended URI is /queries.
+
+3.  The queries parameter must be bounded to between 1 and 500. If the parameter is missing, is not an integer,
+	 or is an integer less than 1, the value should be interpreted as 1; if greater than 500, the value should be interpreted as 500.
+
+3.  The schema for World is id (int, primary key) and randomNumber (int).
+
+4.  The request handler must retrieve a set of World objects, equal in count to the queries parameter, from the World database table.
+
+5.  Each row must be selected randomly in the same fashion as the single database query test (Test #2 above).
+
+6.  Since this test is designed to exercise multiple queries, each row must be selected individually by a query.
+	 It is not acceptable to retrieve all required rows using a SELECT ... WHERE id IN (...) clause.
+
+7.  Each World object must be added to a list or array.
+
+8.  The list or array must be serialized to JSON and sent as a response.
+
+9.  The response content type must be set to application/json.
+
+10. The response headers must include either Content-Length or Transfer-Encoding.
+
+11. The response headers must include Server and Date.
+
+12. Use of an in-memory cache of World objects or rows by the application is not permitted.
+
+13. Use of prepared statements for SQL database tests (e.g., for MySQL) is encouraged but not required.
+
+14. gzip compression is not permitted.
+
+15. Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+16. If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+17. The request handler will be exercised at 256 concurrency only.
+
+18. The request handler will be exercised with query counts of 1, 5, 10, 15, and 20.
+
+19. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /queries?queries=10 HTTP/1.1
+Host: server
+User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Type: application/json; charset=UTF-8
+Content-Length: 315
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+
+[{"id":4174,"randomNumber":331},{"id":51,"randomNumber":6544},{"id":4462,"randomNumber":952},{"id":2221,"randomNumber":532},{"id":9276,"randomNumber":3097},{"id":3056,"randomNumber":7293},{"id":6964,"randomNumber":620},{"id":675,"randomNumber":6601},{"id":8414,"randomNumber":6569},{"id":2753,"randomNumber":4065}]
+-->
+<!--#declaration
+#include "world.h"
+
+#define AS_cpoll_cppsp_DO
+
+#ifndef AS_cpoll_cppsp_DO
+static UValue*	pvalue;
+#endif
+static UOrmSession*		psql_queries;
+static UOrmStatement*	pstmt_queries;
+static World*				pworld_queries;
+static UVector<World*>* pvworld_queries;
+
+static void usp_init_queries()
+{
+	U_TRACE(5, "::usp_init_queries()")
+
+	psql_queries  = U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
+	pstmt_queries = U_NEW(UOrmStatement(*psql_queries, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
+
+	if (pstmt_queries == 0) U_ERROR("usp_init_queries(): we cound't connect to db, exiting...");
+
+	pworld_queries  = U_NEW(World);
+	pvworld_queries = U_NEW(UVector<World*>(500));
+
+	pstmt_queries->use( pworld_queries->id);
+	pstmt_queries->into(pworld_queries->randomNumber);
+
+#ifndef AS_cpoll_cppsp_DO
+	pvalue = U_NEW(UValue(ARRAY_VALUE));
+#endif
+}
+
+static void usp_end_queries()
+{
+	U_TRACE(5, "::usp_end_queries()")
+
+	delete pstmt_queries;
+	delete psql_queries;
+	delete pvworld_queries;
+	delete pworld_queries;
+#ifndef AS_cpoll_cppsp_DO
+	delete pvalue;
+#endif
+}
+-->
+<!--#args
+queries;
+-->
+<!--#header
+Content-Type: application/json; charset=UTF-8
+-->
+<!--#code
+int i = 0, num_queries = queries.strtol();
+
+	  if (num_queries <   1) num_queries = 1;
+else if (num_queries > 500) num_queries = 500;
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR('[');
+#endif
+
+while (true)
+	{
+	pworld_queries->id = u_get_num_random(10000);
+
+	pstmt_queries->execute();
+
+#ifdef AS_cpoll_cppsp_DO
+	USP_PRINTF("{\"id\":%d,\"randomNumber\":%d}", pworld_queries->id, pworld_queries->randomNumber);
+#endif
+
+	pvworld_queries->push_back(U_NEW(World(*pworld_queries)));
+
+	if (++i == num_queries) break;
+
+#ifdef AS_cpoll_cppsp_DO
+	USP_PUTS_CHAR(',');
+#endif
+	}
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR(']');
+#else
+USP_JSON_stringify(*pvalue, UVector<World*>, *pvworld_queries);
+#endif
+pvworld_queries->clear();
+-->

+ 184 - 0
ULib/src/updates.usp

@@ -0,0 +1,184 @@
+<!--#
+Test 5: Database updates
+
+This test is a variation of Test #3 that exercises the ORM's persistence of
+objects and the database driver's performance at running UPDATE statements or similar.
+
+Requirements
+
+1.  The recommended URI is /updates.
+
+2.  For every request, an integer query string parameter named queries must be retrieved from the request.
+    The parameter specifies the number of rows to fetch and update in preparing the HTTP response (see below).
+
+3.  The queries parameter must be bounded to between 1 and 500. If the parameter is missing, is not an integer,
+	 or is an integer less than 1, the value should be interpreted as 1; if greater than 500, the value should be interpreted as 500.
+
+3.  The schema for World is id (int, primary key) and randomNumber (int).
+
+4.  The request handler must retrieve a set of World objects, equal in count to the queries parameter, from the World database table.
+
+5.  Each row must be selected randomly in the same fashion as the single database query test (Test #2 above).
+
+6.  At least the randomNumber field must be read from the database result set.
+
+7.  Each World object must have its randomNumber field updated to a new random integer between 1 and 10000.
+
+8.  Each World object must be persisted to the database with its new randomNumber value.
+
+9.  Use of batch updates is acceptable but not required.
+
+10. Use of transactions is acceptable but not required.
+
+11. For raw tests (that is, tests without an ORM), each updated row must receive a unique new randomNumber value.
+    It is not acceptable to change the randomNumber value of all rows to the same random number using an UPDATE ... WHERE id IN (...) clause.
+
+12. Each World object must be added to a list or array.
+
+13. The list or array must be serialized to JSON and sent as a response.
+
+14. The response content type must be set to application/json.
+
+15. The response headers must include either Content-Length or Transfer-Encoding.
+
+16. The response headers must include Server and Date.
+
+17. Use of an in-memory cache of World objects or rows by the application is not permitted.
+
+18. Use of prepared statements for SQL database tests (e.g., for MySQL) is encouraged but not required.
+
+19. gzip compression is not permitted.
+
+20. Server support for HTTP Keep-Alive is strongly encouraged but not required.
+
+21. If HTTP Keep-Alive is enabled, no maximum Keep-Alive timeout is specified by this test.
+
+22. The request handler will be exercised at 256 concurrency only.
+
+23. The request handler will be exercised with query counts of 1, 5, 10, 15, and 20.
+
+24. The request handler will be exercised using GET requests.
+
+Example request:
+
+GET /updates?queries=10 HTTP/1.1
+Host: server
+User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
+Accept-Language: en-US,en;q=0.5
+Connection: keep-alive
+
+Example response:
+
+HTTP/1.1 200 OK
+Content-Type: application/json; charset=UTF-8
+Content-Length: 315
+Server: Example
+Date: Wed, 17 Apr 2013 12:00:00 GMT
+
+[{"id":4174,"randomNumber":331},{"id":51,"randomNumber":6544},{"id":4462,"randomNumber":952},{"id":2221,"randomNumber":532},{"id":9276,"randomNumber":3097},{"id":3056,"randomNumber":7293},{"id":6964,"randomNumber":620},{"id":675,"randomNumber":6601},{"id":8414,"randomNumber":6569},{"id":2753,"randomNumber":4065}]
+-->
+<!--#declaration
+#include "world.h"
+
+#define AS_cpoll_cppsp_DO
+
+#ifndef AS_cpoll_cppsp_DO
+static UValue*	pvalue;
+#endif
+static UOrmSession*		psql_updates;
+static UOrmStatement*	pstmt1;
+static UOrmStatement*	pstmt2;
+static World*				pworld_updates;
+static UVector<World*>* pvworld_updates;
+
+static void usp_init_updates()
+{
+	U_TRACE(5, "::usp_init_updates()")
+
+	psql_updates = U_NEW(UOrmSession(U_CONSTANT_TO_PARAM("hello_world")));
+	pstmt1		 = U_NEW(UOrmStatement(*psql_updates, U_CONSTANT_TO_PARAM("SELECT randomNumber FROM World WHERE id = ?")));
+	pstmt2		 = U_NEW(UOrmStatement(*psql_updates, U_CONSTANT_TO_PARAM("UPDATE World SET randomNumber = ? WHERE id = ?")));
+
+	if (pstmt1 == 0 ||
+		 pstmt2 == 0)
+		{
+		U_ERROR("usp_init_updates(): we cound't connect to db, exiting...");
+		}
+
+	pworld_updates  = U_NEW(World);
+	pvworld_updates = U_NEW(UVector<World*>(500));
+
+	pstmt1->use( pworld_updates->id);
+	pstmt1->into(pworld_updates->randomNumber);
+	pstmt2->use( pworld_updates->randomNumber, pworld_updates->id);
+
+#ifndef AS_cpoll_cppsp_DO
+	pvalue = U_NEW(UValue(ARRAY_VALUE));
+#endif
+}
+
+static void usp_end_updates()
+{
+	U_TRACE(5, "::usp_end_updates()")
+
+	delete pstmt1;
+	delete pstmt2;
+	delete psql_updates;
+	delete pvworld_updates;
+	delete pworld_updates;
+#ifndef AS_cpoll_cppsp_DO
+	delete pvalue;
+#endif
+}
+-->
+<!--#args
+queries;
+-->
+<!--#header
+Content-Type: application/json; charset=UTF-8
+-->
+<!--#code
+int i = 0, num_queries = queries.strtol();
+
+	  if (num_queries <   1) num_queries = 1;
+else if (num_queries > 500) num_queries = 500;
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR('[');
+#endif
+
+while (true)
+	{
+	pworld_updates->id = u_get_num_random(10000);
+
+	pstmt1->execute();
+
+	U_INTERNAL_DUMP("pworld_updates->randomNumber = %d", pworld_updates->randomNumber)
+
+	pworld_updates->randomNumber = u_get_num_random(10000);
+
+	pstmt2->execute();
+
+#ifdef AS_cpoll_cppsp_DO
+	USP_PRINTF("{\"id\":%d,\"randomNumber\":%d}", pworld_updates->id, pworld_updates->randomNumber);
+#endif
+
+	pvworld_updates->push_back(U_NEW(World(*pworld_updates)));
+
+	if (++i == num_queries) break;
+
+#ifdef AS_cpoll_cppsp_DO
+	USP_PUTS_CHAR(',');
+#endif
+	}
+
+#ifdef AS_cpoll_cppsp_DO
+USP_PUTS_CHAR(']');
+#else
+USP_JSON_stringify(*pvalue, UVector<World*>, *pvworld_updates);
+#endif
+
+pvworld_updates->clear();
+-->

+ 106 - 0
ULib/src/world.h

@@ -0,0 +1,106 @@
+// world.h
+
+#ifndef WORLD_H
+#define WORLD_H 1
+
+#include <ulib/orm/orm.h>
+#include <ulib/json/value.h>
+
+class World {
+public:
+	// Check for memory error
+	U_MEMORY_TEST
+
+	// Allocator e Deallocator
+	U_MEMORY_ALLOCATOR
+	U_MEMORY_DEALLOCATOR
+
+	int id, randomNumber;
+
+	// CONSTRUCTOR
+
+	World()
+		{
+		U_TRACE_REGISTER_OBJECT(5, World, "")
+		}
+
+	World(const World& w) : id(w.id), randomNumber(w.randomNumber)
+		{
+		U_TRACE_REGISTER_OBJECT(5, World, "%p", &w)
+
+		U_MEMORY_TEST_COPY(w)
+		}
+
+	~World()
+		{
+		U_TRACE_UNREGISTER_OBJECT(5, World)
+		}
+
+#ifdef DEBUG
+	const char* dump(bool breset) const
+		{
+		*UObjectIO::os << "id           " << id				<< '\n'
+							<< "randomNumber " << randomNumber;
+
+		if (breset)
+			{
+			UObjectIO::output();
+
+			return UObjectIO::buffer_output;
+			}
+
+		return 0;
+		}
+#endif
+
+private:
+	World& operator=(const World&) { return *this; }
+};
+
+// ORM TEMPLATE SPECIALIZATIONS
+
+template <> class U_EXPORT UOrmTypeHandler<World> : public UOrmTypeHandler_Base {
+public:
+	explicit UOrmTypeHandler(World& val) : UOrmTypeHandler_Base(&val) {}
+
+	void bindParam(UOrmStatement* stmt) const
+		{
+		U_TRACE(0, "UOrmTypeHandler<World>::bindParam(%p)", stmt)
+
+		stmt->bindParam(U_ORM_TYPE_HANDLER(World, id,				int));
+		stmt->bindParam(U_ORM_TYPE_HANDLER(World, randomNumber,	int));
+		}
+
+	void bindResult(UOrmStatement* stmt)
+		{
+		U_TRACE(0, "UOrmTypeHandler<World>::bindResult(%p)", stmt)
+
+		stmt->bindResult(U_ORM_TYPE_HANDLER(World, id,				int));
+		stmt->bindResult(U_ORM_TYPE_HANDLER(World, randomNumber,	int));
+		}
+};
+
+// JSON TEMPLATE SPECIALIZATIONS
+
+template <> class U_EXPORT UJsonTypeHandler<World> : public UJsonTypeHandler_Base {
+public:
+	explicit UJsonTypeHandler(World& val) : UJsonTypeHandler_Base(&val) {}
+
+	void toJSON(UValue& json)
+		{
+		U_TRACE(0, "UJsonTypeHandler<World>::toJSON(%p)", &json)
+
+		json.toJSON(U_JSON_TYPE_HANDLER(World, id,				int));
+		json.toJSON(U_JSON_TYPE_HANDLER(World, randomNumber,	int));
+		}
+
+	void fromJSON(UValue& json)
+		{
+		U_TRACE(0, "UJsonTypeHandler<World>::fromJSON(%p)", &json)
+
+		json.fromJSON(U_JSON_TYPE_HANDLER(World, id,				 int));
+		json.fromJSON(U_JSON_TYPE_HANDLER(World, randomNumber, int));
+		}
+};
+
+#endif