Skip to content

Commit

Permalink
support blob/clob types in service api testing
Browse files Browse the repository at this point in the history
  • Loading branch information
kuron99 committed Jan 29, 2025
1 parent c0b5e25 commit d1f41d8
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 34 deletions.
7 changes: 7 additions & 0 deletions include/jogasaki/blob_locator.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ class blob_locator {
path_(std::move(path))
{}

/**
* @brief return path of the blob data file
*/
[[nodiscard]] std::string_view path() const noexcept {
return path_;
}

/**
* @brief compare two blob object references
* @param a first arg to compare
Expand Down
7 changes: 7 additions & 0 deletions include/jogasaki/clob_locator.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ class clob_locator {
path_(std::move(path))
{}

/**
* @brief return path of the clob data file
*/
[[nodiscard]] std::string_view path() const noexcept {
return path_;
}

/**
* @brief compare two blob object references
* @param a first arg to compare
Expand Down
47 changes: 18 additions & 29 deletions mock/jogasaki/utils/command_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once

#include <any>
#include <sstream>
Expand All @@ -22,16 +23,18 @@
#include <takatori/util/downcast.h>
#include <takatori/util/maybe_shared_ptr.h>

#include "jogasaki/proto/sql/common.pb.h"
#include "jogasaki/proto/sql/request.pb.h"
#include "jogasaki/proto/sql/response.pb.h"
#include <jogasaki/api.h>
#include <jogasaki/api/impl/map_error_code.h>
#include <jogasaki/blob_locator.h>
#include <jogasaki/clob_locator.h>
#include <jogasaki/meta/field_type.h>
#include <jogasaki/meta/record_meta.h>
#include <jogasaki/request_statistics.h>
#include <jogasaki/utils/convert_offset.h>
#include <jogasaki/utils/decimal.h>
#include "jogasaki/proto/sql/common.pb.h"
#include "jogasaki/proto/sql/request.pb.h"
#include "jogasaki/proto/sql/response.pb.h"

namespace jogasaki::utils {

Expand Down Expand Up @@ -83,32 +86,8 @@ inline jogasaki::meta::record_meta create_record_meta(std::vector<colinfo> const
case sql::common::AtomType::TIME_OF_DAY_WITH_TIME_ZONE: fields.emplace_back(std::make_shared<meta::time_of_day_field_option>(true)); break;
case sql::common::AtomType::TIME_POINT: fields.emplace_back(std::make_shared<meta::time_point_field_option>(false)); break;
case sql::common::AtomType::TIME_POINT_WITH_TIME_ZONE: fields.emplace_back(std::make_shared<meta::time_point_field_option>(true)); break;
default: std::abort();
}
}
jogasaki::meta::record_meta meta{std::move(fields), std::move(nullities)};
return meta;
}

inline jogasaki::meta::record_meta create_record_meta(sql::response::ResultSetMetadata const& proto) {
std::vector<meta::field_type> fields{};
boost::dynamic_bitset<std::uint64_t> nullities;
for(std::size_t i=0, n=proto.columns_size(); i<n; ++i) {
auto& c = proto.columns(i);
meta::field_type field{};
nullities.push_back(true); // TODO assume all nullable
switch(c.atom_type()) {
using kind = meta::field_type_kind;
case sql::common::AtomType::INT4: fields.emplace_back(meta::field_enum_tag<kind::int4>); break;
case sql::common::AtomType::INT8: fields.emplace_back(meta::field_enum_tag<kind::int8>); break;
case sql::common::AtomType::FLOAT4: fields.emplace_back(meta::field_enum_tag<kind::float4>); break;
case sql::common::AtomType::FLOAT8: fields.emplace_back(meta::field_enum_tag<kind::float8>); break;
case sql::common::AtomType::CHARACTER: fields.emplace_back(std::make_shared<meta::character_field_option>()); break;
case sql::common::AtomType::OCTET: fields.emplace_back(std::make_shared<meta::octet_field_option>()); break;
case sql::common::AtomType::DECIMAL: fields.emplace_back(std::make_shared<meta::decimal_field_option>()); break;
case sql::common::AtomType::DATE: fields.emplace_back(meta::field_enum_tag<kind::date>); break;
case sql::common::AtomType::TIME_OF_DAY: fields.emplace_back(std::make_shared<meta::time_of_day_field_option>()); break;
case sql::common::AtomType::TIME_POINT: fields.emplace_back(std::make_shared<meta::time_point_field_option>()); break;
case sql::common::AtomType::BLOB: fields.emplace_back(meta::field_enum_tag<kind::blob>); break;
case sql::common::AtomType::CLOB: fields.emplace_back(meta::field_enum_tag<kind::clob>); break;
default: std::abort();
}
}
Expand Down Expand Up @@ -466,6 +445,16 @@ inline void fill_parameters(
v->set_time_zone_offset(offset);
break;
}
case ValueCase::kBlob: {
auto loc = std::any_cast<blob_locator>(p.value_);
c0->mutable_blob()->mutable_local_path()->assign(loc.path());
break;
}
case ValueCase::kClob: {
auto loc = std::any_cast<clob_locator>(p.value_);
c0->mutable_clob()->mutable_local_path()->assign(loc.path());
break;
}
case ValueCase::kReferenceColumnPosition: c0->set_reference_column_position(std::any_cast<std::uint64_t>(p.value_)); break;
case ValueCase::kReferenceColumnName: c0->set_reference_column_name(std::any_cast<std::string>(p.value_)); break;
default: std::abort();
Expand Down
26 changes: 21 additions & 5 deletions src/jogasaki/api/impl/service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1296,19 +1296,19 @@ void service::set_params(
params->set_decimal(p.name(), to_triple(p.decimal_value()));
break;
case sql::request::Parameter::ValueCase::kDateValue:
params->set_date(p.name(), field_type_traits<kind::date>::runtime_type{p.date_value()});
params->set_date(p.name(), field_type_traits<kind::date>::parameter_type{p.date_value()});
break;
case sql::request::Parameter::ValueCase::kTimeOfDayValue:
params->set_time_of_day(
p.name(),
field_type_traits<kind::time_of_day>::runtime_type{
field_type_traits<kind::time_of_day>::parameter_type{
std::chrono::duration<std::uint64_t, std::nano>{p.time_of_day_value()}
}
);
break;
case sql::request::Parameter::ValueCase::kTimePointValue: {
auto& v = p.time_point_value();
params->set_time_point(p.name(), field_type_traits<kind::time_point>::runtime_type{
params->set_time_point(p.name(), field_type_traits<kind::time_point>::parameter_type{
std::chrono::duration<std::int64_t>{v.offset_seconds()},
std::chrono::nanoseconds{v.nano_adjustment()}
});
Expand All @@ -1321,7 +1321,7 @@ void service::set_params(
}
};
auto offset_min = p.time_of_day_with_time_zone_value().time_zone_offset();
params->set_time_of_day(p.name(), field_type_traits<kind::time_of_day>::runtime_type{
params->set_time_of_day(p.name(), field_type_traits<kind::time_of_day>::parameter_type{
utils::remove_offset({tod, offset_min})
});
break;
Expand All @@ -1333,11 +1333,21 @@ void service::set_params(
std::chrono::nanoseconds{v.nano_adjustment()}
};
auto offset_min = v.time_zone_offset();
params->set_time_point(p.name(), field_type_traits<kind::time_point>::runtime_type{
params->set_time_point(p.name(), field_type_traits<kind::time_point>::parameter_type{
utils::remove_offset({tp, offset_min})
});
break;
}
case sql::request::Parameter::ValueCase::kBlob: {
auto& v = p.blob();
params->set_blob(p.name(), field_type_traits<kind::blob>::parameter_type{v.local_path()});
break;
}
case sql::request::Parameter::ValueCase::kClob: {
auto& v = p.clob();
params->set_clob(p.name(), field_type_traits<kind::clob>::parameter_type{v.local_path()});
break;
}
case sql::request::Parameter::ValueCase::kReferenceColumnPosition:
params->set_reference_column(p.name(), p.reference_column_position());
break;
Expand Down Expand Up @@ -1544,6 +1554,12 @@ void details::set_metadata(jogasaki::api::record_meta const* metadata, T& meta)
case jogasaki::api::field_type_kind::time_point_with_time_zone:
column->set_atom_type(sql::common::AtomType::TIME_POINT_WITH_TIME_ZONE);
break;
case jogasaki::api::field_type_kind::blob:
column->set_atom_type(sql::common::AtomType::BLOB);
break;
case jogasaki::api::field_type_kind::clob:
column->set_atom_type(sql::common::AtomType::CLOB);
break;
case jogasaki::api::field_type_kind::unknown:
column->set_atom_type(sql::common::AtomType::UNKNOWN);
break;
Expand Down
2 changes: 2 additions & 0 deletions src/jogasaki/utils/proto_field_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ namespace sql = proto::sql;
case sql::common::AtomType::TIME_OF_DAY_WITH_TIME_ZONE: return jogasaki::api::field_type_kind::time_of_day_with_time_zone;
case sql::common::AtomType::TIME_POINT: return jogasaki::api::field_type_kind::time_point;
case sql::common::AtomType::TIME_POINT_WITH_TIME_ZONE: return jogasaki::api::field_type_kind::time_point_with_time_zone;
case sql::common::AtomType::BLOB: return jogasaki::api::field_type_kind::blob;
case sql::common::AtomType::CLOB: return jogasaki::api::field_type_kind::clob;
default: return jogasaki::api::field_type_kind::undefined;
}
}
Expand Down
105 changes: 105 additions & 0 deletions test/jogasaki/api/service_api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,90 @@ TEST_F(service_api_test, boolean_type) {
test_dispose_prepare(query_handle);
}

TEST_F(service_api_test, blob_types) {
execute_statement("create table t (c0 int primary key, c1 blob, c2 clob)");
std::uint64_t tx_handle{};
test_begin(tx_handle);
std::uint64_t stmt_handle{};
test_prepare(
stmt_handle,
"insert into t values (0, :p0, :p1)",
std::pair{"p0"s, sql::common::AtomType::BLOB},
std::pair{"p1"s, sql::common::AtomType::CLOB}
);

{
std::vector<parameter> parameters{
{"p0"s, ValueCase::kBlob, std::any{std::in_place_type<blob_locator>, blob_locator{""}}}, //TODO fill path
{"p1"s, ValueCase::kClob, std::any{std::in_place_type<clob_locator>, clob_locator{""}}}, //TODO fill path
};
auto s = encode_execute_prepared_statement(tx_handle, stmt_handle, parameters);

auto req = std::make_shared<tateyama::api::server::mock::test_request>(s);
auto res = std::make_shared<tateyama::api::server::mock::test_response>();

auto st = (*service_)(req, res);
EXPECT_TRUE(res->wait_completion());
EXPECT_TRUE(res->completed());
ASSERT_TRUE(st);

auto [success, error, stats] = decode_execute_result(res->body_);
ASSERT_TRUE(success);
}
test_commit(tx_handle);
std::uint64_t query_handle{};
test_prepare(
query_handle,
"select c1, c2 from t"
);
test_begin(tx_handle);
{
std::vector<parameter> parameters{};
auto s = encode_execute_prepared_query(tx_handle, query_handle, parameters);

auto req = std::make_shared<tateyama::api::server::mock::test_request>(s);
auto res = std::make_shared<tateyama::api::server::mock::test_response>();

auto st = (*service_)(req, res);
EXPECT_TRUE(res->wait_completion());
EXPECT_TRUE(res->completed());
ASSERT_TRUE(st);

{
auto [name, cols] = decode_execute_query(res->body_head_);
ASSERT_EQ(2, cols.size());

EXPECT_EQ(sql::common::AtomType::BLOB, cols[0].type_);
EXPECT_TRUE(cols[0].nullable_); //TODO for now all nullable
EXPECT_EQ(sql::common::AtomType::CLOB, cols[1].type_);
EXPECT_TRUE(cols[1].nullable_);
{
ASSERT_TRUE(res->channel_);
auto& ch = *res->channel_;
auto m = create_record_meta(cols);
auto v = deserialize_msg(ch.view(), m);
ASSERT_EQ(1, v.size());

EXPECT_EQ((mock::typed_nullable_record<ft::blob, ft::clob>(
std::tuple{meta::blob_type(), meta::clob_type()},
{
blob_reference{0, lob_data_provider::datastore},
clob_reference{0, lob_data_provider::datastore},
},
{true, true}
)), v[0]);
}
}
{
auto [success, error] = decode_result_only(res->body_);
ASSERT_TRUE(success);
}
}
test_commit(tx_handle);
test_dispose_prepare(stmt_handle);
test_dispose_prepare(query_handle);
}

TEST_F(service_api_test, protobuf1) {
// verify protobuf behavior
using namespace std::string_view_literals;
Expand Down Expand Up @@ -2014,6 +2098,27 @@ TEST_F(service_api_test, describe_table_temporal_types) {
EXPECT_EQ(sql::common::AtomType::TIME_POINT_WITH_TIME_ZONE, result.columns_[4].atom_type_);
}

TEST_F(service_api_test, describe_table_blob_types) {
// verify with_offset is correctly reflected on the output schema
execute_statement("create table T (C0 BLOB, C1 CLOB)");
auto s = encode_describe_table("T");
auto req = std::make_shared<tateyama::api::server::mock::test_request>(s);
auto res = std::make_shared<tateyama::api::server::mock::test_response>();

auto st = (*service_)(req, res);
EXPECT_TRUE(res->wait_completion());
EXPECT_TRUE(res->completed());
ASSERT_TRUE(st);

auto [result, error] = decode_describe_table(res->body_);
ASSERT_EQ("T", result.table_name_);
ASSERT_EQ(2, result.columns_.size());
EXPECT_EQ("C0", result.columns_[0].name_);
EXPECT_EQ(sql::common::AtomType::BLOB, result.columns_[0].atom_type_);
EXPECT_EQ("C1", result.columns_[1].name_);
EXPECT_EQ(sql::common::AtomType::CLOB, result.columns_[1].atom_type_);
}

TEST_F(service_api_test, describe_pkless_table) {
// make sure generated pk column is not visible
execute_statement("create table T (C0 INT)");
Expand Down

0 comments on commit d1f41d8

Please sign in to comment.