Skip to content

Commit c042132

Browse files
committed
Make format and value nullable and improve maintenance and purge queries
1 parent b68646c commit c042132

File tree

6 files changed

+154
-58
lines changed

6 files changed

+154
-58
lines changed

base/database/storage/sqlite/migrations/1_initial.sql

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
CREATE TABLE records (
44
key TEXT PRIMARY KEY,
55

6-
format SMALLINT NOT NULL,
7-
value BLOB NOT NULL,
6+
format SMALLINT,
7+
value BLOB,
88

99
created BIGINT NOT NULL,
1010
modified BIGINT NOT NULL,

base/database/storage/sqlite/models/records.bob.go

+26-24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

base/database/storage/sqlite/sqlite.go

+109-22
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ import (
1010
"time"
1111

1212
"github.com/aarondl/opt/omit"
13+
"github.com/aarondl/opt/omitnull"
1314
migrate "github.com/rubenv/sql-migrate"
15+
sqldblogger "github.com/simukti/sqldb-logger"
16+
"github.com/stephenafamo/bob"
17+
"github.com/stephenafamo/bob/dialect/sqlite"
18+
"github.com/stephenafamo/bob/dialect/sqlite/im"
19+
"github.com/stephenafamo/bob/dialect/sqlite/um"
20+
_ "modernc.org/sqlite"
21+
1422
"github.com/safing/portmaster/base/database/accessor"
1523
"github.com/safing/portmaster/base/database/iterator"
1624
"github.com/safing/portmaster/base/database/query"
@@ -19,12 +27,6 @@ import (
1927
"github.com/safing/portmaster/base/database/storage/sqlite/models"
2028
"github.com/safing/portmaster/base/log"
2129
"github.com/safing/structures/dsd"
22-
"github.com/stephenafamo/bob"
23-
"github.com/stephenafamo/bob/dialect/sqlite"
24-
"github.com/stephenafamo/bob/dialect/sqlite/im"
25-
"github.com/stephenafamo/bob/dialect/sqlite/um"
26-
27-
_ "modernc.org/sqlite"
2830
)
2931

3032
// SQLite storage.
@@ -47,14 +49,40 @@ func init() {
4749

4850
// NewSQLite creates a sqlite database.
4951
func NewSQLite(name, location string) (*SQLite, error) {
52+
return openSQLite(name, location, false)
53+
}
54+
55+
// openSQLite creates a sqlite database.
56+
func openSQLite(name, location string, printStmts bool) (*SQLite, error) {
5057
dbFile := filepath.Join(location, "db.sqlite")
5158

5259
// Open database file.
60+
// Default settings:
61+
// _time_format = YYYY-MM-DDTHH:MM:SS.SSS
62+
// _txlock = deferred
5363
db, err := sql.Open("sqlite", dbFile)
5464
if err != nil {
5565
return nil, fmt.Errorf("open sqlite: %w", err)
5666
}
5767

68+
// Enable statement printing.
69+
if printStmts {
70+
db = sqldblogger.OpenDriver(dbFile, db.Driver(), &statementLogger{})
71+
}
72+
73+
// Set other settings.
74+
pragmas := []string{
75+
"PRAGMA journal_mode=WAL;", // Corruption safe write ahead log for txs.
76+
"PRAGMA synchronous=NORMAL;", // Best for WAL.
77+
"PRAGMA cache_size=-10000;", // 10MB Cache.
78+
}
79+
for _, pragma := range pragmas {
80+
_, err := db.Exec(pragma)
81+
if err != nil {
82+
return nil, fmt.Errorf("failed to init sqlite with %s: %w", pragma, err)
83+
}
84+
}
85+
5886
// Run migrations on database.
5987
n, err := migrate.Exec(db, "sqlite3", getMigrations(), migrate.Up)
6088
if err != nil {
@@ -84,7 +112,13 @@ func (db *SQLite) Get(key string) (record.Record, error) {
84112
}
85113

86114
// Return data in wrapper.
87-
return record.NewWrapperFromDatabase(db.name, key, getMeta(r), uint8(r.Format), r.Value)
115+
return record.NewWrapperFromDatabase(
116+
db.name,
117+
key,
118+
getMeta(r),
119+
uint8(r.Format.GetOrZero()),
120+
r.Value.GetOrZero(),
121+
)
88122
}
89123

90124
// GetMeta returns the metadata of a database record.
@@ -114,13 +148,20 @@ func (db *SQLite) putRecord(r record.Record, tx *bob.Tx) (record.Record, error)
114148
if err != nil {
115149
return nil, err
116150
}
151+
// Prepare for setter.
152+
setFormat := omitnull.From(int16(dsd.JSON))
153+
setData := omitnull.From(data)
154+
if len(data) == 0 {
155+
setFormat.Null()
156+
setData.Null()
157+
}
117158

118159
// Create structure for insert.
119160
m := r.Meta()
120161
setter := models.RecordSetter{
121162
Key: omit.From(r.DatabaseKey()),
122-
Format: omit.From(int16(dsd.JSON)),
123-
Value: omit.From(data),
163+
Format: setFormat,
164+
Value: setData,
124165
Created: omit.From(m.Created),
125166
Modified: omit.From(m.Modified),
126167
Expires: omit.From(m.Expires),
@@ -269,15 +310,25 @@ recordsLoop:
269310

270311
// Check Data.
271312
if q.HasWhereCondition() {
272-
jsonData := string(r.Value)
313+
if r.Format.IsNull() || r.Value.IsNull() {
314+
continue recordsLoop
315+
}
316+
317+
jsonData := string(r.Value.GetOrZero())
273318
jsonAccess := accessor.NewJSONAccessor(&jsonData)
274319
if !q.MatchesAccessor(jsonAccess) {
275320
continue recordsLoop
276321
}
277322
}
278323

279324
// Build database record.
280-
matched, _ := record.NewWrapperFromDatabase(db.name, r.Key, m, uint8(r.Format), r.Value)
325+
matched, _ := record.NewWrapperFromDatabase(
326+
db.name,
327+
r.Key,
328+
m,
329+
uint8(r.Format.GetOrZero()),
330+
r.Value.GetOrZero(),
331+
)
281332

282333
select {
283334
case <-queryIter.Done:
@@ -301,7 +352,7 @@ recordsLoop:
301352

302353
// Purge deletes all records that match the given query. It returns the number of successful deletes and an error.
303354
func (db *SQLite) Purge(ctx context.Context, q *query.Query, local, internal, shadowDelete bool) (int, error) {
304-
// Optimize for local and internal queries without where clause.
355+
// Optimize for local and internal queries without where clause and without shadow delete.
305356
if local && internal && !shadowDelete && !q.HasWhereCondition() {
306357
db.lock.Lock()
307358
defer db.lock.Unlock()
@@ -321,21 +372,49 @@ func (db *SQLite) Purge(ctx context.Context, q *query.Query, local, internal, sh
321372
return int(n), err
322373
}
323374

375+
// Optimize for local and internal queries without where clause, but with shadow delete.
376+
if local && internal && shadowDelete && !q.HasWhereCondition() {
377+
db.lock.Lock()
378+
defer db.lock.Unlock()
379+
380+
// First count entries (SQLite does not support affected rows)
381+
n, err := models.Records.Query(
382+
models.SelectWhere.Records.Key.Like(q.DatabaseKeyPrefix()+"%"),
383+
).Count(db.ctx, db.bob)
384+
if err != nil || n == 0 {
385+
return int(n), err
386+
}
387+
388+
// Mark purged records as deleted.
389+
now := time.Now().Unix()
390+
_, err = models.Records.Update(
391+
um.SetCol("format").ToArg(nil),
392+
um.SetCol("value").ToArg(nil),
393+
um.SetCol("deleted").ToArg(now),
394+
models.UpdateWhere.Records.Key.Like(q.DatabaseKeyPrefix()+"%"),
395+
).Exec(db.ctx, db.bob)
396+
return int(n), err
397+
}
398+
324399
// Otherwise, iterate over all entries and delete matching ones.
400+
return 0, storage.ErrNotImplemented
325401

326402
// Create iterator to check all matching records.
327-
queryIter := iterator.New()
328-
defer queryIter.Cancel()
329-
go db.queryExecutor(queryIter, q, local, internal)
330403

331-
// Delete all matching records.
332-
var deleted int
333-
for r := range queryIter.Next {
334-
db.Delete(r.DatabaseKey())
335-
deleted++
336-
}
404+
// TODO: This is untested and also needs handling of shadowDelete.
405+
// For now: Use only without where condition and with a local and internal db interface.
406+
// queryIter := iterator.New()
407+
// defer queryIter.Cancel()
408+
// go db.queryExecutor(queryIter, q, local, internal)
409+
410+
// // Delete all matching records.
411+
// var deleted int
412+
// for r := range queryIter.Next {
413+
// db.Delete(r.DatabaseKey())
414+
// deleted++
415+
// }
337416

338-
return deleted, nil
417+
// return deleted, nil
339418
}
340419

341420
// ReadOnly returns whether the database is read only.
@@ -360,6 +439,8 @@ func (db *SQLite) MaintainRecordStates(ctx context.Context, purgeDeletedBefore t
360439
if shadowDelete {
361440
// Mark expired records as deleted.
362441
models.Records.Update(
442+
um.SetCol("format").ToArg(nil),
443+
um.SetCol("value").ToArg(nil),
363444
um.SetCol("deleted").ToArg(now),
364445
models.UpdateWhere.Records.Deleted.EQ(0),
365446
models.UpdateWhere.Records.Expires.GT(0),
@@ -416,3 +497,9 @@ func (db *SQLite) Shutdown() error {
416497

417498
return db.bob.Close()
418499
}
500+
501+
type statementLogger struct{}
502+
503+
func (sl statementLogger) Log(ctx context.Context, level sqldblogger.Level, msg string, data map[string]interface{}) {
504+
fmt.Printf("SQL: %s --- %+v\n", msg, data)
505+
}

0 commit comments

Comments
 (0)