Skip to content

Commit 32c7f6e

Browse files
committedMar 17, 2025
Merge branch 'feature/sqlite-db-v1' into develop
2 parents 4f3ebc7 + 9b12dff commit 32c7f6e

32 files changed

+2234
-110
lines changed
 

‎.github/workflows/go.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ jobs:
3838
cache: false
3939

4040
- name: Run golangci-lint
41-
uses: golangci/golangci-lint-action@v4
41+
uses: golangci/golangci-lint-action@v6
4242
with:
43-
version: v1.60.3
43+
version: v1.64.6
4444
only-new-issues: true
4545
args: -c ./.golangci.yml --timeout 15m
4646

‎.golangci.yml

+3-10
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,15 @@ linters:
88
- contextcheck
99
- cyclop
1010
- depguard
11-
- exhaustivestruct
1211
- exhaustruct
1312
- forbidigo
1413
- funlen
1514
- gochecknoglobals
1615
- gochecknoinits
1716
- gocognit
1817
- gocyclo
19-
- goerr113
20-
- gomnd
2118
- gomoddirectives
22-
- ifshort
2319
- interfacebloat
24-
- interfacer
2520
- ireturn
2621
- lll
2722
- mnd
@@ -32,7 +27,6 @@ linters:
3227
- noctx
3328
- nolintlint
3429
- nonamedreturns
35-
- nosnakecase
3630
- perfsprint # TODO(ppacher): we should re-enanble this one to avoid costly fmt.* calls in the hot-path
3731
- revive
3832
- tagliatelle
@@ -42,15 +36,14 @@ linters:
4236
- whitespace
4337
- wrapcheck
4438
- wsl
39+
- gci
40+
- tenv # Deprecated
4541

4642
linters-settings:
4743
revive:
4844
# See https://github.com/mgechev/revive#available-rules for details.
4945
enable-all-rules: true
50-
gci:
51-
# put imports beginning with prefix after 3rd-party packages;
52-
# only support one prefix
53-
# if not set, use goimports.local-prefixes
46+
goimports:
5447
local-prefixes: github.com/safing
5548
godox:
5649
# report any comments starting with keywords, this is useful for TODO or FIXME comments that

‎Earthfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
VERSION --arg-scope-and-set --global-cache 0.8
22

3-
ARG --global go_version = 1.22
3+
ARG --global go_version = 1.24
44
ARG --global node_version = 18
55
ARG --global rust_version = 1.79
6-
ARG --global golangci_lint_version = 1.57.1
6+
ARG --global golangci_lint_version = 1.64.6
77

88
ARG --global go_builder_image = "golang:${go_version}-alpine"
99
ARG --global node_builder_image = "node:${node_version}"

‎base/config/main.go

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"path/filepath"
1111
"sort"
1212

13+
"github.com/safing/portmaster/base/database/dbmodule"
1314
"github.com/safing/portmaster/base/dataroot"
1415
"github.com/safing/portmaster/base/utils"
1516
"github.com/safing/portmaster/base/utils/debug"
@@ -150,6 +151,7 @@ func InitializeUnitTestDataroot(testName string) (string, error) {
150151
if err != nil {
151152
return "", fmt.Errorf("failed to initialize dataroot: %w", err)
152153
}
154+
dbmodule.SetDatabaseLocation(dataroot.Root())
153155

154156
return basePath, nil
155157
}

‎base/database/controller.go

+14
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,20 @@ func (c *Controller) Purge(ctx context.Context, q *query.Query, local, internal
264264
return 0, ErrNotImplemented
265265
}
266266

267+
// PurgeOlderThan deletes all records last updated before the given time.
268+
// It returns the number of successful deletes and an error.
269+
func (c *Controller) PurgeOlderThan(ctx context.Context, prefix string, purgeBefore time.Time, local, internal bool) (int, error) {
270+
if shuttingDown.IsSet() {
271+
return 0, ErrShuttingDown
272+
}
273+
274+
if purger, ok := c.storage.(storage.PurgeOlderThan); ok {
275+
return purger.PurgeOlderThan(ctx, prefix, purgeBefore, local, internal, c.shadowDelete)
276+
}
277+
278+
return 0, ErrNotImplemented
279+
}
280+
267281
// Shutdown shuts down the storage.
268282
func (c *Controller) Shutdown() error {
269283
return c.storage.Shutdown()

‎base/database/dbmodule/db.go

-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"sync/atomic"
66

77
"github.com/safing/portmaster/base/database"
8-
"github.com/safing/portmaster/base/dataroot"
98
"github.com/safing/portmaster/base/utils"
109
"github.com/safing/portmaster/service/mgr"
1110
)
@@ -37,7 +36,6 @@ func SetDatabaseLocation(dirStructureRoot *utils.DirStructure) {
3736
}
3837

3938
func prep() error {
40-
SetDatabaseLocation(dataroot.Root())
4139
if databaseStructureRoot == nil {
4240
return errors.New("database location not specified")
4341
}

‎base/database/interface.go

+21
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,27 @@ func (i *Interface) Purge(ctx context.Context, q *query.Query) (int, error) {
562562
return db.Purge(ctx, q, i.options.Local, i.options.Internal)
563563
}
564564

565+
// PurgeOlderThan deletes all records last updated before the given time.
566+
// It returns the number of successful deletes and an error.
567+
func (i *Interface) PurgeOlderThan(ctx context.Context, prefix string, purgeBefore time.Time) (int, error) {
568+
dbName, dbKeyPrefix := record.ParseKey(prefix)
569+
if dbName == "" {
570+
return 0, errors.New("unknown database")
571+
}
572+
573+
db, err := getController(dbName)
574+
if err != nil {
575+
return 0, err
576+
}
577+
578+
// Check if database is read only before we add to the cache.
579+
if db.ReadOnly() {
580+
return 0, ErrReadOnly
581+
}
582+
583+
return db.PurgeOlderThan(ctx, dbKeyPrefix, purgeBefore, i.options.Local, i.options.Internal)
584+
}
585+
565586
// Subscribe subscribes to updates matching the given query.
566587
func (i *Interface) Subscribe(q *query.Query) (*Subscription, error) {
567588
_, err := q.Check()

‎base/database/main.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import (
99
"github.com/safing/portmaster/base/utils"
1010
)
1111

12-
const (
13-
databasesSubDir = "databases"
14-
)
12+
// DatabasesSubDir defines the sub directory where the databases are stored.
13+
const DatabasesSubDir = "databases"
1514

1615
var (
1716
initialized = abool.NewBool(false)
@@ -34,7 +33,7 @@ func Initialize(dirStructureRoot *utils.DirStructure) error {
3433
rootStructure = dirStructureRoot
3534

3635
// ensure root and databases dirs
37-
databasesStructure = rootStructure.ChildDir(databasesSubDir, utils.AdminOnlyPermission)
36+
databasesStructure = rootStructure.ChildDir(DatabasesSubDir, utils.AdminOnlyPermission)
3837
err := databasesStructure.Ensure()
3938
if err != nil {
4039
return fmt.Errorf("could not create/open database directory (%s): %w", rootStructure.Path, err)

‎base/database/query/query.go

+5
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ func (q *Query) MatchesKey(dbKey string) bool {
9999
return strings.HasPrefix(dbKey, q.dbKeyPrefix)
100100
}
101101

102+
// HasWhereCondition returns whether the query has a "where" condition set.
103+
func (q *Query) HasWhereCondition() bool {
104+
return q.where != nil
105+
}
106+
102107
// MatchesRecord checks whether the query matches the supplied database record (value only).
103108
func (q *Query) MatchesRecord(r record.Record) bool {
104109
if q.where == nil {

‎base/database/record/base.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func (b *Base) SetMeta(meta *Meta) {
102102
b.meta = meta
103103
}
104104

105-
// Marshal marshals the object, without the database key or metadata. It returns nil if the record is deleted.
105+
// Marshal marshals the format and data.
106106
func (b *Base) Marshal(self Record, format uint8) ([]byte, error) {
107107
if b.Meta() == nil {
108108
return nil, errors.New("missing meta")
@@ -119,7 +119,20 @@ func (b *Base) Marshal(self Record, format uint8) ([]byte, error) {
119119
return dumped, nil
120120
}
121121

122-
// MarshalRecord packs the object, including metadata, into a byte array for saving in a database.
122+
// MarshalDataOnly marshals the data only.
123+
func (b *Base) MarshalDataOnly(self Record, format uint8) ([]byte, error) {
124+
if b.Meta() == nil {
125+
return nil, errors.New("missing meta")
126+
}
127+
128+
if b.Meta().Deleted > 0 {
129+
return nil, nil
130+
}
131+
132+
return dsd.DumpWithoutIdentifier(self, format, "")
133+
}
134+
135+
// MarshalRecord marshals the data, format and metadata.
123136
func (b *Base) MarshalRecord(self Record) ([]byte, error) {
124137
if b.Meta() == nil {
125138
return nil, errors.New("missing meta")

‎base/database/record/meta.go

+10
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,21 @@ func (m *Meta) MakeCrownJewel() {
4949
m.cronjewel = true
5050
}
5151

52+
// IsCrownJewel returns whether the database record is marked as a crownjewel.
53+
func (m *Meta) IsCrownJewel() bool {
54+
return m.cronjewel
55+
}
56+
5257
// MakeSecret sets the database record as secret, meaning that it may only be used internally, and not by interfacing processes, such as the UI.
5358
func (m *Meta) MakeSecret() {
5459
m.secret = true
5560
}
5661

62+
// IsSecret returns whether the database record is marked as a secret.
63+
func (m *Meta) IsSecret() bool {
64+
return m.secret
65+
}
66+
5767
// Update updates the internal meta states and should be called before writing the record to the database.
5868
func (m *Meta) Update() {
5969
now := time.Now().Unix()

‎base/database/record/record.go

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Record interface {
2020

2121
// Serialization.
2222
Marshal(self Record, format uint8) ([]byte, error)
23+
MarshalDataOnly(self Record, format uint8) ([]byte, error)
2324
MarshalRecord(self Record) ([]byte, error)
2425
GetAccessor(self Record) accessor.Accessor
2526

‎base/database/record/wrapper.go

+33-2
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,21 @@ func NewWrapper(key string, meta *Meta, format uint8, data []byte) (*Wrapper, er
7979
}, nil
8080
}
8181

82-
// Marshal marshals the object, without the database key or metadata.
82+
// NewWrapperFromDatabase returns a new record wrapper for the given data.
83+
func NewWrapperFromDatabase(dbName, dbKey string, meta *Meta, format uint8, data []byte) (*Wrapper, error) {
84+
return &Wrapper{
85+
Base{
86+
dbName: dbName,
87+
dbKey: dbKey,
88+
meta: meta,
89+
},
90+
sync.Mutex{},
91+
format,
92+
data,
93+
}, nil
94+
}
95+
96+
// Marshal marshals the format and data.
8397
func (w *Wrapper) Marshal(r Record, format uint8) ([]byte, error) {
8498
if w.Meta() == nil {
8599
return nil, errors.New("missing meta")
@@ -100,7 +114,24 @@ func (w *Wrapper) Marshal(r Record, format uint8) ([]byte, error) {
100114
return data, nil
101115
}
102116

103-
// MarshalRecord packs the object, including metadata, into a byte array for saving in a database.
117+
// MarshalDataOnly marshals the data only.
118+
func (w *Wrapper) MarshalDataOnly(self Record, format uint8) ([]byte, error) {
119+
if w.Meta() == nil {
120+
return nil, errors.New("missing meta")
121+
}
122+
123+
if w.Meta().Deleted > 0 {
124+
return nil, nil
125+
}
126+
127+
if format != dsd.AUTO && format != w.Format {
128+
return nil, errors.New("could not dump model, wrapped object format mismatch")
129+
}
130+
131+
return w.Data, nil
132+
}
133+
134+
// MarshalRecord marshals the data, format and metadata.
104135
func (w *Wrapper) MarshalRecord(r Record) ([]byte, error) {
105136
// Duplication necessary, as the version from Base would call Base.Marshal instead of Wrapper.Marshal
106137

‎base/database/storage/interface.go

+5
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,8 @@ type Batcher interface {
4646
type Purger interface {
4747
Purge(ctx context.Context, q *query.Query, local, internal, shadowDelete bool) (int, error)
4848
}
49+
50+
// PurgeOlderThan defines the database storage API for backends that support the PurgeOlderThan operation.
51+
type PurgeOlderThan interface {
52+
PurgeOlderThan(ctx context.Context, prefix string, purgeBefore time.Time, local, internal, shadowDelete bool) (int, error)
53+
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
sqlite:
2+
dsn: "testdata/schema.db"
3+
except:
4+
migrations:
5+
6+
no_factory: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- +migrate Up
2+
-- SQL in section 'Up' is executed when this migration is applied
3+
PRAGMA auto_vacuum = INCREMENTAL; -- https://sqlite.org/pragma.html#pragma_auto_vacuum
4+
5+
-- +migrate Down
6+
-- SQL section 'Down' is executed when this migration is rolled back
7+
PRAGMA auto_vacuum = NONE; -- https://sqlite.org/pragma.html#pragma_auto_vacuum
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-- +migrate Up
2+
-- SQL in section 'Up' is executed when this migration is applied
3+
CREATE TABLE records (
4+
key TEXT PRIMARY KEY,
5+
6+
format SMALLINT,
7+
value BLOB,
8+
9+
created BIGINT NOT NULL,
10+
modified BIGINT NOT NULL,
11+
expires BIGINT DEFAULT 0 NOT NULL,
12+
deleted BIGINT DEFAULT 0 NOT NULL,
13+
secret BOOLEAN DEFAULT FALSE NOT NULL,
14+
crownjewel BOOLEAN DEFAULT FALSE NOT NULL
15+
);
16+
17+
-- +migrate Down
18+
-- SQL section 'Down' is executed when this migration is rolled back
19+
DROP TABLE records;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
development:
2+
dialect: sqlite3
3+
datasource: testdata/schema.db
4+
dir: migrations
5+
table: migrations

0 commit comments

Comments
 (0)
Failed to load comments.