From 500b751f23af091ff29e2b325ebf640dbdb11870 Mon Sep 17 00:00:00 2001 From: Zacharias Knudsen Date: Mon, 24 Aug 2020 15:31:29 +0200 Subject: [PATCH] WIP: SP-GiST support --- sql/install/14-opclass_spgist.sql | 42 +++ sql/updates/h3--3.6.5--unreleased.sql | 26 ++ src/opclass_spgist.c | 425 ++++++++++++++++++++++++++ src/upstream/h3Index.h | 222 ++++++++++++++ test/sql/opclass_spgist.sql | 20 ++ 5 files changed, 735 insertions(+) create mode 100644 sql/install/14-opclass_spgist.sql create mode 100644 src/opclass_spgist.c create mode 100644 src/upstream/h3Index.h create mode 100644 test/sql/opclass_spgist.sql diff --git a/sql/install/14-opclass_spgist.sql b/sql/install/14-opclass_spgist.sql new file mode 100644 index 00000000..c61f99b4 --- /dev/null +++ b/sql/install/14-opclass_spgist.sql @@ -0,0 +1,42 @@ +/* + * 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. + */ + +-- ---------- ---------- ---------- ---------- ---------- ---------- ---------- +-- SP-GiST Operator Class (opclass_spgist.c) +-- ---------- ---------- ---------- ---------- ---------- ---------- ---------- + +-- SP-GiST operator class +CREATE OR REPLACE FUNCTION h3index_spgist_config(internal, internal) RETURNS void + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE OR REPLACE FUNCTION h3index_spgist_choose(internal, internal) RETURNS void + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE OR REPLACE FUNCTION h3index_spgist_picksplit(internal, internal) RETURNS void + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE OR REPLACE FUNCTION h3index_spgist_inner_consistent(internal, internal) RETURNS void + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE OR REPLACE FUNCTION h3index_spgist_leaf_consistent(internal, internal) RETURNS bool + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR CLASS spgist_h3index_ops DEFAULT FOR TYPE h3index USING spgist AS + OPERATOR 6 = , + OPERATOR 7 @> , + OPERATOR 8 <@ , + + FUNCTION 1 h3index_spgist_config(internal, internal), + FUNCTION 2 h3index_spgist_choose(internal, internal), + FUNCTION 3 h3index_spgist_picksplit(internal, internal), + FUNCTION 4 h3index_spgist_inner_consistent(internal, internal), + FUNCTION 5 h3index_spgist_leaf_consistent(internal, internal); diff --git a/sql/updates/h3--3.6.5--unreleased.sql b/sql/updates/h3--3.6.5--unreleased.sql index 609c48e7..9f4f3cea 100644 --- a/sql/updates/h3--3.6.5--unreleased.sql +++ b/sql/updates/h3--3.6.5--unreleased.sql @@ -23,3 +23,29 @@ CREATE OPERATOR <-> ( PROCEDURE = h3_distance, COMMUTATOR = <-> ); + +-- ---------- ---------- ---------- ---------- ---------- ---------- ---------- +-- SP-GiST Operator Class (opclass_spgist.c) +-- ---------- ---------- ---------- ---------- ---------- ---------- ---------- + +CREATE OR REPLACE FUNCTION h3index_spgist_config(internal, internal) RETURNS void + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE OR REPLACE FUNCTION h3index_spgist_choose(internal, internal) RETURNS void + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE OR REPLACE FUNCTION h3index_spgist_picksplit(internal, internal) RETURNS void + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE OR REPLACE FUNCTION h3index_spgist_inner_consistent(internal, internal) RETURNS void + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE OR REPLACE FUNCTION h3index_spgist_leaf_consistent(internal, internal) RETURNS bool + AS 'h3' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR CLASS spgist_h3index_ops DEFAULT FOR TYPE h3index USING spgist AS + OPERATOR 6 = , + OPERATOR 7 @> , + OPERATOR 8 <@ , + + FUNCTION 1 h3index_spgist_config(internal, internal), + FUNCTION 2 h3index_spgist_choose(internal, internal), + FUNCTION 3 h3index_spgist_picksplit(internal, internal), + FUNCTION 4 h3index_spgist_inner_consistent(internal, internal), + FUNCTION 5 h3index_spgist_leaf_consistent(internal, internal); diff --git a/src/opclass_spgist.c b/src/opclass_spgist.c new file mode 100644 index 00000000..47bae6fd --- /dev/null +++ b/src/opclass_spgist.c @@ -0,0 +1,425 @@ +/* + * 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 // SP-GiST +#include "catalog/pg_type.h" + +#include // Main H3 include +#include "extension.h" + +#include "inttypes.h" + +#include "upstream/h3Index.h" // Copied from upstream + +PG_FUNCTION_INFO_V1(h3index_spgist_config); +PG_FUNCTION_INFO_V1(h3index_spgist_choose); +PG_FUNCTION_INFO_V1(h3index_spgist_picksplit); +PG_FUNCTION_INFO_V1(h3index_spgist_inner_consistent); +PG_FUNCTION_INFO_V1(h3index_spgist_leaf_consistent); + +#define H3_ROOT_INDEX -1 +#define NUM_BASE_CELLS 122 +#define MAX_H3_RES 15 + +#define H3_NUM_CHILDREN 7 + +static int +spgist_cmp(H3Index * a, H3Index * b) +{ + int aRes = h3GetResolution(*a); + int bRes = h3GetResolution(*b); + H3Index aParent = h3ToParent(*a, bRes); + H3Index bParent = h3ToParent(*b, aRes); + + /* a contains b */ + if (*a == H3_ROOT_INDEX || *a == bParent) + { + return 1; + } + + /* a contained by b */ + if (*b == H3_ROOT_INDEX || *b == aParent) + { + return -1; + } + + /* no overlap */ + return 0; +} + +/* + * h3index_spgist_config + * Returns static information about the index implementation + * + * Including the data type OIDs of the prefix and node label data types + */ +Datum +h3index_spgist_config(PG_FUNCTION_ARGS) +{ + /*-------------------------------------------------------------------------- + * Oid attType data type to be indexed + *-------------------------------------------------------------------------- + */ + spgConfigIn *in = (spgConfigIn *) PG_GETARG_POINTER(0); + + /*-------------------------------------------------------------------------- + * Oid prefixType data type of inner-tupleprefixes + * Oid labelType data type of inner-tuple node labels + * Oid leafType data type of leaf-tuple values + * bool canReturnData opclass can reconstruct original data + * bool longValuesOK opclass can cope with values > 1 page + *-------------------------------------------------------------------------- + */ + spgConfigOut *out = (spgConfigOut *) PG_GETARG_POINTER(1); + + /* prefix is parent H3 index */ + out->prefixType = in->attType; + /* no need for labels */ + out->labelType = VOIDOID; + + out->canReturnData = true; + out->longValuesOK = false; + + PG_RETURN_VOID(); +} + +/* + * h3index_spgist_choose + * Chooses a method for inserting a new value into an inner tuple + * + * The choose function can determine either that the new value matches one of + * the existing child nodes, or that a new child node must be added, or that the + * new value is inconsistent with the tuple prefix and so the inner tuple must + * be split to create a less restrictive prefix. + * + * NOTE: When working with an inner tuple having unlabeled nodes, it is an error + * for choose to return spgAddNode, since the set of nodes is supposed to be + * fixed in such cases. Also, there is no provision for generating an unlabeled + * node in spgSplitTuple actions, since it is expected that an spgAddNode action + * will be needed as well. + */ +Datum +h3index_spgist_choose(PG_FUNCTION_ARGS) +{ + /*-------------------------------------------------------------------------- + * Datum datum original datum to be indexed + * Datum leafDatum current datum to be stored at leaf + * int level current level (counting from zero) + * + * (data from current inner tuple) + * bool allTheSame tuple is marked all-the-same? + * bool hasPrefix tuple has a prefix? + * Datum prefixDatum if so, the prefix value + * int nNodes number of nodes in the inner tuple + * Datum* nodeLabels node label values (NULL if none) + *-------------------------------------------------------------------------- + */ + spgChooseIn *in = (spgChooseIn *) PG_GETARG_POINTER(0); + + /*-------------------------------------------------------------------------- + * spgChooseResultType resultType action code, see below + * + * -- results for spgMatchNode -- + * int nodeN descend to this node (index from 0) + * int levelAdd increment level by this much + * Datum restDatum new leaf datum + * + * -- results for spgAddNode -- + * Datum nodeLabel new node's label + * int nodeN where to insert it (index from 0) + * + * -- results for spgSplitTuple -- + * (info to form new upper-level inner tuple with one child tuple) + * bool prefixHasPrefix tuple should have a prefix? + * Datum prefixPrefixDatum if so, its value + * int prefixNNodes number of nodes + * Datum* prefixNodeLabels their labels (or NULL for no labels) + * int childNodeN which node gets child tuple + * (info to form new lower-level inner tuple with all old nodes) + * bool postfixHasPrefix tuple should have a prefix? + * Datum postfixPrefixDatum if so, its value + *-------------------------------------------------------------------------- + */ + spgChooseOut *out = (spgChooseOut *) PG_GETARG_POINTER(1); + + int resolution = in->level; + H3Index insert = DatumGetH3Index(in->datum); + int node; + + out->resultType = spgMatchNode; + out->result.matchNode.levelAdd = 1; + out->result.matchNode.restDatum = H3IndexGetDatum(insert); + + if (!in->allTheSame) + { + if (resolution == 0) + { + node = h3GetBaseCell(insert); + } + else + { + node = H3_GET_INDEX_DIGIT(insert, resolution); + } + + out->result.matchNode.nodeN = node; + } + + PG_RETURN_VOID(); +} + +/** + * Decides how to create a new inner tuple over a set of leaf tuples. + * + * An inner tuple contains a set of one or more nodes, + * which represent groups of similar leaf values. + * + * struct spgPickSplitIn + * int nTuples number of leaf tuples + * Datum *datums their datums (array of length nTuples) + * int level current level (counting from zero) + * + * struct spgPickSplitOut + * bool hasPrefix new inner tuple should have a prefix? + * Datum prefixDatum if so, its value + * int nNodes number of nodes for new inner tuple + * Datum *nodeLabels their labels (or NULL for no labels) + * int *mapTuplesToNodes node index for each leaf tuple + * Datum *leafTupleDatums datum to store in each new leaf tuple + */ +Datum +h3index_spgist_picksplit(PG_FUNCTION_ARGS) +{ + spgPickSplitIn *in = (spgPickSplitIn *) PG_GETARG_POINTER(0); + spgPickSplitOut *out = (spgPickSplitOut *) PG_GETARG_POINTER(1); + int resolution = in->level; + + /* we don't need node labels */ + out->nodeLabels = NULL; + out->mapTuplesToNodes = palloc(sizeof(int) * in->nTuples); + out->leafTupleDatums = palloc(sizeof(Datum) * in->nTuples); + + if (resolution == 0) + { + /* at resolution 0 there is one node per base cell */ + out->nNodes = NUM_BASE_CELLS; + out->hasPrefix = false; + } + else + { + /* at finer resolutions there is exactly 7 nodes, one per child */ + H3Index first = DatumGetH3Index(in->datums[0]); + H3Index parent = h3ToParent(first, resolution); + + /* + * TODO: consider decreasing nNodes for pentagons which only have 6 + * children? + */ + out->nNodes = H3_NUM_CHILDREN; + out->hasPrefix = true; + out->prefixDatum = H3IndexGetDatum(parent); + } + + /* map each leaf tuple to node in the new inner tuple */ + for (int i = 0; i < in->nTuples; i++) + { + H3Index insert = DatumGetH3Index(in->datums[i]); + int node; + + if (resolution == 0) + { + /* first resolution is base cells */ + node = h3GetBaseCell(insert); + } + else if (h3GetResolution(insert) >= resolution) + { + /* finer resolutions use index digit 0-6 */ + node = H3_GET_INDEX_DIGIT(insert, resolution); + } + else + { + /* coarse indexes are put into center node */ + node = 0; + } + + out->leafTupleDatums[i] = H3IndexGetDatum(insert); + out->mapTuplesToNodes[i] = node; + } + + PG_RETURN_VOID(); +} + +/** + * Returns set of nodes (branches) to follow during tree search. + * + * Each query is a single H3 index to be checked against the parent prefix + * + * We either return all or none (except for res 0) + */ +Datum +h3index_spgist_inner_consistent(PG_FUNCTION_ARGS) +{ + spgInnerConsistentIn *in = (spgInnerConsistentIn *) PG_GETARG_POINTER(0); + spgInnerConsistentOut *out = (spgInnerConsistentOut *) PG_GETARG_POINTER(1); + H3Index *parent = NULL; + int bc, + i; + bool stop; + int innerNodes = in->nNodes; + + if (in->hasPrefix) + { + *parent = DatumGetH3Index(in->prefixDatum); + } + + if (in->allTheSame) + { + /* Report that all nodes should be visited */ + out->nNodes = in->nNodes; + out->nodeNumbers = (int *) palloc(sizeof(int) * in->nNodes); + for (i = 0; i < in->nNodes; i++) + { + out->nodeNumbers[i] = i; + } + PG_RETURN_VOID(); + } + + out->levelAdds = palloc(sizeof(int) * innerNodes); + for (i = 0; i < innerNodes; ++i) + out->levelAdds[i] = 1; + + /* We must descend into the quadrant(s) identified by which */ + out->nodeNumbers = (int *) palloc(sizeof(int) * innerNodes); + out->nNodes = 0; + + /* "which" is a bitmask of child nodes that satisfy all constraints */ + bc = -1; + stop = false; + for (i = 0; i < in->nkeys; i++) + { + /* each scankey is a constraint to be checked against */ + StrategyNumber strategy = in->scankeys[i].sk_strategy; + H3Index query = DatumGetH3Index(in->scankeys[i].sk_argument); + + if (parent == NULL) + { + if (bc > -1) + { + stop = true; + } + bc = h3GetBaseCell(query); + } + else + { + switch (strategy) + { + case RTSameStrategyNumber: + if (spgist_cmp(parent, &query) == 0) + stop = true; + break; + case RTContainsStrategyNumber: + if (spgist_cmp(parent, &query) == 0) + stop = true; + /* no overlap */ + break; + case RTContainedByStrategyNumber: + if (spgist_cmp(parent, &query) == 0) + stop = true; + /* no overlap */ + break; + default: + elog(ERROR, "unrecognized strategy number: %d", strategy); + break; + } + } + + if (stop) + break; /* no need to consider remaining conditions */ + } + + if (!stop) + { + if (bc > -1) + { + out->nodeNumbers[out->nNodes] = bc; + out->nNodes++; + } + else + { + for (i = 0; i < innerNodes; i++) + { + out->nodeNumbers[out->nNodes] = i; + out->nNodes++; + } + } + } + + PG_RETURN_VOID(); +} + +/** + * Returns true if a leaf satisfies a query. + * + * To satisfy the query, the leaf must satisfy all the conditions described by scankeys. + */ +Datum +h3index_spgist_leaf_consistent(PG_FUNCTION_ARGS) +{ + spgLeafConsistentIn *in = (spgLeafConsistentIn *) PG_GETARG_POINTER(0); + spgLeafConsistentOut *out = (spgLeafConsistentOut *) PG_GETARG_POINTER(1); + + H3Index leaf = DatumGetH3Index(in->leafDatum); + bool retval = true; + + out->leafValue = in->leafDatum; + /* leafDatum is what it is... */ + out->recheck = false; + /* all tests are exact */ + + /* Perform the required comparison(s) */ + for (int i = 0; i < in->nkeys; i++) + { + StrategyNumber strategy = in->scankeys[i].sk_strategy; + H3Index query = DatumGetH3Index(in->scankeys[i].sk_argument); + + switch (strategy) + { + case RTSameStrategyNumber: + /* leaf is equal to query */ + retval = (leaf == query); + break; + case RTContainsStrategyNumber: + /* leaf contains the query */ + retval = (spgist_cmp(&leaf, &query) > 0); + break; + case RTContainedByStrategyNumber: + /* leaf is contained by the query */ + retval = (spgist_cmp(&leaf, &query) < 0); + break; + default: + ereport(ERROR, ( + errcode(ERRCODE_INTERNAL_ERROR), + errmsg("unrecognized StrategyNumber: %d", strategy)) + ); + } + + if (!retval) + break; + } + + PG_RETURN_BOOL(retval); +} diff --git a/src/upstream/h3Index.h b/src/upstream/h3Index.h new file mode 100644 index 00000000..9d799250 --- /dev/null +++ b/src/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/test/sql/opclass_spgist.sql b/test/sql/opclass_spgist.sql new file mode 100644 index 00000000..35d0a2fd --- /dev/null +++ b/test/sql/opclass_spgist.sql @@ -0,0 +1,20 @@ +\pset tuples_only on +\set hexagon '\'831c02fffffffff\'::h3index' + +CREATE TABLE h3_test_spgist (hex h3index); +CREATE INDEX SPGIST_IDX ON h3_test_spgist USING spgist(hex); +INSERT INTO h3_test_spgist (hex) SELECT h3_to_parent(:hexagon); +INSERT INTO h3_test_spgist (hex) SELECT h3_to_children(:hexagon); +INSERT INTO h3_test_spgist (hex) SELECT h3_to_center_child(:hexagon, 15); + +-- +-- TEST SP-GiST +-- +SELECT COUNT(*) = 1 FROM h3_test_spgist WHERE hex @> :hexagon; +SELECT COUNT(*) = 8 FROM h3_test_spgist WHERE hex <@ :hexagon; + +-- + +TRUNCATE TABLE h3_test_spgist; +INSERT INTO h3_test_spgist (hex) SELECT h3_to_children(h3_to_center_child(:hexagon, 10), 15); +SELECT COUNT(*) = 16807 FROM h3_test_spgist WHERE hex <@ :hexagon; \ No newline at end of file