mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
deploy / build (push) Waiting to run
core/sqlite_math_required.go failed any cgo build not carrying the
sqlite_math_functions tag, by referencing a symbol that was never defined
anywhere. So `go build ./...` — no tags, default CGO_ENABLED — did not
work in this repo or in any repo importing core, and the error named a
symbol rather than the problem:
undefined: cgoBuildNeedsSQLiteMathFunctions
The capability it was protecting is real. tools/search emits acos, cos,
sin and radians for geoDistance, and SQLite only has those when compiled
with SQLITE_ENABLE_MATH_FUNCTIONS. Measured, per config:
CGO_ENABLED=0 present (what the Dockerfile ships)
CGO_ENABLED=1 ABSENT <- the one deficient config
CGO_ENABLED=1 -tags sqlite_math_functions present
CGO_ENABLED=1 -tags libsqlite3 present <- blocked anyway
The tag was a proxy for the capability, and the proxy was wrong. Row four
is the config the driver's own doc comment calls production: that tag
links the system sqlcipher, so the amalgamation's -D flag is never
compiled and the tag decides nothing, yet the guard rejected the build.
It also could not have caught a system libsqlcipher genuinely missing the
functions, and no library can satisfy it from the inside, because build
tags are chosen by the top-level build command.
Replace it with a measurement. VerifySQLiteMathFunctions runs the
geoDistance expression once per process against a throwaway in-memory
database and DefaultDBConnect refuses to hand back a connection when the
answer is no, so a deficient binary dies at startup with the three ways
to fix it rather than at a customer's failed search. A probe that could
not run at all is not an error: this reports a measured absence, never a
failure to measure.
Detection is strictly better than before. tools/search already had
TestTokenFunctionsGeoDistanceExec executing the real emitted SQL, and it
fails on the deficient config today; core now carries its own, since
tools/search does not import core and its failure said nothing about
this package.
build, all four configs above exit 0 (was: 2 of 4 refused)
go test ./... CGO_ENABLED=0 64 packages ok, 0 failures
go test ./core -run SQLiteMath CGO=1 FAIL, as it should
Co-authored-by: Hanzo Dev <[email protected]>
94 lines
4.1 KiB
Go
94 lines
4.1 KiB
Go
package core
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/hanzoai/dbx"
|
|
)
|
|
|
|
// Base's search layer emits SQL that calls acos, cos, sin and radians — see the
|
|
// geoDistance token function in tools/search. SQLite only has those when it was
|
|
// compiled with SQLITE_ENABLE_MATH_FUNCTIONS, so a build can link an engine
|
|
// whose SQL surface is smaller than the one the query builder writes against,
|
|
// and nothing complains until someone filters by geoDistance and gets
|
|
// "no such function: acos" from an endpoint that works elsewhere.
|
|
//
|
|
// Which builds are deficient is a property of the linked engine, not of any
|
|
// build tag. Measured on this tree:
|
|
//
|
|
// CGO_ENABLED=0 present (pure-Go modernc; what the Dockerfile ships)
|
|
// CGO_ENABLED=1 ABSENT <- the one deficient config
|
|
// CGO_ENABLED=1 -tags sqlite_math_functions present (csqlite compiles the amalgamation with -DSQLITE_ENABLE_MATH_FUNCTIONS)
|
|
// CGO_ENABLED=1 -tags libsqlite3 present (links the system sqlcipher, which already has them)
|
|
//
|
|
// This was previously a compile-time guard that failed any cgo build not
|
|
// carrying the sqlite_math_functions tag. It tested a proxy rather than the
|
|
// property: it rejected -tags libsqlite3 builds whose SQL surface is complete
|
|
// (row four — that tag selects the system library, so the amalgamation's -D
|
|
// flag is never compiled), it could never catch a system libsqlcipher that
|
|
// genuinely lacked the functions, and no library can satisfy it from the
|
|
// inside, because build tags are chosen by the top-level build command. Its
|
|
// only mechanism was an undefined symbol, so every consumer of this package
|
|
// lost `go build ./...` to an error that named a symbol rather than the
|
|
// problem.
|
|
//
|
|
// So ask the engine instead of the build.
|
|
|
|
// errNoSQLiteMathFunctions is returned by VerifySQLiteMathFunctions when the
|
|
// linked SQLite is measurably missing the math functions.
|
|
var errNoSQLiteMathFunctions = errors.New("base: the SQLite engine linked into this binary has no math functions")
|
|
|
|
// mathProbeSQL is the shape tools/search emits for geoDistance: every math
|
|
// function the filter builder can produce, in one round trip.
|
|
const mathProbeSQL = `SELECT 6371 * acos(cos(radians(1)) * cos(radians(2)) * cos(radians(3) - radians(4)) + sin(radians(1)) * sin(radians(2)))`
|
|
|
|
var (
|
|
mathOnce sync.Once
|
|
mathErr error
|
|
)
|
|
|
|
// VerifySQLiteMathFunctions reports whether the SQLite engine linked into this
|
|
// binary implements the math functions the search layer's geoDistance filter
|
|
// emits. It answers from a throwaway in-memory database, so a caller's own
|
|
// connection is neither opened nor kept warm on its behalf, and it measures
|
|
// once per process — the answer is a property of the linked engine, not of any
|
|
// database file.
|
|
//
|
|
// A definitive "no such function" is an error: a capability the query builder
|
|
// assumes is not something to discover from a customer's failed search. A probe
|
|
// that could not run at all — no "sqlite" driver registered under the
|
|
// no_default_driver tag, or an environment that refuses an in-memory database —
|
|
// is not: this reports a measured absence, never a failure to measure.
|
|
func VerifySQLiteMathFunctions() error {
|
|
mathOnce.Do(func() { mathErr = probeSQLiteMathFunctions() })
|
|
return mathErr
|
|
}
|
|
|
|
func probeSQLiteMathFunctions() error {
|
|
db, err := dbx.Open("sqlite", "file:base_sqlite_math_probe?mode=memory")
|
|
if err != nil {
|
|
return nil // inconclusive
|
|
}
|
|
defer db.Close()
|
|
|
|
var distance float64
|
|
if err := db.NewQuery(mathProbeSQL).Row(&distance); err != nil {
|
|
// Both backends phrase a missing scalar function this way; anything
|
|
// else is a probe that failed for reasons of its own.
|
|
if strings.Contains(err.Error(), "no such function") {
|
|
return fmt.Errorf(
|
|
"%w (%v) — search filters using geoDistance would fail at query time. "+
|
|
"Rebuild with CGO_ENABLED=0 (how Base ships), or with -tags sqlite_math_functions, "+
|
|
"or with -tags libsqlite3 to link a system SQLite that already has them",
|
|
errNoSQLiteMathFunctions, err,
|
|
)
|
|
}
|
|
return nil // inconclusive
|
|
}
|
|
|
|
return nil
|
|
}
|