Skip to content

Commit da3aa07

Browse files
committed
firewalldb: add kvstores SQL implementation
1 parent 45bf999 commit da3aa07

File tree

7 files changed

+486
-5
lines changed

7 files changed

+486
-5
lines changed

firewalldb/kvstores_sql.go

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
package firewalldb
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"fmt"
8+
9+
"github.com/lightninglabs/lightning-terminal/db"
10+
"github.com/lightninglabs/lightning-terminal/db/sqlc"
11+
"github.com/lightninglabs/lightning-terminal/session"
12+
"github.com/lightningnetwork/lnd/fn"
13+
)
14+
15+
// SQLSessionQueries is a subset of the sqlc.Queries interface that can be used
16+
// to interact with the sessions tables.
17+
type SQLSessionQueries interface {
18+
GetSessionIDByAlias(ctx context.Context, legacyID []byte) (int64, error)
19+
}
20+
21+
type SQLKVStoreQueries interface {
22+
SQLSessionQueries
23+
24+
DeleteFeatureKVStoreRecord(ctx context.Context, arg sqlc.DeleteFeatureKVStoreRecordParams) error
25+
DeleteGlobalKVStoreRecord(ctx context.Context, arg sqlc.DeleteGlobalKVStoreRecordParams) error
26+
DeleteSessionKVStoreRecord(ctx context.Context, arg sqlc.DeleteSessionKVStoreRecordParams) error
27+
GetFeatureKVStoreRecord(ctx context.Context, arg sqlc.GetFeatureKVStoreRecordParams) ([]byte, error)
28+
GetGlobalKVStoreRecord(ctx context.Context, arg sqlc.GetGlobalKVStoreRecordParams) ([]byte, error)
29+
GetSessionKVStoreRecord(ctx context.Context, arg sqlc.GetSessionKVStoreRecordParams) ([]byte, error)
30+
UpsertKVStoreRecord(ctx context.Context, arg sqlc.UpsertKVStoreRecordParams) error
31+
DeleteAllTempKVStores(ctx context.Context) error
32+
GetOrInsertFeatureID(ctx context.Context, name string) (int64, error)
33+
GetOrInsertRuleID(ctx context.Context, name string) (int64, error)
34+
GetFeatureID(ctx context.Context, name string) (int64, error)
35+
GetRuleID(ctx context.Context, name string) (int64, error)
36+
}
37+
38+
func (s *SQLDB) DeleteTempKVStores(ctx context.Context) error {
39+
var writeTxOpts db.QueriesTxOptions
40+
41+
return s.db.ExecTx(ctx, &writeTxOpts, func(tx SQLQueries) error {
42+
return tx.DeleteAllTempKVStores(ctx)
43+
})
44+
}
45+
46+
func (s *SQLDB) GetKVStores(rule string, groupAlias session.ID,
47+
feature string) KVStores {
48+
49+
return &sqlExecutor[KVStoreTx]{
50+
db: s.db,
51+
wrapTx: func(queries SQLQueries) KVStoreTx {
52+
return &sqlKVStoresSQLTx{
53+
queries: queries,
54+
db: &kvStoreSQLDB{
55+
SQLDB: s,
56+
groupAlias: groupAlias,
57+
rule: rule,
58+
feature: feature,
59+
},
60+
}
61+
},
62+
}
63+
}
64+
65+
type kvStoreSQLDB struct {
66+
*SQLDB
67+
groupAlias session.ID
68+
rule string
69+
feature string
70+
}
71+
72+
type sqlKVStoresSQLTx struct {
73+
db *kvStoreSQLDB
74+
queries SQLKVStoreQueries
75+
}
76+
77+
type sqlKVStore struct {
78+
*sqlKVStoresSQLTx
79+
80+
params *sqlKVStoreParams
81+
}
82+
83+
func (s *sqlKVStoresSQLTx) Global() KVStore {
84+
return &sqlKVStore{
85+
sqlKVStoresSQLTx: s,
86+
params: &sqlKVStoreParams{
87+
perm: true,
88+
ruleName: s.db.rule,
89+
},
90+
}
91+
}
92+
93+
func (s *sqlKVStoresSQLTx) Local() KVStore {
94+
var featureName fn.Option[string]
95+
if s.db.feature != "" {
96+
featureName = fn.Some(s.db.feature)
97+
}
98+
99+
return &sqlKVStore{
100+
sqlKVStoresSQLTx: s,
101+
params: &sqlKVStoreParams{
102+
perm: true,
103+
ruleName: s.db.rule,
104+
sessionID: fn.Some(s.db.groupAlias),
105+
featureName: featureName,
106+
},
107+
}
108+
}
109+
110+
func (s *sqlKVStoresSQLTx) GlobalTemp() KVStore {
111+
return &sqlKVStore{
112+
sqlKVStoresSQLTx: s,
113+
params: &sqlKVStoreParams{
114+
perm: false,
115+
ruleName: s.db.rule,
116+
},
117+
}
118+
}
119+
120+
func (s *sqlKVStoresSQLTx) LocalTemp() KVStore {
121+
var featureName fn.Option[string]
122+
if s.db.feature != "" {
123+
featureName = fn.Some(s.db.feature)
124+
}
125+
126+
return &sqlKVStore{
127+
sqlKVStoresSQLTx: s,
128+
params: &sqlKVStoreParams{
129+
perm: false,
130+
ruleName: s.db.rule,
131+
sessionID: fn.Some(s.db.groupAlias),
132+
featureName: featureName,
133+
},
134+
}
135+
}
136+
137+
var _ KVStoreTx = (*sqlKVStoresSQLTx)(nil)
138+
139+
type sqlKVStoreParams struct {
140+
perm bool
141+
ruleName string
142+
sessionID fn.Option[session.ID]
143+
featureName fn.Option[string]
144+
}
145+
146+
func (s *sqlKVStore) genNamespaceFields(ctx context.Context,
147+
readOnly bool) (int64, sql.NullInt64, sql.NullInt64, error) {
148+
149+
var (
150+
sessionID sql.NullInt64
151+
featureID sql.NullInt64
152+
ruleID int64
153+
err error
154+
)
155+
156+
s.params.sessionID.WhenSome(func(id session.ID) {
157+
var groupID int64
158+
groupID, err = s.queries.GetSessionIDByAlias(ctx, id[:])
159+
if errors.Is(err, sql.ErrNoRows) {
160+
err = session.ErrUnknownGroup
161+
162+
return
163+
} else if err != nil {
164+
return
165+
}
166+
167+
sessionID = sql.NullInt64{
168+
Int64: groupID,
169+
Valid: true,
170+
}
171+
})
172+
if err != nil {
173+
return ruleID, sessionID, featureID, err
174+
}
175+
176+
if readOnly {
177+
ruleID, err = s.queries.GetRuleID(ctx, s.params.ruleName)
178+
if err != nil {
179+
return 0, sessionID, featureID,
180+
fmt.Errorf("unable to get rule ID: %w", err)
181+
}
182+
} else {
183+
ruleID, err = s.queries.GetOrInsertRuleID(
184+
ctx, s.params.ruleName,
185+
)
186+
if err != nil {
187+
return 0, sessionID, featureID,
188+
fmt.Errorf("unable to get rule ID: %w", err)
189+
}
190+
}
191+
192+
s.params.featureName.WhenSome(func(feature string) {
193+
var id int64
194+
if readOnly {
195+
id, err = s.queries.GetFeatureID(ctx, feature)
196+
if err != nil {
197+
return
198+
}
199+
} else {
200+
id, err = s.queries.GetOrInsertFeatureID(ctx, feature)
201+
if err != nil {
202+
return
203+
}
204+
}
205+
206+
featureID = sql.NullInt64{
207+
Int64: id,
208+
Valid: true,
209+
}
210+
})
211+
212+
return ruleID, sessionID, featureID, err
213+
}
214+
215+
func (s *sqlKVStore) Get(ctx context.Context, key string) ([]byte, error) {
216+
value, err := s.get(ctx, key)
217+
if errors.Is(err, sql.ErrNoRows) ||
218+
errors.Is(err, session.ErrUnknownGroup) {
219+
220+
return nil, nil
221+
} else if err != nil {
222+
return nil, err
223+
}
224+
225+
return value, nil
226+
}
227+
228+
func (s *sqlKVStore) get(ctx context.Context, key string) ([]byte, error) {
229+
ruleID, sessionID, featureID, err := s.genNamespaceFields(ctx, true)
230+
if err != nil {
231+
return nil, err
232+
}
233+
234+
switch {
235+
case sessionID.Valid && featureID.Valid:
236+
return s.queries.GetFeatureKVStoreRecord(
237+
ctx, sqlc.GetFeatureKVStoreRecordParams{
238+
Key: key,
239+
Perm: s.params.perm,
240+
SessionID: sessionID,
241+
RuleID: ruleID,
242+
FeatureID: featureID,
243+
},
244+
)
245+
246+
case sessionID.Valid:
247+
return s.queries.GetSessionKVStoreRecord(
248+
ctx, sqlc.GetSessionKVStoreRecordParams{
249+
Key: key,
250+
Perm: s.params.perm,
251+
SessionID: sessionID,
252+
RuleID: ruleID,
253+
},
254+
)
255+
256+
case featureID.Valid:
257+
return nil, fmt.Errorf("a global feature kv store is " +
258+
"not currently supported")
259+
default:
260+
return s.queries.GetGlobalKVStoreRecord(
261+
ctx, sqlc.GetGlobalKVStoreRecordParams{
262+
Key: key,
263+
Perm: s.params.perm,
264+
RuleID: ruleID,
265+
},
266+
)
267+
}
268+
}
269+
270+
func (s *sqlKVStore) Set(ctx context.Context, key string, value []byte) error {
271+
ruleID, sessionID, featureID, err := s.genNamespaceFields(ctx, false)
272+
if err != nil {
273+
return err
274+
}
275+
276+
return s.queries.UpsertKVStoreRecord(ctx, sqlc.UpsertKVStoreRecordParams{
277+
Perm: s.params.perm,
278+
RuleID: ruleID,
279+
SessionID: sessionID,
280+
FeatureID: featureID,
281+
EntryKey: key,
282+
Value: value,
283+
})
284+
}
285+
286+
func (s *sqlKVStore) Del(ctx context.Context, key string) error {
287+
// Note: we pass in true here for "read-only" since because this is a
288+
// Delete, if the record does not exist, we don't need to create one.
289+
// But no need to error out if it doesn't exist.
290+
ruleID, sessionID, featureID, err := s.genNamespaceFields(ctx, true)
291+
if errors.Is(err, sql.ErrNoRows) ||
292+
errors.Is(err, session.ErrUnknownGroup) {
293+
294+
return nil
295+
} else if err != nil {
296+
return err
297+
}
298+
299+
switch {
300+
case sessionID.Valid && featureID.Valid:
301+
return s.queries.DeleteFeatureKVStoreRecord(
302+
ctx, sqlc.DeleteFeatureKVStoreRecordParams{
303+
Key: key,
304+
Perm: s.params.perm,
305+
SessionID: sessionID,
306+
RuleID: ruleID,
307+
FeatureID: featureID,
308+
},
309+
)
310+
311+
case sessionID.Valid:
312+
return s.queries.DeleteSessionKVStoreRecord(
313+
ctx, sqlc.DeleteSessionKVStoreRecordParams{
314+
Key: key,
315+
Perm: s.params.perm,
316+
SessionID: sessionID,
317+
RuleID: ruleID,
318+
},
319+
)
320+
321+
case featureID.Valid:
322+
return fmt.Errorf("a global feature kv store is " +
323+
"not currently supported")
324+
default:
325+
return s.queries.DeleteGlobalKVStoreRecord(
326+
ctx, sqlc.DeleteGlobalKVStoreRecordParams{
327+
Key: key,
328+
Perm: s.params.perm,
329+
RuleID: ruleID,
330+
},
331+
)
332+
}
333+
}
334+
335+
var _ KVStore = (*sqlKVStore)(nil)

0 commit comments

Comments
 (0)