Skip to content

Commit

Permalink
feat: add PG.OID type (#13127)
Browse files Browse the repository at this point in the history
Allows for using PG.OID type in parameterized queries and reading them
from result sets.
  • Loading branch information
thiagotnunes authored Nov 21, 2023
1 parent 793f3b5 commit f7c9022
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 0 deletions.
1 change: 1 addition & 0 deletions google/cloud/spanner/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ add_library(
mutations.h
numeric.cc
numeric.h
oid.h
options.h
partition_options.cc
partition_options.h
Expand Down
1 change: 1 addition & 0 deletions google/cloud/spanner/google_cloud_cpp_spanner.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ google_cloud_cpp_spanner_hdrs = [
"keys.h",
"mutations.h",
"numeric.h",
"oid.h",
"options.h",
"partition_options.h",
"partitioned_dml_result.h",
Expand Down
43 changes: 43 additions & 0 deletions google/cloud/spanner/internal/connection_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,49 @@ TEST(ConnectionImplTest, ExecuteQueryNumericParameter) {
}
}

TEST(ConnectionImplTest, ExecuteQueryPgOidResult) {
auto mock = std::make_shared<spanner_testing::MockSpannerStub>();
auto db = spanner::Database("placeholder_project", "placeholder_instance",
"placeholder_database_id");
EXPECT_CALL(*mock, BatchCreateSessions(_, HasDatabase(db)))
.WillOnce(Return(MakeSessionsResponse({"test-session-name"})));
auto constexpr kText = R"pb(
metadata: {
row_type: {
fields: {
name: "ColumnA",
type: { code: INT64 type_annotation: PG_OID }
}
fields: {
name: "ColumnB",
type: { code: INT64 type_annotation: PG_OID }
}
}
}
values: { string_value: "42" }
values: { null_value: NULL_VALUE }
values: { string_value: "0" }
values: { string_value: "999" }
)pb";
EXPECT_CALL(*mock, ExecuteStreamingSql)
.WillOnce(Return(ByMove(MakeReader<PartialResultSet>({kText}))));

auto conn = MakeConnectionImpl(db, mock);
internal::OptionsSpan span(MakeLimitedTimeOptions());
auto rows = conn->ExecuteQuery(
{MakeSingleUseTransaction(spanner::Transaction::ReadOnlyOptions()),
spanner::SqlStatement("SELECT * FROM Table")});

using RowType = std::tuple<spanner::PgOid, absl::optional<spanner::PgOid>>;
auto stream = spanner::StreamOf<RowType>(rows);
auto actual = std::vector<StatusOr<RowType>>{stream.begin(), stream.end()};
EXPECT_THAT(
actual,
ElementsAre(
IsOkAndHolds(RowType(spanner::PgOid(42), absl::nullopt)),
IsOkAndHolds(RowType(spanner::PgOid(0), spanner::PgOid(999)))));
}

/// @test Verify implicit "begin transaction" in ExecuteQuery() works.
TEST(ConnectionImplTest, ExecuteQueryImplicitBeginTransaction) {
auto mock = std::make_shared<spanner_testing::MockSpannerStub>();
Expand Down
78 changes: 78 additions & 0 deletions google/cloud/spanner/oid.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_OID_H
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_OID_H

#include "google/cloud/spanner/version.h"
#include <cstdint>
#include <ostream>

namespace google {
namespace cloud {
namespace spanner {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

/**
* A representation of the PostgreSQL Object Identifier (OID) type: used as
* primary keys for various system tables in the PostgreSQL dialect.
*/
class PgOid {
public:
/// Default construction yields OID 0.
PgOid() : rep_(0) {}

/// @name Regular value type, supporting copy, assign, move.
///@{
PgOid(PgOid const&) = default;
PgOid& operator=(PgOid const&) = default;
PgOid(PgOid&&) = default;
PgOid& operator=(PgOid&&) = default;
///@}

/**
* Construction from an OID unsigned four-byte integer.
*/
explicit PgOid(std::uint64_t value) : rep_(value) {}

/// @name Conversion to an OID unsigned four-byte integer.
///@{
explicit operator std::uint64_t() const { return rep_; }
///@}

/// @name Relational operators
///@{
friend bool operator==(PgOid const& lhs, PgOid const& rhs) {
return lhs.rep_ == rhs.rep_;
}
friend bool operator!=(PgOid const& lhs, PgOid const& rhs) {
return !(lhs == rhs);
}
///@}

private:
std::uint64_t rep_;
};

/// Outputs an OID to the provided stream.
inline std::ostream& operator<<(std::ostream& os, PgOid const& oid) {
return os << static_cast<std::uint64_t>(oid);
}

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace spanner
} // namespace cloud
} // namespace google

#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_SPANNER_OID_H
27 changes: 27 additions & 0 deletions google/cloud/spanner/value.cc
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,12 @@ bool Value::TypeProtoIs(PgNumeric const&,
google::spanner::v1::TypeAnnotationCode::PG_NUMERIC;
}

bool Value::TypeProtoIs(PgOid const&, google::spanner::v1::Type const& type) {
return type.code() == google::spanner::v1::TypeCode::INT64 &&
type.type_annotation() ==
google::spanner::v1::TypeAnnotationCode::PG_OID;
}

//
// Value::MakeTypeProto
//
Expand Down Expand Up @@ -323,6 +329,13 @@ google::spanner::v1::Type Value::MakeTypeProto(PgNumeric const&) {
return t;
}

google::spanner::v1::Type Value::MakeTypeProto(PgOid const&) {
google::spanner::v1::Type t;
t.set_code(google::spanner::v1::TypeCode::INT64);
t.set_type_annotation(google::spanner::v1::TypeAnnotationCode::PG_OID);
return t;
}

google::spanner::v1::Type Value::MakeTypeProto(Timestamp) {
google::spanner::v1::Type t;
t.set_code(google::spanner::v1::TypeCode::TIMESTAMP);
Expand Down Expand Up @@ -413,6 +426,12 @@ google::protobuf::Value Value::MakeValueProto(PgNumeric n) {
return v;
}

google::protobuf::Value Value::MakeValueProto(PgOid n) {
google::protobuf::Value v;
v.set_string_value(std::to_string(static_cast<std::uint64_t>(n)));
return v;
}

google::protobuf::Value Value::MakeValueProto(Timestamp ts) {
google::protobuf::Value v;
v.set_string_value(spanner_internal::TimestampToRFC3339(ts));
Expand Down Expand Up @@ -562,6 +581,14 @@ StatusOr<PgNumeric> Value::GetValue(PgNumeric const&,
return *decoded;
}

StatusOr<PgOid> Value::GetValue(PgOid const&, google::protobuf::Value const& pv,
google::spanner::v1::Type const&) {
if (pv.kind_case() != google::protobuf::Value::kStringValue) {
return Status(StatusCode::kUnknown, "missing OID");
}
return PgOid(std::stoull(pv.string_value()));
}

StatusOr<Timestamp> Value::GetValue(Timestamp,
google::protobuf::Value const& pv,
google::spanner::v1::Type const&) {
Expand Down
9 changes: 9 additions & 0 deletions google/cloud/spanner/value.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "google/cloud/spanner/internal/tuple_utils.h"
#include "google/cloud/spanner/json.h"
#include "google/cloud/spanner/numeric.h"
#include "google/cloud/spanner/oid.h"
#include "google/cloud/spanner/timestamp.h"
#include "google/cloud/spanner/version.h"
#include "google/cloud/internal/throw_delegate.h"
Expand Down Expand Up @@ -68,6 +69,7 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
* JSONB | `google::cloud::spanner::JsonB`
* NUMERIC | `google::cloud::spanner::Numeric`
* NUMERIC(PG) | `google::cloud::spanner::PgNumeric`
* OID(PG) | `google::cloud::spanner::PgOid`
* TIMESTAMP | `google::cloud::spanner::Timestamp`
* DATE | `absl::CivilDay`
* ARRAY | `std::vector<T>` // [1]
Expand Down Expand Up @@ -201,6 +203,8 @@ class Value {
/// @copydoc Value(bool)
explicit Value(PgNumeric v) : Value(PrivateConstructor{}, std::move(v)) {}
/// @copydoc Value(bool)
explicit Value(PgOid v) : Value(PrivateConstructor{}, std::move(v)) {}
/// @copydoc Value(bool)
explicit Value(Timestamp v) : Value(PrivateConstructor{}, std::move(v)) {}
/// @copydoc Value(bool)
explicit Value(CommitTimestamp v)
Expand Down Expand Up @@ -372,6 +376,7 @@ class Value {
static bool TypeProtoIs(JsonB const&, google::spanner::v1::Type const&);
static bool TypeProtoIs(Numeric const&, google::spanner::v1::Type const&);
static bool TypeProtoIs(PgNumeric const&, google::spanner::v1::Type const&);
static bool TypeProtoIs(PgOid const&, google::spanner::v1::Type const&);
template <typename T>
static bool TypeProtoIs(absl::optional<T>,
google::spanner::v1::Type const& type) {
Expand Down Expand Up @@ -421,6 +426,7 @@ class Value {
static google::spanner::v1::Type MakeTypeProto(JsonB const&);
static google::spanner::v1::Type MakeTypeProto(Numeric const&);
static google::spanner::v1::Type MakeTypeProto(PgNumeric const&);
static google::spanner::v1::Type MakeTypeProto(PgOid const&);
static google::spanner::v1::Type MakeTypeProto(Timestamp);
static google::spanner::v1::Type MakeTypeProto(CommitTimestamp);
static google::spanner::v1::Type MakeTypeProto(absl::CivilDay);
Expand Down Expand Up @@ -484,6 +490,7 @@ class Value {
static google::protobuf::Value MakeValueProto(JsonB j);
static google::protobuf::Value MakeValueProto(Numeric n);
static google::protobuf::Value MakeValueProto(PgNumeric n);
static google::protobuf::Value MakeValueProto(PgOid n);
static google::protobuf::Value MakeValueProto(Timestamp ts);
static google::protobuf::Value MakeValueProto(CommitTimestamp ts);
static google::protobuf::Value MakeValueProto(absl::CivilDay d);
Expand Down Expand Up @@ -555,6 +562,8 @@ class Value {
static StatusOr<PgNumeric> GetValue(PgNumeric const&,
google::protobuf::Value const&,
google::spanner::v1::Type const&);
static StatusOr<PgOid> GetValue(PgOid const&, google::protobuf::Value const&,
google::spanner::v1::Type const&);
static StatusOr<Timestamp> GetValue(Timestamp, google::protobuf::Value const&,
google::spanner::v1::Type const&);
static StatusOr<CommitTimestamp> GetValue(CommitTimestamp,
Expand Down
40 changes: 40 additions & 0 deletions google/cloud/spanner/value_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ TEST(Value, Equality) {
{Value(JsonB("42")), Value(JsonB("true"))},
{Value(MakeNumeric(0).value()), Value(MakeNumeric(1).value())},
{Value(MakePgNumeric(0).value()), Value(MakePgNumeric(1).value())},
{Value(PgOid(200)), Value(PgOid(999))},
{Value(absl::CivilDay(1970, 1, 1)), Value(absl::CivilDay(2020, 3, 15))},
{Value(std::vector<double>{1.2, 3.4}),
Value(std::vector<double>{4.5, 6.7})},
Expand Down Expand Up @@ -753,6 +754,23 @@ TEST(Value, ProtoConversionNumeric) {
}
}

TEST(Value, ProtoConversionPgOid) {
for (auto const& x : std::vector<PgOid>{
PgOid(0),
PgOid(200),
PgOid(999),
}) {
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::INT64, p.first.code());
EXPECT_EQ(google::spanner::v1::TypeAnnotationCode::PG_OID,
p.first.type_annotation());
EXPECT_EQ(std::to_string(static_cast<std::uint64_t>(x)),
p.second.string_value());
}
}

TEST(Value, ProtoConversionTimestamp) {
for (auto ts : TestTimes()) {
Value const v(ts);
Expand Down Expand Up @@ -975,6 +993,21 @@ TEST(Value, GetBadNumeric) {
EXPECT_THAT(v.get<std::int64_t>(), Not(IsOk()));
}

TEST(Value, GetBadPgOid) {
Value v(PgOid(1));
ClearProtoKind(v);
EXPECT_THAT(v.get<std::string>(), Not(IsOk()));

SetProtoKind(v, google::protobuf::NULL_VALUE);
EXPECT_THAT(v.get<PgOid>(), Not(IsOk()));

SetProtoKind(v, true);
EXPECT_THAT(v.get<PgOid>(), Not(IsOk()));

SetProtoKind(v, 0.0);
EXPECT_THAT(v.get<PgOid>(), Not(IsOk()));
}

TEST(Value, GetBadInt) {
Value v(42);
ClearProtoKind(v);
Expand Down Expand Up @@ -1164,6 +1197,7 @@ TEST(Value, OutputStream) {
{Value(JsonB("true")), "true", normal},
{Value(MakeNumeric(1234567890).value()), "1234567890", normal},
{Value(MakePgNumeric(1234567890).value()), "1234567890", normal},
{Value(PgOid(1234567890)), "1234567890", normal},
{Value(absl::CivilDay()), "1970-01-01", normal},
{Value(Timestamp()), "1970-01-01T00:00:00Z", normal},

Expand All @@ -1189,6 +1223,7 @@ TEST(Value, OutputStream) {
{MakeNullValue<Json>(), "NULL", normal},
{MakeNullValue<JsonB>(), "NULL", normal},
{MakeNullValue<Numeric>(), "NULL", normal},
{MakeNullValue<PgOid>(), "NULL", normal},
{MakeNullValue<absl::CivilDay>(), "NULL", normal},
{MakeNullValue<Timestamp>(), "NULL", normal},

Expand Down Expand Up @@ -1343,6 +1378,11 @@ TEST(Value, OutputStreamMatchesT) {
StreamMatchesValueStream(MakePgNumeric(42).value());
StreamMatchesValueStream(MakePgNumeric("NaN").value());

// PgOid
StreamMatchesValueStream(PgOid(999));
StreamMatchesValueStream(PgOid(42));
StreamMatchesValueStream(PgOid(0));

// Date
StreamMatchesValueStream(absl::CivilDay(1, 1, 1));
StreamMatchesValueStream(absl::CivilDay());
Expand Down

0 comments on commit f7c9022

Please sign in to comment.