From ccd6b9de25d6ca0406372f25a5f135ce836e2a8d Mon Sep 17 00:00:00 2001 From: Bradley White <14679271+devbww@users.noreply.github.com> Date: Wed, 24 Apr 2024 23:09:23 -0400 Subject: [PATCH 1/4] feat(spanner): add a representation for the Spanner INTERVAL Add a C++ type, `spanner::Interval`, to model the Spanner `INTERVAL`: the difference between two date/time values. Basic operations and string conversions are provided. Extra operations involving `Date` and `Timestamp` values are upcoming. Integration of `Interval` with `spanner::Value` awaits the publication of the `google.spanner.v1.TypeCode::INTERVAL` enum value. --- google/cloud/spanner/CMakeLists.txt | 3 + .../spanner/google_cloud_cpp_spanner.bzl | 2 + google/cloud/spanner/interval.cc | 367 ++++++++++++++++++ google/cloud/spanner/interval.h | 136 +++++++ google/cloud/spanner/interval_test.cc | 205 ++++++++++ .../spanner/spanner_client_unit_tests.bzl | 1 + 6 files changed, 714 insertions(+) create mode 100644 google/cloud/spanner/interval.cc create mode 100644 google/cloud/spanner/interval.h create mode 100644 google/cloud/spanner/interval_test.cc diff --git a/google/cloud/spanner/CMakeLists.txt b/google/cloud/spanner/CMakeLists.txt index 728b78a125bf0..3f6ca16b31184 100644 --- a/google/cloud/spanner/CMakeLists.txt +++ b/google/cloud/spanner/CMakeLists.txt @@ -171,6 +171,8 @@ add_library( internal/transaction_impl.cc internal/transaction_impl.h internal/tuple_utils.h + interval.cc + interval.h json.h keys.cc keys.h @@ -481,6 +483,7 @@ function (spanner_client_define_tests) internal/status_utils_test.cc internal/transaction_impl_test.cc internal/tuple_utils_test.cc + interval_test.cc json_test.cc keys_test.cc mutations_test.cc diff --git a/google/cloud/spanner/google_cloud_cpp_spanner.bzl b/google/cloud/spanner/google_cloud_cpp_spanner.bzl index f80303ed18afd..6e6e882317f5e 100644 --- a/google/cloud/spanner/google_cloud_cpp_spanner.bzl +++ b/google/cloud/spanner/google_cloud_cpp_spanner.bzl @@ -93,6 +93,7 @@ google_cloud_cpp_spanner_hdrs = [ "internal/status_utils.h", "internal/transaction_impl.h", "internal/tuple_utils.h", + "interval.h", "json.h", "keys.h", "mutations.h", @@ -183,6 +184,7 @@ google_cloud_cpp_spanner_srcs = [ "internal/spanner_tracing_stub.cc", "internal/status_utils.cc", "internal/transaction_impl.cc", + "interval.cc", "keys.cc", "mutations.cc", "numeric.cc", diff --git a/google/cloud/spanner/interval.cc b/google/cloud/spanner/interval.cc new file mode 100644 index 0000000000000..8626a187e8642 --- /dev/null +++ b/google/cloud/spanner/interval.cc @@ -0,0 +1,367 @@ +// Copyright 2024 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. + +#include "google/cloud/spanner/interval.h" +#include "google/cloud/internal/absl_str_cat_quiet.h" +#include "google/cloud/internal/make_status.h" +#include "absl/strings/ascii.h" +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/string_view.h" +#include "absl/strings/strip.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace google { +namespace cloud { +namespace spanner { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN + +namespace { + +// Interval factories for quantity units. +struct { + absl::string_view name; + std::function factory; +} const kUnitFactories[] = { + // "days" is first so that it is chosen when unit is empty. + {"days", [](auto n) { return Interval(0, 0, n); }}, + + // "minutes" comes before other "m"s so it is chosen for, say, "6m". + {"minutes", [](auto n) { return Interval(absl::Minutes(n)); }}, + + {"microseconds", [](auto n) { return Interval(absl::Microseconds(n)); }}, + {"milliseconds", [](auto n) { return Interval(absl::Milliseconds(n)); }}, + {"seconds", [](auto n) { return Interval(absl::Seconds(n)); }}, + {"hours", [](auto n) { return Interval(absl::Hours(n)); }}, + {"weeks", [](auto n) { return Interval(0, 0, n * 7); }}, + {"months", [](auto n) { return Interval(0, n, 0); }}, + {"years", [](auto n) { return Interval(n, 0, 0); }}, + {"decades", [](auto n) { return Interval(n * 10, 0, 0); }}, + {"century", [](auto n) { return Interval(n * 100, 0, 0); }}, + {"centuries", [](auto n) { return Interval(n * 100, 0, 0); }}, + {"millennium", [](auto n) { return Interval(n * 1000, 0, 0); }}, + {"millennia", [](auto n) { return Interval(n * 1000, 0, 0); }}, +}; + +// Interval comparison is done by logically combining the fields into a +// single value by assuming that 1 month == 30 days and 1 day == 24 hours, +// and by rounding to a microsecond boundary. +void Normalize(std::int64_t& months, std::int64_t& days, + absl::Duration& offset) { + auto const precision = absl::Microseconds(1); + offset += ((offset < absl::ZeroDuration()) ? -precision : precision) / 2, + offset = absl::Trunc(offset, precision); + days += absl::IDivDuration(offset, absl::Hours(24), &offset); + if (offset < absl::ZeroDuration()) { + offset += absl::Hours(24); + days -= 1; + } + months += days / 30; + days %= 30; + if (days < 0) { + days += 30; + months -= 1; + } +} + +template