fix(datastore): write gitlab_user_id on hosted maven and npm versions
Summary
Every hosted Maven and npm version serves created_by: null. The columns, the OpenAPI field, and the management-API serializer exist, but nothing writes gitlab_user_id on either table. The monolith's version list (S14) and sidebar (S06) render Source from it. The fix reads auth.IdentityFromContext(ctx).Subject.LocalID at the two publish handlers and binds it into the two version INSERTs.
gitlab_project_id and gitlab_git_commit_sha stay null. Today's token has no carrier: userauth.GitLabClaims has five fields, none CI-related, and ADR-021 collapses every credential type to the User principal. Writing them waits on an ADR-020 amendment and an IAM token-issuer change, both outside this repo.
What a reviewer should not have to infer:
- First publish wins. The value rides the INSERT
VALUESonly and bothON CONFLICTarms are untouched, so the column names the request that created the version row. On Maven that row commits on the pool before the body streams, so an aborted first upload keeps its user. S10 Step 4c now states this as the contract. - The accessor bounds the value and never fails a publish.
(*auth.Identity).GitLabUserID()returns nil for an anonymous identity, for a principal outsiderelationships.OriginOrganization(a bare federated id would collide with an organization one, and the column carries no origin), and for an empty, over-255-byte, NUL-bearing, or invalid-UTF-8 id. The CHECK ischar_length <= 255, so a byte bound is strictly tighter for valid UTF-8. Invalid UTF-8 would otherwise fail the INSERT with SQLSTATE 22021. - Attribution is metered where the row is written.
gitlab_artifact_registry_maven_versions_created_totaland its npm twin book each created version row underattributed="true"or"false", Maven on the INSERT arm only and npm on every 201, andpublisherFromContextlogs a WARN and proceeds unattributed when the identity holder is missing. Under the bootstrap stub every row booksfalse, which the twodocs/dev/observability.mdrows name as the expected reading. FindOrCreateVersiongains a positional*stringacross 13 call sites. ItsRETURNINGstill does not project the column, so the tests read it back through a separate query.authtest.Anonymousis a second plain-equality validator for the bootstrap arm. The existingno-authtest-in-proddepguard rule keeps it out of production, and itsdescnow names both validators.
Size
1046 reviewable LOC across 40 files (git diff --numstat origin/main...HEAD at 17d140ad4), over the 500-LOC guardrail.
| Group | Files | LOC |
|---|---|---|
| Test Go | 22 | 768 |
Test-support Go without a build tag (internal/auth/authtest/fake.go) |
1 | 65 |
| Production Go | 10 | 145 |
| Docs (S10, S17, observability, two e2e catalogs) | 5 | 42 |
.golangci.yaml |
1 | 9 |
api/openapi/v1.yaml |
1 | 17 |
Tests and test support are 833 of the 1046 (80%). The production delta is the accessor, two VALUES entries, two field declarations, two handler reads through publisherFromContext, one adapter pass-through, and the two versions_created_total collectors with their cardinality entries. Splitting tests from the fix breaks the test-first pairing, and splitting Maven from npm leaves the shared accessor's second caller unreached. Eight test files carry only the one-token call-site edit the positional parameter forces, and another changes one assertion message.
Merge order
fix(datastore): fence the npm publish and conta... (!2268 - merged) • Pawel Rozlach • 19.4 and fix(maven): register and enqueue metadata recon... (!2297 - merged) • Moaz Khalifa • 19.4 both landed first. The rebase kept both sides of the one conflicting hunk, CommitPublish's commitCtx beside this MR's seventh commitInput argument. Main's tombstone-reaper test also gained a FindOrCreateVersion call after this branch widened the signature, so the rebased tree compiled alone and failed the datastore integration jobs until that call gained its argument.
Governing ADRs
ADR-007 defines both columns as nullable text capped at 255 characters. The write stays inside the cap, and there is no schema change. ADR-020 fixes the token payload: gitlab.local_id is the user id the accessor reads, and the five-member gitlab object carries no project or commit. ADR-021 resolves every credential type to the same User principal, so the credential cannot tell a CI publish from a human one. No deviation.
Testing
Gates run at 17d140ad4:
go build ./...andgo vet -tags=integration ./....go testoninternal/auth/...,internal/datastore,internal/format/maven,internal/format/npm,internal/metrics, andcmd/artifact-registry.- The Maven and npm integration suites and the datastore version and reaper suites with
-tags=integration, against apostgres:17-alpinestarted with CI's.pg-service-options. golangci-lint run --build-tags=integration --max-same-issues=0 --max-issues-per-linter=0 --uniq-by-line=falseon the touched Go packages: 0 findings on added lines. The default lint oncmd/artifact-registryis clean.scripts/ci/check-comment-caps.sh --base origin/main.- markdownlint and Vale on the five docs, lychee through the pre-commit hook.
EXPLAINon the Maven INSERT is byte-identical with and without the column:Insert on maven_versions / Conflict Resolution: NOTHING / Conflict Arbiter Indexes: unique_maven_versions_ns_id_pkg_id_version -> Result.
AC-19, reviewer-runnable. No added Go line names the two unwritten columns:
git diff origin/main...HEAD -- '*.go' | grep -E '^\+' | grep -iE 'gitlab_project_id|GitlabProjectID|git_commit_sha|GitlabGitCommitSha'e2e catalogs (guardrail 12): docs/testing/e2e/maven.md and docs/testing/e2e/npm.md gain an e2e.<format>.discover.version-publisher row under Discover, blocked because the rig's bootstrap token establishes no user.
Spec coverage
Specs: docs/specs/S17-rest-management-api.md ("Artifact resources (Phase 3)") and docs/specs/S10-maven-hosted.md (Step 4c, Out of Scope).
| # | Criterion | Tests | File |
|---|---|---|---|
| AC-1 | The accessor yields nil for a nil receiver, AnonymousIdentity(), the zero Identity{}, and a LocalID under OriginUnspecified |
TestIdentity_GitLabUserID cases nil identity, anonymous, zero value, mis-originated |
internal/auth/auth_test.go |
| AC-2 | An organization-origin "42" yields "42", and a federated origin or an empty LocalID yields nil |
TestIdentity_GitLabUserID cases organization 42, federated 42, empty local id |
internal/auth/auth_test.go |
| AC-3 | Bound: 255 ASCII bytes survive, and 256 bytes, a NUL byte, invalid UTF-8, and 255 two-byte runes (510 bytes) yield nil | TestIdentity_GitLabUserID cases 255 bytes, 256 bytes, nul byte, invalid utf-8, 255 runes over the byte bound |
internal/auth/auth_test.go |
| AC-4 | authtest.Anonymous accepts its token as anonymous, rejects a mismatched, empty, prefix, and suffix token, and an empty configured token fails the test |
TestAnonymous (5 cases), TestAnonymous_RejectsEmptyToken |
internal/auth/authtest/fake_test.go |
| AC-5 | The Maven store writes the publisher on the INSERT arm and the conflict arm leaves it: "42" creates, "43" finds, and the column reads "42" both times through FindMavenVersionByID |
TestMavenVersionStore_FindOrCreateVersion subtest writes the publisher on the insert arm and the conflict arm leaves it |
internal/datastore/maven_versions_integration_test.go |
| AC-6 | The Maven store binds NULL for a nil publisher | TestMavenVersionStore_FindOrCreateVersion subtest binds NULL for a nil publisher |
internal/datastore/maven_versions_integration_test.go |
| AC-7 | The Maven coordinate guards still reject each bad input with the widened signature | TestMavenVersionStore_VersionCoordinateGuards |
internal/datastore/maven_versions_test.go |
| AC-8 | The npm store writes the publisher, and the package's version list reads it back | TestNpmVersionStore_InsertNpmVersion subtest inserts a new version and returns its id |
internal/datastore/npm_write_integration_test.go |
| AC-9 | A second npm insert of the same version by "43" returns ErrNpmVersionExists and the row still reads "42" |
TestNpmVersionStore_InsertNpmVersion subtest a second insert of the same version returns ErrNpmVersionExists and writes no row |
internal/datastore/npm_write_integration_test.go |
| AC-10 | The npm store binds NULL for a nil publisher | TestNpmVersionStore_ListNpmVersionsByPackage subtest projects the resource columns and omits package_json, assertion a publish that established no user reads null attribution |
internal/datastore/npm_versions_list_integration_test.go |
| AC-11 | An authenticated Maven primary-file PUT answers 201 and the version row reads the user id | TestUpload_RecordsPublisher case authenticated |
internal/format/maven/upload_test.go |
| AC-12 | An anonymous Maven publish writes NULL, and a 256-byte, NUL-bearing, or invalid-UTF-8 id rides Basic's base64 and writes NULL rather than 500 | TestUpload_RecordsPublisher cases anonymous, over-long id, nul byte, invalid utf-8 |
internal/format/maven/upload_test.go |
| AC-13 | Sidecar and later Maven uploads by "43" leave "42" on the one live version, every intermediate status asserted with require |
TestUpload_LaterUploadsKeepTheFirstPublisher |
internal/format/maven/upload_test.go |
| AC-14 | The Maven fake seam compiles and the 500 path is unchanged | TestServePrimaryUpload_StoreError_Yields500 |
internal/format/maven/upload_internal_test.go |
| AC-15 | An authenticated npm publish answers 201 and npm_versions.gitlab_user_id reads the user id |
TestPublishCommitIntegration_RecordsPublisher case authenticated |
internal/format/npm/publish_commit_integration_test.go |
| AC-16 | An anonymous npm publish writes NULL, and a 256-byte or invalid-UTF-8 id rides the Bearer header and writes NULL rather than 500 | TestPublishCommitIntegration_RecordsPublisher cases anonymous, over-long id, invalid utf-8 |
internal/format/npm/publish_commit_integration_test.go |
| AC-17 | An npm republish by "43" answers 409 and the row still reads "42", every intermediate status asserted with require |
TestPublishCommitIntegration_RepublishKeepsTheFirstPublisher |
internal/format/npm/publish_commit_integration_test.go |
| AC-18 | The management API serializes the stored id as created_by |
Existing TestVersionDetailHandler_SerializesPublishAttribution and TestVersionFileHandlersIntegration_MavenWalksAndDetails (created_by reads the stored gitlab_user_id), cited, not duplicated |
internal/managementapi/version_file_handlers_test.go, internal/managementapi/version_file_read_handlers_integration_test.go |
| AC-19 | No writer, parameter, field, or stub for gitlab_project_id or gitlab_git_commit_sha |
The grep above returns no line | n/a |
| AC-20 | The production Maven adapter binds the id through to the store as a statement argument, so dropping it in wire_maven.go fails a unit test |
TestMavenStoreAdapterFindOrCreateVersion_BindsPublisher |
cmd/artifact-registry/wire_maven_adapter_test.go |
| AC-21 | The npm handler forwards the id into NpmPublishCommitInput.GitlabUserID at the committer seam |
TestPublish_ForwardsPublisherToCommitter |
internal/format/npm/publish_usagedata_test.go |
| AC-22 | Each created version row is booked under versions_created_total{attributed}, Maven on the created arm only and npm on every 201, and a later Maven upload or a refused npm republish books nothing |
TestUpload_RecordsPublisher, TestUpload_LaterUploadsKeepTheFirstPublisher, TestPublishCommitIntegration_RecordsPublisher, TestPublishCommitIntegration_RepublishKeepsTheFirstPublisher |
internal/format/maven/upload_test.go, internal/format/npm/publish_commit_integration_test.go |
| AC-23 | A request with no identity holder commits with NULL attribution, books attributed="false", and logs a WARN rather than answering 500 |
TestUpload_NoIdentityHolder_RecordsNoPublisherAndWarns, TestPublish_NoIdentityHolder_RecordsNoPublisherAndWarns |
internal/format/maven/upload_test.go, internal/format/npm/publish_usagedata_test.go |
| AC-24 | Both format registries pass the cardinality audit with the attributed set emitted, and each package's exposure list names the new series |
TestRegisterMetrics_PassesCardinalityAudit (both packages), TestRegisterMetrics_ExposedViaGatherer, TestRegisterMetrics_ExposesAllOperationalVectors |
internal/format/maven/metrics_test.go, internal/format/maven/buffered_test.go, internal/format/npm/metrics_test.go |
Error cases: the cited spec passages name none of their own. An unstorable id (empty, over 255 bytes, NUL-bearing, or invalid UTF-8) binds NULL and never fails the publish: AC-3 at the accessor, AC-12 and AC-16 at the handlers for the shapes a credential can carry. Measured against net/http: a NUL byte cannot ride a Bearer header (the client refuses it and the server answers 400), so on npm it is accessor-only, while invalid UTF-8 rides Bearer as obs-text and both shapes ride Basic's base64. The empty id is accessor-only on both arms, because ExtractCredential refuses a zero-length credential.
Security: the recorded publisher comes only from the identity the auth chain established, never from a client-supplied field. AC-11, AC-13, AC-15, and AC-17 exercise the real auth.IdentityHolderMiddleware and auth.Middleware pair with one wrapped handler per credential, so a second publisher cannot pass as the first.
Context for LLM agents
Rationale
- Fill
gitlab_user_idon Maven's conflict arm when null. Rejected: it attributes an old version to whoever next uploads a file into it. - Write attribution inside the file-commit transaction. Rejected: it re-litigates S10's pool-then-commit design, where the version row commits before the body streams, and that design is a non-goal here.
- A
FindOrCreateMavenVersionInputstruct instead of the positional parameter. Rejected: the same 12 call sites change either way, and the struct turns a fix into a seam refactor. (string, bool)instead of*stringfrom the accessor. Rejected: both callers would convert and restate the bound the accessor exists to hold once.- Chosen:
*stringend to end, bounded once inauth.Identity.GitLabUserID, bound intoVALUESthrough the existingnullableStringExpr.
Non-goals
gitlab_project_idandgitlab_git_commit_sha. No carrier in the token. Thebyteacolumn holds raw digest bytes (20 for SHA-1, 32 for SHA-256) serialized byhex.EncodeToString, so a future writer hex-decodes and rejects a non-hex or wrong-length input.- Metering inside
internal/auth.local_idis an IAM-issued stringified int64, so the accessor's drop arm is unreachable from a conforming issuer, and a counter in a pure accessor is the wrong layer. The format packages book the outcome instead, where the row is written, so an issuer or wiring change that stops attribution landing moves a signal. - Failing the publish when
IdentityFromContextreturnsok == false. Unreachable on the current wiring, since the holder is seeded outside auth for every request and both routes sit insideauth.Middleware, sopublisherFromContextlogs a WARN and proceeds unattributed rather than answering 500. - A negative-control probe for the
no-authtest-in-proddepguard rule. The rule pre-exists and its mechanics are untouched. - S11. Its silence on attribution is not a contradiction, and it is another owner's spec.
- Container/OCI.
container_manifestshas no attribution columns.
Deployment
On the AR_BOOTSTRAP_TOKEN stub every publish is anonymous and created_by stays null. An observable change needs auth.token_exchange on the target.
Follow-ups, not filed here
repositories.gitlab_created_by_user_idhas the identical never-written gap, and S17's S08 row is stale on it.- ADR-007 describes
container_manifestsattribution columns no migration created. - The ADR-020 CI-claims amendment plus the IAM issuer change that unblocks the two null columns.
- Federated-origin publishers record no attribution: the accessor admits only the organization origin, and widening it needs a column that carries the origin.
- No startup log names the live validator.
- Both catalogs' Authentication intros still say scenarios run under the bootstrap token.
- Rows created before the fix cannot be backfilled.
Review residue
The test(datastore) commit body names a seedRepoPackage closure a later commit folded into seedPackage. Historical, and the squash removes it. The two docs/plans/ findings from /review-branch are accepted deviations of the working-plan pipeline this MR came from.
Database Review Evidence
Queries
Note
Plans are from EXPLAIN (ANALYZE, BUFFERS) against an ephemeral
PostgreSQL 17 container (matching GL_PG_CURR_VERSION from
.gitlab-ci-other-versions.yml), with synthesized seed data rolled
back per query and the container torn down at the end of the run.
Numbers reflect moderate cardinality and do not capture
production-scale effects. See
Database review evidence
for seed sizing, methodology, and the anomalies the skill flags.
Expand each row's details for the seed shape, rendered SQL, bound args,
and raw plan.
| Method | Plan node | Index | Rows (plan / actual) | Cost | Time | Buffers (hit / read) | Partitions |
|---|---|---|---|---|---|---|---|
datastore.MavenVersionStore.FindOrCreateVersion.UserSet |
Insert | unique_maven_versions_ns_id_pkg_id_version (arbiter) |
1 / 1 | 0.01 | 0.581ms | 112 / 1 | 1/64 |
datastore.MavenVersionStore.FindOrCreateVersion.UserNil |
Insert | unique_maven_versions_ns_id_pkg_id_version (arbiter) |
1 / 1 | 0.01 | 0.482ms | 84 / 1 | 1/64 |
datastore.NpmVersionStore.InsertNpmVersion.UserSet |
Insert | unique_npm_versions_ns_id_pkg_id_version (arbiter) |
1 / 1 | 0.01 | 0.468ms | 104 / 1 | 1/64 |
datastore.NpmVersionStore.InsertNpmVersion.UserNil |
Insert | unique_npm_versions_ns_id_pkg_id_version (arbiter) |
1 / 1 | 0.01 | 0.428ms | 70 / 1 | 1/64 |
datastore.MavenVersionStore.FindOrCreateVersion.UserSet
Summary: Single-row Insert with unique_maven_versions_ns_id_pkg_id_version as the conflict arbiter, routed to one partition (maven_versions_p13 by tableoid). The plan text is byte-identical to the merge-base statement without gitlab_user_id, under EXPLAIN (COSTS OFF) and with costs (width=208 in both). A second EXECUTE on the same coordinates bound to '43' reports Conflicting Tuples: 1 and the row still reads '42'. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=1
Rendered SQL:
INSERT INTO public.maven_versions (id, namespace_id, maven_package_id, version, gitlab_user_id)
VALUES ($1::uuid, $2::uuid, $3::uuid, $4::text, $5::text)
ON CONFLICT (namespace_id, maven_package_id, version) WHERE soft_deleted_at IS NULL DO NOTHING
RETURNING maven_versions.namespace_id AS "maven_versions.namespace_id",
maven_versions.id AS "maven_versions.id",
maven_versions.maven_package_id AS "maven_versions.maven_package_id",
maven_versions.version AS "maven_versions.version";Bound args: [394d7237-c918-7b26-9c15-0a6a59f3fbc3, edbaad9e-2b6c-7e15-b11a-82602c51cc93, ad9bf990-bc02-7e67-9ebb-0748771f7d34, 1.0.0, 42]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Insert on maven_versions (cost=0.00..0.01 rows=1 width=208) (actual time=0.581..0.581 rows=1 loops=1)
Conflict Resolution: NOTHING
Conflict Arbiter Indexes: unique_maven_versions_ns_id_pkg_id_version
Tuples Inserted: 1
Conflicting Tuples: 0
Buffers: shared hit=112 read=1 dirtied=17 written=9
-> Result (cost=0.00..0.01 rows=1 width=208) (actual time=0.001..0.002 rows=1 loops=1)
Planning:
Buffers: shared hit=163
Planning Time: 0.475 ms
Trigger for constraint fk_maven_versions_maven_package_id_maven_packages on maven_versions_p13: time=1.146 calls=1
Trigger for constraint fk_maven_versions_namespace_id_namespaces on maven_versions_p13: time=0.299 calls=1
Execution Time: 2.563 msTimings: planning 0.475ms, execution 2.563ms, total 3.038ms.
datastore.MavenVersionStore.FindOrCreateVersion.UserNil
Summary: Same plan as the bound branch with NULL::text inline: single-row Insert, arbiter unique_maven_versions_ns_id_pkg_id_version, routed to maven_versions_p00. The stored gitlab_user_id reads NULL. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=1
Rendered SQL:
INSERT INTO public.maven_versions (id, namespace_id, maven_package_id, version, gitlab_user_id)
VALUES ($1::uuid, $2::uuid, $3::uuid, $4::text, NULL::text)
ON CONFLICT (namespace_id, maven_package_id, version) WHERE soft_deleted_at IS NULL DO NOTHING
RETURNING maven_versions.namespace_id AS "maven_versions.namespace_id",
maven_versions.id AS "maven_versions.id",
maven_versions.maven_package_id AS "maven_versions.maven_package_id",
maven_versions.version AS "maven_versions.version";Bound args: [cae910a4-fb1c-75ec-8cb8-75a817250af5, 902c1a9e-8153-72e9-adde-6ebda4ce2a96, f65597ce-ac86-7f7a-b98b-8f92986044a5, 1.0.0]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Insert on maven_versions (cost=0.00..0.01 rows=1 width=208) (actual time=0.481..0.482 rows=1 loops=1)
Conflict Resolution: NOTHING
Conflict Arbiter Indexes: unique_maven_versions_ns_id_pkg_id_version
Tuples Inserted: 1
Conflicting Tuples: 0
Buffers: shared hit=84 read=1 dirtied=17 written=9
-> Result (cost=0.00..0.01 rows=1 width=208) (actual time=0.001..0.001 rows=1 loops=1)
Planning:
Buffers: shared hit=156
Planning Time: 0.459 ms
Trigger for constraint fk_maven_versions_maven_package_id_maven_packages on maven_versions_p00: time=0.578 calls=1
Trigger for constraint fk_maven_versions_namespace_id_namespaces on maven_versions_p00: time=0.103 calls=1
Execution Time: 1.268 msTimings: planning 0.459ms, execution 1.268ms, total 1.727ms.
datastore.NpmVersionStore.InsertNpmVersion.UserSet
Summary: Single-row Insert with unique_npm_versions_ns_id_pkg_id_version as the conflict arbiter, routed to one partition (npm_versions_p29 by tableoid). The plan text is byte-identical to the merge-base statement without gitlab_user_id, under EXPLAIN (COSTS OFF) and with costs (width=240 in both). A second EXECUTE on the same coordinates bound to '43' reports Conflicting Tuples: 1 and the row still reads '42'. No anomalies.
Seed shape: namespaces=1, repositories=1, npm_repositories=1, npm_packages=1
Rendered SQL:
INSERT INTO public.npm_versions (id, namespace_id, npm_package_id, version, size_bytes, package_json, gitlab_user_id)
VALUES ($1::uuid, $2::uuid, $3::uuid, $4::text, $5, $6::text::jsonb, $7::text)
ON CONFLICT (namespace_id, npm_package_id, version) WHERE soft_deleted_at IS NULL DO NOTHING
RETURNING npm_versions.id AS "npm_versions.id";Bound args: [7b27d43c-48ed-794e-a51d-8003e23fe6e3, 8ceb803c-9038-7af3-bb6c-982e76052b5c, b924800a-19ff-7367-9620-e0bc1500c45a, 1.0.0, 1234, {"name":"review-prep-npm","version":"1.0.0"}, 42]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Insert on npm_versions (cost=0.00..0.01 rows=1 width=240) (actual time=0.467..0.468 rows=1 loops=1)
Conflict Resolution: NOTHING
Conflict Arbiter Indexes: unique_npm_versions_ns_id_pkg_id_version
Tuples Inserted: 1
Conflicting Tuples: 0
Buffers: shared hit=104 read=1 dirtied=13 written=7
-> Result (cost=0.00..0.01 rows=1 width=240) (actual time=0.001..0.002 rows=1 loops=1)
Planning:
Buffers: shared hit=136 read=1
Planning Time: 0.420 ms
Trigger for constraint fk_npm_versions_namespace_id_namespaces on npm_versions_p29: time=0.143 calls=1
Trigger for constraint fk_npm_versions_npm_package_id_npm_packages on npm_versions_p29: time=1.173 calls=1
Execution Time: 2.291 msTimings: planning 0.420ms, execution 2.291ms, total 2.711ms.
datastore.NpmVersionStore.InsertNpmVersion.UserNil
Summary: Same plan as the bound branch with NULL::text inline: single-row Insert, arbiter unique_npm_versions_ns_id_pkg_id_version, routed to npm_versions_p41. The stored gitlab_user_id reads NULL. No anomalies.
Seed shape: namespaces=1, repositories=1, npm_repositories=1, npm_packages=1
Rendered SQL:
INSERT INTO public.npm_versions (id, namespace_id, npm_package_id, version, size_bytes, package_json, gitlab_user_id)
VALUES ($1::uuid, $2::uuid, $3::uuid, $4::text, $5, $6::text::jsonb, NULL::text)
ON CONFLICT (namespace_id, npm_package_id, version) WHERE soft_deleted_at IS NULL DO NOTHING
RETURNING npm_versions.id AS "npm_versions.id";Bound args: [9e1f2e9a-51ff-76ee-8e39-58755ccfc046, 67188e4f-b897-7bae-88c3-d2a42fc6bee0, 9d421bf3-2f64-7ce9-82b5-b3e3cccf35c7, 1.0.0, 1234, {"name":"review-prep-npm","version":"1.0.0"}]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Insert on npm_versions (cost=0.00..0.01 rows=1 width=240) (actual time=0.428..0.428 rows=1 loops=1)
Conflict Resolution: NOTHING
Conflict Arbiter Indexes: unique_npm_versions_ns_id_pkg_id_version
Tuples Inserted: 1
Conflicting Tuples: 0
Buffers: shared hit=70 read=1 dirtied=13 written=7
-> Result (cost=0.00..0.01 rows=1 width=240) (actual time=0.001..0.001 rows=1 loops=1)
Planning:
Buffers: shared hit=128
Planning Time: 0.400 ms
Trigger for constraint fk_npm_versions_namespace_id_namespaces on npm_versions_p41: time=0.149 calls=1
Trigger for constraint fk_npm_versions_npm_package_id_npm_packages on npm_versions_p41: time=0.626 calls=1
Execution Time: 1.301 msTimings: planning 0.400ms, execution 1.301ms, total 1.701ms.
Related to #1151 (closed)