Files
multica/server/pkg/db/generated/skill.sql.go
T
be93feb063 MUL-6792: perf(server): batch skill-file loads in LoadAgentSkills (#7689)
* perf(server): batch skill-file loads in LoadAgentSkills

LoadAgentSkills issued one ListSkillFiles query per skill inside a loop,
so an agent with N skills cost N+1 round trips on the task-claim hot
path (three callers in daemon.go). Each query also pulls full file
bodies, so the fixed per-query overhead (pool acquire, RTT, parse) was
paid N times.

Add ListSkillFilesBySkillIDs (skill_id = ANY(...), ordered by skill_id,
path — backed by idx_skill_file_skill) and load all files in one query,
grouping by skill_id in a single linear pass. N+1 becomes 2. Output
order is preserved by assembling in ListAgentSkills order. Total bytes
transferred are unchanged; this removes round trips and pool acquires,
not payload. The per-skill ListSkillFiles is retained for the single-
skill handler callers that legitimately need it.

Co-Authored-By: Claude Fable 5 <[email protected]>

* fix(server): fail closed when the agent skill read fails

Batching the per-skill ListSkillFiles loop into one query changed what a
swallowed read error means. Before, one failed query dropped one skill's
supporting files. Now a single failure drops the files of EVERY skill --
and the `files, _ :=` discard let claim and resolve carry on as if that
were the agent's real skill set.

Nothing downstream can catch it. BuildAgentSkillBundles hashes whatever
loaded, so the refs sent at claim and the bundle served at resolve agree
with each other; the daemon's validateSkillBundle compares a bundle
against a ref derived from that same bundle, accepts it, and writes it to
the on-disk cache. The agent then starts on skills whose rules are
missing, and the next dispatch reuses the cached truncated copy. The same
was true of the ListAgentSkills error above it, which could silently
strip every workspace skill from an agent.

LoadAgentSkills and LoadAgentSkillBundles now return the error, and the
three call sites settle it:

- claim (both the slim skill-refs path and the legacy inline-skills
  path) refuses with 500 via rejectClaimSkillLoad and leaves the task
  dispatched, so the stale-dispatched reclaim redelivers it -- the same
  treatment rejectClaimSourceLoad gives a transient source read.
- ResolveTaskSkillBundles returns 500, which the daemon's existing
  resolve retry schedule already treats as transient, instead of serving
  a bundle built from a failed read.

Tests: service-level coverage for both read failures, for
LoadAgentSkillBundles failing closed rather than returning built-ins
only, and for the batch grouping itself -- the fixture puts the two
result sets in deliberately different orders (skills by name, files by
skill_id) so a positional zip would hand each skill another skill's
files, and asserts the per-skill query is no longer issued. Handler-level
coverage asserts the 500s and that a refused claim leaves the task
dispatched and unstarted.

Co-Authored-By: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Fable 5 <[email protected]>
Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-31 14:52:00 +08:00

711 lines
19 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: skill.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const addAgentSkill = `-- name: AddAgentSkill :exec
INSERT INTO agent_skill (agent_id, skill_id)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
`
type AddAgentSkillParams struct {
AgentID pgtype.UUID `json:"agent_id"`
SkillID pgtype.UUID `json:"skill_id"`
}
func (q *Queries) AddAgentSkill(ctx context.Context, arg AddAgentSkillParams) error {
_, err := q.db.Exec(ctx, addAgentSkill, arg.AgentID, arg.SkillID)
return err
}
const createSkill = `-- name: CreateSkill :one
INSERT INTO skill (workspace_id, name, description, content, config, created_by)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id
`
type CreateSkillParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
Name string `json:"name"`
Description string `json:"description"`
Content string `json:"content"`
Config []byte `json:"config"`
CreatedBy pgtype.UUID `json:"created_by"`
}
func (q *Queries) CreateSkill(ctx context.Context, arg CreateSkillParams) (Skill, error) {
row := q.db.QueryRow(ctx, createSkill,
arg.WorkspaceID,
arg.Name,
arg.Description,
arg.Content,
arg.Config,
arg.CreatedBy,
)
var i Skill
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
)
return i, err
}
const deleteSkill = `-- name: DeleteSkill :exec
DELETE FROM skill WHERE id = $1 AND workspace_id = $2
`
type DeleteSkillParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
// Defense-in-depth: workspace_id is a SQL-layer tenant guard. See DeleteIssue.
func (q *Queries) DeleteSkill(ctx context.Context, arg DeleteSkillParams) error {
_, err := q.db.Exec(ctx, deleteSkill, arg.ID, arg.WorkspaceID)
return err
}
const deleteSkillFile = `-- name: DeleteSkillFile :exec
DELETE FROM skill_file WHERE id = $1
`
func (q *Queries) DeleteSkillFile(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteSkillFile, id)
return err
}
const deleteSkillFilesBySkill = `-- name: DeleteSkillFilesBySkill :exec
DELETE FROM skill_file WHERE skill_id = $1
`
func (q *Queries) DeleteSkillFilesBySkill(ctx context.Context, skillID pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteSkillFilesBySkill, skillID)
return err
}
const getSkill = `-- name: GetSkill :one
SELECT id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id FROM skill
WHERE id = $1
`
func (q *Queries) GetSkill(ctx context.Context, id pgtype.UUID) (Skill, error) {
row := q.db.QueryRow(ctx, getSkill, id)
var i Skill
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
)
return i, err
}
const getSkillByWorkspaceAndName = `-- name: GetSkillByWorkspaceAndName :one
SELECT id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id FROM skill
WHERE workspace_id = $1 AND name = $2
`
type GetSkillByWorkspaceAndNameParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
Name string `json:"name"`
}
// Used by skill import and runtime-local skill discovery to reuse a workspace
// skill by name rather than violating UNIQUE(workspace_id, name).
func (q *Queries) GetSkillByWorkspaceAndName(ctx context.Context, arg GetSkillByWorkspaceAndNameParams) (Skill, error) {
row := q.db.QueryRow(ctx, getSkillByWorkspaceAndName, arg.WorkspaceID, arg.Name)
var i Skill
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
)
return i, err
}
const getSkillFile = `-- name: GetSkillFile :one
SELECT id, skill_id, path, content, created_at, updated_at FROM skill_file
WHERE id = $1
`
func (q *Queries) GetSkillFile(ctx context.Context, id pgtype.UUID) (SkillFile, error) {
row := q.db.QueryRow(ctx, getSkillFile, id)
var i SkillFile
err := row.Scan(
&i.ID,
&i.SkillID,
&i.Path,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getSkillInWorkspace = `-- name: GetSkillInWorkspace :one
SELECT id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id FROM skill
WHERE id = $1 AND workspace_id = $2
`
type GetSkillInWorkspaceParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
func (q *Queries) GetSkillInWorkspace(ctx context.Context, arg GetSkillInWorkspaceParams) (Skill, error) {
row := q.db.QueryRow(ctx, getSkillInWorkspace, arg.ID, arg.WorkspaceID)
var i Skill
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
)
return i, err
}
const listAgentSkillNamesByAgentIDs = `-- name: ListAgentSkillNamesByAgentIDs :many
SELECT ask.agent_id, s.name
FROM agent_skill ask
JOIN skill s ON s.id = ask.skill_id
WHERE ask.agent_id = ANY($1::uuid[])
AND ask.enabled = TRUE
ORDER BY ask.agent_id, s.name ASC
`
type ListAgentSkillNamesByAgentIDsRow struct {
AgentID pgtype.UUID `json:"agent_id"`
Name string `json:"name"`
}
func (q *Queries) ListAgentSkillNamesByAgentIDs(ctx context.Context, agentIds []pgtype.UUID) ([]ListAgentSkillNamesByAgentIDsRow, error) {
rows, err := q.db.Query(ctx, listAgentSkillNamesByAgentIDs, agentIds)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListAgentSkillNamesByAgentIDsRow{}
for rows.Next() {
var i ListAgentSkillNamesByAgentIDsRow
if err := rows.Scan(&i.AgentID, &i.Name); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAgentSkillSummaries = `-- name: ListAgentSkillSummaries :many
SELECT s.id, s.workspace_id, s.name, s.description, s.config, s.created_by, s.created_at, s.updated_at, ask.enabled
FROM skill s
JOIN agent_skill ask ON ask.skill_id = s.id
WHERE ask.agent_id = $1
ORDER BY s.name ASC
`
type ListAgentSkillSummariesRow struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
Name string `json:"name"`
Description string `json:"description"`
Config []byte `json:"config"`
CreatedBy pgtype.UUID `json:"created_by"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
Enabled bool `json:"enabled"`
}
// Summary variant for the agent skills list endpoint — omits `content` for
// the same reason as ListSkillSummariesByWorkspace.
func (q *Queries) ListAgentSkillSummaries(ctx context.Context, agentID pgtype.UUID) ([]ListAgentSkillSummariesRow, error) {
rows, err := q.db.Query(ctx, listAgentSkillSummaries, agentID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListAgentSkillSummariesRow{}
for rows.Next() {
var i ListAgentSkillSummariesRow
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.Enabled,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAgentSkills = `-- name: ListAgentSkills :many
SELECT s.id, s.workspace_id, s.name, s.description, s.content, s.config, s.created_by, s.created_at, s.updated_at, s.plugin_installation_id FROM skill s
JOIN agent_skill ask ON ask.skill_id = s.id
WHERE ask.agent_id = $1 AND ask.enabled = TRUE
ORDER BY s.name ASC
`
// Agent-Skill junction
func (q *Queries) ListAgentSkills(ctx context.Context, agentID pgtype.UUID) ([]Skill, error) {
rows, err := q.db.Query(ctx, listAgentSkills, agentID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Skill{}
for rows.Next() {
var i Skill
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAgentSkillsByWorkspace = `-- name: ListAgentSkillsByWorkspace :many
SELECT ask.agent_id, s.id, s.name, s.description, ask.enabled
FROM agent_skill ask
JOIN skill s ON s.id = ask.skill_id
WHERE s.workspace_id = $1
ORDER BY s.name ASC
`
type ListAgentSkillsByWorkspaceRow struct {
AgentID pgtype.UUID `json:"agent_id"`
ID pgtype.UUID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
}
func (q *Queries) ListAgentSkillsByWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]ListAgentSkillsByWorkspaceRow, error) {
rows, err := q.db.Query(ctx, listAgentSkillsByWorkspace, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListAgentSkillsByWorkspaceRow{}
for rows.Next() {
var i ListAgentSkillsByWorkspaceRow
if err := rows.Scan(
&i.AgentID,
&i.ID,
&i.Name,
&i.Description,
&i.Enabled,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSkillFileMetadata = `-- name: ListSkillFileMetadata :many
SELECT id, skill_id, path,
octet_length(content)::bigint AS size,
encode(sha256(convert_to(content, 'UTF8')), 'hex') AS content_hash,
created_at, updated_at
FROM skill_file
WHERE skill_id = $1
ORDER BY path ASC
`
type ListSkillFileMetadataRow struct {
ID pgtype.UUID `json:"id"`
SkillID pgtype.UUID `json:"skill_id"`
Path string `json:"path"`
Size int64 `json:"size"`
ContentHash string `json:"content_hash"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
// Metadata-only variant of ListSkillFiles: path, byte size and content hash
// without the body. Same reason as ListSkillSummariesByWorkspace — a skill
// whose supporting files total ~600KB cannot be listed at all when every row
// carries its full content, and the one command that would show which file is
// oversized was the command that timed out (GH multica-ai/multica#7498).
// size/hash are computed in Postgres so the file bodies never leave it.
//
// convert_to(content, 'UTF8'), never content::bytea: the cast runs the bytea
// INPUT parser over the text, so it reads backslash escapes instead of taking
// the bytes. A file containing `\x41` would hash as the single byte `A`, and
// one containing a bare backslash — a regex `\d+`, a Windows path, a LaTeX
// snippet — fails outright with "invalid input syntax for type bytea",
// turning an ordinary skill into a 500 on this endpoint.
func (q *Queries) ListSkillFileMetadata(ctx context.Context, skillID pgtype.UUID) ([]ListSkillFileMetadataRow, error) {
rows, err := q.db.Query(ctx, listSkillFileMetadata, skillID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListSkillFileMetadataRow{}
for rows.Next() {
var i ListSkillFileMetadataRow
if err := rows.Scan(
&i.ID,
&i.SkillID,
&i.Path,
&i.Size,
&i.ContentHash,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSkillFiles = `-- name: ListSkillFiles :many
SELECT id, skill_id, path, content, created_at, updated_at FROM skill_file
WHERE skill_id = $1
ORDER BY path ASC
`
// Skill File CRUD
func (q *Queries) ListSkillFiles(ctx context.Context, skillID pgtype.UUID) ([]SkillFile, error) {
rows, err := q.db.Query(ctx, listSkillFiles, skillID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SkillFile{}
for rows.Next() {
var i SkillFile
if err := rows.Scan(
&i.ID,
&i.SkillID,
&i.Path,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSkillFilesBySkillIDs = `-- name: ListSkillFilesBySkillIDs :many
SELECT id, skill_id, path, content, created_at, updated_at FROM skill_file
WHERE skill_id = ANY($1::uuid[])
ORDER BY skill_id, path ASC
`
// Batch variant of ListSkillFiles: loads every file for a set of skills in one
// round trip so LoadAgentSkills doesn't issue one query per skill on the
// task-claim hot path. Ordered by skill_id so the caller can group in a single
// linear pass. Like ListSkillFiles it returns full file bodies — callers that
// only need metadata must use ListSkillFileMetadata instead. Uses
// idx_skill_file_skill.
func (q *Queries) ListSkillFilesBySkillIDs(ctx context.Context, skillIds []pgtype.UUID) ([]SkillFile, error) {
rows, err := q.db.Query(ctx, listSkillFilesBySkillIDs, skillIds)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SkillFile{}
for rows.Next() {
var i SkillFile
if err := rows.Scan(
&i.ID,
&i.SkillID,
&i.Path,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSkillSummariesByWorkspace = `-- name: ListSkillSummariesByWorkspace :many
SELECT id, workspace_id, name, description, config, created_by, created_at, updated_at
FROM skill
WHERE workspace_id = $1
ORDER BY name ASC
`
type ListSkillSummariesByWorkspaceRow struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
Name string `json:"name"`
Description string `json:"description"`
Config []byte `json:"config"`
CreatedBy pgtype.UUID `json:"created_by"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
// Same as ListSkillsByWorkspace but omits the SKILL.md `content` column. Used
// by list endpoints (CLI table, web list page) where the body is never read;
// shipping it everywhere blew up payload size on workspaces with many skills
// and caused 15s CLI timeouts from high-latency regions (GH multica-ai/multica#2174).
func (q *Queries) ListSkillSummariesByWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]ListSkillSummariesByWorkspaceRow, error) {
rows, err := q.db.Query(ctx, listSkillSummariesByWorkspace, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListSkillSummariesByWorkspaceRow{}
for rows.Next() {
var i ListSkillSummariesByWorkspaceRow
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSkillsByWorkspace = `-- name: ListSkillsByWorkspace :many
SELECT id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id FROM skill
WHERE workspace_id = $1
ORDER BY name ASC
`
// Skill CRUD
func (q *Queries) ListSkillsByWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]Skill, error) {
rows, err := q.db.Query(ctx, listSkillsByWorkspace, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Skill{}
for rows.Next() {
var i Skill
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const removeAgentSkill = `-- name: RemoveAgentSkill :exec
DELETE FROM agent_skill
WHERE agent_id = $1 AND skill_id = $2
`
type RemoveAgentSkillParams struct {
AgentID pgtype.UUID `json:"agent_id"`
SkillID pgtype.UUID `json:"skill_id"`
}
func (q *Queries) RemoveAgentSkill(ctx context.Context, arg RemoveAgentSkillParams) error {
_, err := q.db.Exec(ctx, removeAgentSkill, arg.AgentID, arg.SkillID)
return err
}
const removeAllAgentSkills = `-- name: RemoveAllAgentSkills :exec
DELETE FROM agent_skill WHERE agent_id = $1
`
func (q *Queries) RemoveAllAgentSkills(ctx context.Context, agentID pgtype.UUID) error {
_, err := q.db.Exec(ctx, removeAllAgentSkills, agentID)
return err
}
const setAgentSkillEnabled = `-- name: SetAgentSkillEnabled :execrows
UPDATE agent_skill
SET enabled = $3
WHERE agent_id = $1 AND skill_id = $2
`
type SetAgentSkillEnabledParams struct {
AgentID pgtype.UUID `json:"agent_id"`
SkillID pgtype.UUID `json:"skill_id"`
Enabled bool `json:"enabled"`
}
func (q *Queries) SetAgentSkillEnabled(ctx context.Context, arg SetAgentSkillEnabledParams) (int64, error) {
result, err := q.db.Exec(ctx, setAgentSkillEnabled, arg.AgentID, arg.SkillID, arg.Enabled)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const updateSkill = `-- name: UpdateSkill :one
UPDATE skill SET
name = COALESCE($2, name),
description = COALESCE($3, description),
content = COALESCE($4, content),
config = COALESCE($5, config),
updated_at = now()
WHERE id = $1
RETURNING id, workspace_id, name, description, content, config, created_by, created_at, updated_at, plugin_installation_id
`
type UpdateSkillParams struct {
ID pgtype.UUID `json:"id"`
Name pgtype.Text `json:"name"`
Description pgtype.Text `json:"description"`
Content pgtype.Text `json:"content"`
Config []byte `json:"config"`
}
func (q *Queries) UpdateSkill(ctx context.Context, arg UpdateSkillParams) (Skill, error) {
row := q.db.QueryRow(ctx, updateSkill,
arg.ID,
arg.Name,
arg.Description,
arg.Content,
arg.Config,
)
var i Skill
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.Content,
&i.Config,
&i.CreatedBy,
&i.CreatedAt,
&i.UpdatedAt,
&i.PluginInstallationID,
)
return i, err
}
const upsertSkillFile = `-- name: UpsertSkillFile :one
INSERT INTO skill_file (skill_id, path, content)
VALUES ($1, $2, $3)
ON CONFLICT (skill_id, path) DO UPDATE SET
content = EXCLUDED.content,
updated_at = now()
RETURNING id, skill_id, path, content, created_at, updated_at
`
type UpsertSkillFileParams struct {
SkillID pgtype.UUID `json:"skill_id"`
Path string `json:"path"`
Content string `json:"content"`
}
func (q *Queries) UpsertSkillFile(ctx context.Context, arg UpsertSkillFileParams) (SkillFile, error) {
row := q.db.QueryRow(ctx, upsertSkillFile, arg.SkillID, arg.Path, arg.Content)
var i SkillFile
err := row.Scan(
&i.ID,
&i.SkillID,
&i.Path,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}