tenseleyflow/shithub / 4551ba5

Browse files

migration: workflow_caches table + sqlc queries

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
4551ba5f625a7572cf6da65854a3665d68b52127
Parents
cc079b9
Tree
fc9fb7f

6 changed files

StatusFile+-
A internal/actions/queries/workflow_caches.sql 63 0
M internal/actions/sqlc/models.go 12 0
M internal/actions/sqlc/querier.go 19 0
A internal/actions/sqlc/workflow_caches.sql.go 234 0
A internal/migrationsfs/migrations/0065_workflow_caches.sql 56 0
M sqlc.yaml 6 0
internal/actions/queries/workflow_caches.sqladded
@@ -0,0 +1,63 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+-- name: InsertWorkflowCache :one
4
+-- Called by the future runner-side upload handler when an
5
+-- actions/cache step completes its tarball upload. Idempotent on
6
+-- (repo_id, cache_key, cache_version, git_ref): a re-upload with the
7
+-- same coordinates updates size + last_accessed_at + object_key.
8
+INSERT INTO workflow_caches (
9
+    repo_id, cache_key, cache_version, git_ref, object_key, size_bytes
10
+) VALUES ($1, $2, $3, $4, $5, $6)
11
+ON CONFLICT (repo_id, cache_key, cache_version, git_ref) DO UPDATE
12
+    SET object_key       = EXCLUDED.object_key,
13
+        size_bytes       = EXCLUDED.size_bytes,
14
+        last_accessed_at = now()
15
+RETURNING id, repo_id, cache_key, cache_version, git_ref, object_key,
16
+          size_bytes, last_accessed_at, created_at;
17
+
18
+-- name: ListWorkflowCachesForRepo :many
19
+-- Paginated list filtered optionally by ref + key. NULL params skip
20
+-- the filter. Sorted by last_accessed_at DESC so an operator sees the
21
+-- live caches first.
22
+SELECT id, repo_id, cache_key, cache_version, git_ref, object_key,
23
+       size_bytes, last_accessed_at, created_at
24
+FROM workflow_caches
25
+WHERE repo_id = $1
26
+  AND (sqlc.narg(git_ref)::text IS NULL OR git_ref = sqlc.narg(git_ref)::text)
27
+  AND (sqlc.narg(cache_key)::text IS NULL OR cache_key = sqlc.narg(cache_key)::text)
28
+ORDER BY last_accessed_at DESC, id DESC
29
+LIMIT $2 OFFSET $3;
30
+
31
+-- name: CountWorkflowCachesForRepo :one
32
+SELECT COUNT(*) FROM workflow_caches
33
+WHERE repo_id = $1
34
+  AND (sqlc.narg(git_ref)::text IS NULL OR git_ref = sqlc.narg(git_ref)::text)
35
+  AND (sqlc.narg(cache_key)::text IS NULL OR cache_key = sqlc.narg(cache_key)::text);
36
+
37
+-- name: GetWorkflowCacheByID :one
38
+SELECT id, repo_id, cache_key, cache_version, git_ref, object_key,
39
+       size_bytes, last_accessed_at, created_at
40
+FROM workflow_caches
41
+WHERE id = $1;
42
+
43
+-- name: DeleteWorkflowCacheByID :execrows
44
+DELETE FROM workflow_caches WHERE id = $1 AND repo_id = $2;
45
+
46
+-- name: DeleteWorkflowCachesByKey :many
47
+-- Returns the deleted rows' object_keys so the handler can purge the
48
+-- backing tarballs from object storage.
49
+WITH deleted AS (
50
+    DELETE FROM workflow_caches
51
+    WHERE repo_id = $1
52
+      AND cache_key = $2
53
+      AND (sqlc.narg(git_ref)::text IS NULL OR git_ref = sqlc.narg(git_ref)::text)
54
+    RETURNING object_key
55
+)
56
+SELECT object_key FROM deleted;
57
+
58
+-- name: TouchWorkflowCache :exec
59
+-- Bumps last_accessed_at on cache hit. Called by the future
60
+-- restore-side handler.
61
+UPDATE workflow_caches
62
+SET last_accessed_at = now()
63
+WHERE id = $1;
internal/actions/sqlc/models.gomodified
@@ -2682,6 +2682,18 @@ type WorkflowArtifact struct {
26822682
 	CreatedAt pgtype.Timestamptz
26832683
 }
26842684
 
2685
+type WorkflowCache struct {
2686
+	ID             int64
2687
+	RepoID         int64
2688
+	CacheKey       string
2689
+	CacheVersion   string
2690
+	GitRef         string
2691
+	ObjectKey      string
2692
+	SizeBytes      int64
2693
+	LastAccessedAt pgtype.Timestamptz
2694
+	CreatedAt      pgtype.Timestamptz
2695
+}
2696
+
26852697
 type WorkflowDisabled struct {
26862698
 	RepoID           int64
26872699
 	WorkflowFile     string
internal/actions/sqlc/querier.gomodified
@@ -17,6 +17,7 @@ type Querier interface {
1717
 	ClaimQueuedWorkflowJob(ctx context.Context, db DBTX, arg ClaimQueuedWorkflowJobParams) (ClaimQueuedWorkflowJobRow, error)
1818
 	CompleteWorkflowRun(ctx context.Context, db DBTX, arg CompleteWorkflowRunParams) (WorkflowRun, error)
1919
 	CountRunningJobsForRunner(ctx context.Context, db DBTX, runnerID int64) (int32, error)
20
+	CountWorkflowCachesForRepo(ctx context.Context, db DBTX, arg CountWorkflowCachesForRepoParams) (int64, error)
2021
 	CountWorkflowRunsForRepo(ctx context.Context, db DBTX, arg CountWorkflowRunsForRepoParams) (int64, error)
2122
 	DeleteOldRunnerJWTUsesForCleanup(ctx context.Context, db DBTX, expiresAt pgtype.Timestamptz) (int64, error)
2223
 	DeleteOldWorkflowRunsForCleanup(ctx context.Context, db DBTX, completedAt pgtype.Timestamptz) (int64, error)
@@ -28,6 +29,10 @@ type Querier interface {
2829
 	DeleteStepLogChunks(ctx context.Context, db DBTX, stepID int64) error
2930
 	DeleteWorkflowArtifactByID(ctx context.Context, db DBTX, id int64) (int64, error)
3031
 	DeleteWorkflowArtifactsByIDs(ctx context.Context, db DBTX, dollar_1 []int64) (int64, error)
32
+	DeleteWorkflowCacheByID(ctx context.Context, db DBTX, arg DeleteWorkflowCacheByIDParams) (int64, error)
33
+	// Returns the deleted rows' object_keys so the handler can purge the
34
+	// backing tarballs from object storage.
35
+	DeleteWorkflowCachesByKey(ctx context.Context, db DBTX, arg DeleteWorkflowCachesByKeyParams) ([]string, error)
3136
 	// Cascades to workflow_jobs → workflow_steps → workflow_step_log_chunks
3237
 	// and workflow_artifacts via the on-delete-cascade FK chain. The
3338
 	// S3-side artifact blobs are NOT removed here; the handler queries
@@ -61,6 +66,7 @@ type Querier interface {
6166
 	GetRunnerByTokenHash(ctx context.Context, db DBTX, tokenHash []byte) (GetRunnerByTokenHashRow, error)
6267
 	GetStepLogChunkBefore(ctx context.Context, db DBTX, arg GetStepLogChunkBeforeParams) (WorkflowStepLogChunk, error)
6368
 	GetStepLogChunkByStepSeq(ctx context.Context, db DBTX, arg GetStepLogChunkByStepSeqParams) (WorkflowStepLogChunk, error)
69
+	GetWorkflowCacheByID(ctx context.Context, db DBTX, id int64) (WorkflowCache, error)
6470
 	GetWorkflowJobByID(ctx context.Context, db DBTX, id int64) (WorkflowJob, error)
6571
 	GetWorkflowJobSecretMask(ctx context.Context, db DBTX, jobID int64) (WorkflowJobSecretMask, error)
6672
 	GetWorkflowRunByID(ctx context.Context, db DBTX, id int64) (WorkflowRun, error)
@@ -73,6 +79,12 @@ type Querier interface {
7379
 	InsertRunner(ctx context.Context, db DBTX, arg InsertRunnerParams) (WorkflowRunner, error)
7480
 	InsertRunnerToken(ctx context.Context, db DBTX, arg InsertRunnerTokenParams) (RunnerToken, error)
7581
 	// SPDX-License-Identifier: AGPL-3.0-or-later
82
+	// Called by the future runner-side upload handler when an
83
+	// actions/cache step completes its tarball upload. Idempotent on
84
+	// (repo_id, cache_key, cache_version, git_ref): a re-upload with the
85
+	// same coordinates updates size + last_accessed_at + object_key.
86
+	InsertWorkflowCache(ctx context.Context, db DBTX, arg InsertWorkflowCacheParams) (WorkflowCache, error)
87
+	// SPDX-License-Identifier: AGPL-3.0-or-later
7688
 	InsertWorkflowJob(ctx context.Context, db DBTX, arg InsertWorkflowJobParams) (WorkflowJob, error)
7789
 	// SPDX-License-Identifier: AGPL-3.0-or-later
7890
 	InsertWorkflowRun(ctx context.Context, db DBTX, arg InsertWorkflowRunParams) (WorkflowRun, error)
@@ -107,6 +119,10 @@ type Querier interface {
107119
 	ListRunners(ctx context.Context, db DBTX) ([]ListRunnersRow, error)
108120
 	ListStepLogChunks(ctx context.Context, db DBTX, arg ListStepLogChunksParams) ([]WorkflowStepLogChunk, error)
109121
 	ListStepsForJob(ctx context.Context, db DBTX, jobID int64) ([]ListStepsForJobRow, error)
122
+	// Paginated list filtered optionally by ref + key. NULL params skip
123
+	// the filter. Sorted by last_accessed_at DESC so an operator sees the
124
+	// live caches first.
125
+	ListWorkflowCachesForRepo(ctx context.Context, db DBTX, arg ListWorkflowCachesForRepoParams) ([]WorkflowCache, error)
110126
 	ListWorkflowRunWorkflowsForRepo(ctx context.Context, db DBTX, repoID int64) ([]ListWorkflowRunWorkflowsForRepoRow, error)
111127
 	ListWorkflowRunsForRepo(ctx context.Context, db DBTX, arg ListWorkflowRunsForRepoParams) ([]ListWorkflowRunsForRepoRow, error)
112128
 	LockRunnerByID(ctx context.Context, db DBTX, id int64) (WorkflowRunner, error)
@@ -129,6 +145,9 @@ type Querier interface {
129145
 	RevokeAllTokensForRunner(ctx context.Context, db DBTX, runnerID int64) error
130146
 	StartWorkflowRun(ctx context.Context, db DBTX, id int64) (WorkflowRun, error)
131147
 	TouchRunnerHeartbeat(ctx context.Context, db DBTX, arg TouchRunnerHeartbeatParams) error
148
+	// Bumps last_accessed_at on cache hit. Called by the future
149
+	// restore-side handler.
150
+	TouchWorkflowCache(ctx context.Context, db DBTX, id int64) error
132151
 	UpdateStepLogChunk(ctx context.Context, db DBTX, arg UpdateStepLogChunkParams) error
133152
 	UpdateWorkflowJobStatus(ctx context.Context, db DBTX, arg UpdateWorkflowJobStatusParams) (WorkflowJob, error)
134153
 	UpdateWorkflowStepLogObject(ctx context.Context, db DBTX, arg UpdateWorkflowStepLogObjectParams) (WorkflowStep, error)
internal/actions/sqlc/workflow_caches.sql.goadded
@@ -0,0 +1,234 @@
1
+// Code generated by sqlc. DO NOT EDIT.
2
+// versions:
3
+//   sqlc v1.31.1
4
+// source: workflow_caches.sql
5
+
6
+package actionsdb
7
+
8
+import (
9
+	"context"
10
+
11
+	"github.com/jackc/pgx/v5/pgtype"
12
+)
13
+
14
+const countWorkflowCachesForRepo = `-- name: CountWorkflowCachesForRepo :one
15
+SELECT COUNT(*) FROM workflow_caches
16
+WHERE repo_id = $1
17
+  AND ($2::text IS NULL OR git_ref = $2::text)
18
+  AND ($3::text IS NULL OR cache_key = $3::text)
19
+`
20
+
21
+type CountWorkflowCachesForRepoParams struct {
22
+	RepoID   int64
23
+	GitRef   pgtype.Text
24
+	CacheKey pgtype.Text
25
+}
26
+
27
+func (q *Queries) CountWorkflowCachesForRepo(ctx context.Context, db DBTX, arg CountWorkflowCachesForRepoParams) (int64, error) {
28
+	row := db.QueryRow(ctx, countWorkflowCachesForRepo, arg.RepoID, arg.GitRef, arg.CacheKey)
29
+	var count int64
30
+	err := row.Scan(&count)
31
+	return count, err
32
+}
33
+
34
+const deleteWorkflowCacheByID = `-- name: DeleteWorkflowCacheByID :execrows
35
+DELETE FROM workflow_caches WHERE id = $1 AND repo_id = $2
36
+`
37
+
38
+type DeleteWorkflowCacheByIDParams struct {
39
+	ID     int64
40
+	RepoID int64
41
+}
42
+
43
+func (q *Queries) DeleteWorkflowCacheByID(ctx context.Context, db DBTX, arg DeleteWorkflowCacheByIDParams) (int64, error) {
44
+	result, err := db.Exec(ctx, deleteWorkflowCacheByID, arg.ID, arg.RepoID)
45
+	if err != nil {
46
+		return 0, err
47
+	}
48
+	return result.RowsAffected(), nil
49
+}
50
+
51
+const deleteWorkflowCachesByKey = `-- name: DeleteWorkflowCachesByKey :many
52
+WITH deleted AS (
53
+    DELETE FROM workflow_caches
54
+    WHERE repo_id = $1
55
+      AND cache_key = $2
56
+      AND ($3::text IS NULL OR git_ref = $3::text)
57
+    RETURNING object_key
58
+)
59
+SELECT object_key FROM deleted
60
+`
61
+
62
+type DeleteWorkflowCachesByKeyParams struct {
63
+	RepoID   int64
64
+	CacheKey string
65
+	GitRef   pgtype.Text
66
+}
67
+
68
+// Returns the deleted rows' object_keys so the handler can purge the
69
+// backing tarballs from object storage.
70
+func (q *Queries) DeleteWorkflowCachesByKey(ctx context.Context, db DBTX, arg DeleteWorkflowCachesByKeyParams) ([]string, error) {
71
+	rows, err := db.Query(ctx, deleteWorkflowCachesByKey, arg.RepoID, arg.CacheKey, arg.GitRef)
72
+	if err != nil {
73
+		return nil, err
74
+	}
75
+	defer rows.Close()
76
+	items := []string{}
77
+	for rows.Next() {
78
+		var object_key string
79
+		if err := rows.Scan(&object_key); err != nil {
80
+			return nil, err
81
+		}
82
+		items = append(items, object_key)
83
+	}
84
+	if err := rows.Err(); err != nil {
85
+		return nil, err
86
+	}
87
+	return items, nil
88
+}
89
+
90
+const getWorkflowCacheByID = `-- name: GetWorkflowCacheByID :one
91
+SELECT id, repo_id, cache_key, cache_version, git_ref, object_key,
92
+       size_bytes, last_accessed_at, created_at
93
+FROM workflow_caches
94
+WHERE id = $1
95
+`
96
+
97
+func (q *Queries) GetWorkflowCacheByID(ctx context.Context, db DBTX, id int64) (WorkflowCache, error) {
98
+	row := db.QueryRow(ctx, getWorkflowCacheByID, id)
99
+	var i WorkflowCache
100
+	err := row.Scan(
101
+		&i.ID,
102
+		&i.RepoID,
103
+		&i.CacheKey,
104
+		&i.CacheVersion,
105
+		&i.GitRef,
106
+		&i.ObjectKey,
107
+		&i.SizeBytes,
108
+		&i.LastAccessedAt,
109
+		&i.CreatedAt,
110
+	)
111
+	return i, err
112
+}
113
+
114
+const insertWorkflowCache = `-- name: InsertWorkflowCache :one
115
+
116
+INSERT INTO workflow_caches (
117
+    repo_id, cache_key, cache_version, git_ref, object_key, size_bytes
118
+) VALUES ($1, $2, $3, $4, $5, $6)
119
+ON CONFLICT (repo_id, cache_key, cache_version, git_ref) DO UPDATE
120
+    SET object_key       = EXCLUDED.object_key,
121
+        size_bytes       = EXCLUDED.size_bytes,
122
+        last_accessed_at = now()
123
+RETURNING id, repo_id, cache_key, cache_version, git_ref, object_key,
124
+          size_bytes, last_accessed_at, created_at
125
+`
126
+
127
+type InsertWorkflowCacheParams struct {
128
+	RepoID       int64
129
+	CacheKey     string
130
+	CacheVersion string
131
+	GitRef       string
132
+	ObjectKey    string
133
+	SizeBytes    int64
134
+}
135
+
136
+// SPDX-License-Identifier: AGPL-3.0-or-later
137
+// Called by the future runner-side upload handler when an
138
+// actions/cache step completes its tarball upload. Idempotent on
139
+// (repo_id, cache_key, cache_version, git_ref): a re-upload with the
140
+// same coordinates updates size + last_accessed_at + object_key.
141
+func (q *Queries) InsertWorkflowCache(ctx context.Context, db DBTX, arg InsertWorkflowCacheParams) (WorkflowCache, error) {
142
+	row := db.QueryRow(ctx, insertWorkflowCache,
143
+		arg.RepoID,
144
+		arg.CacheKey,
145
+		arg.CacheVersion,
146
+		arg.GitRef,
147
+		arg.ObjectKey,
148
+		arg.SizeBytes,
149
+	)
150
+	var i WorkflowCache
151
+	err := row.Scan(
152
+		&i.ID,
153
+		&i.RepoID,
154
+		&i.CacheKey,
155
+		&i.CacheVersion,
156
+		&i.GitRef,
157
+		&i.ObjectKey,
158
+		&i.SizeBytes,
159
+		&i.LastAccessedAt,
160
+		&i.CreatedAt,
161
+	)
162
+	return i, err
163
+}
164
+
165
+const listWorkflowCachesForRepo = `-- name: ListWorkflowCachesForRepo :many
166
+SELECT id, repo_id, cache_key, cache_version, git_ref, object_key,
167
+       size_bytes, last_accessed_at, created_at
168
+FROM workflow_caches
169
+WHERE repo_id = $1
170
+  AND ($4::text IS NULL OR git_ref = $4::text)
171
+  AND ($5::text IS NULL OR cache_key = $5::text)
172
+ORDER BY last_accessed_at DESC, id DESC
173
+LIMIT $2 OFFSET $3
174
+`
175
+
176
+type ListWorkflowCachesForRepoParams struct {
177
+	RepoID   int64
178
+	Limit    int32
179
+	Offset   int32
180
+	GitRef   pgtype.Text
181
+	CacheKey pgtype.Text
182
+}
183
+
184
+// Paginated list filtered optionally by ref + key. NULL params skip
185
+// the filter. Sorted by last_accessed_at DESC so an operator sees the
186
+// live caches first.
187
+func (q *Queries) ListWorkflowCachesForRepo(ctx context.Context, db DBTX, arg ListWorkflowCachesForRepoParams) ([]WorkflowCache, error) {
188
+	rows, err := db.Query(ctx, listWorkflowCachesForRepo,
189
+		arg.RepoID,
190
+		arg.Limit,
191
+		arg.Offset,
192
+		arg.GitRef,
193
+		arg.CacheKey,
194
+	)
195
+	if err != nil {
196
+		return nil, err
197
+	}
198
+	defer rows.Close()
199
+	items := []WorkflowCache{}
200
+	for rows.Next() {
201
+		var i WorkflowCache
202
+		if err := rows.Scan(
203
+			&i.ID,
204
+			&i.RepoID,
205
+			&i.CacheKey,
206
+			&i.CacheVersion,
207
+			&i.GitRef,
208
+			&i.ObjectKey,
209
+			&i.SizeBytes,
210
+			&i.LastAccessedAt,
211
+			&i.CreatedAt,
212
+		); err != nil {
213
+			return nil, err
214
+		}
215
+		items = append(items, i)
216
+	}
217
+	if err := rows.Err(); err != nil {
218
+		return nil, err
219
+	}
220
+	return items, nil
221
+}
222
+
223
+const touchWorkflowCache = `-- name: TouchWorkflowCache :exec
224
+UPDATE workflow_caches
225
+SET last_accessed_at = now()
226
+WHERE id = $1
227
+`
228
+
229
+// Bumps last_accessed_at on cache hit. Called by the future
230
+// restore-side handler.
231
+func (q *Queries) TouchWorkflowCache(ctx context.Context, db DBTX, id int64) error {
232
+	_, err := db.Exec(ctx, touchWorkflowCache, id)
233
+	return err
234
+}
internal/migrationsfs/migrations/0065_workflow_caches.sqladded
@@ -0,0 +1,56 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+--
3
+-- Workflow caches. Records of cached workflow artifacts uploaded by
4
+-- the `actions/cache@v*` workflow step. Mirrors GitHub Actions'
5
+-- cache semantics:
6
+--
7
+--   * cache_key: the user-supplied cache key (e.g. "node-modules-${{ hashFiles(...) }}").
8
+--   * cache_version: a content-derived version string (matches gh's
9
+--     `version` field — opaque to the server).
10
+--   * git_ref: the ref the cache was created against. The actions/cache
11
+--     restore logic looks up caches for the current ref first, then
12
+--     falls back to the repo's default branch.
13
+--   * object_key: location of the tarball in the shared object store.
14
+--   * size_bytes: persisted size for capacity-tracking.
15
+--
16
+-- A row uniquely identifies a cache by (repo_id, cache_key,
17
+-- cache_version, git_ref) — multiple refs/versions can share a key,
18
+-- but a given (key, version, ref) is one tarball.
19
+--
20
+-- last_accessed_at is bumped by the runner on cache restore so the
21
+-- LRU eviction policy can purge oldest entries first. The §13 REST
22
+-- surface exposes list + delete; the eviction sweeper runs out of
23
+-- band (future sprint).
24
+--
25
+-- The runner-side write path that POPULATES this table is not yet
26
+-- implemented — the actions/cache toolkit's upload protocol is its
27
+-- own sprint. This table + the REST surface land first so operators
28
+-- have an observation seat for when caches arrive.
29
+
30
+-- +goose Up
31
+CREATE TABLE workflow_caches (
32
+    id              bigserial   PRIMARY KEY,
33
+    repo_id         bigint      NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
34
+    cache_key       text        NOT NULL,
35
+    cache_version   text        NOT NULL,
36
+    git_ref         text        NOT NULL,
37
+    object_key      text        NOT NULL,
38
+    size_bytes      bigint      NOT NULL DEFAULT 0,
39
+    last_accessed_at timestamptz NOT NULL DEFAULT now(),
40
+    created_at      timestamptz NOT NULL DEFAULT now(),
41
+
42
+    UNIQUE (repo_id, cache_key, cache_version, git_ref),
43
+    CONSTRAINT workflow_caches_cache_key_length CHECK (char_length(cache_key) BETWEEN 1 AND 512),
44
+    CONSTRAINT workflow_caches_cache_version_length CHECK (char_length(cache_version) BETWEEN 1 AND 256),
45
+    CONSTRAINT workflow_caches_git_ref_length CHECK (char_length(git_ref) BETWEEN 1 AND 256),
46
+    CONSTRAINT workflow_caches_size_nonneg CHECK (size_bytes >= 0)
47
+);
48
+
49
+CREATE INDEX workflow_caches_repo_id_idx
50
+    ON workflow_caches (repo_id, last_accessed_at DESC);
51
+
52
+CREATE INDEX workflow_caches_repo_id_key_idx
53
+    ON workflow_caches (repo_id, cache_key);
54
+
55
+-- +goose Down
56
+DROP TABLE IF EXISTS workflow_caches;
sqlc.yamlmodified
@@ -241,6 +241,12 @@ sql:
241241
         emit_exact_table_names: false
242242
         emit_empty_slices: true
243243
         emit_methods_with_db_argument: true
244
+        rename:
245
+          # sqlc strips "-es" suffixes naively; ours has a CH cluster so
246
+          # the default singular "WorkflowCach" reads as a typo. The
247
+          # rename targets the pre-snake-case Go identifier sqlc would
248
+          # otherwise emit, not the table name.
249
+          workflow_cach: WorkflowCache
244250
 
245251
   - engine: postgresql
246252
     schema: internal/migrationsfs/migrations