Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1voiLj-00ClqT-1s for pgsql-hackers@arkaria.postgresql.org; Sat, 07 Feb 2026 13:27:47 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1voiLg-006mKo-20 for pgsql-hackers@arkaria.postgresql.org; Sat, 07 Feb 2026 13:27:44 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1voiLg-006mKg-0c for pgsql-hackers@lists.postgresql.org; Sat, 07 Feb 2026 13:27:44 +0000 Received: from mail-ed1-x52a.google.com ([2a00:1450:4864:20::52a]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1voiLe-00000000zg7-0ixi for pgsql-hackers@postgresql.org; Sat, 07 Feb 2026 13:27:43 +0000 Received: by mail-ed1-x52a.google.com with SMTP id 4fb4d7f45d1cf-658381b28e8so2173776a12.0 for ; Sat, 07 Feb 2026 05:27:42 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1770470861; cv=none; d=google.com; s=arc-20240605; b=j++IpK3fCyqY/imcsVRvj/sdWrdgf7fhHINjkRDJofwFZ23iFmjkpZgX4ncihKu5ST OShex0LlvLSEixeLDi6cFUjjTh+dG+8MgvTwZ9Q+kz4adcSAiAgU/wrhy2j0umA3Gn1O poo+KtvBYLTeXXNCtWHKJ5y7n39nk0wj3CXDFBXQ86RndJw+ZGWByVF3xRbm3Fj7FoBm jIcvH8d/S/KVqyMkffCee84uFIr14cKJWNXeQ0gBxFCLQmaDv/mgVqs/FV7hU0M48XaZ AbqpLJD5VCoBVDA/VDvAR26J7nrJRay8wApMQu0EMyMeP/bGrb02UR86EXYZsGtcJluC rj5A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=uZ/65t4rDaroWOVo9ITfGBnSqmaKHXZK5JSDuzqRcJk=; fh=cLXhIyrNsz8355uigqjFN3c21LhyV83o+xDdTPj9A34=; b=SphY50tInge1vk5RL0koKZHtOMy4xepnpJ3sA8Lilvhtty/wVeueEvvFRh4b5BIcwl WcvNCvZ8ng1X/yNrRR5WRr1t1rf5OKNpo6hr9jo8ej59OrYbwuW2dnLiLg+EG4Iir2wb Lveoxl+Kux65eilGy1U0WB0mNL261iRnOCsXxYp2PrKQELEGglt4++UGE5KM5TULWMqN n5TIcHjuxwgIOMROjHL00eX0hqef9AlI2hTqAD7/qcKEHlK/SjmLBwAD7YPy0NRNmMNj DtO/VICQgpLTrulKamojJdXEbu5x6mKItCt7C95zrJQ1r2HkfHB047jME3sarPgobBY7 +zZg==; darn=postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1770470861; x=1771075661; darn=postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=uZ/65t4rDaroWOVo9ITfGBnSqmaKHXZK5JSDuzqRcJk=; b=Ll15ZGxTv5rPEC1glDlQ/wVQ5Y3xiqpGF2HZIS5riolkpPk+XAWnUct2joGpTq93kZ DRUNgsHj2lv0kPOoFaWgW2X1vfoHSMnJRHKLGp0qW/wypLyiTPS+8706R/tOZ2dwDvQg scJNHS04XB881ERAubby3ZeNfS+J/0ZQSF3vyhAvtPMkWZ0f8keMXuYJzzkR7FiS7Hze ib8WsV8D7NQ+JXFq9bXX1JnIfyDX3GazIlJvUE06VZisj3EMyXE/5kkzoXPiOZuYfNSY ByYZLk+UGuqRFGRGKD/4U3s/B1d2ewvp7ycCv/wYsTB4vojuI0768ULrdcy+SKhNzbAS 7o/w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1770470861; x=1771075661; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=uZ/65t4rDaroWOVo9ITfGBnSqmaKHXZK5JSDuzqRcJk=; b=gKXtxOQtNnOc7dW7pLIGyQBmR/Eh/MxG620giP9rApUCsVaQ6l0zPkjzepI1U0h7O7 bucAUTSJSAFQxlosdPnouR+0d6I3/Hpzthe9HijfREHtsd6KRUV0W5ySSylnzYZZhN3I zuDkmsduws14F9k9R8vkBrIRRIO0XvD09JzXkRDT8cr27AUiuM66o4igbW6cRc1JQtfG Vf1c8tM1+YgBHQuV6i9tRgYT3jrhQbmF3O1a+H1n9AMkS5Te6mVAB2jdnL2SV+hi2bz8 JzF4Q4rXgeh0oNnM46zxSHGCPM+5PuoyvMdueRwPAbxz59u8SfZaT5ujov3+bB+0o0fD vMuQ== X-Forwarded-Encrypted: i=1; AJvYcCX1eTRghCDLlmZF7pfljau7T1DUY98nokP4s0ZWzjgMQICI5anbMAarygG52qRgRmTGZpJ58ZNNrhCxlLo3@postgresql.org X-Gm-Message-State: AOJu0YxEbBWLHUNDcw5/jC0MaIp7ttW26k/NA+DrEeo+4fUQb0WPY/70 C78Mg9+yHMpej2WkCXNTdjh8Y8EQ2L6rp2vpzsOGPqUqqbWRYujIIz0o0jMCu6DIFGUleYzqZs7 1N9URDsL7Rnf43NQG2Oy59ezmO9N5H8Y= X-Gm-Gg: AZuq6aLMrGPfLSyNf36Q0RGBH8vvdsMOJaz9HL2kfe8dYCXS9a4HLMFiCHg0v+4QHaz xJp06IFkcVsy3tjNx9k/K21d0RD2R6rTLeTbCJj1+NdvzmSommgyyz7gyTQ+0Cl+nx3YISHy3GB TBzjs013KdSHLdi0jJq6dszh2o4nQA3yZVnAQS13fsXiSbfXiCkUuCGxTeAQRuuJ9ibYRr8VqWC Cf4xdEZJVXO9fUTdY1iuXHYfk85ObcBVZoFx3R+/RyoiN/EBSyAuIzJMX+OSr3hC6ro6IwS/WD6 uFEYEwP83ZWFXZEtEFPTkAxr6ck= X-Received: by 2002:a05:6402:2356:b0:658:c1b3:b958 with SMTP id 4fb4d7f45d1cf-659840b980cmr3348680a12.0.1770470860450; Sat, 07 Feb 2026 05:27:40 -0800 (PST) MIME-Version: 1.0 References: <8620df11-96e4-4ca3-8f3c-33a479260961@joeconway.com> <4162f7f7-6fd6-4720-98e2-89f80e3de2ed@joeconway.com> <153b71b8-b6fb-4930-aaff-a7334cb3f6d6@joeconway.com> <8ef5f5d6-38db-4624-8eef-2d96c9eba9d4@joeconway.com> In-Reply-To: From: Junwang Zhao Date: Sat, 7 Feb 2026 21:27:29 +0800 X-Gm-Features: AZwV_Qg15Wy3XjFYcwrfBNpHk8pXO2KuTk4PNLpRj2aaQFoDRERhRTilJzc-H78 Message-ID: Subject: Re: Emitting JSON to file using COPY TO To: jian he Cc: Florents Tselai , Joe Conway , "Andrey M. Borodin" , Dean Rasheed , Daniel Verite , Andrew Dunstan , Davin Shearer , PostgreSQL-development Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi jian, Thanks for keeping the patch set up to date with the master. On Fri, Feb 6, 2026 at 11:26=E2=80=AFAM jian he wrote: > > On Wed, Feb 4, 2026 at 12:41=E2=80=AFAM Florents Tselai > wrote: > > > > I (and others I assume) would really like to see this in 19; > > glancing at the thread above and in the test cases I see this is in goo= d shape for comitter review. > > No? > > > > If I were to add something it would be an example in copy.sgml > > > > When the FORCE_ARRAY option is enabled, > > the entire output is wrapped in a JSON array and individual rows are= separated by commas: > > > > COPY (SELECT id, name FROM users) TO STDOUT (FORMAT JSON, FORCE_ARRAY); > > > > > > [ > > {"id": 1, "name": "Alice"} > > ,{"id": 2, "name": "Bob"} > > ,{"id": 3, "name": "Charlie"} > > ] > > > > > > > > v23-0003-Add-option-force_array-for-COPY-JSON-FORMAT.patch > I've added: > > + > + When the FORCE_ARRAY option is enabled, > + the entire output is wrapped in a single JSON array with rows > separated by commas: > + > +COPY (SELECT * FROM (VALUES(1),(2)) val(id)) TO STDOUT (FORMAT JSON, > FORCE_ARRAY); > + > +The output is as follows: > + > +[ > + {"id":1} > +,{"id":2} > +] > + > + > + > + > > > Also, apologies if that has been discussed already, > > is there a good reason why didn't we just go with a simple "WRAP_ARRAY"= ? > > > > I don=E2=80=99t have a particular preference. > If the consensus is that WRAP_ARRAY is better than FORCE_ARRAY, we can > change it accordingly. > > > -- > jian > https://www.enterprisedb.com/ Here are some comments on v23: 0001: The refactor looks straightforward to me. Introducing a format field should make future extensions easier. One suggestion is that we could add some helper macros around format, for example: #define IS_FORMAT_CSV(format) (format =3D=3D COPY_FORMAT_CSV) #define IS_FORMAT_TEXT_LIKE(format) \ (format =3D=3D COPY_FORMAT_TEXT || format =3D=3D COPY_FORMAT_CSV) I think this would improve readability. 0002: Since you have moved the `CopyFormat enum` into 0001, the following commit msg should be rephrased. The CopyFormat enum was originally contributed by Joel Jacobson joel@compiler.org, later refactored by Jian He to address various issues, a= nd further adapted by Junwang Zhao to support the newly introduced CopyToRouti= ne struct (commit 2e4127b6d2). - if (opts_out->format =3D=3D COPY_FORMAT_BINARY && opts_out->delim) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - /*- translator: %s is the name of a COPY option, e.g. ON_ERROR */ - errmsg("cannot specify %s in BINARY mode", "DELIMITER"))); + if (opts_out->delim) + { + if (opts_out->format =3D=3D COPY_FORMAT_BINARY) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + /*- translator: %s is the name of a COPY option, e.g. ON_ERROR */ + errmsg("cannot specify %s in BINARY mode", "DELIMITER")); + else if (opts_out->format =3D=3D COPY_FORMAT_JSON) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot specify %s in JSON mode", "DELIMITER")); + } Can we add a function that converts CopyFormat to a string? Treating CopyFormat as %s in error messages would make the code shorter. However, I'm not sure whether this aligns with translation conventions, correct me if I'm wrong. - * CSV and text formats share the same TextLike routines except for the + * CSV and text, json formats share the same TextLike routines except for = the I'd suggest rewording to `CSV, text and json ...`. The same applied to other parts in this patch. 0003: The commit message includes some changes(adapt the newly introduced CopyToRoutine) that actually belong to 0002; it would be better to remove them from this commit. + if (cstate->json_row_delim_needed && cstate->opts.force_array) + CopySendChar(cstate, ','); + else if (cstate->opts.force_array) + { + /* first row needs no delimiter */ + CopySendChar(cstate, ' '); + cstate->json_row_delim_needed =3D true; + } can we do this: if (cstate->opts.force_array) { if (cstate->json_row_delim_needed) CopySendChar(cstate, ','); else { /* first row needs no delimiter */ CopySendChar(cstate, ' '); cstate->json_row_delim_needed =3D true; } } --=20 Regards Junwang Zhao