#define BOOST_TEST_MODULE TestPQ #include #include #include #include #include #include #include #include #include #include #include #include #include #include class StandardMockDatabase : public DB::PluginMock { public: StandardMockDatabase() : DB::PluginMock("PQmock", {rootDir / "pqschema.sql"}, "user=postgres dbname=postgres") { } }; BOOST_GLOBAL_FIXTURE(StandardMockDatabase); BOOST_FIXTURE_TEST_SUITE(Core, DB::TestCore) BOOST_AUTO_TEST_CASE(transactions) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); BOOST_REQUIRE_EQUAL(false, ro->inTx()); ro->beginTx(); BOOST_REQUIRE_EQUAL(true, ro->inTx()); ro->rollbackTx(); BOOST_REQUIRE_EQUAL(false, ro->inTx()); ro->beginTx(); BOOST_REQUIRE_EQUAL(true, ro->inTx()); ro->commitTx(); BOOST_REQUIRE_EQUAL(false, ro->inTx()); } BOOST_AUTO_TEST_CASE(bindAndSend) { auto rw = DB::MockDatabase::openConnectionTo("PQmock"); auto mod = rw->modify("INSERT INTO test VALUES(?, ?, ?, ?, ?, ?)"); mod->bindParamI(0, testInt); mod->bindParamF(1, testDouble); mod->bindParamS(2, testString); mod->bindParamB(3, testBool); mod->bindParamT(4, testDateTime); mod->bindParamT(5, testInterval); mod->execute(); mod->bindParamI(0, static_cast(testInt + 10)); mod->bindParamF(1, static_cast(testDouble + 10)); mod->bindParamS(2, std::string(testString) + " something"); mod->bindParamB(3, true); mod->bindParamT(4, testDateTime); mod->bindParamT(5, testInterval); mod->execute(); mod->bindNull(0); mod->bindParamI(1, static_cast(testDouble + 10)); mod->bindParamI(2, static_cast(testInt)); mod->bindParamB(3, true); mod->bindNull(4); mod->bindNull(5); mod->execute(); mod->bindNull(0); mod->bindParamI(1, static_cast(testDouble + 10)); mod->bindParamI(2, testInt); mod->bindParamB(3, true); mod->bindParamS(4, "2016-01-01T12:34:56"); mod->bindNull(5); mod->execute(); mod = rw->modify("DELETE FROM test WHERE string = '?'"); BOOST_REQUIRE_THROW(mod->execute(false), DB::NoRowsAffected); // cppcheck-suppress assertWithSideEffect BOOST_REQUIRE_EQUAL(0, mod->execute(true)); } BOOST_AUTO_TEST_CASE(bindAndSelect) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto select = ro->select("SELECT * FROM test WHERE id = ?"); select->bindParamI(0, testInt); select->execute(); int rows = 0; while (select->fetch()) { assertColumnValueHelper(*select, 0, testInt); assertColumnValueHelper(*select, 1, testDouble); assertColumnValueHelper(*select, 2, testString); assertColumnValueHelper(*select, 3, testBool); assertColumnValueHelper(*select, 4, testDateTime); assertColumnValueHelper(*select, 5, testInterval); rows += 1; } BOOST_REQUIRE_EQUAL(1, rows); } BOOST_AUTO_TEST_CASE(selectInTx) { auto db = DB::MockDatabase::openConnectionTo("PQmock"); // Loop to ensure we can create the same statement several times for (int x = 0; x < 2; x++) { auto select = db->select("SELECT * FROM test"); // Loop to ensure we can use the same command several times for (int y = 0; y < 2; y++) { while (select->fetch()) { } } } db->finish(); db->beginTx(); auto select = db->select("SELECT * FROM test"); while (select->fetch()) { } db->commitTx(); db->finish(); } BOOST_AUTO_TEST_CASE(bindAndSelectOther) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto select = ro->select("SELECT * FROM test WHERE id != ? AND id != ?"); select->bindParamI(0, testInt); select->bindParamI(1, testInt + 10); select->execute(); int rows = 0; while (select->fetch()) { assertColumnValueHelper(*select, 0, 4); assertColumnValueHelper(*select, 1, 123.45); assertColumnValueHelper(*select, 2, std::string_view("some text with a ; in it and a ' too")); assertColumnValueHelper(*select, 3, true); assertColumnValueHelper( *select, 4, boost::posix_time::ptime_from_tm({3, 6, 23, 27, 3, 115, 0, 0, 0, 0, nullptr})); assertColumnValueHelper(*select, 5, boost::posix_time::time_duration(38, 13, 12)); rows += 1; } BOOST_REQUIRE_EQUAL(1, rows); } BOOST_AUTO_TEST_CASE(testP2MockScriptDir) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto select = ro->select("SELECT path FROM test2"); select->execute(); while (select->fetch()) { std::string path; (*select)[0] >> path; BOOST_REQUIRE(std::filesystem::exists(path)); } } BOOST_AUTO_TEST_CASE(bulkload) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto count = ro->select("SELECT COUNT(*) FROM bulktest"); // Test empty ro->beginBulkUpload("bulktest", ""); ro->endBulkUpload(nullptr); assertScalarValueHelper(*count, 0); // Test sample file ro->beginBulkUpload("bulktest", ""); std::ifstream in(rootDir / "bulk.sample"); if (!in.good()) { throw std::runtime_error("Couldn't open bulk.sample"); } std::array buf {}; for (std::streamsize r; (r = in.readsome(buf.data(), buf.size())) > 0;) { ro->bulkUploadData(buf.data(), static_cast(r)); } ro->endBulkUpload(nullptr); assertScalarValueHelper(*count, 800); } BOOST_AUTO_TEST_CASE(nofetch) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto count = ro->select("SELECT * FROM bulktest"); count->execute(); } BOOST_AUTO_TEST_CASE(bigIterate) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto count = ro->select("SELECT * FROM bulktest"); unsigned int rows = 0; while (count->fetch()) { rows += 1; } BOOST_REQUIRE_EQUAL(800, rows); } BOOST_AUTO_TEST_CASE(insertId) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto ins = ro->modify("INSERT INTO idtest(foo) VALUES(1)"); for (int x = 1; x < 4; x++) { ins->execute(); // cppcheck-suppress assertWithSideEffect BOOST_REQUIRE_EQUAL(x, ro->insertId()); } } BOOST_AUTO_TEST_CASE(reconnect) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto rok = DB::MockDatabase::openConnectionTo("PQmock"); auto pqconn = std::dynamic_pointer_cast(ro); int pid1 = PQbackendPID(pqconn->conn); BOOST_REQUIRE(pid1); ro->ping(); ro->modify("TRUNCATE TABLE test")->execute(); auto kil = rok->modify("SELECT pg_terminate_backend(?)"); kil->bindParamI(0, pid1); kil->execute(); usleep(5000); ro->ping(); int pid2 = PQbackendPID(pqconn->conn); BOOST_REQUIRE(pid2); BOOST_REQUIRE(pid1 != pid2); ro->modify("TRUNCATE TABLE test")->execute(); } BOOST_AUTO_TEST_CASE(reconnectInTx) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto rok = DB::MockDatabase::openConnectionTo("PQmock"); auto pqconn = std::dynamic_pointer_cast(ro); int pid1 = PQbackendPID(pqconn->conn); BOOST_REQUIRE(pid1); ro->ping(); ro->beginTx(); auto kil = rok->modify("SELECT pg_terminate_backend(?)"); kil->bindParamI(0, pid1); kil->execute(); usleep(5000); BOOST_REQUIRE_THROW(ro->ping(), DB::ConnectionError); } BOOST_AUTO_TEST_CASE(statementReuse) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto pqconn = std::dynamic_pointer_cast(ro); BOOST_REQUIRE_EQUAL(pqconn->preparedStatements.size(), 0); ro->modify("DELETE FROM test")->execute(); BOOST_REQUIRE_EQUAL(pqconn->preparedStatements.size(), 1); for (int y = 0; y < 4; y += 1) { auto m1 = ro->modify("INSERT INTO test(id) VALUES(?)"); BOOST_REQUIRE_EQUAL(pqconn->preparedStatements.size(), y == 0 ? 1 : 2); for (int x = 0; x < 4; x += 1) { m1->bindParamI(0, x); m1->execute(); } } BOOST_REQUIRE_EQUAL(pqconn->preparedStatements.size(), 2); auto select = ro->select("SELECT COUNT(id), SUM(id) FROM test"); while (select->fetch()) { assertColumnValueHelper(*select, 0, 16); assertColumnValueHelper(*select, 1, 24); } } BOOST_AUTO_TEST_CASE(bulkSelect) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto co = std::make_shared(0, 35, false); auto sel = ro->select("SELECT * FROM test WHERE id > ?", co); sel->bindParamI(0, 1); int64_t totalInt = 0, count = 0; sel->forEachRow([&totalInt, &count](auto i) { totalInt += i; count += 1; }); BOOST_REQUIRE_EQUAL(20, totalInt); BOOST_REQUIRE_EQUAL(8, count); } BOOST_AUTO_TEST_CASE(selectWithSmallPages) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto co = std::make_shared(0, 1, true); auto sel = ro->select("SELECT * FROM test WHERE id > ?", co); sel->bindParamI(0, 1); int64_t totalInt = 0, count = 0; sel->forEachRow([&totalInt, &count](auto i) { totalInt += i; count += 1; }); BOOST_REQUIRE_EQUAL(20, totalInt); BOOST_REQUIRE_EQUAL(8, count); } BOOST_AUTO_TEST_CASE(dateoid) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto co = std::make_shared(0, 1, false); auto sel = ro->select("SELECT '2017-01-08'::date", co); for (const auto & r : sel->as()) { BOOST_REQUIRE_EQUAL(boost::posix_time::ptime(boost::gregorian::date(2017, 1, 8)), r.value<0>()); } } BOOST_AUTO_TEST_CASE(insertReturning) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto co = std::make_shared(0, 35, false); auto sel = ro->select("INSERT INTO test(id, fl) VALUES(1, 3) RETURNING id + fl", co); int64_t totalInt = 0, count = 0; sel->forEachRow([&totalInt, &count](auto i) { totalInt += i; count += 1; }); BOOST_REQUIRE_EQUAL(4, totalInt); BOOST_REQUIRE_EQUAL(1, count); } BOOST_AUTO_TEST_CASE(closeOnError) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); BOOST_REQUIRE_THROW( { ro->select("SELECT * FROM test")->forEachRow<>([&ro]() { ro->execute("nonsense"); }); }, DB::Error); BOOST_REQUIRE_THROW( { ro->select("SELECT * FROM test")->forEachRow<>([&ro]() { ro->select("SELECT * FROM test")->forEachRow<>([&ro]() { ro->execute("nonsense"); }); }); }, DB::Error); ro->beginTx(); BOOST_REQUIRE_THROW( { ro->select("SELECT * FROM test")->forEachRow<>([&ro]() { ro->execute("nonsense"); }); }, DB::Error); BOOST_REQUIRE_THROW( { ro->select("SELECT * FROM test")->forEachRow<>([&ro]() { ro->select("SELECT * FROM test")->forEachRow<>([&ro]() { ro->execute("nonsense"); }); }); }, DB::Error); ro->commitTx(); } BOOST_AUTO_TEST_CASE(blobs) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); std::vector buf(29); memcpy(&buf[0], "This is some binary text data", 29); auto ins = ro->modify("INSERT INTO blobtest(data) VALUES(?)"); DB::Blob blob(buf); ins->bindParamBLOB(0, blob); ins->execute(); ro->execute("UPDATE blobtest SET md5 = md5(data)"); auto sel = ro->select("SELECT data, md5, length(data) FROM blobtest"); for (const auto & r : sel->as()) { // Assert the DB understood the insert BOOST_REQUIRE_EQUAL(r.value<2>(), buf.size()); BOOST_REQUIRE_EQUAL(r.value<1>(), "37c7c3737f93e8d17e845deff8fa74d2"); // Assert the fetch of the data is correct BOOST_REQUIRE_EQUAL(r.value<0>().len, buf.size()); std::string_view str(reinterpret_cast(r.value<0>().data), r.value<0>().len); BOOST_REQUIRE_EQUAL(str, "This is some binary text data"); BOOST_REQUIRE_EQUAL(r.value<0>(), blob); } } BOOST_AUTO_TEST_CASE(fetchAsBinary) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); std::vector buf(29); memcpy(&buf[0], "This is some binary text data", 29); DB::Blob blob(buf); auto opts = std::make_shared(0); opts->fetchBinary = true; opts->useCursor = false; auto sel = ro->select("SELECT data, md5, length(data) FROM blobtest", opts); for (const auto & r : sel->as, int64_t>()) { // Assert the DB understood the insert BOOST_REQUIRE_EQUAL(r.value<2>(), buf.size()); BOOST_REQUIRE(r.value<1>()); BOOST_REQUIRE_EQUAL(*r.value<1>(), "37c7c3737f93e8d17e845deff8fa74d2"); // Assert the fetch of the data is correct BOOST_REQUIRE_EQUAL(r.value<0>(), blob); } *opts->hash += 1; sel = ro->select( "SELECT CAST(length(data) AS BIGINT) big, CAST(length(data) AS SMALLINT) small FROM blobtest", opts); for (const auto & r : sel->as()) { BOOST_REQUIRE_EQUAL(r.value<0>(), buf.size()); BOOST_REQUIRE_EQUAL(r.value<1>(), buf.size()); } *opts->hash += 1; sel = ro->select("SELECT true a, false b", opts); for (const auto & r : sel->as()) { BOOST_REQUIRE_EQUAL(r.value<0>(), true); BOOST_REQUIRE_EQUAL(r.value<1>(), false); } *opts->hash += 1; sel = ro->select("SELECT xmlelement(name xml)", opts); for (const auto & r : sel->as()) { BOOST_REQUIRE_EQUAL(r.value<0>(), ""); } *opts->hash += 1; sel = ro->select("SELECT NULL, now()", opts); for (const auto & r : sel->as, boost::posix_time::ptime>()) { BOOST_REQUIRE(!r.value<0>()); BOOST_REQUIRE_THROW((void)r.value<1>(), DB::ColumnTypeNotSupported); } } BOOST_AUTO_TEST_CASE(largeBlob) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); ro->execute("TRUNCATE TABLE blobtest"); AdHoc::FileUtils::MemMap f("/proc/self/exe"); DB::Blob blob(f.data, static_cast(f.getStat().st_size)); BOOST_REQUIRE(blob.len > 140000); // Just assert the mapped file is actually "large" auto ins = ro->modify("INSERT INTO blobtest(data) VALUES(?)"); ins->bindParamBLOB(0, blob); ins->execute(); auto opts = std::make_shared(0); opts->fetchBinary = true; opts->useCursor = false; auto sel = ro->select("SELECT data, length(data) FROM blobtest", opts); for (const auto & r : sel->as()) { BOOST_REQUIRE_EQUAL(r.value<1>(), f.getStat().st_size); BOOST_REQUIRE_EQUAL(r.value<0>(), blob); } } BOOST_AUTO_TEST_CASE(bulkPerfTest) { auto ro = DB::MockDatabase::openConnectionTo("PQmock"); auto sel = ro->select(R"SQL(select s a, cast(s as numeric(7,1)) b, cast(s as text) c, make_interval(secs => s) d, make_timestamp(2019,1,1,1,1,1) + make_interval(mins=>s) e, s % 2 = 0 f from generate_series(1, 1000) s)SQL"); int64_t tot = 0; for (const auto & [a, b, c, d, e, f] : sel->as()) { tot += a + static_cast(b) + static_cast(c.length()) + d.hours() + e.time_of_day().hours() + f; } BOOST_REQUIRE_EQUAL(tot, 1013265); } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_CASE(connfail) { BOOST_REQUIRE_THROW( (void)DB::ConnectionFactory::createNew("postgresql", "host=localhost user=no"), DB::ConnectionError); try { (void)DB::ConnectionFactory::createNew("postgresql", "host=localhost user=no"); } catch (const DB::ConnectionError & e) { BOOST_REQUIRE(std::string(e.what()).find("\"no\"")); } } BOOST_AUTO_TEST_CASE(ssl) { auto conn = DB::ConnectionFactory::createNew( "postgresql", "host=randomdan.homeip.net user=gentoo dbname=postgres sslmode=require"); BOOST_REQUIRE(conn); auto pqconn = std::dynamic_pointer_cast(conn); BOOST_REQUIRE(pqconn); BOOST_REQUIRE(PQgetssl(pqconn->conn)); }