mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: backfill legacy Bedrock AI provider rows and stale model config strings (#26155)
Fixes CODAGT-548
Adds two idempotent startup backfills run after `newAPI():
- `BackfillBedrockProviderType`: promotes `ai_providers` rows from
`type=anthropic` with Bedrock settings to `type=bedrock`.
- `BackfillChatModelConfigProviderStrings`: fixes stale
`chat_model_configs.provider = "anthropic"` strings on rows whose linked
provider was just promoted.
- `UpdateAIProvider` query now also writes the `type` column, so the
fix persists on any subsequent PATCH.
> 🤖 Generated by Claude with oversight from a human.
This commit is contained in:
@@ -1701,6 +1701,13 @@ func (q *querier) AutoArchiveInactiveChats(ctx context.Context, arg database.Aut
|
||||
return q.db.AutoArchiveInactiveChats(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) BackfillChatModelConfigProvider(ctx context.Context, arg database.BackfillChatModelConfigProviderParams) (sql.Result, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.BackfillChatModelConfigProvider(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) BackoffChatDiffStatus(ctx context.Context, arg database.BackoffChatDiffStatusParams) error {
|
||||
// This is a system-level operation used by the gitsync
|
||||
// background worker to reschedule failed refreshes. Same
|
||||
|
||||
@@ -6570,6 +6570,7 @@ func (s *MethodTestSuite) TestAIBridge() {
|
||||
provider := testutil.Fake(s.T(), faker, database.AIProvider{})
|
||||
arg := database.UpdateAIProviderParams{
|
||||
ID: provider.ID,
|
||||
Type: provider.Type,
|
||||
Enabled: true,
|
||||
BaseUrl: "https://api.example.com/",
|
||||
}
|
||||
@@ -6581,6 +6582,14 @@ func (s *MethodTestSuite) TestAIBridge() {
|
||||
dbm.EXPECT().DeleteAIProviderByID(gomock.Any(), provider.ID).Return(nil).AnyTimes()
|
||||
check.Args(provider.ID).Asserts(rbac.ResourceAIProvider, policy.ActionDelete).Returns()
|
||||
}))
|
||||
s.Run("BackfillChatModelConfigProvider", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
|
||||
arg := database.BackfillChatModelConfigProviderParams{
|
||||
OldProvider: "anthropic",
|
||||
NewProvider: "bedrock",
|
||||
}
|
||||
dbm.EXPECT().BackfillChatModelConfigProvider(gomock.Any(), arg).Return(nil, nil).AnyTimes()
|
||||
check.Args(arg).Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate)
|
||||
}))
|
||||
s.Run("UpdateEncryptedAIProviderSettings", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
|
||||
provider := testutil.Fake(s.T(), faker, database.AIProvider{})
|
||||
arg := database.UpdateEncryptedAIProviderSettingsParams{
|
||||
|
||||
+9
@@ -5,6 +5,7 @@ package dbmetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"time"
|
||||
@@ -185,6 +186,14 @@ func (m queryMetricsStore) AutoArchiveInactiveChats(ctx context.Context, arg dat
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) BackfillChatModelConfigProvider(ctx context.Context, arg database.BackfillChatModelConfigProviderParams) (sql.Result, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.BackfillChatModelConfigProvider(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("BackfillChatModelConfigProvider").Observe(time.Since(start).Seconds())
|
||||
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "BackfillChatModelConfigProvider").Inc()
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) BackoffChatDiffStatus(ctx context.Context, arg database.BackoffChatDiffStatusParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.BackoffChatDiffStatus(ctx, arg)
|
||||
|
||||
Generated
+16
@@ -11,6 +11,7 @@ package dbmock
|
||||
|
||||
import (
|
||||
context "context"
|
||||
sql "database/sql"
|
||||
json "encoding/json"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
@@ -193,6 +194,21 @@ func (mr *MockStoreMockRecorder) AutoArchiveInactiveChats(ctx, arg any) *gomock.
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AutoArchiveInactiveChats", reflect.TypeOf((*MockStore)(nil).AutoArchiveInactiveChats), ctx, arg)
|
||||
}
|
||||
|
||||
// BackfillChatModelConfigProvider mocks base method.
|
||||
func (m *MockStore) BackfillChatModelConfigProvider(ctx context.Context, arg database.BackfillChatModelConfigProviderParams) (sql.Result, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BackfillChatModelConfigProvider", ctx, arg)
|
||||
ret0, _ := ret[0].(sql.Result)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BackfillChatModelConfigProvider indicates an expected call of BackfillChatModelConfigProvider.
|
||||
func (mr *MockStoreMockRecorder) BackfillChatModelConfigProvider(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BackfillChatModelConfigProvider", reflect.TypeOf((*MockStore)(nil).BackfillChatModelConfigProvider), ctx, arg)
|
||||
}
|
||||
|
||||
// BackoffChatDiffStatus mocks base method.
|
||||
func (m *MockStore) BackoffChatDiffStatus(ctx context.Context, arg database.BackoffChatDiffStatusParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
+6
@@ -6,6 +6,7 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
@@ -70,6 +71,11 @@ type sqlcQuerier interface {
|
||||
// created_at ASC flows through to dbpurge's digest truncation; see
|
||||
// buildDigestData in dbpurge.go for the tradeoff rationale.
|
||||
AutoArchiveInactiveChats(ctx context.Context, arg AutoArchiveInactiveChatsParams) ([]AutoArchiveInactiveChatsRow, error)
|
||||
// old_provider is matched as text; new_provider is also cast to ai_provider_type
|
||||
// for the EXISTS check against ai_providers.type.
|
||||
// ai_provider_id IS NOT NULL is defensive; the check constraint already
|
||||
// enforces that non-deleted rows always have a provider ID.
|
||||
BackfillChatModelConfigProvider(ctx context.Context, arg BackfillChatModelConfigProviderParams) (sql.Result, error)
|
||||
BackoffChatDiffStatus(ctx context.Context, arg BackoffChatDiffStatusParams) error
|
||||
BatchUpdateWorkspaceAgentMetadata(ctx context.Context, arg BatchUpdateWorkspaceAgentMetadataParams) error
|
||||
BatchUpdateWorkspaceLastUsedAt(ctx context.Context, arg BatchUpdateWorkspaceLastUsedAtParams) error
|
||||
|
||||
Generated
+40
-6
@@ -738,19 +738,21 @@ const updateAIProvider = `-- name: UpdateAIProvider :one
|
||||
UPDATE
|
||||
ai_providers
|
||||
SET
|
||||
display_name = $1::text,
|
||||
enabled = $2::boolean,
|
||||
base_url = $3::text,
|
||||
settings = $4::text,
|
||||
settings_key_id = $5::text,
|
||||
type = $1::ai_provider_type,
|
||||
display_name = $2::text,
|
||||
enabled = $3::boolean,
|
||||
base_url = $4::text,
|
||||
settings = $5::text,
|
||||
settings_key_id = $6::text,
|
||||
updated_at = NOW()
|
||||
WHERE
|
||||
id = $6::uuid AND deleted = FALSE
|
||||
id = $7::uuid AND deleted = FALSE
|
||||
RETURNING
|
||||
id, type, name, display_name, enabled, deleted, base_url, settings, settings_key_id, created_at, updated_at
|
||||
`
|
||||
|
||||
type UpdateAIProviderParams struct {
|
||||
Type AIProviderType `db:"type" json:"type"`
|
||||
DisplayName sql.NullString `db:"display_name" json:"display_name"`
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
BaseUrl string `db:"base_url" json:"base_url"`
|
||||
@@ -761,6 +763,7 @@ type UpdateAIProviderParams struct {
|
||||
|
||||
func (q *sqlQuerier) UpdateAIProvider(ctx context.Context, arg UpdateAIProviderParams) (AIProvider, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateAIProvider,
|
||||
arg.Type,
|
||||
arg.DisplayName,
|
||||
arg.Enabled,
|
||||
arg.BaseUrl,
|
||||
@@ -5551,6 +5554,37 @@ func (q *sqlQuerier) GetPRInsightsTimeSeries(ctx context.Context, arg GetPRInsig
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const backfillChatModelConfigProvider = `-- name: BackfillChatModelConfigProvider :execresult
|
||||
UPDATE
|
||||
chat_model_configs
|
||||
SET
|
||||
provider = $1::text,
|
||||
updated_at = NOW()
|
||||
WHERE
|
||||
provider = $2::text
|
||||
AND deleted = FALSE
|
||||
AND ai_provider_id IS NOT NULL
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM ai_providers
|
||||
WHERE id = chat_model_configs.ai_provider_id
|
||||
AND type = $1::ai_provider_type
|
||||
AND deleted = FALSE
|
||||
)
|
||||
`
|
||||
|
||||
type BackfillChatModelConfigProviderParams struct {
|
||||
NewProvider string `db:"new_provider" json:"new_provider"`
|
||||
OldProvider string `db:"old_provider" json:"old_provider"`
|
||||
}
|
||||
|
||||
// old_provider is matched as text; new_provider is also cast to ai_provider_type
|
||||
// for the EXISTS check against ai_providers.type.
|
||||
// ai_provider_id IS NOT NULL is defensive; the check constraint already
|
||||
// enforces that non-deleted rows always have a provider ID.
|
||||
func (q *sqlQuerier) BackfillChatModelConfigProvider(ctx context.Context, arg BackfillChatModelConfigProviderParams) (sql.Result, error) {
|
||||
return q.db.ExecContext(ctx, backfillChatModelConfigProvider, arg.NewProvider, arg.OldProvider)
|
||||
}
|
||||
|
||||
const deleteChatModelConfigByID = `-- name: DeleteChatModelConfigByID :exec
|
||||
UPDATE
|
||||
chat_model_configs
|
||||
|
||||
@@ -66,6 +66,7 @@ RETURNING
|
||||
UPDATE
|
||||
ai_providers
|
||||
SET
|
||||
type = @type::ai_provider_type,
|
||||
display_name = sqlc.narg('display_name')::text,
|
||||
enabled = @enabled::boolean,
|
||||
base_url = @base_url::text,
|
||||
|
||||
@@ -144,6 +144,27 @@ WHERE
|
||||
provider = @provider::text
|
||||
AND deleted = FALSE;
|
||||
|
||||
-- name: BackfillChatModelConfigProvider :execresult
|
||||
-- old_provider is matched as text; new_provider is also cast to ai_provider_type
|
||||
-- for the EXISTS check against ai_providers.type.
|
||||
-- ai_provider_id IS NOT NULL is defensive; the check constraint already
|
||||
-- enforces that non-deleted rows always have a provider ID.
|
||||
UPDATE
|
||||
chat_model_configs
|
||||
SET
|
||||
provider = @new_provider::text,
|
||||
updated_at = NOW()
|
||||
WHERE
|
||||
provider = @old_provider::text
|
||||
AND deleted = FALSE
|
||||
AND ai_provider_id IS NOT NULL
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM ai_providers
|
||||
WHERE id = chat_model_configs.ai_provider_id
|
||||
AND type = @new_provider::ai_provider_type
|
||||
AND deleted = FALSE
|
||||
);
|
||||
|
||||
-- name: DeleteChatModelConfigsByAIProviderID :exec
|
||||
UPDATE
|
||||
chat_model_configs
|
||||
|
||||
Reference in New Issue
Block a user