compat.go 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. package executor
  2. import (
  3. "fmt"
  4. "math"
  5. "strconv"
  6. "strings"
  7. "time"
  8. )
  9. // SQLiteCompatVersion is the SQLite version reported by sqlite_version(). It is
  10. // deliberately decoupled from PizzaSQL's own version (pizzasql_version()) so
  11. // clients that gate behavior on a minimum SQLite version see a consistent,
  12. // documented compatibility floor. 3.35.0 is the first release with
  13. // INSERT/UPDATE/DELETE ... RETURNING, which GoatCounter's release-2.7 port
  14. // relies on; the JSON1 and generated-column surface this engine implements is
  15. // documented against that same floor.
  16. const SQLiteCompatVersion = "3.35.0"
  17. // evalPercentDiff implements the scalar percent_diff(start, final) function used
  18. // by GoatCounter's hit_list.DiffTotal query. It matches the existing function:
  19. // a zero start yields +Inf, and standard SQL NULL propagation applies. Fewer or
  20. // more than two arguments is an error.
  21. func evalPercentDiff(args []interface{}) (interface{}, error) {
  22. if len(args) != 2 {
  23. return nil, fmt.Errorf("percent_diff() requires exactly 2 arguments")
  24. }
  25. if args[0] == nil || args[1] == nil {
  26. return nil, nil
  27. }
  28. start := toFloat(args[0])
  29. final := toFloat(args[1])
  30. if start == 0 {
  31. return math.Inf(1), nil
  32. }
  33. return (final - start) / start * 100.0, nil
  34. }
  35. // formatSQLiteReal renders a REAL the way SQLite's text conversion does for
  36. // concatenation: the shortest round-tripping decimal, with a fractional part
  37. // retained for integral values so `1.0 || 'px'` yields "1.0px" like SQLite. The
  38. // generated-size queries concatenate numeric columns, so this must not fall back
  39. // to Go's "1" or "true"/"1e+06" spellings.
  40. func formatSQLiteReal(f float64) string {
  41. if math.IsNaN(f) {
  42. return "NaN"
  43. }
  44. if math.IsInf(f, 1) {
  45. return "Inf"
  46. }
  47. if math.IsInf(f, -1) {
  48. return "-Inf"
  49. }
  50. // SQLite's %!.15g keeps 15 significant digits and always shows a decimal
  51. // point for non-integral values; integral values get a trailing ".0".
  52. if f == math.Trunc(f) && math.Abs(f) < 1e15 {
  53. return strconv.FormatFloat(f, 'f', 1, 64)
  54. }
  55. s := strconv.FormatFloat(f, 'g', 15, 64)
  56. if !strings.ContainsAny(s, ".eE") {
  57. s += ".0"
  58. }
  59. return s
  60. }
  61. // sqliteCurrentTimeValue resolves the SQLite special date/time keywords
  62. // CURRENT_TIMESTAMP, CURRENT_DATE, and CURRENT_TIME. The lexer treats them as
  63. // plain identifiers, so they are recognized here (only when the row has no real
  64. // column of that name) to support DEFAULT current_timestamp and expressions
  65. // like strftime('%Y', current_timestamp).
  66. func sqliteCurrentTimeValue(name string) (interface{}, bool) {
  67. now := time.Now().UTC()
  68. switch strings.ToLower(name) {
  69. case "current_timestamp":
  70. return now.Format("2006-01-02 15:04:05"), true
  71. case "current_date":
  72. return now.Format("2006-01-02"), true
  73. case "current_time":
  74. return now.Format("15:04:05"), true
  75. }
  76. return nil, false
  77. }