@@ -10,7 +10,15 @@ import (
10
10
"time"
11
11
12
12
"github.com/aarondl/opt/omit"
13
+ "github.com/aarondl/opt/omitnull"
13
14
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
+
14
22
"github.com/safing/portmaster/base/database/accessor"
15
23
"github.com/safing/portmaster/base/database/iterator"
16
24
"github.com/safing/portmaster/base/database/query"
@@ -19,12 +27,6 @@ import (
19
27
"github.com/safing/portmaster/base/database/storage/sqlite/models"
20
28
"github.com/safing/portmaster/base/log"
21
29
"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"
28
30
)
29
31
30
32
// SQLite storage.
@@ -47,14 +49,40 @@ func init() {
47
49
48
50
// NewSQLite creates a sqlite database.
49
51
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 ) {
50
57
dbFile := filepath .Join (location , "db.sqlite" )
51
58
52
59
// Open database file.
60
+ // Default settings:
61
+ // _time_format = YYYY-MM-DDTHH:MM:SS.SSS
62
+ // _txlock = deferred
53
63
db , err := sql .Open ("sqlite" , dbFile )
54
64
if err != nil {
55
65
return nil , fmt .Errorf ("open sqlite: %w" , err )
56
66
}
57
67
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
+
58
86
// Run migrations on database.
59
87
n , err := migrate .Exec (db , "sqlite3" , getMigrations (), migrate .Up )
60
88
if err != nil {
@@ -84,7 +112,13 @@ func (db *SQLite) Get(key string) (record.Record, error) {
84
112
}
85
113
86
114
// 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
+ )
88
122
}
89
123
90
124
// 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)
114
148
if err != nil {
115
149
return nil , err
116
150
}
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
+ }
117
158
118
159
// Create structure for insert.
119
160
m := r .Meta ()
120
161
setter := models.RecordSetter {
121
162
Key : omit .From (r .DatabaseKey ()),
122
- Format : omit . From ( int16 ( dsd . JSON )) ,
123
- Value : omit . From ( data ) ,
163
+ Format : setFormat ,
164
+ Value : setData ,
124
165
Created : omit .From (m .Created ),
125
166
Modified : omit .From (m .Modified ),
126
167
Expires : omit .From (m .Expires ),
@@ -269,15 +310,25 @@ recordsLoop:
269
310
270
311
// Check Data.
271
312
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 ())
273
318
jsonAccess := accessor .NewJSONAccessor (& jsonData )
274
319
if ! q .MatchesAccessor (jsonAccess ) {
275
320
continue recordsLoop
276
321
}
277
322
}
278
323
279
324
// 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
+ )
281
332
282
333
select {
283
334
case <- queryIter .Done :
@@ -301,7 +352,7 @@ recordsLoop:
301
352
302
353
// Purge deletes all records that match the given query. It returns the number of successful deletes and an error.
303
354
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 .
305
356
if local && internal && ! shadowDelete && ! q .HasWhereCondition () {
306
357
db .lock .Lock ()
307
358
defer db .lock .Unlock ()
@@ -321,21 +372,49 @@ func (db *SQLite) Purge(ctx context.Context, q *query.Query, local, internal, sh
321
372
return int (n ), err
322
373
}
323
374
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
+
324
399
// Otherwise, iterate over all entries and delete matching ones.
400
+ return 0 , storage .ErrNotImplemented
325
401
326
402
// Create iterator to check all matching records.
327
- queryIter := iterator .New ()
328
- defer queryIter .Cancel ()
329
- go db .queryExecutor (queryIter , q , local , internal )
330
403
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
+ // }
337
416
338
- return deleted , nil
417
+ // return deleted, nil
339
418
}
340
419
341
420
// ReadOnly returns whether the database is read only.
@@ -360,6 +439,8 @@ func (db *SQLite) MaintainRecordStates(ctx context.Context, purgeDeletedBefore t
360
439
if shadowDelete {
361
440
// Mark expired records as deleted.
362
441
models .Records .Update (
442
+ um .SetCol ("format" ).ToArg (nil ),
443
+ um .SetCol ("value" ).ToArg (nil ),
363
444
um .SetCol ("deleted" ).ToArg (now ),
364
445
models .UpdateWhere .Records .Deleted .EQ (0 ),
365
446
models .UpdateWhere .Records .Expires .GT (0 ),
@@ -416,3 +497,9 @@ func (db *SQLite) Shutdown() error {
416
497
417
498
return db .bob .Close ()
418
499
}
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