diff --git a/backend/controller/admin/testdata/go/dischema/go.mod b/backend/controller/admin/testdata/go/dischema/go.mod index bb9ee02347..6fb04263c5 100644 --- a/backend/controller/admin/testdata/go/dischema/go.mod +++ b/backend/controller/admin/testdata/go/dischema/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/backend/controller/admin/testdata/go/dischema/go.sum b/backend/controller/admin/testdata/go/dischema/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/backend/controller/admin/testdata/go/dischema/go.sum +++ b/backend/controller/admin/testdata/go/dischema/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/backend/controller/console/testdata/go/console/go.mod b/backend/controller/console/testdata/go/console/go.mod index 48032787b9..7711cca5d9 100644 --- a/backend/controller/console/testdata/go/console/go.mod +++ b/backend/controller/console/testdata/go/console/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/backend/controller/console/testdata/go/console/go.sum b/backend/controller/console/testdata/go/console/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/backend/controller/console/testdata/go/console/go.sum +++ b/backend/controller/console/testdata/go/console/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/backend/controller/cronjobs/testdata/go/cron/go.mod b/backend/controller/cronjobs/testdata/go/cron/go.mod index 1e1f5ed0ac..5a17dcd854 100644 --- a/backend/controller/cronjobs/testdata/go/cron/go.mod +++ b/backend/controller/cronjobs/testdata/go/cron/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/backend/controller/cronjobs/testdata/go/cron/go.sum b/backend/controller/cronjobs/testdata/go/cron/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/backend/controller/cronjobs/testdata/go/cron/go.sum +++ b/backend/controller/cronjobs/testdata/go/cron/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/backend/controller/cronjobs/testdata/go/cron/types.ftl.go b/backend/controller/cronjobs/testdata/go/cron/types.ftl.go new file mode 100644 index 0000000000..9f1d69404a --- /dev/null +++ b/backend/controller/cronjobs/testdata/go/cron/types.ftl.go @@ -0,0 +1,27 @@ +// Code generated by FTL. DO NOT EDIT. +package cron + +import ( + "context" + "github.com/TBD54566975/ftl/go-runtime/ftl/reflection" +) + +type CronClient func(context.Context) error + +type FiveMinutesClient func(context.Context) error + +type SaturdayClient func(context.Context) error + +func init() { + reflection.Register( + reflection.ProvideResourcesForVerb( + Cron, + ), + reflection.ProvideResourcesForVerb( + FiveMinutes, + ), + reflection.ProvideResourcesForVerb( + Saturday, + ), + ) +} diff --git a/backend/controller/encryption/testdata/go/encryption/go.mod b/backend/controller/encryption/testdata/go/encryption/go.mod index f07de1349d..e3ef981cd8 100644 --- a/backend/controller/encryption/testdata/go/encryption/go.mod +++ b/backend/controller/encryption/testdata/go/encryption/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/backend/controller/encryption/testdata/go/encryption/go.sum b/backend/controller/encryption/testdata/go/encryption/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/backend/controller/encryption/testdata/go/encryption/go.sum +++ b/backend/controller/encryption/testdata/go/encryption/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/backend/controller/ingress/testdata/go/httpingress/go.mod b/backend/controller/ingress/testdata/go/httpingress/go.mod index 2cf3436741..32be9ab390 100644 --- a/backend/controller/ingress/testdata/go/httpingress/go.mod +++ b/backend/controller/ingress/testdata/go/httpingress/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/backend/controller/ingress/testdata/go/httpingress/go.sum b/backend/controller/ingress/testdata/go/httpingress/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/backend/controller/ingress/testdata/go/httpingress/go.sum +++ b/backend/controller/ingress/testdata/go/httpingress/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/backend/controller/pubsub/testdata/go/publisher/go.mod b/backend/controller/pubsub/testdata/go/publisher/go.mod index c79c6f9deb..146a2a4173 100644 --- a/backend/controller/pubsub/testdata/go/publisher/go.mod +++ b/backend/controller/pubsub/testdata/go/publisher/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/backend/controller/pubsub/testdata/go/publisher/go.sum b/backend/controller/pubsub/testdata/go/publisher/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/backend/controller/pubsub/testdata/go/publisher/go.sum +++ b/backend/controller/pubsub/testdata/go/publisher/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/backend/controller/pubsub/testdata/go/slow/go.mod b/backend/controller/pubsub/testdata/go/slow/go.mod index 3dec55631a..13bb0989e5 100644 --- a/backend/controller/pubsub/testdata/go/slow/go.mod +++ b/backend/controller/pubsub/testdata/go/slow/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/backend/controller/pubsub/testdata/go/slow/go.sum b/backend/controller/pubsub/testdata/go/slow/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/backend/controller/pubsub/testdata/go/slow/go.sum +++ b/backend/controller/pubsub/testdata/go/slow/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/backend/controller/pubsub/testdata/go/subscriber/go.mod b/backend/controller/pubsub/testdata/go/subscriber/go.mod index 87eb676332..70955a3116 100644 --- a/backend/controller/pubsub/testdata/go/subscriber/go.mod +++ b/backend/controller/pubsub/testdata/go/subscriber/go.mod @@ -12,7 +12,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect github.com/alecthomas/types v0.16.0 // indirect diff --git a/backend/controller/pubsub/testdata/go/subscriber/go.sum b/backend/controller/pubsub/testdata/go/subscriber/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/backend/controller/pubsub/testdata/go/subscriber/go.sum +++ b/backend/controller/pubsub/testdata/go/subscriber/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/backend/controller/sql/testdata/go/database/database.go b/backend/controller/sql/testdata/go/database/database.go index 692319a411..399d6d6aa3 100644 --- a/backend/controller/sql/testdata/go/database/database.go +++ b/backend/controller/sql/testdata/go/database/database.go @@ -6,7 +6,11 @@ import ( "github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK. ) -var db = ftl.PostgresDatabase("testdb") +type MyDbConfig struct { + ftl.DefaultPostgresDatabaseConfig +} + +func (MyDbConfig) Name() string { return "testdb" } type InsertRequest struct { Data string @@ -15,8 +19,8 @@ type InsertRequest struct { type InsertResponse struct{} //ftl:verb -func Insert(ctx context.Context, req InsertRequest) (InsertResponse, error) { - err := persistRequest(ctx, req) +func Insert(ctx context.Context, req InsertRequest, db ftl.DatabaseHandle[MyDbConfig]) (InsertResponse, error) { + err := persistRequest(ctx, req, db) if err != nil { return InsertResponse{}, err } @@ -24,7 +28,7 @@ func Insert(ctx context.Context, req InsertRequest) (InsertResponse, error) { return InsertResponse{}, nil } -func persistRequest(ctx context.Context, req InsertRequest) error { +func persistRequest(ctx context.Context, req InsertRequest, db ftl.DatabaseHandle[MyDbConfig]) error { _, err := db.Get(ctx).Exec(`CREATE TABLE IF NOT EXISTS requests ( data TEXT, diff --git a/backend/controller/sql/testdata/go/database/database_test.go b/backend/controller/sql/testdata/go/database/database_test.go index d5b026600f..c443db9cfe 100644 --- a/backend/controller/sql/testdata/go/database/database_test.go +++ b/backend/controller/sql/testdata/go/database/database_test.go @@ -11,22 +11,26 @@ import ( func TestDatabase(t *testing.T) { ctx := ftltest.Context( + ftltest.WithCallsAllowedWithinModule(), ftltest.WithProjectFile("ftl-project.toml"), - ftltest.WithDatabase(db), + ftltest.WithDatabase[MyDbConfig](), ) - Insert(ctx, InsertRequest{Data: "unit test 1"}) + _, err := ftltest.Call[InsertClient, InsertRequest, InsertResponse](ctx, InsertRequest{Data: "unit test 1"}) + assert.NoError(t, err) list, err := getAll(ctx) assert.NoError(t, err) assert.Equal(t, 1, len(list)) assert.Equal(t, "unit test 1", list[0]) ctx = ftltest.Context( + ftltest.WithCallsAllowedWithinModule(), ftltest.WithProjectFile("ftl-project.toml"), - ftltest.WithDatabase(db), + ftltest.WithDatabase[MyDbConfig](), ) - Insert(ctx, InsertRequest{Data: "unit test 2"}) + _, err = ftltest.Call[InsertClient, InsertRequest, InsertResponse](ctx, InsertRequest{Data: "unit test 2"}) + assert.NoError(t, err) list, err = getAll(ctx) assert.NoError(t, err) assert.Equal(t, 1, len(list)) @@ -35,11 +39,13 @@ func TestDatabase(t *testing.T) { func TestOptionOrdering(t *testing.T) { ctx := ftltest.Context( - ftltest.WithDatabase(db), // <--- consumes DSNs + ftltest.WithCallsAllowedWithinModule(), + ftltest.WithDatabase[MyDbConfig](), // <--- consumes DSNs ftltest.WithProjectFile("ftl-project.toml"), // <--- provides DSNs ) - Insert(ctx, InsertRequest{Data: "unit test 1"}) + _, err := ftltest.Call[InsertClient, InsertRequest, InsertResponse](ctx, InsertRequest{Data: "unit test 1"}) + assert.NoError(t, err) list, err := getAll(ctx) assert.NoError(t, err) assert.Equal(t, 1, len(list)) @@ -47,6 +53,10 @@ func TestOptionOrdering(t *testing.T) { } func getAll(ctx context.Context) ([]string, error) { + db, err := ftltest.GetDatabaseHandle[MyDbConfig]() + if err != nil { + return nil, err + } rows, err := db.Get(ctx).Query("SELECT data FROM requests ORDER BY created_at;") if err != nil { return nil, err diff --git a/backend/controller/sql/testdata/go/database/types.ftl.go b/backend/controller/sql/testdata/go/database/types.ftl.go new file mode 100644 index 0000000000..56c4357978 --- /dev/null +++ b/backend/controller/sql/testdata/go/database/types.ftl.go @@ -0,0 +1,20 @@ +// Code generated by FTL. DO NOT EDIT. +package database + +import ( + "context" + "github.com/TBD54566975/ftl/go-runtime/ftl/reflection" + "github.com/TBD54566975/ftl/go-runtime/server" +) + +type InsertClient func(context.Context, InsertRequest) (InsertResponse, error) + +func init() { + reflection.Register( + reflection.Database[MyDbConfig](server.InitPostgres), + reflection.ProvideResourcesForVerb( + Insert, + server.PostgresDatabaseHandle[MyDbConfig](), + ), + ) +} diff --git a/backend/provisioner/testdata/go/echo/echo.go b/backend/provisioner/testdata/go/echo/echo.go index dd48baa478..c4a98d24ef 100644 --- a/backend/provisioner/testdata/go/echo/echo.go +++ b/backend/provisioner/testdata/go/echo/echo.go @@ -9,12 +9,16 @@ import ( "github.com/TBD54566975/ftl/go-runtime/ftl" ) -var db = ftl.PostgresDatabase("echodb") +type EchoDBConfig struct { + ftl.DefaultPostgresDatabaseConfig +} + +func (EchoDBConfig) Name() string { return "echodb" } // Echo returns a greeting with the current time. // //ftl:verb export -func Echo(ctx context.Context, req string) (string, error) { +func Echo(ctx context.Context, req string, db ftl.DatabaseHandle[EchoDBConfig]) (string, error) { _, err := db.Get(ctx).Exec(`CREATE TABLE IF NOT EXISTS messages( message TEXT );`) diff --git a/backend/provisioner/testdata/go/echo/go.mod b/backend/provisioner/testdata/go/echo/go.mod index edf186ec10..28d1407c41 100644 --- a/backend/provisioner/testdata/go/echo/go.mod +++ b/backend/provisioner/testdata/go/echo/go.mod @@ -11,7 +11,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/backend/provisioner/testdata/go/echo/go.sum b/backend/provisioner/testdata/go/echo/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/backend/provisioner/testdata/go/echo/go.sum +++ b/backend/provisioner/testdata/go/echo/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/docs/content/docs/reference/unittests.md b/docs/content/docs/reference/unittests.md index 16641a3152..c92226999f 100644 --- a/docs/content/docs/reference/unittests.md +++ b/docs/content/docs/reference/unittests.md @@ -60,19 +60,22 @@ ctx := ftltest.Context( ``` ### Databases -By default, calling `Get(ctx)` on a database panics. - To enable database access in a test, you must first [provide a DSN via a project file](#project-files-configs-and-secrets). You can then set up a test database: ```go ctx := ftltest.Context( ftltest.WithDefaultProjectFile(), - ftltest.WithDatabase(db), + ftltest.WithDatabase[MyDBConfig](), ) ``` This will: - Take the provided DSN and appends `_test` to the database name. Eg: `accounts` becomes `accounts_test` - Wipe all tables in the database so each test run happens on a clean database +You can access the database in your test using its handle: +```go +db, err := ftltest.GetDatabaseHandle[MyDBConfig]() +db.Get(ctx).Exec(...) +``` ### Maps By default, calling `Get(ctx)` on a map handle will panic. diff --git a/examples/go/cron/go.mod b/examples/go/cron/go.mod index 8a1896a998..a3793bfa08 100644 --- a/examples/go/cron/go.mod +++ b/examples/go/cron/go.mod @@ -11,7 +11,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/examples/go/cron/go.sum b/examples/go/cron/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/examples/go/cron/go.sum +++ b/examples/go/cron/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/examples/go/http/go.mod b/examples/go/http/go.mod index ac18729b82..31a1d8b75e 100644 --- a/examples/go/http/go.mod +++ b/examples/go/http/go.mod @@ -11,7 +11,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/examples/go/http/go.sum b/examples/go/http/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/examples/go/http/go.sum +++ b/examples/go/http/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/examples/go/pubsub/go.mod b/examples/go/pubsub/go.mod index 4be89a775e..888c07c704 100644 --- a/examples/go/pubsub/go.mod +++ b/examples/go/pubsub/go.mod @@ -14,7 +14,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/examples/go/pubsub/go.sum b/examples/go/pubsub/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/examples/go/pubsub/go.sum +++ b/examples/go/pubsub/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/frontend/cli/testdata/go/echo/go.mod b/frontend/cli/testdata/go/echo/go.mod index 12ea5b75d1..a446e79869 100644 --- a/frontend/cli/testdata/go/echo/go.mod +++ b/frontend/cli/testdata/go/echo/go.mod @@ -11,7 +11,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/frontend/cli/testdata/go/echo/go.sum b/frontend/cli/testdata/go/echo/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/frontend/cli/testdata/go/echo/go.sum +++ b/frontend/cli/testdata/go/echo/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/frontend/cli/testdata/go/time/go.mod b/frontend/cli/testdata/go/time/go.mod index 43f6271d06..6001f4166f 100644 --- a/frontend/cli/testdata/go/time/go.mod +++ b/frontend/cli/testdata/go/time/go.mod @@ -11,7 +11,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/frontend/cli/testdata/go/time/go.sum b/frontend/cli/testdata/go/time/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/frontend/cli/testdata/go/time/go.sum +++ b/frontend/cli/testdata/go/time/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/compile/build-template/.ftl.tmpl/go/main/main.go.tmpl b/go-runtime/compile/build-template/.ftl.tmpl/go/main/main.go.tmpl index 7febb945a2..5a413f8cd1 100644 --- a/go-runtime/compile/build-template/.ftl.tmpl/go/main/main.go.tmpl +++ b/go-runtime/compile/build-template/.ftl.tmpl/go/main/main.go.tmpl @@ -1,4 +1,5 @@ {{- $verbs := .Verbs -}} +{{- $dbs := .Databases -}} {{- $name := .Name -}} {{- with .MainCtx -}} @@ -24,6 +25,11 @@ func init() { {{- range .ExternalTypes}} reflection.ExternalType(*new({{.TypeName}})), {{- end}} +{{- range $dbs}} + {{- if eq .Type "postgres" }} + reflection.Database[{{.TypeName}}]("{{.Name}}", server.InitPostgres), + {{- end }} +{{- end}} {{- range $verbs}} reflection.ProvideResourcesForVerb( {{.TypeName}}, @@ -38,6 +44,11 @@ func init() { {{- else }} server.VerbClient[{{.TypeName}}, {{.Request.TypeName}}, {{.Response.TypeName}}](), {{- end -}} + {{- end }} + {{- with getDatabaseHandle . }} + {{- if eq .Type "postgres" }} + server.PostgresDatabaseHandle[{{.TypeName}}](), + {{- end }} {{- end }} {{- end}} ), diff --git a/go-runtime/compile/build-template/types.ftl.go.tmpl b/go-runtime/compile/build-template/types.ftl.go.tmpl index 4d30ea5267..7dd99d09c8 100644 --- a/go-runtime/compile/build-template/types.ftl.go.tmpl +++ b/go-runtime/compile/build-template/types.ftl.go.tmpl @@ -1,4 +1,5 @@ {{- $verbs := .Verbs -}} +{{- $dbs := .Databases -}} {{- $name := .Name -}} {{- with .TypesCtx -}} {{- $moduleName := .MainModulePkg -}} @@ -42,6 +43,11 @@ func init() { {{- range .ExternalTypes}} reflection.ExternalType(*new({{.TypeName}})), {{- end}} +{{- range $dbs}} + {{- if eq .Type "postgres" }} + reflection.Database[{{ trimModuleQualifier $moduleName .TypeName }}]("{{.Name}}", server.InitPostgres), + {{- end }} +{{- end}} {{- range $verbs}} reflection.ProvideResourcesForVerb( {{ trimModuleQualifier $moduleName .TypeName }}, @@ -58,6 +64,11 @@ func init() { {{- else }} server.VerbClient[{{$verb}}, {{.Request.LocalTypeName}}, {{.Response.LocalTypeName}}](), {{- end }} + {{- end }} + {{- with getDatabaseHandle . }} + {{- if eq .Type "postgres" }} + server.PostgresDatabaseHandle[{{ trimModuleQualifier $moduleName .TypeName }}](), + {{- end }} {{- end }} {{- end}} ), diff --git a/go-runtime/compile/build.go b/go-runtime/compile/build.go index 71002beccc..5968a0b9dc 100644 --- a/go-runtime/compile/build.go +++ b/go-runtime/compile/build.go @@ -45,6 +45,7 @@ type mainModuleContext struct { Name string SharedModulesPaths []string Verbs []goVerb + Databases []goDBHandle Replacements []*modfile.Replace MainCtx mainFileContext TypesCtx typesFileContext @@ -88,6 +89,9 @@ func (c *mainModuleContext) generateTypesImports(mainModuleImport string) []stri if len(c.Verbs) > 0 { imports.Add(`"context"`) } + if len(c.Databases) > 0 { + imports.Add(`"github.com/TBD54566975/ftl/go-runtime/server"`) + } for _, st := range c.TypesCtx.SumTypes { imports.Add(st.importStatement()) for _, v := range st.Variants { @@ -238,10 +242,18 @@ type verbClient struct { func (v verbClient) resource() {} -type ModifyFilesTransaction interface { - Begin() error - ModifiedFiles(paths ...string) error - End() error +type goDBHandle struct { + Type string + Name string + Module string + + nativeType +} + +func (d goDBHandle) resource() {} + +func (d goDBHandle) getNativeType() nativeType { + return d.nativeType } const buildDirName = ".ftl" @@ -416,6 +428,7 @@ func (b *mainModuleContextBuilder) build(goModVersion, ftlVersion, projectName s SharedModulesPaths: sharedModulesPaths, Replacements: replacements, Verbs: make([]goVerb, 0, len(b.mainModule.Decls)), + Databases: make([]goDBHandle, 0, len(b.mainModule.Decls)), MainCtx: mainFileContext{ ProjectName: projectName, SumTypes: []goSumType{}, @@ -498,6 +511,8 @@ func (b *mainModuleContextBuilder) visit( case goExternalType: ctx.TypesCtx.ExternalTypes = append(ctx.TypesCtx.ExternalTypes, n) ctx.MainCtx.ExternalTypes = append(ctx.MainCtx.ExternalTypes, n) + case goDBHandle: + ctx.Databases = append(ctx.Databases, n) } return next() }) @@ -535,6 +550,15 @@ func (b *mainModuleContextBuilder) getGoType(module *schema.Module, node schema. return optional.None[goType](), isLocal, nil } return b.processExternalTypeAlias(n), isLocal, nil + case *schema.Database: + if !isLocal { + return optional.None[goType](), false, nil + } + dbHandle, err := b.processDatabase(module.Name, n) + if err != nil { + return optional.None[goType](), isLocal, err + } + return optional.Some[goType](dbHandle), isLocal, nil default: } @@ -633,6 +657,26 @@ func (b *mainModuleContextBuilder) processVerb(verb *schema.Verb) (goVerb, error calleeverb, }) } + case *schema.MetadataDatabases: + for _, call := range md.Calls { + resolved, ok := b.sch.Resolve(call).Get() + if !ok { + return goVerb{}, fmt.Errorf("failed to resolve %s database, used by %s.%s", call, + b.mainModule.Name, verb.Name) + } + db, ok := resolved.(*schema.Database) + if !ok { + return goVerb{}, fmt.Errorf("%s.%s uses %s database handle, but %s is not a database", + b.mainModule.Name, verb.Name, call, call) + } + + dbHandle, err := b.processDatabase(call.Module, db) + if err != nil { + return goVerb{}, err + } + resources = append(resources, dbHandle) + } + default: // TODO: implement other resources } @@ -645,6 +689,24 @@ func (b *mainModuleContextBuilder) processVerb(verb *schema.Verb) (goVerb, error return b.getGoVerb(nativeName, verb, resources...) } +func (b *mainModuleContextBuilder) processDatabase(moduleName string, db *schema.Database) (goDBHandle, error) { + nn, ok := b.nativeNames[db] + if !ok { + return goDBHandle{}, fmt.Errorf("missing native name for database %s.%s", moduleName, db.Name) + } + + nt, err := b.getNativeType(nn) + if err != nil { + return goDBHandle{}, err + } + return goDBHandle{ + Name: db.Name, + Module: moduleName, + Type: db.Type, + nativeType: nt, + }, nil +} + func (b *mainModuleContextBuilder) getGoVerb(nativeName string, verb *schema.Verb, resources ...verbResource) (goVerb, error) { nt, err := b.getNativeType(nativeName) if err != nil { @@ -830,6 +892,12 @@ var scaffoldFuncs = scaffolder.FuncMap{ } return nil }, + "getDatabaseHandle": func(resource verbResource) *goDBHandle { + if c, ok := resource.(goDBHandle); ok { + return &c + } + return nil + }, } // returns the import path and the directory name for a type alias if there is an associated go library diff --git a/go-runtime/compile/compile_integration_test.go b/go-runtime/compile/compile_integration_test.go index e90fe6b74b..def686ff89 100644 --- a/go-runtime/compile/compile_integration_test.go +++ b/go-runtime/compile/compile_integration_test.go @@ -20,7 +20,7 @@ func TestNonExportedDecls(t *testing.T) { in.CopyModule("notexportedverb"), in.ExpectError( in.ExecWithOutput("ftl", []string{"deploy", "notexportedverb"}, func(_ string) {}), - `unsupported verb parameter type &{"echo" "EchoClient"}; verbs must have the signature func(Context, Request?, Resources...)`, + `unsupported verb parameter type; verbs must have the signature func(Context, Request?, Resources...)`, ), ) } @@ -34,7 +34,7 @@ func TestUndefinedExportedDecls(t *testing.T) { in.CopyModule("undefinedverb"), in.ExpectError( in.ExecWithOutput("ftl", []string{"deploy", "undefinedverb"}, func(_ string) {}), - `unsupported verb parameter type &{"echo" "UndefinedClient"}; verbs must have the signature func(Context, Request?, Resources...)`, + `unsupported verb parameter type; verbs must have the signature func(Context, Request?, Resources...)`, ), ) } diff --git a/go-runtime/compile/testdata/go/notexportedverb/go.mod b/go-runtime/compile/testdata/go/notexportedverb/go.mod index 7b1ef69997..5ac5779b78 100644 --- a/go-runtime/compile/testdata/go/notexportedverb/go.mod +++ b/go-runtime/compile/testdata/go/notexportedverb/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/compile/testdata/go/notexportedverb/go.sum b/go-runtime/compile/testdata/go/notexportedverb/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/compile/testdata/go/notexportedverb/go.sum +++ b/go-runtime/compile/testdata/go/notexportedverb/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/compile/testdata/go/one/go.mod b/go-runtime/compile/testdata/go/one/go.mod index e31f404759..0363e62cff 100644 --- a/go-runtime/compile/testdata/go/one/go.mod +++ b/go-runtime/compile/testdata/go/one/go.mod @@ -11,7 +11,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/compile/testdata/go/one/go.sum b/go-runtime/compile/testdata/go/one/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/compile/testdata/go/one/go.sum +++ b/go-runtime/compile/testdata/go/one/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/compile/testdata/go/one/one.go b/go-runtime/compile/testdata/go/one/one.go index 2e07d9466d..74e0de09af 100644 --- a/go-runtime/compile/testdata/go/one/one.go +++ b/go-runtime/compile/testdata/go/one/one.go @@ -133,7 +133,12 @@ type ExportedData struct { var configValue = ftl.Config[Config]("configValue") var secretValue = ftl.Secret[string]("secretValue") -var testDb = ftl.PostgresDatabase("testDb") + +type MyDbConfig struct { + ftl.DefaultPostgresDatabaseConfig +} + +func (MyDbConfig) Name() string { return "testDb" } //ftl:verb func Verb(ctx context.Context, req Req) (Resp, error) { diff --git a/go-runtime/compile/testdata/go/undefinedverb/go.mod b/go-runtime/compile/testdata/go/undefinedverb/go.mod index 7b1ef69997..5ac5779b78 100644 --- a/go-runtime/compile/testdata/go/undefinedverb/go.mod +++ b/go-runtime/compile/testdata/go/undefinedverb/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/compile/testdata/go/undefinedverb/go.sum b/go-runtime/compile/testdata/go/undefinedverb/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/compile/testdata/go/undefinedverb/go.sum +++ b/go-runtime/compile/testdata/go/undefinedverb/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/encoding/testdata/go/omitempty/go.mod b/go-runtime/encoding/testdata/go/omitempty/go.mod index abe00848e9..154f92be8a 100644 --- a/go-runtime/encoding/testdata/go/omitempty/go.mod +++ b/go-runtime/encoding/testdata/go/omitempty/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/encoding/testdata/go/omitempty/go.sum b/go-runtime/encoding/testdata/go/omitempty/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/encoding/testdata/go/omitempty/go.sum +++ b/go-runtime/encoding/testdata/go/omitempty/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/ftl/database.go b/go-runtime/ftl/database.go index e434e3f94a..c963636353 100644 --- a/go-runtime/ftl/database.go +++ b/go-runtime/ftl/database.go @@ -4,69 +4,74 @@ import ( "context" "database/sql" "fmt" - "time" - "github.com/XSAM/otelsql" "github.com/alecthomas/types/once" _ "github.com/jackc/pgx/v5/stdlib" // Register Postgres driver - "go.opentelemetry.io/otel/attribute" - semconv "go.opentelemetry.io/otel/semconv/v1.4.0" - - "github.com/TBD54566975/ftl/internal/log" - "github.com/TBD54566975/ftl/internal/modulecontext" ) -type Database struct { - Name string - DBType modulecontext.DBType +type DatabaseConfig interface { + // Name returns the name of the database. + Name() string + /* + // RAM returns the amount of memory (in bytes) allocated to the database. + RAM() int64 + // Disk returns the path or identifier for the disk where the database data is stored. + Disk() string + // Timeout returns the timeout value (in milliseconds) for database operations, such as queries or connections. + Timeout() int64 + // MaxConnections returns the maximum number of concurrent database connections allowed. + MaxConnections() int + */ + db() +} - db *once.Handle[*sql.DB] +type PostgresDatabaseConfig interface { + DatabaseConfig + pg() } -// PostgresDatabase returns a handler for the named database. -func PostgresDatabase(name string) Database { - return Database{ - Name: name, - DBType: modulecontext.DBTypePostgres, - db: once.Once(func(ctx context.Context) (*sql.DB, error) { - logger := log.FromContext(ctx) +// DefaultPostgresDatabaseConfig is a default implementation of PostgresDatabaseConfig. It does not provide +// an implementation for the Name method and should be embedded in a struct that does. +type DefaultPostgresDatabaseConfig struct{} - provider := modulecontext.FromContext(ctx).CurrentContext() - dsn, err := provider.GetDatabase(name, modulecontext.DBTypePostgres) - if err != nil { - return nil, fmt.Errorf("failed to get database %q: %w", name, err) - } +func (DefaultPostgresDatabaseConfig) db() {} //nolint:unused +func (DefaultPostgresDatabaseConfig) pg() {} //nolint:unused - logger.Debugf("Opening database: %s", name) - db, err := otelsql.Open("pgx", dsn) - if err != nil { - return nil, fmt.Errorf("failed to open database %q: %w", name, err) - } +type DatabaseType string - // sets db.system and db.name attributes - metricAttrs := otelsql.WithAttributes( - semconv.DBSystemPostgreSQL, - semconv.DBNameKey.String(name), - attribute.Bool("ftl.is_user_service", true), - ) - err = otelsql.RegisterDBStatsMetrics(db, metricAttrs) - if err != nil { - return nil, fmt.Errorf("failed to register database metrics: %w", err) - } - db.SetConnMaxIdleTime(time.Minute) - db.SetMaxOpenConns(20) - return db, nil - }), - } +const ( + DatabaseTypePostgres DatabaseType = "postgres" +) + +type DatabaseHandle[T DatabaseConfig] struct { + name string + _type DatabaseType + db *once.Handle[*sql.DB] } -func (d Database) String() string { return fmt.Sprintf("database %q", d.Name) } +// Name returns the name of the database. +func (d DatabaseHandle[T]) Name() string { return d.name } + +// Type returns the type of the database, e.g. "postgres" +func (d DatabaseHandle[T]) Type() DatabaseType { + return d._type +} + +// String returns a string representation of the database handle. +func (d DatabaseHandle[T]) String() string { + return fmt.Sprintf("database %q", d.name) +} // Get returns the SQL DB connection for the database. -func (d Database) Get(ctx context.Context) *sql.DB { +func (d DatabaseHandle[T]) Get(ctx context.Context) *sql.DB { db, err := d.db.Get(ctx) if err != nil { panic(err) } return db } + +// NewDatabaseHandle is managed by FTL. +func NewDatabaseHandle[T DatabaseConfig](config T, dbType DatabaseType, db *once.Handle[*sql.DB]) DatabaseHandle[T] { + return DatabaseHandle[T]{name: config.Name(), db: db, _type: dbType} +} diff --git a/go-runtime/ftl/ftltest/ftltest.go b/go-runtime/ftl/ftltest/ftltest.go index 1bddf6ff63..1c5681cd82 100644 --- a/go-runtime/ftl/ftltest/ftltest.go +++ b/go-runtime/ftl/ftltest/ftltest.go @@ -217,21 +217,16 @@ func WithSecret[T ftl.SecretType](secret ftl.SecretValue[T], value T) Option { } // WithDatabase sets up a database for testing by appending "_test" to the DSN and emptying all tables -// -// To be used when setting up a context for a test: -// -// ctx := ftltest.Context( -// ftltest.WithDatabase(db), -// // ... other options -// ) -func WithDatabase(dbHandle ftl.Database) Option { +func WithDatabase[T ftl.DatabaseConfig]() Option { return Option{ rank: other, apply: func(ctx context.Context, state *OptionsState) error { + cfg := defaultDatabaseConfig[T]() + name := cfg.Name() fftl := internal.FromContext(ctx) - originalDSN, err := getDSNFromSecret(fftl, moduleGetter(), dbHandle.Name) + originalDSN, err := getDSNFromSecret(fftl, moduleGetter(), name) if err != nil { - return err + return fmt.Errorf("could not get DSN for database %q, try adding ftltest.WithProject[MyConfig] as an option with ftltest.Context(...): %w", name, err) } // convert DSN by appending "_test" to table name @@ -242,7 +237,7 @@ func WithDatabase(dbHandle ftl.Database) Option { return fmt.Errorf("could not parse DSN: %w", err) } if dsnURL.Path == "" { - return fmt.Errorf("DSN for %s must include table name: %s", dbHandle.Name, originalDSN) + return fmt.Errorf("DSN for %s must include table name: %s", name, originalDSN) } dsnURL.Path += "_test" dsn := dsnURL.String() @@ -250,7 +245,7 @@ func WithDatabase(dbHandle ftl.Database) Option { // connect to db and clear out the contents of each table sqlDB, err := sql.Open("pgx", dsn) if err != nil { - return fmt.Errorf("could not create database %q with DSN %q: %w", dbHandle.Name, dsn, err) + return fmt.Errorf("could not create database %q with DSN %q: %w", name, dsn, err) } _, err = sqlDB.ExecContext(ctx, `DO $$ DECLARE @@ -264,15 +259,15 @@ func WithDatabase(dbHandle ftl.Database) Option { END LOOP; END $$;`) if err != nil { - return fmt.Errorf("could not clear tables in database %q: %w", dbHandle.Name, err) + return fmt.Errorf("could not clear tables in database %q: %w", name, err) } // replace original database with test database replacementDB, err := modulecontext.NewTestDatabase(modulecontext.DBTypePostgres, dsn) if err != nil { - return fmt.Errorf("could not create database %q with DSN %q: %w", dbHandle.Name, dsn, err) + return fmt.Errorf("could not create database %q with DSN %q: %w", name, dsn, err) } - state.databases[dbHandle.Name] = replacementDB + state.databases[name] = replacementDB return nil }, } @@ -526,6 +521,23 @@ func CallEmpty[VerbClient any](ctx context.Context) error { return err } +// GetDatabaseHandle returns a database handle using the given database config. +func GetDatabaseHandle[T ftl.DatabaseConfig]() (ftl.DatabaseHandle[T], error) { + reflectedDB := reflection.GetDatabase[T]() + if reflectedDB == nil { + return ftl.DatabaseHandle[T]{}, fmt.Errorf("could not find database for config") + } + + var dbType ftl.DatabaseType + switch reflectedDB.DBType { + case "postgres": + dbType = ftl.DatabaseTypePostgres + default: + return ftl.DatabaseHandle[T]{}, fmt.Errorf("unsupported database type %v", reflectedDB.DBType) + } + return ftl.NewDatabaseHandle[T](defaultDatabaseConfig[T](), dbType, reflectedDB.DB), nil +} + func call[VerbClient, Req, Resp any](ctx context.Context, req Req) (resp Resp, err error) { ref := reflection.ClientRef[VerbClient]() // always allow direct behavior for the verb triggered by this call @@ -534,7 +546,7 @@ func call[VerbClient, Req, Resp any](ctx context.Context, req Req) (resp Resp, e ).AddAllowedDirectVerb(ref).Build() ctx = mcu.MakeDynamic(ctx, moduleCtx).ApplyToContext(ctx) - inline := server.Call[Req, Resp](ref) + inline := server.InvokeVerb[Req, Resp](ref) override, err := moduleCtx.BehaviorForVerb(schema.Ref{Module: ref.Module, Name: ref.Name}) if err != nil { return resp, fmt.Errorf("test harness failed to retrieve behavior for verb %s: %w", ref, err) @@ -561,3 +573,14 @@ func widenVerb[Req, Resp any](verb ftl.Verb[Req, Resp]) ftl.Verb[any, any] { return verb(ctx, req) } } + +func defaultDatabaseConfig[T ftl.DatabaseConfig]() T { + typ := reflect.TypeFor[T]() + var cfg T + if typ.Kind() == reflect.Ptr { + cfg = reflect.New(typ.Elem()).Interface().(T) //nolint:forcetypeassert + } else { + cfg = reflect.New(typ).Elem().Interface().(T) //nolint:forcetypeassert + } + return cfg +} diff --git a/go-runtime/ftl/reflection/database.go b/go-runtime/ftl/reflection/database.go new file mode 100644 index 0000000000..f08199cca6 --- /dev/null +++ b/go-runtime/ftl/reflection/database.go @@ -0,0 +1,49 @@ +package reflection + +import ( + "database/sql" + "fmt" + "reflect" + + "github.com/alecthomas/types/once" +) + +type ReflectedDatabaseHandle struct { + DBType string + DB *once.Handle[*sql.DB] + + // configs + Name string +} + +func Database[T any](dbname string, init func(ref Ref) *ReflectedDatabaseHandle) Registree { + ref := Ref{ + Module: moduleForType(reflect.TypeFor[T]()), + Name: dbname, + } + return func(t *TypeRegistry) { + t.databases[ref] = init(ref) + } +} + +func getDatabaseName[T any]() string { + typ := reflect.TypeFor[T]() + var config T + if typ.Kind() == reflect.Ptr { + config = reflect.New(typ.Elem()).Interface().(T) //nolint:forcetypeassert + } else { + config = reflect.New(typ).Elem().Interface().(T) //nolint:forcetypeassert + } + + nameMethod := reflect.ValueOf(config).MethodByName("Name") + if !nameMethod.IsValid() { + panic(fmt.Sprintf("type %T must implement ftl.DatabaseConfig but does not have a Name() method", config)) + } + nameResult := nameMethod.Call(nil) + name, ok := nameResult[0].Interface().(string) + if !ok { + panic(fmt.Sprintf("Name() method of type %T must return a string, but returned %T", config, + nameResult[0].Interface())) + } + return name +} diff --git a/go-runtime/ftl/reflection/singleton.go b/go-runtime/ftl/reflection/singleton.go index a3b9e06337..1744996049 100644 --- a/go-runtime/ftl/reflection/singleton.go +++ b/go-runtime/ftl/reflection/singleton.go @@ -34,6 +34,14 @@ func GetVariantByName(discriminator reflect.Type, name string) optional.Option[r return singletonTypeRegistry.getVariantByName(discriminator, name) } +func GetDatabase[T any]() *ReflectedDatabaseHandle { + ref := Ref{ + Module: moduleForType(reflect.TypeFor[T]()), + Name: getDatabaseName[T](), + } + return singletonTypeRegistry.databases[ref] +} + // GetDiscriminatorByVariant returns the discriminator type for the given variant type. func GetDiscriminatorByVariant(variant reflect.Type) optional.Option[reflect.Type] { return singletonTypeRegistry.getDiscriminatorByVariant(variant) diff --git a/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.mod b/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.mod index ba2e23828e..062d1dd763 100644 --- a/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.mod +++ b/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.mod @@ -12,7 +12,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.sum b/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.sum +++ b/go-runtime/ftl/reflection/testdata/go/runtimereflection/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/ftl/reflection/type_registry.go b/go-runtime/ftl/reflection/type_registry.go index 6b00664397..ab56eefad4 100644 --- a/go-runtime/ftl/reflection/type_registry.go +++ b/go-runtime/ftl/reflection/type_registry.go @@ -15,6 +15,7 @@ type TypeRegistry struct { variantsToDiscriminators map[reflect.Type]reflect.Type externalTypes map[reflect.Type]struct{} verbCalls map[Ref]verbCall + databases map[Ref]*ReflectedDatabaseHandle } type sumTypeVariant struct { @@ -53,6 +54,7 @@ func newTypeRegistry(options ...Registree) *TypeRegistry { variantsToDiscriminators: map[reflect.Type]reflect.Type{}, externalTypes: map[reflect.Type]struct{}{}, verbCalls: map[Ref]verbCall{}, + databases: map[Ref]*ReflectedDatabaseHandle{}, } for _, o := range options { o(t) diff --git a/go-runtime/ftl/testdata/go/echo/go.mod b/go-runtime/ftl/testdata/go/echo/go.mod index 9ee4527d0c..9a24428ca5 100644 --- a/go-runtime/ftl/testdata/go/echo/go.mod +++ b/go-runtime/ftl/testdata/go/echo/go.mod @@ -11,7 +11,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/ftl/testdata/go/echo/go.sum b/go-runtime/ftl/testdata/go/echo/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/ftl/testdata/go/echo/go.sum +++ b/go-runtime/ftl/testdata/go/echo/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/goplugin/testdata/another/go.mod b/go-runtime/goplugin/testdata/another/go.mod index 2a995e671d..cfa2842f52 100644 --- a/go-runtime/goplugin/testdata/another/go.mod +++ b/go-runtime/goplugin/testdata/another/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/goplugin/testdata/another/go.sum b/go-runtime/goplugin/testdata/another/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/goplugin/testdata/another/go.sum +++ b/go-runtime/goplugin/testdata/another/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/goplugin/testdata/other/go.mod b/go-runtime/goplugin/testdata/other/go.mod index d5f0150c12..4418b9c19d 100644 --- a/go-runtime/goplugin/testdata/other/go.mod +++ b/go-runtime/goplugin/testdata/other/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/goplugin/testdata/other/go.sum b/go-runtime/goplugin/testdata/other/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/goplugin/testdata/other/go.sum +++ b/go-runtime/goplugin/testdata/other/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/internal/testdata/go/mapper/go.mod b/go-runtime/internal/testdata/go/mapper/go.mod index f28b2ea7d5..9cf63b7151 100644 --- a/go-runtime/internal/testdata/go/mapper/go.mod +++ b/go-runtime/internal/testdata/go/mapper/go.mod @@ -11,7 +11,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/internal/testdata/go/mapper/go.sum b/go-runtime/internal/testdata/go/mapper/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/internal/testdata/go/mapper/go.sum +++ b/go-runtime/internal/testdata/go/mapper/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/schema/common/common.go b/go-runtime/schema/common/common.go index f81b3bef45..7fcf192163 100644 --- a/go-runtime/schema/common/common.go +++ b/go-runtime/schema/common/common.go @@ -70,15 +70,18 @@ func NewDeclExtractor[T schema.Decl, N ast.Node](name string, extractFunc Extrac // NewResourceDeclExtractor creates a new schema declaration extractor to extract resources, e.g. Database, Subscription, // Topics. // -// Resources are extracted on the basis of their underlying type, e.g. ftl.PostgresDatabaseHandle, rather than +// Only resources where the provided `matchFunc` returns true will be visited for extraction. These resources are +// typically extracted on the basis of their underlying type, e.g. ftl.DatabaseConfig, rather than // an FTL directive. -func NewResourceDeclExtractor[T schema.Decl](name string, extractFunc ExtractResourceDeclFunc[T], typePaths ...string) *analysis.Analyzer { +func NewResourceDeclExtractor[T schema.Decl](name string, extractFunc ExtractResourceDeclFunc[T], matchFn matchFunc) *analysis.Analyzer { type Tag struct{} // Tag uniquely identifies the fact type for this extractor. - return NewExtractor(name, (*DefaultFact[Tag])(nil), runExtractResourceDeclsFunc[T](extractFunc, typePaths...)) + return NewExtractor(name, (*DefaultFact[Tag])(nil), runExtractResourceDeclsFunc[T](extractFunc, matchFn)) } -// ExtractResourceDeclFunc extracts a schema declaration from the given node, providing the path to the underlying resource type. -type ExtractResourceDeclFunc[T schema.Decl] func(pass *analysis.Pass, object types.Object, node *ast.TypeSpec, typePath string) optional.Option[T] +type matchFunc func(pass *analysis.Pass, node ast.Node) bool + +// ExtractResourceDeclFunc extracts a schema resource declaration from the given node. +type ExtractResourceDeclFunc[T schema.Decl] func(pass *analysis.Pass, object types.Object, node *ast.TypeSpec) optional.Option[T] // ExtractCallDeclFunc extracts a schema declaration from the given node. type ExtractCallDeclFunc[T schema.Decl] func(pass *analysis.Pass, object types.Object, node *ast.GenDecl, callExpr *ast.CallExpr, callPath string) optional.Option[T] @@ -138,7 +141,7 @@ func runExtractDeclsFunc[T schema.Decl, N ast.Node](extractFunc ExtractDeclFunc[ } } -func runExtractResourceDeclsFunc[T schema.Decl](extractFunc ExtractResourceDeclFunc[T], typePaths ...string) func(pass *analysis.Pass) (interface{}, error) { +func runExtractResourceDeclsFunc[T schema.Decl](extractFunc ExtractResourceDeclFunc[T], matchFunc matchFunc) func(pass *analysis.Pass) (interface{}, error) { return func(pass *analysis.Pass) (interface{}, error) { nodeFilter := []ast.Node{ (*ast.TypeSpec)(nil), @@ -154,24 +157,10 @@ func runExtractResourceDeclsFunc[T schema.Decl](extractFunc ExtractResourceDeclF return } - typeObj, ok := GetObjectForNode(pass.TypesInfo, node.Type).Get() - if !ok { - return - } - if typeObj.Pkg() == nil { - return - } - typePath := typeObj.Pkg().Path() + "." + typeObj.Name() - var matchesType bool - for _, path := range typePaths { - if typePath == path { - matchesType = true - } - } - if !matchesType { + if !matchFunc(pass, node) { return } - decl := extractFunc(pass, obj, node, typePath) + decl := extractFunc(pass, obj, node) if d, ok := decl.Get(); ok { MarkSchemaDecl(pass, obj, d) } @@ -371,6 +360,8 @@ func extractType(pass *analysis.Pass, node ast.Node) optional.Option[schema.Type for _, idx := range typ.Indices { if param, ok := ExtractType(pass, idx).Get(); ok { params = append(params, param) + } else { + Errorf(pass, idx, "unsupported type for type argument") } } ref.TypeParameters = params @@ -786,6 +777,47 @@ func CallExprFromVar(node *ast.GenDecl) optional.Option[*ast.CallExpr] { return optional.Some(callExpr) } +// IsDatabaseConfigType will return true if the provided type implements the `DatabaseConfig` type. +func IsDatabaseConfigType(pass *analysis.Pass, typ types.Type) bool { + return implementsType(pass, typ, "github.com/TBD54566975/ftl/go-runtime/ftl", "DatabaseConfig") +} + +// IsPostgresDatabaseConfigType will return true if the provided type implements the `DatabaseConfig` type. +func IsPostgresDatabaseConfigType(pass *analysis.Pass, typ types.Type) bool { + return implementsType(pass, typ, "github.com/TBD54566975/ftl/go-runtime/ftl", "PostgresDatabaseConfig") +} + +func implementsType(pass *analysis.Pass, typ types.Type, pkg string, name string) bool { + ityp, ok := loadRefFromImports(pass, pkg, name).Get() + if !ok { + return false + } + res := types.Implements(typ, ityp) || types.Implements(types.NewPointer(typ), ityp) + return res +} + +// Lazy load the compile-time reference from a package if it is imported by the package in this pass. +func loadRefFromImports(pass *analysis.Pass, pkg, name string) optional.Option[*types.Interface] { + var importedPkg *types.Package + for _, p := range pass.Pkg.Imports() { + if p.Path() == pkg { + importedPkg = p + } + } + if importedPkg == nil { + return optional.None[*types.Interface]() + } + obj := importedPkg.Scope().Lookup(name) + if obj == nil { + return optional.None[*types.Interface]() + } + ifaceType, ok := obj.Type().Underlying().(*types.Interface) + if !ok { + return optional.None[*types.Interface]() + } + return optional.Some(ifaceType) +} + // FuncPathEquals checks if the function call expression is a call to the given path. func FuncPathEquals(pass *analysis.Pass, callExpr *ast.CallExpr, path string) bool { _, fn := Deref[*types.Func](pass, callExpr.Fun) diff --git a/go-runtime/schema/common/fact.go b/go-runtime/schema/common/fact.go index ed8dd23398..45a936f8bf 100644 --- a/go-runtime/schema/common/fact.go +++ b/go-runtime/schema/common/fact.go @@ -128,6 +128,27 @@ type IncludeNativeName struct { func (*IncludeNativeName) schemaFactValue() {} +type DatabaseConfigMethod int + +const ( + DatabaseConfigMethodName DatabaseConfigMethod = iota +) + +type DatabaseType string + +const ( + DatabaseTypePostgres DatabaseType = "postgres" +) + +// DatabaseConfig marks a database node with an extracted configuration value. +type DatabaseConfig struct { + Type DatabaseType + Method DatabaseConfigMethod + Value any +} + +func (*DatabaseConfig) schemaFactValue() {} + // MarkSchemaDecl marks the given object as having been extracted to the given schema decl. func MarkSchemaDecl(pass *analysis.Pass, obj types.Object, decl schema.Decl) { fact := newFact(pass, obj) @@ -191,6 +212,14 @@ func MarkIncludeNativeName(pass *analysis.Pass, obj types.Object, node schema.No pass.ExportObjectFact(obj, fact) } +// MarkDatabaseConfig marks the given database object with an extracted config value. +func MarkDatabaseConfig(pass *analysis.Pass, obj types.Object, dbType DatabaseType, + method DatabaseConfigMethod, value any) { + fact := newFact(pass, obj) + fact.Add(&DatabaseConfig{Type: dbType, Method: method, Value: value}) + pass.ExportObjectFact(obj, fact) +} + // GetAllFactsExtractionStatus merges schema facts inclusive of all available results and the present pass facts. // For a given object, it provides the current extraction status. // @@ -291,7 +320,7 @@ func GetFactForObject[T SchemaFactValue](pass *analysis.Pass, obj types.Object) return optional.None[T]() } -// GetFactsForObject returns the all facts of the provided type marked on the object. +// GetFactsForObject returns all facts of the provided type marked on the object. func GetFactsForObject[T SchemaFactValue](pass *analysis.Pass, obj types.Object) []T { facts := []T{} for _, fact := range allFacts(pass) { diff --git a/go-runtime/schema/config/analyzer.go b/go-runtime/schema/config/analyzer.go new file mode 100644 index 0000000000..fca0648051 --- /dev/null +++ b/go-runtime/schema/config/analyzer.go @@ -0,0 +1,114 @@ +package config + +import ( + "go/ast" + "go/types" + "strconv" + + "github.com/TBD54566975/golang-tools/go/analysis" + "github.com/TBD54566975/golang-tools/go/analysis/passes/inspect" + "github.com/TBD54566975/golang-tools/go/ast/inspector" + + "github.com/TBD54566975/ftl/go-runtime/schema/common" +) + +// Extractor extracts config values relating to another decl, e.g. database configurations associated with a +// database decl. +// +// Configs follow a pattern where they implement an interface, like `ftl.DatabaseConfig`. +// We extract values by looking at known receiver methods. For example: +// +// type FooConfig struct{} +// +// func (f FooConfig) Name() string { +// return "foo" +// } +// +// From this, we'd extract the "foo" value as the database name for `FooConfig`. +var Extractor = common.NewExtractor("config", (*Fact)(nil), Extract) + +type Tag struct{} // Tag uniquely identifies the fact type for this extractor. +type Fact = common.DefaultFact[Tag] + +func Extract(pass *analysis.Pass) (interface{}, error) { + in := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert + nodeFilter := []ast.Node{ + (*ast.FuncDecl)(nil), + } + + in.Preorder(nodeFilter, func(n ast.Node) { + fn := n.(*ast.FuncDecl) //nolint:forcetypeassert + + // skip if there is no receiver + if fn.Recv == nil || len(fn.Recv.List) == 0 { + return + } + + // handle both pointer and non-pointer receivers + var ident *ast.Ident + var ok bool + switch expr := fn.Recv.List[0].Type.(type) { + case *ast.StarExpr: + ident, ok = expr.X.(*ast.Ident) + if !ok { + return + } + case *ast.Ident: + ident = expr + default: + return + } + + recType := pass.TypesInfo.TypeOf(ident) + if recType == nil { + return + } + + // receiver implements ftl.DatabaseConfig + if common.IsDatabaseConfigType(pass, recType) { + extractDatabaseConfig(pass, getDBType(pass, ident, recType), fn, ident) + } + + }) + return common.NewExtractorResult(pass), nil +} + +func extractDatabaseConfig(pass *analysis.Pass, dbType common.DatabaseType, fn *ast.FuncDecl, receiver *ast.Ident) { + obj, ok := common.GetObjectForNode(pass.TypesInfo, receiver).Get() + if !ok { + return + } + if len(fn.Body.List) == 0 { + return + } + returnStmt, ok := fn.Body.List[0].(*ast.ReturnStmt) + if !ok || returnStmt.Results == nil || len(returnStmt.Results) == 0 { + return + } + + switch fn.Name.Name { + case "Name": + lit, ok := returnStmt.Results[0].(*ast.BasicLit) + if !ok { + common.Errorf(pass, fn, "unexpected return type; must implement ftl.DatabaseConfig") + return + } + name, err := strconv.Unquote(lit.Value) + if err != nil { + common.Errorf(pass, fn, "unexpected return type; must implement ftl.DatabaseConfig") + return + } + common.MarkDatabaseConfig(pass, obj, dbType, common.DatabaseConfigMethodName, name) + + default: + return + } +} + +func getDBType(pass *analysis.Pass, receiver *ast.Ident, receiverType types.Type) common.DatabaseType { + if common.IsPostgresDatabaseConfigType(pass, receiverType) { + return common.DatabaseTypePostgres + } + common.Errorf(pass, receiver, "unsupported database type") + return "" +} diff --git a/go-runtime/schema/database/analyzer.go b/go-runtime/schema/database/analyzer.go index 8ce7ed7011..5b26ce0f5d 100644 --- a/go-runtime/schema/database/analyzer.go +++ b/go-runtime/schema/database/analyzer.go @@ -11,43 +11,76 @@ import ( "github.com/TBD54566975/ftl/internal/schema" ) -const ftlPostgresDBFuncPath = "github.com/TBD54566975/ftl/go-runtime/ftl.PostgresDatabase" - // Extractor extracts databases to the module schema. -var Extractor = common.NewCallDeclExtractor[*schema.Database]("database", Extract, ftlPostgresDBFuncPath) +var Extractor = common.NewResourceDeclExtractor[*schema.Database]("database", Extract, matchFunc) -func Extract(pass *analysis.Pass, obj types.Object, node *ast.GenDecl, callExpr *ast.CallExpr, - callPath string) optional.Option[*schema.Database] { +func Extract(pass *analysis.Pass, obj types.Object, node *ast.TypeSpec) optional.Option[*schema.Database] { var comments []string if md, ok := common.GetFactForObject[*common.ExtractedMetadata](pass, obj).Get(); ok { comments = md.Comments } - if callPath == ftlPostgresDBFuncPath { - return extractDatabase(pass, callExpr, schema.PostgresDatabaseType, comments) + switch getDBType(pass, node) { + case postgres: + return extractDatabase(pass, obj, node, schema.PostgresDatabaseType, comments) + default: + return optional.None[*schema.Database]() } - return optional.None[*schema.Database]() } func extractDatabase( pass *analysis.Pass, - node *ast.CallExpr, + obj types.Object, + node *ast.TypeSpec, dbType string, comments []string, ) optional.Option[*schema.Database] { - name := common.ExtractStringLiteralArg(pass, node, 0) - if name == "" { - return optional.None[*schema.Database]() + db := &schema.Database{ + Pos: common.GoPosToSchemaPos(pass.Fset, node.Pos()), + Comments: comments, + Type: dbType, } - if !schema.ValidateName(name) { - common.Errorf(pass, node, "invalid database name %q", name) + for _, cfg := range common.GetFactsForObject[*common.DatabaseConfig](pass, obj) { + if cfg.Method == common.DatabaseConfigMethodName { + name, ok := cfg.Value.(string) + if !ok { + common.Errorf(pass, node, "database name must be a string, was %T", cfg.Value) + return optional.None[*schema.Database]() + } + if !schema.ValidateName(name) { + common.Errorf(pass, node, "invalid database name %q", name) + return optional.None[*schema.Database]() + } + db.Name = name + } + } + if db.Name == "" { + common.Errorf(pass, node.Type, "database config must provide a name") return optional.None[*schema.Database]() } - return optional.Some(&schema.Database{ - Pos: common.GoPosToSchemaPos(pass.Fset, node.Pos()), - Comments: comments, - Name: name, - Type: dbType, - }) + return optional.Some(db) +} + +func matchFunc(pass *analysis.Pass, node ast.Node) bool { + return getDBType(pass, node) != none +} + +type dbType int + +const ( + none dbType = iota + postgres +) + +func getDBType(pass *analysis.Pass, node ast.Node) dbType { + ts := node.(*ast.TypeSpec) //nolint:forcetypeassert + typ, ok := common.GetTypeInfoForNode(ts.Name, pass.TypesInfo).Get() + if !ok { + return none + } + if common.IsPostgresDatabaseConfigType(pass, typ) { + return postgres + } + return none } diff --git a/go-runtime/schema/extract.go b/go-runtime/schema/extract.go index cec828575c..cac3b76e12 100644 --- a/go-runtime/schema/extract.go +++ b/go-runtime/schema/extract.go @@ -15,6 +15,7 @@ import ( "github.com/TBD54566975/ftl/go-runtime/schema/call" "github.com/TBD54566975/ftl/go-runtime/schema/common" + "github.com/TBD54566975/ftl/go-runtime/schema/config" "github.com/TBD54566975/ftl/go-runtime/schema/configsecret" "github.com/TBD54566975/ftl/go-runtime/schema/data" "github.com/TBD54566975/ftl/go-runtime/schema/database" @@ -49,6 +50,7 @@ var extractors = [][]*analysis.Analyzer{ metadata.Extractor, }, { + config.Extractor, // must run before typeenumvariant.Extractor; typeenum.Extractor determines all possible discriminator // interfaces and typeenumvariant.Extractor determines any types that implement these typeenum.Extractor, @@ -61,7 +63,6 @@ var extractors = [][]*analysis.Analyzer{ typealias.Extractor, typeenumvariant.Extractor, valueenumvariant.Extractor, - verb.Extractor, }, { call.Extractor, @@ -69,6 +70,7 @@ var extractors = [][]*analysis.Analyzer{ // visits a node and aggregates its enum variants if present enum.Extractor, subscription.Extractor, + verb.Extractor, }, { transitive.Extractor, @@ -310,6 +312,9 @@ func combineAllPackageResults(results map[*analysis.Analyzer][]any, diagnostics return Result{}, fmt.Errorf("schema extraction finalizer result not found") } for _, r := range fResults { + if r == nil { + return Result{}, fmt.Errorf("schema extraction failed") + } fr, ok := r.(finalize.Result) if !ok { return Result{}, fmt.Errorf("unexpected schema extraction result type: %T", r) diff --git a/go-runtime/schema/initialize/analyzer.go b/go-runtime/schema/initialize/analyzer.go index b410281c7b..750ad7cb58 100644 --- a/go-runtime/schema/initialize/analyzer.go +++ b/go-runtime/schema/initialize/analyzer.go @@ -27,8 +27,8 @@ type Result struct { types map[string]*types.Interface } -// IsFtlErrorType will return true if the provided type is assertable to the `builtin.error` type. -func (r Result) IsFtlErrorType(typ types.Type) bool { +// IsStdlibErrorType will return true if the provided type is assertable to the stdlib `error` type. +func (r Result) IsStdlibErrorType(typ types.Type) bool { return r.assertableToType(typ, "builtin", "error") } @@ -46,11 +46,11 @@ func (r Result) assertableToType(typ types.Type, pkg string, name string) bool { } func Run(pass *analysis.Pass) (interface{}, error) { - ctxType, err := loadRef("context", "Context") + ctxType, err := loadRef(pass, "context", "Context") if err != nil { return nil, err } - errType, err := loadRef("builtin", "error") + errType, err := loadRef(pass, "builtin", "error") if err != nil { return nil, err } @@ -61,17 +61,33 @@ func Run(pass *analysis.Pass) (interface{}, error) { }}, nil } -// Lazy load the compile-time reference from a package. -func loadRef(pkg, name string) (*types.Interface, error) { - pkgs, err := packages.Load(&packages.Config{Fset: token.NewFileSet(), Mode: packages.NeedTypes}, pkg) - if err != nil { - return nil, fmt.Errorf("failed to load package %q: %w", pkg, err) +// Lazy load the compile-time reference from a package. First attempts to derive the package from imports, then if not +// found attempts to load the package directly. +func loadRef(pass *analysis.Pass, pkg, name string) (*types.Interface, error) { + var importedPkg *types.Package + for _, p := range pass.Pkg.Imports() { + if p.Path() == pkg { + importedPkg = p + } + } + + // if the package is not imported, attempt to load it. + if importedPkg == nil { + pkgs, err := packages.Load(&packages.Config{Fset: token.NewFileSet(), Mode: packages.NeedTypes}, pkg) + if err != nil { + return nil, fmt.Errorf("failed to load package %q: %w", pkg, err) + } + if len(pkgs) != 1 { + return nil, fmt.Errorf("expected one package, got %s", + strings.Join(slices.Map(pkgs, func(p *packages.Package) string { return p.Name }), ", ")) + } + importedPkg = pkgs[0].Types } - if len(pkgs) != 1 { - return nil, fmt.Errorf("expected one package, got %s", - strings.Join(slices.Map(pkgs, func(p *packages.Package) string { return p.Name }), ", ")) + if importedPkg == nil { + return nil, fmt.Errorf("package %q not found", pkg) } - obj := pkgs[0].Types.Scope().Lookup(name) + + obj := importedPkg.Scope().Lookup(name) if obj == nil { return nil, fmt.Errorf("interface %q not found", name) } diff --git a/go-runtime/schema/schema_fuzz_integration_test.go b/go-runtime/schema/schema_fuzz_integration_test.go index c55a7d85fa..0b81daf0bb 100644 --- a/go-runtime/schema/schema_fuzz_integration_test.go +++ b/go-runtime/schema/schema_fuzz_integration_test.go @@ -143,7 +143,11 @@ func DataFunc(ctx context.Context, req Data) (Data, error) { } -var db = ftl.PostgresDatabase("testDb") +type MyDbConfig struct { + ftl.DefaultPostgresDatabaseConfig +} + +func (MyDbConfig) Name() string { return "testdb" } ` + (func() string { if symbol == "int" || symbol == "string" { @@ -336,7 +340,7 @@ module test { config cfg {{.TypeName}} secret secret {{.TypeName}} - database postgres testDb + database postgres testdb export topic exportedTopic {{.TypeName}} topic topic {{.TypeName}} diff --git a/go-runtime/schema/schema_integration_test.go b/go-runtime/schema/schema_integration_test.go index c4c323d1ba..936f1fb101 100644 --- a/go-runtime/schema/schema_integration_test.go +++ b/go-runtime/schema/schema_integration_test.go @@ -209,6 +209,8 @@ func testExtractModuleSchemaTwo(t *testing.T) { } actual := schema.Normalise(r.Module) expected := `module two { + database postgres foo + typealias BackoffAlias Any +typemap go "github.com/jpillora/backoff.Backoff" typealias ExplicitAliasAlias Any @@ -298,6 +300,7 @@ func testExtractModuleSchemaTwo(t *testing.T) { export verb three(two.Payload) two.Payload export verb two(two.Payload) two.Payload + +database calls two.foo } ` assert.Equal(t, normaliseString(expected), normaliseString(actual.String())) @@ -530,43 +533,43 @@ func testErrorReporting(t *testing.T) { `12:13-34: expected string literal for argument at index 0`, `15:18: duplicate config declaration for "failing.FTL_CONFIG_ENDPOINT"; already declared at "37:18"`, `18:18: duplicate secret declaration for "failing.FTL_SECRET_ENDPOINT"; already declared at "38:18"`, - `21:14: duplicate database declaration for "failing.testDb"; already declared at "41:14"`, - `24:2-10: unsupported type "error" for field "BadParam"`, - `27:2-17: unsupported type "uint64" for field "AnotherBadParam"`, - `30:3: unexpected directive "ftl:export" attached for verb, did you mean to use '//ftl:verb export' instead?`, - `36:45: unsupported request type "ftl/failing.Request"`, - `36:54-66: unsupported verb parameter type "Request"; verbs must have the signature func(Context, Request?, Resources...)`, - `36:69: unsupported response type "ftl/failing.Response"`, - `41:22-27: first parameter must be of type context.Context but is ftl/failing.Request`, - `41:53: unsupported response type "ftl/failing.Response"`, - `46:43-47: second parameter must not be ftl.Unit`, - `46:59: unsupported response type "ftl/failing.Response"`, - `51:1-2: first parameter must be context.Context`, - `51:18: unsupported response type "ftl/failing.Response"`, - `56:1-2: must have at most two results (, error)`, - `56:45: unsupported request type "ftl/failing.Request"`, - `61:1-2: must at least return an error`, - `61:40: unsupported request type "ftl/failing.Request"`, - `65:39: unsupported request type "ftl/failing.Request"`, - `65:48: must return an error but is ftl/failing.Response`, - `70:45: unsupported request type "ftl/failing.Request"`, - `70:63: must return an error but is string`, - `70:63: second result must not be ftl.Unit`, - `81:3: unexpected directive "ftl:verb"`, - `90:6-18: "BadValueEnum" is a value enum and cannot be tagged as a variant of type enum "TypeEnum" directly`, - `99:6-35: "BadValueEnumOrderDoesntMatter" is a value enum and cannot be tagged as a variant of type enum "TypeEnum" directly`, - `111:6: schema declaration with name "PrivateData" already exists for module "failing"; previously declared at "40:25"`, - `115:21-60: config names must be valid identifiers`, - `121:1: schema declaration contains conflicting directives`, - `121:1-26: only one directive expected when directive "ftl:enum" is present, found multiple`, - `143:6-45: enum discriminator "TypeEnum3" cannot contain exported methods`, - `146:6-35: enum discriminator "NoMethodsTypeEnum" must define at least one method`, - `158:3-14: unexpected token "d"`, - `165:2-62: can not publish directly to topics in other modules`, - `171:2-12: struct field unexported must be exported by starting with an uppercase letter`, - `175:6: unsupported type "ftl/failing/child.BadChildStruct" for field "child"`, - `180:6: duplicate data declaration for "failing.Redeclared"; already declared at "27:6"`, - `197:9: direct verb calls are not allowed; use the provided EmptyClient instead. See https://tbd54566975.github.io/ftl/docs/reference/verbs/#calling-verbs`, + `20:6: duplicate database declaration for "failing.testdb"; already declared at "42:6"`, + `27:2-10: unsupported type "error" for field "BadParam"`, + `30:2-17: unsupported type "uint64" for field "AnotherBadParam"`, + `33:3: unexpected directive "ftl:export" attached for verb, did you mean to use '//ftl:verb export' instead?`, + `39:45: unsupported request type "ftl/failing.Request"`, + `39:54-66: unsupported verb parameter type; verbs must have the signature func(Context, Request?, Resources...)`, + `39:69: unsupported response type "ftl/failing.Response"`, + `44:22-27: first parameter must be of type context.Context but is ftl/failing.Request`, + `44:53: unsupported response type "ftl/failing.Response"`, + `49:43-47: second parameter must not be ftl.Unit`, + `49:59: unsupported response type "ftl/failing.Response"`, + `54:1-2: first parameter must be context.Context`, + `54:18: unsupported response type "ftl/failing.Response"`, + `59:1-2: must have at most two results (, error)`, + `59:45: unsupported request type "ftl/failing.Request"`, + `64:1-2: must at least return an error`, + `64:40: unsupported request type "ftl/failing.Request"`, + `68:39: unsupported request type "ftl/failing.Request"`, + `68:48: must return an error but is "ftl/failing.Response"`, + `73:45: unsupported request type "ftl/failing.Request"`, + `73:63: must return an error but is "string"`, + `73:63: second result must not be ftl.Unit`, + `84:3: unexpected directive "ftl:verb"`, + `93:6-18: "BadValueEnum" is a value enum and cannot be tagged as a variant of type enum "TypeEnum" directly`, + `102:6-35: "BadValueEnumOrderDoesntMatter" is a value enum and cannot be tagged as a variant of type enum "TypeEnum" directly`, + `114:6: schema declaration with name "PrivateData" already exists for module "failing"; previously declared at "40:25"`, + `118:21-60: config names must be valid identifiers`, + `124:1: schema declaration contains conflicting directives`, + `124:1-26: only one directive expected when directive "ftl:enum" is present, found multiple`, + `146:6-45: enum discriminator "TypeEnum3" cannot contain exported methods`, + `149:6-35: enum discriminator "NoMethodsTypeEnum" must define at least one method`, + `161:3-14: unexpected token "d"`, + `168:2-62: can not publish directly to topics in other modules`, + `174:2-12: struct field unexported must be exported by starting with an uppercase letter`, + `178:6: unsupported type "ftl/failing/child.BadChildStruct" for field "child"`, + `183:6: duplicate data declaration for "failing.Redeclared"; already declared at "27:6"`, + `200:9: direct verb calls are not allowed; use the provided EmptyClient instead. See https://tbd54566975.github.io/ftl/docs/reference/verbs/#calling-verbs`, } // failing/child/child.go @@ -576,7 +579,7 @@ func testErrorReporting(t *testing.T) { `14:8: unsupported external type "github.com/TBD54566975/ftl/go-runtime/schema/testdata.NonFTLType"; see FTL docs on using external types: tbd54566975.github.io/ftl/docs/reference/externaltypes/`, `19:6-41: declared type github.com/blah.lib.NonFTLType in typemap does not match native type github.com/TBD54566975/ftl/go-runtime/schema/testdata.lib.NonFTLType`, `24:6: multiple Go type mappings found for "ftl/failing/child.MultipleMappings"`, - `34:2-13: enum variant "SameVariant" conflicts with existing enum variant of "EnumVariantConflictParent" at "187:2"`, + `34:2-13: enum variant "SameVariant" conflicts with existing enum variant of "EnumVariantConflictParent" at "190:2"`, } assert.Equal(t, expectedParent, actualParent) assert.Equal(t, expectedChild, actualChild) diff --git a/go-runtime/schema/testdata/failing/child/child.go b/go-runtime/schema/testdata/failing/child/child.go index e51e75175d..0170cc0626 100644 --- a/go-runtime/schema/testdata/failing/child/child.go +++ b/go-runtime/schema/testdata/failing/child/child.go @@ -38,4 +38,9 @@ var duplConfig = ftl.Config[string]("FTL_CONFIG_ENDPOINT") var duplSecret = ftl.Secret[string]("FTL_SECRET_ENDPOINT") var duplicateDeclName = ftl.Config[string]("PrivateData") -var duplDB = ftl.PostgresDatabase("testDb") + +type DuplDbConfig struct { + ftl.DefaultPostgresDatabaseConfig +} + +func (DuplDbConfig) Name() string { return "testdb" } diff --git a/go-runtime/schema/testdata/failing/failing.go b/go-runtime/schema/testdata/failing/failing.go index 85aabcf0c0..868e17fedd 100644 --- a/go-runtime/schema/testdata/failing/failing.go +++ b/go-runtime/schema/testdata/failing/failing.go @@ -17,8 +17,11 @@ var goodConfig = ftl.Config[string]("FTL_CONFIG_ENDPOINT") // var duplSecret = ftl.Secret[string]("FTL_ENDPOINT") var goodSecret = ftl.Secret[string]("FTL_SECRET_ENDPOINT") -// var duplDB = ftl.PostgresDatabase("testDb") -var goodDB = ftl.PostgresDatabase("testDb") +type DuplDbConfig struct { + ftl.DefaultPostgresDatabaseConfig +} + +func (DuplDbConfig) Name() string { return "testdb" } type Request struct { BadParam error diff --git a/go-runtime/schema/testdata/failing/go.mod b/go-runtime/schema/testdata/failing/go.mod index 45983b84a1..5d3dd361c4 100644 --- a/go-runtime/schema/testdata/failing/go.mod +++ b/go-runtime/schema/testdata/failing/go.mod @@ -11,7 +11,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/schema/testdata/failing/go.sum b/go-runtime/schema/testdata/failing/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/schema/testdata/failing/go.sum +++ b/go-runtime/schema/testdata/failing/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/schema/testdata/fsm/types.ftl.go b/go-runtime/schema/testdata/fsm/types.ftl.go new file mode 100644 index 0000000000..02cf3f9ff3 --- /dev/null +++ b/go-runtime/schema/testdata/fsm/types.ftl.go @@ -0,0 +1,32 @@ +// Code generated by FTL. DO NOT EDIT. +package fsm + +import ( + "context" + "github.com/TBD54566975/ftl/go-runtime/ftl/reflection" +) + +type CreatedClient func(context.Context, OnlinePaymentCreated) error + +type PaidClient func(context.Context, OnlinePaymentPaid) error + +type CompletedClient func(context.Context, OnlinePaymentCompleted) error + +type FailedClient func(context.Context, OnlinePaymentFailed) error + +func init() { + reflection.Register( + reflection.ProvideResourcesForVerb( + Created, + ), + reflection.ProvideResourcesForVerb( + Paid, + ), + reflection.ProvideResourcesForVerb( + Completed, + ), + reflection.ProvideResourcesForVerb( + Failed, + ), + ) +} diff --git a/go-runtime/schema/testdata/one/go.mod b/go-runtime/schema/testdata/one/go.mod index 704031f62f..370a4d231b 100644 --- a/go-runtime/schema/testdata/one/go.mod +++ b/go-runtime/schema/testdata/one/go.mod @@ -14,14 +14,18 @@ require ( github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect + github.com/alecthomas/kong v1.3.0 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect github.com/alecthomas/types v0.16.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/danieljoos/wincred v1.2.2 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/hashicorp/cronexpr v1.1.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -36,8 +40,15 @@ require ( github.com/swaggest/refl v1.3.0 // indirect github.com/zalando/go-keyring v0.2.6 // indirect go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.21.0 // indirect @@ -45,5 +56,8 @@ require ( golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect ) diff --git a/go-runtime/schema/testdata/one/go.sum b/go-runtime/schema/testdata/one/go.sum index 91fa4d7a80..3584716293 100644 --- a/go-runtime/schema/testdata/one/go.sum +++ b/go-runtime/schema/testdata/one/go.sum @@ -16,6 +16,8 @@ github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELk github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI= github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/kong v1.3.0 h1:YJKuU6/TV2XOBtymafSeuzDvLAFR8cYMZiXVNLhAO6g= +github.com/alecthomas/kong v1.3.0/go.mod h1:IDc8HyiouDdpdiEiY81iaEJM8rSIW6LzX8On4FCO0bE= github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= @@ -28,6 +30,8 @@ github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -68,6 +72,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -112,6 +118,8 @@ github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -145,6 +153,12 @@ github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8u github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= @@ -153,6 +167,12 @@ go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4Jjx go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= @@ -176,6 +196,10 @@ golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/go-runtime/schema/testdata/one/one.go b/go-runtime/schema/testdata/one/one.go index 56f02c0116..c924584c3d 100644 --- a/go-runtime/schema/testdata/one/one.go +++ b/go-runtime/schema/testdata/one/one.go @@ -134,7 +134,12 @@ type ExportedData struct { var configValue = ftl.Config[Config]("configValue") var secretValue = ftl.Secret[string]("secretValue") -var testDb = ftl.PostgresDatabase("testDb") + +type MyDbConfig struct { + ftl.DefaultPostgresDatabaseConfig +} + +func (MyDbConfig) Name() string { return "testDb" } //ftl:verb func Verb(ctx context.Context, req Req) (Resp, error) { diff --git a/go-runtime/schema/testdata/one/types.ftl.go b/go-runtime/schema/testdata/one/types.ftl.go index 981057bd21..cb73650e2d 100644 --- a/go-runtime/schema/testdata/one/types.ftl.go +++ b/go-runtime/schema/testdata/one/types.ftl.go @@ -6,6 +6,7 @@ import ( ftlbuiltin "ftl/builtin" "github.com/TBD54566975/ftl/go-runtime/ftl" "github.com/TBD54566975/ftl/go-runtime/ftl/reflection" + "github.com/TBD54566975/ftl/go-runtime/server" stdtime "time" ) @@ -40,6 +41,7 @@ func init() { *new(Option), *new(ValueEnum), ), + reflection.Database[MyDbConfig]("testDb", server.InitPostgres), reflection.ProvideResourcesForVerb( BatchStringToTime, ), diff --git a/go-runtime/schema/testdata/parent/go.mod b/go-runtime/schema/testdata/parent/go.mod index 1c8f2f36d3..db1928a3e8 100644 --- a/go-runtime/schema/testdata/parent/go.mod +++ b/go-runtime/schema/testdata/parent/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/schema/testdata/parent/go.sum b/go-runtime/schema/testdata/parent/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/schema/testdata/parent/go.sum +++ b/go-runtime/schema/testdata/parent/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/schema/testdata/pubsub/go.mod b/go-runtime/schema/testdata/pubsub/go.mod index 1efea13409..401fe1635e 100644 --- a/go-runtime/schema/testdata/pubsub/go.mod +++ b/go-runtime/schema/testdata/pubsub/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/schema/testdata/pubsub/go.sum b/go-runtime/schema/testdata/pubsub/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/schema/testdata/pubsub/go.sum +++ b/go-runtime/schema/testdata/pubsub/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/schema/testdata/subscriber/go.mod b/go-runtime/schema/testdata/subscriber/go.mod index 1168f1d967..9761018e63 100644 --- a/go-runtime/schema/testdata/subscriber/go.mod +++ b/go-runtime/schema/testdata/subscriber/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/schema/testdata/subscriber/go.sum b/go-runtime/schema/testdata/subscriber/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/schema/testdata/subscriber/go.sum +++ b/go-runtime/schema/testdata/subscriber/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/schema/testdata/two/two.go b/go-runtime/schema/testdata/two/two.go index 1f949568e3..581fe1101f 100644 --- a/go-runtime/schema/testdata/two/two.go +++ b/go-runtime/schema/testdata/two/two.go @@ -10,6 +10,12 @@ import ( "ftl/builtin" ) +type FooConfig struct { + ftl.DefaultPostgresDatabaseConfig +} + +func (FooConfig) Name() string { return "foo" } + //ftl:enum export type TwoEnum string @@ -53,7 +59,7 @@ type UserResponse struct { } //ftl:verb export -func Two(ctx context.Context, req Payload[string]) (Payload[string], error) { +func Two(ctx context.Context, req Payload[string], handle ftl.DatabaseHandle[FooConfig]) (Payload[string], error) { return Payload[string]{}, nil } diff --git a/go-runtime/schema/testdata/two/types.ftl.go b/go-runtime/schema/testdata/two/types.ftl.go index a946d86462..504b312d0f 100644 --- a/go-runtime/schema/testdata/two/types.ftl.go +++ b/go-runtime/schema/testdata/two/types.ftl.go @@ -33,12 +33,14 @@ func init() { ), reflection.ExternalType(*new(backoff.Backoff)), reflection.ExternalType(*new(lib.NonFTLType)), + reflection.Database[FooConfig]("foo", server.InitPostgres), reflection.ProvideResourcesForVerb( CallsTwo, server.VerbClient[TwoClient, Payload[string], Payload[string]](), ), reflection.ProvideResourcesForVerb( Two, + server.PostgresDatabaseHandle[FooConfig](), ), reflection.ProvideResourcesForVerb( CallsTwoAndThree, diff --git a/go-runtime/schema/testdata/validation/go.mod b/go-runtime/schema/testdata/validation/go.mod index c94ac80cf9..7189298ba7 100644 --- a/go-runtime/schema/testdata/validation/go.mod +++ b/go-runtime/schema/testdata/validation/go.mod @@ -11,7 +11,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/go-runtime/schema/testdata/validation/go.sum b/go-runtime/schema/testdata/validation/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/go-runtime/schema/testdata/validation/go.sum +++ b/go-runtime/schema/testdata/validation/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/go-runtime/schema/verb/analyzer.go b/go-runtime/schema/verb/analyzer.go index 373fadecaa..573cb96c45 100644 --- a/go-runtime/schema/verb/analyzer.go +++ b/go-runtime/schema/verb/analyzer.go @@ -20,6 +20,7 @@ type resourceType int const ( none resourceType = iota verbClient + databaseHandle ) // Extractor extracts verbs to the module schema. @@ -31,6 +32,8 @@ func Extract(pass *analysis.Pass, node *ast.FuncDecl, obj types.Object) optional Name: strcase.ToLowerCamel(node.Name.Name), } + loaded := pass.ResultOf[initialize.Analyzer].(initialize.Result) //nolint:forcetypeassert + hasRequest := false if !common.ApplyMetadata[*schema.Verb](pass, obj, func(md *common.ExtractedMetadata) { verb.Comments = md.Comments @@ -38,11 +41,11 @@ func Extract(pass *analysis.Pass, node *ast.FuncDecl, obj types.Object) optional verb.Metadata = md.Metadata for idx, param := range node.Type.Params.List { paramObj, hasObj := common.GetObjectForNode(pass.TypesInfo, param.Type).Get() - switch getParamResourceType(paramObj) { + switch getParamResourceType(pass, paramObj) { case none: if idx > 1 { - common.Errorf(pass, param, "unsupported verb parameter type %q; verbs must have the "+ - "signature func(Context, Request?, Resources...)", param.Type) + common.Errorf(pass, param, "unsupported verb parameter type; verbs must have the "+ + "signature func(Context, Request?, Resources...)") continue } if idx == 1 { @@ -50,13 +53,37 @@ func Extract(pass *analysis.Pass, node *ast.FuncDecl, obj types.Object) optional } case verbClient: if !hasObj { - common.Errorf(pass, param, "unsupported verb parameter type %q", param.Type) + common.Errorf(pass, param, "unsupported verb parameter type") continue } calleeRef := getResourceRef(paramObj, pass, param) calleeRef.Name = strings.TrimSuffix(calleeRef.Name, "Client") verb.AddCall(calleeRef) common.MarkIncludeNativeName(pass, paramObj, calleeRef) + case databaseHandle: + idxExpr, ok := param.Type.(*ast.IndexExpr) + if !ok { + common.Errorf(pass, param, "unsupported verb parameter type") + continue + } + idxObj, ok := common.GetObjectForNode(pass.TypesInfo, idxExpr.Index).Get() + if !ok { + common.Errorf(pass, param, "unsupported verb parameter type") + continue + } + decl, ok := common.GetFactForObject[*common.ExtractedDecl](pass, idxObj).Get() + if !ok { + common.Errorf(pass, param, "unsupported verb parameter type") + continue + } + db, ok := decl.Decl.(*schema.Database) + if !ok { + common.Errorf(pass, param, "no database found for config provided to database handle") + continue + } + ref := getResourceRef(idxObj, pass, param) + ref.Name = db.Name + verb.AddDatabase(ref) } } }) { @@ -70,7 +97,7 @@ func Extract(pass *analysis.Pass, node *ast.FuncDecl, obj types.Object) optional return optional.None[*schema.Verb]() } - reqt, respt := checkSignature(pass, node, sig, hasRequest) + reqt, respt := checkSignature(pass, loaded, node, sig, hasRequest) req := optional.Some[schema.Type](&schema.Unit{}) if reqt.Ok() { req = common.ExtractType(pass, node.Type.Params.List[1].Type) @@ -97,7 +124,13 @@ func Extract(pass *analysis.Pass, node *ast.FuncDecl, obj types.Object) optional return optional.Some(verb) } -func checkSignature(pass *analysis.Pass, node *ast.FuncDecl, sig *types.Signature, hasRequest bool) (req, resp optional.Option[*types.Var]) { +func checkSignature( + pass *analysis.Pass, + loaded initialize.Result, + node *ast.FuncDecl, + sig *types.Signature, + hasRequest bool, +) (req, resp optional.Option[*types.Var]) { if node.Name.Name == "" { common.Errorf(pass, node, "verb function must be named") return optional.None[*types.Var](), optional.None[*types.Var]() @@ -109,7 +142,6 @@ func checkSignature(pass *analysis.Pass, node *ast.FuncDecl, sig *types.Signatur params := sig.Params() results := sig.Results() - loaded := pass.ResultOf[initialize.Analyzer].(initialize.Result) //nolint:forcetypeassert if params.Len() == 0 { common.Errorf(pass, node, "first parameter must be context.Context") } else if !loaded.IsContextType(params.At(0).Type()) { @@ -131,8 +163,8 @@ func checkSignature(pass *analysis.Pass, node *ast.FuncDecl, sig *types.Signatur } if results.Len() == 0 { common.Errorf(pass, node, "must at least return an error") - } else if !loaded.IsFtlErrorType(results.At(results.Len() - 1).Type()) { - common.TokenErrorf(pass, results.At(results.Len()-1).Pos(), results.At(results.Len()-1).Name(), "must return an error but is %s", results.At(0).Type()) + } else if !loaded.IsStdlibErrorType(results.At(results.Len() - 1).Type()) { + common.TokenErrorf(pass, results.At(results.Len()-1).Pos(), results.At(results.Len()-1).Name(), "must return an error but is %q", results.At(0).Type()) } if results.Len() == 2 { if results.At(1).Type().String() == common.FtlUnitTypePath { @@ -143,18 +175,32 @@ func checkSignature(pass *analysis.Pass, node *ast.FuncDecl, sig *types.Signatur return req, resp } -func getParamResourceType(paramObj types.Object) resourceType { +func getParamResourceType(pass *analysis.Pass, paramObj types.Object) resourceType { if paramObj == nil { return none } + if paramObj.Pkg() == nil { + return none + } switch t := paramObj.Type().(type) { case *types.Named: + if isDatabaseHandleType(pass, t) { + return databaseHandle + } + if _, ok := t.Underlying().(*types.Signature); !ok { return none } return verbClient + case *types.Alias: + named, ok := t.Rhs().(*types.Named) + if !ok { + return none + } + return getParamResourceType(pass, named.Obj()) + default: return none } @@ -163,7 +209,7 @@ func getParamResourceType(paramObj types.Object) resourceType { func getResourceRef(paramObj types.Object, pass *analysis.Pass, param *ast.Field) *schema.Ref { paramModule, err := common.FtlModuleFromGoPackage(paramObj.Pkg().Path()) if err != nil { - common.Errorf(pass, param, "failed to resolve module for type %q: %v", paramObj.String(), err) + common.Errorf(pass, param, "failed to resolve module for type: %v", err) } dbRef := &schema.Ref{ Module: paramModule, @@ -171,3 +217,17 @@ func getResourceRef(paramObj types.Object, pass *analysis.Pass, param *ast.Field } return dbRef } + +func isDatabaseHandleType(pass *analysis.Pass, named *types.Named) bool { + if named.Obj().Pkg().Path()+"."+named.Obj().Name() != "github.com/TBD54566975/ftl/go-runtime/ftl.DatabaseHandle" { + return false + } + + if named.TypeParams().Len() != 1 { + return false + } + typeArg := named.TypeParams().At(0) + + // type argument implements `DatabaseConfig`, e.g. DatabaseHandle[MyConfig] where MyConfig implements DatabaseConfig + return common.IsDatabaseConfigType(pass, typeArg) +} diff --git a/go-runtime/server/database.go b/go-runtime/server/database.go new file mode 100644 index 0000000000..a0bb81c2c5 --- /dev/null +++ b/go-runtime/server/database.go @@ -0,0 +1,71 @@ +package server + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "time" + + "github.com/XSAM/otelsql" + "github.com/alecthomas/types/once" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + + "github.com/TBD54566975/ftl/go-runtime/ftl" + "github.com/TBD54566975/ftl/go-runtime/ftl/reflection" + "github.com/TBD54566975/ftl/internal/log" + "github.com/TBD54566975/ftl/internal/modulecontext" +) + +func PostgresDatabaseHandle[T ftl.DatabaseConfig]() reflection.VerbResource { + typ := reflect.TypeFor[T]() + var config T + if typ.Kind() == reflect.Ptr { + config = reflect.New(typ.Elem()).Interface().(T) //nolint:forcetypeassert + } else { + config = reflect.New(typ).Elem().Interface().(T) //nolint:forcetypeassert + } + + return func() reflect.Value { + reflectedDB := reflection.GetDatabase[T]() + db := ftl.NewDatabaseHandle(config, ftl.DatabaseTypePostgres, reflectedDB.DB) + return reflect.ValueOf(db) + } +} + +func InitPostgres(ref reflection.Ref) *reflection.ReflectedDatabaseHandle { + return &reflection.ReflectedDatabaseHandle{ + Name: ref.Name, + DBType: "postgres", + DB: once.Once(func(ctx context.Context) (*sql.DB, error) { + logger := log.FromContext(ctx) + + provider := modulecontext.FromContext(ctx).CurrentContext() + dsn, err := provider.GetDatabase(ref.Name, modulecontext.DBTypePostgres) + if err != nil { + return nil, fmt.Errorf("failed to get database %q: %w", ref.Name, err) + } + + logger.Debugf("Opening database: %s", ref.Name) + db, err := otelsql.Open("pgx", dsn) + if err != nil { + return nil, fmt.Errorf("failed to open database %q: %w", ref.Name, err) + } + + // sets db.system and db.name attributes + metricAttrs := otelsql.WithAttributes( + semconv.DBSystemPostgreSQL, + semconv.DBNameKey.String(ref.Name), + attribute.Bool("ftl.is_user_service", true), + ) + err = otelsql.RegisterDBStatsMetrics(db, metricAttrs) + if err != nil { + return nil, fmt.Errorf("failed to register database metrics: %w", err) + } + db.SetConnMaxIdleTime(time.Minute) + db.SetMaxOpenConns(20) + return db, nil + }), + } +} diff --git a/go-runtime/server/server.go b/go-runtime/server/server.go index bd3487321f..8f107cbc79 100644 --- a/go-runtime/server/server.go +++ b/go-runtime/server/server.go @@ -81,8 +81,8 @@ func HandleCall[Req, Resp any](verb any) Handler { } ctx = observability.AddSpanContextToLogger(ctx) - // Call Verb. - resp, err := Call[Req, Resp](ref)(ctx, req) + // InvokeVerb Verb. + resp, err := InvokeVerb[Req, Resp](ref)(ctx, req) if err != nil { return nil, fmt.Errorf("call to verb %s failed: %w", ref, err) } @@ -109,56 +109,29 @@ func HandleEmpty(verb any) Handler { return HandleCall[ftl.Unit, ftl.Unit](verb) } -func call[Verb, Req, Resp any]() func(ctx context.Context, req Req) (resp Resp, err error) { - typ := reflect.TypeFor[Verb]() - if typ.Kind() != reflect.Func { - panic(fmt.Sprintf("Cannot register %s: expected function, got %s", typ, typ.Kind())) - } - callee := reflection.TypeRef[Verb]() - callee.Name = strings.TrimSuffix(callee.Name, "Client") +func InvokeVerb[Req, Resp any](ref reflection.Ref) func(ctx context.Context, req Req) (resp Resp, err error) { return func(ctx context.Context, req Req) (resp Resp, err error) { - ref := reflection.Ref{Module: callee.Module, Name: callee.Name} - moduleCtx := modulecontext.FromContext(ctx).CurrentContext() - override, err := moduleCtx.BehaviorForVerb(schema.Ref{Module: ref.Module, Name: ref.Name}) - if err != nil { - return resp, fmt.Errorf("%s: %w", ref, err) - } - if behavior, ok := override.Get(); ok { - uncheckedResp, err := behavior.Call(ctx, modulecontext.Verb(widenVerb(Call[Req, Resp](ref))), req) - if err != nil { - return resp, fmt.Errorf("%s: %w", ref, err) - } - if r, ok := uncheckedResp.(Resp); ok { - return r, nil - } - return resp, fmt.Errorf("%s: overridden verb had invalid response type %T, expected %v", ref, - uncheckedResp, reflect.TypeFor[Resp]()) + request := optional.Some[any](req) + if reflect.TypeFor[Req]() == reflect.TypeFor[ftl.Unit]() { + request = optional.None[any]() } - reqData, err := encoding.Marshal(req) + out, err := reflection.CallVerb(reflection.Ref{Module: ref.Module, Name: ref.Name})(ctx, request) if err != nil { - return resp, fmt.Errorf("%s: failed to marshal request: %w", callee, err) + return resp, err } - client := rpc.ClientFromContext[ftlv1connect.VerbServiceClient](ctx) - cresp, err := client.Call(ctx, connect.NewRequest(&ftlv1.CallRequest{Verb: callee.ToProto(), Body: reqData})) - if err != nil { - return resp, fmt.Errorf("%s: failed to call Verb: %w", callee, err) + var respValue any + if r, ok := out.Get(); ok { + respValue = r + } else { + respValue = ftl.Unit{} } - switch cresp := cresp.Msg.Response.(type) { - case *ftlv1.CallResponse_Error_: - return resp, fmt.Errorf("%s: %s", callee, cresp.Error.Message) - - case *ftlv1.CallResponse_Body: - err = encoding.Unmarshal(cresp.Body, &resp) - if err != nil { - return resp, fmt.Errorf("%s: failed to decode response: %w", callee, err) - } - return resp, nil - - default: - panic(fmt.Sprintf("%s: invalid response type %T", callee, cresp)) + resp, ok := respValue.(Resp) + if !ok { + return resp, fmt.Errorf("unexpected response type from verb %s: %T", ref, resp) } + return resp, err } } @@ -201,29 +174,56 @@ func EmptyClient[Verb any]() reflection.VerbResource { } } -func Call[Req, Resp any](ref reflection.Ref) func(ctx context.Context, req Req) (resp Resp, err error) { +func call[Verb, Req, Resp any]() func(ctx context.Context, req Req) (resp Resp, err error) { + typ := reflect.TypeFor[Verb]() + if typ.Kind() != reflect.Func { + panic(fmt.Sprintf("Cannot register %s: expected function, got %s", typ, typ.Kind())) + } + callee := reflection.TypeRef[Verb]() + callee.Name = strings.TrimSuffix(callee.Name, "Client") return func(ctx context.Context, req Req) (resp Resp, err error) { - request := optional.Some[any](req) - if reflect.TypeFor[Req]() == reflect.TypeFor[ftl.Unit]() { - request = optional.None[any]() + ref := reflection.Ref{Module: callee.Module, Name: callee.Name} + moduleCtx := modulecontext.FromContext(ctx).CurrentContext() + override, err := moduleCtx.BehaviorForVerb(schema.Ref{Module: ref.Module, Name: ref.Name}) + if err != nil { + return resp, fmt.Errorf("%s: %w", ref, err) + } + if behavior, ok := override.Get(); ok { + uncheckedResp, err := behavior.Call(ctx, modulecontext.Verb(widenVerb(InvokeVerb[Req, Resp](ref))), req) + if err != nil { + return resp, fmt.Errorf("%s: %w", ref, err) + } + if r, ok := uncheckedResp.(Resp); ok { + return r, nil + } + return resp, fmt.Errorf("%s: overridden verb had invalid response type %T, expected %v", ref, + uncheckedResp, reflect.TypeFor[Resp]()) } - out, err := reflection.CallVerb(reflection.Ref{Module: ref.Module, Name: ref.Name})(ctx, request) + reqData, err := encoding.Marshal(req) if err != nil { - return resp, err + return resp, fmt.Errorf("%s: failed to marshal request: %w", callee, err) } - var respValue any - if r, ok := out.Get(); ok { - respValue = r - } else { - respValue = ftl.Unit{} + client := rpc.ClientFromContext[ftlv1connect.VerbServiceClient](ctx) + cresp, err := client.Call(ctx, connect.NewRequest(&ftlv1.CallRequest{Verb: callee.ToProto(), Body: reqData})) + if err != nil { + return resp, fmt.Errorf("%s: failed to call Verb: %w", callee, err) } - resp, ok := respValue.(Resp) - if !ok { - return resp, fmt.Errorf("unexpected response type from verb %s: %T", ref, resp) + switch cresp := cresp.Msg.Response.(type) { + case *ftlv1.CallResponse_Error_: + return resp, fmt.Errorf("%s: %s", callee, cresp.Error.Message) + + case *ftlv1.CallResponse_Body: + err = encoding.Unmarshal(cresp.Body, &resp) + if err != nil { + return resp, fmt.Errorf("%s: failed to decode response: %w", callee, err) + } + return resp, nil + + default: + panic(fmt.Sprintf("%s: invalid response type %T", callee, cresp)) } - return resp, err } } diff --git a/internal/buildengine/languageplugin/testdata/go/plugintest/go.mod b/internal/buildengine/languageplugin/testdata/go/plugintest/go.mod index 36aa6e83b2..f2f51a8c18 100644 --- a/internal/buildengine/languageplugin/testdata/go/plugintest/go.mod +++ b/internal/buildengine/languageplugin/testdata/go/plugintest/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/internal/buildengine/languageplugin/testdata/go/plugintest/go.sum b/internal/buildengine/languageplugin/testdata/go/plugintest/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/internal/buildengine/languageplugin/testdata/go/plugintest/go.sum +++ b/internal/buildengine/languageplugin/testdata/go/plugintest/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/internal/buildengine/testdata/another/go.mod b/internal/buildengine/testdata/another/go.mod index 2a995e671d..cfa2842f52 100644 --- a/internal/buildengine/testdata/another/go.mod +++ b/internal/buildengine/testdata/another/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/internal/buildengine/testdata/another/go.sum b/internal/buildengine/testdata/another/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/internal/buildengine/testdata/another/go.sum +++ b/internal/buildengine/testdata/another/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/internal/buildengine/testdata/other/go.mod b/internal/buildengine/testdata/other/go.mod index d5f0150c12..4418b9c19d 100644 --- a/internal/buildengine/testdata/other/go.mod +++ b/internal/buildengine/testdata/other/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/internal/buildengine/testdata/other/go.sum b/internal/buildengine/testdata/other/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/internal/buildengine/testdata/other/go.sum +++ b/internal/buildengine/testdata/other/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/internal/modulecontext/module_context.go b/internal/modulecontext/module_context.go index 93350a7382..4d4e6a22a0 100644 --- a/internal/modulecontext/module_context.go +++ b/internal/modulecontext/module_context.go @@ -165,7 +165,7 @@ func (m ModuleContext) GetDatabase(name string, dbType DBType) (string, error) { return "", fmt.Errorf("database %s does not match expected type of %s", name, dbType) } if m.isTesting && !db.isTestDB { - return "", fmt.Errorf("accessing non-test database %q while testing: try adding ftltest.WithDatabase(db) as an option with ftltest.Context(...)", name) + return "", fmt.Errorf("accessing non-test database %q while testing: try adding ftltest.WithDatabase[MyConfig]() as an option with ftltest.Context(...)", name) } return db.DSN, nil } diff --git a/internal/projectconfig/testdata/go/echo/go.mod b/internal/projectconfig/testdata/go/echo/go.mod index 9017349724..d3407d6c1b 100644 --- a/internal/projectconfig/testdata/go/echo/go.mod +++ b/internal/projectconfig/testdata/go/echo/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/internal/projectconfig/testdata/go/echo/go.sum b/internal/projectconfig/testdata/go/echo/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/internal/projectconfig/testdata/go/echo/go.sum +++ b/internal/projectconfig/testdata/go/echo/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/internal/projectconfig/testdata/go/findconfig/go.mod b/internal/projectconfig/testdata/go/findconfig/go.mod index e962463088..9eca60700f 100644 --- a/internal/projectconfig/testdata/go/findconfig/go.mod +++ b/internal/projectconfig/testdata/go/findconfig/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/internal/projectconfig/testdata/go/findconfig/go.sum b/internal/projectconfig/testdata/go/findconfig/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/internal/projectconfig/testdata/go/findconfig/go.sum +++ b/internal/projectconfig/testdata/go/findconfig/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/internal/projectconfig/testdata/go/validateconfig/go.mod b/internal/projectconfig/testdata/go/validateconfig/go.mod index 9bc5836ed2..900ad13a5a 100644 --- a/internal/projectconfig/testdata/go/validateconfig/go.mod +++ b/internal/projectconfig/testdata/go/validateconfig/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/internal/projectconfig/testdata/go/validateconfig/go.sum b/internal/projectconfig/testdata/go/validateconfig/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/internal/projectconfig/testdata/go/validateconfig/go.sum +++ b/internal/projectconfig/testdata/go/validateconfig/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/internal/schema/verb.go b/internal/schema/verb.go index 3fb5f0425b..a9d6c9113f 100644 --- a/internal/schema/verb.go +++ b/internal/schema/verb.go @@ -126,6 +126,15 @@ func (v *Verb) AddSecret(secret *Ref) { v.Metadata = append(v.Metadata, &MetadataSecrets{Secrets: []*Ref{secret}}) } +// AddDatabase adds a DB reference to the Verb. +func (v *Verb) AddDatabase(db *Ref) { + if c, ok := slices.FindVariant[*MetadataDatabases](v.Metadata); ok { + c.Calls = append(c.Calls, db) + return + } + v.Metadata = append(v.Metadata, &MetadataDatabases{Calls: []*Ref{db}}) +} + func (v *Verb) GetMetadataIngress() optional.Option[*MetadataIngress] { if m, ok := slices.FindVariant[*MetadataIngress](v.Metadata); ok { return optional.Some(m) diff --git a/internal/watch/testdata/another/go.mod b/internal/watch/testdata/another/go.mod index 2a995e671d..cfa2842f52 100644 --- a/internal/watch/testdata/another/go.mod +++ b/internal/watch/testdata/another/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/internal/watch/testdata/another/go.sum b/internal/watch/testdata/another/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/internal/watch/testdata/another/go.sum +++ b/internal/watch/testdata/another/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/internal/watch/testdata/other/go.mod b/internal/watch/testdata/other/go.mod index d5f0150c12..4418b9c19d 100644 --- a/internal/watch/testdata/other/go.mod +++ b/internal/watch/testdata/other/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/internal/watch/testdata/other/go.sum b/internal/watch/testdata/other/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/internal/watch/testdata/other/go.sum +++ b/internal/watch/testdata/other/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/jvm-runtime/testdata/go/gomodule/go.mod b/jvm-runtime/testdata/go/gomodule/go.mod index 49a624161b..fbdc05eea5 100644 --- a/jvm-runtime/testdata/go/gomodule/go.mod +++ b/jvm-runtime/testdata/go/gomodule/go.mod @@ -14,7 +14,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/jvm-runtime/testdata/go/gomodule/go.sum b/jvm-runtime/testdata/go/gomodule/go.sum index 0c7555c6e8..a35014485f 100644 --- a/jvm-runtime/testdata/go/gomodule/go.sum +++ b/jvm-runtime/testdata/go/gomodule/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert v1.0.0 h1:3XmGh/PSuLzDbK3W2gUbRXwgW5lqPkuqvRgeQ30FI5o= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= diff --git a/smoketest/origin/go.mod b/smoketest/origin/go.mod index 11ab8afdde..5e6b4d9f0d 100644 --- a/smoketest/origin/go.mod +++ b/smoketest/origin/go.mod @@ -9,7 +9,6 @@ require ( connectrpc.com/connect v1.16.2 // indirect connectrpc.com/grpcreflect v1.2.0 // indirect connectrpc.com/otelconnect v0.7.1 // indirect - github.com/XSAM/otelsql v0.35.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alecthomas/concurrency v0.0.2 // indirect github.com/alecthomas/participle/v2 v2.1.1 // indirect diff --git a/smoketest/origin/go.sum b/smoketest/origin/go.sum index 91fa4d7a80..3f6403960b 100644 --- a/smoketest/origin/go.sum +++ b/smoketest/origin/go.sum @@ -8,8 +8,6 @@ connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= github.com/TBD54566975/scaffolder v1.2.0 h1:7NqCC+iTDgk2awkOgk2Pj95Nz3GonhdIhwHQgQxug9k= github.com/TBD54566975/scaffolder v1.2.0/go.mod h1:oHLiKFPkkSMHP4ALVZ91T2V/xyx4MvPpRSpQttJxY7g= -github.com/XSAM/otelsql v0.35.0 h1:nMdbU/XLmBIB6qZF61uDqy46E0LVA4ZgF/FCNw8Had4= -github.com/XSAM/otelsql v0.35.0/go.mod h1:wO028mnLzmBpstK8XPsoeRLl/kgt417yjAwOGDIptTc= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/smoketest/relay/relay.go b/smoketest/relay/relay.go index 30011acad9..ad781bbc3d 100644 --- a/smoketest/relay/relay.go +++ b/smoketest/relay/relay.go @@ -13,8 +13,6 @@ import ( var logFile = ftl.Config[string]("log_file") -// var db = ftl.PostgresDatabase("exemplardb") - // PubSub var _ = ftl.Subscription(origin.AgentBroadcast, "agentConsumer")