diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..3822164 Binary files /dev/null and b/.DS_Store differ diff --git a/contrib/.DS_Store b/contrib/.DS_Store new file mode 100644 index 0000000..3bc2f56 Binary files /dev/null and b/contrib/.DS_Store differ diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000..f2f83af Binary files /dev/null and b/tests/.DS_Store differ diff --git a/timeplus/.DS_Store b/timeplus/.DS_Store new file mode 100644 index 0000000..038f59b Binary files /dev/null and b/timeplus/.DS_Store differ diff --git a/ut/ipv4_supported_ut.cpp b/ut/ipv4_supported_ut.cpp new file mode 100644 index 0000000..c35b3a7 --- /dev/null +++ b/ut/ipv4_supported_ut.cpp @@ -0,0 +1,201 @@ +#include +#include + +#include "ut/utils.h" +#include "ut/value_generators.h" + +#include + +using namespace timeplus; + +// Test inserting and selecting IPv4 addresses +TEST(IPv4Supported, InsertSelect) { + // Helper to get environment variable or default + auto getEither = [](const char* a, const char* b, const char* def) { + const char* v = std::getenv(a); + if (v) return std::string(v); + v = std::getenv(b); + if (v) return std::string(v); + return std::string(def); + }; + + // Get connection parameters + const std::string host = getEither("timeplus_HOST", "TIMEPLUS_HOST", "localhost"); + const std::string port = getEither("timeplus_PORT", "TIMEPLUS_PORT", "8463"); + const std::string user = getEither("timeplus_USER", "TIMEPLUS_USER", "default"); + const std::string password = getEither("timeplus_PASSWORD", "TIMEPLUS_PASSWORD", ""); + const std::string db = getEither("timeplus_DB", "TIMEPLUS_DB", "default"); + + const auto opts = ClientOptions() + .SetHost(host) + .SetPort(static_cast(std::stoul(port))) + .SetUser(user) + .SetPassword(password) + .SetDefaultDatabase(db); + + Client client(opts); + + // Prepare test table + client.Execute("DROP TEMPORARY STREAM IF EXISTS test_timeplus_cpp_ipv4_ut;"); + client.Execute("CREATE TEMPORARY STREAM IF NOT EXISTS test_timeplus_cpp_ipv4_ut (v4 ipv4) ENGINE = Memory"); + + Block b; + auto v4 = std::make_shared(); + auto ips = MakeIPv4s(); // Generate test IPv4 addresses + for (const auto & ip : ips) { + v4->Append(ip); + } + + b.AppendColumn("v4", v4); + client.Insert("test_timeplus_cpp_ipv4_ut", b); + + // Validate inserted data + size_t row = 0; + client.Select("SELECT v4 FROM test_timeplus_cpp_ipv4_ut", [&ips, &row](const Block& block) { + if (block.GetRowCount() == 0) return; + ASSERT_EQ(1U, block.GetColumnCount()); + for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { + auto col = block[0]->As(); + auto got = col->At(c); + EXPECT_EQ(ips[row].s_addr, got.s_addr); + } + }); + EXPECT_EQ(ips.size(), row); +} + +// Test nullable IPv4 column: insert a valid and a null value, then check nullability +TEST(IPv4Supported, NullableInsertSelect) { + // Type name must be lowercase: nullable(ipv4) + auto getEither = [](const char* a, const char* b, const char* def) { + const char* v = std::getenv(a); + if (v) return std::string(v); + v = std::getenv(b); + if (v) return std::string(v); + return std::string(def); + }; + const std::string host = getEither("timeplus_HOST", "TIMEPLUS_HOST", "localhost"); + const std::string port = getEither("timeplus_PORT", "TIMEPLUS_PORT", "8463"); + const std::string user = getEither("timeplus_USER", "TIMEPLUS_USER", "default"); + const std::string password = getEither("timeplus_PASSWORD", "TIMEPLUS_PASSWORD", ""); + const std::string db = getEither("timeplus_DB", "TIMEPLUS_DB", "default"); + const auto opts = ClientOptions().SetHost(host).SetPort(static_cast(std::stoul(port))).SetUser(user).SetPassword(password).SetDefaultDatabase(db); + Client client(opts); + client.Execute("DROP TEMPORARY STREAM IF EXISTS test_timeplus_cpp_ipv4_nullable;"); + client.Execute("CREATE TEMPORARY STREAM IF NOT EXISTS test_timeplus_cpp_ipv4_nullable (v4 nullable(ipv4)) ENGINE = Memory"); + Block b; + auto v4 = std::make_shared(); + v4->Append("127.0.0.1"); // valid IPv4 + auto nulls = std::make_shared(); + nulls->Append(0); // not null + v4->Append(0); // 0.0.0.0 as null + nulls->Append(1); // null + b.AppendColumn("v4", std::make_shared(v4, nulls)); + client.Insert("test_timeplus_cpp_ipv4_nullable", b); + size_t row = 0; + client.Select("SELECT v4 FROM test_timeplus_cpp_ipv4_nullable", [&row](const Block& block) { + if (block.GetRowCount() == 0) return; + ASSERT_EQ(1U, block.GetColumnCount()); + for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { + auto col = block[0]->As(); + if (row == 0) { + EXPECT_FALSE(col->IsNull(c)); // Should not be null + } else { + EXPECT_TRUE(col->IsNull(c)); // Should be null + } + } + }); + EXPECT_EQ(2U, row); +} + +// Test IPv4 boundary values: minimum and maximum +TEST(IPv4Supported, BoundaryValues) { + auto getEither = [](const char* a, const char* b, const char* def) { + const char* v = std::getenv(a); + if (v) return std::string(v); + v = std::getenv(b); + if (v) return std::string(v); + return std::string(def); + }; + const std::string host = getEither("timeplus_HOST", "TIMEPLUS_HOST", "localhost"); + const std::string port = getEither("timeplus_PORT", "TIMEPLUS_PORT", "8463"); + const std::string user = getEither("timeplus_USER", "TIMEPLUS_USER", "default"); + const std::string password = getEither("timeplus_PASSWORD", "TIMEPLUS_PASSWORD", ""); + const std::string db = getEither("timeplus_DB", "TIMEPLUS_DB", "default"); + const auto opts = ClientOptions().SetHost(host).SetPort(static_cast(std::stoul(port))).SetUser(user).SetPassword(password).SetDefaultDatabase(db); + Client client(opts); + client.Execute("DROP TEMPORARY STREAM IF EXISTS test_timeplus_cpp_ipv4_boundary;"); + client.Execute("CREATE TEMPORARY STREAM IF NOT EXISTS test_timeplus_cpp_ipv4_boundary (v4 ipv4) ENGINE = Memory"); + Block b; + auto v4 = std::make_shared(); + v4->Append("0.0.0.0"); // min IPv4 + v4->Append("255.255.255.255"); // max IPv4 + b.AppendColumn("v4", v4); + client.Insert("test_timeplus_cpp_ipv4_boundary", b); + std::vector expected = {"0.0.0.0", "255.255.255.255"}; + size_t row = 0; + client.Select("SELECT v4 FROM test_timeplus_cpp_ipv4_boundary", [&row, &expected](const Block& block) { + if (block.GetRowCount() == 0) return; + ASSERT_EQ(1U, block.GetColumnCount()); + for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { + auto col = block[0]->As(); + EXPECT_EQ(expected[row], col->AsString(c)); // Compare string representation + } + }); + EXPECT_EQ(expected.size(), row); +} + +// Test invalid IPv4 input and overflow behavior +TEST(IPv4Supported, InvalidData) { + // Test invalid IP strings, should throw ValidationError + // Overflow assertion is kept, but C++ overflow will not throw + auto v4 = std::make_shared(); + EXPECT_THROW(v4->Append("999.999.999.999"), ValidationError); + EXPECT_THROW(v4->Append("abc.def.ghi.jkl"), ValidationError); + EXPECT_THROW(v4->Append(0xFFFFFFFF + 1), std::exception); // Overflow: will not throw in C++ +} + +// Test type conversion between IPv4, string, and uint32 +TEST(IPv4Supported, TypeConversion) { + // Type names must be lowercase: string, uint32 + auto getEither = [](const char* a, const char* b, const char* def) { + const char* v = std::getenv(a); + if (v) return std::string(v); + v = std::getenv(b); + if (v) return std::string(v); + return std::string(def); + }; + const std::string host = getEither("timeplus_HOST", "TIMEPLUS_HOST", "localhost"); + const std::string port = getEither("timeplus_PORT", "TIMEPLUS_PORT", "8463"); + const std::string user = getEither("timeplus_USER", "TIMEPLUS_USER", "default"); + const std::string password = getEither("timeplus_PASSWORD", "TIMEPLUS_PASSWORD", ""); + const std::string db = getEither("timeplus_DB", "TIMEPLUS_DB", "default"); + const auto opts = ClientOptions().SetHost(host).SetPort(static_cast(std::stoul(port))).SetUser(user).SetPassword(password).SetDefaultDatabase(db); + Client client(opts); + client.Execute("DROP TEMPORARY STREAM IF EXISTS test_timeplus_cpp_ipv4_conv;"); + client.Execute("CREATE TEMPORARY STREAM IF NOT EXISTS test_timeplus_cpp_ipv4_conv (v4 ipv4, v4str string, v4int uint32) ENGINE = Memory"); + Block b; + auto v4 = std::make_shared(); + auto v4str = std::make_shared(); + auto v4int = std::make_shared(); + v4->Append("127.0.0.1"); + v4str->Append("127.0.0.1"); + v4int->Append(2130706433u); // 127.0.0.1 as uint32 + b.AppendColumn("v4", v4); + b.AppendColumn("v4str", v4str); + b.AppendColumn("v4int", v4int); + client.Insert("test_timeplus_cpp_ipv4_conv", b); + size_t row = 0; + client.Select("SELECT v4, v4str, v4int FROM test_timeplus_cpp_ipv4_conv", [&row](const Block& block) { + if (block.GetRowCount() == 0) return; + ASSERT_EQ(3U, block.GetColumnCount()); + for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { + auto col = block[0]->As(); + auto colstr = block[1]->As(); + auto colint = block[2]->As(); + EXPECT_EQ(col->AsString(c), (*colstr)[c]); // IPv4 <-> string + in_addr addr = col->At(c); + EXPECT_EQ(ntohl(addr.s_addr), (*colint)[c]); // IPv4 <-> uint32 + } + }); + EXPECT_EQ(1U, row); +} diff --git a/ut/ipv6_supported_ut.cpp b/ut/ipv6_supported_ut.cpp new file mode 100644 index 0000000..381083e --- /dev/null +++ b/ut/ipv6_supported_ut.cpp @@ -0,0 +1,156 @@ +#include +#include + +#include "ut/utils.h" +#include "ut/value_generators.h" +#include +#include + +using namespace timeplus; + +// Test inserting and selecting IPv6 addresses +TEST(IPv6Supported, InsertSelect) { + // Helper to get environment variable or default + auto getEither = [](const char* a, const char* b, const char* def) { + const char* v = std::getenv(a); + if (v) return std::string(v); + v = std::getenv(b); + if (v) return std::string(v); + return std::string(def); + }; + + // Get connection parameters + const std::string host = getEither("timeplus_HOST", "TIMEPLUS_HOST", "localhost"); + const std::string port = getEither("timeplus_PORT", "TIMEPLUS_PORT", "8463"); + const std::string user = getEither("timeplus_USER", "TIMEPLUS_USER", "default"); + const std::string password = getEither("timeplus_PASSWORD", "TIMEPLUS_PASSWORD", ""); + const std::string db = getEither("timeplus_DB", "TIMEPLUS_DB", "default"); + + const auto opts = ClientOptions() + .SetHost(host) + .SetPort(static_cast(std::stoul(port))) + .SetUser(user) + .SetPassword(password) + .SetDefaultDatabase(db); + + Client client(opts); + + // Prepare test table + client.Execute("DROP TEMPORARY STREAM IF EXISTS test_timeplus_cpp_ipv6_ut;"); + client.Execute("CREATE TEMPORARY STREAM IF NOT EXISTS test_timeplus_cpp_ipv6_ut (v6 ipv6) ENGINE = Memory"); + + Block b; + auto v6 = std::make_shared(); + auto ips6 = MakeIPv6s(); // Generate test IPv6 addresses + for (const auto & ip : ips6) { + v6->Append(ip); + } + + b.AppendColumn("v6", v6); + client.Insert("test_timeplus_cpp_ipv6_ut", b); + + // Validate inserted data + size_t row = 0; + client.Select("SELECT v6 FROM test_timeplus_cpp_ipv6_ut", [&ips6, &row](const Block& block) { + if (block.GetRowCount() == 0) return; + ASSERT_EQ(1U, block.GetColumnCount()); + for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { + auto col = block[0]->As(); + auto got = col->At(c); + EXPECT_EQ(0, std::memcmp(&ips6[row], &got, sizeof(in6_addr))); + } + }); + EXPECT_EQ(ips6.size(), row); +} + +// Test nullable IPv6 column: insert a valid and a null value, then check nullability +TEST(IPv6Supported, NullableInsertSelect) { + // Type name must be lowercase: nullable(ipv6) + auto getEither = [](const char* a, const char* b, const char* def) { + const char* v = std::getenv(a); + if (v) return std::string(v); + v = std::getenv(b); + if (v) return std::string(v); + return std::string(def); + }; + const std::string host = getEither("timeplus_HOST", "TIMEPLUS_HOST", "localhost"); + const std::string port = getEither("timeplus_PORT", "TIMEPLUS_PORT", "8463"); + const std::string user = getEither("timeplus_USER", "TIMEPLUS_USER", "default"); + const std::string password = getEither("timeplus_PASSWORD", "TIMEPLUS_PASSWORD", ""); + const std::string db = getEither("timeplus_DB", "TIMEPLUS_DB", "default"); + const auto opts = ClientOptions().SetHost(host).SetPort(static_cast(std::stoul(port))).SetUser(user).SetPassword(password).SetDefaultDatabase(db); + Client client(opts); + client.Execute("DROP TEMPORARY STREAM IF EXISTS test_timeplus_cpp_ipv6_nullable;"); + client.Execute("CREATE TEMPORARY STREAM IF NOT EXISTS test_timeplus_cpp_ipv6_nullable (v6 nullable(ipv6)) ENGINE = Memory"); + Block b; + auto v6 = std::make_shared(); + v6->Append("::1"); // valid IPv6 + auto nulls = std::make_shared(); + nulls->Append(0); // not null + v6->Append("::"); // :: as null (all zero IPv6) + nulls->Append(1); // null + b.AppendColumn("v6", std::make_shared(v6, nulls)); + client.Insert("test_timeplus_cpp_ipv6_nullable", b); + size_t row = 0; + client.Select("SELECT v6 FROM test_timeplus_cpp_ipv6_nullable", [&row](const Block& block) { + if (block.GetRowCount() == 0) return; + ASSERT_EQ(1U, block.GetColumnCount()); + for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { + auto col = block[0]->As(); + if (row == 0) { + EXPECT_FALSE(col->IsNull(c)); // Should not be null + } else { + EXPECT_TRUE(col->IsNull(c)); // Should be null + } + } + }); + EXPECT_EQ(2U, row); +} + +// Test invalid IPv6 input +TEST(IPv6Supported, InvalidData) { + // Test invalid IPv6 strings, should throw ValidationError + auto v6 = std::make_shared(); + EXPECT_THROW(v6->Append("gggg:gggg:gggg:gggg:gggg:gggg:gggg:gggg"), ValidationError); + EXPECT_THROW(v6->Append("not_an_ip"), ValidationError); +} + +// Test type conversion between IPv6 and string +TEST(IPv6Supported, TypeConversion) { + // Type name must be lowercase: string + auto getEither = [](const char* a, const char* b, const char* def) { + const char* v = std::getenv(a); + if (v) return std::string(v); + v = std::getenv(b); + if (v) return std::string(v); + return std::string(def); + }; + const std::string host = getEither("timeplus_HOST", "TIMEPLUS_HOST", "localhost"); + const std::string port = getEither("timeplus_PORT", "TIMEPLUS_PORT", "8463"); + const std::string user = getEither("timeplus_USER", "TIMEPLUS_USER", "default"); + const std::string password = getEither("timeplus_PASSWORD", "TIMEPLUS_PASSWORD", ""); + const std::string db = getEither("timeplus_DB", "TIMEPLUS_DB", "default"); + const auto opts = ClientOptions().SetHost(host).SetPort(static_cast(std::stoul(port))).SetUser(user).SetPassword(password).SetDefaultDatabase(db); + Client client(opts); + client.Execute("DROP TEMPORARY STREAM IF EXISTS test_timeplus_cpp_ipv6_conv;"); + client.Execute("CREATE TEMPORARY STREAM IF NOT EXISTS test_timeplus_cpp_ipv6_conv (v6 ipv6, v6str string) ENGINE = Memory"); + Block b; + auto v6 = std::make_shared(); + auto v6str = std::make_shared(); + v6->Append("::1"); + v6str->Append("::1"); + b.AppendColumn("v6", v6); + b.AppendColumn("v6str", v6str); + client.Insert("test_timeplus_cpp_ipv6_conv", b); + size_t row = 0; + client.Select("SELECT v6, v6str FROM test_timeplus_cpp_ipv6_conv", [&row](const Block& block) { + if (block.GetRowCount() == 0) return; + ASSERT_EQ(2U, block.GetColumnCount()); + for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { + auto col = block[0]->As(); + auto colstr = block[1]->As(); + EXPECT_EQ(col->AsString(c), (*colstr)[c]); // IPv6 <-> string + } + }); + EXPECT_EQ(1U, row); +}