2018-05-15 13:30:37 +02:00
|
|
|
package sqlstore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/usefathom/fathom/pkg/models"
|
|
|
|
)
|
|
|
|
|
2018-07-11 15:03:56 +02:00
|
|
|
// GetPageview selects a single pageview by its string ID
|
|
|
|
func (db *sqlstore) GetPageview(id string) (*models.Pageview, error) {
|
2018-05-15 13:30:37 +02:00
|
|
|
result := &models.Pageview{}
|
2018-07-11 15:03:56 +02:00
|
|
|
query := db.Rebind(`SELECT * FROM pageviews WHERE id = ? LIMIT 1`)
|
|
|
|
err := db.Get(result, query, id)
|
|
|
|
|
2018-05-15 13:30:37 +02:00
|
|
|
if err != nil {
|
2018-10-05 14:34:39 +02:00
|
|
|
return nil, mapError(err)
|
2018-05-15 13:30:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2018-07-12 13:30:32 +02:00
|
|
|
// InsertPageviews inserts multiple pageviews using a single INSERT statement
|
|
|
|
func (db *sqlstore) InsertPageviews(pageviews []*models.Pageview) error {
|
2018-07-15 07:37:45 +02:00
|
|
|
n := len(pageviews)
|
|
|
|
if n == 0 {
|
2018-07-12 13:30:32 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-07-15 09:36:28 +02:00
|
|
|
// generate placeholders string
|
2018-10-05 10:07:00 +02:00
|
|
|
placeholderTemplate := "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?),"
|
2018-09-21 09:30:05 +02:00
|
|
|
placeholders := strings.Repeat(placeholderTemplate, n)
|
2018-07-15 09:36:28 +02:00
|
|
|
placeholders = placeholders[:len(placeholders)-1]
|
2018-09-21 09:30:05 +02:00
|
|
|
nPlaceholders := strings.Count(placeholderTemplate, "?")
|
2018-07-12 13:30:32 +02:00
|
|
|
|
2018-07-15 10:05:03 +02:00
|
|
|
// init values slice with correct length
|
2018-09-21 09:30:05 +02:00
|
|
|
nValues := n * nPlaceholders
|
2018-07-15 10:05:03 +02:00
|
|
|
values := make([]interface{}, nValues)
|
|
|
|
|
|
|
|
// overwrite nil values in slice
|
|
|
|
j := 0
|
|
|
|
for i := range pageviews {
|
2018-09-21 09:30:05 +02:00
|
|
|
j = i * nPlaceholders
|
2018-07-15 10:05:03 +02:00
|
|
|
values[j] = pageviews[i].ID
|
2018-10-05 10:07:00 +02:00
|
|
|
values[j+1] = pageviews[i].SiteTrackingID
|
|
|
|
values[j+2] = pageviews[i].Hostname
|
|
|
|
values[j+3] = pageviews[i].Pathname
|
|
|
|
values[j+4] = pageviews[i].IsNewVisitor
|
|
|
|
values[j+5] = pageviews[i].IsNewSession
|
|
|
|
values[j+6] = pageviews[i].IsUnique
|
|
|
|
values[j+7] = pageviews[i].IsBounce
|
|
|
|
values[j+8] = pageviews[i].Referrer
|
|
|
|
values[j+9] = pageviews[i].Duration
|
|
|
|
values[j+10] = pageviews[i].Timestamp
|
2018-07-12 13:30:32 +02:00
|
|
|
}
|
2018-07-15 07:37:45 +02:00
|
|
|
|
2018-07-15 10:05:03 +02:00
|
|
|
// string together query & execute with values
|
2018-10-05 10:07:00 +02:00
|
|
|
query := `INSERT INTO pageviews(id, site_tracking_id, hostname, pathname, is_new_visitor, is_new_session, is_unique, is_bounce, referrer, duration, timestamp) VALUES ` + placeholders
|
2018-07-12 13:30:32 +02:00
|
|
|
query = db.Rebind(query)
|
|
|
|
_, err := db.Exec(query, values...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdatePageviews updates multiple pageviews using a single transaction
|
|
|
|
// Please note that this function only updates the IsBounce and Duration properties
|
|
|
|
func (db *sqlstore) UpdatePageviews(pageviews []*models.Pageview) error {
|
|
|
|
if len(pageviews) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
tx, err := db.Beginx()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-07-30 09:33:49 +02:00
|
|
|
|
2018-07-12 13:30:32 +02:00
|
|
|
query := tx.Rebind(`UPDATE pageviews SET is_bounce = ?, duration = ? WHERE id = ?`)
|
|
|
|
stmt, err := tx.Preparex(query)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-07-15 10:05:03 +02:00
|
|
|
for i := range pageviews {
|
2018-07-30 10:51:20 +02:00
|
|
|
_, err := stmt.Exec(pageviews[i].IsBounce, pageviews[i].Duration, pageviews[i].ID)
|
2018-07-30 09:33:49 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
tx.Rollback()
|
|
|
|
return err
|
|
|
|
}
|
2018-07-12 13:30:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
err = tx.Commit()
|
2018-07-11 15:03:56 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetProcessablePageviews selects all pageviews which are "done" (ie not still waiting for bounce flag or duration)
|
2018-05-15 13:30:37 +02:00
|
|
|
func (db *sqlstore) GetProcessablePageviews() ([]*models.Pageview, error) {
|
|
|
|
var results []*models.Pageview
|
|
|
|
thirtyMinsAgo := time.Now().Add(-30 * time.Minute)
|
2018-06-08 15:39:38 +02:00
|
|
|
// We use FALSE here, even though SQLite has no BOOLEAN value. If it fails, maybe we can roll our own Rebind?
|
|
|
|
query := db.Rebind(`SELECT * FROM pageviews WHERE ( duration > 0 AND is_bounce = FALSE ) OR timestamp < ? LIMIT 500`)
|
2018-05-15 13:30:37 +02:00
|
|
|
err := db.Select(&results, query, thirtyMinsAgo)
|
|
|
|
return results, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *sqlstore) DeletePageviews(pageviews []*models.Pageview) error {
|
|
|
|
ids := []string{}
|
|
|
|
for _, p := range pageviews {
|
2018-07-11 15:03:56 +02:00
|
|
|
ids = append(ids, "'"+p.ID+"'")
|
2018-05-15 13:30:37 +02:00
|
|
|
}
|
|
|
|
query := db.Rebind(`DELETE FROM pageviews WHERE id IN(` + strings.Join(ids, ",") + `)`)
|
|
|
|
_, err := db.Exec(query)
|
|
|
|
return err
|
|
|
|
}
|