From dd41626ee72288f4f06e3991cc146f68d428456d Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Sun, 15 Sep 2024 09:12:17 +1000 Subject: [PATCH] fix: address PR comments --- backend/controller/controller.go | 10 +- .../internal/sql/deployment_queries.sql.go | 89 ++++++++++ backend/controller/dal/internal/sql/models.go | 14 -- .../controller/dal/internal/sql/queries.sql | 40 ----- .../dal/internal/sql/queries.sql.go | 154 ------------------ backend/controller/timeline/dal/dal.go | 5 +- .../dal/internal/sql/deployment_queries.sql | 39 +++++ .../internal/sql/deployment_queries.sql.go | 89 ++++++++++ .../timeline/dal/internal/sql/models.go | 7 +- .../timeline/dal/internal/sql/querier.go | 2 + .../timeline/dal/internal/sql/queries.sql.go | 11 +- backend/controller/timeline/timeline.go | 21 ++- sqlc.yaml | 28 +--- 13 files changed, 251 insertions(+), 258 deletions(-) create mode 100644 backend/controller/dal/internal/sql/deployment_queries.sql.go create mode 100644 backend/controller/timeline/dal/internal/sql/deployment_queries.sql create mode 100644 backend/controller/timeline/dal/internal/sql/deployment_queries.sql.go diff --git a/backend/controller/controller.go b/backend/controller/controller.go index 2cf4c337cc..08cc19edb6 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -235,7 +235,7 @@ func New(ctx context.Context, conn *sql.DB, config Config, devel bool) (*Service config.ControllerTimeout = time.Second * 5 } - encryption, err := encryption.New(ctx, conn, ftlencryption.NewBuilder().WithKMSURI(optional.Ptr(config.KMSURI))) + encryption, err := encryption.New(ctx, conn, api.NewBuilder().WithKMSURI(optional.Ptr(config.KMSURI))) if err != nil { return nil, fmt.Errorf("failed to create encryption dal: %w", err) } @@ -1073,12 +1073,13 @@ func (s *Service) callWithRequest( response, err := client.verb.Call(ctx, req) var resp *connect.Response[ftlv1.CallResponse] - var maybeResponse optional.Option[*ftlv1.CallResponse] + var callResponse either.Either[*ftlv1.CallResponse, error] if err == nil { resp = connect.NewResponse(response.Msg) - maybeResponse = optional.Some(resp.Msg) + callResponse = either.LeftOf[error](resp.Msg) observability.Calls.Request(ctx, req.Msg.Verb, start, optional.None[string]()) } else { + callResponse = either.RightOf[*ftlv1.CallResponse](err) observability.Calls.Request(ctx, req.Msg.Verb, start, optional.Some("verb call failed")) } s.timeline.RecordCall(ctx, &timeline.Call{ @@ -1088,9 +1089,8 @@ func (s *Service) callWithRequest( StartTime: start, DestVerb: verbRef, Callers: callers, - CallError: optional.Nil(err), Request: req.Msg, - Response: maybeResponse, + Response: callResponse, }) return resp, err } diff --git a/backend/controller/dal/internal/sql/deployment_queries.sql.go b/backend/controller/dal/internal/sql/deployment_queries.sql.go new file mode 100644 index 0000000000..34a7b55897 --- /dev/null +++ b/backend/controller/dal/internal/sql/deployment_queries.sql.go @@ -0,0 +1,89 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: deployment_queries.sql + +package sql + +import ( + "context" + + "github.com/TBD54566975/ftl/backend/controller/encryption/api" + "github.com/TBD54566975/ftl/internal/model" +) + +const insertTimelineDeploymentCreatedEvent = `-- name: InsertTimelineDeploymentCreatedEvent :exec +INSERT INTO timeline ( + deployment_id, + type, + custom_key_1, + custom_key_2, + payload +) +VALUES ( + ( + SELECT id + FROM deployments + WHERE deployments.key = $1::deployment_key + ), + 'deployment_created', + $2::TEXT, + $3::TEXT, + $4 +) +` + +type InsertTimelineDeploymentCreatedEventParams struct { + DeploymentKey model.DeploymentKey + Language string + ModuleName string + Payload api.EncryptedTimelineColumn +} + +func (q *Queries) InsertTimelineDeploymentCreatedEvent(ctx context.Context, arg InsertTimelineDeploymentCreatedEventParams) error { + _, err := q.db.ExecContext(ctx, insertTimelineDeploymentCreatedEvent, + arg.DeploymentKey, + arg.Language, + arg.ModuleName, + arg.Payload, + ) + return err +} + +const insertTimelineDeploymentUpdatedEvent = `-- name: InsertTimelineDeploymentUpdatedEvent :exec +INSERT INTO timeline ( + deployment_id, + type, + custom_key_1, + custom_key_2, + payload +) +VALUES ( + ( + SELECT id + FROM deployments + WHERE deployments.key = $1::deployment_key + ), + 'deployment_updated', + $2::TEXT, + $3::TEXT, + $4 +) +` + +type InsertTimelineDeploymentUpdatedEventParams struct { + DeploymentKey model.DeploymentKey + Language string + ModuleName string + Payload api.EncryptedTimelineColumn +} + +func (q *Queries) InsertTimelineDeploymentUpdatedEvent(ctx context.Context, arg InsertTimelineDeploymentUpdatedEventParams) error { + _, err := q.db.ExecContext(ctx, insertTimelineDeploymentUpdatedEvent, + arg.DeploymentKey, + arg.Language, + arg.ModuleName, + arg.Payload, + ) + return err +} diff --git a/backend/controller/dal/internal/sql/models.go b/backend/controller/dal/internal/sql/models.go index 7cf957c6da..b3141f073a 100644 --- a/backend/controller/dal/internal/sql/models.go +++ b/backend/controller/dal/internal/sql/models.go @@ -355,20 +355,6 @@ type Module struct { Name string } -type Timeline struct { - ID int64 - TimeStamp time.Time - DeploymentID int64 - RequestID optional.Option[int64] - Type EventType - CustomKey1 optional.Option[string] - CustomKey2 optional.Option[string] - CustomKey3 optional.Option[string] - CustomKey4 optional.Option[string] - Payload api.EncryptedTimelineColumn - ParentRequestID optional.Option[string] -} - type Topic struct { ID int64 Key model.TopicKey diff --git a/backend/controller/dal/internal/sql/queries.sql b/backend/controller/dal/internal/sql/queries.sql index 38c8f34e73..3adc68db2d 100644 --- a/backend/controller/dal/internal/sql/queries.sql +++ b/backend/controller/dal/internal/sql/queries.sql @@ -205,46 +205,6 @@ FROM runners r WHERE state = 'assigned' AND d.key = sqlc.arg('key')::deployment_key; --- name: InsertTimelineDeploymentCreatedEvent :exec -INSERT INTO timeline ( - deployment_id, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - ( - SELECT id - FROM deployments - WHERE deployments.key = sqlc.arg('deployment_key')::deployment_key - ), - 'deployment_created', - sqlc.arg('language')::TEXT, - sqlc.arg('module_name')::TEXT, - sqlc.arg('payload') -); - --- name: InsertTimelineDeploymentUpdatedEvent :exec -INSERT INTO timeline ( - deployment_id, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - ( - SELECT id - FROM deployments - WHERE deployments.key = sqlc.arg('deployment_key')::deployment_key - ), - 'deployment_updated', - sqlc.arg('language')::TEXT, - sqlc.arg('module_name')::TEXT, - sqlc.arg('payload') -); - -- name: CreateRequest :exec INSERT INTO requests (origin, "key", source_addr) VALUES ($1, $2, $3); diff --git a/backend/controller/dal/internal/sql/queries.sql.go b/backend/controller/dal/internal/sql/queries.sql.go index 9436ced395..548ee03c71 100644 --- a/backend/controller/dal/internal/sql/queries.sql.go +++ b/backend/controller/dal/internal/sql/queries.sql.go @@ -1757,160 +1757,6 @@ func (q *Queries) InsertSubscriber(ctx context.Context, arg InsertSubscriberPara return err } -const insertTimelineDeploymentCreatedEvent = `-- name: InsertTimelineDeploymentCreatedEvent :exec -INSERT INTO timeline ( - deployment_id, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - ( - SELECT id - FROM deployments - WHERE deployments.key = $1::deployment_key - ), - 'deployment_created', - $2::TEXT, - $3::TEXT, - $4 -) -` - -type InsertTimelineDeploymentCreatedEventParams struct { - DeploymentKey model.DeploymentKey - Language string - ModuleName string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineDeploymentCreatedEvent(ctx context.Context, arg InsertTimelineDeploymentCreatedEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineDeploymentCreatedEvent, - arg.DeploymentKey, - arg.Language, - arg.ModuleName, - arg.Payload, - ) - return err -} - -const insertTimelineDeploymentUpdatedEvent = `-- name: InsertTimelineDeploymentUpdatedEvent :exec -INSERT INTO timeline ( - deployment_id, - type, - custom_key_1, - custom_key_2, - payload -) -VALUES ( - ( - SELECT id - FROM deployments - WHERE deployments.key = $1::deployment_key - ), - 'deployment_updated', - $2::TEXT, - $3::TEXT, - $4 -) -` - -type InsertTimelineDeploymentUpdatedEventParams struct { - DeploymentKey model.DeploymentKey - Language string - ModuleName string - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineDeploymentUpdatedEvent(ctx context.Context, arg InsertTimelineDeploymentUpdatedEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineDeploymentUpdatedEvent, - arg.DeploymentKey, - arg.Language, - arg.ModuleName, - arg.Payload, - ) - return err -} - -const insertTimelineEvent = `-- name: InsertTimelineEvent :exec -INSERT INTO timeline (deployment_id, request_id, parent_request_id, type, - custom_key_1, custom_key_2, custom_key_3, custom_key_4, - payload) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) -RETURNING id -` - -type InsertTimelineEventParams struct { - DeploymentID int64 - RequestID optional.Option[int64] - ParentRequestID optional.Option[string] - Type EventType - CustomKey1 optional.Option[string] - CustomKey2 optional.Option[string] - CustomKey3 optional.Option[string] - CustomKey4 optional.Option[string] - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineEvent(ctx context.Context, arg InsertTimelineEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineEvent, - arg.DeploymentID, - arg.RequestID, - arg.ParentRequestID, - arg.Type, - arg.CustomKey1, - arg.CustomKey2, - arg.CustomKey3, - arg.CustomKey4, - arg.Payload, - ) - return err -} - -const insertTimelineLogEvent = `-- name: InsertTimelineLogEvent :exec -INSERT INTO timeline ( - deployment_id, - request_id, - time_stamp, - custom_key_1, - type, - payload -) -VALUES ( - (SELECT id FROM deployments d WHERE d.key = $1::deployment_key LIMIT 1), - ( - CASE - WHEN $2::TEXT IS NULL THEN NULL - ELSE (SELECT id FROM requests ir WHERE ir.key = $2::TEXT LIMIT 1) - END - ), - $3::TIMESTAMPTZ, - $4::INT, - 'log', - $5 -) -` - -type InsertTimelineLogEventParams struct { - DeploymentKey model.DeploymentKey - RequestKey optional.Option[string] - TimeStamp time.Time - Level int32 - Payload api.EncryptedTimelineColumn -} - -func (q *Queries) InsertTimelineLogEvent(ctx context.Context, arg InsertTimelineLogEventParams) error { - _, err := q.db.ExecContext(ctx, insertTimelineLogEvent, - arg.DeploymentKey, - arg.RequestKey, - arg.TimeStamp, - arg.Level, - arg.Payload, - ) - return err -} - const isCronJobPending = `-- name: IsCronJobPending :one SELECT EXISTS ( SELECT 1 diff --git a/backend/controller/timeline/dal/dal.go b/backend/controller/timeline/dal/dal.go index 39d332c77b..816df58f8d 100644 --- a/backend/controller/timeline/dal/dal.go +++ b/backend/controller/timeline/dal/dal.go @@ -8,15 +8,16 @@ import ( "strconv" "time" + "github.com/alecthomas/types/optional" + "github.com/TBD54566975/ftl/backend/controller/encryption" + ftlencryption "github.com/TBD54566975/ftl/backend/controller/encryption/api" "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" "github.com/TBD54566975/ftl/backend/controller/timeline/dal/internal/sql" "github.com/TBD54566975/ftl/backend/libdal" "github.com/TBD54566975/ftl/backend/schema" - ftlencryption "github.com/TBD54566975/ftl/internal/encryption" "github.com/TBD54566975/ftl/internal/log" "github.com/TBD54566975/ftl/internal/model" - "github.com/alecthomas/types/optional" ) type DAL struct { diff --git a/backend/controller/timeline/dal/internal/sql/deployment_queries.sql b/backend/controller/timeline/dal/internal/sql/deployment_queries.sql new file mode 100644 index 0000000000..268a22047e --- /dev/null +++ b/backend/controller/timeline/dal/internal/sql/deployment_queries.sql @@ -0,0 +1,39 @@ +-- name: InsertTimelineDeploymentCreatedEvent :exec +INSERT INTO timeline ( + deployment_id, + type, + custom_key_1, + custom_key_2, + payload +) +VALUES ( + ( + SELECT id + FROM deployments + WHERE deployments.key = sqlc.arg('deployment_key')::deployment_key + ), + 'deployment_created', + sqlc.arg('language')::TEXT, + sqlc.arg('module_name')::TEXT, + sqlc.arg('payload') +); + +-- name: InsertTimelineDeploymentUpdatedEvent :exec +INSERT INTO timeline ( + deployment_id, + type, + custom_key_1, + custom_key_2, + payload +) +VALUES ( + ( + SELECT id + FROM deployments + WHERE deployments.key = sqlc.arg('deployment_key')::deployment_key + ), + 'deployment_updated', + sqlc.arg('language')::TEXT, + sqlc.arg('module_name')::TEXT, + sqlc.arg('payload') +); diff --git a/backend/controller/timeline/dal/internal/sql/deployment_queries.sql.go b/backend/controller/timeline/dal/internal/sql/deployment_queries.sql.go new file mode 100644 index 0000000000..34a7b55897 --- /dev/null +++ b/backend/controller/timeline/dal/internal/sql/deployment_queries.sql.go @@ -0,0 +1,89 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: deployment_queries.sql + +package sql + +import ( + "context" + + "github.com/TBD54566975/ftl/backend/controller/encryption/api" + "github.com/TBD54566975/ftl/internal/model" +) + +const insertTimelineDeploymentCreatedEvent = `-- name: InsertTimelineDeploymentCreatedEvent :exec +INSERT INTO timeline ( + deployment_id, + type, + custom_key_1, + custom_key_2, + payload +) +VALUES ( + ( + SELECT id + FROM deployments + WHERE deployments.key = $1::deployment_key + ), + 'deployment_created', + $2::TEXT, + $3::TEXT, + $4 +) +` + +type InsertTimelineDeploymentCreatedEventParams struct { + DeploymentKey model.DeploymentKey + Language string + ModuleName string + Payload api.EncryptedTimelineColumn +} + +func (q *Queries) InsertTimelineDeploymentCreatedEvent(ctx context.Context, arg InsertTimelineDeploymentCreatedEventParams) error { + _, err := q.db.ExecContext(ctx, insertTimelineDeploymentCreatedEvent, + arg.DeploymentKey, + arg.Language, + arg.ModuleName, + arg.Payload, + ) + return err +} + +const insertTimelineDeploymentUpdatedEvent = `-- name: InsertTimelineDeploymentUpdatedEvent :exec +INSERT INTO timeline ( + deployment_id, + type, + custom_key_1, + custom_key_2, + payload +) +VALUES ( + ( + SELECT id + FROM deployments + WHERE deployments.key = $1::deployment_key + ), + 'deployment_updated', + $2::TEXT, + $3::TEXT, + $4 +) +` + +type InsertTimelineDeploymentUpdatedEventParams struct { + DeploymentKey model.DeploymentKey + Language string + ModuleName string + Payload api.EncryptedTimelineColumn +} + +func (q *Queries) InsertTimelineDeploymentUpdatedEvent(ctx context.Context, arg InsertTimelineDeploymentUpdatedEventParams) error { + _, err := q.db.ExecContext(ctx, insertTimelineDeploymentUpdatedEvent, + arg.DeploymentKey, + arg.Language, + arg.ModuleName, + arg.Payload, + ) + return err +} diff --git a/backend/controller/timeline/dal/internal/sql/models.go b/backend/controller/timeline/dal/internal/sql/models.go index 11ad26d656..5571794e2f 100644 --- a/backend/controller/timeline/dal/internal/sql/models.go +++ b/backend/controller/timeline/dal/internal/sql/models.go @@ -5,12 +5,11 @@ package sql import ( - "database/sql" "database/sql/driver" "fmt" "time" - "github.com/TBD54566975/ftl/internal/encryption" + "github.com/TBD54566975/ftl/backend/controller/encryption/api" "github.com/alecthomas/types/optional" ) @@ -62,12 +61,12 @@ type Timeline struct { ID int64 TimeStamp time.Time DeploymentID int64 - RequestID sql.NullInt64 + RequestID optional.Option[int64] Type EventType CustomKey1 optional.Option[string] CustomKey2 optional.Option[string] CustomKey3 optional.Option[string] CustomKey4 optional.Option[string] - Payload encryption.EncryptedTimelineColumn + Payload api.EncryptedTimelineColumn ParentRequestID optional.Option[string] } diff --git a/backend/controller/timeline/dal/internal/sql/querier.go b/backend/controller/timeline/dal/internal/sql/querier.go index afa6fa3142..9d0bf33988 100644 --- a/backend/controller/timeline/dal/internal/sql/querier.go +++ b/backend/controller/timeline/dal/internal/sql/querier.go @@ -15,6 +15,8 @@ type Querier interface { // This is a dummy query to ensure that the Timeline model is generated. DummyQueryTimeline(ctx context.Context, id int64) (Timeline, error) InsertTimelineCallEvent(ctx context.Context, arg InsertTimelineCallEventParams) error + InsertTimelineDeploymentCreatedEvent(ctx context.Context, arg InsertTimelineDeploymentCreatedEventParams) error + InsertTimelineDeploymentUpdatedEvent(ctx context.Context, arg InsertTimelineDeploymentUpdatedEventParams) error InsertTimelineLogEvent(ctx context.Context, arg InsertTimelineLogEventParams) error } diff --git a/backend/controller/timeline/dal/internal/sql/queries.sql.go b/backend/controller/timeline/dal/internal/sql/queries.sql.go index 19914f65a5..c75ac34a78 100644 --- a/backend/controller/timeline/dal/internal/sql/queries.sql.go +++ b/backend/controller/timeline/dal/internal/sql/queries.sql.go @@ -9,8 +9,9 @@ import ( "context" "time" + "github.com/TBD54566975/ftl/backend/controller/encryption/api" "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes" - "github.com/TBD54566975/ftl/internal/encryption" + "github.com/TBD54566975/ftl/internal/model" "github.com/alecthomas/types/optional" ) @@ -90,7 +91,7 @@ VALUES ( ` type InsertTimelineCallEventParams struct { - DeploymentKey interface{} + DeploymentKey model.DeploymentKey RequestKey optional.Option[string] ParentRequestKey optional.Option[string] TimeStamp time.Time @@ -98,7 +99,7 @@ type InsertTimelineCallEventParams struct { SourceVerb optional.Option[string] DestModule string DestVerb string - Payload encryption.EncryptedTimelineColumn + Payload api.EncryptedTimelineColumn } func (q *Queries) InsertTimelineCallEvent(ctx context.Context, arg InsertTimelineCallEventParams) error { @@ -141,11 +142,11 @@ VALUES ( ` type InsertTimelineLogEventParams struct { - DeploymentKey interface{} + DeploymentKey model.DeploymentKey RequestKey optional.Option[string] TimeStamp time.Time Level int32 - Payload encryption.EncryptedTimelineColumn + Payload api.EncryptedTimelineColumn } func (q *Queries) InsertTimelineLogEvent(ctx context.Context, arg InsertTimelineLogEventParams) error { diff --git a/backend/controller/timeline/timeline.go b/backend/controller/timeline/timeline.go index 360399d2ca..8205453c34 100644 --- a/backend/controller/timeline/timeline.go +++ b/backend/controller/timeline/timeline.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/alecthomas/types/either" "github.com/alecthomas/types/optional" "github.com/TBD54566975/ftl/backend/controller/encryption" @@ -17,11 +18,11 @@ import ( ) type Service struct { - dal dal.DAL + dal *dal.DAL } func New(ctx context.Context, conn libdal.Connection, encryption *encryption.Service) *Service { - return &Service{dal: *dal.New(conn, encryption)} + return &Service{dal: dal.New(conn, encryption)} } type Log struct { @@ -54,8 +55,7 @@ type Call struct { DestVerb *schema.Ref Callers []*schema.Ref Request *ftlv1.CallRequest - Response optional.Option[*ftlv1.CallResponse] - CallError optional.Option[error] + Response either.Either[*ftlv1.CallResponse, error] } func (s *Service) RecordCall(ctx context.Context, call *Call) { @@ -69,14 +69,17 @@ func (s *Service) RecordCall(ctx context.Context, call *Call) { var stack optional.Option[string] var responseBody []byte - if callError, ok := call.CallError.Get(); ok { - errorStr = optional.Some(callError.Error()) - } else if response, ok := call.Response.Get(); ok { - responseBody = response.GetBody() - if callError := response.GetError(); callError != nil { + switch response := call.Response.(type) { + case either.Left[*ftlv1.CallResponse, error]: + resp := response.Get() + responseBody = resp.GetBody() + if callError := resp.GetError(); callError != nil { errorStr = optional.Some(callError.Message) stack = optional.Ptr(callError.Stack) } + case either.Right[*ftlv1.CallResponse, error]: + callError := response.Get() + errorStr = optional.Some(callError.Error()) } err := s.dal.InsertCallEvent(ctx, &dal.CallEvent{ diff --git a/sqlc.yaml b/sqlc.yaml index ef8601c182..dd555fa9fe 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -7,6 +7,8 @@ sql: - backend/controller/dal/internal/sql/async_queries.sql # FIXME: Until we fully decouple cron from the controller, we need to include the cron queries here - backend/controller/cronjobs/dal/internal/sql/queries.sql + # Some of the timeline entries happen within a controller transaction, so we need to include them here + - backend/controller/timeline/dal/internal/sql/deployment_queries.sql schema: "backend/controller/sql/schema" database: uri: postgres://localhost:15432/ftl?sslmode=disable&user=postgres&password=secret @@ -181,35 +183,11 @@ sql: - <<: *daldir queries: - backend/controller/timeline/dal/internal/sql/queries.sql + - backend/controller/timeline/dal/internal/sql/deployment_queries.sql gen: go: <<: *gengo out: "backend/controller/timeline/dal/internal/sql" - overrides: - - db_type: "encrypted_timeline" - go_type: "github.com/TBD54566975/ftl/internal/encryption.EncryptedTimelineColumn" - - db_type: "encrypted_timeline" - nullable: true - go_type: "github.com/TBD54566975/ftl/internal/encryption.OptionalEncryptedTimelineColumn" - - db_type: "timestamptz" - go_type: "time.Time" - - db_type: "timestamptz" - nullable: true - go_type: - type: "optional.Option[time.Time]" - - db_type: "pg_catalog.interval" - go_type: "github.com/TBD54566975/ftl/backend/controller/sql/sqltypes.Duration" - - db_type: "pg_catalog.interval" - nullable: true - go_type: - type: "optional.Option[sqltypes.Duration]" - - db_type: "text" - go_type: "string" - - db_type: "text" - nullable: true - go_type: - import: "github.com/alecthomas/types/optional" - type: "Option[string]" rules: - name: postgresql-query-too-costly message: "Query cost estimate is too high"