RecordLifecycleRequest was one function (gocognit 32) that opened its own transaction, validated the spec, read the content, took the lock, decided new against repeat and answered the repeat. It is now a short body over named steps, and the steps that run under the instance lock take the caller's queries and open nothing. Measured scores of the functions written: inOwnTx 1, checkAddress 2, content 4, lockLatest 2, recordNew 6, answerAddress 4, heldAt 3. RecordLifecycleRequest goes from 32 to 8, RecordFirstLifecycleRequest from 12 to 7 and LookupLifecycleRequest from 3 to 2 (golangci-lint's gocognit, measured at the parent and at this commit). The pinned complexity rule for RecordLifecycleRequest leaves .golangci.yml. heldAt answers both the recording's repeat and LookupLifecycleRequest from the same read. Only the two exported entry points open a transaction, through db.InTx, which begins with default options, rolls back on an error and commits on nil, as the code did. The statements, their order, the answers, the lock key and the error texts at run time are unchanged. RecordFirstLifecycleRequest still takes the lock and reads the latest generation, then calls RecordLifecycleRequest, which does both again. The comments of LockLifecycleInstance and InsertLifecycleRequest now say what recording runs and that no row back from the insert means the operation or key was refused; sqlc was regenerated and only comment lines differ in lifecycle_requests.sql.go and querier.go. The content method gains a doc comment. How no output changed: - go test over ./internal/integration and ./internal/integrations/ fedwiki/... gives 302 test and subtest names with the same results at the parent and here (301 pass, 1 skip, 0 fail); the lifecycle answer golden is byte-equal. No flake appeared. - The Temporal surface (activities, workflows, workflow bodies, payload types) is byte-equal to the parent's and to the baseline. TestFedWikiSitesWalkthrough passes at the parent and here. - Literals of lifecycle.go: the two address formats "record lifecycle request: instance %q generation %d" and "record first lifecycle request: instance %q generation %d" become "%s: instance %q generation %d", with "record lifecycle request" and "record first lifecycle request" as literals of their own (the texts printed are the same). "lock lifecycle instance: %w", "latest lifecycle generation: %w" and "read lifecycle request: %w" are written once each where they were written twice. The empty string literal is written five times where it was six, and the import path of internal/db is new. Nothing else moves. - 21 breaks per side, each caught: 12 run on both sides, 9 on the parent only and 9 here only, where the code the break edits moved. The lock key, sameLifecycleContent and the spec and answer types print the same at both sides; the only transaction opener left in lifecycle.go is the one in inOwnTx, and GetLifecycleRequestByAddress is called from heldAt only. - go run . lint prints the same at both sides; the lint snapshot adds and removes nothing. Not probed: a database error after the first statement (no test makes one fail and nothing else); the order in which RecordFirstLifecycleRequest reads the person (pinned by the answer golden, unchanged); the *sql.Conn path, which inOwnTx treats as a transaction as the code did and no caller uses.
485 lines
16 KiB
Go
485 lines
16 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.29.0
|
|
// source: lifecycle_requests.sql
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/lib/pq"
|
|
)
|
|
|
|
const acknowledgeLifecycleRequest = `-- name: AcknowledgeLifecycleRequest :execrows
|
|
UPDATE core.lifecycle_requests
|
|
SET acknowledged_at = NOW()
|
|
WHERE instance_id = $1::uuid
|
|
AND generation = $2::bigint
|
|
AND workspace_id = $3::uuid
|
|
AND status IN ('declined', 'failed')
|
|
AND acknowledged_at IS NULL
|
|
`
|
|
|
|
type AcknowledgeLifecycleRequestParams struct {
|
|
InstanceID string `json:"instance_id"`
|
|
Generation int64 `json:"generation"`
|
|
WorkspaceID string `json:"workspace_id"`
|
|
}
|
|
|
|
// The member dismissed a declined or failed outcome in their workspace.
|
|
func (q *Queries) AcknowledgeLifecycleRequest(ctx context.Context, arg AcknowledgeLifecycleRequestParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, acknowledgeLifecycleRequest, arg.InstanceID, arg.Generation, arg.WorkspaceID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const deleteUnstartedLifecycleRequest = `-- name: DeleteUnstartedLifecycleRequest :execrows
|
|
DELETE FROM core.lifecycle_requests
|
|
WHERE instance_id = $1::uuid
|
|
AND generation = $2::bigint
|
|
AND workspace_id = $3::uuid
|
|
AND status = 'accepted'
|
|
`
|
|
|
|
type DeleteUnstartedLifecycleRequestParams struct {
|
|
InstanceID string `json:"instance_id"`
|
|
Generation int64 `json:"generation"`
|
|
WorkspaceID string `json:"workspace_id"`
|
|
}
|
|
|
|
// A handler whose workflow start was refused with NotFound deletes the row it
|
|
// inserted, so the member's retry is a fresh insert. Only an accepted row in
|
|
// the caller's workspace is deleted.
|
|
func (q *Queries) DeleteUnstartedLifecycleRequest(ctx context.Context, arg DeleteUnstartedLifecycleRequestParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, deleteUnstartedLifecycleRequest, arg.InstanceID, arg.Generation, arg.WorkspaceID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const finishLifecycleRequest = `-- name: FinishLifecycleRequest :one
|
|
UPDATE core.lifecycle_requests
|
|
SET status = $1::text,
|
|
reason_code = $2::text,
|
|
finished_at = CASE WHEN status = 'accepted' THEN $3::timestamptz ELSE finished_at END
|
|
WHERE instance_id = $4::uuid
|
|
AND generation = $5::bigint
|
|
AND (status = 'accepted'
|
|
OR (status = $1::text AND reason_code IS NOT DISTINCT FROM $2::text))
|
|
RETURNING request_id
|
|
`
|
|
|
|
type FinishLifecycleRequestParams struct {
|
|
Status string `json:"status"`
|
|
ReasonCode sql.NullString `json:"reason_code"`
|
|
FinishedAt time.Time `json:"finished_at"`
|
|
InstanceID string `json:"instance_id"`
|
|
Generation int64 `json:"generation"`
|
|
}
|
|
|
|
// Record a request's outcome. Repeating the same outcome (a retried local
|
|
// activity whose first attempt committed) matches again and keeps the first
|
|
// finish time; a missing request, or one finished with another outcome,
|
|
// matches nothing and returns sql.ErrNoRows, which fails the activity.
|
|
func (q *Queries) FinishLifecycleRequest(ctx context.Context, arg FinishLifecycleRequestParams) (string, error) {
|
|
row := q.db.QueryRowContext(ctx, finishLifecycleRequest,
|
|
arg.Status,
|
|
arg.ReasonCode,
|
|
arg.FinishedAt,
|
|
arg.InstanceID,
|
|
arg.Generation,
|
|
)
|
|
var request_id string
|
|
err := row.Scan(&request_id)
|
|
return request_id, err
|
|
}
|
|
|
|
const getFirstLifecycleRequest = `-- name: GetFirstLifecycleRequest :one
|
|
SELECT request_id, instance_id, generation, connection_id, resource_key, workspace_id, operation, parameters, status, reason_code, workflow_id, requested_by_person_id, created_at, updated_at, finished_at, acknowledged_at FROM core.lifecycle_requests
|
|
WHERE instance_id = $1::uuid
|
|
ORDER BY generation
|
|
LIMIT 1
|
|
`
|
|
|
|
// An instance's first request. A create is always its instance's first
|
|
// request, so the JSON API finds a create by its instance alone (design D25).
|
|
func (q *Queries) GetFirstLifecycleRequest(ctx context.Context, instanceID string) (LifecycleRequest, error) {
|
|
row := q.db.QueryRowContext(ctx, getFirstLifecycleRequest, instanceID)
|
|
var i LifecycleRequest
|
|
err := row.Scan(
|
|
&i.RequestID,
|
|
&i.InstanceID,
|
|
&i.Generation,
|
|
&i.ConnectionID,
|
|
&i.ResourceKey,
|
|
&i.WorkspaceID,
|
|
&i.Operation,
|
|
&i.Parameters,
|
|
&i.Status,
|
|
&i.ReasonCode,
|
|
&i.WorkflowID,
|
|
&i.RequestedByPersonID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FinishedAt,
|
|
&i.AcknowledgedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getLifecycleRequestByAddress = `-- name: GetLifecycleRequestByAddress :one
|
|
SELECT request_id, instance_id, generation, connection_id, resource_key, workspace_id, operation, parameters, status, reason_code, workflow_id, requested_by_person_id, created_at, updated_at, finished_at, acknowledged_at FROM core.lifecycle_requests
|
|
WHERE instance_id = $1::uuid AND generation = $2::bigint
|
|
`
|
|
|
|
type GetLifecycleRequestByAddressParams struct {
|
|
InstanceID string `json:"instance_id"`
|
|
Generation int64 `json:"generation"`
|
|
}
|
|
|
|
func (q *Queries) GetLifecycleRequestByAddress(ctx context.Context, arg GetLifecycleRequestByAddressParams) (LifecycleRequest, error) {
|
|
row := q.db.QueryRowContext(ctx, getLifecycleRequestByAddress, arg.InstanceID, arg.Generation)
|
|
var i LifecycleRequest
|
|
err := row.Scan(
|
|
&i.RequestID,
|
|
&i.InstanceID,
|
|
&i.Generation,
|
|
&i.ConnectionID,
|
|
&i.ResourceKey,
|
|
&i.WorkspaceID,
|
|
&i.Operation,
|
|
&i.Parameters,
|
|
&i.Status,
|
|
&i.ReasonCode,
|
|
&i.WorkflowID,
|
|
&i.RequestedByPersonID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FinishedAt,
|
|
&i.AcknowledgedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getLifecycleRequesterSubject = `-- name: GetLifecycleRequesterSubject :one
|
|
SELECT u.oidc_subject
|
|
FROM core.lifecycle_requests lr
|
|
JOIN core.persons p ON p.person_id = lr.requested_by_person_id
|
|
JOIN core.users u ON u.user_id = p.user_id
|
|
WHERE lr.instance_id = $1::uuid
|
|
AND lr.generation = $2::bigint
|
|
`
|
|
|
|
type GetLifecycleRequesterSubjectParams struct {
|
|
InstanceID string `json:"instance_id"`
|
|
Generation int64 `json:"generation"`
|
|
}
|
|
|
|
// The identity-provider subject of the person who made a request, read when
|
|
// a workflow must compare it with an application's owner record without
|
|
// carrying it in its input (design D27's create reconcile).
|
|
func (q *Queries) GetLifecycleRequesterSubject(ctx context.Context, arg GetLifecycleRequesterSubjectParams) (string, error) {
|
|
row := q.db.QueryRowContext(ctx, getLifecycleRequesterSubject, arg.InstanceID, arg.Generation)
|
|
var oidc_subject string
|
|
err := row.Scan(&oidc_subject)
|
|
return oidc_subject, err
|
|
}
|
|
|
|
const insertLifecycleRequest = `-- name: InsertLifecycleRequest :one
|
|
INSERT INTO core.lifecycle_requests
|
|
(instance_id, generation, connection_id, resource_key, workspace_id,
|
|
operation, parameters, workflow_id, requested_by_person_id)
|
|
SELECT $1::uuid,
|
|
$2::bigint,
|
|
pc.connection_id,
|
|
rk.resource_key,
|
|
$3::uuid,
|
|
po.operation,
|
|
$4::jsonb,
|
|
$5::text,
|
|
$6::uuid
|
|
FROM core.provider_connections pc
|
|
JOIN core.provider_operations po
|
|
ON po.provider = pc.provider AND po.operation = $7::text
|
|
JOIN core.resource_keys rk
|
|
ON rk.provider = pc.provider AND rk.resource_key = $8::text
|
|
WHERE pc.connection_id = $9::uuid
|
|
ON CONFLICT (instance_id, generation) DO NOTHING
|
|
RETURNING request_id
|
|
`
|
|
|
|
type InsertLifecycleRequestParams struct {
|
|
InstanceID string `json:"instance_id"`
|
|
Generation int64 `json:"generation"`
|
|
WorkspaceID string `json:"workspace_id"`
|
|
Parameters json.RawMessage `json:"parameters"`
|
|
WorkflowID string `json:"workflow_id"`
|
|
RequestedByPersonID uuid.NullUUID `json:"requested_by_person_id"`
|
|
Operation string `json:"operation"`
|
|
ResourceKey string `json:"resource_key"`
|
|
ConnectionID string `json:"connection_id"`
|
|
}
|
|
|
|
// Insert a request at its address. The operation must be one the
|
|
// connection's provider declares in core.provider_operations, and the
|
|
// provider must own the resource key; otherwise nothing is inserted.
|
|
// A held address also inserts nothing (ON CONFLICT on the address only). The
|
|
// recording inserts only above the instance's latest generation, under its
|
|
// lock, so there no row back means the operation or key was refused. A second
|
|
// accepted request on the instance under another generation raises a unique
|
|
// violation on uq_lifecycle_requests_one_open.
|
|
func (q *Queries) InsertLifecycleRequest(ctx context.Context, arg InsertLifecycleRequestParams) (string, error) {
|
|
row := q.db.QueryRowContext(ctx, insertLifecycleRequest,
|
|
arg.InstanceID,
|
|
arg.Generation,
|
|
arg.WorkspaceID,
|
|
arg.Parameters,
|
|
arg.WorkflowID,
|
|
arg.RequestedByPersonID,
|
|
arg.Operation,
|
|
arg.ResourceKey,
|
|
arg.ConnectionID,
|
|
)
|
|
var request_id string
|
|
err := row.Scan(&request_id)
|
|
return request_id, err
|
|
}
|
|
|
|
const latestLifecycleGeneration = `-- name: LatestLifecycleGeneration :one
|
|
SELECT COALESCE(MAX(generation), 0)::bigint AS latest_generation
|
|
FROM core.lifecycle_requests
|
|
WHERE instance_id = $1::uuid
|
|
`
|
|
|
|
// The highest generation recorded for an instance, 0 when it has none.
|
|
func (q *Queries) LatestLifecycleGeneration(ctx context.Context, instanceID string) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, latestLifecycleGeneration, instanceID)
|
|
var latest_generation int64
|
|
err := row.Scan(&latest_generation)
|
|
return latest_generation, err
|
|
}
|
|
|
|
const listAcceptedLifecycleRequestsOlderThan = `-- name: ListAcceptedLifecycleRequestsOlderThan :many
|
|
SELECT request_id, instance_id, generation, connection_id, resource_key, workspace_id, operation, parameters, status, reason_code, workflow_id, requested_by_person_id, created_at, updated_at, finished_at, acknowledged_at FROM core.lifecycle_requests
|
|
WHERE connection_id = $1::uuid
|
|
AND status = 'accepted'
|
|
AND created_at < NOW() - ($2::bigint * INTERVAL '1 second')
|
|
ORDER BY created_at, request_id
|
|
`
|
|
|
|
type ListAcceptedLifecycleRequestsOlderThanParams struct {
|
|
ConnectionID string `json:"connection_id"`
|
|
AgeSeconds int64 `json:"age_seconds"`
|
|
}
|
|
|
|
// The boot pass (design D27): accepted requests at one connection older than
|
|
// the given age, whose workflow may never have started or may have closed
|
|
// without recording an outcome.
|
|
func (q *Queries) ListAcceptedLifecycleRequestsOlderThan(ctx context.Context, arg ListAcceptedLifecycleRequestsOlderThanParams) ([]LifecycleRequest, error) {
|
|
rows, err := q.db.QueryContext(ctx, listAcceptedLifecycleRequestsOlderThan, arg.ConnectionID, arg.AgeSeconds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []LifecycleRequest{}
|
|
for rows.Next() {
|
|
var i LifecycleRequest
|
|
if err := rows.Scan(
|
|
&i.RequestID,
|
|
&i.InstanceID,
|
|
&i.Generation,
|
|
&i.ConnectionID,
|
|
&i.ResourceKey,
|
|
&i.WorkspaceID,
|
|
&i.Operation,
|
|
&i.Parameters,
|
|
&i.Status,
|
|
&i.ReasonCode,
|
|
&i.WorkflowID,
|
|
&i.RequestedByPersonID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FinishedAt,
|
|
&i.AcknowledgedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const lockLifecycleInstance = `-- name: LockLifecycleInstance :exec
|
|
SELECT pg_advisory_xact_lock(hashtextextended('lifecycle:' || $1::uuid::text, 0))
|
|
`
|
|
|
|
// Lifecycle requests (design D24 to D28). Recording a request runs, in one
|
|
// transaction: LockLifecycleInstance, LatestLifecycleGeneration, then for a
|
|
// generation above the latest GenerationCeiling, OpenLifecycleGeneration and
|
|
// InsertLifecycleRequest, and otherwise GetLifecycleRequestByAddress, so the
|
|
// latest-generation check and the insert are one step per instance.
|
|
func (q *Queries) LockLifecycleInstance(ctx context.Context, instanceID string) error {
|
|
_, err := q.db.ExecContext(ctx, lockLifecycleInstance, instanceID)
|
|
return err
|
|
}
|
|
|
|
const mintInstanceID = `-- name: MintInstanceID :one
|
|
SELECT uuidv7()::uuid AS instance_id
|
|
`
|
|
|
|
// A new instance's identifier comes from the database, the only source of
|
|
// UUIDs (design D25).
|
|
func (q *Queries) MintInstanceID(ctx context.Context) (string, error) {
|
|
row := q.db.QueryRowContext(ctx, mintInstanceID)
|
|
var instance_id string
|
|
err := row.Scan(&instance_id)
|
|
return instance_id, err
|
|
}
|
|
|
|
const mintLifecycleGeneration = `-- name: MintLifecycleGeneration :one
|
|
SELECT core.next_generation(NULL, NULL)::bigint AS generation
|
|
`
|
|
|
|
// A generation for a request on an instance that has no request yet,
|
|
// floored by the clock (design D25).
|
|
func (q *Queries) MintLifecycleGeneration(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, mintLifecycleGeneration)
|
|
var generation int64
|
|
err := row.Scan(&generation)
|
|
return generation, err
|
|
}
|
|
|
|
const nextLifecycleGenerations = `-- name: NextLifecycleGenerations :many
|
|
SELECT ids.instance_id::uuid AS instance_id,
|
|
core.next_generation(MAX(lr.generation), NULL)::bigint AS next_generation
|
|
FROM unnest($1::uuid[]) AS ids(instance_id)
|
|
LEFT JOIN core.lifecycle_requests lr ON lr.instance_id = ids.instance_id
|
|
GROUP BY ids.instance_id
|
|
`
|
|
|
|
type NextLifecycleGenerationsRow struct {
|
|
InstanceID string `json:"instance_id"`
|
|
NextGeneration int64 `json:"next_generation"`
|
|
}
|
|
|
|
// The generation each instance's next request carries: above its latest
|
|
// request and at least the clock, one row per instance asked for.
|
|
func (q *Queries) NextLifecycleGenerations(ctx context.Context, instanceIds []string) ([]NextLifecycleGenerationsRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, nextLifecycleGenerations, pq.Array(instanceIds))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []NextLifecycleGenerationsRow{}
|
|
for rows.Next() {
|
|
var i NextLifecycleGenerationsRow
|
|
if err := rows.Scan(&i.InstanceID, &i.NextGeneration); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const openLifecycleGeneration = `-- name: OpenLifecycleGeneration :one
|
|
SELECT COALESCE(MAX(generation), 0)::bigint AS open_generation
|
|
FROM core.lifecycle_requests
|
|
WHERE instance_id = $1::uuid AND status = 'accepted'
|
|
`
|
|
|
|
// The generation of the instance's accepted request, 0 when none is open.
|
|
// Read under LockLifecycleInstance, so the one-open rule is answered before
|
|
// the insert instead of by a unique violation that would abort the caller's
|
|
// transaction.
|
|
func (q *Queries) OpenLifecycleGeneration(ctx context.Context, instanceID string) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, openLifecycleGeneration, instanceID)
|
|
var open_generation int64
|
|
err := row.Scan(&open_generation)
|
|
return open_generation, err
|
|
}
|
|
|
|
const openLifecycleRequestsByWorkspace = `-- name: OpenLifecycleRequestsByWorkspace :many
|
|
SELECT lr.request_id, lr.instance_id, lr.generation, lr.connection_id, lr.resource_key, lr.workspace_id, lr.operation, lr.parameters, lr.status, lr.reason_code, lr.workflow_id, lr.requested_by_person_id, lr.created_at, lr.updated_at, lr.finished_at, lr.acknowledged_at FROM core.lifecycle_requests lr
|
|
WHERE lr.workspace_id = $1::uuid
|
|
AND lr.connection_id = $2::uuid
|
|
AND (lr.status = 'accepted'
|
|
OR (lr.status IN ('declined', 'failed')
|
|
AND lr.acknowledged_at IS NULL
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM core.lifecycle_requests later
|
|
WHERE later.instance_id = lr.instance_id
|
|
AND later.generation > lr.generation)))
|
|
ORDER BY lr.created_at, lr.request_id
|
|
`
|
|
|
|
type OpenLifecycleRequestsByWorkspaceParams struct {
|
|
WorkspaceID string `json:"workspace_id"`
|
|
ConnectionID string `json:"connection_id"`
|
|
}
|
|
|
|
// What a workspace's list shows for one connection: every accepted request,
|
|
// and per instance the latest request when it is declined or failed and not
|
|
// acknowledged (a later request on the instance supersedes it).
|
|
func (q *Queries) OpenLifecycleRequestsByWorkspace(ctx context.Context, arg OpenLifecycleRequestsByWorkspaceParams) ([]LifecycleRequest, error) {
|
|
rows, err := q.db.QueryContext(ctx, openLifecycleRequestsByWorkspace, arg.WorkspaceID, arg.ConnectionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []LifecycleRequest{}
|
|
for rows.Next() {
|
|
var i LifecycleRequest
|
|
if err := rows.Scan(
|
|
&i.RequestID,
|
|
&i.InstanceID,
|
|
&i.Generation,
|
|
&i.ConnectionID,
|
|
&i.ResourceKey,
|
|
&i.WorkspaceID,
|
|
&i.Operation,
|
|
&i.Parameters,
|
|
&i.Status,
|
|
&i.ReasonCode,
|
|
&i.WorkflowID,
|
|
&i.RequestedByPersonID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.FinishedAt,
|
|
&i.AcknowledgedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|