diff --git a/CHANGELOG.md b/CHANGELOG.md index 7297e0c4..1432cd65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ avoid adding features or APIs which do not map onto the - Use CMake for entire build (see [#70]) - Add helper to fix pass-by-value migration (see [#111]) - Allow distance operator `<->` to work for cells at different resolutions (using center child). +- Add `gist` operator class (see [#42], thanks [@abelvm]) @@ -219,6 +220,7 @@ avoid adding features or APIs which do not map onto the [#37]: https://github.com/zachasme/h3-pg/issues/37 [#38]: https://github.com/zachasme/h3-pg/issues/38 [#41]: https://github.com/zachasme/h3-pg/issues/41 +[#42]: https://github.com/zachasme/h3-pg/pull/42 [#55]: https://github.com/zachasme/h3-pg/issues/55 [#64]: https://github.com/zachasme/h3-pg/issues/64 [#65]: https://github.com/zachasme/h3-pg/pull/65 diff --git a/docs/api.md b/docs/api.md index 5c58bece..696c9791 100644 --- a/docs/api.md +++ b/docs/api.md @@ -445,6 +445,30 @@ Returns true if A containts B. Returns true if A is contained by B. +### h3index_gist_consistent(`internal`, `h3index`, `smallint`, `oid`, `internal`) ⇒ `boolean` + + +### h3index_gist_union(`internal`, `internal`) ⇒ `h3index` + + +### h3index_gist_compress(`internal`) ⇒ `internal` + + +### h3index_gist_decompress(`internal`) ⇒ `internal` + + +### h3index_gist_penalty(`internal`, `internal`, `internal`) ⇒ `internal` + + +### h3index_gist_picksplit(`internal`, `internal`) ⇒ `internal` + + +### h3index_gist_same(`h3index`, `h3index`, `internal`) ⇒ `internal` + + +### h3index_gist_distance(`internal`, `h3index`, `smallint`, `oid`, `internal`) ⇒ `float8` + + # Type casts ### `h3index` :: `bigint` diff --git a/h3/CMakeLists.txt b/h3/CMakeLists.txt index 1e43a288..496db739 100644 --- a/h3/CMakeLists.txt +++ b/h3/CMakeLists.txt @@ -13,6 +13,7 @@ PostgreSQL_add_extension(postgresql_h3 src/inspection.c src/miscellaneous.c src/opclass_btree.c + src/opclass_gist.c src/opclass_hash.c src/operators.c src/regions.c @@ -33,6 +34,7 @@ PostgreSQL_add_extension(postgresql_h3 sql/install/11-opclass_btree.sql sql/install/12-opclass_hash.sql sql/install/13-opclass_brin.sql + sql/install/14-opclass_gist.sql sql/install/20-casts.sql sql/install/30-extension.sql sql/install/99-deprecated.sql diff --git a/h3/sql/install/14-opclass_gist.sql b/h3/sql/install/14-opclass_gist.sql new file mode 100644 index 00000000..63b9d1f6 --- /dev/null +++ b/h3/sql/install/14-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 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--4.0.3--unreleased.sql b/h3/sql/updates/h3--4.0.3--unreleased.sql index f9d47fd7..e5bdf058 100644 --- a/h3/sql/updates/h3--4.0.3--unreleased.sql +++ b/h3/sql/updates/h3--4.0.3--unreleased.sql @@ -43,3 +43,39 @@ CREATE OPERATOR <-> ( ); COMMENT ON OPERATOR <-> (h3index, h3index) IS 'Returns the distance in grid cells between the two indices (at the lowest resolution of the two).'; +-- ---------- ---------- ---------- ---------- ---------- ---------- ---------- +-- 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 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/opclass_gist.c b/h3/src/opclass_gist.c new file mode 100644 index 00000000..ce3fabc6 --- /dev/null +++ b/h3/src/opclass_gist.c @@ -0,0 +1,430 @@ +/* + * 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) + +PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_gist_union); +PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_gist_consistent); +PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_gist_compress); +PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_gist_decompress); +PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_gist_penalty); +PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_gist_picksplit); +PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_gist_same); +PGDLLEXPORT PG_FUNCTION_INFO_V1(h3index_gist_distance); + +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; + H3Index aParent, + bParent; + H3Error error; + + /* 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 */ + error = cellToParent(b, aRes, &bParent); + if (!error && a == H3_ROOT_INDEX || (aRes < bRes && bParent == a)) + return 1; + /* a contained by b */ + error = cellToParent(a, bRes, &aParent); + if (!error && b == H3_ROOT_INDEX || (aRes > bRes && aParent == 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, + aParent, + bParent; + + 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 (cellToParent(a, i, &aParent)) + continue; + if (cellToParent(b, i, &bParent)) + continue; + + if (aParent == bParent) + return aParent; + } + + 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 + */ +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. + */ +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? + */ +Datum +h3index_gist_compress(PG_FUNCTION_ARGS) +{ + LOG_NOTICE(0); + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); +} + +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 +*/ +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) getResolution(orig) - getResolution(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 + */ +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; + + /* 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); + /* DEBUG_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); + /* DEBUG_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.) + */ +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. + */ +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; + int64_t distance; + H3Error error; + H3Index child; + + switch (strategy) + { + case RTKNNSearchStrategyNumber: + error = cellToCenterChild(key, getResolution(query), &child); + H3_ERROR(error, "cellToCenterChild"); + error = gridDistance(query, child, &distance); + H3_ERROR(error, "gridDistance"); + retval = distance; + default: + retval = -1; + } + + debug_func(retval); + + PG_RETURN_FLOAT8(retval); +} diff --git a/h3/src/upstream/h3Index.h b/h3/src/upstream/h3Index.h new file mode 100644 index 00000000..6d779921 --- /dev/null +++ b/h3/src/upstream/h3Index.h @@ -0,0 +1,207 @@ +/* + * Copyright 2016-2018, 2020 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))) + +void setH3Index(H3Index * h, int res, int baseCell, Direction initDigit); +int isResolutionClassIII(int r); + +/* Internal functions */ +/* +int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK * fijk); +H3Error _h3ToFaceIjk(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); +DECLSPEC H3Index _zeroIndexDigits(H3Index h, int start, int end); +*/ +#endif diff --git a/h3/test/CMakeLists.txt b/h3/test/CMakeLists.txt index 4cd9f32b..4b5d617a 100644 --- a/h3/test/CMakeLists.txt +++ b/h3/test/CMakeLists.txt @@ -6,6 +6,7 @@ set(TESTS miscellaneous opclass_brin opclass_btree + opclass_gist opclass_hash regions traversal diff --git a/h3/test/expected/opclass_gist.out b/h3/test/expected/opclass_gist.out new file mode 100644 index 00000000..9bfb00b6 --- /dev/null +++ b/h3/test/expected/opclass_gist.out @@ -0,0 +1,33 @@ +\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_cell_to_parent(:hexagon); +INSERT INTO h3_test_gist (hex) SELECT h3_cell_to_children(:hexagon); +INSERT INTO h3_test_gist (hex) SELECT h3_cell_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_grid_disk(: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 + +SELECT COUNT(*) FROM h3_test_gist WHERE hex <@ :hexagon; + 8 + +INSERT INTO h3_test_gist (hex) SELECT h3_cell_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..17bdcfcc --- /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_cell_to_parent(:hexagon); +INSERT INTO h3_test_gist (hex) SELECT h3_cell_to_children(:hexagon); +INSERT INTO h3_test_gist (hex) SELECT h3_cell_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_grid_disk(: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_cell_to_children(:hexagon, 8); +SELECT COUNT(*) > 8 FROM h3_test_gist WHERE hex <@ :hexagon; diff --git a/scripts/sql2doc/sql.lark b/scripts/sql2doc/sql.lark index 3c34dd56..e81c56cb 100644 --- a/scripts/sql2doc/sql.lark +++ b/scripts/sql2doc/sql.lark @@ -40,8 +40,8 @@ create_cast_stmt: "CREATE" "CAST" "(" DATATYPE "AS" DATATYPE ")" "WITH" "FUNCTIO // | STORAGE storage_type // } [, ... ] create_opcl_stmt: "CREATE" "OPERATOR" "CLASS" CNAME "DEFAULT"? "FOR" "TYPE" CNAME "USING" CNAME "AS" create_opcl_list -create_opcl_opts: "OPERATOR" SIGNED_NUMBER OPERATOR -| "FUNCTION" SIGNED_NUMBER fun_name "(" [argument_list] ")" +create_opcl_opts: "OPERATOR" SIGNED_NUMBER OPERATOR ("(" [argument_list] ")")? ("FOR" "ORDER" "BY" CNAME)? + | "FUNCTION" SIGNED_NUMBER ("(" [argument_list] ")")? fun_name "(" [argument_list] ")" create_opcl_list: create_opcl_opts ("," create_opcl_opts)* // ----------------------------------------------------------------------------- @@ -146,20 +146,23 @@ ARGMODE.2: "IN" | "OUT" | "INOUT" DATATYPE_SCALAR: "h3index" | "bigint" | "boolean" + | "bytea" | "cstring" | "double" WS "precision" | "float" + | "float8" | "geography" | "geometry" - | "bytea" + | "int" | "int32" | "int8" | "integer" | "internal" - | "int" + | "oid" | "point" | "polygon" | "record" + | "smallint" | "text" | "void" DATATYPE: DATATYPE_SCALAR "[]"?