From 3d675fc5328b92cd4ef6cd15339b111ee78ce97c Mon Sep 17 00:00:00 2001 From: blitss Date: Tue, 11 Aug 2026 19:40:34 +0000 Subject: [PATCH] fix: build release binaries on an older glibc, and refuse to ship a bad one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 0.4.0 linux artifacts do not load on Debian bookworm: could not load library ".../typeid.so": /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found bookworm is the base for cloudnative-pg and the official postgres images, so in practice the release was unusable there. It took down a production database that installs the extension from the latest release. Cause is the move off BuildJet. Those runners were Ubuntu 22.04 (glibc 2.35); ubuntu-latest is 24.04 (glibc 2.39), and glibc is backward but not forward compatible — the linker records a minimum version per symbol and the loader refuses anything it cannot satisfy. Three symbols crossed the line: __isoc23_sscanf GLIBC_2.38 pidfd_getpid GLIBC_2.39 pidfd_spawnp GLIBC_2.39 The first is the subtle one. Nothing asked for a C23 API: from glibc 2.38 the headers redirect plain sscanf to __isoc23_sscanf, so ordinary code picks up a 2.38-only symbol purely from the build host's headers. Build the linux release jobs on ubuntu-22.04 and ubuntu-22.04-arm instead. 2.35 loads on bookworm (2.36), trixie (2.41) and anything newer; the rule is to build against the oldest runtime supported, not the newest available. Compilation and linking succeed either way — this only fails at dlopen on the target, which no test in this repository reaches. So the release now also inspects the built object and fails if any symbol requires more than glibc 2.35. Checked against both binaries: the 0.3.0 object reports 2.34 and passes, the 0.4.0 object reports 2.39 and fails. Released as 0.4.1 because 0.4.0 is already published; the upgrade script is empty since nothing about the schema changed. --- .github/workflows/release.yaml | 38 ++- Cargo.toml | 2 +- sql/typeid--0.4.0--0.4.1.sql | 13 + sql/typeid--0.4.1.sql | 505 +++++++++++++++++++++++++++++++++ 4 files changed, 555 insertions(+), 3 deletions(-) create mode 100644 sql/typeid--0.4.0--0.4.1.sql create mode 100644 sql/typeid--0.4.1.sql diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a065a02..376077b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -25,8 +25,13 @@ jobs: pg_version: [14, 15, 16, 17, 18] os: [ - ubuntu-latest, - ubuntu-24.04-arm, + # Built on the OLDEST runtime we support, not the newest. glibc is + # backward but not forward compatible: a binary linked on 24.04 + # (glibc 2.39) will not load on Debian bookworm (2.36), which is + # what cloudnative-pg and most Postgres images are based on. + # 22.04 gives glibc 2.35, which loads everywhere newer. + ubuntu-22.04, + ubuntu-22.04-arm, macos-latest, ] @@ -85,6 +90,35 @@ jobs: cargo pgrx package --features pg${{ matrix.pg_version }} \ --pg-config "$PG_CONFIG_PATH" + # --- Guard against shipping a binary the targets cannot load ------ + # + # Compiling and linking succeed on the build host regardless; the failure + # only appears at dlopen time on an older runtime, which no test here + # would reach. So assert the requirement directly. + # + # 2.35 is what Ubuntu 22.04 provides. Debian bookworm, the base for + # cloudnative-pg and the official postgres images, has 2.36. + - name: Check glibc requirements (Linux) + if: runner.os == 'Linux' + run: | + set -euo pipefail + MAX_ALLOWED="2.35" + SO="target/release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }}$(/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config --pkglibdir)/${{ env.EXTENSION_NAME }}.so" + echo "Inspecting ${SO}" + + REQUIRED="$(objdump -T "${SO}" | grep -oE 'GLIBC_[0-9]+\.[0-9]+' | sort -u -V || true)" + echo "Requires: $(echo "${REQUIRED}" | tr '\n' ' ')" + + HIGHEST="$(echo "${REQUIRED}" | sed 's/GLIBC_//' | sort -V | tail -1)" + if [ "$(printf '%s\n%s\n' "${MAX_ALLOWED}" "${HIGHEST}" | sort -V | tail -1)" != "${MAX_ALLOWED}" ]; then + echo "::error::Binary needs glibc ${HIGHEST}, above the ${MAX_ALLOWED} ceiling." + echo "It will fail to load on Debian bookworm (2.36) with 'version GLIBC_... not found'." + echo "Offending symbols:" + objdump -T "${SO}" | grep -E "GLIBC_(2\.3[6-9]|2\.[4-9][0-9])" || true + exit 1 + fi + echo "OK: highest requirement is ${HIGHEST}" + # --- Normalise OS + arch names ----------------------------------- - name: Format OS name for release run: echo "LOWERCASE_OS=$(uname -s | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV diff --git a/Cargo.toml b/Cargo.toml index 1217209..945a95b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "typeid" -version = "0.4.0" +version = "0.4.1" edition = "2024" resolver = "3" diff --git a/sql/typeid--0.4.0--0.4.1.sql b/sql/typeid--0.4.0--0.4.1.sql new file mode 100644 index 0000000..f3d43c8 --- /dev/null +++ b/sql/typeid--0.4.0--0.4.1.sql @@ -0,0 +1,13 @@ +/* + * No schema changes. + * + * 0.4.1 exists only to republish the binaries. The 0.4.0 artifacts were built + * on Ubuntu 24.04 and required GLIBC_2.38/2.39, so they failed to load on + * Debian bookworm (glibc 2.36) — the base for cloudnative-pg and the official + * postgres images — with: + * + * could not load library ".../typeid.so": version `GLIBC_2.38' not found + * + * The release now builds on Ubuntu 22.04 (glibc 2.35) and CI refuses to + * publish a binary requiring anything newer. + */ diff --git a/sql/typeid--0.4.1.sql b/sql/typeid--0.4.1.sql new file mode 100644 index 0000000..0f0ed61 --- /dev/null +++ b/sql/typeid--0.4.1.sql @@ -0,0 +1,505 @@ +/* */ +/* +This file is auto generated by pgrx. + +The ordering of items is not stable, it is driven by a dependency graph. +*/ +/* */ + +/* */ +-- src/lib.rs:60 +-- typeid::typeid_is_valid +CREATE FUNCTION "typeid_is_valid"( + "input" TEXT /* & str */ +) RETURNS bool /* bool */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_is_valid_wrapper'; +/* */ + +/* */ +-- src/typeid.rs:113 +-- TypeID +CREATE TYPE TypeID; + +-- src/typeid.rs:113 +-- typeid::typeid::typeid_in +CREATE FUNCTION "typeid_in"( + "input" cstring /* Option < & :: core :: ffi :: CStr > */ +) RETURNS TypeID /* Option < TypeID > */ +IMMUTABLE PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_in_wrapper'; + +-- src/typeid.rs:113 +-- typeid::typeid::typeid_out +CREATE FUNCTION "typeid_out"( + "input" TypeID /* TypeID */ +) RETURNS cstring /* :: pgrx :: ffi :: CString */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_out_wrapper'; + +-- src/typeid.rs:113 +-- typeid::typeid::typeid_recv +CREATE FUNCTION "typeid_recv"( + "internal" internal /* :: pgrx :: datum :: Internal */ +) RETURNS TypeID /* TypeID */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_recv_wrapper'; + +-- src/typeid.rs:113 +-- typeid::typeid::typeid_send +CREATE FUNCTION "typeid_send"( + "input" TypeID /* TypeID */ +) RETURNS bytea /* Vec < u8 > */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_send_wrapper'; + + +-- src/typeid.rs:113 +-- TypeID +CREATE TYPE TypeID ( + INTERNALLENGTH = variable, + INPUT = typeid_in, /* typeid::typeid::typeid_in */ + OUTPUT = typeid_out, /* typeid::typeid::typeid_out */ + RECEIVE = typeid_recv, /* typeid::typeid::typeid_recv */ + SEND = typeid_send, /* typeid::typeid::typeid_send */ + STORAGE = extended +); +/* */ + +/* */ +-- src/aggregate.rs:12 +-- typeid::aggregate::type_id_min_type_id_min_combine +CREATE FUNCTION "type_id_min_type_id_min_combine"( + "this" TypeID, /* Option < TypeID > */ + "v" TypeID /* Option < TypeID > */ +) RETURNS TypeID /* Option < TypeID > */ +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'type_id_min_type_id_min_combine_wrapper'; +/* */ + +/* */ +-- src/lib.rs:113 +-- typeid::typeid_ge +CREATE FUNCTION "typeid_ge"( + "a" TypeID, /* TypeID */ + "b" TypeID /* TypeID */ +) RETURNS bool /* bool */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_ge_wrapper'; +/* */ + +/* */ +-- src/lib.rs:66 +-- typeid::typeid_prefix +CREATE FUNCTION "typeid_prefix"( + "typeid" TypeID /* TypeID */ +) RETURNS TEXT /* String */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_prefix_wrapper'; +/* */ + +/* */ +-- src/lib.rs:108 +-- typeid::typeid_eq +CREATE FUNCTION "typeid_eq"( + "a" TypeID, /* TypeID */ + "b" TypeID /* TypeID */ +) RETURNS bool /* bool */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_eq_wrapper'; +/* */ + +/* */ +-- src/aggregate.rs:12 +-- typeid::aggregate::type_id_min_type_id_min_state +CREATE FUNCTION "type_id_min_type_id_min_state"( + "this" TypeID, /* Option < TypeID > */ + "arg_one" TypeID /* TypeID */ +) RETURNS TypeID /* Option < TypeID > */ +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'type_id_min_type_id_min_state_wrapper'; +/* */ + +/* */ +-- src/aggregate.rs:42 +-- typeid::aggregate::type_id_max_type_id_max_state +CREATE FUNCTION "type_id_max_type_id_max_state"( + "this" TypeID, /* Option < TypeID > */ + "arg_one" TypeID /* TypeID */ +) RETURNS TypeID /* Option < TypeID > */ +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'type_id_max_type_id_max_state_wrapper'; +/* */ + +/* */ +-- src/lib.rs:118 +-- typeid::typeid_gt +CREATE FUNCTION "typeid_gt"( + "a" TypeID, /* TypeID */ + "b" TypeID /* TypeID */ +) RETURNS bool /* bool */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_gt_wrapper'; +/* */ + +/* */ +-- src/lib.rs:656 +-- typeid::typeid_is_nil_prefix +CREATE FUNCTION "typeid_is_nil_prefix"( + "typeid" TypeID /* TypeID */ +) RETURNS bool /* bool */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_is_nil_prefix_wrapper'; +/* */ + +/* */ +-- src/lib.rs:48 +-- typeid::typeid_generate_nil +CREATE FUNCTION "typeid_generate_nil"() RETURNS TypeID /* TypeID */ +STRICT VOLATILE PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_generate_nil_wrapper'; +/* */ + +/* */ +-- src/lib.rs:644 +-- typeid::typeid_has_prefix +CREATE FUNCTION "typeid_has_prefix"( + "typeid" TypeID, /* TypeID */ + "prefix" TEXT /* & str */ +) RETURNS bool /* bool */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_has_prefix_wrapper'; +/* */ + +/* */ +-- src/lib.rs:129 +-- typeid::typeid_hash +CREATE FUNCTION "typeid_hash"( + "typeid" TypeID /* TypeID */ +) RETURNS INT /* i32 */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_hash_wrapper'; +/* */ + +/* */ +-- src/lib.rs:668 +-- typeid::typeid_generate_batch +CREATE FUNCTION "typeid_generate_batch"( + "prefix" TEXT, /* & str */ + "count" INT /* i32 */ +) RETURNS TypeID[] /* Vec < TypeID > */ +STRICT VOLATILE PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_generate_batch_wrapper'; +/* */ + +/* */ +-- src/lib.rs:93 +-- typeid::typeid_cmp +CREATE FUNCTION "typeid_cmp"( + "a" TypeID, /* TypeID */ + "b" TypeID /* TypeID */ +) RETURNS INT /* i32 */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_cmp_wrapper'; +/* */ + +/* */ +-- src/lib.rs:31 +-- typeid::typeid_generate +CREATE FUNCTION "typeid_generate"( + "prefix" TEXT /* & str */ +) RETURNS TypeID /* TypeID */ +STRICT VOLATILE PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_generate_wrapper'; +/* */ + +/* */ +-- src/lib.rs:98 +-- typeid::typeid_lt +CREATE FUNCTION "typeid_lt"( + "a" TypeID, /* TypeID */ + "b" TypeID /* TypeID */ +) RETURNS bool /* bool */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_lt_wrapper'; +/* */ + +/* */ +-- src/aggregate.rs:42 +-- typeid::aggregate::type_id_max_type_id_max_combine +CREATE FUNCTION "type_id_max_type_id_max_combine"( + "this" TypeID, /* Option < TypeID > */ + "v" TypeID /* Option < TypeID > */ +) RETURNS TypeID /* Option < TypeID > */ +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'type_id_max_type_id_max_combine_wrapper'; +/* */ + +/* */ +-- src/lib.rs:136 +-- typeid::typeid_hash_extended +CREATE FUNCTION "typeid_hash_extended"( + "typeid" TypeID, /* TypeID */ + "seed" bigint /* i64 */ +) RETURNS bigint /* i64 */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_hash_extended_wrapper'; +/* */ + +/* */ +-- src/lib.rs:103 +-- typeid::typeid_le +CREATE FUNCTION "typeid_le"( + "a" TypeID, /* TypeID */ + "b" TypeID /* TypeID */ +) RETURNS bool /* bool */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_le_wrapper'; +/* */ + +/* */ +-- src/lib.rs:123 +-- typeid::typeid_ne +CREATE FUNCTION "typeid_ne"( + "a" TypeID, /* TypeID */ + "b" TypeID /* TypeID */ +) RETURNS bool /* bool */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_ne_wrapper'; +/* */ + +/* */ +-- src/lib.rs:72 +-- typeid::typeid_to_uuid +CREATE FUNCTION "typeid_to_uuid"( + "typeid" TypeID /* TypeID */ +) RETURNS uuid /* pgrx :: Uuid */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_to_uuid_wrapper'; +/* */ + +/* */ +-- src/lib.rs:144 +-- typeid::typeid_uuid_generate_v7 +CREATE FUNCTION "typeid_uuid_generate_v7"() RETURNS uuid /* pgrx :: Uuid */ +STRICT +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'typeid_uuid_generate_v7_wrapper'; +/* */ + +/* */ +-- src/lib.rs:77 +-- typeid::uuid_to_typeid +CREATE FUNCTION "uuid_to_typeid"( + "prefix" TEXT, /* & str */ + "uuid" uuid /* pgrx :: Uuid */ +) RETURNS TypeID /* TypeID */ +IMMUTABLE STRICT PARALLEL SAFE +LANGUAGE c /* Rust */ +AS 'MODULE_PATHNAME', 'uuid_to_typeid_wrapper'; +/* */ + +/* */ +-- src/aggregate.rs:42 +-- typeid::aggregate::TypeIDMax +CREATE AGGREGATE max ( + TypeID /* TypeID */ +) +( + SFUNC = "type_id_max_type_id_max_state", /* typeid::aggregate::TypeIDMax::state */ + STYPE = TypeID, /* Option < TypeID > */ + COMBINEFUNC = "type_id_max_type_id_max_combine", /* typeid::aggregate::TypeIDMax::combine */ + PARALLEL = SAFE /* typeid::aggregate::TypeIDMax::PARALLEL */ +); +/* */ + +/* */ +-- src/aggregate.rs:12 +-- typeid::aggregate::TypeIDMin +CREATE AGGREGATE min ( + TypeID /* TypeID */ +) +( + SFUNC = "type_id_min_type_id_min_state", /* typeid::aggregate::TypeIDMin::state */ + STYPE = TypeID, /* Option < TypeID > */ + COMBINEFUNC = "type_id_min_type_id_min_combine", /* typeid::aggregate::TypeIDMin::combine */ + PARALLEL = SAFE /* typeid::aggregate::TypeIDMin::PARALLEL */ +); +/* */ + +/* */ +-- src/lib.rs:149 +-- finalize + +/* ────────────────────────────────────────────────────────────── + * Implicit cast: text → typeid + * Allows: SELECT 'user_01h…' = id; + * Context: IMPLICIT (works everywhere a typeid is expected) + * Safety: relies on typeid_in for validation; bad literals + * still fail with ERROR. + * ──────────────────────────────────────────────────────────────*/ +CREATE CAST (text AS typeid) + WITH INOUT + AS IMPLICIT; + +/* ────────────────────────────────────────────────────────────── + * Additional utility functions for better SQL integration + * ──────────────────────────────────────────────────────────────*/ + +-- Create an operator for prefix matching to enable efficient queries +-- +-- @< must actually exist. Naming it as the commutator of @> without defining +-- it leaves a shell operator behind, and any query using it fails with +-- "operator is only a shell: text @< typeid". +-- +-- contsel/contjoinsel are the estimators Postgres uses for containment-style +-- operators; without them the planner assumes a prefix test matches half the +-- table. +CREATE FUNCTION typeid_prefix_matches(text, typeid) RETURNS boolean + LANGUAGE sql + IMMUTABLE + PARALLEL SAFE + STRICT + AS $$ SELECT typeid_has_prefix($2, $1) $$; + +COMMENT ON FUNCTION typeid_prefix_matches(text, typeid) IS 'Commutator form of typeid_has_prefix - backs the @< operator'; + +CREATE OPERATOR @> ( + LEFTARG = typeid, + RIGHTARG = text, + PROCEDURE = typeid_has_prefix, + COMMUTATOR = '@<', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR @< ( + LEFTARG = text, + RIGHTARG = typeid, + PROCEDURE = typeid_prefix_matches, + COMMUTATOR = '@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +-- Create a functional index helper for prefix-based queries +-- Usage: CREATE INDEX idx_user_ids ON users (typeid_prefix(id)) WHERE typeid_has_prefix(id, 'user'); +COMMENT ON FUNCTION typeid_prefix(typeid) IS 'Extract the prefix from a TypeID for indexing and filtering'; +COMMENT ON FUNCTION typeid_has_prefix(typeid, text) IS 'Check if TypeID has a specific prefix - useful for filtering'; +COMMENT ON FUNCTION typeid_is_valid(text) IS 'Validate TypeID format without parsing - useful for constraints'; +COMMENT ON FUNCTION typeid_generate_nil() IS 'Generate TypeID with empty prefix (UUID-only format)'; + +/* Every operator below declares RESTRICT and JOIN. + * + * Without them the planner has no way to estimate how many rows a predicate + * matches, so it falls back to a fixed guess of ~50% of the table — even for + * equality on a unique primary key. At that estimate a sequential scan always + * looks cheaper than an index scan, so indexes on typeid columns are built, + * maintained, and then never used. + * + * These are the same estimators Postgres uses for its own scalar types, and + * they work here because typeid_cmp gives the type a total order (it already + * backs the btree opclass below). + * + * COMMUTATOR/NEGATOR are declared for the ordering operators too, so the + * planner can flip predicates into index-friendly form. + */ + CREATE OPERATOR < ( + LEFTARG = typeid, + RIGHTARG = typeid, + PROCEDURE = typeid_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel + ); + + CREATE OPERATOR <= ( + LEFTARG = typeid, + RIGHTARG = typeid, + PROCEDURE = typeid_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel + ); + + CREATE OPERATOR = ( + LEFTARG = typeid, + RIGHTARG = typeid, + PROCEDURE = typeid_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES + ); + + CREATE OPERATOR >= ( + LEFTARG = typeid, + RIGHTARG = typeid, + PROCEDURE = typeid_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargesel, + JOIN = scalargejoinsel + ); + + CREATE OPERATOR > ( + LEFTARG = typeid, + RIGHTARG = typeid, + PROCEDURE = typeid_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel + ); + + CREATE OPERATOR <> ( + LEFTARG = typeid, + RIGHTARG = typeid, + PROCEDURE = typeid_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel + ); + + CREATE OPERATOR CLASS typeid_ops DEFAULT FOR TYPE typeid USING btree AS + OPERATOR 1 < (typeid, typeid), + OPERATOR 2 <= (typeid, typeid), + OPERATOR 3 = (typeid, typeid), + OPERATOR 4 >= (typeid, typeid), + OPERATOR 5 > (typeid, typeid), + FUNCTION 1 typeid_cmp(typeid, typeid); + + CREATE OPERATOR FAMILY typeid_hash_ops USING hash; + + CREATE OPERATOR CLASS typeid_hash_ops DEFAULT FOR TYPE typeid USING hash AS + OPERATOR 1 = (typeid, typeid), + FUNCTION 1 typeid_hash(typeid), + FUNCTION 2 typeid_hash_extended(typeid, bigint); +/* */ +