2024-10-22 23:32:57 +01:00
|
|
|
package sql3util
|
2024-01-03 00:54:30 +00:00
|
|
|
|
2025-12-04 15:27:16 +00:00
|
|
|
import (
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
2024-01-03 00:54:30 +00:00
|
|
|
|
|
|
|
|
// NamedArg splits an named arg into a key and value,
|
|
|
|
|
// around an equals sign.
|
|
|
|
|
// Spaces are trimmed around both key and value.
|
|
|
|
|
func NamedArg(arg string) (key, val string) {
|
|
|
|
|
key, val, _ = strings.Cut(arg, "=")
|
|
|
|
|
key = strings.TrimSpace(key)
|
|
|
|
|
val = strings.TrimSpace(val)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unquote unquotes a string.
|
2024-10-24 00:22:20 +01:00
|
|
|
//
|
|
|
|
|
// https://sqlite.org/lang_keywords.html
|
2024-01-03 00:54:30 +00:00
|
|
|
func Unquote(val string) string {
|
|
|
|
|
if len(val) < 2 {
|
|
|
|
|
return val
|
|
|
|
|
}
|
2024-10-22 23:32:57 +01:00
|
|
|
fst := val[0]
|
|
|
|
|
lst := val[len(val)-1]
|
|
|
|
|
rst := val[1 : len(val)-1]
|
|
|
|
|
if fst == '[' && lst == ']' {
|
|
|
|
|
return rst
|
|
|
|
|
}
|
|
|
|
|
if fst != lst {
|
2024-01-03 00:54:30 +00:00
|
|
|
return val
|
|
|
|
|
}
|
|
|
|
|
var old, new string
|
2024-10-22 23:32:57 +01:00
|
|
|
switch fst {
|
2024-01-03 00:54:30 +00:00
|
|
|
default:
|
|
|
|
|
return val
|
2024-09-22 12:09:23 +01:00
|
|
|
case '`':
|
|
|
|
|
old, new = "``", "`"
|
2024-10-22 23:32:57 +01:00
|
|
|
case '"':
|
|
|
|
|
old, new = `""`, `"`
|
2024-01-03 00:54:30 +00:00
|
|
|
case '\'':
|
|
|
|
|
old, new = `''`, `'`
|
|
|
|
|
}
|
2024-10-22 23:32:57 +01:00
|
|
|
return strings.ReplaceAll(rst, old, new)
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-24 00:22:20 +01:00
|
|
|
// ParseBool parses a boolean.
|
|
|
|
|
//
|
|
|
|
|
// https://sqlite.org/pragma.html#syntax
|
2024-10-22 23:32:57 +01:00
|
|
|
func ParseBool(s string) (b, ok bool) {
|
|
|
|
|
if len(s) == 0 {
|
|
|
|
|
return false, false
|
|
|
|
|
}
|
|
|
|
|
if s[0] == '0' {
|
|
|
|
|
return false, true
|
|
|
|
|
}
|
|
|
|
|
if '1' <= s[0] && s[0] <= '9' {
|
|
|
|
|
return true, true
|
|
|
|
|
}
|
|
|
|
|
switch strings.ToLower(s) {
|
|
|
|
|
case "true", "yes", "on":
|
|
|
|
|
return true, true
|
|
|
|
|
case "false", "no", "off":
|
|
|
|
|
return false, true
|
|
|
|
|
}
|
|
|
|
|
return false, false
|
2024-01-03 00:54:30 +00:00
|
|
|
}
|
2025-12-04 15:27:16 +00:00
|
|
|
|
|
|
|
|
// ParseTimeShift parses a time shift modifier,
|
|
|
|
|
// also the output of timediff.
|
|
|
|
|
//
|
|
|
|
|
// https://sqlite.org/lang_datefunc.html
|
|
|
|
|
func ParseTimeShift(s string) (years, months, days int, duration time.Duration, ok bool) {
|
|
|
|
|
// Sign part: ±
|
|
|
|
|
neg := strings.HasPrefix(s, "-")
|
|
|
|
|
sign := neg || strings.HasPrefix(s, "+")
|
|
|
|
|
if sign {
|
|
|
|
|
s = s[1:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ok = len(s) >= 5; !ok {
|
|
|
|
|
return // !ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
if neg {
|
|
|
|
|
years = -years
|
|
|
|
|
months = -months
|
|
|
|
|
days = -days
|
|
|
|
|
duration = -duration
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
// Date part: YYYY-MM-DD
|
|
|
|
|
if s[4] == '-' {
|
|
|
|
|
if ok = sign && len(s) >= 10 && s[7] == '-'; !ok {
|
|
|
|
|
return // !ok
|
|
|
|
|
}
|
|
|
|
|
if years, ok = parseInt(s[0:4]); !ok {
|
|
|
|
|
return // !ok
|
|
|
|
|
}
|
|
|
|
|
if months, ok = parseInt(s[5:7]); !ok {
|
|
|
|
|
return // !ok
|
|
|
|
|
}
|
|
|
|
|
if days, ok = parseInt(s[8:10]); !ok {
|
|
|
|
|
return // !ok
|
|
|
|
|
}
|
|
|
|
|
if len(s) == 10 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if ok = s[10] == ' '; !ok {
|
|
|
|
|
return // !ok
|
|
|
|
|
}
|
|
|
|
|
s = s[11:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Time part: HH:MM
|
|
|
|
|
if ok = len(s) >= 5 && s[2] == ':'; !ok {
|
|
|
|
|
return // !ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var hours, minutes int
|
|
|
|
|
if hours, ok = parseInt(s[0:2]); !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if minutes, ok = parseInt(s[3:5]); !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
duration = time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute
|
|
|
|
|
|
|
|
|
|
if len(s) == 5 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if ok = len(s) >= 8 && s[5] == ':'; !ok {
|
|
|
|
|
return // !ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Seconds part: HH:MM:SS
|
|
|
|
|
var seconds int
|
|
|
|
|
if seconds, ok = parseInt(s[6:8]); !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
duration += time.Duration(seconds) * time.Second
|
|
|
|
|
|
|
|
|
|
if len(s) == 8 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if ok = len(s) >= 10 && s[8] == '.'; !ok {
|
|
|
|
|
return // !ok
|
|
|
|
|
}
|
|
|
|
|
s = s[9:]
|
|
|
|
|
|
|
|
|
|
// Nanosecond part: HH:MM:SS.SSS
|
|
|
|
|
var nanos int
|
|
|
|
|
if nanos, ok = parseInt(s[0:min(9, len(s))]); !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for i := len(s); i < 9; i++ {
|
|
|
|
|
nanos *= 10
|
|
|
|
|
}
|
|
|
|
|
duration += time.Duration(nanos)
|
|
|
|
|
|
|
|
|
|
// Subnanosecond part.
|
|
|
|
|
if len(s) > 9 {
|
|
|
|
|
_, ok = parseInt(s[9:])
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseInt(s string) (i int, _ bool) {
|
|
|
|
|
for _, r := range []byte(s) {
|
|
|
|
|
r -= '0'
|
|
|
|
|
if r > 9 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
i = i*10 + int(r)
|
|
|
|
|
}
|
|
|
|
|
return i, true
|
|
|
|
|
}
|