Skip to content

Commit

Permalink
feat: BingTile cast to/from Bigint (facebookincubator#12505)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebookincubator#12505

This just implements the casting functionality.  Methods
to actually access the properties of the BingTile will come later.

Reviewed By: bikramSingh91

Differential Revision: D69942803

fbshipit-source-id: cab6f27203b93f3908740f4dd94c5610bf15d9de
  • Loading branch information
jagill authored and facebook-github-bot committed Mar 6, 2025
1 parent 0e3e0d2 commit 9a5946a
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 3 deletions.
60 changes: 60 additions & 0 deletions velox/functions/prestosql/tests/BingTileCastTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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
*
* http://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 "velox/common/base/tests/GTestUtils.h"
#include "velox/functions/prestosql/tests/utils/FunctionBaseTest.h"
#include "velox/functions/prestosql/types/BingTileType.h"

namespace facebook::velox::functions::prestosql {

namespace {

class BingTileCastTest : public functions::test::FunctionBaseTest {
protected:
std::optional<int64_t> castToBigintRoundtrip(std::optional<int64_t> input) {
std::optional<int64_t> result =
evaluateOnce<int64_t>("cast(cast(c0 as bingtile) as bigint)", input);
return result;
}
};

TEST_F(BingTileCastTest, roundtrip) {
auto assertRoundtrip = [this](int32_t x, int32_t y, int8_t zoom) {
std::optional<int64_t> result_bigint = castToBigintRoundtrip(
(int64_t)BingTileType::bingTileCoordsToInt(x, y, zoom));
ASSERT_TRUE(result_bigint.has_value());
uint64_t tile = static_cast<uint64_t>(result_bigint.value());
ASSERT_TRUE(BingTileType::isBingTileIntValid(tile));
ASSERT_EQ(BingTileType::bingTileZoom(tile), zoom);
ASSERT_EQ(BingTileType::bingTileX(tile), x);
ASSERT_EQ(BingTileType::bingTileY(tile), y);
};

assertRoundtrip(0, 0, 0);
assertRoundtrip(123, 111, 7);
}

TEST_F(BingTileCastTest, errors) {
VELOX_ASSERT_USER_THROW(
evaluateOnce<int64_t>(
"cast(c0 as bingtile)",
std::optional((int64_t)BingTileType::bingTileCoordsToInt(0, 1, 0))),
"Y coordinate 1 is greater than max coordinate 0 at zoom 0");
}

} // namespace

} // namespace facebook::velox::functions::prestosql
1 change: 1 addition & 0 deletions velox/functions/prestosql/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ add_executable(
ArrayUnionTest.cpp
ArgTypesGeneratorTest.cpp
BinaryFunctionsTest.cpp
BingTileCastTest.cpp
BitwiseTest.cpp
CardinalityTest.cpp
CeilFloorTest.cpp
Expand Down
98 changes: 96 additions & 2 deletions velox/functions/prestosql/types/BingTileRegistration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,107 @@

#include "velox/functions/prestosql/types/BingTileRegistration.h"

#include "velox/expression/CastExpr.h"
#include "velox/functions/prestosql/types/BingTileType.h"

namespace facebook::velox {

namespace {

class BingTileCastOperator : public exec::CastOperator {
public:
static const std::shared_ptr<const CastOperator>& get() {
static const std::shared_ptr<const CastOperator> instance{
new BingTileCastOperator()};

return instance;
}

bool isSupportedFromType(const TypePtr& other) const override {
switch (other->kind()) {
case TypeKind::BIGINT:
return true;
default:
return false;
}
}

bool isSupportedToType(const TypePtr& other) const override {
switch (other->kind()) {
case TypeKind::BIGINT:
return true;
default:
return false;
}
}

void castTo(
const BaseVector& input,
exec::EvalCtx& context,
const SelectivityVector& rows,
const TypePtr& resultType,
VectorPtr& result) const override {
context.ensureWritable(rows, resultType, result);
if (input.typeKind() == TypeKind::BIGINT) {
auto* bingTileResult = result->asFlatVector<int64_t>();
bingTileResult->clearNulls(rows);
const auto inputVector = input.asChecked<SimpleVector<int64_t>>();
// The values we just copy, and we will set errors later
bingTileResult->copy(inputVector, rows, nullptr);

// These are simple ops on int64s. We can vectorize for performance.
// For example, we could:
// 1. check validity isBingTileIntValid in parallel (branchless SIMD)
// 2. bitwise-OR valid flags with `rows` selectivity (branchless SIMD)
// 3. add an error to any false rows (uncommon)
context.applyToSelectedNoThrow(rows, [&](auto row) {
const uint64_t tileInt = bingTileResult->valueAt(row);
if (FOLLY_UNLIKELY(!BingTileType::isBingTileIntValid(tileInt))) {
std::optional<std::string> reasonOpt =
BingTileType::bingTileInvalidReason(tileInt);
if (reasonOpt.has_value()) {
context.setStatus(row, Status::UserError(*reasonOpt));
} else {
// isBingTileIntValid and bingTileInvalidReason have gotten out of
// sync
context.setStatus(
row,
Status::UnknownError(
"Unknown BingTile validation error for tile %ld: this is a bug in Velox",
tileInt));
}
}
});

} else {
VELOX_UNSUPPORTED(
"Cast from {} to BINGTILE not supported", input.type()->toString());
}
}

void castFrom(
const BaseVector& input,
exec::EvalCtx& context,
const SelectivityVector& rows,
const TypePtr& resultType,
VectorPtr& result) const override {
context.ensureWritable(rows, resultType, result);

if (resultType->kind() == TypeKind::BIGINT) {
auto* flatResult = result->asChecked<FlatVector<int64_t>>();
flatResult->clearNulls(rows);
const auto inputVector = input.asChecked<SimpleVector<int64_t>>();
flatResult->copy(inputVector, rows, nullptr);
} else {
VELOX_UNSUPPORTED(
"Cast from BINGTILE to {} not yet supported", resultType->toString());
}
}

private:
BingTileCastOperator() = default;
};

class BingTileTypeFactories : public CustomTypeFactories {
public:
velox::TypePtr getType(
Expand All @@ -30,9 +125,8 @@ class BingTileTypeFactories : public CustomTypeFactories {
return BINGTILE();
}

// TODO: Provide casting to/from BIGINT
exec::CastOperatorPtr getCastOperator() const override {
return nullptr;
return BingTileCastOperator::get();
}

AbstractInputGeneratorPtr getInputGenerator(
Expand Down
2 changes: 1 addition & 1 deletion velox/functions/prestosql/types/BingTileRegistration.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ namespace facebook::velox {

void registerBingTileType();

}
} // namespace facebook::velox
85 changes: 85 additions & 0 deletions velox/functions/prestosql/types/BingTileType.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,91 @@ class BingTileType : public BigintType {
obj["type"] = name();
return obj;
}

static constexpr uint8_t kBingTileVersion = 0;
static constexpr uint8_t kBingTileMaxZoomLevel = 23;
static constexpr uint8_t kBingTileZoomBitWidth = 5;
static constexpr uint8_t kBingTileVersionOffset = 63 - kBingTileZoomBitWidth;
static constexpr uint8_t kBingTileZoomOffset = 31 - kBingTileZoomBitWidth;
static constexpr uint64_t kBits23Mask = (1 << 24) - 1;
static constexpr uint64_t kBits5Mask = (1 << 6) - 1;

static inline uint64_t
bingTileCoordsToInt(uint32_t x, uint32_t y, uint8_t zoom) {
uint64_t tile = (static_cast<uint64_t>(zoom) << kBingTileZoomOffset) |
(static_cast<uint64_t>(x) << 32) | static_cast<uint64_t>(y);
return tile;
}

/// Returns the version of the tile (as uint64)
static inline uint8_t bingTileVersion(uint64_t tile) {
return (tile >> kBingTileVersionOffset) & kBits5Mask;
}

/// Returns the zoom of the tile (as uint64)
static inline uint8_t bingTileZoom(uint64_t tile) {
return (tile >> kBingTileZoomOffset) & kBits5Mask;
}

/// Returns the x-coordinate of the tile (as uint64)
static inline uint32_t bingTileX(uint64_t tile) {
return (tile >> 32) & kBits23Mask;
}

/// Returns the y-coordinate of the tile (as uint64)
static inline uint32_t bingTileY(uint64_t tile) {
return tile & kBits23Mask;
}

/// Returns true if the tile (as uint64) is valid
static inline bool isBingTileIntValid(uint64_t tile) {
uint8_t zoom = bingTileZoom(tile);
uint64_t coordinateBound = 1 << zoom;
// Using bitwise & so that it's branchless and the data
// can be prefetched and the ops pipelined.
// Linter wants the bools cast to uint8 for bitwise ops.
return (uint8_t)(bingTileVersion(tile) == kBingTileVersion) &
(uint8_t)(zoom <= kBingTileMaxZoomLevel) &
(uint8_t)(bingTileX(tile) < coordinateBound) &
(uint8_t)(bingTileY(tile) < coordinateBound);
}

static std::optional<std::string> bingTileInvalidReason(uint64_t tile) {
// TODO?: We are duplicating some logic in isBingTileIntValid; maybe we
// should extract?

uint8_t version = BingTileType::bingTileVersion(tile);
if (version != BingTileType::kBingTileVersion) {
return fmt::format("Version {} not supported", version);
}

uint8_t zoom = BingTileType::bingTileZoom(tile);
if (zoom > BingTileType::kBingTileMaxZoomLevel) {
return fmt::format(
"Zoom {} is greater than max zoom {}",
zoom,
BingTileType::kBingTileMaxZoomLevel);
}

uint64_t coordinateBound = 1 << zoom;

if (BingTileType::bingTileX(tile) >= coordinateBound) {
return fmt::format(
"X coordinate {} is greater than max coordinate {} at zoom {}",
BingTileType::bingTileX(tile),
coordinateBound - 1,
zoom);
}
if (BingTileType::bingTileY(tile) >= coordinateBound) {
return fmt::format(
"Y coordinate {} is greater than max coordinate {} at zoom {}",
BingTileType::bingTileY(tile),
coordinateBound - 1,
zoom);
}

return std::nullopt;
}
};

inline bool isBingTileType(const TypePtr& type) {
Expand Down
40 changes: 40 additions & 0 deletions velox/functions/prestosql/types/tests/BingTileTypeTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,44 @@ TEST_F(BingTileTypeTest, serde) {
testTypeSerde(BINGTILE());
}

TEST_F(BingTileTypeTest, packing) {
uint64_t tileX123Y111Z7 = BingTileType::bingTileCoordsToInt(123, 111, 7);
ASSERT_TRUE(BingTileType::isBingTileIntValid(tileX123Y111Z7));
ASSERT_EQ(BingTileType::bingTileVersion(tileX123Y111Z7), 0);
ASSERT_EQ(BingTileType::bingTileZoom(tileX123Y111Z7), 7);
ASSERT_EQ(BingTileType::bingTileX(tileX123Y111Z7), 123);
ASSERT_EQ(BingTileType::bingTileY(tileX123Y111Z7), 111);

uint64_t tileZ0 = BingTileType::bingTileCoordsToInt(0, 0, 0);
ASSERT_TRUE(BingTileType::isBingTileIntValid(tileZ0));
ASSERT_EQ(BingTileType::bingTileVersion(tileZ0), 0);
ASSERT_EQ(BingTileType::bingTileZoom(tileZ0), 0);
ASSERT_EQ(BingTileType::bingTileX(tileZ0), 0);
ASSERT_EQ(BingTileType::bingTileY(tileZ0), 0);

// Error cases
uint64_t tileV1 = (uint64_t)1 << (63 - 5);
ASSERT_FALSE(BingTileType::isBingTileIntValid(tileV1));
ASSERT_EQ(
BingTileType::bingTileInvalidReason(tileV1), "Version 1 not supported");

uint64_t tileZ24 = BingTileType::bingTileCoordsToInt(0, 0, 24);
ASSERT_FALSE(BingTileType::isBingTileIntValid(tileZ24));
ASSERT_EQ(
BingTileType::bingTileInvalidReason(tileZ24),
"Zoom 24 is greater than max zoom 23");

uint64_t tileX0Y1Z0 = BingTileType::bingTileCoordsToInt(0, 1, 0);
ASSERT_FALSE(BingTileType::isBingTileIntValid(tileX0Y1Z0));
ASSERT_EQ(
BingTileType::bingTileInvalidReason(tileX0Y1Z0),
"Y coordinate 1 is greater than max coordinate 0 at zoom 0");

uint64_t tileX256Y1Z8 = BingTileType::bingTileCoordsToInt(256, 1, 8);
ASSERT_FALSE(BingTileType::isBingTileIntValid(tileX256Y1Z8));
ASSERT_EQ(
BingTileType::bingTileInvalidReason(tileX256Y1Z8),
"X coordinate 256 is greater than max coordinate 255 at zoom 8");
}

} // namespace facebook::velox::test

0 comments on commit 9a5946a

Please sign in to comment.