From fe1a8f641c080ac1f1f722c4bd4ec0c971eb1f58 Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Thu, 3 Sep 2026 03:08:41 +0200 Subject: [PATCH] Give the generated handles the pointer boundary a caller can cross WITNESS: the root `gomeos` package calls 681 distinct MEOS C symbols by hand, and every one of them already has a wrapper in the generated `functions` package -- 3554 wrapped symbols plus 62 `#define` aliases. The hand-written package nevertheless imports `functions` 0 times, and it cannot: a cgo type belongs to the package that declares it, so this package's `*C.Temporal` and the generated package's `*C.Temporal` are different Go types, and `Inner()` returns one of them. Nothing in the generated surface exposes a type both packages can name. MEASURED: the emitter gives each opaque handle a `Pointer()` method and a `FromPointer` constructor over `unsafe.Pointer`, which is the same contract MEOS.NET's generated handles already carry as IntPtr. Three examples cross the boundary in both directions: a sequence built here reads back through `TemporalNumInstants` and `TemporalSubtype` as 3 and `Sequence`; the pointer compares equal after the round trip, so the handle is carried rather than what it points at; and `TemporalFromPointer(nil)` answers a nil handle whose `Pointer()` is nil. The suite reads 118 result lines, 0 skipped, against 115 before, and the floor moves with it. `functions/meos_meos_pointcloud.go` moves because regeneration takes the catalog as it stands and MEOS publishes two more point cloud entries than the committed snapshot carries; the snapshot is a projection, so it follows. WHY: a binding is a projection of the catalog, and the hand-written package duplicates 7377 lines of a surface the generator already produces -- including `TBoolValueAtTimestamp`, which composes `temporal_at_timestamptz` with `TBoolStartValue` where MEOS publishes `tbool_value_at_timestamptz` and the generated package wraps it. Retiring that duplication needs the two packages to exchange a handle, and the pointer is the only type they can both name. This adds no signature and removes none. --- .github/workflows/build.yml | 2 +- example_pointer_boundary_test.go | 48 +++++++++++++++++++++++++ functions/meos_meos_pointcloud.go | 22 ++++++++++++ functions/types.go | 58 +++++++++++++++++++++++++++++++ tools/codegen.py | 16 +++++++++ 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 example_pointer_boundary_test.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2886177..5a219ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -118,4 +118,4 @@ jobs: uses: MobilityDB/MEOS-API/.github/actions/check-test-outcome@master with: log: test.log - min-tests: "115" + min-tests: "118" diff --git a/example_pointer_boundary_test.go b/example_pointer_boundary_test.go new file mode 100644 index 0000000..a920f4e --- /dev/null +++ b/example_pointer_boundary_test.go @@ -0,0 +1,48 @@ +package gomeos + +// The generated package and this one each declare their own cgo view of the +// MEOS structs, so a `*C.Temporal` from one is a DIFFERENT Go type in the other +// and Inner() cannot cross between them. The opaque pointer is what both sides +// agree on, which is what these examples exercise: a handle built here reaches +// a generated wrapper, the pointer survives the crossing unchanged, and an +// absent handle stays absent. + +import ( + "fmt" + "unsafe" + + "github.com/MobilityDB/GoMEOS/functions" +) + +func ExampleTemporalFromPointer() { + tb := NewTBoolSeq("{FALSE@2022-10-01, FALSE@2022-10-02, TRUE@2022-10-03}") + + // Hand this package's handle to the generated package through the pointer, + // and let a generated wrapper read it. + crossed := functions.TemporalFromPointer(unsafe.Pointer(tb.Inner())) + instants, err := functions.TemporalNumInstants(crossed) + subtype, err2 := functions.TemporalSubtype(crossed) + fmt.Println(instants, err, subtype, err2) + // Output: + // 3 Sequence +} + +func ExampleTemporal_Pointer() { + tb := NewTBoolSeq("{FALSE@2022-10-01, TRUE@2022-10-03}") + crossed := functions.TemporalFromPointer(unsafe.Pointer(tb.Inner())) + + // The pointer survives the round trip unchanged, so the boundary carries + // the handle rather than copying what it points at. + fmt.Println(crossed.Pointer() == unsafe.Pointer(tb.Inner())) + // Output: + // true +} + +func ExampleTemporalFromPointer_absent() { + // A MEOS entry answering NULL crosses as a nil handle rather than a + // dereference, and a nil handle answers a nil pointer. + absent := functions.TemporalFromPointer(nil) + fmt.Println(absent == nil, absent.Pointer() == nil) + // Output: + // true true +} diff --git a/functions/meos_meos_pointcloud.go b/functions/meos_meos_pointcloud.go index c0f6b08..00e5dc7 100644 --- a/functions/meos_meos_pointcloud.go +++ b/functions/meos_meos_pointcloud.go @@ -556,6 +556,28 @@ func PcpatchNpoints(pa *Pcpatch) (_r0 uint32, _err error) { } +// PcpatchPointN wraps MEOS C function pcpatch_point_n. +func PcpatchPointN(pa *Pcpatch, n int) (_r0 *Pcpoint, _err error) { + C.meos_errno_reset() + _cret := C.pcpatch_point_n(pa._inner, C.int(n)) + if _err = meosError(); _err != nil { + return + } + return &Pcpoint{_inner: _cret}, nil +} + + +// PcpatchPoints wraps MEOS C function pcpatch_points. +func PcpatchPoints(pa *Pcpatch, count unsafe.Pointer) (_r0 unsafe.Pointer, _err error) { + C.meos_errno_reset() + _cret := C.pcpatch_points(pa._inner, (*C.int)(unsafe.Pointer(count))) + if _err = meosError(); _err != nil { + return + } + return unsafe.Pointer(_cret), nil +} + + // PcpatchHash wraps MEOS C function pcpatch_hash. func PcpatchHash(pa *Pcpatch) (_r0 uint32, _err error) { C.meos_errno_reset() diff --git a/functions/types.go b/functions/types.go index 1df0991..dd2a8a3 100644 --- a/functions/types.go +++ b/functions/types.go @@ -26,62 +26,120 @@ var _ = unsafe.Pointer(nil) // -------------------- opaque handle types -------------------- type Temporal struct { _inner *C.Temporal } func (x *Temporal) Inner() *C.Temporal { if x == nil { return nil }; return x._inner } +func (x *Temporal) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func TemporalFromPointer(p unsafe.Pointer) *Temporal { if p == nil { return nil }; return &Temporal{_inner: (*C.Temporal)(p)} } type TInstant struct { _inner *C.TInstant } func (x *TInstant) Inner() *C.TInstant { if x == nil { return nil }; return x._inner } +func (x *TInstant) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func TInstantFromPointer(p unsafe.Pointer) *TInstant { if p == nil { return nil }; return &TInstant{_inner: (*C.TInstant)(p)} } type TSequence struct { _inner *C.TSequence } func (x *TSequence) Inner() *C.TSequence { if x == nil { return nil }; return x._inner } +func (x *TSequence) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func TSequenceFromPointer(p unsafe.Pointer) *TSequence { if p == nil { return nil }; return &TSequence{_inner: (*C.TSequence)(p)} } type TSequenceSet struct { _inner *C.TSequenceSet } func (x *TSequenceSet) Inner() *C.TSequenceSet { if x == nil { return nil }; return x._inner } +func (x *TSequenceSet) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func TSequenceSetFromPointer(p unsafe.Pointer) *TSequenceSet { if p == nil { return nil }; return &TSequenceSet{_inner: (*C.TSequenceSet)(p)} } type STBox struct { _inner *C.STBox } func (x *STBox) Inner() *C.STBox { if x == nil { return nil }; return x._inner } +func (x *STBox) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func STBoxFromPointer(p unsafe.Pointer) *STBox { if p == nil { return nil }; return &STBox{_inner: (*C.STBox)(p)} } type TBox struct { _inner *C.TBox } func (x *TBox) Inner() *C.TBox { if x == nil { return nil }; return x._inner } +func (x *TBox) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func TBoxFromPointer(p unsafe.Pointer) *TBox { if p == nil { return nil }; return &TBox{_inner: (*C.TBox)(p)} } type Span struct { _inner *C.Span } func (x *Span) Inner() *C.Span { if x == nil { return nil }; return x._inner } +func (x *Span) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func SpanFromPointer(p unsafe.Pointer) *Span { if p == nil { return nil }; return &Span{_inner: (*C.Span)(p)} } type SpanSet struct { _inner *C.SpanSet } func (x *SpanSet) Inner() *C.SpanSet { if x == nil { return nil }; return x._inner } +func (x *SpanSet) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func SpanSetFromPointer(p unsafe.Pointer) *SpanSet { if p == nil { return nil }; return &SpanSet{_inner: (*C.SpanSet)(p)} } type Set struct { _inner *C.Set } func (x *Set) Inner() *C.Set { if x == nil { return nil }; return x._inner } +func (x *Set) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func SetFromPointer(p unsafe.Pointer) *Set { if p == nil { return nil }; return &Set{_inner: (*C.Set)(p)} } type Geom struct { _inner *C.GSERIALIZED } func (x *Geom) Inner() *C.GSERIALIZED { if x == nil { return nil }; return x._inner } +func (x *Geom) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func GeomFromPointer(p unsafe.Pointer) *Geom { if p == nil { return nil }; return &Geom{_inner: (*C.GSERIALIZED)(p)} } type Interval struct { _inner *C.Interval } func (x *Interval) Inner() *C.Interval { if x == nil { return nil }; return x._inner } +func (x *Interval) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func IntervalFromPointer(p unsafe.Pointer) *Interval { if p == nil { return nil }; return &Interval{_inner: (*C.Interval)(p)} } type Npoint struct { _inner *C.Npoint } func (x *Npoint) Inner() *C.Npoint { if x == nil { return nil }; return x._inner } +func (x *Npoint) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func NpointFromPointer(p unsafe.Pointer) *Npoint { if p == nil { return nil }; return &Npoint{_inner: (*C.Npoint)(p)} } type Nsegment struct { _inner *C.Nsegment } func (x *Nsegment) Inner() *C.Nsegment { if x == nil { return nil }; return x._inner } +func (x *Nsegment) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func NsegmentFromPointer(p unsafe.Pointer) *Nsegment { if p == nil { return nil }; return &Nsegment{_inner: (*C.Nsegment)(p)} } type Cbuffer struct { _inner *C.Cbuffer } func (x *Cbuffer) Inner() *C.Cbuffer { if x == nil { return nil }; return x._inner } +func (x *Cbuffer) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func CbufferFromPointer(p unsafe.Pointer) *Cbuffer { if p == nil { return nil }; return &Cbuffer{_inner: (*C.Cbuffer)(p)} } type Pose struct { _inner *C.Pose } func (x *Pose) Inner() *C.Pose { if x == nil { return nil }; return x._inner } +func (x *Pose) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func PoseFromPointer(p unsafe.Pointer) *Pose { if p == nil { return nil }; return &Pose{_inner: (*C.Pose)(p)} } type Jsonb struct { _inner *C.Jsonb } func (x *Jsonb) Inner() *C.Jsonb { if x == nil { return nil }; return x._inner } +func (x *Jsonb) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func JsonbFromPointer(p unsafe.Pointer) *Jsonb { if p == nil { return nil }; return &Jsonb{_inner: (*C.Jsonb)(p)} } type JsonPath struct { _inner *C.JsonPath } func (x *JsonPath) Inner() *C.JsonPath { if x == nil { return nil }; return x._inner } +func (x *JsonPath) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func JsonPathFromPointer(p unsafe.Pointer) *JsonPath { if p == nil { return nil }; return &JsonPath{_inner: (*C.JsonPath)(p)} } type Pcpoint struct { _inner *C.Pcpoint } func (x *Pcpoint) Inner() *C.Pcpoint { if x == nil { return nil }; return x._inner } +func (x *Pcpoint) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func PcpointFromPointer(p unsafe.Pointer) *Pcpoint { if p == nil { return nil }; return &Pcpoint{_inner: (*C.Pcpoint)(p)} } type Pcpatch struct { _inner *C.Pcpatch } func (x *Pcpatch) Inner() *C.Pcpatch { if x == nil { return nil }; return x._inner } +func (x *Pcpatch) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func PcpatchFromPointer(p unsafe.Pointer) *Pcpatch { if p == nil { return nil }; return &Pcpatch{_inner: (*C.Pcpatch)(p)} } type TPCBox struct { _inner *C.TPCBox } func (x *TPCBox) Inner() *C.TPCBox { if x == nil { return nil }; return x._inner } +func (x *TPCBox) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func TPCBoxFromPointer(p unsafe.Pointer) *TPCBox { if p == nil { return nil }; return &TPCBox{_inner: (*C.TPCBox)(p)} } type MeosArray struct { _inner *C.MeosArray } func (x *MeosArray) Inner() *C.MeosArray { if x == nil { return nil }; return x._inner } +func (x *MeosArray) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func MeosArrayFromPointer(p unsafe.Pointer) *MeosArray { if p == nil { return nil }; return &MeosArray{_inner: (*C.MeosArray)(p)} } type PCSchema struct { _inner *C.PCSCHEMA } func (x *PCSchema) Inner() *C.PCSCHEMA { if x == nil { return nil }; return x._inner } +func (x *PCSchema) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func PCSchemaFromPointer(p unsafe.Pointer) *PCSchema { if p == nil { return nil }; return &PCSchema{_inner: (*C.PCSCHEMA)(p)} } type SkipList struct { _inner *C.SkipList } func (x *SkipList) Inner() *C.SkipList { if x == nil { return nil }; return x._inner } +func (x *SkipList) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func SkipListFromPointer(p unsafe.Pointer) *SkipList { if p == nil { return nil }; return &SkipList{_inner: (*C.SkipList)(p)} } type RTree struct { _inner *C.RTree } func (x *RTree) Inner() *C.RTree { if x == nil { return nil }; return x._inner } +func (x *RTree) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func RTreeFromPointer(p unsafe.Pointer) *RTree { if p == nil { return nil }; return &RTree{_inner: (*C.RTree)(p)} } type Match struct { _inner *C.Match } func (x *Match) Inner() *C.Match { if x == nil { return nil }; return x._inner } +func (x *Match) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func MatchFromPointer(p unsafe.Pointer) *Match { if p == nil { return nil }; return &Match{_inner: (*C.Match)(p)} } type Box3D struct { _inner *C.BOX3D } func (x *Box3D) Inner() *C.BOX3D { if x == nil { return nil }; return x._inner } +func (x *Box3D) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func Box3DFromPointer(p unsafe.Pointer) *Box3D { if p == nil { return nil }; return &Box3D{_inner: (*C.BOX3D)(p)} } type GBox struct { _inner *C.GBOX } func (x *GBox) Inner() *C.GBOX { if x == nil { return nil }; return x._inner } +func (x *GBox) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func GBoxFromPointer(p unsafe.Pointer) *GBox { if p == nil { return nil }; return &GBox{_inner: (*C.GBOX)(p)} } type AFFINE struct { _inner *C.AFFINE } func (x *AFFINE) Inner() *C.AFFINE { if x == nil { return nil }; return x._inner } +func (x *AFFINE) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func AFFINEFromPointer(p unsafe.Pointer) *AFFINE { if p == nil { return nil }; return &AFFINE{_inner: (*C.AFFINE)(p)} } type PJContext struct { _inner *C.PJ_CONTEXT } func (x *PJContext) Inner() *C.PJ_CONTEXT { if x == nil { return nil }; return x._inner } +func (x *PJContext) Pointer() unsafe.Pointer { if x == nil { return nil }; return unsafe.Pointer(x._inner) } +func PJContextFromPointer(p unsafe.Pointer) *PJContext { if p == nil { return nil }; return &PJContext{_inner: (*C.PJ_CONTEXT)(p)} } // -------------------- enums -------------------- type TempSubtype C.tempSubtype diff --git a/tools/codegen.py b/tools/codegen.py index a719ee6..ddc10f9 100644 --- a/tools/codegen.py +++ b/tools/codegen.py @@ -276,6 +276,22 @@ def referenced(go_name: str) -> bool: f"func (x *{go_name}) Inner() *C.{c_name} {{ " f"if x == nil {{ return nil }}; return x._inner }}" ) + # A cgo type belongs to the package that declares it, so this package's + # `*C.{c_name}` and a caller's own `*C.{c_name}` are DIFFERENT Go types + # and Inner() cannot cross a package boundary. The handle is an opaque + # MEOS pointer either way, so the pointer itself is the boundary -- the + # same contract MEOS.NET's generated handles carry as IntPtr. A nil + # handle yields a nil pointer and a nil pointer yields a nil handle, so + # a MEOS entry answering NULL cannot become a dereference here. + out.append( + f"func (x *{go_name}) Pointer() unsafe.Pointer {{ " + f"if x == nil {{ return nil }}; return unsafe.Pointer(x._inner) }}" + ) + out.append( + f"func {go_name}FromPointer(p unsafe.Pointer) *{go_name} {{ " + f"if p == nil {{ return nil }}; " + f"return &{go_name}{{_inner: (*C.{c_name})(p)}} }}" + ) # -- enums: Go type aliased to the C enum + a numeric const block -------- out.append("\n// -------------------- enums --------------------")