From 7abd722bb9bd9d5dbdddf84bf9beb1e9f60b0561 Mon Sep 17 00:00:00 2001 From: Zacharias Knudsen Date: Mon, 24 Aug 2020 16:00:29 +0200 Subject: [PATCH] WIP: GiST support --- CHANGELOG.md | 7 + h3/sql/install/13-opclass_gist.sql | 52 +++ h3/sql/updates/h3--3.7.1--unreleased.sql | 37 ++ h3/src/include/extension.in.h | 51 +-- h3/src/lib/opclass_gist.c | 408 +++++++++++++++++++++++ h3/src/lib/upstream/h3Index.h | 222 ++++++++++++ h3/test/expected/opclass_gist.out | 30 ++ h3/test/sql/opclass_gist.sql | 26 ++ 8 files changed, 808 insertions(+), 25 deletions(-) create mode 100644 h3/sql/install/13-opclass_gist.sql create mode 100644 h3/src/lib/opclass_gist.c create mode 100644 h3/src/lib/upstream/h3Index.h create mode 100644 h3/test/expected/opclass_gist.out create mode 100644 h3/test/sql/opclass_gist.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index e39c2e44..a0eff1b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,8 +26,11 @@ avoid adding features or APIs which do not map onto the Click to see more. +- Add `gist` operator class (see [#42], thanks [@abelvm]) + + ## [3.7.1] - 2021-06-23 - Update `h3` core library to `v3.7.1` @@ -178,7 +181,11 @@ avoid adding features or APIs which do not map onto the [#31]: https://github.com/bytesandbrains/h3-pg/pull/31 [#37]: https://github.com/bytesandbrains/h3-pg/issues/37 [#38]: https://github.com/bytesandbrains/h3-pg/issues/38 +<<<<<<< HEAD [#41]: https://github.com/bytesandbrains/h3-pg/issues/41 +======= +[#42]: https://github.com/bytesandbrains/h3-pg/issues/42 +>>>>>>> 371058d (WIP: GiST support) [@abelvm]: https://github.com/AbelVM [@komzpa]: https://github.com/Komzpa [@kmacdough]: https://github.com/kmacdough diff --git a/h3/sql/install/13-opclass_gist.sql b/h3/sql/install/13-opclass_gist.sql new file mode 100644 index 00000000..fc275d37 --- /dev/null +++ b/h3/sql/install/13-opclass_gist.sql @@ -0,0 +1,52 @@ +/* + * Copyright 2019-2020 Bytes & Brains + * + * 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. + */ + +-- ---------- ---------- ---------- ---------- ---------- ---------- ---------- +-- GiST Operator Class (opclass_gist.c) +-- ---------- ---------- ---------- ---------- ---------- ---------- ---------- + +CREATE OR REPLACE FUNCTION h3index_gist_consistent(internal, h3index, smallint, oid, internal) RETURNS boolean + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_union(internal, internal) RETURNS h3index + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_compress(internal) RETURNS internal + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_decompress(internal) RETURNS internal + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_penalty(internal, internal, internal) RETURNS internal + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_picksplit(internal, internal) RETURNS internal + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_same(h3index, h3index, internal) RETURNS internal + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_distance(internal, h3index, smallint, oid, internal) RETURNS float8 + AS 'h3' LANGUAGE C STRICT; + +CREATE OPERATOR CLASS gist_h3index_ops DEFAULT FOR TYPE h3index USING gist AS + OPERATOR 3 && , + OPERATOR 6 = , + OPERATOR 7 @> , + OPERATOR 8 <@ , + OPERATOR 15 <-> (h3index, h3index) FOR ORDER BY integer_ops, + + FUNCTION 1 h3index_gist_consistent(internal, h3index, smallint, oid, internal), + FUNCTION 2 h3index_gist_union(internal, internal), +-- FUNCTION 3 h3index_gist_compress(internal), +-- FUNCTION 4 h3index_gist_decompress(internal), + FUNCTION 5 h3index_gist_penalty(internal, internal, internal), + FUNCTION 6 h3index_gist_picksplit(internal, internal), + FUNCTION 7 h3index_gist_same(h3index, h3index, internal), + FUNCTION 8 (h3index, h3index) h3index_gist_distance(internal, h3index, smallint, oid, internal); diff --git a/h3/sql/updates/h3--3.7.1--unreleased.sql b/h3/sql/updates/h3--3.7.1--unreleased.sql index ddf9e131..994bcc5b 100644 --- a/h3/sql/updates/h3--3.7.1--unreleased.sql +++ b/h3/sql/updates/h3--3.7.1--unreleased.sql @@ -16,3 +16,40 @@ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "ALTER EXTENSION h3 UPDATE TO 'unreleased'" to load this file. \quit + +-- ---------- ---------- ---------- ---------- ---------- ---------- ---------- +-- GiST Operator Class (opclass_gist.c) +-- ---------- ---------- ---------- ---------- ---------- ---------- ---------- + +CREATE OR REPLACE FUNCTION h3index_gist_consistent(internal, h3index, smallint, oid, internal) RETURNS boolean + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_union(internal, internal) RETURNS h3index + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_compress(internal) RETURNS internal + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_decompress(internal) RETURNS internal + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_penalty(internal, internal, internal) RETURNS internal + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_picksplit(internal, internal) RETURNS internal + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_same(h3index, h3index, internal) RETURNS internal + AS 'h3' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION h3index_gist_distance(internal, h3index, smallint, oid, internal) RETURNS float8 + AS 'h3' LANGUAGE C STRICT; + +CREATE OPERATOR CLASS gist_h3index_ops DEFAULT FOR TYPE h3index USING gist AS + OPERATOR 3 && , + OPERATOR 6 = , + OPERATOR 7 @> , + OPERATOR 8 <@ , + OPERATOR 15 <-> (h3index, h3index) FOR ORDER BY integer_ops, + + FUNCTION 1 h3index_gist_consistent(internal, h3index, smallint, oid, internal), + FUNCTION 2 h3index_gist_union(internal, internal), +-- FUNCTION 3 h3index_gist_compress(internal), +-- FUNCTION 4 h3index_gist_decompress(internal), + FUNCTION 5 h3index_gist_penalty(internal, internal, internal), + FUNCTION 6 h3index_gist_picksplit(internal, internal), + FUNCTION 7 h3index_gist_same(h3index, h3index, internal), + FUNCTION 8 (h3index, h3index) h3index_gist_distance(internal, h3index, smallint, oid, internal); diff --git a/h3/src/include/extension.in.h b/h3/src/include/extension.in.h index 5913b346..d4fd04e0 100644 --- a/h3/src/include/extension.in.h +++ b/h3/src/include/extension.in.h @@ -19,14 +19,14 @@ #include // Main H3 include -void _PG_init(void); +void _PG_init(void); extern bool h3_guc_strict; typedef struct { - H3Index *indices; - int *distances; -} hexDistanceTuple; + H3Index *indices; + int *distances; +} hexDistanceTuple; #define MAX_H3_RES 15 @@ -41,9 +41,9 @@ typedef struct */ #ifdef USE_FLOAT8_BYVAL -#define DatumGetH3Index(X) ((H3Index) (X)) +#define DatumGetH3Index(X) ((H3Index)(X)) #else -#define DatumGetH3Index(X) (* ((H3Index *) DatumGetPointer(X))) +#define DatumGetH3Index(X) (*((H3Index *)DatumGetPointer(X))) #endif /* @@ -55,9 +55,9 @@ typedef struct */ #ifdef USE_FLOAT8_BYVAL -#define H3IndexGetDatum(X) ((Datum) (X)) +#define H3IndexGetDatum(X) ((Datum)(X)) #else -#define H3IndexGetDatum(X) Int64GetDatum((int64) (X)) +#define H3IndexGetDatum(X) Int64GetDatum((int64)(X)) #endif /* Macros for fetching arguments and returning results of h3 index type */ @@ -66,8 +66,8 @@ typedef struct #define PG_RETURN_H3INDEX(x) return H3IndexGetDatum(x) /* helper functions to return sets from user fctx */ -Datum srf_return_h3_indexes_from_user_fctx(PG_FUNCTION_ARGS); -Datum srf_return_h3_index_distances_from_user_fctx(PG_FUNCTION_ARGS); +Datum srf_return_h3_indexes_from_user_fctx(PG_FUNCTION_ARGS); +Datum srf_return_h3_index_distances_from_user_fctx(PG_FUNCTION_ARGS); /* macros to pass on fcinfo to above helpers */ #define SRF_RETURN_H3_INDEXES_FROM_USER_FCTX() \ @@ -75,28 +75,29 @@ Datum srf_return_h3_index_distances_from_user_fctx(PG_FUNCTION_ARGS); #define SRF_RETURN_H3_INDEX_DISTANCES_FROM_USER_FCTX() \ return srf_return_h3_index_distances_from_user_fctx(fcinfo) -#define ASSERT(condition, code, msg, ...) \ - if (0 == (condition)) ereport(ERROR, ( \ - errcode(code), \ - errmsg(msg, ##__VA_ARGS__) \ - )) +#define ASSERT(condition, code, msg, ...) \ + if (0 == (condition)) \ + ereport(ERROR, ( \ + errcode(code), \ + errmsg(msg, ##__VA_ARGS__))) #define ASSERT_EXTERNAL(condition, msg, ...) \ ASSERT(condition, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, msg, ##__VA_ARGS__) -#define ENSURE_TYPEFUNC_COMPOSITE(x) \ - ASSERT( \ - x == TYPEFUNC_COMPOSITE, \ - ERRCODE_INVALID_PARAMETER_VALUE, \ +#define ENSURE_TYPEFUNC_COMPOSITE(x) \ + ASSERT( \ + x == TYPEFUNC_COMPOSITE, \ + ERRCODE_INVALID_PARAMETER_VALUE, \ "Function returning record called in context " \ - "that cannot accept type record" \ - ) + "that cannot accept type record") -#define DEBUG(msg, ...) \ - ereport(ERROR, ( \ - errmsg(msg, ##__VA_ARGS__) \ - )) +#define DEBUG(msg, ...) \ + ereport(ERROR, ( \ + errmsg(msg, ##__VA_ARGS__))) #define DEBUG_H3INDEX(h3index) DEBUG("index: %lx", h3index) +#define PRINT_H3INDEX(h3index) \ + ereport(NOTICE, ( \ + errmsg("index: %lx", h3index))) #endif diff --git a/h3/src/lib/opclass_gist.c b/h3/src/lib/opclass_gist.c new file mode 100644 index 00000000..1dfd584b --- /dev/null +++ b/h3/src/lib/opclass_gist.c @@ -0,0 +1,408 @@ +/* + * Copyright 2019-2020 Bytes & Brains + * + * 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 // Datum, etc. +#include // PG_FUNCTION_ARGS, etc. +#include // making native points +#include // RTOverlapStrategyNumber, etc. +#include // GiST + +#include // Main H3 include +#include "extension.h" +#include "upstream/h3Index.h" + +#define H3_ROOT_INDEX -1 + +#define LOG_NOTICE(X) \ + do \ + { \ + if (false) \ + ereport(NOTICE, (errmsg("[%s]: (%d).", (const char *)__FUNCTION__, (int)X))); \ + } while (0) + +#define debug_func(x) \ + do \ + { \ + if (false) \ + { \ + ereport(NOTICE, ( \ + errmsg("[%s]: Returned nonzero result (%d).", (const char *)__FUNCTION__, (int)x))); \ + } \ + } while (0) + +static int +gist_cmp(H3Index a, H3Index b) +{ + int aRes; + int bRes; + + uint64_t cellMask = (1LL << 45) - 1; /* rightmost 45 bits */ + uint64_t aCell; + uint64_t bCell; + + /* identity */ + if (a == b) + { + return 1; + } + + /* no shared basecell */ + if (H3_GET_BASE_CELL(a) != H3_GET_BASE_CELL(b)) + { + return 0; + } + + aRes = H3_GET_RESOLUTION(a); + bRes = H3_GET_RESOLUTION(b); + + //---- + + H3Index big, sml; + int maxRes; + + /* a contains b */ + if (a == H3_ROOT_INDEX || (aRes < bRes && h3ToParent(b, aRes) == a)) + return 1; + /* a contained by b */ + if (b == H3_ROOT_INDEX || (aRes > bRes && h3ToParent(a, bRes) == b)) + return -1; + + /* no overlap */ + return 0; +} + +/** + * GiST support + */ + +static H3Index +common_ancestor(H3Index a, H3Index b) +{ + int aRes; + int bRes; + int maxRes, bigRes; + uint64_t cellMask = (1LL << 45) - 1; /* rightmost 45 bits */ + uint64_t abCell; + uint64_t mask; + H3Index masked; + + if (a == b) + { + return a; + } + + /* do not even share the basecell */ + if (H3_GET_BASE_CELL(a) != H3_GET_BASE_CELL(b)) + { + return H3_ROOT_INDEX; + } + + aRes = H3_GET_RESOLUTION(a); + bRes = H3_GET_RESOLUTION(b); + bigRes = (aRes > bRes) ? aRes : bRes; + for (int i = bigRes; i > 0; i--) // iterate back basecells + { + if (h3ToParent(a, i) == h3ToParent(b, i)) + return h3ToParent(a, i); + } + + LOG_NOTICE(0); + + return H3_ROOT_INDEX; +} + +/** + * The GiST Union method for H3 indexes + * returns the minimal H3 index that encloses all the entries in entryvec + */ +PG_FUNCTION_INFO_V1(h3index_gist_union); +Datum + h3index_gist_union(PG_FUNCTION_ARGS) +{ + LOG_NOTICE(0); + + GistEntryVector *entryvec = (GistEntryVector *)PG_GETARG_POINTER(0); + GISTENTRY *entries = entryvec->vector; + int n = entryvec->n; + H3Index out = DatumGetH3Index(entries[0].key); + H3Index tmp; + + // build smallest common parent + for (int i = 1; i < n; i++) + { + tmp = DatumGetH3Index(entries[i].key); + out = common_ancestor(out, tmp); + } + + debug_func(n); + + PG_RETURN_H3INDEX(out); +} + +/** + * The GiST Consistent method for H3 indexes + * Should return false if for all data items x below entry, + * the predicate x op query == false, where op is the operation + * corresponding to strategy in the pg_amop table. + */ +PG_FUNCTION_INFO_V1(h3index_gist_consistent); +Datum + h3index_gist_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *)PG_GETARG_POINTER(0); + H3Index query = PG_GETARG_H3INDEX(1); + StrategyNumber strategy = (StrategyNumber)PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *)PG_GETARG_POINTER(4); + H3Index key = DatumGetH3Index(entry->key); + + /* When the result is true, a recheck flag must also be returned. */ + *recheck = true; + + switch (strategy) + { + case RTOverlapStrategyNumber: + PG_RETURN_BOOL(gist_cmp(key, query) != 0); + case RTContainsStrategyNumber: + LOG_NOTICE(4); + PG_RETURN_BOOL(gist_cmp(key, query) > 0); + case RTContainedByStrategyNumber: + LOG_NOTICE(5); + if (GIST_LEAF(entry)) + { + PG_RETURN_BOOL(gist_cmp(key, query) < 0); + } + LOG_NOTICE(6); + LOG_NOTICE(gist_cmp(key, query)); + /* internal nodes, just check if we overlap */ + PG_RETURN_BOOL(gist_cmp(key, query) != 0); + default: + ereport(ERROR, ( + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("unrecognized StrategyNumber: %d", strategy))); + } +} + +/** + * GiST Compress and Decompress methods for H3Indexes + * do not do anything. We *could* use compact/uncompact? + */ +PG_FUNCTION_INFO_V1(h3index_gist_compress); +Datum + h3index_gist_compress(PG_FUNCTION_ARGS) +{ + LOG_NOTICE(0); + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); +} + +PG_FUNCTION_INFO_V1(h3index_gist_decompress); +Datum + h3index_gist_decompress(PG_FUNCTION_ARGS) +{ + LOG_NOTICE(0); + PG_RETURN_POINTER(PG_GETARG_POINTER(0)); +} + +/* +** The GiST Penalty method for H3 indexes +** We use change resolution as our penalty metric +*/ +PG_FUNCTION_INFO_V1(h3index_gist_penalty); +Datum + h3index_gist_penalty(PG_FUNCTION_ARGS) +{ + LOG_NOTICE(0); + GISTENTRY *origentry = (GISTENTRY *)PG_GETARG_POINTER(0); + GISTENTRY *newentry = (GISTENTRY *)PG_GETARG_POINTER(1); + float *penalty = (float *)PG_GETARG_POINTER(2); + + H3Index orig = DatumGetH3Index(origentry->key); + H3Index new = DatumGetH3Index(newentry->key); + + H3Index ancestor = common_ancestor(orig, new); + + *penalty = (float)h3GetResolution(orig) - h3GetResolution(ancestor); + + debug_func(*penalty); + + PG_RETURN_POINTER(penalty); +} + +/** + * The GiST PickSplit method for H3 indexes + * + * given a full page; + * split into two new pages, each with a new + */ +PG_FUNCTION_INFO_V1(h3index_gist_picksplit); +Datum + h3index_gist_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *)PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *)PG_GETARG_POINTER(1); + + LOG_NOTICE(entryvec->n); + OffsetNumber maxoff = entryvec->n - 1; + GISTENTRY *ent = entryvec->vector; + int i, + nbytes; + OffsetNumber *left, + *right; + H3Index tmp_union, + unionL, + unionR; + GISTENTRY **raw_entryvec; + + bool lset = false, rset = false; + + nbytes = (maxoff + 1) * sizeof(OffsetNumber); + + v->spl_left = (OffsetNumber *)palloc(nbytes); + left = v->spl_left; + v->spl_nleft = 0; + + v->spl_right = (OffsetNumber *)palloc(nbytes); + right = v->spl_right; + v->spl_nright = 0; + + unionL = NULL; + unionR = NULL; + + /* Initialize the raw entry vector. */ + raw_entryvec = (GISTENTRY **)malloc(entryvec->n * sizeof(void *)); + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + raw_entryvec[i] = &(ent[i]); + + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + LOG_NOTICE(i); + int real_index = raw_entryvec[i] - ent; + + tmp_union = DatumGetH3Index(ent[real_index].key); + //PRINT_H3INDEX(tmp_union); + //Assert(tmp_union != NULL); + /* + * Choose where to put the index entries and update unionL and unionR + * accordingly. Append the entries to either v_spl_left or + * v_spl_right, and care about the counters. + */ + + if (v->spl_nleft < v->spl_nright) + { + LOG_NOTICE(5); + if (lset == false) + { + lset = true; + unionL = tmp_union; + } + else + { + unionL = common_ancestor(unionL, tmp_union); + } + *left = real_index; + ++left; + ++(v->spl_nleft); + } + else + { + LOG_NOTICE(6); + if (rset == false) + { + rset = true; + LOG_NOTICE(7); + //PRINT_H3INDEX(tmp_union); + unionR = tmp_union; + } + else + { + unionR = common_ancestor(unionR, tmp_union); + } + LOG_NOTICE(8); + *right = real_index; + LOG_NOTICE(9); + ++right; + LOG_NOTICE(10); + ++(v->spl_nright); + LOG_NOTICE(11); + } + } + + debug_func(maxoff); + + v->spl_ldatum = H3IndexGetDatum(unionL); + v->spl_rdatum = H3IndexGetDatum(unionR); + PG_RETURN_POINTER(v); +} + +/** + * Returns true if two index entries are identical, false otherwise. + * (An “index entry” is a value of the index's storage type, not necessarily + * the original indexed column's type.) + */ +PG_FUNCTION_INFO_V1(h3index_gist_same); +Datum + h3index_gist_same(PG_FUNCTION_ARGS) +{ + LOG_NOTICE(0); + + H3Index a = PG_GETARG_H3INDEX(0); + H3Index b = PG_GETARG_H3INDEX(1); + bool *result = (bool *)PG_GETARG_POINTER(2); + + debug_func(*result); + + *result = a == b; + PG_RETURN_POINTER(result); +} + +/** + * Given an index entry p and a query value q, this function determines the + * index entry's “distance” from the query value. This function must be + * supplied if the operator class contains any ordering operators. A query + * using the ordering operator will be implemented by returning index entries + * with the smallest “distance” values first, so the results must be consistent + * with the operator's semantics. For a leaf index entry the result just + * represents the distance to the index entry; for an internal tree node, the + * result must be the smallest distance that any child entry could have. + */ +PG_FUNCTION_INFO_V1(h3index_gist_distance); +Datum + h3index_gist_distance(PG_FUNCTION_ARGS) +{ + LOG_NOTICE(0); + + GISTENTRY *entry = (GISTENTRY *)PG_GETARG_POINTER(0); + H3Index query = PG_GETARG_H3INDEX(1); + StrategyNumber strategy = (StrategyNumber)PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + /* bool *recheck = (bool *) PG_GETARG_POINTER(4); */ + H3Index key = DatumGetH3Index(entry->key); + double retval; + + switch (strategy) + { + case RTKNNSearchStrategyNumber: + retval = h3Distance(query, key); + default: + retval = -1; + } + + debug_func(retval); + + PG_RETURN_FLOAT8(retval); +} diff --git a/h3/src/lib/upstream/h3Index.h b/h3/src/lib/upstream/h3Index.h new file mode 100644 index 00000000..08a6af5c --- /dev/null +++ b/h3/src/lib/upstream/h3Index.h @@ -0,0 +1,222 @@ +/* + * Copyright 2016-2018 Uber Technologies, Inc. + * + * 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. + */ +/** @file h3Index.h + * @brief H3Index functions. + */ + +#ifndef H3INDEX_H +#define H3INDEX_H + +/* +#include "faceijk.h" +#include "h3api.h" +*/ + +typedef enum +{ + /** H3 digit in center */ + CENTER_DIGIT = 0, + /** H3 digit in k-axes direction */ + K_AXES_DIGIT = 1, + /** H3 digit in j-axes direction */ + J_AXES_DIGIT = 2, + /** H3 digit in j == k direction */ + JK_AXES_DIGIT = J_AXES_DIGIT | K_AXES_DIGIT, /* 3 */ + /** H3 digit in i-axes direction */ + I_AXES_DIGIT = 4, + /** H3 digit in i == k direction */ + IK_AXES_DIGIT = I_AXES_DIGIT | K_AXES_DIGIT, /* 5 */ + /** H3 digit in i == j direction */ + IJ_AXES_DIGIT = I_AXES_DIGIT | J_AXES_DIGIT, /* 6 */ + /** H3 digit in the invalid direction */ + INVALID_DIGIT = 7, + /** Valid digits will be less than this value. Same value as INVALID_DIGIT. + */ + NUM_DIGITS = INVALID_DIGIT +} Direction; + +/* define's of constants and macros for bitwise manipulation of H3Index's. */ + +/** The number of bits in an H3 index. */ +#define H3_NUM_BITS 64 + +/** The bit offset of the max resolution digit in an H3 index. */ +#define H3_MAX_OFFSET 63 + +/** The bit offset of the mode in an H3 index. */ +#define H3_MODE_OFFSET 59 + +/** The bit offset of the base cell in an H3 index. */ +#define H3_BC_OFFSET 45 + +/** The bit offset of the resolution in an H3 index. */ +#define H3_RES_OFFSET 52 + +/** The bit offset of the reserved bits in an H3 index. */ +#define H3_RESERVED_OFFSET 56 + +/** The number of bits in a single H3 resolution digit. */ +#define H3_PER_DIGIT_OFFSET 3 + +/** 1 in the highest bit, 0's everywhere else. */ +#define H3_HIGH_BIT_MASK ((uint64_t)(1) << H3_MAX_OFFSET) + +/** 0 in the highest bit, 1's everywhere else. */ +#define H3_HIGH_BIT_MASK_NEGATIVE (~H3_HIGH_BIT_MASK) + +/** 1's in the 4 mode bits, 0's everywhere else. */ +#define H3_MODE_MASK ((uint64_t)(15) << H3_MODE_OFFSET) + +/** 0's in the 4 mode bits, 1's everywhere else. */ +#define H3_MODE_MASK_NEGATIVE (~H3_MODE_MASK) + +/** 1's in the 7 base cell bits, 0's everywhere else. */ +#define H3_BC_MASK ((uint64_t)(127) << H3_BC_OFFSET) + +/** 0's in the 7 base cell bits, 1's everywhere else. */ +#define H3_BC_MASK_NEGATIVE (~H3_BC_MASK) + +/** 1's in the 4 resolution bits, 0's everywhere else. */ +#define H3_RES_MASK (UINT64_C(15) << H3_RES_OFFSET) + +/** 0's in the 4 resolution bits, 1's everywhere else. */ +#define H3_RES_MASK_NEGATIVE (~H3_RES_MASK) + +/** 1's in the 3 reserved bits, 0's everywhere else. */ +#define H3_RESERVED_MASK ((uint64_t)(7) << H3_RESERVED_OFFSET) + +/** 0's in the 3 reserved bits, 1's everywhere else. */ +#define H3_RESERVED_MASK_NEGATIVE (~H3_RESERVED_MASK) + +/** 1's in the 3 bits of res 15 digit bits, 0's everywhere else. */ +#define H3_DIGIT_MASK ((uint64_t)(7)) + +/** 0's in the 7 base cell bits, 1's everywhere else. */ +#define H3_DIGIT_MASK_NEGATIVE (~H3_DIGIT_MASK) + +/** + * H3 index with mode 0, res 0, base cell 0, and 7 for all index digits. + * Typically used to initialize the creation of an H3 cell index, which + * expects all direction digits to be 7 beyond the cell's resolution. + */ +#define H3_INIT (UINT64_C(35184372088831)) + +/** + * Gets the highest bit of the H3 index. + */ +#define H3_GET_HIGH_BIT(h3) ((int)((((h3)&H3_HIGH_BIT_MASK) >> H3_MAX_OFFSET))) + +/** + * Sets the highest bit of the h3 to v. + */ +#define H3_SET_HIGH_BIT(h3, v) \ + (h3) = (((h3)&H3_HIGH_BIT_MASK_NEGATIVE) | \ + (((uint64_t)(v)) << H3_MAX_OFFSET)) + +/** + * Gets the integer mode of h3. + */ +#define H3_GET_MODE(h3) ((int)((((h3)&H3_MODE_MASK) >> H3_MODE_OFFSET))) + +/** + * Sets the integer mode of h3 to v. + */ +#define H3_SET_MODE(h3, v) \ + (h3) = (((h3)&H3_MODE_MASK_NEGATIVE) | (((uint64_t)(v)) << H3_MODE_OFFSET)) + +/** + * Gets the integer base cell of h3. + */ +#define H3_GET_BASE_CELL(h3) ((int)((((h3)&H3_BC_MASK) >> H3_BC_OFFSET))) + +/** + * Sets the integer base cell of h3 to bc. + */ +#define H3_SET_BASE_CELL(h3, bc) \ + (h3) = (((h3)&H3_BC_MASK_NEGATIVE) | (((uint64_t)(bc)) << H3_BC_OFFSET)) + +/** + * Gets the integer resolution of h3. + */ +#define H3_GET_RESOLUTION(h3) ((int)((((h3)&H3_RES_MASK) >> H3_RES_OFFSET))) + +/** + * Sets the integer resolution of h3. + */ +#define H3_SET_RESOLUTION(h3, res) \ + (h3) = (((h3)&H3_RES_MASK_NEGATIVE) | (((uint64_t)(res)) << H3_RES_OFFSET)) + +/** + * Gets the resolution res integer digit (0-7) of h3. + */ +#define H3_GET_INDEX_DIGIT(h3, res) \ + ((Direction)((((h3) >> ((MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET)) & \ + H3_DIGIT_MASK))) + +/** + * Sets a value in the reserved space. Setting to non-zero may produce invalid + * indexes. + */ +#define H3_SET_RESERVED_BITS(h3, v) \ + (h3) = (((h3)&H3_RESERVED_MASK_NEGATIVE) | \ + (((uint64_t)(v)) << H3_RESERVED_OFFSET)) + +/** + * Gets a value in the reserved space. Should always be zero for valid indexes. + */ +#define H3_GET_RESERVED_BITS(h3) \ + ((int)((((h3)&H3_RESERVED_MASK) >> H3_RESERVED_OFFSET))) + +/** + * Sets the resolution res digit of h3 to the integer digit (0-7) + */ +#define H3_SET_INDEX_DIGIT(h3, res, digit) \ + (h3) = (((h3) & ~((H3_DIGIT_MASK \ + << ((MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET)))) | \ + (((uint64_t)(digit)) \ + << ((MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET))) + +/** + * Invalid index used to indicate an error from geoToH3 and related functions + * or missing data in arrays of h3 indices. Analogous to NaN in floating point. + */ +#define H3_NULL 0 + +/* + * Return codes for compact + */ + +#define COMPACT_SUCCESS 0 +#define COMPACT_LOOP_EXCEEDED -1 +#define COMPACT_DUPLICATE -2 +#define COMPACT_ALLOC_FAILED -3 + +void setH3Index(H3Index * h, int res, int baseCell, Direction initDigit); +int isResClassIII(int res); + +/* Internal functions */ + +/* +int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK* fijk); +H3Index _faceIjkToH3(const FaceIJK* fijk, int res); +Direction _h3LeadingNonZeroDigit(H3Index h); +H3Index _h3RotatePent60ccw(H3Index h); +H3Index _h3RotatePent60cw(H3Index h); +H3Index _h3Rotate60ccw(H3Index h); +H3Index _h3Rotate60cw(H3Index h); +*/ + +#endif diff --git a/h3/test/expected/opclass_gist.out b/h3/test/expected/opclass_gist.out new file mode 100644 index 00000000..dafc7da7 --- /dev/null +++ b/h3/test/expected/opclass_gist.out @@ -0,0 +1,30 @@ +\pset tuples_only on +\set hexagon '\'831c02fffffffff\'::h3index' +CREATE TABLE h3_test_gist (hex h3index); +CREATE INDEX GIST_IDX ON h3_test_gist USING gist(hex); +INSERT INTO h3_test_gist (hex) SELECT h3_to_parent(:hexagon); +INSERT INTO h3_test_gist (hex) SELECT h3_to_children(:hexagon); +INSERT INTO h3_test_gist (hex) SELECT h3_to_center_child(:hexagon, 15); +-- +-- TEST GiST +-- +SELECT COUNT(*) = 1 FROM h3_test_gist WHERE hex @> :hexagon; + t + +SELECT COUNT(*) = 8 FROM h3_test_gist WHERE hex <@ :hexagon; + t + +INSERT INTO h3_test_gist (hex) SELECT h3_k_ring(:hexagon, 2); +SELECT COUNT(*) = 8 FROM h3_test_gist WHERE hex <-> :hexagon = 2; + t + +-- +-- BREAK GIST +-- +SELECT COUNT(*) = 8 FROM h3_test_gist WHERE hex <@ :hexagon; + t + +INSERT INTO h3_test_gist (hex) SELECT h3_to_children(:hexagon, 8); +SELECT COUNT(*) > 8 FROM h3_test_gist WHERE hex <@ :hexagon; + t + diff --git a/h3/test/sql/opclass_gist.sql b/h3/test/sql/opclass_gist.sql new file mode 100644 index 00000000..654cd9e8 --- /dev/null +++ b/h3/test/sql/opclass_gist.sql @@ -0,0 +1,26 @@ +\pset tuples_only on +\set hexagon '\'831c02fffffffff\'::h3index' + +CREATE TABLE h3_test_gist (hex h3index); +CREATE INDEX GIST_IDX ON h3_test_gist USING gist(hex); +INSERT INTO h3_test_gist (hex) SELECT h3_to_parent(:hexagon); +INSERT INTO h3_test_gist (hex) SELECT h3_to_children(:hexagon); +INSERT INTO h3_test_gist (hex) SELECT h3_to_center_child(:hexagon, 15); + +-- +-- TEST GiST +-- +SELECT COUNT(*) = 1 FROM h3_test_gist WHERE hex @> :hexagon; +SELECT COUNT(*) = 8 FROM h3_test_gist WHERE hex <@ :hexagon; + +INSERT INTO h3_test_gist (hex) SELECT h3_k_ring(:hexagon, 2); +SELECT COUNT(*) = 8 FROM h3_test_gist WHERE hex <-> :hexagon = 2; + +-- +-- BREAK GIST +-- + +SELECT COUNT(*) = 8 FROM h3_test_gist WHERE hex <@ :hexagon; +SELECT COUNT(*) FROM h3_test_gist WHERE hex <@ :hexagon; +INSERT INTO h3_test_gist (hex) SELECT h3_to_children(:hexagon, 8); +SELECT COUNT(*) > 8 FROM h3_test_gist WHERE hex <@ :hexagon; \ No newline at end of file