Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
480 lines
18 KiB
Go
480 lines
18 KiB
Go
package storesqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// schemaMigrationsTable is this package's own migration ledger, independent
|
|
// of any host ledger.
|
|
const schemaMigrationsTable = "agent_store_schema_migrations"
|
|
|
|
// legacySchemaMigrationsTable is the tuttid ledger these migrations lived in
|
|
// before the store was extracted. On first Migrate against a database that
|
|
// has it, already-applied agent migrations are claimed (copied) into the
|
|
// package ledger so they are not replayed.
|
|
const legacySchemaMigrationsTable = "tuttid_schema_migrations"
|
|
|
|
const schemaMigrationLegacyClaimV1 = "agent_store_legacy_claim_v1"
|
|
|
|
const schemaMigrationWorkspaceAgentActivityV1 = "workspace_agent_activity_v1"
|
|
const schemaMigrationWorkspaceAgentActivityV2 = "workspace_agent_activity_v2"
|
|
const schemaMigrationWorkspaceAgentActivityV3 = "workspace_agent_activity_v3"
|
|
const schemaMigrationWorkspaceAgentActivityV4 = "workspace_agent_activity_v4"
|
|
const schemaMigrationWorkspaceAgentActivityV5 = "workspace_agent_activity_v5"
|
|
const schemaMigrationWorkspaceAgentActivityV6 = "workspace_agent_activity_v6"
|
|
const schemaMigrationWorkspaceAgentActivityV7 = "workspace_agent_activity_v7"
|
|
const schemaMigrationWorkspaceAgentActivityV8 = "workspace_agent_activity_v8"
|
|
const schemaMigrationWorkspaceAgentActivityV9 = "workspace_agent_activity_v9"
|
|
const schemaMigrationWorkspaceAgentActivityV10 = "workspace_agent_activity_v10"
|
|
const schemaMigrationWorkspaceAgentActivityV11 = "workspace_agent_activity_v11"
|
|
const schemaMigrationWorkspaceAgentActivityRailV1 = "workspace_agent_activity_rail_v1"
|
|
const schemaMigrationWorkspaceAgentActivityRailV2 = "workspace_agent_activity_rail_v2"
|
|
const schemaMigrationWorkspaceAgentActivityTurnsV1 = "workspace_agent_activity_turns_v1"
|
|
const schemaMigrationWorkspaceAgentActivityInteractionsV2 = "workspace_agent_activity_interactions_v2"
|
|
const schemaMigrationWorkspaceAgentActivityMessagesV2 = "workspace_agent_activity_messages_v2"
|
|
const schemaMigrationWorkspaceAgentActivityTurnIntegrityV1 = "workspace_agent_activity_turn_integrity_v1"
|
|
const schemaMigrationWorkspaceAgentGeneratedFilesRecentTurnsV1 = "workspace_agent_generated_files_recent_turns_v1"
|
|
const schemaMigrationWorkspaceAgentSessionMetadataV1 = "workspace_agent_session_metadata_v1"
|
|
const schemaMigrationWorkspaceAgentSessionMetadataV2 = "workspace_agent_session_metadata_v2"
|
|
const schemaMigrationWorkspaceAgentSessionEntitiesV3 = "workspace_agent_session_entities_v3"
|
|
const schemaMigrationWorkspaceAgentEntityInvariantsV1 = "workspace_agent_entity_invariants_v1"
|
|
const schemaMigrationWorkspaceAgentRuntimeOperationsV1 = "workspace_agent_runtime_operations_v1"
|
|
const schemaMigrationWorkspaceAgentRuntimeOperationsV2 = "workspace_agent_runtime_operations_v2"
|
|
const schemaMigrationWorkspaceAgentRuntimeOperationsV3 = "workspace_agent_runtime_operations_v3"
|
|
const schemaMigrationWorkspaceAgentRuntimeOperationsV4 = "workspace_agent_runtime_operations_v4"
|
|
const schemaMigrationWorkspaceAgentRuntimeOperationsV5 = "workspace_agent_runtime_operations_v5"
|
|
const schemaMigrationWorkspaceAgentSubmitClaimsV1 = "workspace_agent_submit_claims_v1"
|
|
const schemaMigrationWorkspaceAgentSubmitClaimsV2 = "workspace_agent_submit_claims_v2"
|
|
const schemaMigrationWorkspaceAgentSubmitClaimsV3 = "workspace_agent_submit_claims_v3"
|
|
const schemaMigrationWorkspaceAgentSubmitClaimsV4 = "workspace_agent_submit_claims_v4_submission_metadata"
|
|
const schemaMigrationAgentTargetsV1 = "agent_targets_v1"
|
|
const schemaMigrationAgentTargetsV2 = "agent_targets_v2"
|
|
const schemaMigrationAgentTargetsV3 = "agent_targets_v3"
|
|
const schemaMigrationAgentTargetsV4 = "agent_targets_v4"
|
|
const schemaMigrationAgentTargetsV5 = "agent_targets_v5"
|
|
const schemaMigrationWorkspaceAgentSessionTitlesV1 = "workspace_agent_session_titles_v1"
|
|
const schemaMigrationWorkspaceAgentSessionTitlesV2 = "workspace_agent_session_titles_v2"
|
|
const schemaMigrationWorkspaceAgentChildSessionsV1 = "workspace_agent_child_sessions_v1"
|
|
const schemaMigrationWorkspaceAgentRootTurnCompletionV1 = "workspace_agent_root_turn_completion_v1"
|
|
const schemaMigrationWorkspaceAgentTurnProvenanceV1 = "workspace_agent_turn_provenance_v1"
|
|
const schemaMigrationWorkspaceAgentTurnProvenanceV2 = "workspace_agent_turn_provenance_v2"
|
|
const schemaMigrationWorkspaceAgentGoalStateV1 = "workspace_agent_goal_state_v1"
|
|
const schemaMigrationWorkspaceAgentGoalStateV2 = "workspace_agent_goal_state_v2"
|
|
const schemaMigrationWorkspaceAgentGoalStateV3 = "workspace_agent_goal_state_v3"
|
|
const schemaMigrationWorkspaceAgentGoalStateV4 = "workspace_agent_goal_state_v4"
|
|
const schemaMigrationWorkspaceAgentGoalStateV5 = "workspace_agent_goal_state_v5"
|
|
const schemaMigrationWorkspaceAgentGoalStateV6 = "workspace_agent_goal_state_v6"
|
|
const schemaMigrationWorkspaceAgentGoalStateV7 = "workspace_agent_goal_state_v7"
|
|
const schemaMigrationWorkspaceAgentGoalStateV8 = "workspace_agent_goal_state_v8"
|
|
const schemaMigrationWorkspaceAgentGoalProvenanceLedgerV1 = "workspace_agent_goal_provenance_ledger_v1"
|
|
const schemaMigrationWorkspaceAgentGoalGenerationFencesV1 = "workspace_agent_goal_generation_fences_v1"
|
|
const schemaMigrationWorkspaceAgentMessageSemanticsV1 = "workspace_agent_message_semantics_v1"
|
|
const schemaMigrationWorkspaceAgentDeletedPurgeIndexV1 = "workspace_agent_deleted_purge_index_v1"
|
|
const schemaMigrationWorkspaceAgentSessionTurnPageIndexV1 = "workspace_agent_session_turn_page_index_v1"
|
|
const schemaMigrationWorkspaceAgentTurnCapabilityRefsV1 = "workspace_agent_turn_capability_refs_v1"
|
|
const schemaMigrationWorkspaceAgentImportedTurnsV1 = "workspace_agent_imported_turns_v1"
|
|
const schemaMigrationWorkspaceAgentSessionForkV1 = "workspace_agent_session_fork_v1"
|
|
const schemaMigrationWorkspaceAgentSessionForkV2 = "workspace_agent_session_fork_v2"
|
|
const schemaMigrationWorkspaceAgentSessionForkV3 = "workspace_agent_session_fork_v3"
|
|
const schemaMigrationWorkspaceAgentSessionForkV4 = "workspace_agent_session_fork_v4"
|
|
const schemaMigrationWorkspaceAgentSessionForkV5 = "workspace_agent_session_fork_v5"
|
|
const schemaMigrationWorkspaceAgentEffectiveHistoryV1 = "workspace_agent_effective_history_v1"
|
|
const schemaMigrationWorkspaceAgentEffectiveHistoryV2 = "workspace_agent_effective_history_v2_submission_metadata"
|
|
const schemaMigrationWorkspaceAgentSessionForkV6 = "workspace_agent_session_fork_v6_optimistic"
|
|
const schemaMigrationWorkspaceAgentSessionForkV7 = "workspace_agent_session_fork_v7_full_turn_bindings"
|
|
const schemaMigrationWorkspaceAgentProviderCheckpointV1 = "workspace_agent_provider_checkpoint_v1"
|
|
const schemaMigrationWorkspaceAgentProviderTurnBindingJSONV1 = "workspace_agent_provider_turn_binding_json_v1"
|
|
const schemaMigrationWorkspaceAgentRecoverableDeletionV1 = "workspace_agent_recoverable_deletion_v1"
|
|
const schemaMigrationWorkspaceAgentTurnIdentityAnchorV1 = "workspace_agent_turn_identity_anchor_v1"
|
|
const schemaMigrationWorkspaceAgentCommandOutputAliasesV1 = "workspace_agent_command_output_aliases_v1"
|
|
const schemaMigrationWorkspaceAgentToolPayloadBudgetV1 = "workspace_agent_tool_payload_budget_v1"
|
|
|
|
// claimableMigrationIDs are the migration IDs that may already be recorded
|
|
// in the legacy tuttid ledger; the claim copies exactly these.
|
|
var claimableMigrationIDs = []string{
|
|
schemaMigrationWorkspaceAgentActivityV1,
|
|
schemaMigrationWorkspaceAgentActivityV2,
|
|
schemaMigrationWorkspaceAgentActivityV3,
|
|
schemaMigrationWorkspaceAgentActivityV4,
|
|
schemaMigrationWorkspaceAgentActivityV5,
|
|
schemaMigrationWorkspaceAgentActivityV6,
|
|
schemaMigrationWorkspaceAgentActivityRailV1,
|
|
schemaMigrationAgentTargetsV1,
|
|
}
|
|
|
|
// Migrate creates or upgrades the store's tables. It is idempotent and must
|
|
// run before any other method. System target seeding and legacy target ID
|
|
// reconciliation (per Options) run on every call.
|
|
func (s *Store) Migrate(ctx context.Context) error {
|
|
if s == nil || s.db == nil {
|
|
return errors.New("workspace database is not initialized")
|
|
}
|
|
|
|
if _, err := s.db.ExecContext(ctx, `
|
|
CREATE TABLE IF NOT EXISTS `+schemaMigrationsTable+` (
|
|
id TEXT PRIMARY KEY,
|
|
applied_at_unix_ms INTEGER NOT NULL
|
|
);
|
|
`); err != nil {
|
|
return fmt.Errorf("create agent store schema migrations table: %w", err)
|
|
}
|
|
|
|
if err := s.claimLegacyMigrations(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := s.applyWorkspaceAgentActivityV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityV3(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityV4(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityV5(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityV6(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityV7(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityV8(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyAgentTargetsV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyAgentTargetsV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyAgentTargetsV3(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyAgentTargetsV4(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyAgentTargetsV5(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityRailV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityTurnsV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityInteractionsV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityMessagesV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityTurnIntegrityV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGeneratedFilesRecentTurnsV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionMetadataV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionMetadataV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionEntitiesV3(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityV9(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityV10(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentEntityInvariantsV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentRuntimeOperationsV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentRuntimeOperationsV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentRuntimeOperationsV3(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentRuntimeOperationsV4(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentRuntimeOperationsV5(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSubmitClaimsV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSubmitClaimsV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSubmitClaimsV3(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSubmitClaimsV4(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionTitlesV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionTitlesV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentChildSessionsV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentActivityV11(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentRootTurnCompletionV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentTurnProvenanceV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentTurnProvenanceV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGoalStateV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGoalStateV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGoalStateV3(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGoalStateV4(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGoalStateV5(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGoalStateV6(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGoalStateV7(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGoalStateV8(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGoalProvenanceLedgerV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentGoalGenerationFencesV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentMessageSemanticsV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentDeletedPurgeIndexV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionTurnPageIndexV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentTurnCapabilityRefsV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentImportedTurnsV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionForkV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionForkV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionForkV3(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionForkV4(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionForkV5(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentEffectiveHistoryV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentEffectiveHistoryV2(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionForkV6(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentProviderCheckpointV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentSessionForkV7(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentProviderTurnBindingJSONV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentRecoverableDeletionV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentTurnIdentityAnchorV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentCommandOutputAliasesV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
if err := s.applyWorkspaceAgentToolPayloadBudgetV1(ctx); err != nil {
|
|
return err
|
|
}
|
|
return s.applyWorkspaceAgentActivityRailV2(ctx)
|
|
}
|
|
|
|
// claimLegacyMigrations copies agent-store migration records that were
|
|
// applied under the legacy tuttid ledger into the package ledger, exactly
|
|
// once, so already-applied migrations are not replayed against upgraded
|
|
// databases.
|
|
//
|
|
// Deliberate compatibility trade-off: because v1 is claimed instead of
|
|
// replayed, an upgraded legacy database keeps its original
|
|
// workspace_agent_sessions table including the FOREIGN KEY into the host's
|
|
// workspaces table. That FK is harmless there (the tuttid host always has
|
|
// the workspaces table, and its cascade is redundant with the host's
|
|
// explicit ClearSessionsTx call); only databases created fresh by this
|
|
// package get the FK-free schema. Rebuilding existing tables just to drop
|
|
// the FK is not worth the migration risk.
|
|
func (s *Store) claimLegacyMigrations(ctx context.Context) error {
|
|
claimed, err := s.hasMigration(ctx, schemaMigrationLegacyClaimV1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if claimed {
|
|
return nil
|
|
}
|
|
|
|
legacyExists, err := s.hasTable(ctx, legacySchemaMigrationsTable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if legacyExists {
|
|
placeholders := ""
|
|
args := make([]any, 0, len(claimableMigrationIDs))
|
|
for index, id := range claimableMigrationIDs {
|
|
if index > 0 {
|
|
placeholders += ", "
|
|
}
|
|
placeholders += "?"
|
|
args = append(args, id)
|
|
}
|
|
if _, err := s.db.ExecContext(ctx, `
|
|
INSERT OR IGNORE INTO `+schemaMigrationsTable+` (id, applied_at_unix_ms)
|
|
SELECT id, applied_at_unix_ms
|
|
FROM `+legacySchemaMigrationsTable+`
|
|
WHERE id IN (`+placeholders+`)
|
|
`, args...); err != nil {
|
|
return fmt.Errorf("claim legacy agent store migrations: %w", err)
|
|
}
|
|
}
|
|
|
|
return s.recordMigration(ctx, schemaMigrationLegacyClaimV1)
|
|
}
|
|
|
|
func (s *Store) recordMigration(ctx context.Context, migrationID string) error {
|
|
if _, err := s.db.ExecContext(ctx, `
|
|
INSERT INTO `+schemaMigrationsTable+` (id, applied_at_unix_ms)
|
|
VALUES (?, ?);
|
|
`, migrationID, unixMs(time.Now().UTC())); err != nil {
|
|
return fmt.Errorf("record agent store migration %s: %w", migrationID, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func recordMigrationTx(ctx context.Context, tx *sql.Tx, migrationID string) error {
|
|
if _, err := tx.ExecContext(ctx, `
|
|
INSERT INTO `+schemaMigrationsTable+` (id, applied_at_unix_ms) VALUES (?, ?)
|
|
`, migrationID, unixMs(time.Now().UTC())); err != nil {
|
|
return fmt.Errorf("record agent store migration %s: %w", migrationID, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) hasMigration(ctx context.Context, migrationID string) (bool, error) {
|
|
row := s.db.QueryRowContext(ctx, `
|
|
SELECT 1
|
|
FROM `+schemaMigrationsTable+`
|
|
WHERE id = ?
|
|
`, migrationID)
|
|
|
|
var exists int
|
|
if err := row.Scan(&exists); err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return false, nil
|
|
}
|
|
return false, fmt.Errorf("check agent store migration %s: %w", migrationID, err)
|
|
}
|
|
|
|
return exists == 1, nil
|
|
}
|
|
|
|
func (s *Store) hasTable(ctx context.Context, tableName string) (bool, error) {
|
|
row := s.db.QueryRowContext(ctx, `
|
|
SELECT 1
|
|
FROM sqlite_master
|
|
WHERE type = 'table' AND name = ?
|
|
`, tableName)
|
|
|
|
var exists int
|
|
if err := row.Scan(&exists); err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return false, nil
|
|
}
|
|
return false, fmt.Errorf("check table %s: %w", tableName, err)
|
|
}
|
|
|
|
return exists == 1, nil
|
|
}
|
|
|
|
func (s *Store) hasColumn(ctx context.Context, tableName string, columnName string) (bool, error) {
|
|
rows, err := s.db.QueryContext(ctx, fmt.Sprintf("PRAGMA table_info(%s)", tableName))
|
|
if err != nil {
|
|
return false, fmt.Errorf("inspect agent store table %s: %w", tableName, err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var (
|
|
columnID int
|
|
name string
|
|
columnType string
|
|
notNull int
|
|
defaultSQL sql.NullString
|
|
pk int
|
|
)
|
|
if err := rows.Scan(&columnID, &name, &columnType, ¬Null, &defaultSQL, &pk); err != nil {
|
|
return false, fmt.Errorf("scan agent store table info %s: %w", tableName, err)
|
|
}
|
|
if name == columnName {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return false, fmt.Errorf("iterate agent store table info %s: %w", tableName, err)
|
|
}
|
|
|
|
return false, nil
|
|
}
|