From e8d72f72767532d837b3fed9df9d1c5f78ff81d8 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 10 Mar 2025 14:26:14 -0500 Subject: [PATCH] db/sqlc: kvstores schemas & queries In this commit, we define the schemas and queries that are needed to implement the firewalldb's KVStores in SQL. --- db/migrations.go | 2 +- db/sqlc/kvstores.sql.go | 345 ++++++++++++++++++++ db/sqlc/migrations/000003_kvstores.down.sql | 9 + db/sqlc/migrations/000003_kvstores.up.sql | 57 ++++ db/sqlc/models.go | 20 ++ db/sqlc/querier.go | 15 + db/sqlc/queries/kvstores.sql | 107 ++++++ 7 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 db/sqlc/kvstores.sql.go create mode 100644 db/sqlc/migrations/000003_kvstores.down.sql create mode 100644 db/sqlc/migrations/000003_kvstores.up.sql create mode 100644 db/sqlc/queries/kvstores.sql diff --git a/db/migrations.go b/db/migrations.go index ab0400a09..1b4e7a6c0 100644 --- a/db/migrations.go +++ b/db/migrations.go @@ -22,7 +22,7 @@ const ( // daemon. // // NOTE: This MUST be updated when a new migration is added. - LatestMigrationVersion = 2 + LatestMigrationVersion = 3 ) // MigrationTarget is a functional option that can be passed to applyMigrations diff --git a/db/sqlc/kvstores.sql.go b/db/sqlc/kvstores.sql.go new file mode 100644 index 000000000..b2e6632f4 --- /dev/null +++ b/db/sqlc/kvstores.sql.go @@ -0,0 +1,345 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.25.0 +// source: kvstores.sql + +package sqlc + +import ( + "context" + "database/sql" +) + +const deleteAllTempKVStores = `-- name: DeleteAllTempKVStores :exec +DELETE FROM kvstores +WHERE perm = false +` + +func (q *Queries) DeleteAllTempKVStores(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, deleteAllTempKVStores) + return err +} + +const deleteFeatureKVStoreRecord = `-- name: DeleteFeatureKVStoreRecord :exec +DELETE FROM kvstores +WHERE entry_key = $1 + AND rule_id = $2 + AND perm = $3 + AND session_id = $4 + AND feature_id = $5 +` + +type DeleteFeatureKVStoreRecordParams struct { + Key string + RuleID int64 + Perm bool + SessionID sql.NullInt64 + FeatureID sql.NullInt64 +} + +func (q *Queries) DeleteFeatureKVStoreRecord(ctx context.Context, arg DeleteFeatureKVStoreRecordParams) error { + _, err := q.db.ExecContext(ctx, deleteFeatureKVStoreRecord, + arg.Key, + arg.RuleID, + arg.Perm, + arg.SessionID, + arg.FeatureID, + ) + return err +} + +const deleteGlobalKVStoreRecord = `-- name: DeleteGlobalKVStoreRecord :exec +DELETE FROM kvstores +WHERE entry_key = $1 + AND rule_id = $2 + AND perm = $3 + AND session_id IS NULL + AND feature_id IS NULL +` + +type DeleteGlobalKVStoreRecordParams struct { + Key string + RuleID int64 + Perm bool +} + +func (q *Queries) DeleteGlobalKVStoreRecord(ctx context.Context, arg DeleteGlobalKVStoreRecordParams) error { + _, err := q.db.ExecContext(ctx, deleteGlobalKVStoreRecord, arg.Key, arg.RuleID, arg.Perm) + return err +} + +const deleteSessionKVStoreRecord = `-- name: DeleteSessionKVStoreRecord :exec +DELETE FROM kvstores +WHERE entry_key = $1 + AND rule_id = $2 + AND perm = $3 + AND session_id = $4 + AND feature_id IS NULL +` + +type DeleteSessionKVStoreRecordParams struct { + Key string + RuleID int64 + Perm bool + SessionID sql.NullInt64 +} + +func (q *Queries) DeleteSessionKVStoreRecord(ctx context.Context, arg DeleteSessionKVStoreRecordParams) error { + _, err := q.db.ExecContext(ctx, deleteSessionKVStoreRecord, + arg.Key, + arg.RuleID, + arg.Perm, + arg.SessionID, + ) + return err +} + +const getFeatureID = `-- name: GetFeatureID :one +SELECT id +FROM features +WHERE name = $1 +` + +func (q *Queries) GetFeatureID(ctx context.Context, name string) (int64, error) { + row := q.db.QueryRowContext(ctx, getFeatureID, name) + var id int64 + err := row.Scan(&id) + return id, err +} + +const getFeatureKVStoreRecord = `-- name: GetFeatureKVStoreRecord :one +SELECT value +FROM kvstores +WHERE entry_key = $1 + AND rule_id = $2 + AND perm = $3 + AND session_id = $4 + AND feature_id = $5 +` + +type GetFeatureKVStoreRecordParams struct { + Key string + RuleID int64 + Perm bool + SessionID sql.NullInt64 + FeatureID sql.NullInt64 +} + +func (q *Queries) GetFeatureKVStoreRecord(ctx context.Context, arg GetFeatureKVStoreRecordParams) ([]byte, error) { + row := q.db.QueryRowContext(ctx, getFeatureKVStoreRecord, + arg.Key, + arg.RuleID, + arg.Perm, + arg.SessionID, + arg.FeatureID, + ) + var value []byte + err := row.Scan(&value) + return value, err +} + +const getGlobalKVStoreRecord = `-- name: GetGlobalKVStoreRecord :one +SELECT value +FROM kvstores +WHERE entry_key = $1 + AND rule_id = $2 + AND perm = $3 + AND session_id IS NULL + AND feature_id IS NULL +` + +type GetGlobalKVStoreRecordParams struct { + Key string + RuleID int64 + Perm bool +} + +func (q *Queries) GetGlobalKVStoreRecord(ctx context.Context, arg GetGlobalKVStoreRecordParams) ([]byte, error) { + row := q.db.QueryRowContext(ctx, getGlobalKVStoreRecord, arg.Key, arg.RuleID, arg.Perm) + var value []byte + err := row.Scan(&value) + return value, err +} + +const getOrInsertFeatureID = `-- name: GetOrInsertFeatureID :one +INSERT INTO features (name) +VALUES ($1) +ON CONFLICT(name) DO UPDATE SET name = excluded.name +RETURNING id +` + +func (q *Queries) GetOrInsertFeatureID(ctx context.Context, name string) (int64, error) { + row := q.db.QueryRowContext(ctx, getOrInsertFeatureID, name) + var id int64 + err := row.Scan(&id) + return id, err +} + +const getOrInsertRuleID = `-- name: GetOrInsertRuleID :one +INSERT INTO rules (name) +VALUES ($1) +ON CONFLICT(name) DO UPDATE SET name = excluded.name +RETURNING id +` + +func (q *Queries) GetOrInsertRuleID(ctx context.Context, name string) (int64, error) { + row := q.db.QueryRowContext(ctx, getOrInsertRuleID, name) + var id int64 + err := row.Scan(&id) + return id, err +} + +const getRuleID = `-- name: GetRuleID :one +SELECT id +FROM rules +WHERE name = $1 +` + +func (q *Queries) GetRuleID(ctx context.Context, name string) (int64, error) { + row := q.db.QueryRowContext(ctx, getRuleID, name) + var id int64 + err := row.Scan(&id) + return id, err +} + +const getSessionKVStoreRecord = `-- name: GetSessionKVStoreRecord :one +SELECT value +FROM kvstores +WHERE entry_key = $1 + AND rule_id = $2 + AND perm = $3 + AND session_id = $4 + AND feature_id IS NULL +` + +type GetSessionKVStoreRecordParams struct { + Key string + RuleID int64 + Perm bool + SessionID sql.NullInt64 +} + +func (q *Queries) GetSessionKVStoreRecord(ctx context.Context, arg GetSessionKVStoreRecordParams) ([]byte, error) { + row := q.db.QueryRowContext(ctx, getSessionKVStoreRecord, + arg.Key, + arg.RuleID, + arg.Perm, + arg.SessionID, + ) + var value []byte + err := row.Scan(&value) + return value, err +} + +const insertKVStoreRecord = `-- name: InsertKVStoreRecord :exec +INSERT INTO kvstores (perm, rule_id, session_id, feature_id, entry_key, value) +VALUES ($1, $2, $3, $4, $5, $6) +` + +type InsertKVStoreRecordParams struct { + Perm bool + RuleID int64 + SessionID sql.NullInt64 + FeatureID sql.NullInt64 + EntryKey string + Value []byte +} + +func (q *Queries) InsertKVStoreRecord(ctx context.Context, arg InsertKVStoreRecordParams) error { + _, err := q.db.ExecContext(ctx, insertKVStoreRecord, + arg.Perm, + arg.RuleID, + arg.SessionID, + arg.FeatureID, + arg.EntryKey, + arg.Value, + ) + return err +} + +const updateFeatureKVStoreRecord = `-- name: UpdateFeatureKVStoreRecord :exec +UPDATE kvstores +SET value = $1 +WHERE entry_key = $2 + AND rule_id = $3 + AND perm = $4 + AND session_id = $5 + AND feature_id = $6 +` + +type UpdateFeatureKVStoreRecordParams struct { + Value []byte + Key string + RuleID int64 + Perm bool + SessionID sql.NullInt64 + FeatureID sql.NullInt64 +} + +func (q *Queries) UpdateFeatureKVStoreRecord(ctx context.Context, arg UpdateFeatureKVStoreRecordParams) error { + _, err := q.db.ExecContext(ctx, updateFeatureKVStoreRecord, + arg.Value, + arg.Key, + arg.RuleID, + arg.Perm, + arg.SessionID, + arg.FeatureID, + ) + return err +} + +const updateGlobalKVStoreRecord = `-- name: UpdateGlobalKVStoreRecord :exec +UPDATE kvstores +SET value = $1 +WHERE entry_key = $2 + AND rule_id = $3 + AND perm = $4 + AND session_id IS NULL + AND feature_id IS NULL +` + +type UpdateGlobalKVStoreRecordParams struct { + Value []byte + Key string + RuleID int64 + Perm bool +} + +func (q *Queries) UpdateGlobalKVStoreRecord(ctx context.Context, arg UpdateGlobalKVStoreRecordParams) error { + _, err := q.db.ExecContext(ctx, updateGlobalKVStoreRecord, + arg.Value, + arg.Key, + arg.RuleID, + arg.Perm, + ) + return err +} + +const updateSessionKVStoreRecord = `-- name: UpdateSessionKVStoreRecord :exec +UPDATE kvstores +SET value = $1 +WHERE entry_key = $2 + AND rule_id = $3 + AND perm = $4 + AND session_id = $5 + AND feature_id IS NULL +` + +type UpdateSessionKVStoreRecordParams struct { + Value []byte + Key string + RuleID int64 + Perm bool + SessionID sql.NullInt64 +} + +func (q *Queries) UpdateSessionKVStoreRecord(ctx context.Context, arg UpdateSessionKVStoreRecordParams) error { + _, err := q.db.ExecContext(ctx, updateSessionKVStoreRecord, + arg.Value, + arg.Key, + arg.RuleID, + arg.Perm, + arg.SessionID, + ) + return err +} diff --git a/db/sqlc/migrations/000003_kvstores.down.sql b/db/sqlc/migrations/000003_kvstores.down.sql new file mode 100644 index 000000000..4439d230b --- /dev/null +++ b/db/sqlc/migrations/000003_kvstores.down.sql @@ -0,0 +1,9 @@ +-- Drop indexes first. +DROP INDEX IF EXISTS kvstores_lookup_idx; +DROP INDEX IF EXISTS features_name_idx; +DROP INDEX IF EXISTS rules_name_idx; + +-- Drop tables in reverse dependency order. +DROP TABLE IF EXISTS kvstores; +DROP TABLE IF EXISTS features; +DROP TABLE IF EXISTS rules; diff --git a/db/sqlc/migrations/000003_kvstores.up.sql b/db/sqlc/migrations/000003_kvstores.up.sql new file mode 100644 index 000000000..d2f0653a5 --- /dev/null +++ b/db/sqlc/migrations/000003_kvstores.up.sql @@ -0,0 +1,57 @@ +-- rules holds the unique names of the various rules that are used +-- known to lit and used by the firewall db. +CREATE TABLE IF NOT EXISTS rules ( + -- The auto incrementing primary key. + id INTEGER PRIMARY KEY, + + -- The unique name of the rule. + name TEXT NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS rules_name_idx ON rules (name); + +-- features holds the unique names of the various features that +-- kvstores can be associated with. +CREATE TABLE IF NOT EXISTS features ( + -- The auto incrementing primary key. + id INTEGER PRIMARY KEY, + + -- The unique name of the feature. + name TEXT NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS features_name_idx ON features (name); + +-- kvstores houses key-value pairs under various namespaces determined +-- by the rule name, session ID, and feature name. +CREATE TABLE IF NOT EXISTS kvstores ( + -- The auto incrementing primary key. + id INTEGER PRIMARY KEY, + + -- Whether this record is part of the permanent store. + -- If false, it will be deleted on start-up. + perm BOOLEAN NOT NULL, + + -- The rule that this kv_store belongs to. + -- If only the rule name is set, then this kv_store is a global + -- kv_store. + rule_id BIGINT REFERENCES rules(id) NOT NULL, + + -- The session ID that this kv_store belongs to. + -- If this is set, then this kv_store is a session-specific + -- kv_store for the given rule. + session_id BIGINT REFERENCES sessions(id) ON DELETE CASCADE, + + -- The feature name that this kv_store belongs to. + -- If this is set, then this kv_store is a feature-specific + -- kvstore under the given session ID and rule name. + -- If this is set, then session_id must also be set. + feature_id BIGINT REFERENCES features(id), + + -- The key of the entry. + entry_key TEXT NOT NULL, + + -- The value of the entry. + value BLOB NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS kvstores_lookup_idx + ON kvstores (entry_key, rule_id, perm, session_id, feature_id); diff --git a/db/sqlc/models.go b/db/sqlc/models.go index ae07cd13b..f879ce998 100644 --- a/db/sqlc/models.go +++ b/db/sqlc/models.go @@ -37,6 +37,26 @@ type AccountPayment struct { FullAmountMsat int64 } +type Feature struct { + ID int64 + Name string +} + +type Kvstore struct { + ID int64 + Perm bool + RuleID int64 + SessionID sql.NullInt64 + FeatureID sql.NullInt64 + EntryKey string + Value []byte +} + +type Rule struct { + ID int64 + Name string +} + type Session struct { ID int64 Alias []byte diff --git a/db/sqlc/querier.go b/db/sqlc/querier.go index 76355ed6f..7564856c7 100644 --- a/db/sqlc/querier.go +++ b/db/sqlc/querier.go @@ -13,6 +13,10 @@ type Querier interface { AddAccountInvoice(ctx context.Context, arg AddAccountInvoiceParams) error DeleteAccount(ctx context.Context, id int64) error DeleteAccountPayment(ctx context.Context, arg DeleteAccountPaymentParams) error + DeleteAllTempKVStores(ctx context.Context) error + DeleteFeatureKVStoreRecord(ctx context.Context, arg DeleteFeatureKVStoreRecordParams) error + DeleteGlobalKVStoreRecord(ctx context.Context, arg DeleteGlobalKVStoreRecordParams) error + DeleteSessionKVStoreRecord(ctx context.Context, arg DeleteSessionKVStoreRecordParams) error DeleteSessionsWithState(ctx context.Context, state int16) error GetAccount(ctx context.Context, id int64) (Account, error) GetAccountByLabel(ctx context.Context, label sql.NullString) (Account, error) @@ -21,17 +25,25 @@ type Querier interface { GetAccountInvoice(ctx context.Context, arg GetAccountInvoiceParams) (AccountInvoice, error) GetAccountPayment(ctx context.Context, arg GetAccountPaymentParams) (AccountPayment, error) GetAliasBySessionID(ctx context.Context, id int64) ([]byte, error) + GetFeatureID(ctx context.Context, name string) (int64, error) + GetFeatureKVStoreRecord(ctx context.Context, arg GetFeatureKVStoreRecordParams) ([]byte, error) + GetGlobalKVStoreRecord(ctx context.Context, arg GetGlobalKVStoreRecordParams) ([]byte, error) + GetOrInsertFeatureID(ctx context.Context, name string) (int64, error) + GetOrInsertRuleID(ctx context.Context, name string) (int64, error) + GetRuleID(ctx context.Context, name string) (int64, error) GetSessionAliasesInGroup(ctx context.Context, groupID sql.NullInt64) ([][]byte, error) GetSessionByAlias(ctx context.Context, alias []byte) (Session, error) GetSessionByID(ctx context.Context, id int64) (Session, error) GetSessionByLocalPublicKey(ctx context.Context, localPublicKey []byte) (Session, error) GetSessionFeatureConfigs(ctx context.Context, sessionID int64) ([]SessionFeatureConfig, error) GetSessionIDByAlias(ctx context.Context, alias []byte) (int64, error) + GetSessionKVStoreRecord(ctx context.Context, arg GetSessionKVStoreRecordParams) ([]byte, error) GetSessionMacaroonCaveats(ctx context.Context, sessionID int64) ([]SessionMacaroonCaveat, error) GetSessionMacaroonPermissions(ctx context.Context, sessionID int64) ([]SessionMacaroonPermission, error) GetSessionPrivacyFlags(ctx context.Context, sessionID int64) ([]SessionPrivacyFlag, error) GetSessionsInGroup(ctx context.Context, groupID sql.NullInt64) ([]Session, error) InsertAccount(ctx context.Context, arg InsertAccountParams) (int64, error) + InsertKVStoreRecord(ctx context.Context, arg InsertKVStoreRecordParams) error InsertSession(ctx context.Context, arg InsertSessionParams) (int64, error) InsertSessionFeatureConfig(ctx context.Context, arg InsertSessionFeatureConfigParams) error InsertSessionMacaroonCaveat(ctx context.Context, arg InsertSessionMacaroonCaveatParams) error @@ -50,6 +62,9 @@ type Querier interface { UpdateAccountBalance(ctx context.Context, arg UpdateAccountBalanceParams) (int64, error) UpdateAccountExpiry(ctx context.Context, arg UpdateAccountExpiryParams) (int64, error) UpdateAccountLastUpdate(ctx context.Context, arg UpdateAccountLastUpdateParams) (int64, error) + UpdateFeatureKVStoreRecord(ctx context.Context, arg UpdateFeatureKVStoreRecordParams) error + UpdateGlobalKVStoreRecord(ctx context.Context, arg UpdateGlobalKVStoreRecordParams) error + UpdateSessionKVStoreRecord(ctx context.Context, arg UpdateSessionKVStoreRecordParams) error UpdateSessionState(ctx context.Context, arg UpdateSessionStateParams) error UpsertAccountPayment(ctx context.Context, arg UpsertAccountPaymentParams) error } diff --git a/db/sqlc/queries/kvstores.sql b/db/sqlc/queries/kvstores.sql new file mode 100644 index 000000000..7963e46a4 --- /dev/null +++ b/db/sqlc/queries/kvstores.sql @@ -0,0 +1,107 @@ +-- name: GetOrInsertRuleID :one +INSERT INTO rules (name) +VALUES ($1) +ON CONFLICT(name) DO UPDATE SET name = excluded.name +RETURNING id; + +-- name: GetRuleID :one +SELECT id +FROM rules +WHERE name = sqlc.arg('name'); + +-- name: GetOrInsertFeatureID :one +INSERT INTO features (name) +VALUES ($1) +ON CONFLICT(name) DO UPDATE SET name = excluded.name +RETURNING id; + +-- name: GetFeatureID :one +SELECT id +FROM features +WHERE name = sqlc.arg('name'); + +-- name: InsertKVStoreRecord :exec +INSERT INTO kvstores (perm, rule_id, session_id, feature_id, entry_key, value) +VALUES ($1, $2, $3, $4, $5, $6); + +-- name: DeleteAllTempKVStores :exec +DELETE FROM kvstores +WHERE perm = false; + +-- name: GetGlobalKVStoreRecord :one +SELECT value +FROM kvstores +WHERE entry_key = sqlc.arg('key') + AND rule_id = sqlc.arg('rule_id') + AND perm = sqlc.arg('perm') + AND session_id IS NULL + AND feature_id IS NULL; + +-- name: GetSessionKVStoreRecord :one +SELECT value +FROM kvstores +WHERE entry_key = sqlc.arg('key') + AND rule_id = sqlc.arg('rule_id') + AND perm = sqlc.arg('perm') + AND session_id = sqlc.arg('session_id') + AND feature_id IS NULL; + +-- name: GetFeatureKVStoreRecord :one +SELECT value +FROM kvstores +WHERE entry_key = sqlc.arg('key') + AND rule_id = sqlc.arg('rule_id') + AND perm = sqlc.arg('perm') + AND session_id = sqlc.arg('session_id') + AND feature_id = sqlc.arg('feature_id'); + +-- name: DeleteGlobalKVStoreRecord :exec +DELETE FROM kvstores +WHERE entry_key = sqlc.arg('key') + AND rule_id = sqlc.arg('rule_id') + AND perm = sqlc.arg('perm') + AND session_id IS NULL + AND feature_id IS NULL; + +-- name: DeleteSessionKVStoreRecord :exec +DELETE FROM kvstores +WHERE entry_key = sqlc.arg('key') + AND rule_id = sqlc.arg('rule_id') + AND perm = sqlc.arg('perm') + AND session_id = sqlc.arg('session_id') + AND feature_id IS NULL; + +-- name: DeleteFeatureKVStoreRecord :exec +DELETE FROM kvstores +WHERE entry_key = sqlc.arg('key') + AND rule_id = sqlc.arg('rule_id') + AND perm = sqlc.arg('perm') + AND session_id = sqlc.arg('session_id') + AND feature_id = sqlc.arg('feature_id'); + +-- name: UpdateGlobalKVStoreRecord :exec +UPDATE kvstores +SET value = $1 +WHERE entry_key = sqlc.arg('key') + AND rule_id = sqlc.arg('rule_id') + AND perm = sqlc.arg('perm') + AND session_id IS NULL + AND feature_id IS NULL; + +-- name: UpdateSessionKVStoreRecord :exec +UPDATE kvstores +SET value = $1 +WHERE entry_key = sqlc.arg('key') + AND rule_id = sqlc.arg('rule_id') + AND perm = sqlc.arg('perm') + AND session_id = sqlc.arg('session_id') + AND feature_id IS NULL; + +-- name: UpdateFeatureKVStoreRecord :exec +UPDATE kvstores +SET value = $1 +WHERE entry_key = sqlc.arg('key') + AND rule_id = sqlc.arg('rule_id') + AND perm = sqlc.arg('perm') + AND session_id = sqlc.arg('session_id') + AND feature_id = sqlc.arg('feature_id');