chore: rename 'last_used_at' column (#26749)

Renames the `last_used_at` column  to `last_heartbeat_at` in `ai_gateway_keys` table.  
`ai_gateway_keys` table has not been released yet.  
All references updated.
This commit is contained in:
Paweł Banaszewski
2026-06-26 18:45:37 +02:00
committed by GitHub
parent 73cd34cc5f
commit c15d483863
29 changed files with 136 additions and 132 deletions
+3 -3
View File
@@ -7033,14 +7033,14 @@ func (q *querier) UpdateAIBridgeInterceptionEnded(ctx context.Context, params da
return q.db.UpdateAIBridgeInterceptionEnded(ctx, params)
}
// Records liveness for a key used in active DRPC session between coderd and standalone AI Gateway.
func (q *querier) UpdateAIGatewayKeyLastUsedAt(ctx context.Context, id uuid.UUID) (int64, error) {
// Records heartbeat liveness for a key used in active DRPC session between coderd and standalone AI Gateway.
func (q *querier) UpdateAIGatewayKeyLastHeartbeatAt(ctx context.Context, id uuid.UUID) (int64, error) {
// Standalone AI Gateway has no Coder identity, so this runs under the
// system actor recording connection liveness on the AI Gateway key.
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceAIGatewayKey); err != nil {
return 0, err
}
return q.db.UpdateAIGatewayKeyLastUsedAt(ctx, id)
return q.db.UpdateAIGatewayKeyLastHeartbeatAt(ctx, id)
}
func (q *querier) UpdateAIProvider(ctx context.Context, arg database.UpdateAIProviderParams) (database.AIProvider, error) {
+2 -2
View File
@@ -6974,9 +6974,9 @@ func (s *MethodTestSuite) TestAIBridge() {
dbm.EXPECT().GetAIGatewayKeyByHashedSecret(gomock.Any(), hashedSecret).Return(key, nil).AnyTimes()
check.Args(hashedSecret).Asserts(rbac.ResourceAIGatewayKey, policy.ActionRead).Returns(key)
}))
s.Run("UpdateAIGatewayKeyLastUsedAt", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
s.Run("UpdateAIGatewayKeyLastHeartbeatAt", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
id := uuid.New()
dbm.EXPECT().UpdateAIGatewayKeyLastUsedAt(gomock.Any(), id).Return(int64(1), nil).AnyTimes()
dbm.EXPECT().UpdateAIGatewayKeyLastHeartbeatAt(gomock.Any(), id).Return(int64(1), nil).AnyTimes()
check.Args(id).Asserts(rbac.ResourceAIGatewayKey, policy.ActionUpdate).Returns(int64(1))
}))
}
+4 -4
View File
@@ -5050,11 +5050,11 @@ func (m queryMetricsStore) UpdateAIBridgeInterceptionEnded(ctx context.Context,
return r0, r1
}
func (m queryMetricsStore) UpdateAIGatewayKeyLastUsedAt(ctx context.Context, arg uuid.UUID) (int64, error) {
func (m queryMetricsStore) UpdateAIGatewayKeyLastHeartbeatAt(ctx context.Context, id uuid.UUID) (int64, error) {
start := time.Now()
r0, r1 := m.s.UpdateAIGatewayKeyLastUsedAt(ctx, arg)
m.queryLatencies.WithLabelValues("UpdateAIGatewayKeyLastUsedAt").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateAIGatewayKeyLastUsedAt").Inc()
r0, r1 := m.s.UpdateAIGatewayKeyLastHeartbeatAt(ctx, id)
m.queryLatencies.WithLabelValues("UpdateAIGatewayKeyLastHeartbeatAt").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "UpdateAIGatewayKeyLastHeartbeatAt").Inc()
return r0, r1
}
+6 -6
View File
@@ -9518,19 +9518,19 @@ func (mr *MockStoreMockRecorder) UpdateAIBridgeInterceptionEnded(ctx, arg any) *
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAIBridgeInterceptionEnded", reflect.TypeOf((*MockStore)(nil).UpdateAIBridgeInterceptionEnded), ctx, arg)
}
// UpdateAIGatewayKeyLastUsedAt mocks base method.
func (m *MockStore) UpdateAIGatewayKeyLastUsedAt(ctx context.Context, id uuid.UUID) (int64, error) {
// UpdateAIGatewayKeyLastHeartbeatAt mocks base method.
func (m *MockStore) UpdateAIGatewayKeyLastHeartbeatAt(ctx context.Context, id uuid.UUID) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateAIGatewayKeyLastUsedAt", ctx, id)
ret := m.ctrl.Call(m, "UpdateAIGatewayKeyLastHeartbeatAt", ctx, id)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateAIGatewayKeyLastUsedAt indicates an expected call of UpdateAIGatewayKeyLastUsedAt.
func (mr *MockStoreMockRecorder) UpdateAIGatewayKeyLastUsedAt(ctx, id any) *gomock.Call {
// UpdateAIGatewayKeyLastHeartbeatAt indicates an expected call of UpdateAIGatewayKeyLastHeartbeatAt.
func (mr *MockStoreMockRecorder) UpdateAIGatewayKeyLastHeartbeatAt(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAIGatewayKeyLastUsedAt", reflect.TypeOf((*MockStore)(nil).UpdateAIGatewayKeyLastUsedAt), ctx, id)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAIGatewayKeyLastHeartbeatAt", reflect.TypeOf((*MockStore)(nil).UpdateAIGatewayKeyLastHeartbeatAt), ctx, id)
}
// UpdateAIProvider mocks base method.
+1 -1
View File
@@ -1440,7 +1440,7 @@ CREATE TABLE ai_gateway_keys (
name text NOT NULL,
secret_prefix character varying(11) NOT NULL,
hashed_secret bytea NOT NULL,
last_used_at timestamp with time zone,
last_heartbeat_at timestamp with time zone,
CONSTRAINT ai_gateway_keys_hashed_secret_check CHECK ((length(hashed_secret) > 0)),
CONSTRAINT ai_gateway_keys_name_check CHECK (((length(name) <= 64) AND (name ~ '^[a-z0-9]+(-[a-z0-9]+)*$'::text))),
CONSTRAINT ai_gateway_keys_secret_prefix_check CHECK ((length((secret_prefix)::text) = 11))
@@ -0,0 +1,2 @@
ALTER TABLE ai_gateway_keys
RENAME COLUMN last_heartbeat_at TO last_used_at;
@@ -0,0 +1,2 @@
ALTER TABLE ai_gateway_keys
RENAME COLUMN last_used_at TO last_heartbeat_at;
+3 -3
View File
@@ -4618,9 +4618,9 @@ type AIGatewayKey struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
Name string `db:"name" json:"name"`
// Public token prefix for display and audit correlation. Auth uses hashed_secret.
SecretPrefix string `db:"secret_prefix" json:"secret_prefix"`
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
LastUsedAt sql.NullTime `db:"last_used_at" json:"last_used_at"`
SecretPrefix string `db:"secret_prefix" json:"secret_prefix"`
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
LastHeartbeatAt sql.NullTime `db:"last_heartbeat_at" json:"last_heartbeat_at"`
}
// Per-model token prices used by AI Bridge to compute interception cost.
+2 -2
View File
@@ -1311,10 +1311,10 @@ type sqlcQuerier interface {
UnpinChatByID(ctx context.Context, id uuid.UUID) error
UnsetDefaultChatModelConfigs(ctx context.Context) error
UpdateAIBridgeInterceptionEnded(ctx context.Context, arg UpdateAIBridgeInterceptionEndedParams) (AIBridgeInterception, error)
// Records liveness for an active Gateway DRPC session. The database sets the
// Records heartbeat liveness for an active Gateway DRPC session. The database sets the
// timestamp so it stays consistent regardless of clock drift between API
// replicas.
UpdateAIGatewayKeyLastUsedAt(ctx context.Context, id uuid.UUID) (int64, error)
UpdateAIGatewayKeyLastHeartbeatAt(ctx context.Context, id uuid.UUID) (int64, error)
UpdateAIProvider(ctx context.Context, arg UpdateAIProviderParams) (AIProvider, error)
UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error
UpdateChatACLByID(ctx context.Context, arg UpdateChatACLByIDParams) error
+13 -13
View File
@@ -14938,9 +14938,9 @@ func TestAIGatewayKeysQueries(t *testing.T) {
require.Len(t, keys, 2)
requireAIGatewayKeysRow(t, keys[0], first, firstRow.CreatedAt)
require.False(t, keys[0].LastUsedAt.Valid)
require.False(t, keys[0].LastHeartbeatAt.Valid)
requireAIGatewayKeysRow(t, keys[1], second, secondRow.CreatedAt)
require.False(t, keys[1].LastUsedAt.Valid)
require.False(t, keys[1].LastHeartbeatAt.Valid)
deleted, err := db.DeleteAIGatewayKey(ctx, first.ID)
require.NoError(t, err)
@@ -14989,7 +14989,7 @@ func TestGetAIGatewayKeyByHashedSecret(t *testing.T) {
require.Empty(t, key.ID)
}
func TestUpdateAIGatewayKeyLastUsedAt(t *testing.T) {
func TestUpdateAIGatewayKeyLastHeartbeatAt(t *testing.T) {
t.Parallel()
db, _, sqlDB := dbtestutil.NewDBWithSQLDB(t)
@@ -14999,42 +14999,42 @@ func TestUpdateAIGatewayKeyLastUsedAt(t *testing.T) {
row, err := db.InsertAIGatewayKey(ctx, params)
require.NoError(t, err)
// last_used_at starts NULL until a session records liveness.
// last_heartbeat_at starts NULL until a session records liveness.
keys, err := db.ListAIGatewayKeys(ctx)
require.NoError(t, err)
require.Len(t, keys, 1)
require.False(t, keys[0].LastUsedAt.Valid)
require.False(t, keys[0].LastHeartbeatAt.Valid)
rows, err := db.UpdateAIGatewayKeyLastUsedAt(ctx, params.ID)
rows, err := db.UpdateAIGatewayKeyLastHeartbeatAt(ctx, params.ID)
require.NoError(t, err)
require.EqualValues(t, 1, rows)
keys, err = db.ListAIGatewayKeys(ctx)
require.NoError(t, err)
require.Len(t, keys, 1)
require.True(t, keys[0].LastUsedAt.Valid)
require.True(t, keys[0].LastHeartbeatAt.Valid)
// The database stamps the timestamp, so compare against the row's
// DB-generated CreatedAt to avoid client clock skew.
require.False(t, keys[0].LastUsedAt.Time.Before(row.CreatedAt))
require.False(t, keys[0].LastHeartbeatAt.Time.Before(row.CreatedAt))
// Updating a key that does not exist is a no-op, not an error.
rows, err = db.UpdateAIGatewayKeyLastUsedAt(ctx, uuid.New())
rows, err = db.UpdateAIGatewayKeyLastHeartbeatAt(ctx, uuid.New())
require.NoError(t, err)
require.EqualValues(t, 0, rows)
// Set last_used_at to old time to confirm the update overwrites it with a fresh timestamp.
// Set last_heartbeat_at to old time to confirm the update overwrites it with a fresh timestamp.
staleTime := row.CreatedAt.Add(-time.Hour)
_, err = sqlDB.ExecContext(ctx, "UPDATE ai_gateway_keys SET last_used_at = $1 WHERE id = $2", staleTime, params.ID)
_, err = sqlDB.ExecContext(ctx, "UPDATE ai_gateway_keys SET last_heartbeat_at = $1 WHERE id = $2", staleTime, params.ID)
require.NoError(t, err)
rows, err = db.UpdateAIGatewayKeyLastUsedAt(ctx, params.ID)
rows, err = db.UpdateAIGatewayKeyLastHeartbeatAt(ctx, params.ID)
require.NoError(t, err)
require.EqualValues(t, 1, rows)
keys, err = db.ListAIGatewayKeys(ctx)
require.NoError(t, err)
require.Len(t, keys, 1)
require.True(t, keys[0].LastUsedAt.Time.After(staleTime))
require.True(t, keys[0].LastHeartbeatAt.Time.After(staleTime))
}
func aiGatewayKeyParams(name string, secretPrefix string) database.InsertAIGatewayKeyParams {
+21 -21
View File
@@ -113,15 +113,15 @@ func (q *sqlQuerier) ActivityBumpWorkspace(ctx context.Context, arg ActivityBump
const deleteAIGatewayKey = `-- name: DeleteAIGatewayKey :one
DELETE FROM ai_gateway_keys WHERE id = $1
RETURNING id, name, secret_prefix, created_at, last_used_at
RETURNING id, name, secret_prefix, created_at, last_heartbeat_at
`
type DeleteAIGatewayKeyRow struct {
ID uuid.UUID `db:"id" json:"id"`
Name string `db:"name" json:"name"`
SecretPrefix string `db:"secret_prefix" json:"secret_prefix"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
LastUsedAt sql.NullTime `db:"last_used_at" json:"last_used_at"`
ID uuid.UUID `db:"id" json:"id"`
Name string `db:"name" json:"name"`
SecretPrefix string `db:"secret_prefix" json:"secret_prefix"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
LastHeartbeatAt sql.NullTime `db:"last_heartbeat_at" json:"last_heartbeat_at"`
}
func (q *sqlQuerier) DeleteAIGatewayKey(ctx context.Context, id uuid.UUID) (DeleteAIGatewayKeyRow, error) {
@@ -132,13 +132,13 @@ func (q *sqlQuerier) DeleteAIGatewayKey(ctx context.Context, id uuid.UUID) (Dele
&i.Name,
&i.SecretPrefix,
&i.CreatedAt,
&i.LastUsedAt,
&i.LastHeartbeatAt,
)
return i, err
}
const getAIGatewayKeyByHashedSecret = `-- name: GetAIGatewayKeyByHashedSecret :one
SELECT id, created_at, name, secret_prefix, hashed_secret, last_used_at
SELECT id, created_at, name, secret_prefix, hashed_secret, last_heartbeat_at
FROM ai_gateway_keys
WHERE hashed_secret = $1
`
@@ -155,7 +155,7 @@ func (q *sqlQuerier) GetAIGatewayKeyByHashedSecret(ctx context.Context, hashedSe
&i.Name,
&i.SecretPrefix,
&i.HashedSecret,
&i.LastUsedAt,
&i.LastHeartbeatAt,
)
return i, err
}
@@ -198,17 +198,17 @@ func (q *sqlQuerier) InsertAIGatewayKey(ctx context.Context, arg InsertAIGateway
}
const listAIGatewayKeys = `-- name: ListAIGatewayKeys :many
SELECT id, name, secret_prefix, created_at, last_used_at
SELECT id, name, secret_prefix, created_at, last_heartbeat_at
FROM ai_gateway_keys
ORDER BY created_at ASC
`
type ListAIGatewayKeysRow struct {
ID uuid.UUID `db:"id" json:"id"`
Name string `db:"name" json:"name"`
SecretPrefix string `db:"secret_prefix" json:"secret_prefix"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
LastUsedAt sql.NullTime `db:"last_used_at" json:"last_used_at"`
ID uuid.UUID `db:"id" json:"id"`
Name string `db:"name" json:"name"`
SecretPrefix string `db:"secret_prefix" json:"secret_prefix"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
LastHeartbeatAt sql.NullTime `db:"last_heartbeat_at" json:"last_heartbeat_at"`
}
func (q *sqlQuerier) ListAIGatewayKeys(ctx context.Context) ([]ListAIGatewayKeysRow, error) {
@@ -225,7 +225,7 @@ func (q *sqlQuerier) ListAIGatewayKeys(ctx context.Context) ([]ListAIGatewayKeys
&i.Name,
&i.SecretPrefix,
&i.CreatedAt,
&i.LastUsedAt,
&i.LastHeartbeatAt,
); err != nil {
return nil, err
}
@@ -240,17 +240,17 @@ func (q *sqlQuerier) ListAIGatewayKeys(ctx context.Context) ([]ListAIGatewayKeys
return items, nil
}
const updateAIGatewayKeyLastUsedAt = `-- name: UpdateAIGatewayKeyLastUsedAt :execrows
const updateAIGatewayKeyLastHeartbeatAt = `-- name: UpdateAIGatewayKeyLastHeartbeatAt :execrows
UPDATE ai_gateway_keys
SET last_used_at = NOW()
SET last_heartbeat_at = NOW()
WHERE id = $1
`
// Records liveness for an active Gateway DRPC session. The database sets the
// Records heartbeat liveness for an active Gateway DRPC session. The database sets the
// timestamp so it stays consistent regardless of clock drift between API
// replicas.
func (q *sqlQuerier) UpdateAIGatewayKeyLastUsedAt(ctx context.Context, id uuid.UUID) (int64, error) {
result, err := q.db.ExecContext(ctx, updateAIGatewayKeyLastUsedAt, id)
func (q *sqlQuerier) UpdateAIGatewayKeyLastHeartbeatAt(ctx context.Context, id uuid.UUID) (int64, error) {
result, err := q.db.ExecContext(ctx, updateAIGatewayKeyLastHeartbeatAt, id)
if err != nil {
return 0, err
}
+5 -5
View File
@@ -4,13 +4,13 @@ VALUES ($1, @name, $2, $3, NOW())
RETURNING id, name, secret_prefix, created_at;
-- name: ListAIGatewayKeys :many
SELECT id, name, secret_prefix, created_at, last_used_at
SELECT id, name, secret_prefix, created_at, last_heartbeat_at
FROM ai_gateway_keys
ORDER BY created_at ASC;
-- name: DeleteAIGatewayKey :one
DELETE FROM ai_gateway_keys WHERE id = $1
RETURNING id, name, secret_prefix, created_at, last_used_at;
RETURNING id, name, secret_prefix, created_at, last_heartbeat_at;
-- name: GetAIGatewayKeyByHashedSecret :one
-- Authenticates a standalone AI Gateway replica by its hashed key secret,
@@ -20,10 +20,10 @@ SELECT *
FROM ai_gateway_keys
WHERE hashed_secret = $1;
-- name: UpdateAIGatewayKeyLastUsedAt :execrows
-- Records liveness for an active Gateway DRPC session. The database sets the
-- name: UpdateAIGatewayKeyLastHeartbeatAt :execrows
-- Records heartbeat liveness for an active Gateway DRPC session. The database sets the
-- timestamp so it stays consistent regardless of clock drift between API
-- replicas.
UPDATE ai_gateway_keys
SET last_used_at = NOW()
SET last_heartbeat_at = NOW()
WHERE id = $1;