Skip to content

Fix #2385: Preserve multidimensional array dimensions in CopyFrom - #2679

Merged
jackc merged 3 commits into
jackc:masterfrom
arminazimi:fix-2385-array-dimensions
Oct 17, 2026
Merged

jackc merged 3 commits into
jackc:masterfrom
arminazimi:fix-2385-array-dimensions

Conversation

@arminazimi

@arminazimi arminazimi commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

CopyFrom was flattening multidimensional arrays sent as PostgreSQL text-format
literals (e.g. "{{1,2},{3,4}}"). The issue is reproducible: inserting the same
literal with Exec preserves array_ndims = 2, while inserting it with CopyFrom
gives array_ndims = 1.

Root cause: when CopyFrom needs to binary-encode a value it cannot encode directly,
it falls back into tryScanStringCopyValueThenEncode, which rescans the text literal
into *any. pgtype.ArrayCodec.DecodeValue then flattens the array literal into a
single one-dimensional []any, so the COPY protocol binary payload carries
ndim = 1 even for multidimensional inputs.

This PR makes that fallback path preserve the original array dimensions by scanning
the text literal into pgtype.Array[any] (an ArraySetter) when the target column
is an array type, then re-encoding that value in binary. CopyFrom now produces the
same stored shape that Exec does.

Fixes #2385

Changes

  • pgx/values.go: tryScanStringCopyValueThenEncode now uses pgtype.Map.TypeForOID
    and typechecks the returned *pgtype.Type.Codec against *pgtype.ArrayCodec before
    falling back to the any scan path. Dimension-preserving scan only applies to array
    target columns; the any fallback still handles non-array types.
  • pgx/values_internal_test.go: new DB-free internal test
    TestEncodeCopyValuePreservesArrayDimensions verifying encodeCopyValue produces a
    2-D binary array header from a "{{1,2},{3,4}}" string and a 1-D header from
    "{a,b,c}", with NULL-element handling and non-array-type passthrough.
  • pgx/copy_from_test.go: new integration test
    TestConnCopyFromPreservesMultidimensionalArrayString that inserts the same 2-D
    text[][] literal via both Exec and CopyFrom and asserts pgtype.Array[any].Dims,
    array_ndims(), array_dims(), array_length(b, 1), and NULL handling all match.

Test plan

  • New integration test TestConnCopyFromPreservesMultidimensionalArrayString on
    PostgreSQL 16: inserts the same 2-D text[][] literal via both Exec and
    CopyFrom, then asserts dimensions, array_ndims(), array_dims(),
    array_length(b, 1), NULL elements, and NULL rows all match.
  • New DB-free internal test TestEncodeCopyValuePreservesArrayDimensions:
    asserts encodeCopyValue produces a 2-D binary array header from a
    "{{1,2},{3,4}}" string and a 1-D header from "{a,b,c}", with NULL-element
    handling and non-array-type passthrough.
  • Existing TestConnCopyFromAutomaticStringConversion re-run and still passes:
    CopyFrom still auto-converts "42" to int8 42 rows.
  • Existing TestConnCopyFromAutomaticStringConversionArray re-run and still
    passes: [][]string / []string CopyFrom rows still round-trip through numeric
    arrays correctly.
  • go test ./... -race re-run against the changed package; no new races.
  • gofmt -d clean.

When using CopyFrom with string array values, the text-to-binary encoding
path via tryScanStringCopyValueThenEncode() was scanning into 'any' which
flattened array dimensions. Now it detects array types and uses
pgtype.Array[any] to preserve dimensions.

Also adds regression test TestConnCopyFromPreservesMultidimensionalArrayString.

Signed-off-by: arminazimi <arminazimi@users.noreply.github.com>
@arminazimi
arminazimi force-pushed the fix-2385-array-dimensions branch from e066f6e to b09583d Compare October 13, 2026 11:19
@jackc
jackc merged commit ca79cbd into jackc:master Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multidimensional array flattened when using CopyFrom instead of Exec+Insert

2 participants