Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/generated/generated_temporal_udfs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10264,6 +10264,17 @@ static void Gen_temporal_hash(DataChunk &args, ExpressionState &, Vector &result
});
}

static void Gen_temporal_hash_extended(DataChunk &args, ExpressionState &, Vector &result) {
EnsureMeosThreadInitialized();
BinaryExecutor::Execute<string_t, uint64_t, uint64_t>(args.data[0], args.data[1], result, args.size(),
[&](string_t in, uint64_t a2) {
Temporal *t = BlobToTemporal(in);
uint64_t r = temporal_hash_extended(t, a2);
free(t);
return r;
});
}

static void Gen_temporal_instant_n(DataChunk &args, ExpressionState &, Vector &result) {
EnsureMeosThreadInitialized();
BinaryExecutor::ExecuteWithNulls<string_t, int32_t, string_t>(args.data[0], args.data[1], result, args.size(),
Expand Down Expand Up @@ -17946,6 +17957,21 @@ static void RegisterGenerated_meos_temporal_accessor(ExtensionLoader &loader) {
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash", {TJsonbTypes::tjsonb()}, LogicalType::UINTEGER, Gen_temporal_hash));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash", {TPcpointTypes::tpcpoint()}, LogicalType::UINTEGER, Gen_temporal_hash));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash", {TPcpatchTypes::tpcpatch()}, LogicalType::UINTEGER, Gen_temporal_hash));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TemporalTypes::tint(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TemporalTypes::tbigint(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TemporalTypes::tbool(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TemporalTypes::tfloat(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TemporalTypes::ttext(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TgeompointType::tgeompoint(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TgeogpointType::tgeogpoint(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TGeometryTypes::tgeometry(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TGeographyTypes::tgeography(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {CbufferTypes::tcbuffer(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {H3indexTypes::th3index(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {QuadbinTypes::tquadbin(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TJsonbTypes::tjsonb(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TPcpointTypes::tpcpoint(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("temporal_hash_extended", {TPcpatchTypes::tpcpatch(), LogicalType::UBIGINT}, LogicalType::UBIGINT, Gen_temporal_hash_extended));
RegisterSerializedScalarFunction(loader, ScalarFunction("instantN", {TemporalTypes::tint(), LogicalType::INTEGER}, TemporalTypes::tint(), Gen_temporal_instant_n));
RegisterSerializedScalarFunction(loader, ScalarFunction("instantN", {TemporalTypes::tbigint(), LogicalType::INTEGER}, TemporalTypes::tbigint(), Gen_temporal_instant_n));
RegisterSerializedScalarFunction(loader, ScalarFunction("instantN", {TemporalTypes::tbool(), LogicalType::INTEGER}, TemporalTypes::tbool(), Gen_temporal_instant_n));
Expand Down
43 changes: 43 additions & 0 deletions test/sql/temporal_hash.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# name: test/sql/temporal_hash.test
# description: Generated hash surface for the temporal types — temporal_hash (uint32)
# and temporal_hash_extended (uint64 + seed). Both use DuckDB's native
# unsigned types (UINTEGER / UBIGINT): a MEOS hash fills the full unsigned
# range, so a signed INTEGER/BIGINT would be out of range (DuckDB range-
# checks the cast, unlike PostgreSQL/C which bit-reinterpret).

require mobilityduck

# temporal_hash returns the uint32 hash as the native UINTEGER
query I
SELECT temporal_hash(tint '1@2000-01-01');
----
157457912

query I
SELECT typeof(temporal_hash(tint '1@2000-01-01'));
----
UINTEGER

# temporal_hash_extended returns the uint64 hash as the native UBIGINT. This value
# (> 2**63) does NOT fit in a signed BIGINT — proof that UBIGINT is required.
query I
SELECT temporal_hash_extended(tint '1@2000-01-01', 0);
----
11445401048662056440

query I
SELECT typeof(temporal_hash_extended(tint '1@2000-01-01', 0));
----
UBIGINT

# the seed changes the hash (0 vs 42)
query I
SELECT temporal_hash_extended(tint '1@2000-01-01', 42);
----
10824501823241357556

# inherited over every temporal family via Temporal<T>
query I
SELECT temporal_hash_extended(tfloat '1.5@2000-01-01', 42);
----
17681145013286498633
50 changes: 37 additions & 13 deletions tools/codegen_duck_udfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ def base(canon):
# SCALAR_RET_CPP note below. The uint64 cell ids keep the CELL_UINT path.
"uint32": ("LogicalType::UINTEGER", "uint32_t"),
"uint32_t": ("LogicalType::UINTEGER", "uint32_t"),
# NB: uint64_t is deliberately NOT a generic SCALAR arg/return. A bare uint64 in
# ARG position is a Tcell<T> cell-id base value (h3index/quadbin) already typed by
# the CELL_BASEVAL comparison shape; adding it here makes a SECOND shape emit those
# comparisons -> a duplicate Gen_ body (compile error: redefinition). The only
# uint64 surface is *_hash_extended's (Temporal, seed) -> uint64, handled by
# shape_binary via SCALAR_ARG / SCALAR_RET_CPP (UBIGINT); supported() lets the
# SCALAR_ARG seed through even though arg_type has no generic uint64 mapping.
"int64": ("LogicalType::BIGINT", "int64_t"),
"int64_t": ("LogicalType::BIGINT", "int64_t"),
"double": ("LogicalType::DOUBLE", "double"),
Expand Down Expand Up @@ -152,11 +159,13 @@ def ret_type(f, out_canon):
return None
rc = f["returnType"]["canonical"]; nc = norm(rc); b = base(rc)
if b in PTR_RET and nc.endswith("*"): return (PTR_RET[b][0], "ptr")
if b in CELL_UINT and "*" not in nc: # a bare uint64 RETURN: either a Tcell<T> cell id or a hash
sc = reg_scope(f["name"]) # (checked here, not via SCALAR, so uint64 is NOT a generic
if sc and sc[0] == "types" and len(sc[1]) == 1 and sc[1][0] in CELL_BASEVAL: # scalar ARG — that
return (CELL_BASEVAL[sc[1][0]], "scalar") # would shadow the cell comparisons). A single
return ("LogicalType::UBIGINT", "scalar") # cell type -> its cell DuckDB type; any other
# uint64 (*_hash_extended) -> native UBIGINT.
if b in SCALAR and "*" not in nc: return (SCALAR[b][0], "scalar")
if b in CELL_UINT and "*" not in nc: # Tcell<T> cell-id base value (startValue/endValue)
sc = reg_scope(f["name"])
if sc and sc[0] == "types" and len(sc[1]) == 1 and sc[1][0] in CELL_BASEVAL:
return (CELL_BASEVAL[sc[1][0]], "scalar")
if b == "GSERIALIZED" and nc.endswith("*"): # owned geometry -> DuckDB GEOMETRY (geo marshaller)
return ("GeoTypes::GEOMETRY()", "geo")
if b == "Interval" and nc.endswith("*"): # owned ptr -> by-value interval_t (TakeInterval)
Expand Down Expand Up @@ -242,7 +251,16 @@ def supported(f):
if ret_type(f, out) is None:
return "ret:" + norm(f["returnType"]["canonical"])
for p in in_params:
if arg_type(p["canonical"]) is None:
# A by-value uint64 seed of a *_hash_extended fn has no generic arg_type mapping
# (uint64 is kept out of SCALAR so it does not shadow the cell-id comparisons),
# but it IS marshalled by shape_binary via SCALAR_ARG. Accept it ONLY for the
# *_hash_extended functions so temporal_hash_extended is emittable; a uint64 in
# any OTHER function is a cell-id base value that must STAY rejected here, else a
# second (generic ever/always) shape shadows the specialized cell comparison and
# emits a duplicate Gen_ body (redefinition).
if arg_type(p["canonical"]) is None and not (
base(p["canonical"]) in SCALAR_ARG and "*" not in norm(p["canonical"])
and name.endswith("_hash_extended")):
return "arg:" + norm(p["canonical"])
return None

Expand All @@ -265,11 +283,14 @@ def family(f):
"int64_t": ("int64_t", "LogicalType::BIGINT"),
# The MEOS *_hash functions return uint32 (PG hash width) and
# must register as UINTEGER, not a signed INTEGER that flips
# the sign of hashes >= 2**31. (uint64 returns — hash seeds and
# the H3Index/Quadbin cell ids — are handled elsewhere: cell
# ids via the CELL_UINT path; the *_hash_extended seed width is
# left to a follow-up so it does not surface here yet.)
# the sign of hashes >= 2**31 (DuckDB RANGE-CHECKS the cast, it
# does NOT bit-reinterpret the way PG/C does; PG only *declares*
# integer because it lacks unsigned SQL types). The uint64
# *_hash_extended value returns UBIGINT analogously (native
# unsigned, holds the full uint64 hash; the seed arg is UBIGINT
# too — see SCALAR_ARG). uint64 cell ids keep the CELL_UINT path.
"uint32_t": ("uint32_t", "LogicalType::UINTEGER"),
"uint64_t": ("uint64_t", "LogicalType::UBIGINT"),
"double": ("double", "LogicalType::DOUBLE"),
"bool": ("bool", "LogicalType::BOOLEAN")}
# By-value scalar returns the TEMPORAL detectors accept: the identity-marshalled ones
Expand Down Expand Up @@ -818,14 +839,16 @@ def shape_emittable(f):
# Take only the owned (non-const) return so the body's free(t)+TemporalToBlob stays correct.
if rb in TEMPORAL_PTR_RET and rn.endswith("*") and "const" not in f["returnType"]["canonical"]:
return ("temporal", "MD_TEMPORAL") # ret type resolved per-accessor via ret_temporal_type
if rb in BYVAL_RET and "*" not in rn:
return ("scalar:" + rb, scalar_ret_duck(f))
# Tcell<T> cell-id base value: a per-type cell accessor (reg_scope keys the single cell
# temporal type) returns the collapsed uint64 cell id -> the family's BIGINT-backed cell
# DuckDB type (startValue/endValue on th3index -> h3index()). See CELL_BASEVAL.
# temporal type) returns the collapsed uint64 cell id -> the family's cell DuckDB type
# (startValue/endValue on th3index -> h3index()). Checked BEFORE the generic by-value
# scalar branch so a uint64 cell id keeps its cell type (BYVAL_RET now includes uint64
# for *_hash_extended). See CELL_BASEVAL.
if rb in CELL_UINT and "*" not in rn and sc[0] == "types" and len(sc[1]) == 1 \
and sc[1][0] in CELL_BASEVAL:
return ("cellscalar", CELL_BASEVAL[sc[1][0]])
if rb in BYVAL_RET and "*" not in rn:
return ("scalar:" + rb, scalar_ret_duck(f))
if rb in ("Interval", "text", "char") and rn.endswith("*"): # owned-pointer scalar return (duration / text / cstring)
return ("scalar:" + rb, scalar_ret_duck(f))
# base-value pointer return (Temporal<T> accessor: startValue/endValue -> the family base value,
Expand Down Expand Up @@ -916,6 +939,7 @@ def emit_body(f, kind):
"int": ("LogicalType::INTEGER", "int32_t", "a2"),
"int32_t": ("LogicalType::INTEGER", "int32_t", "a2"),
"int64_t": ("LogicalType::BIGINT", "int64_t", "a2"),
"uint64_t": ("LogicalType::UBIGINT", "uint64_t", "a2"), # *_hash_extended seed (native unsigned)
"double": ("LogicalType::DOUBLE", "double", "a2"),
"bool": ("LogicalType::BOOLEAN", "bool", "a2"),
"TimestampTz": ("LogicalType::TIMESTAMP_TZ", "timestamp_tz_t", "DuckDBToMeosTimestamp(a2).value"),
Expand Down
Loading