forked from sourcegraph/checkup
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sql.go
187 lines (161 loc) · 4.46 KB
/
sql.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package checkup
import (
"encoding/json"
"errors"
"strconv"
"time"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // Enable postgresql beckend
_ "github.com/mattn/go-sqlite3" // Enable sqlite3 backend
)
// schema is the table schema expected by the sqlite3 checkup storage.
const schema = `
CREATE TABLE checks (
name TEXT NOT NULL PRIMARY KEY,
timestamp INT8 NOT NULL,
results TEXT
);
CREATE UNIQUE INDEX idx_checks_timestamp ON checks(timestamp);
`
// SQL is a way to store checkup results in a SQL database.
type SQL struct {
// SqliteDBFile is the sqlite3 DB where check results will be stored.
SqliteDBFile string `json:"sqlite_db_file,omitempty"`
// PostgreSQL contains the Postgres connection settings.
PostgreSQL *struct {
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
User string `json:"user"`
Password string `json:"password,omitempty"`
DBName string `json:"dbname"`
SSLMode string `json:"sslmode,omitempty"`
} `json:"postgresql"`
// Check files older than CheckExpiry will be
// deleted on calls to Maintain(). If this is
// the zero value, no old check files will be
// deleted.
CheckExpiry time.Duration `json:"check_expiry,omitempty"`
}
func (sql SQL) dbConnect() (*sqlx.DB, error) {
// Only one SQL backend can be present
if sql.SqliteDBFile != "" && sql.PostgreSQL != nil {
return nil, errors.New("several SQL backends are configured")
}
// SQLite3 configuration
if sql.SqliteDBFile != "" {
return sqlx.Connect("sqlite3", sql.SqliteDBFile)
}
// PostgreSQL configuration
if sql.PostgreSQL != nil {
var pgOptions string
if sql.PostgreSQL.DBName == "" {
return nil, errors.New("missing PostgreSQL database name")
}
if sql.PostgreSQL.User == "" {
return nil, errors.New("missing PostgreSQL username")
}
if sql.PostgreSQL.Host != "" {
pgOptions += " host=" + sql.PostgreSQL.Host
}
if sql.PostgreSQL.Port != 0 {
pgOptions += " port=" + strconv.Itoa(sql.PostgreSQL.Port)
}
pgOptions += " user=" + sql.PostgreSQL.User
if sql.PostgreSQL.Password != "" {
pgOptions += " password=" + sql.PostgreSQL.Password
}
pgOptions += " dbname=" + sql.PostgreSQL.DBName
if sql.PostgreSQL.SSLMode != "" {
pgOptions += " sslmode=" + sql.PostgreSQL.SSLMode
}
return sqlx.Connect("postgres", pgOptions)
}
// TODO: MySQL backend?
return nil, errors.New("no configured database backend")
}
// GetIndex returns the list of check results for the database.
func (sql SQL) GetIndex() (map[string]int64, error) {
db, err := sql.dbConnect()
if err != nil {
return nil, err
}
defer db.Close()
idx := make(map[string]int64)
var check struct {
Name string `db:"name"`
Timestamp int64 `db:"timestamp"`
}
rows, err := db.Queryx(`SELECT name,timestamp FROM "checks"`)
if err != nil {
return nil, err
}
for rows.Next() {
err := rows.StructScan(&check)
if err != nil {
rows.Close()
return nil, err
}
idx[check.Name] = check.Timestamp
}
return idx, nil
}
// Fetch fetches results of the check with given name.
func (sql SQL) Fetch(name string) ([]Result, error) {
db, err := sql.dbConnect()
if err != nil {
return nil, err
}
defer db.Close()
var checkResult []byte
var results []Result
err = db.Get(&checkResult, `SELECT results FROM "checks" WHERE name=$1 LIMIT 1`, name)
if err != nil {
return nil, err
}
if err = json.Unmarshal(checkResult, &results); err != nil {
return nil, err
}
return results, nil
}
// Store stores results in the database.
func (sql SQL) Store(results []Result) error {
db, err := sql.dbConnect()
if err != nil {
return err
}
defer db.Close()
name := *GenerateFilename()
contents, err := json.Marshal(results)
if err != nil {
return err
}
// Insert data
const insertResults = `INSERT INTO "checks" (name, timestamp, results) VALUES (?, ?, ?)`
_, err = db.Exec(insertResults, name, time.Now().UnixNano(), contents)
return err
}
// Maintain deletes check files that are older than sql.CheckExpiry.
func (sql SQL) Maintain() error {
if sql.CheckExpiry == 0 {
return nil
}
db, err := sql.dbConnect()
if err != nil {
return err
}
defer db.Close()
const st = `DELETE FROM "checks" WHERE timestamp < ?`
ts := time.Now().Add(-1 * sql.CheckExpiry).UnixNano()
_, err = db.Exec(st, ts)
return err
}
// initialize creates the "checks" table in the database.
func (sql SQL) initialize() error {
db, err := sql.dbConnect()
if err != nil {
return err
}
defer db.Close()
_, err = db.Exec(schema)
return err
}