| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182 |
- package executor
- import (
- "fmt"
- "math"
- "strconv"
- "strings"
- "time"
- )
- // SQLiteCompatVersion is the SQLite version reported by sqlite_version(). It is
- // deliberately decoupled from PizzaSQL's own version (pizzasql_version()) so
- // clients that gate behavior on a minimum SQLite version see a consistent,
- // documented compatibility floor. 3.35.0 is the first release with
- // INSERT/UPDATE/DELETE ... RETURNING, which GoatCounter's release-2.7 port
- // relies on; the JSON1 and generated-column surface this engine implements is
- // documented against that same floor.
- const SQLiteCompatVersion = "3.35.0"
- // evalPercentDiff implements the scalar percent_diff(start, final) function used
- // by GoatCounter's hit_list.DiffTotal query. It matches the existing function:
- // a zero start yields +Inf, and standard SQL NULL propagation applies. Fewer or
- // more than two arguments is an error.
- func evalPercentDiff(args []interface{}) (interface{}, error) {
- if len(args) != 2 {
- return nil, fmt.Errorf("percent_diff() requires exactly 2 arguments")
- }
- if args[0] == nil || args[1] == nil {
- return nil, nil
- }
- start := toFloat(args[0])
- final := toFloat(args[1])
- if start == 0 {
- return math.Inf(1), nil
- }
- return (final - start) / start * 100.0, nil
- }
- // formatSQLiteReal renders a REAL the way SQLite's text conversion does for
- // concatenation: the shortest round-tripping decimal, with a fractional part
- // retained for integral values so `1.0 || 'px'` yields "1.0px" like SQLite. The
- // generated-size queries concatenate numeric columns, so this must not fall back
- // to Go's "1" or "true"/"1e+06" spellings.
- func formatSQLiteReal(f float64) string {
- if math.IsNaN(f) {
- return "NaN"
- }
- if math.IsInf(f, 1) {
- return "Inf"
- }
- if math.IsInf(f, -1) {
- return "-Inf"
- }
- // SQLite's %!.15g keeps 15 significant digits and always shows a decimal
- // point for non-integral values; integral values get a trailing ".0".
- if f == math.Trunc(f) && math.Abs(f) < 1e15 {
- return strconv.FormatFloat(f, 'f', 1, 64)
- }
- s := strconv.FormatFloat(f, 'g', 15, 64)
- if !strings.ContainsAny(s, ".eE") {
- s += ".0"
- }
- return s
- }
- // sqliteCurrentTimeValue resolves the SQLite special date/time keywords
- // CURRENT_TIMESTAMP, CURRENT_DATE, and CURRENT_TIME. The lexer treats them as
- // plain identifiers, so they are recognized here (only when the row has no real
- // column of that name) to support DEFAULT current_timestamp and expressions
- // like strftime('%Y', current_timestamp).
- func sqliteCurrentTimeValue(name string) (interface{}, bool) {
- now := time.Now().UTC()
- switch strings.ToLower(name) {
- case "current_timestamp":
- return now.Format("2006-01-02 15:04:05"), true
- case "current_date":
- return now.Format("2006-01-02"), true
- case "current_time":
- return now.Format("15:04:05"), true
- }
- return nil, false
- }
|