diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a219ef..2980a50 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: "118" + min-tests: "121" diff --git a/example_main_tbool_test.go b/example_main_tbool_test.go index b4f94fd..035c005 100644 --- a/example_main_tbool_test.go +++ b/example_main_tbool_test.go @@ -30,10 +30,10 @@ func ExampleTBoolOut() { func ExampleTBoolValueAtTimestamp() { g_is := NewTBoolSeq("{FALSE@2022-10-01, FALSE@2022-10-02,TRUE@2022-10-03}") ts, _ := time.Parse("2006-01-02", "2022-10-01") - res := TBoolValueAtTimestamp(g_is, ts) - fmt.Println(res) + res, ok, err := TBoolValueAtTimestamp(g_is, ts) + fmt.Println(res, ok, err) // Output: - // false + // false true } func ExampleEverEqTBoolBool() { diff --git a/example_main_tfloat_test.go b/example_main_tfloat_test.go index ef809b6..87d637c 100644 --- a/example_main_tfloat_test.go +++ b/example_main_tfloat_test.go @@ -32,8 +32,8 @@ func ExampleTFloatValues() { func ExampleTFloatValueAtTimestamp() { tf_seq := TFloatIn("{1.2@2022-10-01, 2.3@2022-10-02,3.4@2022-10-03}", &TFloatSeq{}) ts, _ := time.Parse("2006-01-02", "2022-10-01") - res := TFloatValueAtTimestamp(tf_seq, ts) - fmt.Println(res) + res, ok, err := TFloatValueAtTimestamp(tf_seq, ts) + fmt.Println(res, ok, err) // Output: - // 1.2 + // 1.2 true } diff --git a/example_value_absent_test.go b/example_value_absent_test.go new file mode 100644 index 0000000..8fa4e15 --- /dev/null +++ b/example_value_absent_test.go @@ -0,0 +1,43 @@ +package gomeos + +// A temporal value holds nothing outside its own span, and MEOS says so by +// answering NULL. These examples pin what the accessors do with that answer: +// they report absence through a second result and leave the error free for a +// MEOS failure, and the nil handle it arrives as is a value the caller can test +// rather than something to dereference. + +import ( + "fmt" + "time" +) + +func ExampleTBoolValueAtTimestamp_absent() { + tb := NewTBoolSeq("{FALSE@2022-10-01, FALSE@2022-10-02, TRUE@2022-10-03}") + outside, _ := time.Parse("2006-01-02", "2021-01-01") + + value, ok, err := TBoolValueAtTimestamp(tb, outside) + fmt.Println(value, ok, err) + // Output: + // false false +} + +func ExampleTFloatValueAtTimestamp_absent() { + tf := TFloatIn("{1.2@2022-10-01, 2.3@2022-10-02, 3.4@2022-10-03}", &TFloatSeq{}) + outside, _ := time.Parse("2006-01-02", "2021-01-01") + + value, ok, err := TFloatValueAtTimestamp(tf, outside) + fmt.Println(value, ok, err) + // Output: + // 0 false +} + +func ExampleCreateTemporal_absent() { + tb := NewTBoolSeq("{FALSE@2022-10-01, TRUE@2022-10-03}") + outside, _ := time.Parse("2006-01-02", "2021-01-01") + + // Restricting to a moment the value does not cover leaves nothing, which + // reaches Go as a nil Temporal rather than a handle onto nothing. + fmt.Println(TemporalAtTimestamptz(tb, outside) == nil) + // Output: + // true +} diff --git a/interfaces.go b/interfaces.go index bc6e642..2bdc506 100644 --- a/interfaces.go +++ b/interfaces.go @@ -97,7 +97,14 @@ type NumSpan interface { IsNumSpan() bool } +// CreateTemporal wraps a MEOS temporal value in the Go type its temptype and +// subtype name. A MEOS entry answers NULL for an empty result — a restriction +// that selects nothing, for one — so a nil pointer is a value rather than a +// fault, and it arrives back as a nil Temporal for the caller to test. func CreateTemporal(inner *C.Temporal) Temporal { + if inner == nil { + return nil + } meosType := inner.temptype subtype := inner.subtype // meosType MeosType, subtype MeosTemporalSubtype diff --git a/main_tbool.go b/main_tbool.go index 50da233..98083f0 100644 --- a/main_tbool.go +++ b/main_tbool.go @@ -11,6 +11,8 @@ import ( "fmt" "time" "unsafe" + + "github.com/MobilityDB/GoMEOS/functions" ) type TBoolInst struct { @@ -198,10 +200,21 @@ func TBoolEndValue[TB TBool](tb TB) bool { return bool(cValue) } -// TBoolValueAtTimestamp Return the value of a temporal boolean at a timestamptz -func TBoolValueAtTimestamp[TB TBool](tb TB, ts time.Time) bool { - tboolinst, _ := TemporalToTBoolInst(TemporalAtTimestamptz(tb, ts)) - return TBoolStartValue(tboolinst) +// TBoolValueAtTimestamp Return the value of a temporal boolean at a timestamptz. +// The second result reports whether the value holds anything at that moment; +// false leaves the first at its zero value. An error is reserved for a MEOS +// failure, which is a different thing from absence. +func TBoolValueAtTimestamp[TB TBool](tb TB, ts time.Time) (bool, bool, error) { + found, value, err := functions.TboolValueAtTimestamptz( + functions.TemporalFromPointer(unsafe.Pointer(tb.Inner())), + int64(DatetimeToTimestamptz(ts)), true) + if err != nil { + return false, false, err + } + if !found { + return false, false, nil + } + return value, true, nil } // AlwaysEqTBoolBool Return true if a temporal boolean is always equal to a boolean diff --git a/main_tfloat.go b/main_tfloat.go index ac04d6e..44b0293 100644 --- a/main_tfloat.go +++ b/main_tfloat.go @@ -11,6 +11,8 @@ import ( "fmt" "time" "unsafe" + + "github.com/MobilityDB/GoMEOS/functions" ) type TFloatInst struct { @@ -368,10 +370,21 @@ func TFloatMinusValue[TF TFloat](tf TF, value float64) Temporal { return CreateTemporal(c_tbools) } -// TFloatValueAtTimestamp Return the value of a temporal float at a timestamptz -func TFloatValueAtTimestamp[TF TFloat](tf TF, ts time.Time) float64 { - tfloatinst, _ := TemporalToTFloatInst(TemporalAtTimestamptz(tf, ts)) - return TFloatStartValue(tfloatinst) +// TFloatValueAtTimestamp Return the value of a temporal float at a timestamptz. +// The second result reports whether the value holds anything at that moment; +// false leaves the first at its zero value. An error is reserved for a MEOS +// failure, which is a different thing from absence. +func TFloatValueAtTimestamp[TF TFloat](tf TF, ts time.Time) (float64, bool, error) { + found, value, err := functions.TfloatValueAtTimestamptz( + functions.TemporalFromPointer(unsafe.Pointer(tf.Inner())), + int64(DatetimeToTimestamptz(ts)), true) + if err != nil { + return 0, false, err + } + if !found { + return 0, false, nil + } + return value, true, nil } // TFloatDerivative Return the derivative of a temporal number diff --git a/main_tint.go b/main_tint.go index a1787a2..facfaa0 100644 --- a/main_tint.go +++ b/main_tint.go @@ -11,6 +11,8 @@ import ( "fmt" "time" "unsafe" + + "github.com/MobilityDB/GoMEOS/functions" ) type TIntInst struct { @@ -252,10 +254,21 @@ func TIntMaxValue[TB TInt](tb TB) int { return int(cValue) } -// TIntValueAtTimestamp Return the value of a temporal int at a timestamptz -func TIntValueAtTimestamp[TF TInt](tf TF, ts time.Time) int { - tintinst, _ := TemporalToTIntInst(TemporalAtTimestamptz(tf, ts)) - return TIntStartValue(tintinst) +// TIntValueAtTimestamp Return the value of a temporal int at a timestamptz. +// The second result reports whether the value holds anything at that moment; +// false leaves the first at its zero value. An error is reserved for a MEOS +// failure, which is a different thing from absence. +func TIntValueAtTimestamp[TF TInt](tf TF, ts time.Time) (int, bool, error) { + found, value, err := functions.TintValueAtTimestamptz( + functions.TemporalFromPointer(unsafe.Pointer(tf.Inner())), + int64(DatetimeToTimestamptz(ts)), true) + if err != nil { + return 0, false, err + } + if !found { + return 0, false, nil + } + return value, true, nil } // AlwaysLtTIntInt Return true if a temporal integer is always less than an integer diff --git a/main_tpoint.go b/main_tpoint.go index 866f3ad..1642b3e 100644 --- a/main_tpoint.go +++ b/main_tpoint.go @@ -11,6 +11,8 @@ import ( "fmt" "time" "unsafe" + + "github.com/MobilityDB/GoMEOS/functions" ) // TPointOut Return a temporal geometry/geography point from its Well-Known Text (WKT) representation @@ -66,10 +68,20 @@ func TPointEndValue[TP TPoint](tp TP) *Geom { return &Geom{_inner: cValue} } -// TPointValueAtTimestamp Return the value of a temporal point at a timestamptz -func TPointValueAtTimestamp[TP TPoint](tp TP, ts time.Time) *Geom { - tpointinst, _ := TemporalToGeomPointInst(TemporalAtTimestamptz(tp, ts)) - return TPointStartValue(tpointinst) +// TPointValueAtTimestamp Return the value of a temporal point at a timestamptz. +// A temporal value that holds nothing at that moment yields an error rather than +// a value. +func TPointValueAtTimestamp[TP TPoint](tp TP, ts time.Time) (*Geom, bool, error) { + found, value, err := functions.TgeoValueAtTimestamptz( + functions.TemporalFromPointer(unsafe.Pointer(tp.Inner())), + int64(DatetimeToTimestamptz(ts)), true) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + return &Geom{_inner: (*C.GSERIALIZED)(value.Pointer())}, true, nil } // TPointValueSet Return the array of base values of a temporal geometry point diff --git a/main_ttext.go b/main_ttext.go index b6e3fcc..0b660b3 100644 --- a/main_ttext.go +++ b/main_ttext.go @@ -11,6 +11,8 @@ import "C" import ( "time" "unsafe" + + "github.com/MobilityDB/GoMEOS/functions" ) type TTextInst struct { @@ -191,10 +193,18 @@ func TTextMaxValue[TT TText](tt TT) string { return C.GoString(C.text_out(cValue)) } -// TTextValueAtTimestamp Return the value of a temporal text at a timestamptz -func TTextValueAtTimestamp[TT TText](tt TT, ts time.Time) string { - ttextinst, _ := TemporalToTTextInst(TemporalAtTimestamptz(tt, ts)) - return TTextStartValue(ttextinst) +// TTextValueAtTimestamp Return the value of a temporal text at a timestamptz. +// The second result reports whether the value holds anything at that moment; +// false leaves the first at its zero value. An error is reserved for a MEOS +// failure, which is a different thing from absence. +func TTextValueAtTimestamp[TT TText](tt TT, ts time.Time) (string, bool, error) { + found, value, err := functions.TtextValueAtTimestamptz( + functions.TemporalFromPointer(unsafe.Pointer(tt.Inner())), + int64(DatetimeToTimestamptz(ts)), true) + if err != nil { + return "", false, err + } + return value, found, nil } // TTextUpper Return a temporal text transformed to uppercase