From 7569ccf82b7e1f4d0b8b092a0c4daadcbe9d43b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnter=20Obiltschnig?= Date: Sat, 19 Jun 2021 08:40:49 +0200 Subject: [PATCH] #3318: Data: Support Poco::UUID for data binding --- .../include/Poco/ActiveRecord/ActiveRecord.h | 1 - .../include/Poco/ActiveRecord/TypeHandler.h | 70 -------- Data/MySQL/include/Poco/Data/MySQL/Binder.h | 3 + .../MySQL/include/Poco/Data/MySQL/Extractor.h | 3 + Data/MySQL/src/Binder.cpp | 7 + Data/MySQL/src/Extractor.cpp | 12 ++ Data/MySQL/testsuite/src/MySQLTest.cpp | 19 +++ Data/MySQL/testsuite/src/MySQLTest.h | 5 +- Data/MySQL/testsuite/src/SQLExecutor.cpp | 23 +++ Data/MySQL/testsuite/src/SQLExecutor.h | 5 +- Data/ODBC/include/Poco/Data/ODBC/Binder.h | 5 + Data/ODBC/include/Poco/Data/ODBC/Extractor.h | 6 + Data/ODBC/include/Poco/Data/ODBC/Preparator.h | 15 ++ Data/ODBC/src/Binder.cpp | 149 ++++++++++++------ Data/ODBC/src/Extractor.cpp | 50 ++++++ Data/ODBC/src/ODBCMetaColumn.cpp | 6 +- Data/PostgreSQL/Makefile | 3 + .../include/Poco/Data/PostgreSQL/Binder.h | 3 + .../include/Poco/Data/PostgreSQL/Extractor.h | 3 + .../Poco/Data/PostgreSQL/PostgreSQLTypes.h | 1 + Data/PostgreSQL/src/Binder.cpp | 15 ++ Data/PostgreSQL/src/Extractor.cpp | 21 +++ Data/PostgreSQL/src/PostgreSQLTypes.cpp | 5 + Data/PostgreSQL/testsuite/Makefile | 3 + .../testsuite/src/PostgreSQLTest.cpp | 18 +++ .../PostgreSQL/testsuite/src/PostgreSQLTest.h | 2 + Data/PostgreSQL/testsuite/src/SQLExecutor.cpp | 25 ++- Data/PostgreSQL/testsuite/src/SQLExecutor.h | 5 +- Data/SQLite/include/Poco/Data/SQLite/Binder.h | 3 + .../include/Poco/Data/SQLite/Extractor.h | 10 ++ Data/SQLite/src/Binder.cpp | 7 + Data/SQLite/src/Extractor.cpp | 10 ++ Data/SQLite/src/Utility.cpp | 2 + Data/SQLite/testsuite/src/SQLiteTest.cpp | 17 ++ Data/SQLite/testsuite/src/SQLiteTest.h | 2 + Data/doc/00200-DataUserManual.page | 147 ++++++++--------- Data/include/Poco/Data/AbstractBinder.h | 13 ++ Data/include/Poco/Data/AbstractExtractor.h | 13 ++ Data/include/Poco/Data/AbstractPreparator.h | 13 ++ Data/include/Poco/Data/MetaColumn.h | 1 + Data/src/AbstractBinder.cpp | 18 +++ Data/src/AbstractExtractor.cpp | 18 +++ Data/src/AbstractPreparator.cpp | 18 +++ Data/src/RecordSet.cpp | 2 + Data/src/StatementImpl.cpp | 2 + Data/testsuite/src/Binder.cpp | 5 + Data/testsuite/src/Binder.h | 5 +- Data/testsuite/src/Extractor.cpp | 7 +- Data/testsuite/src/Extractor.h | 5 +- Data/testsuite/src/Preparator.cpp | 5 + Data/testsuite/src/Preparator.h | 3 + Foundation/include/Poco/Dynamic/Var.h | 10 ++ Foundation/include/Poco/Dynamic/VarHolder.h | 144 ++++++++++++++++- Foundation/src/VarHolder.cpp | 3 +- Foundation/testsuite/src/VarTest.cpp | 27 ++++ Foundation/testsuite/src/VarTest.h | 4 +- 56 files changed, 785 insertions(+), 212 deletions(-) delete mode 100644 ActiveRecord/include/Poco/ActiveRecord/TypeHandler.h diff --git a/ActiveRecord/include/Poco/ActiveRecord/ActiveRecord.h b/ActiveRecord/include/Poco/ActiveRecord/ActiveRecord.h index 29047717e..2415d86fa 100644 --- a/ActiveRecord/include/Poco/ActiveRecord/ActiveRecord.h +++ b/ActiveRecord/include/Poco/ActiveRecord/ActiveRecord.h @@ -19,7 +19,6 @@ #include "Poco/ActiveRecord/ActiveRecordLib.h" #include "Poco/ActiveRecord/Context.h" #include "Poco/ActiveRecord/IDTraits.h" -#include "Poco/ActiveRecord/TypeHandler.h" #include "Poco/DateTime.h" #include "Poco/RefCountedObject.h" #include "Poco/AutoPtr.h" diff --git a/ActiveRecord/include/Poco/ActiveRecord/TypeHandler.h b/ActiveRecord/include/Poco/ActiveRecord/TypeHandler.h deleted file mode 100644 index f65c0606e..000000000 --- a/ActiveRecord/include/Poco/ActiveRecord/TypeHandler.h +++ /dev/null @@ -1,70 +0,0 @@ -// -// TypeHandler.h -// -// Library: ActiveRecord -// Package: ActiveRecord -// Module: TypeHandler -// -// Copyright (c) 2020, Applied Informatics Software Engineering GmbH. -// and Contributors. -// -// SPDX-License-Identifier: BSL-1.0 -// - - -#ifndef ActiveRecord_TypeHandler_INCLUDED -#define ActiveRecord_TypeHandler_INCLUDED - - -#include "Poco/Data/TypeHandler.h" -#include "Poco/ThreadLocal.h" -#include "Poco/UUID.h" -#include - - -namespace Poco { -namespace Data { - - -template <> -class TypeHandler -{ -public: - using UUIDMap = std::map; - - static std::size_t size() - { - return 1; - } - - static void bind(std::size_t pos, const Poco::UUID& uuid, AbstractBinder::Ptr pBinder, AbstractBinder::Direction dir) - { - static Poco::ThreadLocal uuidMap; - std::string& uuidString = (*uuidMap)[pos]; - uuidString = uuid.toString(); - TypeHandler::bind(pos++, uuidString, pBinder, dir); - } - - static void extract(std::size_t pos, Poco::UUID& uuid, const Poco::UUID& deflt, AbstractExtractor::Ptr pExtr) - { - std::string defltString = deflt.toString(); - std::string uuidString; - TypeHandler::extract(pos++, uuidString, defltString, pExtr); - uuid.parse(uuidString); - } - - static void prepare(std::size_t pos, const Poco::UUID& uuid, AbstractPreparator::Ptr pPrep) - { - static Poco::ThreadLocal uuidMap; - std::string& uuidString = (*uuidMap)[pos]; - uuidString = uuid.toString(); - TypeHandler::prepare(pos++, uuidString, pPrep); - } -}; - - -} } // namespace Poco::Data - - -#endif // ActiveRecord_TypeHandler_INCLUDED - diff --git a/Data/MySQL/include/Poco/Data/MySQL/Binder.h b/Data/MySQL/include/Poco/Data/MySQL/Binder.h index 3f379c981..82fa617ab 100644 --- a/Data/MySQL/include/Poco/Data/MySQL/Binder.h +++ b/Data/MySQL/include/Poco/Data/MySQL/Binder.h @@ -104,6 +104,9 @@ public: virtual void bind(std::size_t pos, const Time& val, Direction dir); /// Binds a Time. + virtual void bind(std::size_t pos, const UUID& val, Direction dir); + /// Binds a UUID. + virtual void bind(std::size_t pos, const NullData& val, Direction dir); /// Binds a null. diff --git a/Data/MySQL/include/Poco/Data/MySQL/Extractor.h b/Data/MySQL/include/Poco/Data/MySQL/Extractor.h index 180a0fcb3..36226fc65 100644 --- a/Data/MySQL/include/Poco/Data/MySQL/Extractor.h +++ b/Data/MySQL/include/Poco/Data/MySQL/Extractor.h @@ -110,6 +110,9 @@ public: virtual bool extract(std::size_t pos, Time& val); /// Extracts a Time. Returns false if null was received. + virtual bool extract(std::size_t pos, UUID& val); + /// Extracts a UUID. Returns false if null was received. + virtual bool extract(std::size_t pos, Any& val); /// Extracts an Any. Returns false if null was received. diff --git a/Data/MySQL/src/Binder.cpp b/Data/MySQL/src/Binder.cpp index 5315238ff..c1fb4e7df 100644 --- a/Data/MySQL/src/Binder.cpp +++ b/Data/MySQL/src/Binder.cpp @@ -211,6 +211,13 @@ void Binder::bind(std::size_t pos, const Time& val, Direction dir) } +void Binder::bind(std::size_t pos, const UUID& val, Direction dir) +{ + std::string str = val.toString(); + bind(pos, str, dir); +} + + void Binder::bind(std::size_t pos, const NullData&, Direction dir) { poco_assert(dir == PD_IN); diff --git a/Data/MySQL/src/Extractor.cpp b/Data/MySQL/src/Extractor.cpp index d1c8043e8..0ff16d340 100644 --- a/Data/MySQL/src/Extractor.cpp +++ b/Data/MySQL/src/Extractor.cpp @@ -204,6 +204,18 @@ bool Extractor::extract(std::size_t pos, Time& val) } +bool Extractor::extract(std::size_t pos, UUID& val) +{ + std::string str; + if (extract(pos, str)) + { + val.parse(str); + return true; + } + else return false; +} + + bool Extractor::extract(std::size_t pos, Any& val) { return false; diff --git a/Data/MySQL/testsuite/src/MySQLTest.cpp b/Data/MySQL/testsuite/src/MySQLTest.cpp index 26b0fa67f..006474bf0 100644 --- a/Data/MySQL/testsuite/src/MySQLTest.cpp +++ b/Data/MySQL/testsuite/src/MySQLTest.cpp @@ -496,6 +496,15 @@ void MySQLTest::testDouble() } +void MySQLTest::testUUID() +{ + if (!_pSession) fail ("Test not available."); + + recreateUUIDsTable(); + _pExecutor->uuids(); +} + + void MySQLTest::testTuple() { if (!_pSession) fail ("Test not available."); @@ -779,6 +788,15 @@ void MySQLTest::recreateFloatsTable() } +void MySQLTest::recreateUUIDsTable() +{ + dropTable("Strings"); + try { *_pSession << "CREATE TABLE Strings (str CHAR(36))", now; } + catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail ("recreateUUIDsTable()"); } + catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail ("recreateUUIDsTable()"); } +} + + void MySQLTest::recreateTuplesTable() { dropTable("Tuples"); @@ -904,6 +922,7 @@ CppUnit::Test* MySQLTest::suite() CppUnit_addTest(pSuite, MySQLTest, testUnsignedInts); CppUnit_addTest(pSuite, MySQLTest, testFloat); CppUnit_addTest(pSuite, MySQLTest, testDouble); + CppUnit_addTest(pSuite, MySQLTest, testUUID); CppUnit_addTest(pSuite, MySQLTest, testTuple); CppUnit_addTest(pSuite, MySQLTest, testTupleVector); CppUnit_addTest(pSuite, MySQLTest, testInternalExtraction); diff --git a/Data/MySQL/testsuite/src/MySQLTest.h b/Data/MySQL/testsuite/src/MySQLTest.h index 0589c7ed4..150401c41 100644 --- a/Data/MySQL/testsuite/src/MySQLTest.h +++ b/Data/MySQL/testsuite/src/MySQLTest.h @@ -24,7 +24,7 @@ class MySQLTest: public CppUnit::TestCase /// MySQL test class /// Tested: - /// + /// /// Driver | DB | OS /// ----------------+---------------------------+------------------------------------------ /// 03.51.12.00 | MySQL 5.0.27-community-nt | MS Windows XP Professional x64 v.2003/SP1 @@ -84,6 +84,8 @@ public: void testFloat(); void testDouble(); + void testUUID(); + void testTuple(); void testTupleVector(); @@ -119,6 +121,7 @@ private: void recreateIntsTable(); void recreateUnsignedIntsTable(); void recreateFloatsTable(); + void recreateUUIDsTable(); void recreateTuplesTable(); void recreateVectorsTable(); void recreateNullableIntTable(); diff --git a/Data/MySQL/testsuite/src/SQLExecutor.cpp b/Data/MySQL/testsuite/src/SQLExecutor.cpp index da7ac2f4b..27beb6f44 100644 --- a/Data/MySQL/testsuite/src/SQLExecutor.cpp +++ b/Data/MySQL/testsuite/src/SQLExecutor.cpp @@ -545,6 +545,29 @@ void SQLExecutor::doubles() } +void SQLExecutor::uuids() +{ + std::string funct = "uuids()"; + Poco::UUID data("da8b9c4d-faa0-44e1-b834-ece1e7d31cd5"); + Poco::UUID ret; + + try { *_pSession << "INSERT INTO Strings VALUES (?)", use(data), now; } + catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail (funct); } + catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail (funct); } + + int count = 0; + try { *_pSession << "SELECT COUNT(*) FROM Strings", into(count), now; } + catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail (funct); } + catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail (funct); } + assertTrue (count == 1); + + try { *_pSession << "SELECT str FROM Strings", into(ret), now; } + catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail (funct); } + catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail (funct); } + assertTrue (ret == data); +} + + void SQLExecutor::insertSingleBulkVec() { std::string funct = "insertSingleBulkVec()"; diff --git a/Data/MySQL/testsuite/src/SQLExecutor.h b/Data/MySQL/testsuite/src/SQLExecutor.h index 00121d9da..03e4ffe24 100644 --- a/Data/MySQL/testsuite/src/SQLExecutor.h +++ b/Data/MySQL/testsuite/src/SQLExecutor.h @@ -26,7 +26,7 @@ public: PB_IMMEDIATE, PB_AT_EXEC }; - + enum DataExtraction { DE_MANUAL, @@ -37,7 +37,7 @@ public: ~SQLExecutor(); void bareboneMySQLTest(const char* host, const char* user, const char* pwd, const char* db, int port, const char* tableCreateString); - /// This function uses "bare bone" MySQL API calls (i.e. calls are not + /// This function uses "bare bone" MySQL API calls (i.e. calls are not /// "wrapped" in PocoData framework structures). /// The purpose of the function is to verify that driver behaves /// correctly. If this test passes, subsequent tests failures are likely ours. @@ -86,6 +86,7 @@ public: void unsignedInts(); void floats(); void doubles(); + void uuids(); void tuples(); void tupleVector(); diff --git a/Data/ODBC/include/Poco/Data/ODBC/Binder.h b/Data/ODBC/include/Poco/Data/ODBC/Binder.h index 74a016a6c..94293c91c 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/Binder.h +++ b/Data/ODBC/include/Poco/Data/ODBC/Binder.h @@ -322,6 +322,9 @@ public: void bind(std::size_t pos, const std::list& val, Direction dir); /// Binds a DateTime list. + void bind(std::size_t pos, const UUID& val, Direction dir); + /// Binds a UUID. + void bind(std::size_t pos, const NullData& val, Direction dir); /// Binds a null. In-bound only. @@ -367,6 +370,7 @@ private: typedef std::vector AnyVecVec; typedef std::map StringMap; typedef std::map UTF16StringMap; + typedef std::map UUIDMap; typedef std::map DateMap; typedef std::map TimeMap; typedef std::map TimestampMap; @@ -998,6 +1002,7 @@ private: TimestampMap _timestamps; StringMap _strings; UTF16StringMap _utf16Strings; + UUIDMap _uuids; DateVecVec _dateVecVec; TimeVecVec _timeVecVec; diff --git a/Data/ODBC/include/Poco/Data/ODBC/Extractor.h b/Data/ODBC/include/Poco/Data/ODBC/Extractor.h index 16ea981cd..0d8ed3d9d 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/Extractor.h +++ b/Data/ODBC/include/Poco/Data/ODBC/Extractor.h @@ -305,6 +305,9 @@ public: bool extract(std::size_t pos, std::list& val); /// Extracts a DateTime list. + bool extract(std::size_t pos, Poco::UUID& val); + /// Extracts a UUID. + bool extract(std::size_t pos, Poco::Any& val); /// Extracts an Any. @@ -567,6 +570,9 @@ private: case MetaColumn::FDT_TIMESTAMP: { return extAny(pos, val); } + case MetaColumn::FDT_UUID: + { return extAny(pos, val); } + default: throw DataFormatException("Unsupported data type."); } diff --git a/Data/ODBC/include/Poco/Data/ODBC/Preparator.h b/Data/ODBC/include/Poco/Data/ODBC/Preparator.h index eb235957c..ed1e77a6c 100644 --- a/Data/ODBC/include/Poco/Data/ODBC/Preparator.h +++ b/Data/ODBC/include/Poco/Data/ODBC/Preparator.h @@ -353,6 +353,9 @@ public: void prepare(std::size_t pos, const std::list& val); /// Prepares a DateTime list. + void prepare(std::size_t pos, const Poco::UUID& val); + /// Prepares a UUID. + void prepare(std::size_t pos, const Poco::Any& val); /// Prepares an Any. @@ -548,6 +551,12 @@ private: else return prepareFixedSize(pos, SQL_C_TYPE_TIMESTAMP); + case MetaColumn::FDT_UUID: + if (pVal) + return prepareFixedSize(pos, SQL_C_BINARY, 16); + else + return prepareFixedSize(pos, SQL_C_BINARY); + default: throw DataFormatException("Unsupported data type."); } @@ -1173,6 +1182,12 @@ inline void Preparator::prepare(std::size_t pos, const std::list } +inline void Preparator::prepare(std::size_t pos, const Poco::UUID&) +{ + prepareCharArray(pos, SQL_C_BINARY, 16, 16); +} + + inline void Preparator::prepare(std::size_t pos, const Poco::Any& val) { prepareImpl >(pos); diff --git a/Data/ODBC/src/Binder.cpp b/Data/ODBC/src/Binder.cpp index 45ca6c79b..28cf42fc2 100644 --- a/Data/ODBC/src/Binder.cpp +++ b/Data/ODBC/src/Binder.cpp @@ -80,6 +80,10 @@ void Binder::freeMemory() UTF16CharPtrVec::iterator endUTF16Chr = _utf16CharPtrs.end(); for (; itUTF16Chr != endUTF16Chr; ++itUTF16Chr) std::free(*itUTF16Chr); + UUIDMap::iterator itUUID = _uuids.begin(); + UUIDMap::iterator itUUIDEnd = _uuids.end(); + for(; itUUID != itUUIDEnd; ++itUUID) std::free(itUUID->first); + BoolPtrVec::iterator itBool = _boolPtrs.begin(); BoolPtrVec::iterator endBool = _boolPtrs.end(); for (; itBool != endBool; ++itBool) delete [] *itBool; @@ -129,15 +133,15 @@ void Binder::bind(std::size_t pos, const std::string& val, Direction dir) _lengthIndicator.push_back(pLenIn); - if (Utility::isError(SQLBindParameter(_rStmt, - (SQLUSMALLINT) pos + 1, - toODBCDirection(dir), - SQL_C_CHAR, - Connector::stringBoundToLongVarChar() ? SQL_LONGVARCHAR : SQL_VARCHAR, + if (Utility::isError(SQLBindParameter(_rStmt, + (SQLUSMALLINT) pos + 1, + toODBCDirection(dir), + SQL_C_CHAR, + Connector::stringBoundToLongVarChar() ? SQL_LONGVARCHAR : SQL_VARCHAR, (SQLUINTEGER) colSize, 0, - pVal, - (SQLINTEGER) size, + pVal, + (SQLINTEGER) size, _lengthIndicator.back()))) { throw StatementException(_rStmt, "SQLBindParameter(std::string)"); @@ -206,22 +210,22 @@ void Binder::bind(std::size_t pos, const Date& val, Direction dir) SQL_DATE_STRUCT* pDS = new SQL_DATE_STRUCT; Utility::dateSync(*pDS, val); - + _dates.insert(DateMap::value_type(pDS, const_cast(&val))); SQLINTEGER colSize = 0; SQLSMALLINT decDigits = 0; getColSizeAndPrecision(pos, SQL_TYPE_DATE, colSize, decDigits); - if (Utility::isError(SQLBindParameter(_rStmt, - (SQLUSMALLINT) pos + 1, - toODBCDirection(dir), - SQL_C_TYPE_DATE, - SQL_TYPE_DATE, + if (Utility::isError(SQLBindParameter(_rStmt, + (SQLUSMALLINT) pos + 1, + toODBCDirection(dir), + SQL_C_TYPE_DATE, + SQL_TYPE_DATE, colSize, decDigits, - (SQLPOINTER) pDS, - 0, + (SQLPOINTER) pDS, + 0, _lengthIndicator.back()))) { throw StatementException(_rStmt, "SQLBindParameter(Date)"); @@ -239,22 +243,22 @@ void Binder::bind(std::size_t pos, const Time& val, Direction dir) SQL_TIME_STRUCT* pTS = new SQL_TIME_STRUCT; Utility::timeSync(*pTS, val); - + _times.insert(TimeMap::value_type(pTS, const_cast(&val))); SQLINTEGER colSize = 0; SQLSMALLINT decDigits = 0; getColSizeAndPrecision(pos, SQL_TYPE_TIME, colSize, decDigits); - if (Utility::isError(SQLBindParameter(_rStmt, - (SQLUSMALLINT) pos + 1, - toODBCDirection(dir), - SQL_C_TYPE_TIME, - SQL_TYPE_TIME, + if (Utility::isError(SQLBindParameter(_rStmt, + (SQLUSMALLINT) pos + 1, + toODBCDirection(dir), + SQL_C_TYPE_TIME, + SQL_TYPE_TIME, colSize, decDigits, - (SQLPOINTER) pTS, - 0, + (SQLPOINTER) pTS, + 0, _lengthIndicator.back()))) { throw StatementException(_rStmt, "SQLBindParameter(Time)"); @@ -279,15 +283,15 @@ void Binder::bind(std::size_t pos, const Poco::DateTime& val, Direction dir) SQLSMALLINT decDigits = 0; getColSizeAndPrecision(pos, SQL_TYPE_TIMESTAMP, colSize, decDigits); - if (Utility::isError(SQLBindParameter(_rStmt, - (SQLUSMALLINT) pos + 1, - toODBCDirection(dir), - SQL_C_TYPE_TIMESTAMP, - SQL_TYPE_TIMESTAMP, + if (Utility::isError(SQLBindParameter(_rStmt, + (SQLUSMALLINT) pos + 1, + toODBCDirection(dir), + SQL_C_TYPE_TIMESTAMP, + SQL_TYPE_TIMESTAMP, colSize, decDigits, - (SQLPOINTER) pTS, - 0, + (SQLPOINTER) pTS, + 0, _lengthIndicator.back()))) { throw StatementException(_rStmt, "SQLBindParameter(DateTime)"); @@ -295,6 +299,38 @@ void Binder::bind(std::size_t pos, const Poco::DateTime& val, Direction dir) } +void Binder::bind(std::size_t pos, const UUID& val, Direction dir) +{ + SQLINTEGER size = (SQLINTEGER) 16; + SQLLEN* pLenIn = new SQLLEN; + *pLenIn = size; + + _lengthIndicator.push_back(pLenIn); + + char* pUUID = new char[16]; + val.copyTo(pUUID); + + _uuids.insert(UUIDMap::value_type(pUUID, const_cast(&val))); + + SQLINTEGER colSize = 0; + SQLSMALLINT decDigits = 0; + + if (Utility::isError(SQLBindParameter(_rStmt, + (SQLUSMALLINT) pos + 1, + toODBCDirection(dir), + SQL_C_BINARY, + SQL_GUID, + colSize, + decDigits, + (SQLPOINTER) pUUID, + 0, + _lengthIndicator.back()))) + { + throw StatementException(_rStmt, "SQLBindParameter(UUID)"); + } +} + + void Binder::bind(std::size_t pos, const NullData& val, Direction dir) { if (isOutBound(dir) || !isInBound(dir)) @@ -311,15 +347,15 @@ void Binder::bind(std::size_t pos, const NullData& val, Direction dir) SQLSMALLINT decDigits = 0; getColSizeAndPrecision(pos, SQL_C_STINYINT, colSize, decDigits); - if (Utility::isError(SQLBindParameter(_rStmt, - (SQLUSMALLINT) pos + 1, - SQL_PARAM_INPUT, - SQL_C_STINYINT, - Utility::sqlDataType(SQL_C_STINYINT), + if (Utility::isError(SQLBindParameter(_rStmt, + (SQLUSMALLINT) pos + 1, + SQL_PARAM_INPUT, + SQL_C_STINYINT, + Utility::sqlDataType(SQL_C_STINYINT), colSize, decDigits, - 0, - 0, + 0, + 0, _lengthIndicator.back()))) { throw StatementException(_rStmt, "SQLBindParameter()"); @@ -334,7 +370,7 @@ std::size_t Binder::parameterSize(SQLPOINTER pAddr) const it = _outParams.find(pAddr); if (it != _outParams.end()) return it->second; - + throw NotFoundException("Requested data size not found."); } @@ -350,7 +386,7 @@ SQLSMALLINT Binder::toODBCDirection(Direction dir) const bool in = isInBound(dir); bool out = isOutBound(dir); SQLSMALLINT ioType = SQL_PARAM_TYPE_UNKNOWN; - if (in && out) ioType = SQL_PARAM_INPUT_OUTPUT; + if (in && out) ioType = SQL_PARAM_INPUT_OUTPUT; else if(in) ioType = SQL_PARAM_INPUT; else if(out) ioType = SQL_PARAM_OUTPUT; else throw Poco::IllegalStateException("Binder not bound (must be [in] OR [out])."); @@ -365,7 +401,7 @@ void Binder::synchronize() { DateMap::iterator it = _dates.begin(); DateMap::iterator end = _dates.end(); - for(; it != end; ++it) + for(; it != end; ++it) Utility::dateSync(*it->second, *it->first); } @@ -373,7 +409,7 @@ void Binder::synchronize() { TimeMap::iterator it = _times.begin(); TimeMap::iterator end = _times.end(); - for(; it != end; ++it) + for(; it != end; ++it) Utility::timeSync(*it->second, *it->first); } @@ -381,7 +417,7 @@ void Binder::synchronize() { TimestampMap::iterator it = _timestamps.begin(); TimestampMap::iterator end = _timestamps.end(); - for(; it != end; ++it) + for(; it != end; ++it) Utility::dateTimeSync(*it->second, *it->first); } @@ -392,6 +428,14 @@ void Binder::synchronize() for(; it != end; ++it) it->second->assign(it->first, std::strlen(it->first)); } + + if (_uuids.size()) + { + UUIDMap::iterator it = _uuids.begin(); + UUIDMap::iterator end = _uuids.end(); + for(; it != end; ++it) + it->second->copyFrom(it->first); + } } @@ -405,6 +449,7 @@ void Binder::reset() _times.clear(); _timestamps.clear(); _strings.clear(); + _uuids.clear(); _dateVecVec.clear(); _timeVecVec.clear(); _dateTimeVecVec.clear(); @@ -415,9 +460,9 @@ void Binder::reset() } -void Binder::getColSizeAndPrecision(std::size_t pos, - SQLSMALLINT cDataType, - SQLINTEGER& colSize, +void Binder::getColSizeAndPrecision(std::size_t pos, + SQLSMALLINT cDataType, + SQLINTEGER& colSize, SQLSMALLINT& decDigits, std::size_t actualSize) { @@ -448,9 +493,9 @@ void Binder::getColSizeAndPrecision(std::size_t pos, colSize = (SQLINTEGER) p.columnSize(); decDigits = (SQLSMALLINT) p.decimalDigits(); return; - } + } catch (StatementException&) - { + { } try @@ -459,9 +504,9 @@ void Binder::getColSizeAndPrecision(std::size_t pos, colSize = (SQLINTEGER) c.length(); decDigits = (SQLSMALLINT) c.precision(); return; - } - catch (StatementException&) - { + } + catch (StatementException&) + { } // last check, just in case @@ -505,7 +550,7 @@ void Binder::getColumnOrParameterSize(std::size_t pos, SQLINTEGER& size) if (!Utility::isError(SQLGetStmtAttr(_rStmt, SQL_ATTR_IMP_PARAM_DESC, &hIPD, SQL_IS_POINTER, 0))) { SQLUINTEGER sz = 0; - if (!Utility::isError(SQLGetDescField(hIPD, (SQLSMALLINT) pos + 1, SQL_DESC_LENGTH, &sz, SQL_IS_UINTEGER, 0)) && + if (!Utility::isError(SQLGetDescField(hIPD, (SQLSMALLINT) pos + 1, SQL_DESC_LENGTH, &sz, SQL_IS_UINTEGER, 0)) && sz > 0) { size = sz; @@ -529,7 +574,7 @@ void Binder::setParamSetSize(std::size_t length) { if (0 == _paramSetSize) { - if (Utility::isError(Poco::Data::ODBC::SQLSetStmtAttr(_rStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, SQL_IS_UINTEGER)) || + if (Utility::isError(Poco::Data::ODBC::SQLSetStmtAttr(_rStmt, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, SQL_IS_UINTEGER)) || Utility::isError(Poco::Data::ODBC::SQLSetStmtAttr(_rStmt, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER) length, SQL_IS_UINTEGER))) throw StatementException(_rStmt, "SQLSetStmtAttr()"); diff --git a/Data/ODBC/src/Extractor.cpp b/Data/ODBC/src/Extractor.cpp index b5ad39950..4d60dc7c8 100644 --- a/Data/ODBC/src/Extractor.cpp +++ b/Data/ODBC/src/Extractor.cpp @@ -224,6 +224,20 @@ bool Extractor::extractBoundImplContainer >(std::size_ } +template<> +bool Extractor::extractBoundImpl(std::size_t pos, Poco::UUID& val) +{ + if (isNull(pos)) return false; + + std::size_t dataSize = _pPreparator->actualDataSize(pos); + checkDataSize(dataSize); + char* pBuffer = *AnyCast(&_pPreparator->at(pos)); + val.copyFrom(pBuffer); + + return true; +} + + template<> bool Extractor::extractBoundImplContainer >(std::size_t pos, std::vector& val) @@ -504,6 +518,33 @@ bool Extractor::extractManualImpl(std::size_t pos, } +template<> +bool Extractor::extractManualImpl(std::size_t pos, + Poco::UUID& val, + SQLSMALLINT cType) +{ + char buffer[16]; + resizeLengths(pos); + + SQLRETURN rc = SQLGetData(_rStmt, + (SQLUSMALLINT) pos + 1, + cType, //C data type + &buffer, //returned value + sizeof(buffer), //buffer length + &_lengths[pos]); //length indicator + + if (Utility::isError(rc)) + throw StatementException(_rStmt, "SQLGetData()"); + + if (isNullLengthIndicator(_lengths[pos])) + return false; + else + val.copyFrom(buffer); + + return true; +} + + bool Extractor::extract(std::size_t pos, Poco::Int32& val) { if (Preparator::DE_MANUAL == _dataExtraction) @@ -911,6 +952,15 @@ bool Extractor::extract(std::size_t pos, std::list& val) } +bool Extractor::extract(std::size_t pos, Poco::UUID& val) +{ + if (Preparator::DE_MANUAL == _dataExtraction) + return extractManualImpl(pos, val, SQL_C_BINARY); + else + return extractBoundImpl(pos, val); +} + + bool Extractor::extract(std::size_t pos, Poco::Int8& val) { if (Preparator::DE_MANUAL == _dataExtraction) diff --git a/Data/ODBC/src/ODBCMetaColumn.cpp b/Data/ODBC/src/ODBCMetaColumn.cpp index 913188512..f2603c0f7 100644 --- a/Data/ODBC/src/ODBCMetaColumn.cpp +++ b/Data/ODBC/src/ODBCMetaColumn.cpp @@ -102,9 +102,6 @@ void ODBCMetaColumn::init() case SQL_CHAR: case SQL_VARCHAR: case SQL_LONGVARCHAR: -#ifdef SQL_GUID - case SQL_GUID: -#endif setType(MetaColumn::FDT_STRING); break; case SQL_WCHAR: @@ -168,6 +165,9 @@ void ODBCMetaColumn::init() case SQL_TYPE_TIMESTAMP: setType(MetaColumn::FDT_TIMESTAMP); break; + case SQL_GUID: + setType(MetaColumn::FDT_UUID); break; + default: throw DataFormatException("Unsupported data type."); } diff --git a/Data/PostgreSQL/Makefile b/Data/PostgreSQL/Makefile index 518325476..3abed04aa 100644 --- a/Data/PostgreSQL/Makefile +++ b/Data/PostgreSQL/Makefile @@ -37,6 +37,9 @@ endif ifeq (0, $(shell test -e /opt/postgresql/lib$(LIB64SUFFIX); echo $$?)) SYSLIBS += -L/opt/postgresql/lib$(LIB64SUFFIX) endif +ifeq (0, $(shell test -e /usr/local/opt/libpq/lib; echo $$?)) +SYSLIBS += -L/usr/local/opt/libpq/lib$(LIB64SUFFIX) +endif SYSLIBS += -lpq objects = Extractor Binder SessionImpl Connector \ diff --git a/Data/PostgreSQL/include/Poco/Data/PostgreSQL/Binder.h b/Data/PostgreSQL/include/Poco/Data/PostgreSQL/Binder.h index 5f6ccb538..1374f48ab 100644 --- a/Data/PostgreSQL/include/Poco/Data/PostgreSQL/Binder.h +++ b/Data/PostgreSQL/include/Poco/Data/PostgreSQL/Binder.h @@ -108,6 +108,9 @@ public: virtual void bind(std::size_t pos, const Time& val, Direction dir = PD_IN); /// Binds a Time. + virtual void bind(std::size_t pos, const UUID& val, Direction dir = PD_IN); + /// Binds a UUID. + virtual void bind(std::size_t pos, const NullData& val, Direction dir = PD_IN); /// Binds a null. diff --git a/Data/PostgreSQL/include/Poco/Data/PostgreSQL/Extractor.h b/Data/PostgreSQL/include/Poco/Data/PostgreSQL/Extractor.h index c2c93836f..35d822840 100644 --- a/Data/PostgreSQL/include/Poco/Data/PostgreSQL/Extractor.h +++ b/Data/PostgreSQL/include/Poco/Data/PostgreSQL/Extractor.h @@ -109,6 +109,9 @@ public: virtual bool extract(std::size_t pos, Time& val); /// Extracts a Time. Returns false if null was received. + virtual bool extract(std::size_t pos, UUID& val); + /// Extracts a UUID. Returns false if null was received. + virtual bool extract(std::size_t pos, Any& val); /// Extracts an Any. Returns false if null was received. diff --git a/Data/PostgreSQL/include/Poco/Data/PostgreSQL/PostgreSQLTypes.h b/Data/PostgreSQL/include/Poco/Data/PostgreSQL/PostgreSQLTypes.h index 159653efd..b30a93771 100644 --- a/Data/PostgreSQL/include/Poco/Data/PostgreSQL/PostgreSQLTypes.h +++ b/Data/PostgreSQL/include/Poco/Data/PostgreSQL/PostgreSQLTypes.h @@ -271,6 +271,7 @@ inline const void* InputParameter::pInternalRepresentation() const case Poco::Data::MetaColumn::FDT_DATE: case Poco::Data::MetaColumn::FDT_TIME: case Poco::Data::MetaColumn::FDT_TIMESTAMP: + case Poco::Data::MetaColumn::FDT_UUID: return _stringVersionRepresentation.c_str(); case Poco::Data::MetaColumn::FDT_BLOB: diff --git a/Data/PostgreSQL/src/Binder.cpp b/Data/PostgreSQL/src/Binder.cpp index dc6913474..eec2c3883 100644 --- a/Data/PostgreSQL/src/Binder.cpp +++ b/Data/PostgreSQL/src/Binder.cpp @@ -176,6 +176,13 @@ void Binder::bind(std::size_t pos, const Time& val, Direction dir) } +void Binder::bind(std::size_t pos, const UUID& val, Direction dir) +{ + poco_assert(dir == PD_IN); + realBind(pos, Poco::Data::MetaColumn::FDT_UUID, &val, sizeof(UUID)); +} + + void Binder::bind(std::size_t pos, const NullData&, Direction dir) { poco_assert(dir == PD_IN); @@ -293,6 +300,14 @@ void Binder::updateBindVectorToCurrentValues() const Poco::Data::CLOB& clob = * static_cast(itr->pData()); itr->setNonStringVersionRepresentation(static_cast (clob.rawContent()), clob.size()); } + break; + + case Poco::Data::MetaColumn::FDT_UUID: + { + const Poco::UUID& uuid = * static_cast(itr->pData()); + itr->setStringVersionRepresentation(uuid.toString()); + } + break; case Poco::Data::MetaColumn::FDT_UNKNOWN: default: diff --git a/Data/PostgreSQL/src/Extractor.cpp b/Data/PostgreSQL/src/Extractor.cpp index 3d434df64..8259eb4a2 100644 --- a/Data/PostgreSQL/src/Extractor.cpp +++ b/Data/PostgreSQL/src/Extractor.cpp @@ -432,6 +432,19 @@ bool Extractor::extract(std::size_t pos, Time& val) } +bool Extractor::extract(std::size_t pos, UUID& val) +{ + OutputParameter outputParameter = extractPreamble(pos); + + if (isColumnNull(outputParameter)) + { + return false; + } + + return val.tryParse(outputParameter.pData()); +} + + bool Extractor::extract(std::size_t pos, Any& val) { return extractStringImpl(pos, val); @@ -583,6 +596,14 @@ bool Extractor::extractToDynamic(std::size_t pos, Dynamic::Var& val) val = dt; break; } + case UUIDOID: + { + UUID uuid; + success = extract(pos, uuid); + if (success) + val = uuid; + break; + } } return success; diff --git a/Data/PostgreSQL/src/PostgreSQLTypes.cpp b/Data/PostgreSQL/src/PostgreSQLTypes.cpp index 743e729cc..6254da21c 100644 --- a/Data/PostgreSQL/src/PostgreSQLTypes.cpp +++ b/Data/PostgreSQL/src/PostgreSQLTypes.cpp @@ -94,6 +94,11 @@ Poco::Data::MetaColumn::ColumnDataType oidToColumnDataType(const Oid anOID) cdt = Poco::Data::MetaColumn::FDT_TIMESTAMP; break; + //uuid + case UUIDOID: + cdt = Poco::Data::MetaColumn::FDT_BLOB; + break; + // everything else is a string default: cdt = Poco::Data::MetaColumn::FDT_STRING; diff --git a/Data/PostgreSQL/testsuite/Makefile b/Data/PostgreSQL/testsuite/Makefile index c5decc354..fd56a861a 100644 --- a/Data/PostgreSQL/testsuite/Makefile +++ b/Data/PostgreSQL/testsuite/Makefile @@ -37,6 +37,9 @@ endif ifeq (0, $(shell test -e /opt/postgresql/lib$(LIB64SUFFIX); echo $$?)) SYSLIBS += -L/opt/postgresql/lib$(LIB64SUFFIX) endif +ifeq (0, $(shell test -e /usr/local/opt/libpq/lib; echo $$?)) +SYSLIBS += -L/usr/local/opt/libpq/lib$(LIB64SUFFIX) +endif # Note: linking order is important, do not change it. SYSLIBS += -lpq -lz -lpthread -ldl diff --git a/Data/PostgreSQL/testsuite/src/PostgreSQLTest.cpp b/Data/PostgreSQL/testsuite/src/PostgreSQLTest.cpp index da4c5a46f..f57afc263 100644 --- a/Data/PostgreSQL/testsuite/src/PostgreSQLTest.cpp +++ b/Data/PostgreSQL/testsuite/src/PostgreSQLTest.cpp @@ -690,6 +690,14 @@ void PostgreSQLTest::testDouble() } +void PostgreSQLTest::testUUID() +{ + if (!_pSession) fail ("Test not available."); + + recreateUUIDsTable(); + _pExecutor->uuids(); +} + void PostgreSQLTest::testTuple() { if (!_pSession) fail ("Test not available."); @@ -986,6 +994,15 @@ void PostgreSQLTest::recreateFloatsTable() } +void PostgreSQLTest::recreateUUIDsTable() +{ + dropTable("Strings"); + try { *_pSession << "CREATE TABLE Strings (str UUID)", now; } + catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail ("recreateUUIDsTable()"); } + catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail ("recreateUUIDsTable()"); } +} + + void PostgreSQLTest::recreateTuplesTable() { dropTable("Tuples"); @@ -1110,6 +1127,7 @@ CppUnit::Test* PostgreSQLTest::suite() CppUnit_addTest(pSuite, PostgreSQLTest, testUnsignedInts); CppUnit_addTest(pSuite, PostgreSQLTest, testFloat); CppUnit_addTest(pSuite, PostgreSQLTest, testDouble); + CppUnit_addTest(pSuite, PostgreSQLTest, testUUID); CppUnit_addTest(pSuite, PostgreSQLTest, testTuple); CppUnit_addTest(pSuite, PostgreSQLTest, testTupleVector); CppUnit_addTest(pSuite, PostgreSQLTest, testInternalExtraction); diff --git a/Data/PostgreSQL/testsuite/src/PostgreSQLTest.h b/Data/PostgreSQL/testsuite/src/PostgreSQLTest.h index 6546ae4cf..3898328c2 100644 --- a/Data/PostgreSQL/testsuite/src/PostgreSQLTest.h +++ b/Data/PostgreSQL/testsuite/src/PostgreSQLTest.h @@ -82,6 +82,7 @@ public: void testUnsignedInts(); void testFloat(); void testDouble(); + void testUUID(); void testTuple(); void testTupleVector(); @@ -118,6 +119,7 @@ private: void recreateIntsTable(); void recreateUnsignedIntsTable(); void recreateFloatsTable(); + void recreateUUIDsTable(); void recreateTuplesTable(); void recreateVectorsTable(); void recreateNullableIntTable(); diff --git a/Data/PostgreSQL/testsuite/src/SQLExecutor.cpp b/Data/PostgreSQL/testsuite/src/SQLExecutor.cpp index c1444b38b..a3df32317 100644 --- a/Data/PostgreSQL/testsuite/src/SQLExecutor.cpp +++ b/Data/PostgreSQL/testsuite/src/SQLExecutor.cpp @@ -712,6 +712,29 @@ void SQLExecutor::floats() } +void SQLExecutor::uuids() +{ + std::string funct = "uuids()"; + Poco::UUID data("264a1c6f-7af5-43a5-a593-377aff3d2d7d"); + Poco::UUID ret; + + try { *_pSession << "INSERT INTO Strings VALUES ($1)", use(data), now; } + catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail (funct); } + catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail (funct); } + + int count = 0; + try { *_pSession << "SELECT COUNT(*) FROM Strings", into(count), now; } + catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail (funct); } + catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail (funct); } + assertTrue (count == 1); + + try { *_pSession << "SELECT str FROM Strings", into(ret), now; } + catch(ConnectionException& ce){ std::cout << ce.displayText() << std::endl; fail (funct); } + catch(StatementException& se){ std::cout << se.displayText() << std::endl; fail (funct); } + assertTrue (ret == data); +} + + void SQLExecutor::doubles() { std::string funct = "floats()"; @@ -1611,7 +1634,7 @@ void SQLExecutor::blobStmt() std::string lastName("lastname"); std::string firstName("firstname"); std::string address("Address"); -unsigned char BLOBData[ 10 ] = { 0,1,2,3,4,5,6,7,14,15 }; + unsigned char BLOBData[ 10 ] = { 0,1,2,3,4,5,6,7,14,15 }; Poco::Data::BLOB blob(BLOBData, 10); int count = 0; diff --git a/Data/PostgreSQL/testsuite/src/SQLExecutor.h b/Data/PostgreSQL/testsuite/src/SQLExecutor.h index 7199e92f8..7f6753f8e 100644 --- a/Data/PostgreSQL/testsuite/src/SQLExecutor.h +++ b/Data/PostgreSQL/testsuite/src/SQLExecutor.h @@ -25,7 +25,7 @@ public: PB_IMMEDIATE, PB_AT_EXEC }; - + enum DataExtraction { DE_MANUAL, @@ -37,7 +37,7 @@ public: void oidPostgreSQLTest(std::string host, std::string user, std::string pwd, std::string db, std::string port, const char* tableCreateString, const Oid anOIDArray[]); /// This function verifies the PostgreSQL Column type OIDs are consistent between releases - + void barebonePostgreSQLTest(std::string host, std::string user, std::string pwd, std::string db, std::string port, const char* tableCreateString); /// This function uses "bare bone" API calls (i.e. calls are not /// "wrapped" in PocoSQL framework structures). @@ -89,6 +89,7 @@ public: void unsignedInts(); void floats(); void doubles(); + void uuids(); void tuples(); void tupleVector(); diff --git a/Data/SQLite/include/Poco/Data/SQLite/Binder.h b/Data/SQLite/include/Poco/Data/SQLite/Binder.h index fa10f2144..3610d7215 100644 --- a/Data/SQLite/include/Poco/Data/SQLite/Binder.h +++ b/Data/SQLite/include/Poco/Data/SQLite/Binder.h @@ -106,6 +106,9 @@ public: void bind(std::size_t pos, const DateTime& val, Direction dir); /// Binds a DateTime. + void bind(std::size_t pos, const UUID& val, Direction dir); + /// Binds a UUID. + void bind(std::size_t pos, const NullData& val, Direction dir); /// Binds a null. diff --git a/Data/SQLite/include/Poco/Data/SQLite/Extractor.h b/Data/SQLite/include/Poco/Data/SQLite/Extractor.h index ba7d76d42..ac63be590 100644 --- a/Data/SQLite/include/Poco/Data/SQLite/Extractor.h +++ b/Data/SQLite/include/Poco/Data/SQLite/Extractor.h @@ -116,6 +116,9 @@ public: bool extract(std::size_t pos, Poco::DateTime& val); /// Extracts a DateTime. + bool extract(std::size_t pos, Poco::UUID& val); + /// Extracts a Time. + bool extract(std::size_t pos, Poco::Any& val); /// Extracts an Any. @@ -264,6 +267,13 @@ private: val = dt; break; } + case MetaColumn::FDT_UUID: + { + UUID uuid; + ret = extract(pos, uuid); + val = uuid; + break; + } default: throw Poco::Data::UnknownTypeException("Unknown type during extraction"); } diff --git a/Data/SQLite/src/Binder.cpp b/Data/SQLite/src/Binder.cpp index 4d745637b..3cd07e063 100644 --- a/Data/SQLite/src/Binder.cpp +++ b/Data/SQLite/src/Binder.cpp @@ -111,6 +111,13 @@ void Binder::bind(std::size_t pos, const DateTime& val, Direction dir) } +void Binder::bind(std::size_t pos, const UUID& val, Direction dir) +{ + std::string str(val.toString()); + bind(pos, str, dir); +} + + void Binder::bind(std::size_t pos, const NullData&, Direction) { sqlite3_bind_null(_pStmt, static_cast(pos)); diff --git a/Data/SQLite/src/Extractor.cpp b/Data/SQLite/src/Extractor.cpp index d5162a244..697b9bd21 100644 --- a/Data/SQLite/src/Extractor.cpp +++ b/Data/SQLite/src/Extractor.cpp @@ -208,6 +208,16 @@ bool Extractor::extract(std::size_t pos, DateTime& val) } +bool Extractor::extract(std::size_t pos, UUID& val) +{ + if (isNull(pos)) return false; + std::string str; + extract(pos, str); + val.parse(str); + return true; +} + + bool Extractor::extract(std::size_t pos, Poco::Any& val) { return extractImpl(pos, val); diff --git a/Data/SQLite/src/Utility.cpp b/Data/SQLite/src/Utility.cpp index e6445c951..60af9c3bb 100644 --- a/Data/SQLite/src/Utility.cpp +++ b/Data/SQLite/src/Utility.cpp @@ -134,6 +134,8 @@ Utility::Utility() _types.insert(TypeMap::value_type("TIME", MetaColumn::FDT_TIME)); _types.insert(TypeMap::value_type("DATETIME", MetaColumn::FDT_TIMESTAMP)); _types.insert(TypeMap::value_type("TIMESTAMP", MetaColumn::FDT_TIMESTAMP)); + _types.insert(TypeMap::value_type("UUID", MetaColumn::FDT_UUID)); + _types.insert(TypeMap::value_type("GUID", MetaColumn::FDT_UUID)); } } diff --git a/Data/SQLite/testsuite/src/SQLiteTest.cpp b/Data/SQLite/testsuite/src/SQLiteTest.cpp index 2fecbd183..deac5c2d4 100644 --- a/Data/SQLite/testsuite/src/SQLiteTest.cpp +++ b/Data/SQLite/testsuite/src/SQLiteTest.cpp @@ -30,6 +30,7 @@ #include "Poco/Data/SQLite/SQLiteException.h" #include "Poco/Tuple.h" #include "Poco/Any.h" +#include "Poco/UUIDGenerator.h" #include "Poco/SharedPtr.h" #include "Poco/DynamicAny.h" #include "Poco/DateTime.h" @@ -1916,6 +1917,21 @@ void SQLiteTest::testDateTime() } +void SQLiteTest::testUUID() +{ + Session tmp (Poco::Data::SQLite::Connector::KEY, "dummy.db"); + tmp << "DROP TABLE IF EXISTS Ids", now; + tmp << "CREATE TABLE Ids (id0 UUID)", now; + + Poco::UUID uuid = Poco::UUIDGenerator::defaultGenerator().createRandom(); + tmp << "INSERT INTO Ids VALUES (?)", use(uuid), now; + + Poco::UUID ruuid; + tmp << "SELECT * FROM Ids", into(ruuid), now; + assertTrue (ruuid == uuid); +} + + void SQLiteTest::testInternalExtraction() { Session tmp (Poco::Data::SQLite::Connector::KEY, "dummy.db"); @@ -3453,6 +3469,7 @@ CppUnit::Test* SQLiteTest::suite() CppUnit_addTest(pSuite, SQLiteTest, testTuple1); CppUnit_addTest(pSuite, SQLiteTest, testTupleVector1); CppUnit_addTest(pSuite, SQLiteTest, testDateTime); + CppUnit_addTest(pSuite, SQLiteTest, testUUID); CppUnit_addTest(pSuite, SQLiteTest, testInternalExtraction); CppUnit_addTest(pSuite, SQLiteTest, testPrimaryKeyConstraint); CppUnit_addTest(pSuite, SQLiteTest, testNullable); diff --git a/Data/SQLite/testsuite/src/SQLiteTest.h b/Data/SQLite/testsuite/src/SQLiteTest.h index 0122ca2e6..a19fd85fe 100644 --- a/Data/SQLite/testsuite/src/SQLiteTest.h +++ b/Data/SQLite/testsuite/src/SQLiteTest.h @@ -100,6 +100,8 @@ public: void testDateTime(); + void testUUID(); + void testInternalExtraction(); void testPrimaryKeyConstraint(); void testNullable(); diff --git a/Data/doc/00200-DataUserManual.page b/Data/doc/00200-DataUserManual.page index 3b28dbc8d..706b63fcd 100644 --- a/Data/doc/00200-DataUserManual.page +++ b/Data/doc/00200-DataUserManual.page @@ -16,54 +16,54 @@ The following complete example shows how to use POCO Data: #include "Poco/Data/SQLite/Connector.h" #include #include - + using namespace Poco::Data::Keywords; using Poco::Data::Session; using Poco::Data::Statement; - + struct Person { std::string name; std::string address; int age; }; - + int main(int argc, char** argv) { // register SQLite connector Poco::Data::SQLite::Connector::registerConnector(); - + // create a session Session session("SQLite", "sample.db"); - + // drop sample table, if it exists session << "DROP TABLE IF EXISTS Person", now; - + // (re)create table session << "CREATE TABLE Person (Name VARCHAR(30), Address VARCHAR, Age INTEGER(3))", now; - + // insert some rows - Person person = + Person person = { "Bart Simpson", "Springfield", 12 }; - + Statement insert(session); insert << "INSERT INTO Person VALUES(?, ?, ?)", use(person.name), use(person.address), use(person.age); - + insert.execute(); - + person.name = "Lisa Simpson"; person.address = "Springfield"; person.age = 10; - + insert.execute(); - + // a simple query Statement select(session); select << "SELECT Name, Address, Age FROM Person", @@ -71,24 +71,24 @@ The following complete example shows how to use POCO Data: into(person.address), into(person.age), range(0, 1); // iterate over result set one row at a time - + while (!select.done()) { select.execute(); std::cout << person.name << " " << person.address << " " << person.age << std::endl; } - + return 0; } ---- -The above example is pretty much self explanatory. +The above example is pretty much self explanatory. The <[using namespace Poco::Data ]> is for convenience only but highly recommended for good readable code. While <[ses << "SELECT COUNT(*) -FROM PERSON", Poco::Data::Keywords::into(count), Poco::Data::Keywords::now;]> +FROM PERSON", Poco::Data::Keywords::into(count), Poco::Data::Keywords::now;]> is valid, the aesthetic aspect of the code is improved by eliminating the need -for full namespace qualification; this document uses convention introduced in +for full namespace qualification; this document uses convention introduced in the example above. The remainder of this tutorial is split up into the following parts: @@ -117,12 +117,12 @@ parameter contains the connection string. In the case of SQLite, the path of the database file is sufficient as connection string. -For ODBC, the connection string may be a simple "DSN=MyDSNName" when a DSN is configured or -a complete ODBC driver-specific connection string defining all the necessary connection parameters -(for details, consult your ODBC driver documentation). +For ODBC, the connection string may be a simple "DSN=MyDSNName" when a DSN is configured or +a complete ODBC driver-specific connection string defining all the necessary connection parameters +(for details, consult your ODBC driver documentation). -For MySQL, the connection string is a semicolon-delimited list of name-value pairs -specifying various parameters like host, port, user, password, database, compression and +For MySQL, the connection string is a semicolon-delimited list of name-value pairs +specifying various parameters like host, port, user, password, database, compression and automatic reconnect. Example: <["host=localhost;port=3306;db=mydb;user=alice;password=s3cr3t;compress=true;auto-reconnect=true"]> @@ -130,7 +130,7 @@ automatic reconnect. Example: <["host=localhost;port=3306;db=mydb;user=alice;pas !!Single Data Sets -Inserting data works by <[using]> the content of other variables. +Inserting data works by <[using]> the content of other variables. Assume we have a table that stores only forenames: ForeName (Name VARCHAR(30)) @@ -159,8 +159,8 @@ Rewriting the above code now simply gives: ---- In this example the <[use]> expression matches the placeholder with the -<[Peter]> value. Note that apart from the nicer syntax, the real benefits of -placeholders -- which are performance and protection against SQL injection +<[Peter]> value. Note that apart from the nicer syntax, the real benefits of +placeholders -- which are performance and protection against SQL injection attacks -- don't show here. Check the <[Statements]> section to find out more. Retrieving data from the Database works similar. The <[into]> @@ -172,14 +172,14 @@ database: ses << "SELECT NAME FROM FORENAME", into(aName), now; ses << "SELECT NAME FROM FORENAME", into(aName, 0, "default"), now; -You'll note the integer zero argument in the second into() call. The reason for -that is that Poco::Data supports multiple result sets for those databases/drivers -that have such capbility and we have to indicate the resultset we are referring to. -Attempting to create sufficient overloads of <[into()]> creates more trouble than -what it's worth and null values can effectively be dealt with through use of either -Poco::Nullable wrapper (see Handling Null Entries later in this document) or -Poco::Dynamic::Var, which will be set as empty for null values when used as query -output target. +You'll note the integer zero argument in the second into() call. The reason for +that is that Poco::Data supports multiple result sets for those databases/drivers +that have such capbility and we have to indicate the resultset we are referring to. +Attempting to create sufficient overloads of <[into()]> creates more trouble than +what it's worth and null values can effectively be dealt with through use of either +Poco::Nullable wrapper (see Handling Null Entries later in this document) or +Poco::Dynamic::Var, which will be set as empty for null values when used as query +output target. ---- It is also possible to combine into and use expressions: @@ -224,18 +224,18 @@ To accomodate for NULL, use the Poco::Nullable template: if (!lastName.isNull()) { ... } ---- -The above used Poco::Nullable is a lightweight template class, wrapping any type +The above used Poco::Nullable is a lightweight template class, wrapping any type for the purpose of allowing it to have null value. If the returned value was null, age.isNull() will return true. Whether empty string is null or not is more of a philosophical question (a topic for discussion -in some other time and place); for the purpose of this document, suffice it to say -that different databases handle it differently and Poco::Data provides a way to +in some other time and place); for the purpose of this document, suffice it to say +that different databases handle it differently and Poco::Data provides a way to tweak it to user's needs through folowing <[Session]> features: *emptyStringIsNull *forceEmptyString - + So, if your database does not treat empty strings as null but you want Poco::Data to emulate such behavior, modify the session like this: @@ -259,7 +259,7 @@ set it belongs to: std::vector people; Person pHomer, pLisa; int aHomer(42), aLisa(10), aBart(0); - + session << "SELECT * FROM Person WHERE Age = ?; " "SELECT Age FROM Person WHERE FirstName = 'Bart'; " "SELECT * FROM Person WHERE Age = ?", @@ -293,7 +293,7 @@ More on statements and manipulators in the chapters that follow. Most of the modern database systems support stored procedures and/or functions. Does Poco::Data provide any support there? You bet. While the specifics on what exactly is possible (e.g. the data types -passed in and out, automatic or manual data binding, binding direction, +passed in and out, automatic or manual data binding, binding direction, etc.) is ultimately database dependent, POCO Data does it's best to provide reasonable access to such functionality through <[in]>, <[out]> and <[io]> binding functions. As their names imply, these @@ -306,7 +306,7 @@ here's an Oracle ODBC example: " temp NUMBER := param1; " " BEGIN param1 := param2; param2 := temp; RETURN(param1+param2); " " END storedFunction;" , now; - + int i = 1, j = 2, result = 0; session << "{? = call storedFunction(?, ?)}", out(result), io(i), io(j), now; // i = 2, j = 1, result = 3 ---- @@ -322,10 +322,10 @@ Stored procedures are allowed to return data sets (a.k.a. cursors): " ret SYS_REFCURSOR; " "BEGIN " " OPEN ret FOR " - " SELECT * FROM Person WHERE Age < ageLimit; " + " SELECT * FROM Person WHERE Age < ageLimit; " " RETURN ret; " "END storedCursorFunction;" , now; - + session << "{call storedCursorFunction(?)}", in(age), into(people), now; ---- @@ -387,8 +387,8 @@ Here's how we control when to actually execute the statement: ---- By calling <[execute]> we asserted that our query was executed and that -the value was inserted. The check to <[stmt.done()]> simply guarantees that the -statement was fully completed. +the value was inserted. The check to <[stmt.done()]> simply guarantees that the +statement was fully completed. @@ -479,7 +479,7 @@ return value is because, for asynchronous statements, <[execute()]> always returns zero. This makes sense, because it does not know the number of returned rows (remember, asynchronous <[execute()]> call returns <[immediately]> and does not wait for the completion of the -execution). +execution). !A Word of Warning @@ -513,7 +513,7 @@ later during execution. Thus, one should never pass temporaries to <[use()]>: ---- It is possible to use <[bind()]> instead of <[use()]>. The <[bind()]> call will always create a -copy of the supplied argument. Also, it is possible to execute a statement returning +copy of the supplied argument. Also, it is possible to execute a statement returning data without supplying the storage and have the statement itself store the returned data for later retrieval through <[RecordSet]>. For details, see <[RecordSet]> chapter. @@ -538,7 +538,7 @@ well-known source of many security problems in C and C++ code, do not worry. Poco::format() family of functions is <[safe]> (and, admittedly, slower than printf). -For cases where this type of formatting is used with queries containing +For cases where this type of formatting is used with queries containing the percent sign, use double percent ("%%"): Statement stmt = (ses << "SELECT * FROM %s WHERE Name LIKE 'Simp%%'", "Person"); @@ -696,13 +696,13 @@ object until <[statement.done()]> returns true. For the next example, we assume that our system knows about 101 forenames: std::vector names; - Statement stmt = (ses << "SELECT NAME FROM FORENAME", into(names), limit(50)); + Statement stmt = (ses << "SELECT NAME FROM FORENAME", into(names), limit(50)); stmt.execute(); //names.size() == 50 poco_assert (!stmt.done()); stmt.execute(); //names.size() == 100 poco_assert (!stmt.done()); stmt.execute(); //names.size() == 101 - poco_assert (stmt.done()); + poco_assert (stmt.done()); ---- We previously stated that if no data is returned this is valid too. Thus, executing the following statement on an @@ -750,14 +750,14 @@ off. The <[bulk]> keyword allows to boost performance for the connectors that support column-wise operation and arrays of values and/or parameters -(e.g. ODBC). +(e.g. ODBC). Here's how to signal bulk insertion to the statement: std::vector ints(100, 1); session << "INSERT INTO Test VALUES (?)", use(ints, bulk), now; ---- -The above code will execute a "one-shot" insertion into the target table. +The above code will execute a "one-shot" insertion into the target table. Selection in bulk mode looks like this: @@ -827,7 +827,7 @@ feature. !!! RecordSets, Iterators and Rows In all the examples so far the programmer had to supply the storage for -data to be inserted or retrieved from a database. +data to be inserted or retrieved from a database. It is usually desirable to avoid that and let the framework take care of it, something like this: @@ -840,16 +840,16 @@ No worries -- that's what the RecordSet class does: Statement select(session); // we need a Statement for later RecordSet creation select << "SELECT * FROM Person", now; - // create a RecordSet + // create a RecordSet RecordSet rs(select); std::size_t cols = rs.columnCount(); // print all column names for (std::size_t col = 0; col < cols; ++col) std::cout << rs.columnName(col) << std::endl; - + // iterate over all rows and columns - for (RecordSet::Iterator it = rs.begin(); it != rs.end(); ++it) + for (RecordSet::Iterator it = rs.begin(); it != rs.end(); ++it) std::cout << *it << " "; ---- @@ -889,7 +889,7 @@ used for sorting purposes. However, the sort criteria can be modified at runtime. For example, an additional field may be added to sort fields (think "... ORDER BY Name ASC, Age DESC"): - row.addSortField("Field1"); // now Field0 and Field1 are used for sorting + row.addSortField("Field1"); // now Field0 and Field1 are used for sorting row.replaceSortField("Field0", "Field2");// now Field1 and Field2 are used for sorting ---- @@ -914,7 +914,7 @@ Valid storage type manipulators are: So, if neither data storage, nor storage type are explicitly specified, the data will internally be kept in standard deques. This can be changed -through use of storage type manipulators. +through use of storage type manipulators. !!!Complex Data Types @@ -930,19 +930,19 @@ Assume you have a class Person: // default constructor+destr. // getter and setter methods for all members // ... - + bool operator <(const Person& p) const /// we need this for set and multiset support { return _socialSecNr < p._socialSecNr; } - + Poco::UInt64 operator()() const /// we need this operator to return the key for the map and multimap { return _socialSecNr; } - + private: std::string _firstName; std::string _lastName; @@ -953,12 +953,12 @@ Assume you have a class Person: Ideally, one would like to use a Person as simple as one used a string. All that is needed is a template specialization of the <[TypeHandler]> template. Note that template specializations must be declared in the -<*same namespace*> as the original template, i.e. <[Poco::Data]>. +<*same namespace*> as the original template, i.e. <[Poco::Data]>. The template specialization must implement the following methods: namespace Poco { namespace Data { - + template <> class TypeHandler { @@ -972,12 +972,12 @@ The template specialization must implement the following methods: TypeHandler::bind(pos++, obj.getLastName(), pBinder, dir); TypeHandler::bind(pos++, obj.getSocialSecNr(), pBinder, dir); } - + static std::size_t size() { return 3; // we handle three columns of the Table! } - + static void prepare(std::size_t pos, const Person& obj, AbstractPreparator::Ptr pPrepare) { poco_assert_dbg (!pPrepare.isNull()); @@ -987,7 +987,7 @@ The template specialization must implement the following methods: TypeHandler::prepare(pos++, obj.getLastName(), pPrepare); TypeHandler::prepare(pos++, obj.getSocialSecNr(), pPrepare); } - + static void extract(std::size_t pos, Person& obj, const Person& defVal, AbstractExtractor::Ptr pExt) /// obj will contain the result, defVal contains values we should use when one column is NULL { @@ -1002,14 +1002,14 @@ The template specialization must implement the following methods: obj.setLastName(lastName); obj.setSocialSecNr(socialSecNr); } - + private: TypeHandler(); ~TypeHandler(); TypeHandler(const TypeHandler&); TypeHandler& operator=(const TypeHandler&); }; - + } } // namespace Poco::Data ---- @@ -1022,8 +1022,9 @@ working with a string: !!!Session Pooling + Creating a connection to a database is often a time consuming -operation. Therefore it makes sense to save a session object for +operation. Therefore it makes sense to save a session object for later reuse once it is no longer needed. A Poco::Data::SessionPool manages a collection of sessions. @@ -1046,7 +1047,7 @@ Pooled sessions are automatically returned to the pool when the Session variable holding them is destroyed. One session pool, of course, holds sessions for one database -connection. For sessions to multiple databases, there is +connection. For sessions to multiple databases, there is SessionPoolContainer: SessionPoolContainer spc; @@ -1060,15 +1061,15 @@ SessionPoolContainer: This document provides an overview of the most important features offered by the POCO Data framework. The framework also supports LOB -(specialized to BLOB and CLOB) type as well as Poco::DateTime binding. -The usage of these data types is no different than any C++ type, so we +(specialized to BLOB and CLOB) type as well as Poco::DateTime binding. +The usage of these data types is no different than any C++ type, so we did not go into details here. The great deal of <[RecordSet]> and <[Row]> runtime "magic" is achieved through employment of Poco::Dynamic::Var, which is the POCO equivalent of dynamic language data type. Obviously, due to its nature, there is a run time performance penalty associated with Poco::Dynamic::Var, -but the internal details are beyond the scope of this document. +but the internal details are beyond the scope of this document. POCO Data tries to provide a broad spectrum of functionality, with configurable efficiency/convenience ratio, providing a solid diff --git a/Data/include/Poco/Data/AbstractBinder.h b/Data/include/Poco/Data/AbstractBinder.h index 68fe6c14c..f0ea63346 100644 --- a/Data/include/Poco/Data/AbstractBinder.h +++ b/Data/include/Poco/Data/AbstractBinder.h @@ -24,6 +24,7 @@ #include "Poco/Data/LOB.h" #include "Poco/DateTime.h" #include "Poco/Nullable.h" +#include "Poco/UUID.h" #include "Poco/Any.h" #include "Poco/Dynamic/Var.h" #include "Poco/UTFString.h" @@ -317,6 +318,18 @@ public: virtual void bind(std::size_t pos, const std::list