diff -ruN wt-4.5.0-orig/CMakeLists.txt wt-4.5.0-patched/CMakeLists.txt
--- wt-4.5.0-orig/CMakeLists.txt	2020-12-15 18:32:14.000000000 +0100
+++ wt-4.5.0-patched/CMakeLists.txt	2021-04-20 16:48:29.573236713 +0200
@@ -38,6 +38,7 @@
 OPTION(ENABLE_FIREBIRD "Build FirebirdSQL backend for Wt::Dbo" ON)
 OPTION(ENABLE_MYSQL "Build mariadb/mysql backend for Wt::Dbo" ON)
 OPTION(ENABLE_MSSQLSERVER "Build Microsoft SQL Server backend for Wt::Dbo" ON)
+OPTION(ENABLE_DB2 "Build DB2 backend for Wt::DBO" ON)
 OPTION(ENABLE_QT4 "Build Qt4 interworking library (libwtwithqt)" ON)
 OPTION(ENABLE_QT5 "Build Qt5 interworking library (libwtwithqt5)" ON)
 OPTION(ENABLE_LIBWTTEST "Build Wt::Test" ON)

diff -ruN wt-4.5.0-orig/src/Wt/Auth/Dbo/UserDatabase.h wt-4.5.0-patched/src/Wt/Auth/Dbo/UserDatabase.h
--- wt-4.5.0-orig/src/Wt/Auth/Dbo/UserDatabase.h	2020-12-15 18:32:14.000000000 +0100
+++ wt-4.5.0-patched/src/Wt/Auth/Dbo/UserDatabase.h	2021-04-20 16:48:27.409255910 +0200
@@ -100,7 +100,7 @@
 	  session_.query<Wt::Dbo::ptr<DboType> >(std::string() +
 	       "select u from " + session_.tableNameQuoted<DboType>() + " u "
 	       "join " + session_.tableNameQuoted<AuthIdentityType>() + " i "
-	       "on u.id = i.\"" + session_.tableName<DboType>() + "_id\"")
+           "on u.\"id\" = i.\"" + session_.tableName<DboType>() + "_id\"")
 	      .where("i.\"provider\" = ?").bind(provider);
       if (authService_ && authService_->identityPolicy() == IdentityPolicy::EmailAddress) {
 	query.where("lower(i.\"identity\") = lower(?)").bind(identity);
@@ -359,7 +360,7 @@
 	    (std::string() +
 	     "select u from " + session_.tableNameQuoted<DboType>() + " u "
 	     "join " + session_.tableNameQuoted<AuthTokenType>() + " t "
-	     "on u.id = t.\"" + session_.tableName<DboType>() + "_id\"")
+	     "on u.\"id\" = t.\"" + session_.tableName<DboType>() + "_id\"")
 	    .where("t.\"value\" = ?").bind(hash)
 	    .where("t.\"expires\" > ?").bind(WDateTime::currentDateTime()));
     t.commit();
diff -ruN wt-4.5.0-orig/src/Wt/Dbo/backend/CMakeLists.txt wt-4.5.0-patched/src/Wt/Dbo/backend/CMakeLists.txt
--- wt-4.5.0-orig/src/Wt/Dbo/backend/CMakeLists.txt	2020-12-15 18:32:14.000000000 +0100
+++ wt-4.5.0-patched/src/Wt/Dbo/backend/CMakeLists.txt	2021-04-20 16:48:27.289256974 +0200
@@ -398,3 +400,75 @@
   MESSAGE("** Wt::Dbo: not building Microsoft SQL Server backend.")
   SET(HAVE_MSSQLSERVER OFF CACHE INTERNAL "building Microsoft SQL Server backend" FORCE)
 ENDIF(ENABLE_MSSQLSERVER AND ODBC_FOUND)
+
+
+
+
+IF(ENABLE_DB2 AND ODBC_FOUND)
+
+  SET(HAVE_DB2 ON CACHE INTERNAL "building DB2 backend" FORCE)
+
+  MESSAGE("** Wt::Dbo: building DB2 backend.")
+
+  IF(WIN32)
+    IF(SHARED_LIBS)
+      CONFIGURE_FILE(wtdbodb2-version.rc.in
+                     ${CMAKE_CURRENT_BINARY_DIR}/wtdbodb2-version.rc)
+      SET(db2_SRCS ${db2_SRCS}
+          ${CMAKE_CURRENT_BINARY_DIR}/wtdbodb2-version.rc)
+    ENDIF(SHARED_LIBS)
+  ENDIF(WIN32)
+
+  ADD_LIBRARY(wtdbodb2
+    DB2.h DB2.C
+    ${db2_SRCS}
+  )
+
+  SET_PROPERTY(TARGET wtdbodb2 PROPERTY C_VISIBILITY_PRESET hidden)
+  SET_PROPERTY(TARGET wtdbodb2 PROPERTY CXX_VISIBILITY_PRESET hidden)
+  SET_PROPERTY(TARGET wtdbodb2 PROPERTY VISIBILITY_INLINES_HIDDEN YES)
+
+  TARGET_LINK_LIBRARIES(wtdbodb2
+    PUBLIC
+      wtdbo
+    PRIVATE
+      ${ODBC_LIBRARIES}
+  )
+
+  INCLUDE_DIRECTORIES(${ODBC_INCLUDE})
+
+  INSTALL(TARGETS wtdbodb2
+    EXPORT wt-target-wtdbodb2
+    RUNTIME DESTINATION bin
+    LIBRARY DESTINATION ${LIB_INSTALL_DIR}
+    ARCHIVE DESTINATION ${LIB_INSTALL_DIR})
+
+  INSTALL(EXPORT wt-target-wtdbodb2
+          DESTINATION ${CMAKE_INSTALL_DIR}/wt
+          NAMESPACE Wt::)
+
+  SET_TARGET_PROPERTIES(
+    wtdbodb2
+  PROPERTIES
+    EXPORT_NAME DboDB2
+    VERSION ${VERSION_SERIES}.${VERSION_MAJOR}.${VERSION_MINOR}
+    DEBUG_POSTFIX ${DEBUG_LIB_POSTFIX}
+    )
+
+  IF(MSVC)
+    SET_TARGET_PROPERTIES(
+      wtdbodb2
+    PROPERTIES
+      COMPILE_FLAGS "${BUILD_PARALLEL} /wd4251 /wd4275 /wd4267"
+    )
+    SET_TARGET_PROPERTIES(wtdbodb2 PROPERTIES FOLDER "dbo")
+  endif(MSVC)
+
+  INSTALL_FILES(/include/Wt/Dbo/backend "^.*DB2.*h$")
+ELSE(ENABLE_DB2 AND ODBC_FOUND)
+  MESSAGE("** Wt::Dbo: not building DB2 backend.")
+  IF(ENABLE_DB2)
+    MESSAGE("     ODBC not found")
+  ENDIF(ENABLE_DB2)
+  SET(HAVE_DB2 OFF CACHE INTERNAL "building DB2 backend" FORCE)
+ENDIF(ENABLE_DB2 AND ODBC_FOUND)
diff -ruN wt-4.5.0-orig/src/Wt/Dbo/backend/DB2.C wt-4.5.0-patched/src/Wt/Dbo/backend/DB2.C
--- wt-4.5.0-orig/src/Wt/Dbo/backend/DB2.C	1970-01-01 01:00:00.000000000 +0100
+++ wt-4.5.0-patched/src/Wt/Dbo/backend/DB2.C	2021-04-20 16:48:27.289256974 +0200
@@ -0,0 +1,1075 @@
+/*
+ * Copyright (C) 2017 Emweb bv, Herent, Belgium.
+ *
+ * See the LICENSE file for terms of use.
+ */
+#include "Wt/Dbo/backend/DB2.h"
+
+#include "Wt/Dbo/Exception.h"
+#include "Wt/Dbo/Logger.h"
+#include "Wt/Dbo/StringStream.h"
+
+#include "Wt/Date/date.h"
+
+#ifdef WT_WIN32
+#define NOMINMAX
+#include <windows.h>
+#endif // WT_WIN32
+#include <sql.h>
+#include <sqlext.h>
+#ifndef WT_WIN32
+#include <sqlucode.h>
+
+#include <codecvt>
+#include <string>
+#endif // WT_WIN32
+
+#include <cstring>
+#include <iostream>
+#include <vector>
+
+// define from sqlncli.h
+// See: https://docs.microsoft.com/en-us/sql/relational-databases/native-client/features/using-multiple-active-result-sets-mars
+#ifndef SQL_COPT_SS_MARS_ENABLED
+#define SQL_COPT_SS_MARS_ENABLED 1224
+#define SQL_MARS_ENABLED_YES 1L
+#endif
+
+namespace Wt {
+  namespace Dbo {
+
+LOGGER("Dbo.backend.DB2Server");
+
+    namespace backend {
+
+class DB2ServerException : public Exception
+{
+public:
+  DB2ServerException(const std::string& msg,
+                       const std::string &sqlState = std::string())
+   : Exception(msg, sqlState)
+  { }
+};
+
+namespace {
+
+static void handleErr(SQLSMALLINT handleType, SQLHANDLE handle, SQLRETURN rc)
+{
+  if (SQL_SUCCEEDED(rc))
+    return;
+
+  SQLCHAR sqlState[6];
+  SQLINTEGER nativeErr = 0;
+  SQLSMALLINT msgLength = 0;
+  SQLCHAR buf[SQL_MAX_MESSAGE_LENGTH];
+  SQLGetDiagRec(
+    handleType,
+    handle,
+    1,
+    sqlState,
+    &nativeErr,
+    buf,
+    sizeof(buf),
+    &msgLength
+  );
+  throw DB2ServerException(
+          std::string((const char*)buf, msgLength),
+          std::string((const char*)sqlState, 5));
+}
+
+#ifndef WT_WIN32
+std::u16string toUTF16(const std::string &str)
+{
+  return
+    std::wstring_convert<
+      std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(str.data());
+}
+#endif // WT_WIN32
+
+#ifdef WT_WIN32
+typedef std::basic_string<SQLWCHAR> ConnectionStringType;
+#else // WT_WIN32
+typedef std::u16string ConnectionStringType;
+#endif // WT_WIN32
+
+}
+
+struct DB2::Impl {
+  Impl(const ConnectionStringType &str)
+    : env(NULL),
+      dbc(NULL),
+      stmt(NULL),
+      connectionString(str)
+  {
+    resultBuffer.buf = (char*)malloc(256);
+    resultBuffer.size = 256;
+  }
+
+  Impl(const Impl &other)
+    : env(NULL),
+      dbc(NULL),
+      stmt(NULL),
+      connectionString(other.connectionString)
+  {
+    resultBuffer.buf = (char*)malloc(256);
+    resultBuffer.size = 256;
+  }
+
+  ~Impl()
+  {
+    free(resultBuffer.buf);
+    if (stmt)
+      SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+    if (dbc) {
+      SQLDisconnect(dbc);
+      SQLFreeHandle(SQL_HANDLE_DBC, dbc);
+    }
+    if (env)
+      SQLFreeHandle(SQL_HANDLE_ENV, env);
+  }
+
+  void connect()
+  {
+    // Create SQL env handle
+    SQLRETURN res = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
+    if (res == SQL_ERROR)
+      throw DB2ServerException("Failed to allocate ODBC environment handle!");
+    // Set ODBC version to 3
+    res = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
+    handleErr(SQL_HANDLE_ENV, env, res);
+    // Create SQL connection handle
+    res = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
+    handleErr(SQL_HANDLE_ENV, env, res);
+    // Turn off autocommit
+    res = SQLSetConnectAttrW(dbc, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER);
+    handleErr(SQL_HANDLE_DBC, dbc, res);
+    // Turn on MARS (Multiple Active Result Sets)
+//    res = SQLSetConnectAttrW(dbc, SQL_COPT_SS_MARS_ENABLED, (SQLPOINTER)SQL_MARS_ENABLED_YES, SQL_IS_UINTEGER);
+    handleErr(SQL_HANDLE_DBC, dbc, res);
+    // Connect
+    res = SQLDriverConnectW(
+      dbc,
+      NULL,
+      (SQLWCHAR*)&(connectionString)[0],
+      connectionString.size(),
+      NULL,
+      0,
+      NULL,
+      SQL_DRIVER_NOPROMPT);
+    handleErr(SQL_HANDLE_DBC, dbc, res);
+  }
+
+  SQLHENV env; // Environment handle
+  SQLHDBC dbc; // Connection handle
+  SQLHSTMT stmt; // Statement handle for executeSql
+  ConnectionStringType connectionString;
+  struct ResultBuffer {
+    char *buf;
+    std::size_t size;
+  } resultBuffer;
+};
+
+class DB2ServerStatement : public SqlStatement {
+public:
+  DB2ServerStatement(DB2 &conn, const std::string &sql)
+    : paramValues_(NULL),
+      parameterCount_(0),
+      resultColCount_(0),
+      stmt_(NULL),
+      conn_(conn),
+      sql_(sql),
+      affectedRows_(0),
+      lastId_(-1)
+  {
+    SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_STMT, conn.impl_->dbc, &stmt_);
+    handleErr(SQL_HANDLE_DBC, conn.impl_->dbc, rc);
+#ifdef WT_WIN32
+    if (sql.empty()) {
+      // Empty query, should be an error, but we'll leave the reporting
+      // of that error to ODBC
+      SQLWCHAR wstr[] = L"";
+      rc = SQLPrepareW(stmt_, wstr, 0);
+    } else {
+      int wstrlen = MultiByteToWideChar(CP_UTF8, 0, &sql[0], sql.size(), NULL, 0);
+      assert(wstrlen != 0);
+      SQLWCHAR *wstr = new SQLWCHAR[wstrlen + 1];
+      wstrlen = MultiByteToWideChar(CP_UTF8, 0, &sql[0], sql.size(), wstr, wstrlen);
+      assert(wstrlen != 0);
+      wstr[wstrlen] = 0;
+      rc = SQLPrepareW(stmt_, wstr, wstrlen);
+      delete[] wstr;
+    }
+#else // WT_WIN32
+    std::u16string wstr = toUTF16(sql);
+    rc = SQLPrepareW(stmt_, (SQLWCHAR*)&wstr[0], wstr.size());
+#endif // WT_WIN32
+    handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    rc = SQLNumParams(stmt_, &parameterCount_);
+    handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    SQLSMALLINT numCols = 0;
+    rc = SQLNumResultCols(stmt_, &numCols);
+    handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    resultColCount_ = numCols;
+    if (parameterCount_ > 0) {
+      paramValues_ = new Value[parameterCount_];
+      std::memset(paramValues_, 0, parameterCount_ * sizeof(Value));
+    }
+  }
+
+  virtual ~DB2ServerStatement()
+  {
+    if (stmt_) {
+      SQLFreeStmt(stmt_, SQL_CLOSE);
+      SQLFreeHandle(SQL_HANDLE_STMT, stmt_);
+    }
+    for (SQLSMALLINT i = 0; i < parameterCount_; ++i)
+      paramValues_[i].clear();
+    delete[] paramValues_;
+  }
+
+
+  virtual void reset() override
+  {
+    SQLFreeStmt(stmt_, SQL_CLOSE);
+  }
+
+  virtual void bind(int column, const std::string &value) override
+  {
+    checkColumnIndex(column);
+    Value &v = paramValues_[column];
+    bool newPtr = true;
+    if (value.empty()) {
+      newPtr = createOrResizeBuffer(v, 0);
+      v.lengthOrInd = 0;
+    } else {
+#ifdef WT_WIN32
+      // Convert value from UTF-8 to WCHAR (UTF-16)
+      // Measure length required
+      int bufsize = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), value.size(), NULL, 0);
+      assert(bufsize != 0);
+      newPtr = createOrResizeBuffer(v, (bufsize + 1) * sizeof(WCHAR));
+      bufsize = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), value.size(), (WCHAR*)v.v.buf.p, bufsize);
+      assert(bufsize != 0);
+      ((WCHAR*)v.v.buf.p)[bufsize] = 0;
+      v.lengthOrInd = bufsize * sizeof(WCHAR);
+#else // WT_WIN32
+      newPtr = createOrResizeBuffer(v, value.size() + 1);
+      memcpy(v.v.buf.p, value.c_str(), value.size() + 1);
+      v.lengthOrInd = value.size();
+#endif // WT_WIN32
+    }
+    if (newPtr || v.type != SQL_C_WCHAR) {
+      v.type = SQL_C_WCHAR;
+      SQLRETURN rc = SQLBindParameter(
+        /*StatementHandle: */stmt_,
+        /*ParameterNumber: */column + 1,
+        /*InputOutputType: */SQL_PARAM_INPUT,
+#ifdef WT_WIN32
+        /*ValueType: */SQL_C_WCHAR,
+#else // WT_WIN32
+        /*ValueType: */SQL_C_CHAR, // UTF-8 to UTF-16 done by SQL Server driver
+#endif // WT_WIN32
+        /*ParameterType: */SQL_WVARCHAR,
+        /*ColumnSize: */0, // Ignored
+        /*DecimalDigits: */0, // ignored
+        /*ParameterValuePtr: */v.v.buf.p,
+        /*BufferLength: */v.v.buf.size,
+        /*StrLen_or_IndPtr: */&v.lengthOrInd
+      );
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    }
+  }
+
+  virtual void bind(int column, short value) override
+  {
+    checkColumnIndex(column);
+    Value &v = paramValues_[column];
+    if (v.type == SQL_C_SSHORT) {
+      v.v.s = value;
+      v.lengthOrInd = 0;
+    } else {
+      v.clear();
+      v.v.s = value;
+      v.type = SQL_C_SSHORT;
+      SQLRETURN rc = SQLBindParameter(
+        /*StatementHandle: */stmt_,
+        /*ParameterNumber: */column + 1,
+        /*InputOutputType: */SQL_PARAM_INPUT,
+        /*ValueType: */SQL_C_SSHORT,
+        /*ParameterType: */SQL_INTEGER,
+        /*ColumnSize: */0, // ignored
+        /*DecimalDigits: */0, // ignored
+        /*ParameterValuePtr: */&v.v.s,
+        /*BufferLength: */0,
+        /*StrLen_or_IndPtr: */&v.lengthOrInd
+      );
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    }
+  }
+
+  virtual void bind(int column, int value) override
+  {
+    checkColumnIndex(column);
+    Value &v = paramValues_[column];
+    if (v.type == SQL_C_SLONG) {
+      v.v.i = value;
+      v.lengthOrInd = 0;
+    } else {
+      v.clear();
+      v.v.i = value;
+      v.type = SQL_C_SLONG;
+      SQLRETURN rc = SQLBindParameter(
+        /*StatementHandle: */stmt_,
+        /*ParameterNumber: */column + 1,
+        /*InputOutputType: */SQL_PARAM_INPUT,
+        /*ValueType: */SQL_C_SLONG,
+        /*ParameterType: */SQL_INTEGER,
+        /*Columnsize: */0, // seems ignored?
+        /*DecimalDigits: */0, // ignored
+        /*ParameterValuePtr: */&v.v.i,
+        /*BufferLength: */0,
+        /*StrLen_or_IndPtr: */&v.lengthOrInd
+      );
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    }
+  }
+
+  virtual void bind(int column, long long value) override
+  {
+    checkColumnIndex(column);
+    Value &v = paramValues_[column];
+    if (v.type == SQL_C_SBIGINT) {
+      v.v.ll = value;
+      v.lengthOrInd = 0;
+    } else {
+      v.clear();
+      v.v.ll = value;
+      v.type = SQL_C_SBIGINT;
+      SQLRETURN rc = SQLBindParameter(
+        /*StatementHandle: */stmt_,
+        /*ParameterNumber: */column + 1,
+        /*InputOutputType: */SQL_PARAM_INPUT,
+        /*ValueType: */SQL_C_SBIGINT,
+        /*ParameterType: */SQL_BIGINT,
+        /*Columnsize: */0, // ignored
+        /*DecimalDigits: */0, // ignored
+        /*ParameterValuePtr: */&v.v.ll,
+        /*BufferLength: */0,
+        /*StrLen_or_IndPtr: */&v.lengthOrInd
+      );
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    }
+  }
+
+  virtual void bind(int column, float value) override
+  {
+    checkColumnIndex(column);
+    Value &v = paramValues_[column];
+    if (v.type == SQL_C_FLOAT) {
+      v.v.f = value;
+      v.lengthOrInd = 0;
+    } else {
+      v.clear();
+      v.v.f = value;
+      v.type = SQL_C_FLOAT;
+      SQLRETURN rc = SQLBindParameter(
+        /*StatementHandle: */stmt_,
+        /*ParameterNumber: */column + 1,
+        /*InputOutputType: */SQL_PARAM_INPUT,
+        /*ValueType: */SQL_C_FLOAT,
+        /*ParameterType: */SQL_REAL,
+        /*Columnsize: */0, // seems ignored?
+        /*DecimalDigits: */0, // ignored
+        /*ParameterValuePtr: */&v.v.f,
+        /*BufferLength: */0,
+        /*StrLen_or_IndPtr: */&v.lengthOrInd
+      );
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    }
+  }
+
+  virtual void bind(int column, double value) override
+  {
+    checkColumnIndex(column);
+    Value &v = paramValues_[column];
+    if (v.type == SQL_C_DOUBLE) {
+      v.v.d = value;
+      v.lengthOrInd = 0;
+    } else {
+      v.clear();
+      v.v.d = value;
+      v.type = SQL_C_DOUBLE;
+      SQLRETURN rc = SQLBindParameter(
+        /*StatementHandle: */stmt_,
+        /*ParameterNumber: */column + 1,
+        /*InputOutputType: */SQL_PARAM_INPUT,
+        /*ValueType: */SQL_C_DOUBLE,
+        /*ParameterType: */SQL_DOUBLE,
+        /*Columnsize: */0, // seems ignored?
+        /*DecimalDigits: */0, // ignored
+        /*ParameterValuePtr: */&v.v.d,
+        /*BufferLength: */0,
+        /*StrLen_or_IndPtr: */&v.lengthOrInd
+      );
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    }
+  }
+
+  virtual void bind(
+    int column,
+    const std::chrono::system_clock::time_point& value,
+    SqlDateTimeType type) override
+  {
+    checkColumnIndex(column);
+    Value &v = paramValues_[column];
+    if (type == SqlDateTimeType::Date) {
+      if (v.type != SQL_C_TYPE_DATE)
+        v.clear();
+      v.lengthOrInd = 0;
+      SQL_DATE_STRUCT &date = paramValues_[column].v.date;
+      auto daypoint = date::floor<date::days>(value);
+      auto ymd = date::year_month_day(daypoint);
+      date.year = (int)ymd.year();
+      date.month = (unsigned)ymd.month();
+      date.day = (unsigned)ymd.day();
+      if (v.type != SQL_C_TYPE_DATE) {
+        v.type = SQL_C_TYPE_DATE;
+        SQLRETURN rc = SQLBindParameter(
+          /*StatementHandle: */stmt_,
+          /*ParameterNumber: */column + 1,
+          /*InputOutputType: */SQL_PARAM_INPUT,
+          /*ValueType: */SQL_C_TYPE_DATE,
+          /*ParameterType: */SQL_TYPE_DATE,
+          /*ColumnSize: */0,
+          /*DecimalDigits: */0,
+          /*ParameterValuePtr: */&date,
+          /*BufferLength: */0,
+          /*StrLen_or_IndPtr: */&v.lengthOrInd
+        );
+        handleErr(SQL_HANDLE_STMT, stmt_, rc);
+      }
+    } else {
+      if (v.type != SQL_C_TYPE_TIMESTAMP)
+        v.clear();
+      v.lengthOrInd = 0;
+      SQL_TIMESTAMP_STRUCT &ts = paramValues_[column].v.timestamp;
+      auto daypoint = date::floor<date::days>(value);
+      auto ymd = date::year_month_day(daypoint);
+      auto tod = date::make_time(value - daypoint);
+      ts.year = (int)ymd.year();
+      ts.month = (unsigned)ymd.month();
+      ts.day = (unsigned)ymd.day();
+      ts.hour = tod.hours().count();
+      ts.minute = tod.minutes().count();
+      ts.second = tod.seconds().count();
+      ts.fraction = std::chrono::nanoseconds(std::chrono::duration_cast<
+        std::chrono::duration<long long, std::ratio_multiply<std::ratio<100>, std::nano>>>(tod.subseconds())).count();
+      if (v.type != SQL_C_TYPE_TIMESTAMP) {
+        v.type = SQL_C_TYPE_TIMESTAMP;
+        SQLRETURN rc = SQLBindParameter(
+          /*StatementHandle: */stmt_,
+          /*ParameterNumber: */column + 1,
+          /*InputOutputType: */SQL_PARAM_INPUT,
+          /*ValueType: */SQL_C_TYPE_TIMESTAMP,
+          /*ParameterType: */SQL_TYPE_TIMESTAMP,
+          /*ColumnSize: */0,
+          /*DecimalDigits: */7, // SQL Server limit: max 7 decimal digits
+          /*ParameterValuePtr: */&ts,
+          /*BufferLength: */0,
+          /*StrLen_or_IndPtr: */&v.lengthOrInd
+        );
+        handleErr(SQL_HANDLE_STMT, stmt_, rc);
+      }
+    }
+  }
+
+  virtual void bind(
+    int column,
+    const std::chrono::duration<int, std::milli>& value) override
+  {
+    long long msec = value.count();
+    bind(column, msec);
+  }
+
+  virtual void bind(
+    int column,
+    const std::vector<unsigned char>& value) override
+  {
+    checkColumnIndex(column);
+    Value &v = paramValues_[column];
+    bool newPtr = createOrResizeBuffer(v, value.size());
+    v.lengthOrInd = value.size();
+    memcpy(v.v.buf.p, value.data(), value.size());
+    if (newPtr || v.type != SQL_C_BINARY) {
+      v.type = SQL_C_BINARY;
+      SQLRETURN rc = SQLBindParameter(
+        /*StatementHandle: */stmt_,
+        /*ParameterNumber: */column + 1,
+        /*InputOutputType: */SQL_PARAM_INPUT,
+        /*ValueType: */SQL_C_BINARY,
+        /*ParameterType: */SQL_VARBINARY,
+        /*ColumnSize: */0, // ignored
+        /*DecimalDigits: */0, // ignored
+        /*ParameterValuePtr: */v.v.buf.p,
+        /*BufferLength: */v.v.buf.size,
+        /*StrLen_or_IndPtr: */&v.lengthOrInd
+      );
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    }
+  }
+
+  virtual void bindNull(int column) override
+  {
+    checkColumnIndex(column);
+    Value &v = paramValues_[column];
+    if (v.type != 0)
+      v.lengthOrInd = SQL_NULL_DATA;
+    else {
+      v.clear();
+      v.type = SQL_C_CHAR;
+      v.v.buf.p = 0;
+      v.v.buf.size = SQL_NULL_DATA;
+      SQLRETURN rc = SQLBindParameter(
+        /*StatementHandle: */stmt_,
+        /*ParameterNumber: */column + 1,
+        /*InputOutputType: */SQL_PARAM_INPUT,
+        /*ValueType: */SQL_C_CHAR,
+        /*ParameterType: */SQL_VARCHAR,
+        /*Columnsize: */0,
+        /*DecimalDigits: */0,
+        /*ParameterValuePtr: */NULL,
+        /*BufferLength: */0,
+        /*StrLen_or_IndPtr: */&v.v.buf.size
+      );
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    }
+  }
+
+  virtual void execute() override
+  {
+    if (conn_.showQueries()) {
+      LOG_INFO(sql_);
+    }
+
+    SQLRETURN rc = SQLExecute(stmt_);
+    if (rc != SQL_NO_DATA) // SQL_NO_DATA can occur when no rows are affected
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+
+    rc = SQLRowCount(stmt_, &affectedRows_);
+    handleErr(SQL_HANDLE_STMT, stmt_, rc);
+  }
+
+  virtual long long insertedId() override
+  {
+      char *last_id = (char*)malloc(32);
+      char *sql;
+      SQLHANDLE hstmt;
+      SQLLEN out_length;
+
+      SQLAllocHandle(SQL_HANDLE_STMT, conn_.impl_->dbc, &hstmt);
+      sql = (char*)"SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1";
+      SQLExecDirect(hstmt, (SQLCHAR*)sql, strlen(sql));
+      SQLBindCol(hstmt, 1, SQL_C_CHAR, last_id, 32, &out_length);
+      SQLFetch(hstmt);
+      SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
+      return std::atoi(last_id);
+  }
+
+  virtual int affectedRowCount() override
+  {
+    return static_cast<int>(affectedRows_);
+  }
+
+  virtual bool nextRow() override
+  {
+    SQLRETURN rc = SQLFetch(stmt_);
+    if (rc == SQL_NO_DATA)
+      return false;
+    else {
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+      return true;
+    }
+  }
+
+  virtual int columnCount() const override
+  {
+    return resultColCount_;
+  }
+
+  virtual bool getResult(int column, std::string *value, int size) override
+  {
+    SQLCHAR colName[32];
+    SQLSMALLINT colNameLen;
+    SQLSMALLINT colType;
+    SQLULEN colSize;
+    SQLSMALLINT colScale;
+    SQLLEN colDataDisplaySize;
+    SQLDescribeCol(stmt_, (SQLSMALLINT)(column + 1), colName, sizeof(colName), &colNameLen, &colType, &colSize, &colScale, NULL);
+    SQLColAttribute(stmt_, (SQLSMALLINT)(column + 1), SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &colDataDisplaySize);
+
+    DB2::Impl::ResultBuffer &resultBuffer = conn_.impl_->resultBuffer;
+    std::size_t resultBufferPos = 0;
+    SQLLEN strLen_or_ind = SQL_NO_TOTAL;
+      SQLRETURN rc = SQLGetData(
+        stmt_,
+        column + 1,
+#ifdef WT_WIN32
+        SQL_C_WCHAR,
+#else // WT_WIN32
+        SQL_C_CHAR, // conversion from UTF-16 to UTF-8 done by driver
+#endif // WT_WIN32
+        &resultBuffer.buf[resultBufferPos],
+        colSize,
+        &strLen_or_ind
+      );
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+      if (strLen_or_ind == SQL_NULL_DATA) {
+        return false; // NULL
+      } else {
+        resultBuffer.buf = reinterpret_cast<char*>(resultBuffer.buf);
+        strLen_or_ind = strlen(resultBuffer.buf);
+      }
+    if (resultBufferPos == 0 && strLen_or_ind == 0) {
+      value->clear();
+      return true; // empty string
+    }
+    std::size_t totalDataSize = resultBufferPos + strLen_or_ind;
+#ifdef WT_WIN32
+    int strlen = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)resultBuffer.buf, totalDataSize / sizeof(WCHAR), NULL, 0, NULL, NULL);
+    assert(strlen != 0);
+    value->clear();
+    value->resize(strlen);
+    strlen = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)resultBuffer.buf, totalDataSize / sizeof(WCHAR), &(*value)[0], strlen, NULL, NULL);
+    assert(strlen != 0);
+#else // WT_WIN32
+    *value = std::string(resultBuffer.buf, totalDataSize);
+#endif // WT_WIN32
+    return true;
+  }
+
+  virtual bool getResult(int column, short * value) override
+  {
+    return getRes<SQL_C_SSHORT>(column, value);
+  }
+
+  virtual bool getResult(int column, int * value) override
+  {
+    return getRes<SQL_C_SLONG>(column, value);
+  }
+
+  virtual bool getResult(int column, long long * value) override
+  {
+    return getRes<SQL_C_SBIGINT>(column, value);
+  }
+
+  virtual bool getResult(int column, float * value) override
+  {
+    return getRes<SQL_C_FLOAT>(column, value);
+  }
+
+  virtual bool getResult(int column, double * value) override
+  {
+    return getRes<SQL_C_DOUBLE>(column, value);
+  }
+
+  virtual bool getResult(
+    int column,
+    std::chrono::system_clock::time_point *value,
+    SqlDateTimeType type) override
+  {
+    if (type == SqlDateTimeType::Date) {
+      SQL_DATE_STRUCT date;
+      bool result = getRes<SQL_C_TYPE_DATE>(column, &date);
+      if (!result)
+        return false; // NULL
+      *value = date::sys_days{ date::year{ date.year } / date.month / date.day };
+      return true;
+    } else {
+      SQL_TIMESTAMP_STRUCT ts;
+      bool result = getRes<SQL_C_TYPE_TIMESTAMP>(column, &ts);
+      if (!result)
+        return false; // NULL
+      *value = 
+        date::sys_days{ date::year{ ts.year } / ts.month / ts.day } +
+        std::chrono::duration_cast<std::chrono::system_clock::duration>(
+          std::chrono::hours{ ts.hour } +
+          std::chrono::minutes{ ts.minute } +
+          std::chrono::seconds{ ts.second } +
+          std::chrono::nanoseconds{ ts.fraction }
+        );
+      return true;
+    }
+    assert(false);
+    return false;
+  }
+
+  virtual bool getResult(
+    int column,
+    std::chrono::duration<int, std::milli> *value) override
+  {
+    long long msec;
+    bool res = getResult(column, &msec);
+    if (!res)
+      return res;
+
+    *value = std::chrono::duration<int, std::milli>(msec);
+    return true;
+  }
+
+  virtual bool getResult(
+    int column,
+    std::vector<unsigned char> *value,
+    int) override
+  {
+    DB2::Impl::ResultBuffer &resultBuffer = conn_.impl_->resultBuffer;
+    std::size_t resultBufferPos = 0;
+    SQLLEN strLen_or_ind = SQL_NO_TOTAL;
+    while (strLen_or_ind == SQL_NO_TOTAL) {
+      SQLRETURN rc = SQLGetData(
+        /*StatementHandle: */stmt_,
+        /*ColumnNumber: */column + 1,
+        /*TargetType: */SQL_C_BINARY,
+        /*TargetValue: */&resultBuffer.buf[resultBufferPos],
+        /*BufferLength: */resultBuffer.size - resultBufferPos,
+        /*StrLen_or_IndPtr: */&strLen_or_ind
+      );
+      handleErr(SQL_HANDLE_STMT, stmt_, rc);
+      if (strLen_or_ind == SQL_NULL_DATA) {
+        return false; // NULL
+      } else if (strLen_or_ind == SQL_NO_TOTAL || strLen_or_ind > resultBuffer.size - resultBufferPos) {
+        std::size_t pos = resultBuffer.size;
+        resultBuffer.size *= 2;
+        while (strLen_or_ind != SQL_NO_TOTAL &&
+               strLen_or_ind > resultBuffer.size - resultBufferPos)
+          resultBuffer.size *= 2;
+        resultBufferPos = pos;
+        resultBuffer.buf = (char*)realloc(resultBuffer.buf, resultBuffer.size);
+        strLen_or_ind = SQL_NO_TOTAL;
+      }
+    }
+    std::size_t totalDataSize = resultBufferPos + strLen_or_ind;
+    *value = std::vector<unsigned char>(resultBuffer.buf, resultBuffer.buf + totalDataSize);
+    return true;
+  }
+
+  virtual std::string sql() const override
+  {
+    return sql_;
+  }
+
+private:
+  struct Value {
+    union {
+      struct {
+        void *p; // buffer pointer 
+        SQLLEN size; // buffer size
+      } buf;
+      short s;
+      int i;
+      long long ll;
+      float f;
+      double d;
+      SQL_DATE_STRUCT date;
+      SQL_TIME_STRUCT time;
+      SQL_TIMESTAMP_STRUCT timestamp;
+    } v;
+    SQLLEN lengthOrInd; // length for binary data or string or NULL indicator
+    SQLSMALLINT type; // SQL_C... type
+
+    Value()
+    {
+      std::memset(&v, 0, sizeof(v));
+      lengthOrInd = 0;
+      type = 0;
+    }
+
+    ~Value()
+    {
+      if (type == SQL_C_BINARY ||
+          type == SQL_C_WCHAR)
+        free(v.buf.p);
+    }
+
+#ifdef WT_CXX11
+    Value(const Value &other) = delete;
+    Value &operator=(const Value &other) = delete;
+    Value(Value &&other) = delete;
+    Value &operator=(Value &&other) = delete;
+#endif // WT_CXX11
+
+    void clear()
+    {
+      if (type == SQL_C_BINARY ||
+          type == SQL_C_WCHAR)
+        free(v.buf.p);
+
+      std::memset(&v, 0, sizeof(v));
+      lengthOrInd = 0;
+      type = 0;
+    }
+
+  private:
+#ifndef WT_CXX11
+    Value(const Value &other);
+    Value& operator=(const Value &other);
+#endif // WT_CXX11
+  };
+  Value *paramValues_;
+  SQLSMALLINT parameterCount_;
+  SQLSMALLINT resultColCount_;
+
+  SQLHSTMT stmt_;
+  DB2 &conn_;
+  std::string sql_;
+  SQLLEN affectedRows_;
+  long long lastId_;
+
+  void checkColumnIndex(int column)
+  {
+    if (column >= parameterCount_)
+      throw DB2ServerException(
+        std::string("Trying to bind too many parameters (parameter count = ") +
+          std::to_string(parameterCount_) +
+          ", column = " +
+          std::to_string(column) +
+          std::string(")"));
+  }
+
+  // bool returns whether buffer changed
+  bool createOrResizeBuffer(Value &v, SQLLEN size)
+  {
+    if (v.type == SQL_C_WCHAR || v.type == SQL_C_BINARY) {
+      // We already have a buffer
+      if (v.v.buf.size >= size)
+        return false;
+      v.v.buf.size = size;
+      v.v.buf.p = realloc(v.v.buf.p, v.v.buf.size);
+      return true;
+    }
+    else {
+      // New buffer
+      v.clear();
+      v.v.buf.size = size == 0 ? 1 : size;
+      v.v.buf.p = malloc(v.v.buf.size);
+      return true;
+    }
+  }
+
+  template<SQLSMALLINT TargetType, typename ReturnType>
+  bool getRes(int column, ReturnType * value)
+  {
+    SQLLEN strLen_or_ind = 0;
+    SQLRETURN rc = SQLGetData(
+      /*StatementHandle: */stmt_,
+      /*ColumnNumber: */column + 1,
+      /*TargetType: */TargetType,
+      /*TargetValue: */value,
+      /*BufferLength: */0,
+      /*StrLen_or_IndPtr*/&strLen_or_ind
+    );
+    handleErr(SQL_HANDLE_STMT, stmt_, rc);
+    return rc == 0;
+  }
+};
+
+DB2::DB2()
+  : impl_(0)
+{ }
+
+DB2::DB2(const std::string &connectionString)
+  : impl_(0)
+{
+  connect(connectionString);
+}
+
+DB2::DB2(const DB2 &other)
+  : SqlConnection(other),
+    impl_(other.impl_ ? new Impl(*other.impl_) : 0)
+{
+  if (impl_)
+    impl_->connect();
+}
+
+DB2::~DB2()
+{
+  clearStatementCache();
+  delete impl_;
+}
+
+std::unique_ptr<SqlConnection> DB2::clone() const
+{
+  return std::unique_ptr<SqlConnection>(new DB2(*this));
+}
+
+bool DB2::connect(const std::string &connectionString)
+{
+  if (impl_)
+    throw DB2ServerException("Can't connect: already connected.");
+
+#ifdef WT_WIN32
+  ConnectionStringType connStr;
+  if (!connectionString.empty()) {
+    int wstrlen = MultiByteToWideChar(CP_UTF8, 0, &connectionString[0], connectionString.size(), 0, 0);
+    assert(wstrlen != 0);
+    connStr.resize(wstrlen);
+    wstrlen = MultiByteToWideChar(CP_UTF8, 0, &connectionString[0], connectionString.size(), &connStr[0], connStr.size());
+    assert(wstrlen != 0);
+  }
+#else // WT_WIN32
+  ConnectionStringType connStr = toUTF16(connectionString);
+#endif // WT_WIN32
+
+  Impl *impl = new Impl(connStr);
+  try {
+    impl->connect();
+  } catch (...) {
+    delete impl;
+    throw;
+  }
+
+  impl_ = impl;
+  return true;
+}
+
+void DB2::executeSql(const std::string &sql)
+{
+  if (showQueries()) {
+    LOG_INFO(sql);
+  }
+
+  SQLRETURN rc = SQL_SUCCESS;
+  if (!impl_->stmt) {
+    rc = SQLAllocHandle(SQL_HANDLE_STMT, impl_->dbc, &impl_->stmt);
+    handleErr(SQL_HANDLE_DBC, impl_->dbc, rc);
+  }
+#ifdef WT_WIN32
+  if (sql.empty()) {
+    SQLWCHAR wstr[] = L"";
+    rc = SQLExecDirectW(impl_->stmt, wstr, 0);
+  } else {
+    int wstrlen = MultiByteToWideChar(CP_UTF8, 0, &sql[0], sql.size(), 0, 0);
+    assert(wstrlen != 0);
+    SQLWCHAR *wstr = new SQLWCHAR[wstrlen + 1];
+    wstrlen = MultiByteToWideChar(CP_UTF8, 0, &sql[0], sql.size(), wstr, wstrlen);
+    assert(wstrlen != 0);
+    wstr[wstrlen] = 0;
+    rc = SQLExecDirectW(impl_->stmt, wstr, wstrlen);
+    delete[] wstr;
+  }
+#else // WT_WIN32
+  std::u16string wstr = toUTF16(sql);
+  rc = SQLExecDirectW(impl_->stmt, (SQLWCHAR*)&wstr[0], wstr.size());
+#endif // WT_WIN32
+  try {
+    handleErr(SQL_HANDLE_STMT, impl_->stmt, rc);
+  } catch (...) {
+    SQLFreeStmt(impl_->stmt, SQL_CLOSE);
+    throw;
+  }
+  SQLFreeStmt(impl_->stmt, SQL_CLOSE);
+}
+
+void DB2::startTransaction()
+{
+  if (showQueries()) {
+    LOG_INFO("begin transaction -- implicit");
+  }
+}
+
+void DB2::commitTransaction()
+{
+  if (showQueries()) {
+    LOG_INFO("commit transaction -- using SQLEndTran");
+  }
+
+  SQLRETURN rc = SQLEndTran(SQL_HANDLE_DBC, impl_->dbc, SQL_COMMIT);
+  handleErr(SQL_HANDLE_DBC, impl_->dbc, rc);
+}
+
+void DB2::rollbackTransaction()
+{
+  if (showQueries()) {
+    LOG_INFO("rollback transaction -- using SQLEndTran");
+  }
+
+  SQLRETURN rc = SQLEndTran(SQL_HANDLE_DBC, impl_->dbc, SQL_ROLLBACK);
+  handleErr(SQL_HANDLE_DBC, impl_->dbc, rc);
+}
+
+std::unique_ptr<SqlStatement> DB2::prepareStatement(const std::string &sql)
+{
+  return std::unique_ptr<SqlStatement>(new DB2ServerStatement(*this, sql));
+}
+
+std::string DB2::autoincrementSql() const
+{
+  return "not null GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1)";
+}
+
+std::vector<std::string> DB2::autoincrementCreateSequenceSql(const std::string &table, const std::string &id) const
+{
+  return std::vector<std::string>();
+}
+
+std::vector<std::string> DB2::autoincrementDropSequenceSql(const std::string &table, const std::string &id) const
+{
+  return std::vector<std::string>();
+}
+
+std::string DB2::autoincrementType() const
+{
+  return "bigint";
+}
+
+std::string DB2::autoincrementInsertSuffix(const std::string &id) const
+{
+  return "";
+}
+
+const char *DB2::dateTimeType(SqlDateTimeType type) const
+{
+  if (type == SqlDateTimeType::Date)
+    return "date";
+  if (type == SqlDateTimeType::Time)
+    return "bigint"; // SQL Server has no proper duration type, so store duration as number of milliseconds
+  if (type == SqlDateTimeType::DateTime)
+    return "timestamp";
+  return "";
+}
+
+bool DB2::requireSubqueryAlias() const
+{
+  return true;
+}
+
+const char *DB2::blobType() const
+{
+  return "varbinary(max)";
+}
+
+const char *DB2::booleanType() const
+{
+  return "smallint";
+}
+
+bool DB2::supportAlterTable() const
+{
+  return true;
+}
+
+std::string DB2::textType(int size) const
+{
+  if (size == -1)
+    return "varchar(32672)";
+  else
+    return std::string("varchar(") + std::to_string(size) + ")";
+}
+
+LimitQuery DB2::limitQueryMethod() const
+{
+  return LimitQuery::OffsetFetch;
+}
+
+    }
+  }
+}
diff -ruN wt-4.5.0-orig/src/Wt/Dbo/backend/DB2.h wt-4.5.0-patched/src/Wt/Dbo/backend/DB2.h
--- wt-4.5.0-orig/src/Wt/Dbo/backend/DB2.h	1970-01-01 01:00:00.000000000 +0100
+++ wt-4.5.0-patched/src/Wt/Dbo/backend/DB2.h	2021-04-20 16:48:27.289256974 +0200
@@ -0,0 +1,123 @@
+// This may look like C code, but it's really -*- C++ -*-
+/*
+ * Copyright (C) 2017 Emweb bv, Herent, Belgium.
+ *
+ * See the LICENSE file for terms of use.
+ */
+#ifndef WT_DBO_BACKEND_DB2SERVER_H_
+#define WT_DBO_BACKEND_DB2SERVER_H_
+
+#include <Wt/Dbo/SqlConnection.h>
+#include <Wt/Dbo/SqlStatement.h>
+#include "WDboDB2DllDefs.h"
+
+namespace Wt {
+  namespace Dbo {
+    namespace backend {
+
+/*! \class DB2SErver Wt/Dbo/backend/DB2Server Wt/Dbo/backend/DB2Server
+ *  \brief A DB2 SQL Server connection
+ *
+ * This class provides the backend implementation for DB2 Server databases.
+ *
+ * \ingroup dbo
+ */
+class WTDBODB2_API DB2 : public SqlConnection
+{
+public:
+  /*! \brief Creates a new DB2 SQL Server backend connection.
+   *
+   * The connection is not yet open, and requires a connect() before it
+   * can be used.
+   */
+  DB2();
+
+  /*! \brief Creates a new DB2 Server backend connection.
+   *
+   * For info about the connection string, see the connect() method.
+   *
+   * \sa connect()
+   */
+  DB2(const std::string &connectionString);
+
+  /*! \brief Copy constructor.
+   *
+   * This creates a new backend connection with the same settings
+   * as another connection.
+   *
+   * \sa clone()
+   */
+  DB2(const DB2& other);
+
+  /*! \brief Destructor.
+   *
+   * Closes the connection.
+   */
+  virtual ~DB2();
+
+  virtual std::unique_ptr<Wt::Dbo::SqlConnection> clone() const override;
+
+  /*! \brief Tries to connect.
+   *
+   * Throws an exception if there was a problem, otherwise returns true.
+   *
+   * The connection string is the connection string that should be passed
+   * to SQLDriverConnectW to connect to the DB2 Server database.
+   *
+   * The \p connectionString should be UTF-8 encoded.
+   *
+   * Example connection string:
+   *
+   * \code
+   * Driver={ODBC Driver DB2};
+   * Server=localhost; 
+   * UID=SA;
+   * PWD={example password};
+   * Database=example_db;
+   * \endcode
+   *
+   * You could also specify a DSN (Data Source Name) if you have it configured.
+   *
+   * See the
+   * <a href="https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqldriverconnect-function">SQLDriverConnect</a>
+   * function documentation on MSDN for more info.
+   */
+  bool connect(const std::string &connectionString);
+
+  virtual void executeSql(const std::string &sql) override;
+
+  virtual void startTransaction() override;
+  virtual void commitTransaction() override;
+  virtual void rollbackTransaction() override;
+  
+  virtual std::unique_ptr<SqlStatement> prepareStatement(const std::string &sql) override;
+
+  /** @name Methods that return dialect information
+   */
+  //!@{
+  virtual std::string autoincrementSql() const override;
+  virtual std::vector<std::string> autoincrementCreateSequenceSql(const std::string &table, const std::string &id) const override;
+  virtual std::vector<std::string> autoincrementDropSequenceSql(const std::string &table, const std::string &id) const override;
+  virtual std::string autoincrementType() const override;
+  virtual std::string autoincrementInsertSuffix(const std::string &id) const override;
+  virtual const char *dateTimeType(SqlDateTimeType type) const override;
+  virtual const char *blobType() const override;
+  virtual bool requireSubqueryAlias() const override;
+  virtual const char *booleanType() const override;
+  virtual bool supportAlterTable() const override;
+  virtual std::string textType(int size) const override;
+  virtual LimitQuery limitQueryMethod() const override;
+  //!@}
+
+private:
+  struct Impl;
+  Impl *impl_;
+
+  friend class DB2ServerStatement;
+};
+
+    }
+  }
+}
+
+#endif // WT_DBO_BACKEND_MSSQLSERVER_H_
diff -ruN wt-4.5.0-orig/src/Wt/Dbo/backend/WDboDB2DllDefs.h wt-4.5.0-patched/src/Wt/Dbo/backend/WDboDB2DllDefs.h
--- wt-4.5.0-orig/src/Wt/Dbo/backend/WDboDB2DllDefs.h	1970-01-01 01:00:00.000000000 +0100
+++ wt-4.5.0-patched/src/Wt/Dbo/backend/WDboDB2DllDefs.h	2021-04-20 16:48:27.293256939 +0200
@@ -0,0 +1,36 @@
+// This may look like C code, but it's really -*- C++ -*-
+/*
+* Copyright (C) 2017 Emweb bv, Herent, Belgium.
+*
+* See the LICENSE file for terms of use.
+*/
+#ifndef WDBODB2DLLDEFS_H_
+#define WDBODB2DLLDEFS_H_
+
+#include <Wt/WConfig.h>
+
+// Source: http://www.nedprod.com/programs/gccvisibility.html
+
+#ifdef WT_WIN32
+  #define WTDBODB2_IMPORT __declspec(dllimport)
+  #define WTDBODB2_EXPORT __declspec(dllexport)
+  #define WTDBODB2_DLLLOCAL
+  #define WTDBODB2_DLLPUBLIC
+#else
+  #define WTDBODB2_IMPORT __attribute__ ((visibility("default")))
+  #define WTDBODB2_EXPORT __attribute__ ((visibility("default")))
+  #define WTDBODB2_DLLLOCAL __attribute__ ((visibility("hidden")))
+  #define WTDBODB2_DLLPUBLIC __attribute__ ((visibility("default")))
+#endif
+
+#ifdef WTDBODB2_EXPORTS
+  #define WTDBODB2_API WTDBODB2_EXPORT
+#else
+  #ifdef WTDBODB2_STATIC
+    #define WTDBODB2_API
+  #else
+    #define WTDBODB2_API WTDBODB2_IMPORT
+  #endif
+#endif
+
+#endif // WDBOMSSQLSERVERDLLDEFS_H_
diff -ruN wt-4.5.0-orig/src/Wt/Dbo/backend/wtdbodb2-version.rc.in wt-4.5.0-patched/src/Wt/Dbo/backend/wtdbodb2-version.rc.in
--- wt-4.5.0-orig/src/Wt/Dbo/backend/wtdbodb2-version.rc.in	1970-01-01 01:00:00.000000000 +0100
+++ wt-4.5.0-patched/src/Wt/Dbo/backend/wtdbodb2-version.rc.in	2021-04-20 16:48:27.293256939 +0200
@@ -0,0 +1,53 @@
+/* Template from MSDN */
+#include <windows.h>
+
+
+#define VER_PRODUCTVERSION    @VERSION_SERIES@,@VERSION_MAJOR@,@VERSION_MINOR@,0
+#define VER_PRODUCTVERSION_STR "@VERSION_SERIES@.@VERSION_MAJOR@.@VERSION_MINOR@"
+#define VER_FILEVERSION       @VERSION_SERIES@,@VERSION_MAJOR@,@VERSION_MINOR@,0
+#define VER_FILEVERSION_STR   "@VERSION_SERIES@.@VERSION_MAJOR@.@VERSION_MINOR@"
+#define VER_COMPANYNAME_STR   "Emweb bv, Belgium"
+#define VER_FILEDESCRIPTION_STR "Wt C++ Dbo Microsoft SQL Server backend DLL"
+#define VER_INTERNALNAME_STR ""
+#define VER_LEGALCOPYRIGHT_STR "(c) 2020 Emweb bv, Belgium. See LICENSE file for details"
+#define VER_PRODUCTNAME_STR "Wt C++ Wt::DBO Microsoft SQL Server backend"
+#define VER_ORIGINALFILENAME_STR "wtdbomssqlserver.dll"
+
+#ifndef DEBUG
+#define VER_DEBUG                   0
+#else
+#define VER_DEBUG                   VS_FF_DEBUG
+#endif
+
+#define VER_PRIVATEBUILD 0
+#define VER_PRERELEASE 0
+
+VS_VERSION_INFO VERSIONINFO
+FILEVERSION    	VER_FILEVERSION
+PRODUCTVERSION 	VER_PRODUCTVERSION
+FILEFLAGSMASK  	VS_FFI_FILEFLAGSMASK
+FILEFLAGS      	(VER_PRIVATEBUILD|VER_PRERELEASE|VER_DEBUG)
+FILEOS         	VOS__WINDOWS32
+FILETYPE       	VFT_DLL
+FILESUBTYPE    	VFT2_UNKNOWN
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904E4"
+        BEGIN
+            VALUE "CompanyName",      VER_COMPANYNAME_STR
+            VALUE "FileDescription",  VER_FILEDESCRIPTION_STR
+            VALUE "FileVersion",      VER_FILEVERSION_STR
+            VALUE "InternalName",     VER_INTERNALNAME_STR
+            VALUE "LegalCopyright",   VER_LEGALCOPYRIGHT_STR
+            VALUE "OriginalFilename", VER_ORIGINALFILENAME_STR
+            VALUE "ProductName",      VER_PRODUCTNAME_STR
+            VALUE "ProductVersion",   VER_PRODUCTVERSION_STR
+        END
+    END
+
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
