2020-02-24 16:26:01 +00:00
|
|
|
package database
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"github.com/hunterlong/statping/types"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type GroupBy struct {
|
|
|
|
db Database
|
|
|
|
query *GroupQuery
|
|
|
|
}
|
|
|
|
|
|
|
|
type GroupByer interface {
|
|
|
|
ToTimeValue(interface{}) (*TimeVar, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type By string
|
|
|
|
|
|
|
|
func (b By) String() string {
|
|
|
|
return string(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
type GroupQuery struct {
|
|
|
|
db Database
|
|
|
|
Start time.Time
|
|
|
|
End time.Time
|
|
|
|
Group string
|
|
|
|
Order string
|
|
|
|
Limit int
|
|
|
|
Offset int
|
|
|
|
FillEmpty bool
|
|
|
|
}
|
|
|
|
|
2020-02-25 07:41:28 +00:00
|
|
|
func (b GroupQuery) Find(data interface{}) error {
|
|
|
|
return b.db.Find(&data).Error()
|
|
|
|
}
|
|
|
|
|
2020-02-24 16:26:01 +00:00
|
|
|
func (b GroupQuery) Database() Database {
|
|
|
|
return b.db
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2020-02-25 07:41:28 +00:00
|
|
|
ByCount = By("COUNT(id) as amount")
|
2020-02-24 16:26:01 +00:00
|
|
|
ByAverage = func(column string) By {
|
|
|
|
return By(fmt.Sprintf("AVG(%s) as amount", column))
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func (db *Db) GroupQuery(q *GroupQuery, by By) GroupByer {
|
|
|
|
dbQuery := db.MultipleSelects(
|
|
|
|
db.SelectByTime(q.Group),
|
|
|
|
by.String(),
|
|
|
|
).Group("timeframe")
|
|
|
|
|
|
|
|
return &GroupBy{dbQuery, q}
|
|
|
|
}
|
|
|
|
|
|
|
|
type TimeVar struct {
|
|
|
|
g *GroupBy
|
|
|
|
data []*TimeValue
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TimeVar) ToValues() []*TimeValue {
|
|
|
|
return t.data
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GroupBy) toFloatRows() []*TimeValue {
|
|
|
|
rows, err := g.db.Rows()
|
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var data []*TimeValue
|
|
|
|
for rows.Next() {
|
|
|
|
var timeframe time.Time
|
|
|
|
amount := float64(0)
|
|
|
|
rows.Scan(&timeframe, &amount)
|
|
|
|
newTs := types.FixedTime(timeframe, g.duration())
|
|
|
|
data = append(data, &TimeValue{
|
|
|
|
Timeframe: newTs,
|
|
|
|
Amount: amount,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GroupBy) ToTimeValue(dbType interface{}) (*TimeVar, error) {
|
|
|
|
rows, err := g.db.Rows()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var data []*TimeValue
|
|
|
|
for rows.Next() {
|
|
|
|
var timeframe time.Time
|
|
|
|
amount := float64(0)
|
|
|
|
rows.Scan(&timeframe, &amount)
|
|
|
|
newTs := types.FixedTime(timeframe, g.duration())
|
|
|
|
data = append(data, &TimeValue{
|
|
|
|
Timeframe: newTs,
|
|
|
|
Amount: amount,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return &TimeVar{g, data}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TimeVar) FillMissing(current, end time.Time) []*TimeValue {
|
|
|
|
timeMap := make(map[string]float64)
|
|
|
|
var validSet []*TimeValue
|
|
|
|
dur := t.g.duration()
|
|
|
|
for _, v := range t.data {
|
|
|
|
timeMap[v.Timeframe] = v.Amount
|
|
|
|
}
|
|
|
|
|
|
|
|
currentStr := types.FixedTime(current, t.g.duration())
|
|
|
|
|
|
|
|
for {
|
|
|
|
var amount float64
|
|
|
|
if timeMap[currentStr] != 0 {
|
|
|
|
amount = timeMap[currentStr]
|
|
|
|
}
|
|
|
|
validSet = append(validSet, &TimeValue{
|
|
|
|
Timeframe: currentStr,
|
|
|
|
Amount: amount,
|
|
|
|
})
|
|
|
|
if current.After(end) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
current = current.Add(dur)
|
|
|
|
currentStr = types.FixedTime(current, t.g.duration())
|
|
|
|
}
|
|
|
|
|
|
|
|
return validSet
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GroupBy) duration() time.Duration {
|
|
|
|
switch g.query.Group {
|
|
|
|
case "second":
|
|
|
|
return types.Second
|
|
|
|
case "minute":
|
|
|
|
return types.Minute
|
|
|
|
case "hour":
|
|
|
|
return types.Hour
|
|
|
|
case "day":
|
|
|
|
return types.Day
|
|
|
|
case "month":
|
|
|
|
return types.Month
|
|
|
|
case "year":
|
|
|
|
return types.Year
|
|
|
|
default:
|
|
|
|
return types.Hour
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-25 07:41:28 +00:00
|
|
|
func toInt(v string) int64 {
|
|
|
|
val, _ := strconv.Atoi(v)
|
|
|
|
return int64(val)
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseQueries(r *http.Request, o isObject) *GroupQuery {
|
2020-02-24 16:26:01 +00:00
|
|
|
fields := parseGet(r)
|
|
|
|
grouping := fields.Get("group")
|
|
|
|
if grouping == "" {
|
|
|
|
grouping = "hour"
|
|
|
|
}
|
2020-02-25 07:41:28 +00:00
|
|
|
startField := toInt(fields.Get("start"))
|
|
|
|
endField := toInt(fields.Get("end"))
|
|
|
|
limit := toInt(fields.Get("limit"))
|
|
|
|
offset := toInt(fields.Get("offset"))
|
2020-02-24 16:26:01 +00:00
|
|
|
fill, _ := strconv.ParseBool(fields.Get("fill"))
|
|
|
|
orderBy := fields.Get("order")
|
|
|
|
if limit == 0 {
|
|
|
|
limit = 10000
|
|
|
|
}
|
|
|
|
|
|
|
|
query := &GroupQuery{
|
|
|
|
Start: time.Unix(startField, 0).UTC(),
|
|
|
|
End: time.Unix(endField, 0).UTC(),
|
|
|
|
Group: grouping,
|
|
|
|
Order: orderBy,
|
|
|
|
Limit: int(limit),
|
|
|
|
Offset: int(offset),
|
|
|
|
FillEmpty: fill,
|
|
|
|
}
|
|
|
|
|
2020-02-25 07:41:28 +00:00
|
|
|
db := o.object().db
|
|
|
|
|
2020-02-24 16:26:01 +00:00
|
|
|
if query.Limit != 0 {
|
|
|
|
db = db.Limit(query.Limit)
|
|
|
|
}
|
|
|
|
if query.Offset > 0 {
|
|
|
|
db = db.Offset(query.Offset)
|
|
|
|
}
|
|
|
|
if !query.Start.IsZero() && !query.End.IsZero() {
|
|
|
|
db = db.Where("created_at BETWEEN ? AND ?", db.FormatTime(query.Start), db.FormatTime(query.End))
|
|
|
|
} else {
|
|
|
|
if !query.Start.IsZero() {
|
|
|
|
db = db.Where("created_at > ?", db.FormatTime(query.Start))
|
|
|
|
}
|
|
|
|
if !query.End.IsZero() {
|
|
|
|
db = db.Where("created_at < ?", db.FormatTime(query.End))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if query.Order != "" {
|
|
|
|
db = db.Order(query.Order)
|
|
|
|
}
|
|
|
|
query.db = db.Debug()
|
|
|
|
|
|
|
|
return query
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseForm(r *http.Request) url.Values {
|
|
|
|
r.ParseForm()
|
|
|
|
return r.PostForm
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseGet(r *http.Request) url.Values {
|
|
|
|
r.ParseForm()
|
|
|
|
return r.Form
|
|
|
|
}
|