Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
add create table call

just build-sqlc

use database config resolver from controller when dictated by cli flag

undo controller changes and fix errors in resolver

minor query update and bug fixes

tests

s/inline/db/

db provider

db provider
  • Loading branch information
deniseli committed May 25, 2024
1 parent da1d909 commit 86b4738
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 3 deletions.
81 changes: 81 additions & 0 deletions backend/controller/dal/database_resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package dal

import (
"context"
"net/url"

"github.com/TBD54566975/ftl/backend/controller/sql"
"github.com/TBD54566975/ftl/common/configuration"
"github.com/alecthomas/types/optional"
)

// DatabaseResolver loads values a project's configuration from the given database.
type DatabaseResolver struct {
db sql.DBI
}

// DatabaseResolver should only be used for config, not secrets
var _ configuration.Resolver[configuration.Configuration] = DatabaseResolver{}

func (d DatabaseResolver) Role() configuration.Configuration { return configuration.Configuration{} }

func (d *DAL) NewConfigResolver() DatabaseResolver {
return DatabaseResolver{
db: d.db,
}
}

func (d DatabaseResolver) Get(ctx context.Context, ref configuration.Ref) (*url.URL, error) {
module, moduleOk := ref.Module.Get()
if !moduleOk {
module = "" // global
}
accessor, err := d.db.GetConfig(ctx, module, ref.Name)
if err != nil {
// If we could not find this config within the module, then try getting
// again with scope set to global
if moduleOk {
return d.Get(ctx, configuration.Ref{
Module: optional.None[string](),
Name: ref.Name,
})
}
return nil, configuration.ErrNotFound
}
return url.Parse(accessor)
}

func (d DatabaseResolver) List(ctx context.Context) ([]configuration.Entry, error) {
configs, err := d.db.ListConfigs(ctx)
if err != nil {
return nil, err
}
entries := []configuration.Entry{}
for _, c := range configs {
u, err := url.Parse(c.Accessor)
if err != nil {
return nil, err
}
entries = append(entries, configuration.Entry{
Ref: configuration.NewRef(c.Module, c.Name),
Accessor: u,
})
}
return entries, nil
}

func (d DatabaseResolver) Set(ctx context.Context, ref configuration.Ref, key *url.URL) error {
return d.db.SetConfig(ctx, moduleAsString(ref), ref.Name, key.String())
}

func (d DatabaseResolver) Unset(ctx context.Context, ref configuration.Ref) error {
return d.db.UnsetConfig(ctx, moduleAsString(ref), ref.Name)
}

func moduleAsString(ref configuration.Ref) string {
module, ok := ref.Module.Get()
if !ok {
return ""
}
return module
}
97 changes: 97 additions & 0 deletions backend/controller/dal/database_resolver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package dal

import (
"context"
"net/url"
"testing"

"github.com/TBD54566975/ftl/backend/controller/sql/sqltest"
"github.com/TBD54566975/ftl/common/configuration"
"github.com/TBD54566975/ftl/internal/log"
"github.com/alecthomas/assert/v2"
"github.com/alecthomas/types/optional"
)

func TestDBResolverRoundTrip(t *testing.T) {
tests := []struct {
TestName string
ModuleSet optional.Option[string]
ModuleGet optional.Option[string]
PresetGlobal bool
}{
{
"SetModuleGetModule",
optional.Some("echo"),
optional.Some("echo"),
false,
},
{
"SetGlobalGetGlobal",
optional.None[string](),
optional.None[string](),
false,
},
{
"SetGlobalGetModule",
optional.None[string](),
optional.Some("echo"),
false,
},
{
"SetModuleOverridesGlobal",
optional.Some("echo"),
optional.Some("echo"),
true,
},
}

ctx, cr := setup(t)
u := URL("db://asdfasdf")
for _, test := range tests {
t.Run(test.TestName, func(t *testing.T) {
if test.PresetGlobal {
err := cr.Set(ctx, configuration.Ref{
Module: optional.None[string](),
Name: "configname",
}, URL("db://qwerty"))
assert.NoError(t, err)
}
err := cr.Set(ctx, configuration.Ref{
Module: test.ModuleSet,
Name: "configname",
}, u)
assert.NoError(t, err)
gotURL, err := cr.Get(ctx, configuration.Ref{
Module: test.ModuleGet,
Name: "configname",
})
assert.NoError(t, err)
assert.Equal(t, u, gotURL)
err = cr.Unset(ctx, configuration.Ref{
Module: test.ModuleSet,
Name: "configname",
})
assert.NoError(t, err)
})
}
}

func setup(t *testing.T) (context.Context, DatabaseResolver) {
t.Helper()

ctx := log.ContextWithNewDefaultLogger(context.Background())
conn := sqltest.OpenForTesting(ctx, t)
dal, err := New(ctx, conn)
assert.NoError(t, err)
assert.NotZero(t, dal)

return ctx, dal.NewConfigResolver()
}

func URL(s string) *url.URL {
u, err := url.Parse(s)
if err != nil {
panic(err)
}
return u
}
7 changes: 7 additions & 0 deletions backend/controller/sql/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions backend/controller/sql/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion backend/controller/sql/queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -544,4 +544,23 @@ WITH execution AS (
)
INSERT INTO fsm_transitions (fsm_executions_id, async_calls_id)
VALUES ((SELECT id FROM execution), (SELECT id FROM call))
RETURNING id;
RETURNING id;

-- name: GetConfig :one
SELECT accessor
FROM configs
WHERE module = @module
AND name = @name;

-- name: ListConfigs :many
SELECT *
FROM configs
ORDER BY module, name;

-- name: SetConfig :exec
INSERT INTO configs (module, name, accessor)
VALUES ($1, $2, $3);

-- name: UnsetConfig :exec
DELETE FROM configs
WHERE module = @module AND name = @name;
65 changes: 65 additions & 0 deletions backend/controller/sql/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 11 additions & 2 deletions backend/controller/sql/schema/001_init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,15 @@ CREATE TABLE fsm_transitions (
id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
fsm_executions_id BIGINT NOT NULL REFERENCES fsm_executions(id) ON DELETE CASCADE,
async_calls_id BIGINT NOT NULL REFERENCES async_calls(id) ON DELETE CASCADE
)
);

CREATE TABLE configs
(
id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
module TEXT NOT NULL,
name TEXT NOT NULL,
accessor TEXT NOT NULL,
UNIQUE (module, name)
);

-- migrate:down
-- migrate:down
30 changes: 30 additions & 0 deletions common/configuration/db_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package configuration

import (
"context"
"net/url"
)

// DBProvider is a configuration provider that stores configuration in its key.
type DBProvider struct {
DB bool `help:"Write configuration values to the database." group:"Provider:" xor:"configwriter"`
}

var _ MutableProvider[Configuration] = DBProvider{}

func (DBProvider) Role() Configuration { return Configuration{} }
func (DBProvider) Key() string { return "db" }

func (d DBProvider) Writer() bool { return d.DB }

func (DBProvider) Load(ctx context.Context, ref Ref, key *url.URL) ([]byte, error) {
return []byte(key.Host), nil
}

func (DBProvider) Store(ctx context.Context, ref Ref, value []byte) (*url.URL, error) {
return &url.URL{Scheme: "db", Host: string(value)}, nil
}

func (DBProvider) Delete(ctx context.Context, ref Ref) error {
return nil
}
Loading

0 comments on commit 86b4738

Please sign in to comment.