diff --git a/google/cloud/spanner/value.cc b/google/cloud/spanner/value.cc index 3a1c028c5b724..d7b5ec515d438 100644 --- a/google/cloud/spanner/value.cc +++ b/google/cloud/spanner/value.cc @@ -158,6 +158,7 @@ std::ostream& StreamHelper(std::ostream& os, // NOLINT(misc-no-recursion) case google::spanner::v1::TypeCode::JSON: case google::spanner::v1::TypeCode::TIMESTAMP: case google::spanner::v1::TypeCode::NUMERIC: + case google::spanner::v1::TypeCode::INTERVAL: return os << v.string_value(); case google::spanner::v1::TypeCode::DATE: @@ -269,6 +270,10 @@ bool Value::TypeProtoIs(absl::CivilDay, google::spanner::v1::Type const& type) { return type.code() == google::spanner::v1::TypeCode::DATE; } +bool Value::TypeProtoIs(Interval, google::spanner::v1::Type const& type) { + return type.code() == google::spanner::v1::TypeCode::INTERVAL; +} + bool Value::TypeProtoIs(std::string const&, google::spanner::v1::Type const& type) { return type.code() == google::spanner::v1::TypeCode::STRING; @@ -404,6 +409,12 @@ google::spanner::v1::Type Value::MakeTypeProto(absl::CivilDay) { return t; } +google::spanner::v1::Type Value::MakeTypeProto(Interval) { + google::spanner::v1::Type t; + t.set_code(google::spanner::v1::TypeCode::INTERVAL); + return t; +} + google::spanner::v1::Type Value::MakeTypeProto(int) { return MakeTypeProto(std::int64_t{}); } @@ -520,6 +531,12 @@ google::protobuf::Value Value::MakeValueProto(absl::CivilDay d) { return v; } +google::protobuf::Value Value::MakeValueProto(Interval intvl) { + google::protobuf::Value v; + v.set_string_value(std::string(intvl)); + return v; +} + google::protobuf::Value Value::MakeValueProto(int i) { return MakeValueProto(std::int64_t{i}); } @@ -711,6 +728,14 @@ StatusOr Value::GetValue(absl::CivilDay, s + ": Failed to match RFC3339 full-date", GCP_ERROR_INFO()); } +StatusOr Value::GetValue(Interval, google::protobuf::Value const& pv, + google::spanner::v1::Type const&) { + if (pv.kind_case() != google::protobuf::Value::kStringValue) { + return Status(StatusCode::kUnknown, "missing Interval"); + } + return MakeInterval(pv.string_value()); +} + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace spanner } // namespace cloud diff --git a/google/cloud/spanner/value.h b/google/cloud/spanner/value.h index e9b8f14723bfe..d888c4876a547 100644 --- a/google/cloud/spanner/value.h +++ b/google/cloud/spanner/value.h @@ -18,6 +18,7 @@ #include "google/cloud/spanner/bytes.h" #include "google/cloud/spanner/date.h" #include "google/cloud/spanner/internal/tuple_utils.h" +#include "google/cloud/spanner/interval.h" #include "google/cloud/spanner/json.h" #include "google/cloud/spanner/numeric.h" #include "google/cloud/spanner/oid.h" @@ -77,6 +78,7 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN * OID(PG) | `google::cloud::spanner::PgOid` * TIMESTAMP | `google::cloud::spanner::Timestamp` * DATE | `absl::CivilDay` + * INTERVAL | `google::cloud::spanner::Interval` * ENUM | `google::cloud::spanner::ProtoEnum` * PROTO | `google::cloud::spanner::ProtoMessage` * ARRAY | `std::vector` // [1] @@ -222,6 +224,8 @@ class Value { explicit Value(absl::CivilDay v) : Value(PrivateConstructor{}, std::move(v)) {} /// @copydoc Value(bool) + explicit Value(Interval v) : Value(PrivateConstructor{}, std::move(v)) {} + /// @copydoc Value(bool) template explicit Value(ProtoEnum v) : Value(PrivateConstructor{}, std::move(v)) {} /// @copydoc Value(bool) @@ -387,6 +391,7 @@ class Value { static bool TypeProtoIs(Timestamp, google::spanner::v1::Type const&); static bool TypeProtoIs(CommitTimestamp, google::spanner::v1::Type const&); static bool TypeProtoIs(absl::CivilDay, google::spanner::v1::Type const&); + static bool TypeProtoIs(Interval, google::spanner::v1::Type const&); static bool TypeProtoIs(std::string const&, google::spanner::v1::Type const&); static bool TypeProtoIs(Bytes const&, google::spanner::v1::Type const&); static bool TypeProtoIs(Json const&, google::spanner::v1::Type const&); @@ -460,6 +465,7 @@ class Value { static google::spanner::v1::Type MakeTypeProto(Timestamp); static google::spanner::v1::Type MakeTypeProto(CommitTimestamp); static google::spanner::v1::Type MakeTypeProto(absl::CivilDay); + static google::spanner::v1::Type MakeTypeProto(Interval); template static google::spanner::v1::Type MakeTypeProto(ProtoEnum) { google::spanner::v1::Type t; @@ -539,6 +545,7 @@ class Value { static google::protobuf::Value MakeValueProto(Timestamp ts); static google::protobuf::Value MakeValueProto(CommitTimestamp ts); static google::protobuf::Value MakeValueProto(absl::CivilDay d); + static google::protobuf::Value MakeValueProto(Interval intvl); template static google::protobuf::Value MakeValueProto(ProtoEnum e) { return MakeValueProto(std::int64_t{E{e}}); @@ -629,6 +636,8 @@ class Value { static StatusOr GetValue(absl::CivilDay, google::protobuf::Value const&, google::spanner::v1::Type const&); + static StatusOr GetValue(Interval, google::protobuf::Value const&, + google::spanner::v1::Type const&); template static StatusOr> GetValue(ProtoEnum, google::protobuf::Value const& pv, diff --git a/google/cloud/spanner/value_test.cc b/google/cloud/spanner/value_test.cc index caa87b53f4be5..c604e0778c870 100644 --- a/google/cloud/spanner/value_test.cc +++ b/google/cloud/spanner/value_test.cc @@ -264,6 +264,15 @@ TEST(Value, BasicSemantics) { v.resize(10); TestBasicSemantics(v); + for (auto x : {Interval(), MakeInterval("P1Y1M1D").value()}) { + SCOPED_TRACE("Testing: google::cloud::spanner::Interval " + std::string{x}); + TestBasicSemantics(x); + TestBasicSemantics(std::vector(5, x)); + std::vector> v(5, x); + v.resize(10); + TestBasicSemantics(v); + } + for (auto x : {testing::Genre::POP, testing::Genre::JAZZ, testing::Genre::FOLK, testing::Genre::ROCK}) { SCOPED_TRACE("Testing: ProtoEnum " + @@ -922,6 +931,24 @@ TEST(Value, ProtoConversionDate) { } } +TEST(Value, ProtoConversionInterval) { + for (auto const& x : std::vector{ + MakeInterval("-P178000Y").value(), + MakeInterval("-P1Y2M3DT4H5M6.789S").value(), + MakeInterval("-PT0.000001S").value(), + Interval(), + MakeInterval("PT0.000001S").value(), + MakeInterval("P1Y2M3DT4H5M6.789S").value(), + MakeInterval("P178000Y").value(), + }) { + Value const v(x); + auto const p = spanner_internal::ToProto(v); + EXPECT_EQ(v, spanner_internal::FromProto(p.first, p.second)); + EXPECT_EQ(google::spanner::v1::TypeCode::INTERVAL, p.first.code()); + EXPECT_EQ(std::string{x}, p.second.string_value()); + } +} + TEST(Value, ProtoConversionProtoEnum) { for (auto e : {testing::Genre::POP, testing::Genre::JAZZ, testing::Genre::FOLK, testing::Genre::ROCK}) { @@ -1251,6 +1278,24 @@ TEST(Value, GetBadDate) { EXPECT_THAT(v.get(), Not(IsOk())); } +TEST(Value, GetBadInterval) { + Value v(Interval{}); + ClearProtoKind(v); + EXPECT_THAT(v.get(), Not(IsOk())); + + SetProtoKind(v, google::protobuf::NULL_VALUE); + EXPECT_THAT(v.get(), Not(IsOk())); + + SetProtoKind(v, true); + EXPECT_THAT(v.get(), Not(IsOk())); + + SetProtoKind(v, 0.0); + EXPECT_THAT(v.get(), Not(IsOk())); + + SetProtoKind(v, "blah"); + EXPECT_THAT(v.get(), Not(IsOk())); +} + TEST(Value, GetBadProtoEnum) { Value v(ProtoEnum{}); ClearProtoKind(v); @@ -1429,6 +1474,7 @@ TEST(Value, OutputStream) { {Value(PgOid(1234567890)), "1234567890", normal}, {Value(absl::CivilDay()), "1970-01-01", normal}, {Value(Timestamp()), "1970-01-01T00:00:00Z", normal}, + {Value(Interval()), "P0D", normal}, {Value(ProtoEnum(testing::Genre::POP)), "google.cloud.spanner.testing.POP", normal}, {Value(ProtoMessage(singer)), @@ -1462,6 +1508,7 @@ TEST(Value, OutputStream) { {MakeNullValue(), "NULL", normal}, {MakeNullValue(), "NULL", normal}, {MakeNullValue(), "NULL", normal}, + {MakeNullValue(), "NULL", normal}, {MakeNullValue>(), "NULL", normal}, {MakeNullValue>(), "NULL", normal}, @@ -1482,6 +1529,7 @@ TEST(Value, OutputStream) { {Value(std::vector{2}), "[1970-01-01, 1970-01-01]", normal}, {Value(std::vector{1}), "[1970-01-01T00:00:00Z]", normal}, + {Value(std::vector{1}), "[P0D]", normal}, {Value(std::vector>{testing::JAZZ, testing::FOLK}), "[google.cloud.spanner.testing.JAZZ, google.cloud.spanner.testing.FOLK]", @@ -1507,6 +1555,7 @@ TEST(Value, OutputStream) { {MakeNullValue>(), "NULL", normal}, {MakeNullValue>(), "NULL", normal}, {MakeNullValue>(), "NULL", normal}, + {MakeNullValue>(), "NULL", normal}, {MakeNullValue>>(), "NULL", normal}, {MakeNullValue>>(), "NULL", normal}, @@ -1657,6 +1706,10 @@ TEST(Value, OutputStreamMatchesT) { StreamMatchesValueStream(Timestamp()); StreamMatchesValueStream(MakeTimestamp(MakeTime(1, 1)).value()); + // Interval + StreamMatchesValueStream(Interval()); + StreamMatchesValueStream(MakeInterval("P1Y2M3DT4H5M6.789S").value()); + // ProtoEnum StreamMatchesValueStream(ProtoEnum()); StreamMatchesValueStream(ProtoEnum(testing::ROCK));