Subject: Re: Make COPY format extendable: Extract COPY TO format implementations

From: Tomas Vondra <[email-masked]>
Date: Mon, 29 Jul 2024 14:17:08 +0200

On 7/25/24 06:51, Sutou Kouhei wrote:
> Hi,
> 
> ...
>
> Here are related information for this benchmark/profile:
> 
> * Use -O2 for optimization build flag
>   ("meson setup --buildtype=release" may be used)
> * Use tmpfs for PGDATA
> * Disable fsync
> * Run on scissors (what is "scissors" in this context...?)   [9]
> * Unlogged table may be used
> * Use a table that has 30 integer columns (*1)
> * Use 5M rows (*2)
> * Use '/dev/null' for COPY TO (*3)
> * Use blackhole_am for COPY FROM (*4)
>   https://github.com/michaelpq/pg_plugins/tree/main/blackhole_am
> * perf is used but used options are unknown (sorry)
> 
> 
> (*1) This SQL may be used to create the table:
> 
> CREATE OR REPLACE FUNCTION create_table_cols(tabname text, num_cols int)
> RETURNS VOID AS
> $func$
> DECLARE
>   query text;
> BEGIN
>   query := 'CREATE UNLOGGED TABLE ' || tabname || ' (';
>   FOR i IN 1..num_cols LOOP
>     query := query || 'a_' || i::text || ' int default 1';
>     IF i != num_cols THEN
>       query := query || ', ';
>     END IF;
>   END LOOP;
>   query := query || ')';
>   EXECUTE format(query);
> END
> $func$ LANGUAGE plpgsql;
> SELECT create_table_cols ('to_tab_30', 30);
> SELECT create_table_cols ('from_tab_30', 30);
> 
> 
> (*2) This SQL may be used to insert 5M rows:
> 
> INSERT INTO to_tab_30 SELECT FROM generate_series(1, 5000000);
> 
> 
> (*3) This SQL may be used for COPY TO:
> 
> COPY to_tab_30 TO '/dev/null' WITH (FORMAT text);
> 
> 
> (*4) This SQL may be used for COPY FROM:
> 
> CREATE EXTENSION blackhole_am;
> ALTER TABLE from_tab_30 SET ACCESS METHOD blackhole_am;
> COPY to_tab_30 TO '/tmp/to_tab_30.txt' WITH (FORMAT text);
> COPY from_tab_30 FROM '/tmp/to_tab_30.txt' WITH (FORMAT text);
> 

Thanks for the benchmark instructions and updated patches. Very helpful!

I wrote a simple script to automate the benchmark - it just runs these
tests with different parameters (number of columns and number of
imported/exported rows). See the run.sh attachment, along with two CSV
results from current master and with all patches applied.

The attached PDF has a simple summary, with a median duration for each
combination, and a comparison (patched/master). The results are from my
laptop, so it's probably noisy, and it would be good to test it on a
more realistic hardware (for perf-sensitive things).

- For COPY FROM there is no difference - the results are within 1% of
master, and there's no systemic difference.

- For COPY TO it's a different story, though. There's a pretty clear
regression, by ~5%. It's a bit interesting the correlation with the
number of columns is not stronger ...

I did do some basic profiling, and the perf diff looks like this:

# Event 'task-clock:upppH'
#
# Baseline  Delta Abs  Shared Object  Symbol

# ........  .........  .............
.........................................
#
    13.34%    -12.94%  postgres       [.] CopyOneRowTo
              +10.75%  postgres       [.] CopyToTextOneRow
     4.31%     +2.84%  postgres       [.] pg_ltoa
    10.96%     +1.15%  postgres       [.] CopySendChar
     8.68%     +0.78%  postgres       [.] AllocSetAlloc
    10.89%     -0.70%  postgres       [.] CopyAttributeOutText
     5.01%     -0.47%  postgres       [.] enlargeStringInfo
     4.95%     -0.42%  postgres       [.] OutputFunctionCall
     5.29%     -0.37%  postgres       [.] int4out
     5.90%     -0.31%  postgres       [.] appendBinaryStringInfo
               +0.29%  postgres       [.] CopyToStateFlush
     0.27%     -0.27%  postgres       [.] memcpy@plt

Not particularly surprising that CopyToTextOneRow has +11%, but that's
because it's a new function. The perf difference is perhaps due to
pg_ltoa/CopySendChar, but not sure why.

I also did some flamegraph - attached is for master, patched and diff.

It's interesting the main change in the flamegraphs is CopyToStateFlush
pops up on the left side. Because, what is that about? That is a thing
introduced in the 0005 patch, so maybe the regression is not strictly
about the existing formats moving to the new API, but due to something
else in a later version of the patch?

It would be good do run the tests for each patch in the series, and then
see when does the regression actually appear.

FWIW None of this actually proves this is an issue in practice. No one
will be exporting into /dev/null or importing into blackhole, and I'd
bet the difference gets way smaller for more realistic cases.

regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

---

From: Sutou Kouhei <[email-masked]>
Date: Tue, 30 Jul 2024 16:13:06 +0900 (JST)

Hi,

In <[email-masked]>
  "Re: Make COPY format extendable: Extract COPY TO format implementations" on Mon, 29 Jul 2024 14:17:08 +0200,

> results from current master and with all patches applied.

Thanks. I also used the script with some modifications:

1. Create a test database automatically
2. Enable blackhole_am automatically
3. Create create_table_cols() automatically

I attach it. I also attach results of master and patched. My
results are from my desktop. So it's probably noisy.

> number of columns is not stronger ...

My results showed different trend:

- COPY FROM: Patched is about 15-20% slower than master
- COPY TO: Patched is a bit faster than master

Here are some my numbers:

type	n_cols	n_rows	diff	master		patched
----------------------------------------------------------
TO	5	1	100.56%	218.376000	219.609000
FROM	5	1	113.33%	168.493000	190.954000
...
TO	5	5	100.60%	1037.773000	1044.045000
FROM	5	5	116.46%	767.966000	894.377000
...
TO	5	10	100.15%	2092.245000	2095.472000
FROM	5	10	115.91%	1508.160000	1748.130000
TO	10	1	98.62%	353.087000	348.214000
FROM	10	1	118.65%	260.551000	309.133000
...
TO	10	5	96.89%	1724.061000	1670.427000
FROM	10	5	119.92%	1224.098000	1467.941000
...
TO	10	10	98.70%	3444.291000	3399.538000
FROM	10	10	118.79%	2462.314000	2924.866000
TO	15	1	97.71%	492.082000	480.802000
FROM	15	1	115.59%	347.820000	402.033000
...
TO	15	5	98.32%	2402.419000	2362.140000
FROM	15	5	115.48%	1657.594000	1914.245000
...
TO	15	10	96.91%	4830.319000	4681.145000
FROM	15	10	115.09%	3304.798000	3803.542000
TO	20	1	96.05%	629.828000	604.939000
FROM	20	1	118.50%	438.673000	519.839000
...
TO	20	5	97.15%	3084.210000	2996.331000
FROM	20	5	115.35%	2110.909000	2435.032000
...
TO	25	1	98.29%	764.779000	751.684000
FROM	25	1	115.13%	519.686000	598.301000
...
TO	25	5	94.08%	3843.996000	3616.614000
FROM	25	5	115.62%	2554.008000	2952.928000
...
TO	25	10	97.41%	7504.865000	7310.549000
FROM	25	10	117.25%	4994.463000	5856.029000
TO	30	1	94.39%	906.324000	855.503000
FROM	30	1	119.60%	604.110000	722.491000
...
TO	30	5	96.50%	4419.907000	4265.417000
FROM	30	5	116.97%	2932.883000	3430.556000
...
TO	30	10	94.39%	8974.878000	8470.991000
FROM	30	10	117.84%	5800.793000	6835.900000
----

See the attached diff.txt for full numbers.
I also attach scripts to generate the diff.txt. Here is the
command line I used:

----
ruby diff.rb <(ruby aggregate.rb master.result) <(ruby aggregate.rb patched.result) | tee diff.txt
----

My environment:

* Debian GNU/Linux sid
* gcc (Debian 13.3.0-2) 13.3.0
* AMD Ryzen 9 3900X 12-Core Processor

I'll look into this.

If someone is interested in this proposal, could you share
your numbers?

> else in a later version of the patch?

Ah, making static CopySendEndOfRow() a to non-static function
(CopyToStateFlush()) may be the reason of this. Could you
try the attached v19 patch? It changes the 0005 patch:

* It reverts the static change
* It adds a new non-static function that just exports
  CopySendEndOfRow()

Thanks,
-- 
kou
#!/usr/bin/bash

DIR=${1:-$(pwd)}

psql postgres > /dev/null 2>&1 <<EOF
DROP DATABASE IF EXISTS test;
CREATE DATABASE test;
EOF
psql test > /dev/null 2>&1 <<EOF
CREATE EXTENSION blackhole_am;
CREATE OR REPLACE FUNCTION create_table_cols(tabname text, num_cols int)
RETURNS VOID AS
\$func\$
DECLARE
  query text;
BEGIN
  query := 'CREATE UNLOGGED TABLE ' || tabname || ' (';
  FOR i IN 1..num_cols LOOP
    query := query || 'a_' || i::text || ' int default 1';
    IF i != num_cols THEN
      query := query || ', ';
    END IF;
  END LOOP;
  query := query || ')';
  EXECUTE format(query);
END
\$func\$ LANGUAGE plpgsql;
EOF

for c in $(seq 5 5 30); do

	for rows in $(seq 1 10); do

		psql test > /dev/null 2>&1 <<EOF
DROP TABLE IF EXISTS to_table;
DROP TABLE IF EXISTS from_table;

SELECT create_table_cols ('to_table', $c);
SELECT create_table_cols ('from_table', $c);

INSERT INTO to_table SELECT FROM generate_series(1, $rows * 1000000);

COPY to_table TO '$DIR/test.data' WITH (FORMAT text);

ALTER TABLE from_table SET ACCESS METHOD blackhole_am;
EOF

		for r in $(seq 1 10); do

			s=$(psql test -t -A -c "SELECT EXTRACT(EPOCH FROM now())")
			psql test -c "COPY to_table TO '/dev/null' WITH (FORMAT text)" > /dev/null 2>&1
			d=$(psql test -t -A -c "SELECT 1000 * (EXTRACT(EPOCH FROM now()) - $s)")

			echo "COPY_TO" $c $rows $r $d

		done

		# run COPY FROM 10x
		for r in $(seq 1 10); do

			s=$(psql test -t -A -c "SELECT EXTRACT(EPOCH FROM now())")
			psql test -c "COPY from_table FROM '$DIR/test.data' WITH (FORMAT text)" > /dev/null 2>&1
			d=$(psql test -t -A -c "SELECT 1000 * (EXTRACT(EPOCH FROM now()) - $s)")

			echo "COPY_FROM" $c $rows $r $d

		done

	done

done
COPY_TO 5 1 1 212.831000
COPY_TO 5 1 2 208.677000
COPY_TO 5 1 3 215.074000
COPY_TO 5 1 4 218.376000
COPY_TO 5 1 5 219.056000
COPY_TO 5 1 6 218.237000
COPY_TO 5 1 7 234.709000
COPY_TO 5 1 8 220.561000
COPY_TO 5 1 9 219.747000
COPY_TO 5 1 10 211.881000
COPY_FROM 5 1 1 166.336000
COPY_FROM 5 1 2 166.000000
COPY_FROM 5 1 3 166.776000
COPY_FROM 5 1 4 168.493000
COPY_FROM 5 1 5 169.632000
COPY_FROM 5 1 6 164.290000
COPY_FROM 5 1 7 167.841000
COPY_FROM 5 1 8 169.336000
COPY_FROM 5 1 9 172.948000
COPY_FROM 5 1 10 168.893000
COPY_TO 5 2 1 412.065000
COPY_TO 5 2 2 420.758000
COPY_TO 5 2 3 421.387000
COPY_TO 5 2 4 402.165000
COPY_TO 5 2 5 414.407000
COPY_TO 5 2 6 423.387000
COPY_TO 5 2 7 426.431000
COPY_TO 5 2 8 424.798000
COPY_TO 5 2 9 419.588000
COPY_TO 5 2 10 425.688000
COPY_FROM 5 2 1 308.856000
COPY_FROM 5 2 2 319.487000
COPY_FROM 5 2 3 316.488000
COPY_FROM 5 2 4 315.212000
COPY_FROM 5 2 5 316.066000
COPY_FROM 5 2 6 310.381000
COPY_FROM 5 2 7 322.447000
COPY_FROM 5 2 8 318.206000
COPY_FROM 5 2 9 322.588000
COPY_FROM 5 2 10 317.101000
COPY_TO 5 3 1 633.255000
COPY_TO 5 3 2 616.202000
COPY_TO 5 3 3 610.864000
COPY_TO 5 3 4 628.803000
COPY_TO 5 3 5 638.041000
COPY_TO 5 3 6 647.732000
COPY_TO 5 3 7 624.457000
COPY_TO 5 3 8 624.007000
COPY_TO 5 3 9 616.109000
COPY_TO 5 3 10 624.354000
COPY_FROM 5 3 1 469.425000
COPY_FROM 5 3 2 471.284000
COPY_FROM 5 3 3 468.651000
COPY_FROM 5 3 4 465.177000
COPY_FROM 5 3 5 466.697000
COPY_FROM 5 3 6 463.886000
COPY_FROM 5 3 7 480.866000
COPY_FROM 5 3 8 465.048000
COPY_FROM 5 3 9 469.349000
COPY_FROM 5 3 10 467.342000
COPY_TO 5 4 1 837.447000
COPY_TO 5 4 2 848.536000
COPY_TO 5 4 3 867.580000
COPY_TO 5 4 4 831.669000
COPY_TO 5 4 5 839.633000
COPY_TO 5 4 6 846.060000
COPY_TO 5 4 7 824.590000
COPY_TO 5 4 8 836.084000
COPY_TO 5 4 9 845.936000
COPY_TO 5 4 10 851.128000
COPY_FROM 5 4 1 604.809000
COPY_FROM 5 4 2 617.653000
COPY_FROM 5 4 3 615.883000
COPY_FROM 5 4 4 616.633000
COPY_FROM 5 4 5 617.737000
COPY_FROM 5 4 6 617.361000
COPY_FROM 5 4 7 608.998000
COPY_FROM 5 4 8 621.576000
COPY_FROM 5 4 9 619.759000
COPY_FROM 5 4 10 625.312000
COPY_TO 5 5 1 1057.027000
COPY_TO 5 5 2 1038.905000
COPY_TO 5 5 3 1034.425000
COPY_TO 5 5 4 1048.834000
COPY_TO 5 5 5 1069.693000
COPY_TO 5 5 6 1019.558000
COPY_TO 5 5 7 1007.099000
COPY_TO 5 5 8 1021.759000
COPY_TO 5 5 9 1037.773000
COPY_TO 5 5 10 1008.977000
COPY_FROM 5 5 1 753.724000
COPY_FROM 5 5 2 769.060000
COPY_FROM 5 5 3 765.603000
COPY_FROM 5 5 4 769.101000
COPY_FROM 5 5 5 767.057000
COPY_FROM 5 5 6 767.966000
COPY_FROM 5 5 7 781.901000
COPY_FROM 5 5 8 772.262000
COPY_FROM 5 5 9 762.266000
COPY_FROM 5 5 10 767.036000
COPY_TO 5 6 1 1245.932000
COPY_TO 5 6 2 1254.330000
COPY_TO 5 6 3 1254.507000
COPY_TO 5 6 4 1255.708000
COPY_TO 5 6 5 1238.643000
COPY_TO 5 6 6 1259.656000
COPY_TO 5 6 7 1262.356000
COPY_TO 5 6 8 1253.554000
COPY_TO 5 6 9 1262.281000
COPY_TO 5 6 10 1253.491000
COPY_FROM 5 6 1 940.044000
COPY_FROM 5 6 2 938.479000
COPY_FROM 5 6 3 926.584000
COPY_FROM 5 6 4 920.494000
COPY_FROM 5 6 5 908.873000
COPY_FROM 5 6 6 917.936000
COPY_FROM 5 6 7 917.126000
COPY_FROM 5 6 8 921.488000
COPY_FROM 5 6 9 917.245000
COPY_FROM 5 6 10 916.243000
COPY_TO 5 7 1 1430.487000
COPY_TO 5 7 2 1427.373000
COPY_TO 5 7 3 1497.434000
COPY_TO 5 7 4 1463.688000
COPY_TO 5 7 5 1441.485000
COPY_TO 5 7 6 1474.119000
COPY_TO 5 7 7 1514.650000
COPY_TO 5 7 8 1478.208000
COPY_TO 5 7 9 1495.704000
COPY_TO 5 7 10 1459.739000
COPY_FROM 5 7 1 1077.841000
COPY_FROM 5 7 2 1084.081000
COPY_FROM 5 7 3 1093.168000
COPY_FROM 5 7 4 1078.736000
COPY_FROM 5 7 5 1076.685000
COPY_FROM 5 7 6 1110.902000
COPY_FROM 5 7 7 1079.210000
COPY_FROM 5 7 8 1067.793000
COPY_FROM 5 7 9 1079.762000
COPY_FROM 5 7 10 1084.935000
COPY_TO 5 8 1 1650.517000
COPY_TO 5 8 2 1697.566000
COPY_TO 5 8 3 1667.700000
COPY_TO 5 8 4 1656.847000
COPY_TO 5 8 5 1659.966000
COPY_TO 5 8 6 1702.676000
COPY_TO 5 8 7 1696.178000
COPY_TO 5 8 8 1682.269000
COPY_TO 5 8 9 1690.789000
COPY_TO 5 8 10 1703.132000
COPY_FROM 5 8 1 1253.866000
COPY_FROM 5 8 2 1245.838000
COPY_FROM 5 8 3 1231.959000
COPY_FROM 5 8 4 1216.493000
COPY_FROM 5 8 5 1213.282000
COPY_FROM 5 8 6 1213.838000
COPY_FROM 5 8 7 1251.825000
COPY_FROM 5 8 8 1245.100000
COPY_FROM 5 8 9 1261.415000
COPY_FROM 5 8 10 1212.752000
COPY_TO 5 9 1 1850.090000
COPY_TO 5 9 2 1899.929000
COPY_TO 5 9 3 1860.290000
COPY_TO 5 9 4 1832.055000
COPY_TO 5 9 5 1857.414000
COPY_TO 5 9 6 1879.424000
COPY_TO 5 9 7 1875.373000
COPY_TO 5 9 8 1854.969000
COPY_TO 5 9 9 1915.033000
COPY_TO 5 9 10 1866.939000
COPY_FROM 5 9 1 1370.836000
COPY_FROM 5 9 2 1379.806000
COPY_FROM 5 9 3 1372.183000
COPY_FROM 5 9 4 1367.779000
COPY_FROM 5 9 5 1368.464000
COPY_FROM 5 9 6 1380.544000
COPY_FROM 5 9 7 1363.804000
COPY_FROM 5 9 8 1362.463000
COPY_FROM 5 9 9 1371.727000
COPY_FROM 5 9 10 1377.122000
COPY_TO 5 10 1 2058.078000
COPY_TO 5 10 2 2064.015000
COPY_TO 5 10 3 2120.218000
COPY_TO 5 10 4 2060.682000
COPY_TO 5 10 5 2105.438000
COPY_TO 5 10 6 2076.790000
COPY_TO 5 10 7 2095.560000
COPY_TO 5 10 8 2092.245000
COPY_TO 5 10 9 2034.601000
COPY_TO 5 10 10 2094.292000
COPY_FROM 5 10 1 1557.934000
COPY_FROM 5 10 2 1517.610000
COPY_FROM 5 10 3 1506.637000
COPY_FROM 5 10 4 1515.831000
COPY_FROM 5 10 5 1490.391000
COPY_FROM 5 10 6 1507.338000
COPY_FROM 5 10 7 1508.160000
COPY_FROM 5 10 8 1523.402000
COPY_FROM 5 10 9 1504.555000
COPY_FROM 5 10 10 1500.368000
COPY_TO 10 1 1 350.108000
COPY_TO 10 1 2 354.319000
COPY_TO 10 1 3 347.724000
COPY_TO 10 1 4 344.384000
COPY_TO 10 1 5 355.083000
COPY_TO 10 1 6 363.509000
COPY_TO 10 1 7 355.307000
COPY_TO 10 1 8 345.092000
COPY_TO 10 1 9 353.087000
COPY_TO 10 1 10 352.411000
COPY_FROM 10 1 1 259.050000
COPY_FROM 10 1 2 261.272000
COPY_FROM 10 1 3 258.407000
COPY_FROM 10 1 4 260.551000
COPY_FROM 10 1 5 260.306000
COPY_FROM 10 1 6 262.650000
COPY_FROM 10 1 7 259.448000
COPY_FROM 10 1 8 263.050000
COPY_FROM 10 1 9 259.594000
COPY_FROM 10 1 10 262.014000
COPY_TO 10 2 1 687.593000
COPY_TO 10 2 2 689.272000
COPY_TO 10 2 3 672.518000
COPY_TO 10 2 4 697.031000
COPY_TO 10 2 5 709.173000
COPY_TO 10 2 6 704.194000
COPY_TO 10 2 7 696.468000
COPY_TO 10 2 8 693.674000
COPY_TO 10 2 9 699.779000
COPY_TO 10 2 10 692.238000
COPY_FROM 10 2 1 497.979000
COPY_FROM 10 2 2 513.060000
COPY_FROM 10 2 3 502.765000
COPY_FROM 10 2 4 509.832000
COPY_FROM 10 2 5 507.076000
COPY_FROM 10 2 6 501.886000
COPY_FROM 10 2 7 503.953000
COPY_FROM 10 2 8 509.601000
COPY_FROM 10 2 9 508.680000
COPY_FROM 10 2 10 497.768000
COPY_TO 10 3 1 1036.252000
COPY_TO 10 3 2 1011.853000
COPY_TO 10 3 3 1022.256000
COPY_TO 10 3 4 1034.388000
COPY_TO 10 3 5 1011.247000
COPY_TO 10 3 6 1042.124000
COPY_TO 10 3 7 1040.866000
COPY_TO 10 3 8 1025.704000
COPY_TO 10 3 9 1023.673000
COPY_TO 10 3 10 1061.591000
COPY_FROM 10 3 1 733.149000
COPY_FROM 10 3 2 743.642000
COPY_FROM 10 3 3 752.712000
COPY_FROM 10 3 4 735.685000
COPY_FROM 10 3 5 743.496000
COPY_FROM 10 3 6 749.289000
COPY_FROM 10 3 7 747.307000
COPY_FROM 10 3 8 750.439000
COPY_FROM 10 3 9 747.840000
COPY_FROM 10 3 10 746.275000
COPY_TO 10 4 1 1339.158000
COPY_TO 10 4 2 1349.486000
COPY_TO 10 4 3 1391.879000
COPY_TO 10 4 4 1402.481000
COPY_TO 10 4 5 1402.359000
COPY_TO 10 4 6 1349.511000
COPY_TO 10 4 7 1395.431000
COPY_TO 10 4 8 1395.865000
COPY_TO 10 4 9 1352.711000
COPY_TO 10 4 10 1335.961000
COPY_FROM 10 4 1 988.278000
COPY_FROM 10 4 2 988.250000
COPY_FROM 10 4 3 986.391000
COPY_FROM 10 4 4 992.929000
COPY_FROM 10 4 5 984.924000
COPY_FROM 10 4 6 989.783000
COPY_FROM 10 4 7 984.885000
COPY_FROM 10 4 8 977.104000
COPY_FROM 10 4 9 991.286000
COPY_FROM 10 4 10 984.057000
COPY_TO 10 5 1 1714.117000
COPY_TO 10 5 2 1737.382000
COPY_TO 10 5 3 1744.501000
COPY_TO 10 5 4 1705.814000
COPY_TO 10 5 5 1724.631000
COPY_TO 10 5 6 1670.422000
COPY_TO 10 5 7 1724.061000
COPY_TO 10 5 8 1741.960000
COPY_TO 10 5 9 1698.542000
COPY_TO 10 5 10 1703.680000
COPY_FROM 10 5 1 1236.786000
COPY_FROM 10 5 2 1228.271000
COPY_FROM 10 5 3 1233.229000
COPY_FROM 10 5 4 1223.438000
COPY_FROM 10 5 5 1218.269000
COPY_FROM 10 5 6 1215.843000
COPY_FROM 10 5 7 1218.998000
COPY_FROM 10 5 8 1223.761000
COPY_FROM 10 5 9 1237.311000
COPY_FROM 10 5 10 1224.098000
COPY_TO 10 6 1 2034.971000
COPY_TO 10 6 2 2086.575000
COPY_TO 10 6 3 2061.166000
COPY_TO 10 6 4 2028.774000
COPY_TO 10 6 5 1976.820000
COPY_TO 10 6 6 2048.341000
COPY_TO 10 6 7 2126.830000
COPY_TO 10 6 8 2113.916000
COPY_TO 10 6 9 2044.993000
COPY_TO 10 6 10 2059.930000
COPY_FROM 10 6 1 1460.496000
COPY_FROM 10 6 2 1455.160000
COPY_FROM 10 6 3 1472.230000
COPY_FROM 10 6 4 1466.294000
COPY_FROM 10 6 5 1470.005000
COPY_FROM 10 6 6 1460.124000
COPY_FROM 10 6 7 1484.157000
COPY_FROM 10 6 8 1498.308000
COPY_FROM 10 6 9 1472.033000
COPY_FROM 10 6 10 1464.715000
COPY_TO 10 7 1 2392.091000
COPY_TO 10 7 2 2376.371000
COPY_TO 10 7 3 2409.333000
COPY_TO 10 7 4 2436.201000
COPY_TO 10 7 5 2406.606000
COPY_TO 10 7 6 2415.106000
COPY_TO 10 7 7 2460.604000
COPY_TO 10 7 8 2407.684000
COPY_TO 10 7 9 2352.239000
COPY_TO 10 7 10 2453.835000
COPY_FROM 10 7 1 1720.389000
COPY_FROM 10 7 2 1716.569000
COPY_FROM 10 7 3 1724.858000
COPY_FROM 10 7 4 1714.529000
COPY_FROM 10 7 5 1704.039000
COPY_FROM 10 7 6 1723.536000
COPY_FROM 10 7 7 1725.329000
COPY_FROM 10 7 8 1690.714000
COPY_FROM 10 7 9 1726.614000
COPY_FROM 10 7 10 1740.956000
COPY_TO 10 8 1 2796.200000
COPY_TO 10 8 2 2761.445000
COPY_TO 10 8 3 2753.313000
COPY_TO 10 8 4 2767.549000
COPY_TO 10 8 5 2759.920000
COPY_TO 10 8 6 2753.090000
COPY_TO 10 8 7 2766.374000
COPY_TO 10 8 8 2758.385000
COPY_TO 10 8 9 2822.724000
COPY_TO 10 8 10 2746.903000
COPY_FROM 10 8 1 1963.436000
COPY_FROM 10 8 2 1965.409000
COPY_FROM 10 8 3 1978.345000
COPY_FROM 10 8 4 1957.258000
COPY_FROM 10 8 5 1948.144000
COPY_FROM 10 8 6 1960.546000
COPY_FROM 10 8 7 1985.631000
COPY_FROM 10 8 8 1928.848000
COPY_FROM 10 8 9 1932.803000
COPY_FROM 10 8 10 1950.939000
COPY_TO 10 9 1 3101.821000
COPY_TO 10 9 2 3119.955000
COPY_TO 10 9 3 3071.974000
COPY_TO 10 9 4 3058.962000
COPY_TO 10 9 5 3100.206000
COPY_TO 10 9 6 3085.071000
COPY_TO 10 9 7 3099.553000
COPY_TO 10 9 8 3133.255000
COPY_TO 10 9 9 3112.448000
COPY_TO 10 9 10 3078.218000
COPY_FROM 10 9 1 2231.402000
COPY_FROM 10 9 2 2270.319000
COPY_FROM 10 9 3 2212.585000
COPY_FROM 10 9 4 2214.820000
COPY_FROM 10 9 5 2182.791000
COPY_FROM 10 9 6 2297.706000
COPY_FROM 10 9 7 2203.188000
COPY_FROM 10 9 8 2275.390000
COPY_FROM 10 9 9 2191.008000
COPY_FROM 10 9 10 2202.305000
COPY_TO 10 10 1 3451.928000
COPY_TO 10 10 2 3434.760000
COPY_TO 10 10 3 3473.176000
COPY_TO 10 10 4 3483.251000
COPY_TO 10 10 5 3432.006000
COPY_TO 10 10 6 3423.612000
COPY_TO 10 10 7 3444.291000
COPY_TO 10 10 8 3425.827000
COPY_TO 10 10 9 3411.279000
COPY_TO 10 10 10 3454.997000
COPY_FROM 10 10 1 2447.611000
COPY_FROM 10 10 2 2462.314000
COPY_FROM 10 10 3 2435.629000
COPY_FROM 10 10 4 2536.588000
COPY_FROM 10 10 5 2472.294000
COPY_FROM 10 10 6 2461.858000
COPY_FROM 10 10 7 2451.032000
COPY_FROM 10 10 8 2430.200000
COPY_FROM 10 10 9 2493.472000
COPY_FROM 10 10 10 2473.227000
COPY_TO 15 1 1 484.336000
COPY_TO 15 1 2 479.033000
COPY_TO 15 1 3 479.735000
COPY_TO 15 1 4 485.839000
COPY_TO 15 1 5 478.669000
COPY_TO 15 1 6 503.063000
COPY_TO 15 1 7 497.116000
COPY_TO 15 1 8 496.381000
COPY_TO 15 1 9 492.082000
COPY_TO 15 1 10 503.341000
COPY_FROM 15 1 1 344.944000
COPY_FROM 15 1 2 347.820000
COPY_FROM 15 1 3 350.856000
COPY_FROM 15 1 4 343.687000
COPY_FROM 15 1 5 348.509000
COPY_FROM 15 1 6 352.488000
COPY_FROM 15 1 7 347.183000
COPY_FROM 15 1 8 346.262000
COPY_FROM 15 1 9 346.139000
COPY_FROM 15 1 10 349.911000
COPY_TO 15 2 1 962.932000
COPY_TO 15 2 2 963.658000
COPY_TO 15 2 3 962.137000
COPY_TO 15 2 4 962.108000
COPY_TO 15 2 5 970.632000
COPY_TO 15 2 6 953.700000
COPY_TO 15 2 7 981.138000
COPY_TO 15 2 8 973.898000
COPY_TO 15 2 9 970.741000
COPY_TO 15 2 10 948.693000
COPY_FROM 15 2 1 665.328000
COPY_FROM 15 2 2 676.310000
COPY_FROM 15 2 3 671.458000
COPY_FROM 15 2 4 670.664000
COPY_FROM 15 2 5 679.016000
COPY_FROM 15 2 6 669.444000
COPY_FROM 15 2 7 667.946000
COPY_FROM 15 2 8 667.764000
COPY_FROM 15 2 9 674.499000
COPY_FROM 15 2 10 671.073000
COPY_TO 15 3 1 1420.086000
COPY_TO 15 3 2 1448.308000
COPY_TO 15 3 3 1454.913000
COPY_TO 15 3 4 1439.704000
COPY_TO 15 3 5 1446.298000
COPY_TO 15 3 6 1470.852000
COPY_TO 15 3 7 1456.382000
COPY_TO 15 3 8 1458.444000
COPY_TO 15 3 9 1463.900000
COPY_TO 15 3 10 1461.039000
COPY_FROM 15 3 1 1007.392000
COPY_FROM 15 3 2 1001.408000
COPY_FROM 15 3 3 1010.905000
COPY_FROM 15 3 4 1004.810000
COPY_FROM 15 3 5 1000.949000
COPY_FROM 15 3 6 1011.789000
COPY_FROM 15 3 7 1009.358000
COPY_FROM 15 3 8 1010.479000
COPY_FROM 15 3 9 999.429000
COPY_FROM 15 3 10 1021.780000
COPY_TO 15 4 1 1933.560000
COPY_TO 15 4 2 1903.286000
COPY_TO 15 4 3 1931.942000
COPY_TO 15 4 4 1963.633000
COPY_TO 15 4 5 1936.343000
COPY_TO 15 4 6 1944.860000
COPY_TO 15 4 7 1924.043000
COPY_TO 15 4 8 1923.471000
COPY_TO 15 4 9 1890.255000
COPY_TO 15 4 10 1960.720000
COPY_FROM 15 4 1 1368.884000
COPY_FROM 15 4 2 1339.628000
COPY_FROM 15 4 3 1311.420000
COPY_FROM 15 4 4 1334.597000
COPY_FROM 15 4 5 1351.018000
COPY_FROM 15 4 6 1344.878000
COPY_FROM 15 4 7 1355.443000
COPY_FROM 15 4 8 1339.170000
COPY_FROM 15 4 9 1340.700000
COPY_FROM 15 4 10 1326.241000
COPY_TO 15 5 1 2318.844000
COPY_TO 15 5 2 2402.419000
COPY_TO 15 5 3 2396.781000
COPY_TO 15 5 4 2423.735000
COPY_TO 15 5 5 2409.499000
COPY_TO 15 5 6 2428.851000
COPY_TO 15 5 7 2348.598000
COPY_TO 15 5 8 2322.848000
COPY_TO 15 5 9 2352.590000
COPY_TO 15 5 10 2445.076000
COPY_FROM 15 5 1 1648.351000
COPY_FROM 15 5 2 1651.210000
COPY_FROM 15 5 3 1652.014000
COPY_FROM 15 5 4 1657.594000
COPY_FROM 15 5 5 1664.718000
COPY_FROM 15 5 6 1659.842000
COPY_FROM 15 5 7 1624.535000
COPY_FROM 15 5 8 1674.149000
COPY_FROM 15 5 9 1647.591000
COPY_FROM 15 5 10 1669.124000
COPY_TO 15 6 1 2881.853000
COPY_TO 15 6 2 2868.673000
COPY_TO 15 6 3 2965.197000
COPY_TO 15 6 4 2884.662000
COPY_TO 15 6 5 2838.135000
COPY_TO 15 6 6 2916.165000
COPY_TO 15 6 7 2886.197000
COPY_TO 15 6 8 2933.154000
COPY_TO 15 6 9 2928.349000
COPY_TO 15 6 10 2901.545000
COPY_FROM 15 6 1 2013.017000
COPY_FROM 15 6 2 1978.835000
COPY_FROM 15 6 3 2004.485000
COPY_FROM 15 6 4 1987.586000
COPY_FROM 15 6 5 1975.135000
COPY_FROM 15 6 6 1989.522000
COPY_FROM 15 6 7 1988.856000
COPY_FROM 15 6 8 1983.815000
COPY_FROM 15 6 9 2013.150000
COPY_FROM 15 6 10 1997.074000
COPY_TO 15 7 1 3335.493000
COPY_TO 15 7 2 3357.154000
COPY_TO 15 7 3 3347.085000
COPY_TO 15 7 4 3296.994000
COPY_TO 15 7 5 3376.383000
COPY_TO 15 7 6 3368.554000
COPY_TO 15 7 7 3401.287000
COPY_TO 15 7 8 3359.792000
COPY_TO 15 7 9 3351.542000
COPY_TO 15 7 10 3359.085000
COPY_FROM 15 7 1 2341.669000
COPY_FROM 15 7 2 2318.762000
COPY_FROM 15 7 3 2302.094000
COPY_FROM 15 7 4 2295.824000
COPY_FROM 15 7 5 2282.052000
COPY_FROM 15 7 6 2285.734000
COPY_FROM 15 7 7 2286.871000
COPY_FROM 15 7 8 2301.570000
COPY_FROM 15 7 9 2294.122000
COPY_FROM 15 7 10 2318.100000
COPY_TO 15 8 1 3838.944000
COPY_TO 15 8 2 3832.013000
COPY_TO 15 8 3 3794.855000
COPY_TO 15 8 4 3829.692000
COPY_TO 15 8 5 3902.267000
COPY_TO 15 8 6 3876.061000
COPY_TO 15 8 7 3844.652000
COPY_TO 15 8 8 3819.619000
COPY_TO 15 8 9 3891.511000
COPY_TO 15 8 10 3902.708000
COPY_FROM 15 8 1 2665.396000
COPY_FROM 15 8 2 2677.914000
COPY_FROM 15 8 3 2666.726000
COPY_FROM 15 8 4 2633.747000
COPY_FROM 15 8 5 2632.702000
COPY_FROM 15 8 6 2664.116000
COPY_FROM 15 8 7 2614.453000
COPY_FROM 15 8 8 2662.111000
COPY_FROM 15 8 9 2660.616000
COPY_FROM 15 8 10 2695.048000
COPY_TO 15 9 1 4341.815000
COPY_TO 15 9 2 4302.586000
COPY_TO 15 9 3 4281.296000
COPY_TO 15 9 4 4260.384000
COPY_TO 15 9 5 4354.295000
COPY_TO 15 9 6 4395.239000
COPY_TO 15 9 7 4294.927000
COPY_TO 15 9 8 4299.131000
COPY_TO 15 9 9 4324.381000
COPY_TO 15 9 10 4308.416000
COPY_FROM 15 9 1 2952.762000
COPY_FROM 15 9 2 2976.541000
COPY_FROM 15 9 3 2980.895000
COPY_FROM 15 9 4 2988.607000
COPY_FROM 15 9 5 2931.639000
COPY_FROM 15 9 6 2980.360000
COPY_FROM 15 9 7 2987.142000
COPY_FROM 15 9 8 2942.020000
COPY_FROM 15 9 9 2956.429000
COPY_FROM 15 9 10 2976.833000
COPY_TO 15 10 1 4908.128000
COPY_TO 15 10 2 4808.306000
COPY_TO 15 10 3 4884.962000
COPY_TO 15 10 4 4871.861000
COPY_TO 15 10 5 4793.649000
COPY_TO 15 10 6 4783.691000
COPY_TO 15 10 7 4953.107000
COPY_TO 15 10 8 4770.645000
COPY_TO 15 10 9 4830.319000
COPY_TO 15 10 10 4817.374000
COPY_FROM 15 10 1 3316.914000
COPY_FROM 15 10 2 3317.386000
COPY_FROM 15 10 3 3304.798000
COPY_FROM 15 10 4 3260.573000
COPY_FROM 15 10 5 3275.390000
COPY_FROM 15 10 6 3298.207000
COPY_FROM 15 10 7 3286.026000
COPY_FROM 15 10 8 3363.954000
COPY_FROM 15 10 9 3294.820000
COPY_FROM 15 10 10 3306.407000
COPY_TO 20 1 1 619.998000
COPY_TO 20 1 2 616.942000
COPY_TO 20 1 3 624.587000
COPY_TO 20 1 4 633.838000
COPY_TO 20 1 5 651.659000
COPY_TO 20 1 6 638.405000
COPY_TO 20 1 7 629.828000
COPY_TO 20 1 8 621.210000
COPY_TO 20 1 9 635.503000
COPY_TO 20 1 10 629.262000
COPY_FROM 20 1 1 433.467000
COPY_FROM 20 1 2 431.611000
COPY_FROM 20 1 3 438.673000
COPY_FROM 20 1 4 439.864000
COPY_FROM 20 1 5 436.883000
COPY_FROM 20 1 6 436.025000
COPY_FROM 20 1 7 447.105000
COPY_FROM 20 1 8 452.754000
COPY_FROM 20 1 9 434.757000
COPY_FROM 20 1 10 439.372000
COPY_TO 20 2 1 1215.557000
COPY_TO 20 2 2 1198.834000
COPY_TO 20 2 3 1248.734000
COPY_TO 20 2 4 1224.716000
COPY_TO 20 2 5 1221.355000
COPY_TO 20 2 6 1235.157000
COPY_TO 20 2 7 1213.212000
COPY_TO 20 2 8 1251.544000
COPY_TO 20 2 9 1211.466000
COPY_TO 20 2 10 1232.067000
COPY_FROM 20 2 1 853.265000
COPY_FROM 20 2 2 861.634000
COPY_FROM 20 2 3 875.109000
COPY_FROM 20 2 4 866.576000
COPY_FROM 20 2 5 869.608000
COPY_FROM 20 2 6 867.634000
COPY_FROM 20 2 7 868.359000
COPY_FROM 20 2 8 879.867000
COPY_FROM 20 2 9 856.513000
COPY_FROM 20 2 10 846.929000
COPY_TO 20 3 1 1853.167000
COPY_TO 20 3 2 1908.958000
COPY_TO 20 3 3 1854.300000
COPY_TO 20 3 4 1854.920000
COPY_TO 20 3 5 1908.171000
COPY_TO 20 3 6 1875.182000
COPY_TO 20 3 7 1858.945000
COPY_TO 20 3 8 1836.676000
COPY_TO 20 3 9 1892.760000
COPY_TO 20 3 10 1832.188000
COPY_FROM 20 3 1 1269.621000
COPY_FROM 20 3 2 1268.794000
COPY_FROM 20 3 3 1306.010000
COPY_FROM 20 3 4 1268.746000
COPY_FROM 20 3 5 1285.443000
COPY_FROM 20 3 6 1272.459000
COPY_FROM 20 3 7 1284.552000
COPY_FROM 20 3 8 1277.634000
COPY_FROM 20 3 9 1283.592000
COPY_FROM 20 3 10 1277.291000
COPY_TO 20 4 1 2366.791000
COPY_TO 20 4 2 2467.617000
COPY_TO 20 4 3 2503.922000
COPY_TO 20 4 4 2419.396000
COPY_TO 20 4 5 2362.517000
COPY_TO 20 4 6 2436.106000
COPY_TO 20 4 7 2515.537000
COPY_TO 20 4 8 2444.051000
COPY_TO 20 4 9 2368.470000
COPY_TO 20 4 10 2476.241000
COPY_FROM 20 4 1 1686.377000
COPY_FROM 20 4 2 1766.247000
COPY_FROM 20 4 3 1765.013000
COPY_FROM 20 4 4 1710.638000
COPY_FROM 20 4 5 1681.944000
COPY_FROM 20 4 6 1672.305000
COPY_FROM 20 4 7 1680.594000
COPY_FROM 20 4 8 1692.007000
COPY_FROM 20 4 9 1696.334000
COPY_FROM 20 4 10 1673.502000
COPY_TO 20 5 1 3044.926000
COPY_TO 20 5 2 2999.139000
COPY_TO 20 5 3 3012.201000
COPY_TO 20 5 4 3079.507000
COPY_TO 20 5 5 3084.210000
COPY_TO 20 5 6 3106.328000
COPY_TO 20 5 7 3107.643000
COPY_TO 20 5 8 3103.127000
COPY_TO 20 5 9 3098.074000
COPY_TO 20 5 10 3071.407000
COPY_FROM 20 5 1 2110.909000
COPY_FROM 20 5 2 2119.924000
COPY_FROM 20 5 3 2094.429000
COPY_FROM 20 5 4 2113.787000
COPY_FROM 20 5 5 2093.251000
COPY_FROM 20 5 6 2103.724000
COPY_FROM 20 5 7 2163.264000
COPY_FROM 20 5 8 2110.832000
COPY_FROM 20 5 9 2120.593000
COPY_FROM 20 5 10 2108.865000
COPY_TO 20 6 1 3778.026000
COPY_TO 20 6 2 3660.842000
COPY_TO 20 6 3 3586.255000
COPY_TO 20 6 4 3621.287000
COPY_TO 20 6 5 3765.054000
COPY_TO 20 6 6 3730.942000
COPY_TO 20 6 7 3700.704000
COPY_TO 20 6 8 3683.990000
COPY_TO 20 6 9 3654.364000
COPY_TO 20 6 10 3711.707000
COPY_FROM 20 6 1 2512.796000
COPY_FROM 20 6 2 2499.849000
COPY_FROM 20 6 3 2581.643000
COPY_FROM 20 6 4 2540.972000
COPY_FROM 20 6 5 2522.357000
COPY_FROM 20 6 6 2519.327000
COPY_FROM 20 6 7 2539.536000
COPY_FROM 20 6 8 2529.492000
COPY_FROM 20 6 9 2527.186000
COPY_FROM 20 6 10 2537.575000
COPY_TO 20 7 1 4302.273000
COPY_TO 20 7 2 4320.033000
COPY_TO 20 7 3 4234.169000
COPY_TO 20 7 4 4347.949000
COPY_TO 20 7 5 4297.509000
COPY_TO 20 7 6 4348.086000
COPY_TO 20 7 7 4302.051000
COPY_TO 20 7 8 4325.364000
COPY_TO 20 7 9 4322.654000
COPY_TO 20 7 10 4271.526000
COPY_FROM 20 7 1 2911.560000
COPY_FROM 20 7 2 2940.254000
COPY_FROM 20 7 3 2980.597000
COPY_FROM 20 7 4 2973.070000
COPY_FROM 20 7 5 2933.554000
COPY_FROM 20 7 6 2953.611000
COPY_FROM 20 7 7 2922.042000
COPY_FROM 20 7 8 2906.997000
COPY_FROM 20 7 9 2904.686000
COPY_FROM 20 7 10 2941.453000
COPY_TO 20 8 1 4764.222000
COPY_TO 20 8 2 4728.320000
COPY_TO 20 8 3 4795.743000
COPY_TO 20 8 4 4882.833000
COPY_TO 20 8 5 4815.518000
COPY_TO 20 8 6 4886.483000
COPY_TO 20 8 7 4924.319000
COPY_TO 20 8 8 4838.255000
COPY_TO 20 8 9 4863.534000
COPY_TO 20 8 10 4925.173000
COPY_FROM 20 8 1 3377.310000
COPY_FROM 20 8 2 3374.520000
COPY_FROM 20 8 3 3415.924000
COPY_FROM 20 8 4 3359.085000
COPY_FROM 20 8 5 3354.984000
COPY_FROM 20 8 6 3314.657000
COPY_FROM 20 8 7 3315.929000
COPY_FROM 20 8 8 3446.995000
COPY_FROM 20 8 9 3368.091000
COPY_FROM 20 8 10 3390.674000
COPY_TO 20 9 1 5463.960000
COPY_TO 20 9 2 5463.921000
COPY_TO 20 9 3 5378.138000
COPY_TO 20 9 4 5535.958000
COPY_TO 20 9 5 5503.000000
COPY_TO 20 9 6 5457.850000
COPY_TO 20 9 7 5435.157000
COPY_TO 20 9 8 5422.457000
COPY_TO 20 9 9 5482.427000
COPY_TO 20 9 10 5495.809000
COPY_FROM 20 9 1 3876.496000
COPY_FROM 20 9 2 3770.921000
COPY_FROM 20 9 3 3729.432000
COPY_FROM 20 9 4 3739.708000
COPY_FROM 20 9 5 3787.856000
COPY_FROM 20 9 6 3757.324000
COPY_FROM 20 9 7 3793.676000
COPY_FROM 20 9 8 3840.151000
COPY_FROM 20 9 9 3721.829000
COPY_FROM 20 9 10 3769.584000
COPY_TO 20 10 1 6021.466000
COPY_TO 20 10 2 6050.644000
COPY_TO 20 10 3 6035.796000
COPY_TO 20 10 4 5991.765000
COPY_TO 20 10 5 6095.925000
COPY_TO 20 10 6 6006.453000
COPY_TO 20 10 7 6043.915000
COPY_TO 20 10 8 6184.330000
COPY_TO 20 10 9 5997.352000
COPY_TO 20 10 10 6142.882000
COPY_FROM 20 10 1 4220.218000
COPY_FROM 20 10 2 4160.915000
COPY_FROM 20 10 3 4172.628000
COPY_FROM 20 10 4 4183.532000
COPY_FROM 20 10 5 4208.204000
COPY_FROM 20 10 6 4232.293000
COPY_FROM 20 10 7 4188.968000
COPY_FROM 20 10 8 4191.494000
COPY_FROM 20 10 9 4196.841000
COPY_FROM 20 10 10 4172.418000
COPY_TO 25 1 1 774.678000
COPY_TO 25 1 2 787.791000
COPY_TO 25 1 3 773.815000
COPY_TO 25 1 4 744.220000
COPY_TO 25 1 5 763.742000
COPY_TO 25 1 6 764.779000
COPY_TO 25 1 7 763.397000
COPY_TO 25 1 8 750.529000
COPY_TO 25 1 9 775.028000
COPY_TO 25 1 10 763.085000
COPY_FROM 25 1 1 524.445000
COPY_FROM 25 1 2 519.951000
COPY_FROM 25 1 3 516.212000
COPY_FROM 25 1 4 516.155000
COPY_FROM 25 1 5 519.686000
COPY_FROM 25 1 6 524.260000
COPY_FROM 25 1 7 521.384000
COPY_FROM 25 1 8 516.947000
COPY_FROM 25 1 9 516.268000
COPY_FROM 25 1 10 513.815000
COPY_TO 25 2 1 1513.097000
COPY_TO 25 2 2 1516.435000
COPY_TO 25 2 3 1514.322000
COPY_TO 25 2 4 1515.332000
COPY_TO 25 2 5 1539.159000
COPY_TO 25 2 6 1504.517000
COPY_TO 25 2 7 1551.701000
COPY_TO 25 2 8 1536.408000
COPY_TO 25 2 9 1506.469000
COPY_TO 25 2 10 1507.693000
COPY_FROM 25 2 1 1031.906000
COPY_FROM 25 2 2 1011.518000
COPY_FROM 25 2 3 1015.601000
COPY_FROM 25 2 4 1022.738000
COPY_FROM 25 2 5 1024.219000
COPY_FROM 25 2 6 1018.943000
COPY_FROM 25 2 7 1008.076000
COPY_FROM 25 2 8 1008.687000
COPY_FROM 25 2 9 1019.874000
COPY_FROM 25 2 10 1010.362000
COPY_TO 25 3 1 2275.840000
COPY_TO 25 3 2 2292.456000
COPY_TO 25 3 3 2304.261000
COPY_TO 25 3 4 2260.663000
COPY_TO 25 3 5 2274.911000
COPY_TO 25 3 6 2307.456000
COPY_TO 25 3 7 2304.885000
COPY_TO 25 3 8 2328.952000
COPY_TO 25 3 9 2205.891000
COPY_TO 25 3 10 2252.140000
COPY_FROM 25 3 1 1491.799000
COPY_FROM 25 3 2 1508.012000
COPY_FROM 25 3 3 1507.554000
COPY_FROM 25 3 4 1540.556000
COPY_FROM 25 3 5 1538.755000
COPY_FROM 25 3 6 1524.962000
COPY_FROM 25 3 7 1519.040000
COPY_FROM 25 3 8 1527.385000
COPY_FROM 25 3 9 1542.953000
COPY_FROM 25 3 10 1523.412000
COPY_TO 25 4 1 3052.605000
COPY_TO 25 4 2 2998.820000
COPY_TO 25 4 3 2984.156000
COPY_TO 25 4 4 3034.054000
COPY_TO 25 4 5 3035.638000
COPY_TO 25 4 6 3021.914000
COPY_TO 25 4 7 3086.029000
COPY_TO 25 4 8 3104.967000
COPY_TO 25 4 9 3084.419000
COPY_TO 25 4 10 3052.696000
COPY_FROM 25 4 1 2019.843000
COPY_FROM 25 4 2 2010.303000
COPY_FROM 25 4 3 2008.544000
COPY_FROM 25 4 4 2017.551000
COPY_FROM 25 4 5 1983.106000
COPY_FROM 25 4 6 1972.640000
COPY_FROM 25 4 7 1998.370000
COPY_FROM 25 4 8 1972.399000
COPY_FROM 25 4 9 2014.721000
COPY_FROM 25 4 10 1990.860000
COPY_TO 25 5 1 3803.703000
COPY_TO 25 5 2 3801.972000
COPY_TO 25 5 3 3732.563000
COPY_TO 25 5 4 3844.295000
COPY_TO 25 5 5 3843.996000
COPY_TO 25 5 6 3860.533000
COPY_TO 25 5 7 3885.893000
COPY_TO 25 5 8 3901.853000
COPY_TO 25 5 9 3811.751000
COPY_TO 25 5 10 3830.153000
COPY_FROM 25 5 1 2512.122000
COPY_FROM 25 5 2 2485.190000
COPY_FROM 25 5 3 2514.064000
COPY_FROM 25 5 4 2629.482000
COPY_FROM 25 5 5 2574.073000
COPY_FROM 25 5 6 2554.008000
COPY_FROM 25 5 7 2554.302000
COPY_FROM 25 5 8 2538.815000
COPY_FROM 25 5 9 2557.007000
COPY_FROM 25 5 10 2498.580000
COPY_TO 25 6 1 4623.929000
COPY_TO 25 6 2 4565.644000
COPY_TO 25 6 3 4579.721000
COPY_TO 25 6 4 4524.352000
COPY_TO 25 6 5 4470.642000
COPY_TO 25 6 6 4563.316000
COPY_TO 25 6 7 4576.716000
COPY_TO 25 6 8 4491.117000
COPY_TO 25 6 9 4544.761000
COPY_TO 25 6 10 4424.612000
COPY_FROM 25 6 1 3018.827000
COPY_FROM 25 6 2 2978.490000
COPY_FROM 25 6 3 2995.232000
COPY_FROM 25 6 4 2967.654000
COPY_FROM 25 6 5 3029.289000
COPY_FROM 25 6 6 2956.739000
COPY_FROM 25 6 7 2964.034000
COPY_FROM 25 6 8 2969.406000
COPY_FROM 25 6 9 2990.859000
COPY_FROM 25 6 10 3004.016000
COPY_TO 25 7 1 5388.767000
COPY_TO 25 7 2 5261.497000
COPY_TO 25 7 3 5266.503000
COPY_TO 25 7 4 5328.781000
COPY_TO 25 7 5 5331.428000
COPY_TO 25 7 6 5342.277000
COPY_TO 25 7 7 5309.748000
COPY_TO 25 7 8 5396.271000
COPY_TO 25 7 9 5242.006000
COPY_TO 25 7 10 5204.319000
COPY_FROM 25 7 1 3526.509000
COPY_FROM 25 7 2 3533.526000
COPY_FROM 25 7 3 3574.351000
COPY_FROM 25 7 4 3550.997000
COPY_FROM 25 7 5 3519.623000
COPY_FROM 25 7 6 3462.743000
COPY_FROM 25 7 7 3504.243000
COPY_FROM 25 7 8 3521.010000
COPY_FROM 25 7 9 3431.482000
COPY_FROM 25 7 10 3419.169000
COPY_TO 25 8 1 6097.554000
COPY_TO 25 8 2 5984.897000
COPY_TO 25 8 3 6040.903000
COPY_TO 25 8 4 6147.806000
COPY_TO 25 8 5 6037.164000
COPY_TO 25 8 6 5987.661000
COPY_TO 25 8 7 6096.899000
COPY_TO 25 8 8 6073.973000
COPY_TO 25 8 9 6105.735000
COPY_TO 25 8 10 5974.114000
COPY_FROM 25 8 1 3988.738000
COPY_FROM 25 8 2 4009.777000
COPY_FROM 25 8 3 4027.431000
COPY_FROM 25 8 4 3976.333000
COPY_FROM 25 8 5 3961.928000
COPY_FROM 25 8 6 3974.345000
COPY_FROM 25 8 7 4029.581000
COPY_FROM 25 8 8 4025.947000
COPY_FROM 25 8 9 3977.926000
COPY_FROM 25 8 10 4035.786000
COPY_TO 25 9 1 6753.774000
COPY_TO 25 9 2 6700.288000
COPY_TO 25 9 3 6880.717000
COPY_TO 25 9 4 6825.173000
COPY_TO 25 9 5 6697.153000
COPY_TO 25 9 6 6785.494000
COPY_TO 25 9 7 6879.979000
COPY_TO 25 9 8 6743.111000
COPY_TO 25 9 9 6850.346000
COPY_TO 25 9 10 6787.185000
COPY_FROM 25 9 1 4517.219000
COPY_FROM 25 9 2 4531.329000
COPY_FROM 25 9 3 4529.439000
COPY_FROM 25 9 4 4481.905000
COPY_FROM 25 9 5 4518.109000
COPY_FROM 25 9 6 4502.731000
COPY_FROM 25 9 7 4473.914000
COPY_FROM 25 9 8 4471.436000
COPY_FROM 25 9 9 4500.187000
COPY_FROM 25 9 10 4479.554000
COPY_TO 25 10 1 7557.810000
COPY_TO 25 10 2 7559.711000
COPY_TO 25 10 3 7542.392000
COPY_TO 25 10 4 7291.018000
COPY_TO 25 10 5 7504.865000
COPY_TO 25 10 6 7432.488000
COPY_TO 25 10 7 7432.530000
COPY_TO 25 10 8 7474.229000
COPY_TO 25 10 9 7384.188000
COPY_TO 25 10 10 7551.992000
COPY_FROM 25 10 1 4964.734000
COPY_FROM 25 10 2 5042.329000
COPY_FROM 25 10 3 5013.357000
COPY_FROM 25 10 4 4986.712000
COPY_FROM 25 10 5 4996.862000
COPY_FROM 25 10 6 4945.983000
COPY_FROM 25 10 7 4994.463000
COPY_FROM 25 10 8 4944.533000
COPY_FROM 25 10 9 5018.457000
COPY_FROM 25 10 10 4967.123000
COPY_TO 30 1 1 905.785000
COPY_TO 30 1 2 919.553000
COPY_TO 30 1 3 891.263000
COPY_TO 30 1 4 923.963000
COPY_TO 30 1 5 901.843000
COPY_TO 30 1 6 915.491000
COPY_TO 30 1 7 896.540000
COPY_TO 30 1 8 906.324000
COPY_TO 30 1 9 892.686000
COPY_TO 30 1 10 924.998000
COPY_FROM 30 1 1 587.472000
COPY_FROM 30 1 2 605.176000
COPY_FROM 30 1 3 591.641000
COPY_FROM 30 1 4 622.076000
COPY_FROM 30 1 5 604.110000
COPY_FROM 30 1 6 619.221000
COPY_FROM 30 1 7 612.524000
COPY_FROM 30 1 8 603.729000
COPY_FROM 30 1 9 595.670000
COPY_FROM 30 1 10 598.395000
COPY_TO 30 2 1 1799.114000
COPY_TO 30 2 2 1802.407000
COPY_TO 30 2 3 1813.957000
COPY_TO 30 2 4 1765.727000
COPY_TO 30 2 5 1798.418000
COPY_TO 30 2 6 1817.917000
COPY_TO 30 2 7 1780.496000
COPY_TO 30 2 8 1772.734000
COPY_TO 30 2 9 1771.637000
COPY_TO 30 2 10 1837.537000
COPY_FROM 30 2 1 1186.556000
COPY_FROM 30 2 2 1189.396000
COPY_FROM 30 2 3 1188.794000
COPY_FROM 30 2 4 1196.751000
COPY_FROM 30 2 5 1208.097000
COPY_FROM 30 2 6 1195.639000
COPY_FROM 30 2 7 1181.028000
COPY_FROM 30 2 8 1177.701000
COPY_FROM 30 2 9 1181.959000
COPY_FROM 30 2 10 1171.377000
COPY_TO 30 3 1 2668.510000
COPY_TO 30 3 2 2662.493000
COPY_TO 30 3 3 2659.467000
COPY_TO 30 3 4 2629.276000
COPY_TO 30 3 5 2630.829000
COPY_TO 30 3 6 2632.760000
COPY_TO 30 3 7 2642.559000
COPY_TO 30 3 8 2675.854000
COPY_TO 30 3 9 2686.168000
COPY_TO 30 3 10 2703.022000
COPY_FROM 30 3 1 1749.300000
COPY_FROM 30 3 2 1732.106000
COPY_FROM 30 3 3 1744.452000
COPY_FROM 30 3 4 1762.979000
COPY_FROM 30 3 5 1758.033000
COPY_FROM 30 3 6 1772.605000
COPY_FROM 30 3 7 1754.809000
COPY_FROM 30 3 8 1751.785000
COPY_FROM 30 3 9 1762.331000
COPY_FROM 30 3 10 1745.872000
COPY_TO 30 4 1 3575.638000
COPY_TO 30 4 2 3540.611000
COPY_TO 30 4 3 3555.631000
COPY_TO 30 4 4 3508.023000
COPY_TO 30 4 5 3548.267000
COPY_TO 30 4 6 3530.229000
COPY_TO 30 4 7 3624.151000
COPY_TO 30 4 8 3549.913000
COPY_TO 30 4 9 3579.071000
COPY_TO 30 4 10 3548.049000
COPY_FROM 30 4 1 2333.686000
COPY_FROM 30 4 2 2354.055000
COPY_FROM 30 4 3 2329.804000
COPY_FROM 30 4 4 2393.154000
COPY_FROM 30 4 5 2357.848000
COPY_FROM 30 4 6 2351.915000
COPY_FROM 30 4 7 2340.428000
COPY_FROM 30 4 8 2364.307000
COPY_FROM 30 4 9 2353.620000
COPY_FROM 30 4 10 2363.992000
COPY_TO 30 5 1 4324.843000
COPY_TO 30 5 2 4387.595000
COPY_TO 30 5 3 4416.761000
COPY_TO 30 5 4 4406.291000
COPY_TO 30 5 5 4418.657000
COPY_TO 30 5 6 4432.811000
COPY_TO 30 5 7 4422.989000
COPY_TO 30 5 8 4467.277000
COPY_TO 30 5 9 4474.720000
COPY_TO 30 5 10 4419.907000
COPY_FROM 30 5 1 2911.757000
COPY_FROM 30 5 2 2921.622000
COPY_FROM 30 5 3 2863.662000
COPY_FROM 30 5 4 3017.345000
COPY_FROM 30 5 5 2904.579000
COPY_FROM 30 5 6 2954.328000
COPY_FROM 30 5 7 2965.111000
COPY_FROM 30 5 8 2962.503000
COPY_FROM 30 5 9 2881.468000
COPY_FROM 30 5 10 2932.883000
COPY_TO 30 6 1 5324.111000
COPY_TO 30 6 2 5273.693000
COPY_TO 30 6 3 5477.630000
COPY_TO 30 6 4 5470.590000
COPY_TO 30 6 5 5330.046000
COPY_TO 30 6 6 5314.785000
COPY_TO 30 6 7 5280.238000
COPY_TO 30 6 8 5447.156000
COPY_TO 30 6 9 5470.025000
COPY_TO 30 6 10 5382.615000
COPY_FROM 30 6 1 3519.835000
COPY_FROM 30 6 2 3495.999000
COPY_FROM 30 6 3 3447.579000
COPY_FROM 30 6 4 3503.293000
COPY_FROM 30 6 5 3467.442000
COPY_FROM 30 6 6 3502.490000
COPY_FROM 30 6 7 3539.083000
COPY_FROM 30 6 8 3514.108000
COPY_FROM 30 6 9 3558.769000
COPY_FROM 30 6 10 3557.883000
COPY_TO 30 7 1 6270.765000
COPY_TO 30 7 2 6250.630000
COPY_TO 30 7 3 6291.501000
COPY_TO 30 7 4 6277.021000
COPY_TO 30 7 5 6197.067000
COPY_TO 30 7 6 6204.168000
COPY_TO 30 7 7 6326.866000
COPY_TO 30 7 8 6219.435000
COPY_TO 30 7 9 6229.165000
COPY_TO 30 7 10 6182.055000
COPY_FROM 30 7 1 4064.754000
COPY_FROM 30 7 2 4161.991000
COPY_FROM 30 7 3 4099.098000
COPY_FROM 30 7 4 4098.243000
COPY_FROM 30 7 5 4094.954000
COPY_FROM 30 7 6 4113.331000
COPY_FROM 30 7 7 4162.527000
COPY_FROM 30 7 8 4117.655000
COPY_FROM 30 7 9 4038.147000
COPY_FROM 30 7 10 4247.750000
COPY_TO 30 8 1 7036.335000
COPY_TO 30 8 2 7161.077000
COPY_TO 30 8 3 7198.475000
COPY_TO 30 8 4 7057.568000
COPY_TO 30 8 5 7068.777000
COPY_TO 30 8 6 7145.575000
COPY_TO 30 8 7 7164.393000
COPY_TO 30 8 8 7146.893000
COPY_TO 30 8 9 7263.004000
COPY_TO 30 8 10 7258.462000
COPY_FROM 30 8 1 4709.346000
COPY_FROM 30 8 2 4727.176000
COPY_FROM 30 8 3 4643.916000
COPY_FROM 30 8 4 4646.425000
COPY_FROM 30 8 5 4714.948000
COPY_FROM 30 8 6 4669.370000
COPY_FROM 30 8 7 4649.179000
COPY_FROM 30 8 8 4604.831000
COPY_FROM 30 8 9 4657.557000
COPY_FROM 30 8 10 4672.892000
COPY_TO 30 9 1 7908.138000
COPY_TO 30 9 2 8046.895000
COPY_TO 30 9 3 8140.333000
COPY_TO 30 9 4 8103.733000
COPY_TO 30 9 5 8007.650000
COPY_TO 30 9 6 7955.601000
COPY_TO 30 9 7 8044.544000
COPY_TO 30 9 8 8086.140000
COPY_TO 30 9 9 8062.369000
COPY_TO 30 9 10 7827.011000
COPY_FROM 30 9 1 5204.533000
COPY_FROM 30 9 2 5201.463000
COPY_FROM 30 9 3 5234.632000
COPY_FROM 30 9 4 5236.902000
COPY_FROM 30 9 5 5269.275000
COPY_FROM 30 9 6 5263.596000
COPY_FROM 30 9 7 5192.508000
COPY_FROM 30 9 8 5234.723000
COPY_FROM 30 9 9 5188.671000
COPY_FROM 30 9 10 5160.328000
COPY_TO 30 10 1 8859.946000
COPY_TO 30 10 2 8904.060000
COPY_TO 30 10 3 9075.677000
COPY_TO 30 10 4 8911.511000
COPY_TO 30 10 5 8923.505000
COPY_TO 30 10 6 8955.312000
COPY_TO 30 10 7 9014.532000
COPY_TO 30 10 8 9100.991000
COPY_TO 30 10 9 8978.536000
COPY_TO 30 10 10 8974.878000
COPY_FROM 30 10 1 5820.402000
COPY_FROM 30 10 2 5800.793000
COPY_FROM 30 10 3 5817.289000
COPY_FROM 30 10 4 5766.636000
COPY_FROM 30 10 5 5947.599000
COPY_FROM 30 10 6 5756.134000
COPY_FROM 30 10 7 5764.180000
COPY_FROM 30 10 8 5796.569000
COPY_FROM 30 10 9 5796.612000
COPY_FROM 30 10 10 5849.049000
COPY_TO 5 1 1 226.623000
COPY_TO 5 1 2 227.444000
COPY_TO 5 1 3 214.579000
COPY_TO 5 1 4 218.737000
COPY_TO 5 1 5 218.708000
COPY_TO 5 1 6 221.763000
COPY_TO 5 1 7 212.154000
COPY_TO 5 1 8 219.050000
COPY_TO 5 1 9 225.217000
COPY_TO 5 1 10 219.609000
COPY_FROM 5 1 1 190.806000
COPY_FROM 5 1 2 192.065000
COPY_FROM 5 1 3 192.423000
COPY_FROM 5 1 4 200.560000
COPY_FROM 5 1 5 190.027000
COPY_FROM 5 1 6 190.954000
COPY_FROM 5 1 7 190.775000
COPY_FROM 5 1 8 187.590000
COPY_FROM 5 1 9 194.545000
COPY_FROM 5 1 10 190.831000
COPY_TO 5 2 1 419.239000
COPY_TO 5 2 2 428.527000
COPY_TO 5 2 3 424.408000
COPY_TO 5 2 4 428.882000
COPY_TO 5 2 5 419.476000
COPY_TO 5 2 6 422.894000
COPY_TO 5 2 7 418.744000
COPY_TO 5 2 8 425.265000
COPY_TO 5 2 9 428.402000
COPY_TO 5 2 10 425.687000
COPY_FROM 5 2 1 368.581000
COPY_FROM 5 2 2 368.334000
COPY_FROM 5 2 3 379.807000
COPY_FROM 5 2 4 367.980000
COPY_FROM 5 2 5 364.015000
COPY_FROM 5 2 6 366.088000
COPY_FROM 5 2 7 360.102000
COPY_FROM 5 2 8 366.403000
COPY_FROM 5 2 9 363.637000
COPY_FROM 5 2 10 362.853000
COPY_TO 5 3 1 636.678000
COPY_TO 5 3 2 635.709000
COPY_TO 5 3 3 631.716000
COPY_TO 5 3 4 608.849000
COPY_TO 5 3 5 625.253000
COPY_TO 5 3 6 630.432000
COPY_TO 5 3 7 636.818000
COPY_TO 5 3 8 640.687000
COPY_TO 5 3 9 651.740000
COPY_TO 5 3 10 622.738000
COPY_FROM 5 3 1 541.713000
COPY_FROM 5 3 2 532.056000
COPY_FROM 5 3 3 539.630000
COPY_FROM 5 3 4 549.629000
COPY_FROM 5 3 5 548.109000
COPY_FROM 5 3 6 533.228000
COPY_FROM 5 3 7 532.981000
COPY_FROM 5 3 8 527.524000
COPY_FROM 5 3 9 566.548000
COPY_FROM 5 3 10 531.553000
COPY_TO 5 4 1 823.149000
COPY_TO 5 4 2 842.084000
COPY_TO 5 4 3 841.990000
COPY_TO 5 4 4 834.844000
COPY_TO 5 4 5 847.631000
COPY_TO 5 4 6 852.530000
COPY_TO 5 4 7 822.453000
COPY_TO 5 4 8 851.579000
COPY_TO 5 4 9 841.356000
COPY_TO 5 4 10 840.655000
COPY_FROM 5 4 1 715.727000
COPY_FROM 5 4 2 700.656000
COPY_FROM 5 4 3 714.135000
COPY_FROM 5 4 4 711.922000
COPY_FROM 5 4 5 703.007000
COPY_FROM 5 4 6 700.765000
COPY_FROM 5 4 7 705.071000
COPY_FROM 5 4 8 716.543000
COPY_FROM 5 4 9 702.448000
COPY_FROM 5 4 10 716.714000
COPY_TO 5 5 1 1044.045000
COPY_TO 5 5 2 1039.683000
COPY_TO 5 5 3 1010.508000
COPY_TO 5 5 4 1032.182000
COPY_TO 5 5 5 1056.995000
COPY_TO 5 5 6 1028.120000
COPY_TO 5 5 7 1035.610000
COPY_TO 5 5 8 1047.220000
COPY_TO 5 5 9 1056.572000
COPY_TO 5 5 10 1052.532000
COPY_FROM 5 5 1 880.451000
COPY_FROM 5 5 2 892.421000
COPY_FROM 5 5 3 926.924000
COPY_FROM 5 5 4 891.630000
COPY_FROM 5 5 5 931.319000
COPY_FROM 5 5 6 900.775000
COPY_FROM 5 5 7 894.377000
COPY_FROM 5 5 8 892.984000
COPY_FROM 5 5 9 882.452000
COPY_FROM 5 5 10 941.360000
COPY_TO 5 6 1 1258.759000
COPY_TO 5 6 2 1259.336000
COPY_TO 5 6 3 1268.761000
COPY_TO 5 6 4 1234.730000
COPY_TO 5 6 5 1272.013000
COPY_TO 5 6 6 1233.970000
COPY_TO 5 6 7 1281.098000
COPY_TO 5 6 8 1267.348000
COPY_TO 5 6 9 1259.674000
COPY_TO 5 6 10 1266.219000
COPY_FROM 5 6 1 1052.524000
COPY_FROM 5 6 2 1067.610000
COPY_FROM 5 6 3 1057.225000
COPY_FROM 5 6 4 1053.887000
COPY_FROM 5 6 5 1066.923000
COPY_FROM 5 6 6 1066.930000
COPY_FROM 5 6 7 1064.119000
COPY_FROM 5 6 8 1103.817000
COPY_FROM 5 6 9 1040.265000
COPY_FROM 5 6 10 1049.068000
COPY_TO 5 7 1 1492.215000
COPY_TO 5 7 2 1488.576000
COPY_TO 5 7 3 1467.710000
COPY_TO 5 7 4 1478.339000
COPY_TO 5 7 5 1501.272000
COPY_TO 5 7 6 1483.944000
COPY_TO 5 7 7 1479.922000
COPY_TO 5 7 8 1476.075000
COPY_TO 5 7 9 1470.403000
COPY_TO 5 7 10 1504.996000
COPY_FROM 5 7 1 1231.400000
COPY_FROM 5 7 2 1207.745000
COPY_FROM 5 7 3 1238.918000
COPY_FROM 5 7 4 1228.868000
COPY_FROM 5 7 5 1239.988000
COPY_FROM 5 7 6 1230.274000
COPY_FROM 5 7 7 1236.876000
COPY_FROM 5 7 8 1227.257000
COPY_FROM 5 7 9 1230.378000
COPY_FROM 5 7 10 1286.864000
COPY_TO 5 8 1 1739.946000
COPY_TO 5 8 2 1699.952000
COPY_TO 5 8 3 1679.076000
COPY_TO 5 8 4 1686.910000
COPY_TO 5 8 5 1688.083000
COPY_TO 5 8 6 1694.051000
COPY_TO 5 8 7 1678.831000
COPY_TO 5 8 8 1659.907000
COPY_TO 5 8 9 1641.518000
COPY_TO 5 8 10 1679.057000
COPY_FROM 5 8 1 1437.100000
COPY_FROM 5 8 2 1424.070000
COPY_FROM 5 8 3 1473.867000
COPY_FROM 5 8 4 1405.431000
COPY_FROM 5 8 5 1406.246000
COPY_FROM 5 8 6 1419.742000
COPY_FROM 5 8 7 1387.097000
COPY_FROM 5 8 8 1396.140000
COPY_FROM 5 8 9 1420.520000
COPY_FROM 5 8 10 1412.001000
COPY_TO 5 9 1 1858.925000
COPY_TO 5 9 2 1850.901000
COPY_TO 5 9 3 1873.408000
COPY_TO 5 9 4 1905.935000
COPY_TO 5 9 5 1910.295000
COPY_TO 5 9 6 1889.258000
COPY_TO 5 9 7 1865.899000
COPY_TO 5 9 8 1874.485000
COPY_TO 5 9 9 1906.459000
COPY_TO 5 9 10 1844.316000
COPY_FROM 5 9 1 1584.546000
COPY_FROM 5 9 2 1586.177000
COPY_FROM 5 9 3 1578.157000
COPY_FROM 5 9 4 1553.313000
COPY_FROM 5 9 5 1547.309000
COPY_FROM 5 9 6 1588.149000
COPY_FROM 5 9 7 1569.061000
COPY_FROM 5 9 8 1579.066000
COPY_FROM 5 9 9 1570.615000
COPY_FROM 5 9 10 1592.860000
COPY_TO 5 10 1 2095.472000
COPY_TO 5 10 2 2077.086000
COPY_TO 5 10 3 2082.011000
COPY_TO 5 10 4 2118.808000
COPY_TO 5 10 5 2122.738000
COPY_TO 5 10 6 2116.635000
COPY_TO 5 10 7 2065.169000
COPY_TO 5 10 8 2071.043000
COPY_TO 5 10 9 2104.322000
COPY_TO 5 10 10 2094.018000
COPY_FROM 5 10 1 1746.916000
COPY_FROM 5 10 2 1748.130000
COPY_FROM 5 10 3 1742.154000
COPY_FROM 5 10 4 1822.064000
COPY_FROM 5 10 5 1739.668000
COPY_FROM 5 10 6 1736.219000
COPY_FROM 5 10 7 1828.462000
COPY_FROM 5 10 8 1741.385000
COPY_FROM 5 10 9 1749.339000
COPY_FROM 5 10 10 1749.511000
COPY_TO 10 1 1 348.214000
COPY_TO 10 1 2 344.409000
COPY_TO 10 1 3 348.118000
COPY_TO 10 1 4 351.250000
COPY_TO 10 1 5 345.164000
COPY_TO 10 1 6 348.686000
COPY_TO 10 1 7 347.033000
COPY_TO 10 1 8 356.881000
COPY_TO 10 1 9 363.224000
COPY_TO 10 1 10 344.265000
COPY_FROM 10 1 1 309.644000
COPY_FROM 10 1 2 315.303000
COPY_FROM 10 1 3 309.133000
COPY_FROM 10 1 4 308.645000
COPY_FROM 10 1 5 307.657000
COPY_FROM 10 1 6 308.826000
COPY_FROM 10 1 7 306.510000
COPY_FROM 10 1 8 306.906000
COPY_FROM 10 1 9 312.354000
COPY_FROM 10 1 10 309.572000
COPY_TO 10 2 1 680.964000
COPY_TO 10 2 2 686.492000
COPY_TO 10 2 3 673.836000
COPY_TO 10 2 4 693.009000
COPY_TO 10 2 5 679.089000
COPY_TO 10 2 6 675.584000
COPY_TO 10 2 7 682.799000
COPY_TO 10 2 8 692.569000
COPY_TO 10 2 9 671.136000
COPY_TO 10 2 10 658.264000
COPY_FROM 10 2 1 596.359000
COPY_FROM 10 2 2 592.091000
COPY_FROM 10 2 3 593.862000
COPY_FROM 10 2 4 595.358000
COPY_FROM 10 2 5 595.974000
COPY_FROM 10 2 6 620.312000
COPY_FROM 10 2 7 596.066000
COPY_FROM 10 2 8 600.032000
COPY_FROM 10 2 9 600.454000
COPY_FROM 10 2 10 596.003000
COPY_TO 10 3 1 1019.610000
COPY_TO 10 3 2 1007.821000
COPY_TO 10 3 3 1014.551000
COPY_TO 10 3 4 1004.209000
COPY_TO 10 3 5 1037.550000
COPY_TO 10 3 6 1006.828000
COPY_TO 10 3 7 1018.162000
COPY_TO 10 3 8 992.985000
COPY_TO 10 3 9 1025.867000
COPY_TO 10 3 10 1028.286000
COPY_FROM 10 3 1 892.287000
COPY_FROM 10 3 2 887.084000
COPY_FROM 10 3 3 892.174000
COPY_FROM 10 3 4 899.172000
COPY_FROM 10 3 5 880.837000
COPY_FROM 10 3 6 885.155000
COPY_FROM 10 3 7 893.880000
COPY_FROM 10 3 8 870.693000
COPY_FROM 10 3 9 882.712000
COPY_FROM 10 3 10 878.129000
COPY_TO 10 4 1 1358.053000
COPY_TO 10 4 2 1360.787000
COPY_TO 10 4 3 1322.403000
COPY_TO 10 4 4 1388.729000
COPY_TO 10 4 5 1371.818000
COPY_TO 10 4 6 1349.647000
COPY_TO 10 4 7 1373.746000
COPY_TO 10 4 8 1426.870000
COPY_TO 10 4 9 1347.131000
COPY_TO 10 4 10 1336.103000
COPY_FROM 10 4 1 1187.230000
COPY_FROM 10 4 2 1175.994000
COPY_FROM 10 4 3 1186.190000
COPY_FROM 10 4 4 1186.168000
COPY_FROM 10 4 5 1182.343000
COPY_FROM 10 4 6 1179.842000
COPY_FROM 10 4 7 1175.598000
COPY_FROM 10 4 8 1189.885000
COPY_FROM 10 4 9 1166.391000
COPY_FROM 10 4 10 1175.445000
COPY_TO 10 5 1 1715.079000
COPY_TO 10 5 2 1685.962000
COPY_TO 10 5 3 1670.427000
COPY_TO 10 5 4 1662.272000
COPY_TO 10 5 5 1683.975000
COPY_TO 10 5 6 1662.505000
COPY_TO 10 5 7 1678.846000
COPY_TO 10 5 8 1659.495000
COPY_TO 10 5 9 1640.480000
COPY_TO 10 5 10 1666.186000
COPY_FROM 10 5 1 1469.628000
COPY_FROM 10 5 2 1482.367000
COPY_FROM 10 5 3 1467.941000
COPY_FROM 10 5 4 1443.202000
COPY_FROM 10 5 5 1438.004000
COPY_FROM 10 5 6 1435.447000
COPY_FROM 10 5 7 1440.746000
COPY_FROM 10 5 8 1440.996000
COPY_FROM 10 5 9 1516.147000
COPY_FROM 10 5 10 1523.081000
COPY_TO 10 6 1 2008.597000
COPY_TO 10 6 2 2002.489000
COPY_TO 10 6 3 2054.108000
COPY_TO 10 6 4 2020.325000
COPY_TO 10 6 5 2074.237000
COPY_TO 10 6 6 2029.803000
COPY_TO 10 6 7 2004.565000
COPY_TO 10 6 8 2027.488000
COPY_TO 10 6 9 2018.207000
COPY_TO 10 6 10 2043.407000
COPY_FROM 10 6 1 1732.059000
COPY_FROM 10 6 2 1698.740000
COPY_FROM 10 6 3 1744.397000
COPY_FROM 10 6 4 1752.396000
COPY_FROM 10 6 5 1738.473000
COPY_FROM 10 6 6 1750.763000
COPY_FROM 10 6 7 1760.260000
COPY_FROM 10 6 8 1734.506000
COPY_FROM 10 6 9 1752.343000
COPY_FROM 10 6 10 1800.136000
COPY_TO 10 7 1 2373.267000
COPY_TO 10 7 2 2359.455000
COPY_TO 10 7 3 2376.194000
COPY_TO 10 7 4 2369.713000
COPY_TO 10 7 5 2380.525000
COPY_TO 10 7 6 2355.680000
COPY_TO 10 7 7 2365.292000
COPY_TO 10 7 8 2387.594000
COPY_TO 10 7 9 2361.091000
COPY_TO 10 7 10 2399.029000
COPY_FROM 10 7 1 2040.625000
COPY_FROM 10 7 2 2031.799000
COPY_FROM 10 7 3 2054.883000
COPY_FROM 10 7 4 2020.964000
COPY_FROM 10 7 5 2085.711000
COPY_FROM 10 7 6 2056.172000
COPY_FROM 10 7 7 2053.141000
COPY_FROM 10 7 8 2017.080000
COPY_FROM 10 7 9 2036.249000
COPY_FROM 10 7 10 2055.574000
COPY_TO 10 8 1 2708.496000
COPY_TO 10 8 2 2670.277000
COPY_TO 10 8 3 2739.491000
COPY_TO 10 8 4 2670.203000
COPY_TO 10 8 5 2686.905000
COPY_TO 10 8 6 2715.423000
COPY_TO 10 8 7 2661.954000
COPY_TO 10 8 8 2679.533000
COPY_TO 10 8 9 2700.084000
COPY_TO 10 8 10 2692.732000
COPY_FROM 10 8 1 2332.678000
COPY_FROM 10 8 2 2327.148000
COPY_FROM 10 8 3 2365.272000
COPY_FROM 10 8 4 2323.775000
COPY_FROM 10 8 5 2327.727000
COPY_FROM 10 8 6 2328.340000
COPY_FROM 10 8 7 2351.656000
COPY_FROM 10 8 8 2359.587000
COPY_FROM 10 8 9 2315.807000
COPY_FROM 10 8 10 2323.951000
COPY_TO 10 9 1 3048.751000
COPY_TO 10 9 2 3047.431000
COPY_TO 10 9 3 3033.034000
COPY_TO 10 9 4 3024.685000
COPY_TO 10 9 5 3033.612000
COPY_TO 10 9 6 3071.925000
COPY_TO 10 9 7 3066.067000
COPY_TO 10 9 8 3061.065000
COPY_TO 10 9 9 3033.557000
COPY_TO 10 9 10 3139.233000
COPY_FROM 10 9 1 2637.134000
COPY_FROM 10 9 2 2648.296000
COPY_FROM 10 9 3 2595.698000
COPY_FROM 10 9 4 2684.115000
COPY_FROM 10 9 5 2640.266000
COPY_FROM 10 9 6 2647.282000
COPY_FROM 10 9 7 2626.573000
COPY_FROM 10 9 8 2597.198000
COPY_FROM 10 9 9 2590.305000
COPY_FROM 10 9 10 2607.834000
COPY_TO 10 10 1 3399.538000
COPY_TO 10 10 2 3395.112000
COPY_TO 10 10 3 3379.849000
COPY_TO 10 10 4 3447.512000
COPY_TO 10 10 5 3395.209000
COPY_TO 10 10 6 3372.455000
COPY_TO 10 10 7 3426.450000
COPY_TO 10 10 8 3406.147000
COPY_TO 10 10 9 3401.163000
COPY_TO 10 10 10 3398.863000
COPY_FROM 10 10 1 2918.524000
COPY_FROM 10 10 2 2946.519000
COPY_FROM 10 10 3 2897.459000
COPY_FROM 10 10 4 2949.553000
COPY_FROM 10 10 5 2924.340000
COPY_FROM 10 10 6 2880.430000
COPY_FROM 10 10 7 2943.481000
COPY_FROM 10 10 8 2924.866000
COPY_FROM 10 10 9 2882.415000
COPY_FROM 10 10 10 2939.448000
COPY_TO 15 1 1 481.490000
COPY_TO 15 1 2 480.802000
COPY_TO 15 1 3 505.153000
COPY_TO 15 1 4 480.755000
COPY_TO 15 1 5 487.445000
COPY_TO 15 1 6 478.630000
COPY_TO 15 1 7 471.924000
COPY_TO 15 1 8 484.494000
COPY_TO 15 1 9 475.958000
COPY_TO 15 1 10 476.259000
COPY_FROM 15 1 1 404.762000
COPY_FROM 15 1 2 411.539000
COPY_FROM 15 1 3 396.594000
COPY_FROM 15 1 4 402.033000
COPY_FROM 15 1 5 399.084000
COPY_FROM 15 1 6 402.425000
COPY_FROM 15 1 7 399.751000
COPY_FROM 15 1 8 396.732000
COPY_FROM 15 1 9 408.485000
COPY_FROM 15 1 10 401.768000
COPY_TO 15 2 1 948.346000
COPY_TO 15 2 2 960.359000
COPY_TO 15 2 3 945.425000
COPY_TO 15 2 4 942.055000
COPY_TO 15 2 5 946.342000
COPY_TO 15 2 6 974.876000
COPY_TO 15 2 7 935.041000
COPY_TO 15 2 8 962.795000
COPY_TO 15 2 9 934.524000
COPY_TO 15 2 10 944.476000
COPY_FROM 15 2 1 794.640000
COPY_FROM 15 2 2 764.601000
COPY_FROM 15 2 3 785.607000
COPY_FROM 15 2 4 768.691000
COPY_FROM 15 2 5 789.261000
COPY_FROM 15 2 6 766.484000
COPY_FROM 15 2 7 762.206000
COPY_FROM 15 2 8 777.008000
COPY_FROM 15 2 9 777.736000
COPY_FROM 15 2 10 768.562000
COPY_TO 15 3 1 1471.715000
COPY_TO 15 3 2 1425.784000
COPY_TO 15 3 3 1430.887000
COPY_TO 15 3 4 1411.350000
COPY_TO 15 3 5 1399.500000
COPY_TO 15 3 6 1414.848000
COPY_TO 15 3 7 1471.325000
COPY_TO 15 3 8 1424.225000
COPY_TO 15 3 9 1438.927000
COPY_TO 15 3 10 1383.432000
COPY_FROM 15 3 1 1157.842000
COPY_FROM 15 3 2 1148.168000
COPY_FROM 15 3 3 1170.290000
COPY_FROM 15 3 4 1163.281000
COPY_FROM 15 3 5 1164.792000
COPY_FROM 15 3 6 1170.901000
COPY_FROM 15 3 7 1167.411000
COPY_FROM 15 3 8 1136.925000
COPY_FROM 15 3 9 1163.268000
COPY_FROM 15 3 10 1167.786000
COPY_TO 15 4 1 1879.456000
COPY_TO 15 4 2 1851.491000
COPY_TO 15 4 3 1834.399000
COPY_TO 15 4 4 1909.106000
COPY_TO 15 4 5 1939.416000
COPY_TO 15 4 6 1856.175000
COPY_TO 15 4 7 1936.540000
COPY_TO 15 4 8 1872.650000
COPY_TO 15 4 9 1846.497000
COPY_TO 15 4 10 1851.336000
COPY_FROM 15 4 1 1527.390000
COPY_FROM 15 4 2 1559.869000
COPY_FROM 15 4 3 1549.983000
COPY_FROM 15 4 4 1519.352000
COPY_FROM 15 4 5 1534.720000
COPY_FROM 15 4 6 1531.672000
COPY_FROM 15 4 7 1514.365000
COPY_FROM 15 4 8 1524.385000
COPY_FROM 15 4 9 1519.783000
COPY_FROM 15 4 10 1518.088000
COPY_TO 15 5 1 2315.126000
COPY_TO 15 5 2 2403.590000
COPY_TO 15 5 3 2353.186000
COPY_TO 15 5 4 2362.140000
COPY_TO 15 5 5 2337.372000
COPY_TO 15 5 6 2369.436000
COPY_TO 15 5 7 2344.194000
COPY_TO 15 5 8 2345.627000
COPY_TO 15 5 9 2393.136000
COPY_TO 15 5 10 2390.355000
COPY_FROM 15 5 1 1904.628000
COPY_FROM 15 5 2 1910.340000
COPY_FROM 15 5 3 1918.427000
COPY_FROM 15 5 4 1912.737000
COPY_FROM 15 5 5 1955.806000
COPY_FROM 15 5 6 1892.326000
COPY_FROM 15 5 7 1915.079000
COPY_FROM 15 5 8 1920.116000
COPY_FROM 15 5 9 1914.245000
COPY_FROM 15 5 10 1887.371000
COPY_TO 15 6 1 2825.865000
COPY_TO 15 6 2 2834.549000
COPY_TO 15 6 3 2838.698000
COPY_TO 15 6 4 2769.660000
COPY_TO 15 6 5 2771.549000
COPY_TO 15 6 6 2824.433000
COPY_TO 15 6 7 2850.494000
COPY_TO 15 6 8 2873.406000
COPY_TO 15 6 9 2819.338000
COPY_TO 15 6 10 2800.095000
COPY_FROM 15 6 1 2312.919000
COPY_FROM 15 6 2 2280.861000
COPY_FROM 15 6 3 2276.382000
COPY_FROM 15 6 4 2328.440000
COPY_FROM 15 6 5 2306.146000
COPY_FROM 15 6 6 2290.642000
COPY_FROM 15 6 7 2318.425000
COPY_FROM 15 6 8 2319.431000
COPY_FROM 15 6 9 2271.906000
COPY_FROM 15 6 10 2307.933000
COPY_TO 15 7 1 3276.071000
COPY_TO 15 7 2 3302.277000
COPY_TO 15 7 3 3227.547000
COPY_TO 15 7 4 3205.014000
COPY_TO 15 7 5 3216.083000
COPY_TO 15 7 6 3288.328000
COPY_TO 15 7 7 3273.990000
COPY_TO 15 7 8 3269.459000
COPY_TO 15 7 9 3276.029000
COPY_TO 15 7 10 3257.944000
COPY_FROM 15 7 1 2650.110000
COPY_FROM 15 7 2 2687.294000
COPY_FROM 15 7 3 2679.115000
COPY_FROM 15 7 4 2642.092000
COPY_FROM 15 7 5 2754.050000
COPY_FROM 15 7 6 2670.190000
COPY_FROM 15 7 7 2659.509000
COPY_FROM 15 7 8 2680.944000
COPY_FROM 15 7 9 2702.110000
COPY_FROM 15 7 10 2714.737000
COPY_TO 15 8 1 3743.204000
COPY_TO 15 8 2 3728.396000
COPY_TO 15 8 3 3694.741000
COPY_TO 15 8 4 3792.445000
COPY_TO 15 8 5 3774.482000
COPY_TO 15 8 6 3741.767000
COPY_TO 15 8 7 3763.394000
COPY_TO 15 8 8 3760.802000
COPY_TO 15 8 9 3778.522000
COPY_TO 15 8 10 3719.006000
COPY_FROM 15 8 1 3036.831000
COPY_FROM 15 8 2 3053.770000
COPY_FROM 15 8 3 3084.446000
COPY_FROM 15 8 4 3083.344000
COPY_FROM 15 8 5 3063.590000
COPY_FROM 15 8 6 2994.047000
COPY_FROM 15 8 7 2997.051000
COPY_FROM 15 8 8 3028.378000
COPY_FROM 15 8 9 2985.766000
COPY_FROM 15 8 10 3048.673000
COPY_TO 15 9 1 4209.894000
COPY_TO 15 9 2 4166.767000
COPY_TO 15 9 3 4381.260000
COPY_TO 15 9 4 4166.933000
COPY_TO 15 9 5 4208.765000
COPY_TO 15 9 6 4221.622000
COPY_TO 15 9 7 4243.140000
COPY_TO 15 9 8 4221.371000
COPY_TO 15 9 9 4206.701000
COPY_TO 15 9 10 4173.130000
COPY_FROM 15 9 1 3509.903000
COPY_FROM 15 9 2 3429.411000
COPY_FROM 15 9 3 3476.601000
COPY_FROM 15 9 4 3532.142000
COPY_FROM 15 9 5 3482.214000
COPY_FROM 15 9 6 3481.428000
COPY_FROM 15 9 7 3512.648000
COPY_FROM 15 9 8 3429.037000
COPY_FROM 15 9 9 3481.796000
COPY_FROM 15 9 10 3405.251000
COPY_TO 15 10 1 4783.435000
COPY_TO 15 10 2 4644.036000
COPY_TO 15 10 3 4681.145000
COPY_TO 15 10 4 4649.832000
COPY_TO 15 10 5 4695.900000
COPY_TO 15 10 6 4741.715000
COPY_TO 15 10 7 4645.073000
COPY_TO 15 10 8 4685.227000
COPY_TO 15 10 9 4667.401000
COPY_TO 15 10 10 4674.533000
COPY_FROM 15 10 1 3791.541000
COPY_FROM 15 10 2 3769.122000
COPY_FROM 15 10 3 3875.967000
COPY_FROM 15 10 4 3898.442000
COPY_FROM 15 10 5 3757.463000
COPY_FROM 15 10 6 3803.542000
COPY_FROM 15 10 7 3898.392000
COPY_FROM 15 10 8 3853.286000
COPY_FROM 15 10 9 3796.088000
COPY_FROM 15 10 10 3775.505000
COPY_TO 20 1 1 588.371000
COPY_TO 20 1 2 588.534000
COPY_TO 20 1 3 604.939000
COPY_TO 20 1 4 609.174000
COPY_TO 20 1 5 599.870000
COPY_TO 20 1 6 606.180000
COPY_TO 20 1 7 593.885000
COPY_TO 20 1 8 619.903000
COPY_TO 20 1 9 613.359000
COPY_TO 20 1 10 579.570000
COPY_FROM 20 1 1 512.783000
COPY_FROM 20 1 2 520.086000
COPY_FROM 20 1 3 526.413000
COPY_FROM 20 1 4 503.333000
COPY_FROM 20 1 5 499.622000
COPY_FROM 20 1 6 504.114000
COPY_FROM 20 1 7 522.611000
COPY_FROM 20 1 8 504.078000
COPY_FROM 20 1 9 519.839000
COPY_FROM 20 1 10 521.364000
COPY_TO 20 2 1 1192.504000
COPY_TO 20 2 2 1204.486000
COPY_TO 20 2 3 1184.451000
COPY_TO 20 2 4 1224.046000
COPY_TO 20 2 5 1168.863000
COPY_TO 20 2 6 1185.104000
COPY_TO 20 2 7 1213.106000
COPY_TO 20 2 8 1219.240000
COPY_TO 20 2 9 1239.428000
COPY_TO 20 2 10 1202.900000
COPY_FROM 20 2 1 970.190000
COPY_FROM 20 2 2 968.745000
COPY_FROM 20 2 3 968.316000
COPY_FROM 20 2 4 963.391000
COPY_FROM 20 2 5 977.050000
COPY_FROM 20 2 6 977.689000
COPY_FROM 20 2 7 986.514000
COPY_FROM 20 2 8 996.876000
COPY_FROM 20 2 9 988.527000
COPY_FROM 20 2 10 973.651000
COPY_TO 20 3 1 1830.128000
COPY_TO 20 3 2 1783.896000
COPY_TO 20 3 3 1824.977000
COPY_TO 20 3 4 1808.527000
COPY_TO 20 3 5 1831.361000
COPY_TO 20 3 6 1793.910000
COPY_TO 20 3 7 1790.156000
COPY_TO 20 3 8 1901.378000
COPY_TO 20 3 9 1808.887000
COPY_TO 20 3 10 1820.944000
COPY_FROM 20 3 1 1463.492000
COPY_FROM 20 3 2 1473.649000
COPY_FROM 20 3 3 1470.260000
COPY_FROM 20 3 4 1460.868000
COPY_FROM 20 3 5 1472.357000
COPY_FROM 20 3 6 1467.554000
COPY_FROM 20 3 7 1447.413000
COPY_FROM 20 3 8 1477.952000
COPY_FROM 20 3 9 1428.962000
COPY_FROM 20 3 10 1488.391000
COPY_TO 20 4 1 2425.563000
COPY_TO 20 4 2 2389.778000
COPY_TO 20 4 3 2423.539000
COPY_TO 20 4 4 2402.541000
COPY_TO 20 4 5 2435.064000
COPY_TO 20 4 6 2375.980000
COPY_TO 20 4 7 2405.196000
COPY_TO 20 4 8 2516.577000
COPY_TO 20 4 9 2420.785000
COPY_TO 20 4 10 2396.486000
COPY_FROM 20 4 1 1982.045000
COPY_FROM 20 4 2 1936.064000
COPY_FROM 20 4 3 1949.428000
COPY_FROM 20 4 4 1962.934000
COPY_FROM 20 4 5 1970.196000
COPY_FROM 20 4 6 2011.610000
COPY_FROM 20 4 7 1990.147000
COPY_FROM 20 4 8 1932.480000
COPY_FROM 20 4 9 1988.822000
COPY_FROM 20 4 10 1971.847000
COPY_TO 20 5 1 2981.284000
COPY_TO 20 5 2 2976.269000
COPY_TO 20 5 3 3098.910000
COPY_TO 20 5 4 2959.358000
COPY_TO 20 5 5 3077.661000
COPY_TO 20 5 6 2964.573000
COPY_TO 20 5 7 3000.287000
COPY_TO 20 5 8 3028.061000
COPY_TO 20 5 9 2981.529000
COPY_TO 20 5 10 2996.331000
COPY_FROM 20 5 1 2432.749000
COPY_FROM 20 5 2 2431.906000
COPY_FROM 20 5 3 2439.713000
COPY_FROM 20 5 4 2412.802000
COPY_FROM 20 5 5 2473.731000
COPY_FROM 20 5 6 2481.380000
COPY_FROM 20 5 7 2434.936000
COPY_FROM 20 5 8 2435.429000
COPY_FROM 20 5 9 2424.148000
COPY_FROM 20 5 10 2435.032000
COPY_TO 20 6 1 3515.496000
COPY_TO 20 6 2 3600.580000
COPY_TO 20 6 3 3661.207000
COPY_TO 20 6 4 3560.876000
COPY_TO 20 6 5 3560.392000
COPY_TO 20 6 6 3610.652000
COPY_TO 20 6 7 3592.674000
COPY_TO 20 6 8 3572.048000
COPY_TO 20 6 9 3552.225000
COPY_TO 20 6 10 3544.525000
COPY_FROM 20 6 1 3029.925000
COPY_FROM 20 6 2 2868.143000
COPY_FROM 20 6 3 3003.149000
COPY_FROM 20 6 4 3004.859000
COPY_FROM 20 6 5 2975.037000
COPY_FROM 20 6 6 2941.359000
COPY_FROM 20 6 7 2958.822000
COPY_FROM 20 6 8 2929.398000
COPY_FROM 20 6 9 2942.849000
COPY_FROM 20 6 10 2980.994000
COPY_TO 20 7 1 4116.153000
COPY_TO 20 7 2 4151.977000
COPY_TO 20 7 3 4191.156000
COPY_TO 20 7 4 4122.951000
COPY_TO 20 7 5 4132.277000
COPY_TO 20 7 6 4117.195000
COPY_TO 20 7 7 4102.429000
COPY_TO 20 7 8 4167.083000
COPY_TO 20 7 9 4186.748000
COPY_TO 20 7 10 4176.378000
COPY_FROM 20 7 1 3385.308000
COPY_FROM 20 7 2 3424.239000
COPY_FROM 20 7 3 3469.448000
COPY_FROM 20 7 4 3427.053000
COPY_FROM 20 7 5 3393.027000
COPY_FROM 20 7 6 3390.030000
COPY_FROM 20 7 7 3412.631000
COPY_FROM 20 7 8 3416.462000
COPY_FROM 20 7 9 3440.032000
COPY_FROM 20 7 10 3406.263000
COPY_TO 20 8 1 4763.503000
COPY_TO 20 8 2 4747.748000
COPY_TO 20 8 3 4701.871000
COPY_TO 20 8 4 4792.958000
COPY_TO 20 8 5 4802.671000
COPY_TO 20 8 6 4700.079000
COPY_TO 20 8 7 4735.823000
COPY_TO 20 8 8 4697.905000
COPY_TO 20 8 9 4792.274000
COPY_TO 20 8 10 4824.296000
COPY_FROM 20 8 1 3911.984000
COPY_FROM 20 8 2 3964.766000
COPY_FROM 20 8 3 3868.331000
COPY_FROM 20 8 4 3864.961000
COPY_FROM 20 8 5 3901.993000
COPY_FROM 20 8 6 3913.048000
COPY_FROM 20 8 7 3913.909000
COPY_FROM 20 8 8 3911.791000
COPY_FROM 20 8 9 3922.104000
COPY_FROM 20 8 10 3865.939000
COPY_TO 20 9 1 5345.060000
COPY_TO 20 9 2 5332.009000
COPY_TO 20 9 3 5396.687000
COPY_TO 20 9 4 5465.947000
COPY_TO 20 9 5 5338.527000
COPY_TO 20 9 6 5336.702000
COPY_TO 20 9 7 5312.314000
COPY_TO 20 9 8 5355.565000
COPY_TO 20 9 9 5299.061000
COPY_TO 20 9 10 5444.467000
COPY_FROM 20 9 1 4348.639000
COPY_FROM 20 9 2 4324.543000
COPY_FROM 20 9 3 4405.188000
COPY_FROM 20 9 4 4371.563000
COPY_FROM 20 9 5 4362.497000
COPY_FROM 20 9 6 4399.904000
COPY_FROM 20 9 7 4360.325000
COPY_FROM 20 9 8 4319.934000
COPY_FROM 20 9 9 4374.741000
COPY_FROM 20 9 10 4312.770000
COPY_TO 20 10 1 5898.919000
COPY_TO 20 10 2 5937.106000
COPY_TO 20 10 3 5982.895000
COPY_TO 20 10 4 6006.050000
COPY_TO 20 10 5 6017.783000
COPY_TO 20 10 6 6027.734000
COPY_TO 20 10 7 5980.353000
COPY_TO 20 10 8 5880.622000
COPY_TO 20 10 9 6004.364000
COPY_TO 20 10 10 5991.804000
COPY_FROM 20 10 1 4898.844000
COPY_FROM 20 10 2 4803.747000
COPY_FROM 20 10 3 4811.804000
COPY_FROM 20 10 4 4876.269000
COPY_FROM 20 10 5 4880.505000
COPY_FROM 20 10 6 4911.774000
COPY_FROM 20 10 7 4798.363000
COPY_FROM 20 10 8 4992.114000
COPY_FROM 20 10 9 4979.897000
COPY_FROM 20 10 10 4991.956000
COPY_TO 25 1 1 734.355000
COPY_TO 25 1 2 751.684000
COPY_TO 25 1 3 741.651000
COPY_TO 25 1 4 741.016000
COPY_TO 25 1 5 758.336000
COPY_TO 25 1 6 742.133000
COPY_TO 25 1 7 754.167000
COPY_TO 25 1 8 756.050000
COPY_TO 25 1 9 734.336000
COPY_TO 25 1 10 755.085000
COPY_FROM 25 1 1 596.991000
COPY_FROM 25 1 2 595.280000
COPY_FROM 25 1 3 592.821000
COPY_FROM 25 1 4 598.301000
COPY_FROM 25 1 5 592.075000
COPY_FROM 25 1 6 598.580000
COPY_FROM 25 1 7 612.489000
COPY_FROM 25 1 8 594.139000
COPY_FROM 25 1 9 606.733000
COPY_FROM 25 1 10 599.884000
COPY_TO 25 2 1 1454.990000
COPY_TO 25 2 2 1464.926000
COPY_TO 25 2 3 1442.608000
COPY_TO 25 2 4 1467.204000
COPY_TO 25 2 5 1469.535000
COPY_TO 25 2 6 1441.349000
COPY_TO 25 2 7 1465.709000
COPY_TO 25 2 8 1477.000000
COPY_TO 25 2 9 1466.388000
COPY_TO 25 2 10 1491.142000
COPY_FROM 25 2 1 1186.171000
COPY_FROM 25 2 2 1197.788000
COPY_FROM 25 2 3 1189.075000
COPY_FROM 25 2 4 1168.622000
COPY_FROM 25 2 5 1194.059000
COPY_FROM 25 2 6 1175.976000
COPY_FROM 25 2 7 1180.774000
COPY_FROM 25 2 8 1240.536000
COPY_FROM 25 2 9 1216.618000
COPY_FROM 25 2 10 1162.956000
COPY_TO 25 3 1 2124.055000
COPY_TO 25 3 2 2208.083000
COPY_TO 25 3 3 2171.897000
COPY_TO 25 3 4 2200.009000
COPY_TO 25 3 5 2157.130000
COPY_TO 25 3 6 2178.217000
COPY_TO 25 3 7 2170.618000
COPY_TO 25 3 8 2159.246000
COPY_TO 25 3 9 2180.852000
COPY_TO 25 3 10 2162.987000
COPY_FROM 25 3 1 1814.018000
COPY_FROM 25 3 2 1777.425000
COPY_FROM 25 3 3 1804.940000
COPY_FROM 25 3 4 1777.249000
COPY_FROM 25 3 5 1755.835000
COPY_FROM 25 3 6 1762.743000
COPY_FROM 25 3 7 1791.698000
COPY_FROM 25 3 8 1812.374000
COPY_FROM 25 3 9 1807.720000
COPY_FROM 25 3 10 1779.357000
COPY_TO 25 4 1 2880.647000
COPY_TO 25 4 2 2914.773000
COPY_TO 25 4 3 2935.268000
COPY_TO 25 4 4 2899.907000
COPY_TO 25 4 5 2973.967000
COPY_TO 25 4 6 2957.416000
COPY_TO 25 4 7 2988.516000
COPY_TO 25 4 8 2960.672000
COPY_TO 25 4 9 2995.815000
COPY_TO 25 4 10 2938.285000
COPY_FROM 25 4 1 2390.532000
COPY_FROM 25 4 2 2402.782000
COPY_FROM 25 4 3 2380.246000
COPY_FROM 25 4 4 2313.968000
COPY_FROM 25 4 5 2336.368000
COPY_FROM 25 4 6 2333.098000
COPY_FROM 25 4 7 2341.465000
COPY_FROM 25 4 8 2364.110000
COPY_FROM 25 4 9 2347.709000
COPY_FROM 25 4 10 2440.437000
COPY_TO 25 5 1 3615.292000
COPY_TO 25 5 2 3706.462000
COPY_TO 25 5 3 3629.459000
COPY_TO 25 5 4 3614.923000
COPY_TO 25 5 5 3646.021000
COPY_TO 25 5 6 3622.527000
COPY_TO 25 5 7 3614.309000
COPY_TO 25 5 8 3590.665000
COPY_TO 25 5 9 3570.947000
COPY_TO 25 5 10 3616.614000
COPY_FROM 25 5 1 3025.729000
COPY_FROM 25 5 2 2890.083000
COPY_FROM 25 5 3 2832.641000
COPY_FROM 25 5 4 2896.295000
COPY_FROM 25 5 5 2906.869000
COPY_FROM 25 5 6 2964.634000
COPY_FROM 25 5 7 2981.976000
COPY_FROM 25 5 8 2899.424000
COPY_FROM 25 5 9 2952.928000
COPY_FROM 25 5 10 2956.315000
COPY_TO 25 6 1 4335.410000
COPY_TO 25 6 2 4352.629000
COPY_TO 25 6 3 4328.721000
COPY_TO 25 6 4 4329.229000
COPY_TO 25 6 5 4326.110000
COPY_TO 25 6 6 4350.785000
COPY_TO 25 6 7 4422.414000
COPY_TO 25 6 8 4327.600000
COPY_TO 25 6 9 4399.015000
COPY_TO 25 6 10 4344.686000
COPY_FROM 25 6 1 3546.817000
COPY_FROM 25 6 2 3583.911000
COPY_FROM 25 6 3 3577.654000
COPY_FROM 25 6 4 3545.870000
COPY_FROM 25 6 5 3517.331000
COPY_FROM 25 6 6 3621.318000
COPY_FROM 25 6 7 3518.093000
COPY_FROM 25 6 8 3487.595000
COPY_FROM 25 6 9 3502.635000
COPY_FROM 25 6 10 3442.832000
COPY_TO 25 7 1 5127.114000
COPY_TO 25 7 2 5147.491000
COPY_TO 25 7 3 5030.220000
COPY_TO 25 7 4 5039.242000
COPY_TO 25 7 5 5024.293000
COPY_TO 25 7 6 5177.402000
COPY_TO 25 7 7 5091.543000
COPY_TO 25 7 8 5047.738000
COPY_TO 25 7 9 5032.130000
COPY_TO 25 7 10 5125.969000
COPY_FROM 25 7 1 4236.840000
COPY_FROM 25 7 2 4166.021000
COPY_FROM 25 7 3 4072.998000
COPY_FROM 25 7 4 4044.735000
COPY_FROM 25 7 5 4095.923000
COPY_FROM 25 7 6 4100.569000
COPY_FROM 25 7 7 4065.397000
COPY_FROM 25 7 8 4038.183000
COPY_FROM 25 7 9 4051.760000
COPY_FROM 25 7 10 4100.604000
COPY_TO 25 8 1 5755.047000
COPY_TO 25 8 2 5882.932000
COPY_TO 25 8 3 5711.378000
COPY_TO 25 8 4 5750.234000
COPY_TO 25 8 5 5813.714000
COPY_TO 25 8 6 5818.114000
COPY_TO 25 8 7 5869.900000
COPY_TO 25 8 8 5792.470000
COPY_TO 25 8 9 5842.988000
COPY_TO 25 8 10 5826.206000
COPY_FROM 25 8 1 4633.711000
COPY_FROM 25 8 2 4604.926000
COPY_FROM 25 8 3 4843.423000
COPY_FROM 25 8 4 4623.654000
COPY_FROM 25 8 5 4764.668000
COPY_FROM 25 8 6 4665.590000
COPY_FROM 25 8 7 4743.428000
COPY_FROM 25 8 8 4684.806000
COPY_FROM 25 8 9 4625.929000
COPY_FROM 25 8 10 4796.581000
COPY_TO 25 9 1 6537.881000
COPY_TO 25 9 2 6467.843000
COPY_TO 25 9 3 6485.727000
COPY_TO 25 9 4 6419.503000
COPY_TO 25 9 5 6547.430000
COPY_TO 25 9 6 6647.516000
COPY_TO 25 9 7 6545.266000
COPY_TO 25 9 8 6483.089000
COPY_TO 25 9 9 6488.061000
COPY_TO 25 9 10 6501.837000
COPY_FROM 25 9 1 5218.724000
COPY_FROM 25 9 2 5322.326000
COPY_FROM 25 9 3 5134.734000
COPY_FROM 25 9 4 5181.265000
COPY_FROM 25 9 5 5236.810000
COPY_FROM 25 9 6 5394.804000
COPY_FROM 25 9 7 5216.023000
COPY_FROM 25 9 8 5228.834000
COPY_FROM 25 9 9 5233.673000
COPY_FROM 25 9 10 5370.429000
COPY_TO 25 10 1 7187.403000
COPY_TO 25 10 2 7237.522000
COPY_TO 25 10 3 7215.015000
COPY_TO 25 10 4 7310.549000
COPY_TO 25 10 5 7204.966000
COPY_TO 25 10 6 7395.831000
COPY_TO 25 10 7 7235.840000
COPY_TO 25 10 8 7335.155000
COPY_TO 25 10 9 7320.366000
COPY_TO 25 10 10 7399.998000
COPY_FROM 25 10 1 5766.539000
COPY_FROM 25 10 2 5868.594000
COPY_FROM 25 10 3 5717.698000
COPY_FROM 25 10 4 5824.276000
COPY_FROM 25 10 5 5865.970000
COPY_FROM 25 10 6 5943.953000
COPY_FROM 25 10 7 5730.136000
COPY_FROM 25 10 8 5856.029000
COPY_FROM 25 10 9 5782.006000
COPY_FROM 25 10 10 5872.458000
COPY_TO 30 1 1 855.345000
COPY_TO 30 1 2 871.867000
COPY_TO 30 1 3 855.503000
COPY_TO 30 1 4 852.658000
COPY_TO 30 1 5 872.763000
COPY_TO 30 1 6 840.381000
COPY_TO 30 1 7 858.728000
COPY_TO 30 1 8 854.579000
COPY_TO 30 1 9 849.983000
COPY_TO 30 1 10 865.011000
COPY_FROM 30 1 1 722.491000
COPY_FROM 30 1 2 697.416000
COPY_FROM 30 1 3 724.112000
COPY_FROM 30 1 4 723.154000
COPY_FROM 30 1 5 742.389000
COPY_FROM 30 1 6 736.040000
COPY_FROM 30 1 7 706.634000
COPY_FROM 30 1 8 703.661000
COPY_FROM 30 1 9 711.113000
COPY_FROM 30 1 10 698.277000
COPY_TO 30 2 1 1724.559000
COPY_TO 30 2 2 1719.146000
COPY_TO 30 2 3 1713.379000
COPY_TO 30 2 4 1715.448000
COPY_TO 30 2 5 1731.645000
COPY_TO 30 2 6 1715.003000
COPY_TO 30 2 7 1694.419000
COPY_TO 30 2 8 1692.365000
COPY_TO 30 2 9 1734.901000
COPY_TO 30 2 10 1753.928000
COPY_FROM 30 2 1 1381.412000
COPY_FROM 30 2 2 1393.055000
COPY_FROM 30 2 3 1429.064000
COPY_FROM 30 2 4 1400.549000
COPY_FROM 30 2 5 1390.625000
COPY_FROM 30 2 6 1399.524000
COPY_FROM 30 2 7 1428.245000
COPY_FROM 30 2 8 1396.228000
COPY_FROM 30 2 9 1394.769000
COPY_FROM 30 2 10 1376.140000
COPY_TO 30 3 1 2549.615000
COPY_TO 30 3 2 2549.549000
COPY_TO 30 3 3 2554.699000
COPY_TO 30 3 4 2620.901000
COPY_TO 30 3 5 2542.416000
COPY_TO 30 3 6 2463.919000
COPY_TO 30 3 7 2514.404000
COPY_TO 30 3 8 2606.338000
COPY_TO 30 3 9 2549.300000
COPY_TO 30 3 10 2614.069000
COPY_FROM 30 3 1 2061.723000
COPY_FROM 30 3 2 2054.595000
COPY_FROM 30 3 3 2064.499000
COPY_FROM 30 3 4 2029.387000
COPY_FROM 30 3 5 2060.673000
COPY_FROM 30 3 6 2071.234000
COPY_FROM 30 3 7 2039.847000
COPY_FROM 30 3 8 2034.512000
COPY_FROM 30 3 9 2048.970000
COPY_FROM 30 3 10 2070.192000
COPY_TO 30 4 1 3439.779000
COPY_TO 30 4 2 3415.562000
COPY_TO 30 4 3 3472.690000
COPY_TO 30 4 4 3426.406000
COPY_TO 30 4 5 3417.655000
COPY_TO 30 4 6 3420.833000
COPY_TO 30 4 7 3380.506000
COPY_TO 30 4 8 3462.000000
COPY_TO 30 4 9 3402.428000
COPY_TO 30 4 10 3428.111000
COPY_FROM 30 4 1 2733.262000
COPY_FROM 30 4 2 2683.878000
COPY_FROM 30 4 3 2821.240000
COPY_FROM 30 4 4 2768.113000
COPY_FROM 30 4 5 2867.414000
COPY_FROM 30 4 6 2759.740000
COPY_FROM 30 4 7 2796.335000
COPY_FROM 30 4 8 2688.241000
COPY_FROM 30 4 9 2693.820000
COPY_FROM 30 4 10 2731.140000
COPY_TO 30 5 1 4242.226000
COPY_TO 30 5 2 4337.764000
COPY_TO 30 5 3 4201.378000
COPY_TO 30 5 4 4276.924000
COPY_TO 30 5 5 4195.586000
COPY_TO 30 5 6 4147.869000
COPY_TO 30 5 7 4262.615000
COPY_TO 30 5 8 4283.672000
COPY_TO 30 5 9 4316.076000
COPY_TO 30 5 10 4265.417000
COPY_FROM 30 5 1 3414.952000
COPY_FROM 30 5 2 3484.110000
COPY_FROM 30 5 3 3410.230000
COPY_FROM 30 5 4 3456.846000
COPY_FROM 30 5 5 3383.937000
COPY_FROM 30 5 6 3430.556000
COPY_FROM 30 5 7 3430.628000
COPY_FROM 30 5 8 3428.378000
COPY_FROM 30 5 9 3396.417000
COPY_FROM 30 5 10 3432.408000
COPY_TO 30 6 1 5074.778000
COPY_TO 30 6 2 5101.994000
COPY_TO 30 6 3 5069.600000
COPY_TO 30 6 4 5222.574000
COPY_TO 30 6 5 5071.946000
COPY_TO 30 6 6 5076.127000
COPY_TO 30 6 7 5080.155000
COPY_TO 30 6 8 5189.124000
COPY_TO 30 6 9 5172.174000
COPY_TO 30 6 10 5100.780000
COPY_FROM 30 6 1 4211.710000
COPY_FROM 30 6 2 4088.827000
COPY_FROM 30 6 3 4140.018000
COPY_FROM 30 6 4 4200.005000
COPY_FROM 30 6 5 4083.156000
COPY_FROM 30 6 6 4142.306000
COPY_FROM 30 6 7 4302.596000
COPY_FROM 30 6 8 4166.638000
COPY_FROM 30 6 9 4063.275000
COPY_FROM 30 6 10 3989.077000
COPY_TO 30 7 1 5985.682000
COPY_TO 30 7 2 5944.822000
COPY_TO 30 7 3 5909.677000
COPY_TO 30 7 4 5959.397000
COPY_TO 30 7 5 5973.909000
COPY_TO 30 7 6 5971.125000
COPY_TO 30 7 7 5970.800000
COPY_TO 30 7 8 5928.120000
COPY_TO 30 7 9 6065.392000
COPY_TO 30 7 10 5967.311000
COPY_FROM 30 7 1 4832.597000
COPY_FROM 30 7 2 4763.587000
COPY_FROM 30 7 3 5007.212000
COPY_FROM 30 7 4 4831.589000
COPY_FROM 30 7 5 4761.464000
COPY_FROM 30 7 6 4964.790000
COPY_FROM 30 7 7 4911.089000
COPY_FROM 30 7 8 4804.915000
COPY_FROM 30 7 9 4830.199000
COPY_FROM 30 7 10 4821.159000
COPY_TO 30 8 1 6780.338000
COPY_TO 30 8 2 6780.465000
COPY_TO 30 8 3 6891.504000
COPY_TO 30 8 4 6924.545000
COPY_TO 30 8 5 6887.753000
COPY_TO 30 8 6 6667.140000
COPY_TO 30 8 7 6766.440000
COPY_TO 30 8 8 6847.607000
COPY_TO 30 8 9 6949.330000
COPY_TO 30 8 10 6807.099000
COPY_FROM 30 8 1 5408.566000
COPY_FROM 30 8 2 5430.909000
COPY_FROM 30 8 3 5413.220000
COPY_FROM 30 8 4 5426.873000
COPY_FROM 30 8 5 5471.004000
COPY_FROM 30 8 6 5454.879000
COPY_FROM 30 8 7 5467.374000
COPY_FROM 30 8 8 5463.669000
COPY_FROM 30 8 9 5382.302000
COPY_FROM 30 8 10 5430.827000
COPY_TO 30 9 1 7646.096000
COPY_TO 30 9 2 7663.106000
COPY_TO 30 9 3 7649.568000
COPY_TO 30 9 4 7582.509000
COPY_TO 30 9 5 7677.910000
COPY_TO 30 9 6 7649.933000
COPY_TO 30 9 7 7639.381000
COPY_TO 30 9 8 7628.082000
COPY_TO 30 9 9 7742.443000
COPY_TO 30 9 10 7749.198000
COPY_FROM 30 9 1 6254.021000
COPY_FROM 30 9 2 6189.310000
COPY_FROM 30 9 3 6080.114000
COPY_FROM 30 9 4 6117.857000
COPY_FROM 30 9 5 6120.318000
COPY_FROM 30 9 6 6131.465000
COPY_FROM 30 9 7 6119.603000
COPY_FROM 30 9 8 6132.356000
COPY_FROM 30 9 9 6217.884000
COPY_FROM 30 9 10 6169.986000
COPY_TO 30 10 1 8511.796000
COPY_TO 30 10 2 8541.021000
COPY_TO 30 10 3 8470.991000
COPY_TO 30 10 4 8429.901000
COPY_TO 30 10 5 8399.581000
COPY_TO 30 10 6 8449.127000
COPY_TO 30 10 7 8421.535000
COPY_TO 30 10 8 8409.578000
COPY_TO 30 10 9 8588.901000
COPY_TO 30 10 10 8615.748000
COPY_FROM 30 10 1 6838.794000
COPY_FROM 30 10 2 6835.900000
COPY_FROM 30 10 3 6685.443000
COPY_FROM 30 10 4 6878.933000
COPY_FROM 30 10 5 6862.674000
COPY_FROM 30 10 6 6709.240000
COPY_FROM 30 10 7 6805.730000
COPY_FROM 30 10 8 6793.489000
COPY_FROM 30 10 9 6638.819000
COPY_FROM 30 10 10 6852.015000
TO	5	1	100.56%	218.376000	219.609000
FROM	5	1	113.33%	168.493000	190.954000
TO	5	2	100.92%	421.387000	425.265000
FROM	5	2	115.55%	317.101000	366.403000
TO	5	3	101.80%	624.457000	635.709000
FROM	5	3	115.15%	468.651000	539.630000
TO	5	4	99.53%	845.936000	841.990000
FROM	5	4	115.26%	617.653000	711.922000
TO	5	5	100.60%	1037.773000	1044.045000
FROM	5	5	116.46%	767.966000	894.377000
TO	5	6	100.93%	1254.507000	1266.219000
FROM	5	6	115.60%	920.494000	1064.119000
TO	5	7	100.67%	1474.119000	1483.944000
FROM	5	7	114.04%	1079.762000	1231.400000
TO	5	8	99.77%	1690.789000	1686.910000
FROM	5	8	114.03%	1245.100000	1419.742000
TO	5	9	100.40%	1866.939000	1874.485000
FROM	5	9	115.12%	1371.727000	1579.066000
TO	5	10	100.15%	2092.245000	2095.472000
FROM	5	10	115.91%	1508.160000	1748.130000
TO	10	1	98.62%	353.087000	348.214000
FROM	10	1	118.65%	260.551000	309.133000
TO	10	2	97.77%	696.468000	680.964000
FROM	10	2	117.55%	507.076000	596.066000
TO	10	3	98.57%	1034.388000	1019.610000
FROM	10	3	118.70%	747.307000	887.084000
TO	10	4	97.77%	1391.879000	1360.787000
FROM	10	4	119.64%	988.250000	1182.343000
TO	10	5	96.89%	1724.061000	1670.427000
FROM	10	5	119.92%	1224.098000	1467.941000
TO	10	6	98.43%	2059.930000	2027.488000
FROM	10	6	119.10%	1470.005000	1750.763000
TO	10	7	98.50%	2409.333000	2373.267000
FROM	10	7	119.12%	1723.536000	2053.141000
TO	10	8	97.51%	2761.445000	2692.732000
FROM	10	8	118.76%	1960.546000	2328.340000
TO	10	9	98.34%	3100.206000	3048.751000
FROM	10	9	119.07%	2214.820000	2637.134000
TO	10	10	98.70%	3444.291000	3399.538000
FROM	10	10	118.79%	2462.314000	2924.866000
TO	15	1	97.71%	492.082000	480.802000
FROM	15	1	115.59%	347.820000	402.033000
TO	15	2	98.20%	963.658000	946.342000
FROM	15	2	115.79%	671.073000	777.008000
TO	15	3	97.90%	1456.382000	1425.784000
FROM	15	3	115.27%	1010.479000	1164.792000
TO	15	4	96.85%	1933.560000	1872.650000
FROM	15	4	113.92%	1340.700000	1527.390000
TO	15	5	98.32%	2402.419000	2362.140000
FROM	15	5	115.48%	1657.594000	1914.245000
TO	15	6	97.39%	2901.545000	2825.865000
FROM	15	6	116.00%	1989.522000	2307.933000
TO	15	7	97.47%	3359.085000	3273.990000
FROM	15	7	116.48%	2301.570000	2680.944000
TO	15	8	97.82%	3844.652000	3760.802000
FROM	15	8	114.43%	2664.116000	3048.673000
TO	15	9	97.71%	4308.416000	4209.894000
FROM	15	9	116.96%	2976.833000	3481.796000
TO	15	10	96.91%	4830.319000	4681.145000
FROM	15	10	115.09%	3304.798000	3803.542000
TO	20	1	96.05%	629.828000	604.939000
FROM	20	1	118.50%	438.673000	519.839000
TO	20	2	98.35%	1224.716000	1204.486000
FROM	20	2	112.61%	867.634000	977.050000
TO	20	3	97.96%	1858.945000	1820.944000
FROM	20	3	115.08%	1277.634000	1470.260000
TO	20	4	99.05%	2444.051000	2420.785000
FROM	20	4	116.54%	1692.007000	1971.847000
TO	20	5	97.15%	3084.210000	2996.331000
FROM	20	5	115.35%	2110.909000	2435.032000
TO	20	6	96.52%	3700.704000	3572.048000
FROM	20	6	117.61%	2529.492000	2975.037000
TO	20	7	96.11%	4320.033000	4151.977000
FROM	20	7	116.20%	2940.254000	3416.462000
TO	20	8	97.94%	4863.534000	4763.503000
FROM	20	8	115.93%	3374.520000	3911.984000
TO	20	9	97.82%	5463.960000	5345.060000
FROM	20	9	115.69%	3770.921000	4362.497000
TO	20	10	99.14%	6043.915000	5991.804000
FROM	20	10	116.88%	4191.494000	4898.844000
TO	25	1	98.29%	764.779000	751.684000
FROM	25	1	115.13%	519.686000	598.301000
TO	25	2	96.77%	1515.332000	1466.388000
FROM	25	2	116.70%	1018.943000	1189.075000
TO	25	3	94.74%	2292.456000	2171.897000
FROM	25	3	117.49%	1524.962000	1791.698000
TO	25	4	96.88%	3052.605000	2957.416000
FROM	25	4	117.70%	2008.544000	2364.110000
TO	25	5	94.08%	3843.996000	3616.614000
FROM	25	5	115.62%	2554.008000	2952.928000
TO	25	6	95.21%	4563.316000	4344.686000
FROM	25	6	118.56%	2990.859000	3545.870000
TO	25	7	95.55%	5328.781000	5091.543000
FROM	25	7	116.33%	3521.010000	4095.923000
TO	25	8	95.79%	6073.973000	5818.114000
FROM	25	8	116.83%	4009.777000	4684.806000
TO	25	9	95.80%	6787.185000	6501.837000
FROM	25	9	116.23%	4502.731000	5233.673000
TO	25	10	97.41%	7504.865000	7310.549000
FROM	25	10	117.25%	4994.463000	5856.029000
TO	30	1	94.39%	906.324000	855.503000
FROM	30	1	119.60%	604.110000	722.491000
TO	30	2	95.56%	1799.114000	1719.146000
FROM	30	2	117.45%	1188.794000	1396.228000
TO	30	3	95.76%	2662.493000	2549.615000
FROM	30	3	117.43%	1754.809000	2060.673000
TO	30	4	96.52%	3549.913000	3426.406000
FROM	30	4	117.23%	2354.055000	2759.740000
TO	30	5	96.50%	4419.907000	4265.417000
FROM	30	5	116.97%	2932.883000	3430.556000
TO	30	6	94.76%	5382.615000	5100.780000
FROM	30	6	117.88%	3514.108000	4142.306000
TO	30	7	95.52%	6250.630000	5970.800000
FROM	30	7	117.46%	4113.331000	4831.589000
TO	30	8	95.62%	7161.077000	6847.607000
FROM	30	8	116.31%	4669.370000	5430.909000
TO	30	9	95.07%	8046.895000	7649.933000
FROM	30	9	117.15%	5234.632000	6132.356000
TO	30	10	94.39%	8974.878000	8470.991000
FROM	30	10	117.84%	5800.793000	6835.900000
#!/usr/bin/env ruby

from = File.open(ARGV[0])
to = File.open(ARGV[1])
loop do
  from_line = from.gets
  to_line = to.gets
  break if from_line.nil?
  break if to_line.nil?
  from_type, from_n_columns, from_n_rows, from_runtime = from_line.split
  to_type, to_n_columns, to_n_rows, to_runtime = to_line.split
  break if from_type != to_type
  break if from_n_columns != to_n_columns
  break if from_n_rows != to_n_rows
  runtime_diff = to_runtime.to_f/ from_runtime.to_f
  puts("%s\t%s\t%s\t%5.2f%%\t%s\t%s" % [
         from_type[5..-1],
         from_n_columns,
         from_n_rows,
         runtime_diff * 100,
         from_runtime,
         to_runtime,
       ])
end
#!/usr/bin/env ruby

statistics = {}
ARGF.each_line do |line|
  type, n_columns, n_rows, round, runtime = line.split
  statistics[[type, n_columns, n_rows]] ||= []
  statistics[[type, n_columns, n_rows]] << runtime
end

require "pp"
statistics.each do |(type, n_columns, n_rows), runtimes|
  runtime_median = runtimes.sort[runtimes.size / 2]
  puts("#{type}\t#{n_columns}\t#{n_rows}\t#{runtime_median}")
end
>From 15505e1e2ed59644a567e40a7e8987823b49978d Mon Sep 17 00:00:00 2001
From: Sutou Kouhei <[email-masked]>
Date: Mon, 4 Mar 2024 13:52:34 +0900
Subject: [PATCH v19 1/5] Add CopyFromRoutine/CopyToRountine

They are for implementing custom COPY FROM/TO format. But this is not
enough to implement custom COPY FROM/TO format yet. We'll export some
APIs to receive/send data and add "format" option to COPY FROM/TO
later.

Existing text/csv/binary format implementations don't use
CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we
defer it. Because there are some mysterious profile results in spite
of we get faster runtimes. See [1] for details.

[1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz

Note that this doesn't change existing text/csv/binary format
implementations.
---
 src/backend/commands/copyfrom.c          |  24 +++++-
 src/backend/commands/copyfromparse.c     |   5 ++
 src/backend/commands/copyto.c            |  31 ++++++-
 src/include/commands/copyapi.h           | 101 +++++++++++++++++++++++
 src/include/commands/copyfrom_internal.h |   4 +
 src/tools/pgindent/typedefs.list         |   2 +
 6 files changed, 159 insertions(+), 8 deletions(-)
 create mode 100644 src/include/commands/copyapi.h

diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index ce4d62e707c..ff13b3e3592 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1618,12 +1618,22 @@ BeginCopyFrom(ParseState *pstate,
 
 		/* Fetch the input function and typioparam info */
 		if (cstate->opts.binary)
+		{
 			getTypeBinaryInputInfo(att->atttypid,
 								   &in_func_oid, &typioparams[attnum - 1]);
+			fmgr_info(in_func_oid, &in_functions[attnum - 1]);
+		}
+		else if (cstate->routine)
+			cstate->routine->CopyFromInFunc(cstate, att->atttypid,
+											&in_functions[attnum - 1],
+											&typioparams[attnum - 1]);
+
 		else
+		{
 			getTypeInputInfo(att->atttypid,
 							 &in_func_oid, &typioparams[attnum - 1]);
-		fmgr_info(in_func_oid, &in_functions[attnum - 1]);
+			fmgr_info(in_func_oid, &in_functions[attnum - 1]);
+		}
 
 		/* Get default info if available */
 		defexprs[attnum - 1] = NULL;
@@ -1763,10 +1773,13 @@ BeginCopyFrom(ParseState *pstate,
 		/* Read and verify binary header */
 		ReceiveCopyBinaryHeader(cstate);
 	}
-
-	/* create workspace for CopyReadAttributes results */
-	if (!cstate->opts.binary)
+	else if (cstate->routine)
 	{
+		cstate->routine->CopyFromStart(cstate, tupDesc);
+	}
+	else
+	{
+		/* create workspace for CopyReadAttributes results */
 		AttrNumber	attr_count = list_length(cstate->attnumlist);
 
 		cstate->max_fields = attr_count;
@@ -1784,6 +1797,9 @@ BeginCopyFrom(ParseState *pstate,
 void
 EndCopyFrom(CopyFromState cstate)
 {
+	if (cstate->routine)
+		cstate->routine->CopyFromEnd(cstate);
+
 	/* No COPY FROM related resources except memory. */
 	if (cstate->is_program)
 	{
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 7efcb891598..92b8d5e72d5 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -1012,6 +1012,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
 
 		Assert(fieldno == attr_count);
 	}
+	else if (cstate->routine)
+	{
+		if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls))
+			return false;
+	}
 	else
 	{
 		/* binary */
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index ae8b2e36d72..ff19c457abf 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -20,6 +20,7 @@
 
 #include "access/tableam.h"
 #include "commands/copy.h"
+#include "commands/copyapi.h"
 #include "commands/progress.h"
 #include "executor/execdesc.h"
 #include "executor/executor.h"
@@ -64,6 +65,9 @@ typedef enum CopyDest
  */
 typedef struct CopyToStateData
 {
+	/* format routine */
+	const CopyToRoutine *routine;
+
 	/* low-level state data */
 	CopyDest	copy_dest;		/* type of copy source/destination */
 	FILE	   *copy_file;		/* used if copy_dest == COPY_FILE */
@@ -771,14 +775,22 @@ DoCopyTo(CopyToState cstate)
 		Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
 
 		if (cstate->opts.binary)
+		{
 			getTypeBinaryOutputInfo(attr->atttypid,
 									&out_func_oid,
 									&isvarlena);
+			fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
+		}
+		else if (cstate->routine)
+			cstate->routine->CopyToOutFunc(cstate, attr->atttypid,
+										   &cstate->out_functions[attnum - 1]);
 		else
+		{
 			getTypeOutputInfo(attr->atttypid,
 							  &out_func_oid,
 							  &isvarlena);
-		fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
+			fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
+		}
 	}
 
 	/*
@@ -805,6 +817,8 @@ DoCopyTo(CopyToState cstate)
 		tmp = 0;
 		CopySendInt32(cstate, tmp);
 	}
+	else if (cstate->routine)
+		cstate->routine->CopyToStart(cstate, tupDesc);
 	else
 	{
 		/*
@@ -886,6 +900,8 @@ DoCopyTo(CopyToState cstate)
 		/* Need to flush out the trailer */
 		CopySendEndOfRow(cstate);
 	}
+	else if (cstate->routine)
+		cstate->routine->CopyToEnd(cstate);
 
 	MemoryContextDelete(cstate->rowcontext);
 
@@ -910,15 +926,22 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
 	MemoryContextReset(cstate->rowcontext);
 	oldcontext = MemoryContextSwitchTo(cstate->rowcontext);
 
+	/* Make sure the tuple is fully deconstructed */
+	slot_getallattrs(slot);
+
+	if (cstate->routine)
+	{
+		cstate->routine->CopyToOneRow(cstate, slot);
+		MemoryContextSwitchTo(oldcontext);
+		return;
+	}
+
 	if (cstate->opts.binary)
 	{
 		/* Binary per-tuple header */
 		CopySendInt16(cstate, list_length(cstate->attnumlist));
 	}
 
-	/* Make sure the tuple is fully deconstructed */
-	slot_getallattrs(slot);
-
 	foreach(cur, cstate->attnumlist)
 	{
 		int			attnum = lfirst_int(cur);
diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h
new file mode 100644
index 00000000000..d1289424c67
--- /dev/null
+++ b/src/include/commands/copyapi.h
@@ -0,0 +1,101 @@
+/*-------------------------------------------------------------------------
+ *
+ * copyapi.h
+ *	  API for COPY TO/FROM handlers
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/copyapi.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COPYAPI_H
+#define COPYAPI_H
+
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
+
+/* These are private in commands/copy[from|to].c */
+typedef struct CopyFromStateData *CopyFromState;
+typedef struct CopyToStateData *CopyToState;
+
+/*
+ * API structure for a COPY FROM format implementation.  Note this must be
+ * allocated in a server-lifetime manner, typically as a static const struct.
+ */
+typedef struct CopyFromRoutine
+{
+	/*
+	 * Called when COPY FROM is started to set up the input functions
+	 * associated with the relation's attributes writing to.  `finfo` can be
+	 * optionally filled to provide the catalog information of the input
+	 * function.  `typioparam` can be optionally filled to define the OID of
+	 * the type to pass to the input function.  `atttypid` is the OID of data
+	 * type used by the relation's attribute.
+	 */
+	void		(*CopyFromInFunc) (CopyFromState cstate, Oid atttypid,
+								   FmgrInfo *finfo, Oid *typioparam);
+
+	/*
+	 * Called when COPY FROM is started.
+	 *
+	 * `tupDesc` is the tuple descriptor of the relation where the data needs
+	 * to be copied.  This can be used for any initialization steps required
+	 * by a format.
+	 */
+	void		(*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc);
+
+	/*
+	 * Copy one row to a set of `values` and `nulls` of size tupDesc->natts.
+	 *
+	 * 'econtext' is used to evaluate default expression for each column that
+	 * is either not read from the file or is using the DEFAULT option of COPY
+	 * FROM.  It is NULL if no default values are used.
+	 *
+	 * Returns false if there are no more tuples to copy.
+	 */
+	bool		(*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext,
+								   Datum *values, bool *nulls);
+
+	/* Called when COPY FROM has ended. */
+	void		(*CopyFromEnd) (CopyFromState cstate);
+} CopyFromRoutine;
+
+/*
+ * API structure for a COPY TO format implementation.   Note this must be
+ * allocated in a server-lifetime manner, typically as a static const struct.
+ */
+typedef struct CopyToRoutine
+{
+	/*
+	 * Called when COPY TO is started to set up the output functions
+	 * associated with the relation's attributes reading from.  `finfo` can be
+	 * optionally filled to provide the catalog information of the output
+	 * function.  `atttypid` is the OID of data type used by the relation's
+	 * attribute.
+	 */
+	void		(*CopyToOutFunc) (CopyToState cstate, Oid atttypid,
+								  FmgrInfo *finfo);
+
+	/*
+	 * Called when COPY TO is started.
+	 *
+	 * `tupDesc` is the tuple descriptor of the relation from where the data
+	 * is read.
+	 */
+	void		(*CopyToStart) (CopyToState cstate, TupleDesc tupDesc);
+
+	/*
+	 * Copy one row for COPY TO.
+	 *
+	 * `slot` is the tuple slot where the data is emitted.
+	 */
+	void		(*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot);
+
+	/* Called when COPY TO has ended */
+	void		(*CopyToEnd) (CopyToState cstate);
+} CopyToRoutine;
+
+#endif							/* COPYAPI_H */
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index cad52fcc783..509b9e92a18 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -15,6 +15,7 @@
 #define COPYFROM_INTERNAL_H
 
 #include "commands/copy.h"
+#include "commands/copyapi.h"
 #include "commands/trigger.h"
 #include "nodes/miscnodes.h"
 
@@ -58,6 +59,9 @@ typedef enum CopyInsertMethod
  */
 typedef struct CopyFromStateData
 {
+	/* format routine */
+	const CopyFromRoutine *routine;
+
 	/* low-level state data */
 	CopySource	copy_src;		/* type of copy source */
 	FILE	   *copy_file;		/* used if copy_src == COPY_FILE */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d7f9217ce..3ce855c8f17 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -490,6 +490,7 @@ ConvertRowtypeExpr
 CookedConstraint
 CopyDest
 CopyFormatOptions
+CopyFromRoutine
 CopyFromState
 CopyFromStateData
 CopyHeaderChoice
@@ -501,6 +502,7 @@ CopyMultiInsertInfo
 CopyOnErrorChoice
 CopySource
 CopyStmt
+CopyToRoutine
 CopyToState
 CopyToStateData
 Cost
-- 
2.45.2

>From b26d39f09dc78b3338aa4527d43cd0468f5ddf69 Mon Sep 17 00:00:00 2001
From: Sutou Kouhei <[email-masked]>
Date: Tue, 23 Jul 2024 16:44:44 +0900
Subject: [PATCH v19 2/5] Use CopyFromRoutine/CopyToRountine for the existing
 formats

The existing formats are text, csv and binary. If we find any
performance regression by this, we will not merge this to master.

This will increase indirect function call costs but this will reduce
runtime "if (cstate->opts.binary)" and "if (cstate->opts.csv_mode)"
branch costs.

This uses an optimization based of static inline function and a
constant argument call for cstate->opts.csv_mode. For example,
CopyFromTextLikeOneRow() uses this optimization. It accepts the "bool
is_csv" argument instead of using cstate->opts.csv_mode in
it. CopyFromTextOneRow() calls CopyFromTextLikeOneRow() with
false (constant) for "bool is_csv". Compiler will remove "if (is_csv)"
branch in it by this optimization.

This doesn't change existing logic. This just moves existing codes.
---
 src/backend/commands/copyfrom.c          | 215 ++++++---
 src/backend/commands/copyfromparse.c     | 556 +++++++++++++----------
 src/backend/commands/copyto.c            | 480 ++++++++++++-------
 src/include/commands/copy.h              |   2 -
 src/include/commands/copyfrom_internal.h |   8 +
 5 files changed, 813 insertions(+), 448 deletions(-)

diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index ff13b3e3592..1a59202f5ab 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -103,6 +103,157 @@ typedef struct CopyMultiInsertInfo
 /* non-export function prototypes */
 static void ClosePipeFromProgram(CopyFromState cstate);
 
+
+/*
+ * CopyFromRoutine implementations for text and CSV.
+ */
+
+/*
+ * CopyFromTextLikeInFunc
+ *
+ * Assign input function data for a relation's attribute in text/CSV format.
+ */
+static void
+CopyFromTextLikeInFunc(CopyFromState cstate, Oid atttypid,
+					   FmgrInfo *finfo, Oid *typioparam)
+{
+	Oid			func_oid;
+
+	getTypeInputInfo(atttypid, &func_oid, typioparam);
+	fmgr_info(func_oid, finfo);
+}
+
+/*
+ * CopyFromTextLikeStart
+ *
+ * Start of COPY FROM for text/CSV format.
+ */
+static void
+CopyFromTextLikeStart(CopyFromState cstate, TupleDesc tupDesc)
+{
+	AttrNumber	attr_count;
+
+	/*
+	 * If encoding conversion is needed, we need another buffer to hold the
+	 * converted input data.  Otherwise, we can just point input_buf to the
+	 * same buffer as raw_buf.
+	 */
+	if (cstate->need_transcoding)
+	{
+		cstate->input_buf = (char *) palloc(INPUT_BUF_SIZE + 1);
+		cstate->input_buf_index = cstate->input_buf_len = 0;
+	}
+	else
+		cstate->input_buf = cstate->raw_buf;
+	cstate->input_reached_eof = false;
+
+	initStringInfo(&cstate->line_buf);
+
+	/*
+	 * Create workspace for CopyReadAttributes results; used by CSV and text
+	 * format.
+	 */
+	attr_count = list_length(cstate->attnumlist);
+	cstate->max_fields = attr_count;
+	cstate->raw_fields = (char **) palloc(attr_count * sizeof(char *));
+}
+
+/*
+ * CopyFromTextLikeEnd
+ *
+ * End of COPY FROM for text/CSV format.
+ */
+static void
+CopyFromTextLikeEnd(CopyFromState cstate)
+{
+	/* nothing to do */
+}
+
+/*
+ * CopyFromRoutine implementation for "binary".
+ */
+
+/*
+ * CopyFromBinaryInFunc
+ *
+ * Assign input function data for a relation's attribute in binary format.
+ */
+static void
+CopyFromBinaryInFunc(CopyFromState cstate, Oid atttypid,
+					 FmgrInfo *finfo, Oid *typioparam)
+{
+	Oid			func_oid;
+
+	getTypeBinaryInputInfo(atttypid, &func_oid, typioparam);
+	fmgr_info(func_oid, finfo);
+}
+
+/*
+ * CopyFromBinaryStart
+ *
+ * Start of COPY FROM for binary format.
+ */
+static void
+CopyFromBinaryStart(CopyFromState cstate, TupleDesc tupDesc)
+{
+	/* Read and verify binary header */
+	ReceiveCopyBinaryHeader(cstate);
+}
+
+/*
+ * CopyFromBinaryEnd
+ *
+ * End of COPY FROM for binary format.
+ */
+static void
+CopyFromBinaryEnd(CopyFromState cstate)
+{
+	/* nothing to do */
+}
+
+/*
+ * Routines assigned to each format.
++
+ * CSV and text share the same implementation, at the exception of the
+ * per-row callback.
+ */
+static const CopyFromRoutine CopyFromRoutineText = {
+	.CopyFromInFunc = CopyFromTextLikeInFunc,
+	.CopyFromStart = CopyFromTextLikeStart,
+	.CopyFromOneRow = CopyFromTextOneRow,
+	.CopyFromEnd = CopyFromTextLikeEnd,
+};
+
+static const CopyFromRoutine CopyFromRoutineCSV = {
+	.CopyFromInFunc = CopyFromTextLikeInFunc,
+	.CopyFromStart = CopyFromTextLikeStart,
+	.CopyFromOneRow = CopyFromCSVOneRow,
+	.CopyFromEnd = CopyFromTextLikeEnd,
+};
+
+static const CopyFromRoutine CopyFromRoutineBinary = {
+	.CopyFromInFunc = CopyFromBinaryInFunc,
+	.CopyFromStart = CopyFromBinaryStart,
+	.CopyFromOneRow = CopyFromBinaryOneRow,
+	.CopyFromEnd = CopyFromBinaryEnd,
+};
+
+/*
+ * Define the COPY FROM routines to use for a format.
+ */
+static const CopyFromRoutine *
+CopyFromGetRoutine(CopyFormatOptions opts)
+{
+	if (opts.csv_mode)
+		return &CopyFromRoutineCSV;
+	else if (opts.binary)
+		return &CopyFromRoutineBinary;
+
+	/* default is text */
+	return &CopyFromRoutineText;
+}
+
+
 /*
  * error context callback for COPY FROM
  *
@@ -1381,7 +1532,6 @@ BeginCopyFrom(ParseState *pstate,
 				num_defaults;
 	FmgrInfo   *in_functions;
 	Oid		   *typioparams;
-	Oid			in_func_oid;
 	int		   *defmap;
 	ExprState **defexprs;
 	MemoryContext oldcontext;
@@ -1413,6 +1563,9 @@ BeginCopyFrom(ParseState *pstate,
 	/* Extract options from the statement node tree */
 	ProcessCopyOptions(pstate, &cstate->opts, true /* is_from */ , options);
 
+	/* Set format routine */
+	cstate->routine = CopyFromGetRoutine(cstate->opts);
+
 	/* Process the target relation */
 	cstate->rel = rel;
 
@@ -1566,25 +1719,6 @@ BeginCopyFrom(ParseState *pstate,
 	cstate->raw_buf_index = cstate->raw_buf_len = 0;
 	cstate->raw_reached_eof = false;
 
-	if (!cstate->opts.binary)
-	{
-		/*
-		 * If encoding conversion is needed, we need another buffer to hold
-		 * the converted input data.  Otherwise, we can just point input_buf
-		 * to the same buffer as raw_buf.
-		 */
-		if (cstate->need_transcoding)
-		{
-			cstate->input_buf = (char *) palloc(INPUT_BUF_SIZE + 1);
-			cstate->input_buf_index = cstate->input_buf_len = 0;
-		}
-		else
-			cstate->input_buf = cstate->raw_buf;
-		cstate->input_reached_eof = false;
-
-		initStringInfo(&cstate->line_buf);
-	}
-
 	initStringInfo(&cstate->attribute_buf);
 
 	/* Assign range table and rteperminfos, we'll need them in CopyFrom. */
@@ -1617,23 +1751,9 @@ BeginCopyFrom(ParseState *pstate,
 			continue;
 
 		/* Fetch the input function and typioparam info */
-		if (cstate->opts.binary)
-		{
-			getTypeBinaryInputInfo(att->atttypid,
-								   &in_func_oid, &typioparams[attnum - 1]);
-			fmgr_info(in_func_oid, &in_functions[attnum - 1]);
-		}
-		else if (cstate->routine)
-			cstate->routine->CopyFromInFunc(cstate, att->atttypid,
-											&in_functions[attnum - 1],
-											&typioparams[attnum - 1]);
-
-		else
-		{
-			getTypeInputInfo(att->atttypid,
-							 &in_func_oid, &typioparams[attnum - 1]);
-			fmgr_info(in_func_oid, &in_functions[attnum - 1]);
-		}
+		cstate->routine->CopyFromInFunc(cstate, att->atttypid,
+										&in_functions[attnum - 1],
+										&typioparams[attnum - 1]);
 
 		/* Get default info if available */
 		defexprs[attnum - 1] = NULL;
@@ -1768,23 +1888,7 @@ BeginCopyFrom(ParseState *pstate,
 
 	pgstat_progress_update_multi_param(3, progress_cols, progress_vals);
 
-	if (cstate->opts.binary)
-	{
-		/* Read and verify binary header */
-		ReceiveCopyBinaryHeader(cstate);
-	}
-	else if (cstate->routine)
-	{
-		cstate->routine->CopyFromStart(cstate, tupDesc);
-	}
-	else
-	{
-		/* create workspace for CopyReadAttributes results */
-		AttrNumber	attr_count = list_length(cstate->attnumlist);
-
-		cstate->max_fields = attr_count;
-		cstate->raw_fields = (char **) palloc(attr_count * sizeof(char *));
-	}
+	cstate->routine->CopyFromStart(cstate, tupDesc);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -1797,8 +1901,7 @@ BeginCopyFrom(ParseState *pstate,
 void
 EndCopyFrom(CopyFromState cstate)
 {
-	if (cstate->routine)
-		cstate->routine->CopyFromEnd(cstate);
+	cstate->routine->CopyFromEnd(cstate);
 
 	/* No COPY FROM related resources except memory. */
 	if (cstate->is_program)
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 92b8d5e72d5..90824b47785 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -149,10 +149,10 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";

 /* non-export function prototypes */
-static bool CopyReadLine(CopyFromState cstate);
-static bool CopyReadLineText(CopyFromState cstate);
-static int	CopyReadAttributesText(CopyFromState cstate);
-static int	CopyReadAttributesCSV(CopyFromState cstate);
+static inline bool CopyReadLine(CopyFromState cstate, bool is_csv);
+static inline bool CopyReadLineText(CopyFromState cstate, bool is_csv);
+static inline int CopyReadAttributesText(CopyFromState cstate);
+static inline int CopyReadAttributesCSV(CopyFromState cstate);
 static Datum CopyReadBinaryAttribute(CopyFromState cstate, FmgrInfo *flinfo,
 									 Oid typioparam, int32 typmod,
 									 bool *isnull);
@@ -750,8 +750,8 @@ CopyReadBinaryData(CopyFromState cstate, char *dest, int nbytes)
  *
  * NOTE: force_not_null option are not applied to the returned fields.
  */
-bool
-NextCopyFromRawFields(CopyFromState cstate, char ***fields, int *nfields)
+static inline bool
+NextCopyFromRawFields(CopyFromState cstate, char ***fields, int *nfields, bool is_csv)
 {
 	int			fldct;
 	bool		done;
@@ -768,13 +768,17 @@ NextCopyFromRawFields(CopyFromState cstate, char ***fields, int *nfields)
 		tupDesc = RelationGetDescr(cstate->rel);
 
 		cstate->cur_lineno++;
-		done = CopyReadLine(cstate);
+		done = CopyReadLine(cstate, is_csv);
 
 		if (cstate->opts.header_line == COPY_HEADER_MATCH)
 		{
 			int			fldnum;
 
-			if (cstate->opts.csv_mode)
+			/*
+			 * is_csv will be optimized away by compiler, as argument is
+			 * constant at caller.
+			 */
+			if (is_csv)
 				fldct = CopyReadAttributesCSV(cstate);
 			else
 				fldct = CopyReadAttributesText(cstate);
@@ -818,7 +822,7 @@ NextCopyFromRawFields(CopyFromState cstate, char ***fields, int *nfields)
 	cstate->cur_lineno++;
 
 	/* Actually read the line into memory here */
-	done = CopyReadLine(cstate);
+	done = CopyReadLine(cstate, is_csv);
 
 	/*
 	 * EOF at start of line means we're done.  If we see EOF after some
@@ -828,8 +832,13 @@ NextCopyFromRawFields(CopyFromState cstate, char ***fields, int *nfields)
 	if (done && cstate->line_buf.len == 0)
 		return false;
 
-	/* Parse the line into de-escaped field values */
-	if (cstate->opts.csv_mode)
+	/*
+	 * Parse the line into de-escaped field values
+	 *
+	 * is_csv will be optimized away by compiler, as argument is constant at
+	 * caller.
+	 */
+	if (is_csv)
 		fldct = CopyReadAttributesCSV(cstate);
 	else
 		fldct = CopyReadAttributesText(cstate);
@@ -839,6 +848,267 @@ NextCopyFromRawFields(CopyFromState cstate, char ***fields, int *nfields)
 	return true;
 }
 
+/*
+ * CopyFromTextLikeOneRow
+ *
+ * Copy one row to a set of `values` and `nulls` for the text and CSV
+ * formats.
+ *
+ * Workhorse for CopyFromTextOneRow() and CopyFromCSVOneRow().
+ */
+static inline bool
+CopyFromTextLikeOneRow(CopyFromState cstate,
+					   ExprContext *econtext,
+					   Datum *values,
+					   bool *nulls,
+					   bool is_csv)
+{
+	TupleDesc	tupDesc;
+	AttrNumber	attr_count;
+	FmgrInfo   *in_functions = cstate->in_functions;
+	Oid		   *typioparams = cstate->typioparams;
+	ExprState **defexprs = cstate->defexprs;
+	char	  **field_strings;
+	ListCell   *cur;
+	int			fldct;
+	int			fieldno;
+	char	   *string;
+
+	tupDesc = RelationGetDescr(cstate->rel);
+	attr_count = list_length(cstate->attnumlist);
+
+	/* read raw fields in the next line */
+	if (!NextCopyFromRawFields(cstate, &field_strings, &fldct, is_csv))
+		return false;
+
+	/* check for overflowing fields */
+	if (attr_count > 0 && fldct > attr_count)
+		ereport(ERROR,
+				(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
+				 errmsg("extra data after last expected column")));
+
+	fieldno = 0;
+
+	/* Loop to read the user attributes on the line. */
+	foreach(cur, cstate->attnumlist)
+	{
+		int			attnum = lfirst_int(cur);
+		int			m = attnum - 1;
+		Form_pg_attribute att = TupleDescAttr(tupDesc, m);
+
+		if (fieldno >= fldct)
+			ereport(ERROR,
+					(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
+					 errmsg("missing data for column \"%s\"",
+							NameStr(att->attname))));
+		string = field_strings[fieldno++];
+
+		if (cstate->convert_select_flags &&
+			!cstate->convert_select_flags[m])
+		{
+			/* ignore input field, leaving column as NULL */
+			continue;
+		}
+
+		if (is_csv)
+		{
+			if (string == NULL &&
+				cstate->opts.force_notnull_flags[m])
+			{
+				/*
+				 * FORCE_NOT_NULL option is set and column is NULL - convert
+				 * it to the NULL string.
+				 */
+				string = cstate->opts.null_print;
+			}
+			else if (string != NULL && cstate->opts.force_null_flags[m]
+					 && strcmp(string, cstate->opts.null_print) == 0)
+			{
+				/*
+				 * FORCE_NULL option is set and column matches the NULL
+				 * string. It must have been quoted, or otherwise the string
+				 * would already have been set to NULL. Convert it to NULL as
+				 * specified.
+				 */
+				string = NULL;
+			}
+		}
+
+		cstate->cur_attname = NameStr(att->attname);
+		cstate->cur_attval = string;
+
+		if (string != NULL)
+			nulls[m] = false;
+
+		if (cstate->defaults[m])
+		{
+			/*
+			 * The caller must supply econtext and have switched into the
+			 * per-tuple memory context in it.
+			 */
+			Assert(econtext != NULL);
+			Assert(CurrentMemoryContext == econtext->ecxt_per_tuple_memory);
+
+			values[m] = ExecEvalExpr(defexprs[m], econtext, &nulls[m]);
+		}
+
+		/*
+		 * If ON_ERROR is specified with IGNORE, skip rows with soft errors
+		 */
+		else if (!InputFunctionCallSafe(&in_functions[m],
+										string,
+										typioparams[m],
+										att->atttypmod,
+										(Node *) cstate->escontext,
+										&values[m]))
+		{
+			Assert(cstate->opts.on_error != COPY_ON_ERROR_STOP);
+
+			cstate->num_errors++;
+
+			if (cstate->opts.log_verbosity == COPY_LOG_VERBOSITY_VERBOSE)
+			{
+				/*
+				 * Since we emit line number and column info in the below
+				 * notice message, we suppress error context information other
+				 * than the relation name.
+				 */
+				Assert(!cstate->relname_only);
+				cstate->relname_only = true;
+
+				if (cstate->cur_attval)
+				{
+					char	   *attval;
+
+					attval = CopyLimitPrintoutLength(cstate->cur_attval);
+					ereport(NOTICE,
+							errmsg("skipping row due to data type incompatibility at line %llu for column %s: \"%s\"",
+								   (unsigned long long) cstate->cur_lineno,
+								   cstate->cur_attname,
+								   attval));
+					pfree(attval);
+				}
+				else
+					ereport(NOTICE,
+							errmsg("skipping row due to data type incompatibility at line %llu for column %s: null input",
+								   (unsigned long long) cstate->cur_lineno,
+								   cstate->cur_attname));
+
+				/* reset relname_only */
+				cstate->relname_only = false;
+			}
+
+			return true;
+		}
+
+		cstate->cur_attname = NULL;
+		cstate->cur_attval = NULL;
+	}
+
+	Assert(fieldno == attr_count);
+
+	return true;
+}
+
+
+/*
+ * CopyFromTextOneRow
+ *
+ * Per-row callback for COPY FROM with text format.
+ */
+bool
+CopyFromTextOneRow(CopyFromState cstate,
+				   ExprContext *econtext,
+				   Datum *values,
+				   bool *nulls)
+{
+	return CopyFromTextLikeOneRow(cstate, econtext, values, nulls, false);
+}
+
+/*
+ * CopyFromCSVOneRow
+ *
+ * Per-row callback for COPY FROM with CSV format.
+ */
+bool
+CopyFromCSVOneRow(CopyFromState cstate,
+				  ExprContext *econtext,
+				  Datum *values,
+				  bool *nulls)
+{
+	return CopyFromTextLikeOneRow(cstate, econtext, values, nulls, true);
+}
+
+/*
+ * CopyFromBinaryOneRow
+ *
+ * Copy one row to a set of `values` and `nulls` for the binary format.
+ */
+bool
+CopyFromBinaryOneRow(CopyFromState cstate, ExprContext *econtext,
+					 Datum *values, bool *nulls)
+{
+	TupleDesc	tupDesc;
+	AttrNumber	attr_count;
+	FmgrInfo   *in_functions = cstate->in_functions;
+	Oid		   *typioparams = cstate->typioparams;
+	int16		fld_count;
+	ListCell   *cur;
+
+	tupDesc = RelationGetDescr(cstate->rel);
+	attr_count = list_length(cstate->attnumlist);
+
+	cstate->cur_lineno++;
+
+	if (!CopyGetInt16(cstate, &fld_count))
+	{
+		/* EOF detected (end of file, or protocol-level EOF) */
+		return false;
+	}
+
+	if (fld_count == -1)
+	{
+		/*
+		 * Received EOF marker.  Wait for the protocol-level EOF, and complain
+		 * if it doesn't come immediately.  In COPY FROM STDIN, this ensures
+		 * that we correctly handle CopyFail, if client chooses to send that
+		 * now.  When copying from file, we could ignore the rest of the file
+		 * like in text mode, but we choose to be consistent with the COPY
+		 * FROM STDIN case.
+		 */
+		char		dummy;
+
+		if (CopyReadBinaryData(cstate, &dummy, 1) > 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
+					 errmsg("received copy data after EOF marker")));
+		return false;
+	}
+
+	if (fld_count != attr_count)
+		ereport(ERROR,
+				(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
+				 errmsg("row field count is %d, expected %d",
+						(int) fld_count, attr_count)));
+
+	foreach(cur, cstate->attnumlist)
+	{
+		int			attnum = lfirst_int(cur);
+		int			m = attnum - 1;
+		Form_pg_attribute att = TupleDescAttr(tupDesc, m);
+
+		cstate->cur_attname = NameStr(att->attname);
+		values[m] = CopyReadBinaryAttribute(cstate,
+											&in_functions[m],
+											typioparams[m],
+											att->atttypmod,
+											&nulls[m]);
+		cstate->cur_attname = NULL;
+	}
+
+	return true;
+}
+
 /*
  * Read next tuple from file for COPY FROM. Return false if no more tuples.
  *
@@ -856,221 +1126,21 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
 {
 	TupleDesc	tupDesc;
 	AttrNumber	num_phys_attrs,
-				attr_count,
 				num_defaults = cstate->num_defaults;
-	FmgrInfo   *in_functions = cstate->in_functions;
-	Oid		   *typioparams = cstate->typioparams;
 	int			i;
 	int		   *defmap = cstate->defmap;
 	ExprState **defexprs = cstate->defexprs;
 
 	tupDesc = RelationGetDescr(cstate->rel);
 	num_phys_attrs = tupDesc->natts;
-	attr_count = list_length(cstate->attnumlist);
 
 	/* Initialize all values for row to NULL */
 	MemSet(values, 0, num_phys_attrs * sizeof(Datum));
 	MemSet(nulls, true, num_phys_attrs * sizeof(bool));
 	MemSet(cstate->defaults, false, num_phys_attrs * sizeof(bool));
 
-	if (!cstate->opts.binary)
-	{
-		char	  **field_strings;
-		ListCell   *cur;
-		int			fldct;
-		int			fieldno;
-		char	   *string;
-
-		/* read raw fields in the next line */
-		if (!NextCopyFromRawFields(cstate, &field_strings, &fldct))
-			return false;
-
-		/* check for overflowing fields */
-		if (attr_count > 0 && fldct > attr_count)
-			ereport(ERROR,
-					(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-					 errmsg("extra data after last expected column")));
-
-		fieldno = 0;
-
-		/* Loop to read the user attributes on the line. */
-		foreach(cur, cstate->attnumlist)
-		{
-			int			attnum = lfirst_int(cur);
-			int			m = attnum - 1;
-			Form_pg_attribute att = TupleDescAttr(tupDesc, m);
-
-			if (fieldno >= fldct)
-				ereport(ERROR,
-						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-						 errmsg("missing data for column \"%s\"",
-								NameStr(att->attname))));
-			string = field_strings[fieldno++];
-
-			if (cstate->convert_select_flags &&
-				!cstate->convert_select_flags[m])
-			{
-				/* ignore input field, leaving column as NULL */
-				continue;
-			}
-
-			if (cstate->opts.csv_mode)
-			{
-				if (string == NULL &&
-					cstate->opts.force_notnull_flags[m])
-				{
-					/*
-					 * FORCE_NOT_NULL option is set and column is NULL -
-					 * convert it to the NULL string.
-					 */
-					string = cstate->opts.null_print;
-				}
-				else if (string != NULL && cstate->opts.force_null_flags[m]
-						 && strcmp(string, cstate->opts.null_print) == 0)
-				{
-					/*
-					 * FORCE_NULL option is set and column matches the NULL
-					 * string. It must have been quoted, or otherwise the
-					 * string would already have been set to NULL. Convert it
-					 * to NULL as specified.
-					 */
-					string = NULL;
-				}
-			}
-
-			cstate->cur_attname = NameStr(att->attname);
-			cstate->cur_attval = string;
-
-			if (string != NULL)
-				nulls[m] = false;
-
-			if (cstate->defaults[m])
-			{
-				/*
-				 * The caller must supply econtext and have switched into the
-				 * per-tuple memory context in it.
-				 */
-				Assert(econtext != NULL);
-				Assert(CurrentMemoryContext == econtext->ecxt_per_tuple_memory);
-
-				values[m] = ExecEvalExpr(defexprs[m], econtext, &nulls[m]);
-			}
-
-			/*
-			 * If ON_ERROR is specified with IGNORE, skip rows with soft
-			 * errors
-			 */
-			else if (!InputFunctionCallSafe(&in_functions[m],
-											string,
-											typioparams[m],
-											att->atttypmod,
-											(Node *) cstate->escontext,
-											&values[m]))
-			{
-				Assert(cstate->opts.on_error != COPY_ON_ERROR_STOP);
-
-				cstate->num_errors++;
-
-				if (cstate->opts.log_verbosity == COPY_LOG_VERBOSITY_VERBOSE)
-				{
-					/*
-					 * Since we emit line number and column info in the below
-					 * notice message, we suppress error context information
-					 * other than the relation name.
-					 */
-					Assert(!cstate->relname_only);
-					cstate->relname_only = true;
-
-					if (cstate->cur_attval)
-					{
-						char	   *attval;
-
-						attval = CopyLimitPrintoutLength(cstate->cur_attval);
-						ereport(NOTICE,
-								errmsg("skipping row due to data type incompatibility at line %llu for column %s: \"%s\"",
-									   (unsigned long long) cstate->cur_lineno,
-									   cstate->cur_attname,
-									   attval));
-						pfree(attval);
-					}
-					else
-						ereport(NOTICE,
-								errmsg("skipping row due to data type incompatibility at line %llu for column %s: null input",
-									   (unsigned long long) cstate->cur_lineno,
-									   cstate->cur_attname));
-
-					/* reset relname_only */
-					cstate->relname_only = false;
-				}
-
-				return true;
-			}
-
-			cstate->cur_attname = NULL;
-			cstate->cur_attval = NULL;
-		}
-
-		Assert(fieldno == attr_count);
-	}
-	else if (cstate->routine)
-	{
-		if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls))
-			return false;
-	}
-	else
-	{
-		/* binary */
-		int16		fld_count;
-		ListCell   *cur;
-
-		cstate->cur_lineno++;
-
-		if (!CopyGetInt16(cstate, &fld_count))
-		{
-			/* EOF detected (end of file, or protocol-level EOF) */
-			return false;
-		}
-
-		if (fld_count == -1)
-		{
-			/*
-			 * Received EOF marker.  Wait for the protocol-level EOF, and
-			 * complain if it doesn't come immediately.  In COPY FROM STDIN,
-			 * this ensures that we correctly handle CopyFail, if client
-			 * chooses to send that now.  When copying from file, we could
-			 * ignore the rest of the file like in text mode, but we choose to
-			 * be consistent with the COPY FROM STDIN case.
-			 */
-			char		dummy;
-
-			if (CopyReadBinaryData(cstate, &dummy, 1) > 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-						 errmsg("received copy data after EOF marker")));
-			return false;
-		}
-
-		if (fld_count != attr_count)
-			ereport(ERROR,
-					(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-					 errmsg("row field count is %d, expected %d",
-							(int) fld_count, attr_count)));
-
-		foreach(cur, cstate->attnumlist)
-		{
-			int			attnum = lfirst_int(cur);
-			int			m = attnum - 1;
-			Form_pg_attribute att = TupleDescAttr(tupDesc, m);
-
-			cstate->cur_attname = NameStr(att->attname);
-			values[m] = CopyReadBinaryAttribute(cstate,
-												&in_functions[m],
-												typioparams[m],
-												att->atttypmod,
-												&nulls[m]);
-			cstate->cur_attname = NULL;
-		}
-	}
+	if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls))
+		return false;
 
 	/*
 	 * Now compute and insert any defaults available for the columns not
@@ -1100,8 +1170,8 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
  * by newline.  The terminating newline or EOF marker is not included
  * in the final value of line_buf.
  */
-static bool
-CopyReadLine(CopyFromState cstate)
+static inline bool
+CopyReadLine(CopyFromState cstate, bool is_csv)
 {
 	bool		result;
 
@@ -1109,7 +1179,7 @@ CopyReadLine(CopyFromState cstate)
 	cstate->line_buf_valid = false;
 
 	/* Parse data and transfer into line_buf */
-	result = CopyReadLineText(cstate);
+	result = CopyReadLineText(cstate, is_csv);
 
 	if (result)
 	{
@@ -1176,8 +1246,8 @@ CopyReadLine(CopyFromState cstate)
 /*
  * CopyReadLineText - inner loop of CopyReadLine for text mode
  */
-static bool
-CopyReadLineText(CopyFromState cstate)
+static inline bool
+CopyReadLineText(CopyFromState cstate, bool is_csv)
 {
 	char	   *copy_input_buf;
 	int			input_buf_ptr;
@@ -1193,7 +1263,11 @@ CopyReadLineText(CopyFromState cstate)
 	char		quotec = '\0';
 	char		escapec = '\0';
 
-	if (cstate->opts.csv_mode)
+	/*
+	 * is_csv will be optimized away by compiler, as argument is constant at
+	 * caller.
+	 */
+	if (is_csv)
 	{
 		quotec = cstate->opts.quote[0];
 		escapec = cstate->opts.escape[0];
@@ -1270,7 +1344,11 @@ CopyReadLineText(CopyFromState cstate)
 		prev_raw_ptr = input_buf_ptr;
 		c = copy_input_buf[input_buf_ptr++];
 
-		if (cstate->opts.csv_mode)
+		/*
+		 * is_csv will be optimized away by compiler, as argument is constant
+		 * at caller.
+		 */
+		if (is_csv)
 		{
 			/*
 			 * If character is '\\' or '\r', we may need to look ahead below.
@@ -1309,7 +1387,7 @@ CopyReadLineText(CopyFromState cstate)
 		}
 
 		/* Process \r */
-		if (c == '\r' && (!cstate->opts.csv_mode || !in_quote))
+		if (c == '\r' && (!is_csv || !in_quote))
 		{
 			/* Check for \r\n on first line, _and_ handle \r\n. */
 			if (cstate->eol_type == EOL_UNKNOWN ||
@@ -1337,10 +1415,10 @@ CopyReadLineText(CopyFromState cstate)
 					if (cstate->eol_type == EOL_CRNL)
 						ereport(ERROR,
 								(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-								 !cstate->opts.csv_mode ?
+								 !is_csv ?
 								 errmsg("literal carriage return found in data") :
 								 errmsg("unquoted carriage return found in data"),
-								 !cstate->opts.csv_mode ?
+								 !is_csv ?
 								 errhint("Use \"\\r\" to represent carriage return.") :
 								 errhint("Use quoted CSV field to represent carriage return.")));
 
@@ -1354,10 +1432,10 @@ CopyReadLineText(CopyFromState cstate)
 			else if (cstate->eol_type == EOL_NL)
 				ereport(ERROR,
 						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-						 !cstate->opts.csv_mode ?
+						 !is_csv ?
 						 errmsg("literal carriage return found in data") :
 						 errmsg("unquoted carriage return found in data"),
-						 !cstate->opts.csv_mode ?
+						 !is_csv ?
 						 errhint("Use \"\\r\" to represent carriage return.") :
 						 errhint("Use quoted CSV field to represent carriage return.")));
 			/* If reach here, we have found the line terminator */
@@ -1365,15 +1443,15 @@ CopyReadLineText(CopyFromState cstate)
 		}
 
 		/* Process \n */
-		if (c == '\n' && (!cstate->opts.csv_mode || !in_quote))
+		if (c == '\n' && (!is_csv || !in_quote))
 		{
 			if (cstate->eol_type == EOL_CR || cstate->eol_type == EOL_CRNL)
 				ereport(ERROR,
 						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-						 !cstate->opts.csv_mode ?
+						 !is_csv ?
 						 errmsg("literal newline found in data") :
 						 errmsg("unquoted newline found in data"),
-						 !cstate->opts.csv_mode ?
+						 !is_csv ?
 						 errhint("Use \"\\n\" to represent newline.") :
 						 errhint("Use quoted CSV field to represent newline.")));
 			cstate->eol_type = EOL_NL;	/* in case not set yet */
@@ -1385,7 +1463,7 @@ CopyReadLineText(CopyFromState cstate)
 		 * In CSV mode, we only recognize \. alone on a line.  This is because
 		 * \. is a valid CSV data value.
 		 */
-		if (c == '\\' && (!cstate->opts.csv_mode || first_char_in_line))
+		if (c == '\\' && (!is_csv || first_char_in_line))
 		{
 			char		c2;
 
@@ -1418,7 +1496,11 @@ CopyReadLineText(CopyFromState cstate)
 
 					if (c2 == '\n')
 					{
-						if (!cstate->opts.csv_mode)
+						/*
+						 * is_csv will be optimized away by compiler, as
+						 * argument is constant at caller.
+						 */
+						if (!is_csv)
 							ereport(ERROR,
 									(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
 									 errmsg("end-of-copy marker does not match previous newline style")));
@@ -1427,7 +1509,11 @@ CopyReadLineText(CopyFromState cstate)
 					}
 					else if (c2 != '\r')
 					{
-						if (!cstate->opts.csv_mode)
+						/*
+						 * is_csv will be optimized away by compiler, as
+						 * argument is constant at caller.
+						 */
+						if (!is_csv)
 							ereport(ERROR,
 									(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
 									 errmsg("end-of-copy marker corrupt")));
@@ -1443,7 +1529,11 @@ CopyReadLineText(CopyFromState cstate)
 
 				if (c2 != '\r' && c2 != '\n')
 				{
-					if (!cstate->opts.csv_mode)
+					/*
+					 * is_csv will be optimized away by compiler, as argument
+					 * is constant at caller.
+					 */
+					if (!is_csv)
 						ereport(ERROR,
 								(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
 								 errmsg("end-of-copy marker corrupt")));
@@ -1472,7 +1562,7 @@ CopyReadLineText(CopyFromState cstate)
 				result = true;	/* report EOF */
 				break;
 			}
-			else if (!cstate->opts.csv_mode)
+			else if (!is_csv)
 			{
 				/*
 				 * If we are here, it means we found a backslash followed by
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index ff19c457abf..c7f69ba606d 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -128,6 +128,321 @@ static void CopySendEndOfRow(CopyToState cstate);
 static void CopySendInt32(CopyToState cstate, int32 val);
 static void CopySendInt16(CopyToState cstate, int16 val);
 
+/*
+ * CopyToRoutine implementations.
+ */
+
+/*
+ * CopyToTextLikeSendEndOfRow
+ *
+ * Apply line terminations for a line sent in text or CSV format depending
+ * on the destination, then send the end of a row.
+ */
+static inline void
+CopyToTextLikeSendEndOfRow(CopyToState cstate)
+{
+	switch (cstate->copy_dest)
+	{
+		case COPY_FILE:
+			/* Default line termination depends on platform */
+#ifndef WIN32
+			CopySendChar(cstate, '\n');
+#else
+			CopySendString(cstate, "\r\n");
+#endif
+			break;
+		case COPY_FRONTEND:
+			/* The FE/BE protocol uses \n as newline for all platforms */
+			CopySendChar(cstate, '\n');
+			break;
+		default:
+			break;
+	}
+
+	/* Now take the actions related to the end of a row */
+	CopySendEndOfRow(cstate);
+}
+
+/*
+ * CopyToTextLikeStart
+ *
+ * Start of COPY TO for text and CSV format.
+ */
+static void
+CopyToTextLikeStart(CopyToState cstate, TupleDesc tupDesc)
+{
+	/*
+	 * For non-binary copy, we need to convert null_print to file encoding,
+	 * because it will be sent directly with CopySendString.
+	 */
+	if (cstate->need_transcoding)
+		cstate->opts.null_print_client = pg_server_to_any(cstate->opts.null_print,
+														  cstate->opts.null_print_len,
+														  cstate->file_encoding);
+
+	/* if a header has been requested send the line */
+	if (cstate->opts.header_line)
+	{
+		ListCell   *cur;
+		bool		hdr_delim = false;
+
+		foreach(cur, cstate->attnumlist)
+		{
+			int			attnum = lfirst_int(cur);
+			char	   *colname;
+
+			if (hdr_delim)
+				CopySendChar(cstate, cstate->opts.delim[0]);
+			hdr_delim = true;
+
+			colname = NameStr(TupleDescAttr(tupDesc, attnum - 1)->attname);
+
+			if (cstate->opts.csv_mode)
+				CopyAttributeOutCSV(cstate, colname, false);
+			else
+				CopyAttributeOutText(cstate, colname);
+		}
+
+		CopyToTextLikeSendEndOfRow(cstate);
+	}
+}
+
+/*
+ * CopyToTextLikeOutFunc
+ *
+ * Assign output function data for a relation's attribute in text/CSV format.
+ */
+static void
+CopyToTextLikeOutFunc(CopyToState cstate, Oid atttypid, FmgrInfo *finfo)
+{
+	Oid			func_oid;
+	bool		is_varlena;
+
+	/* Set output function for an attribute */
+	getTypeOutputInfo(atttypid, &func_oid, &is_varlena);
+	fmgr_info(func_oid, finfo);
+}
+
+
+/*
+ * CopyToTextLikeOneRow
+ *
+ * Process one row for text/CSV format.
+ *
+ * Workhorse for CopyToTextOneRow() and CopyToCSVOneRow().
+ */
+static inline void
+CopyToTextLikeOneRow(CopyToState cstate,
+					 TupleTableSlot *slot,
+					 bool is_csv)
+{
+	bool		need_delim = false;
+	FmgrInfo   *out_functions = cstate->out_functions;
+	ListCell   *cur;
+
+	foreach(cur, cstate->attnumlist)
+	{
+		int			attnum = lfirst_int(cur);
+		Datum		value = slot->tts_values[attnum - 1];
+		bool		isnull = slot->tts_isnull[attnum - 1];
+
+		if (need_delim)
+			CopySendChar(cstate, cstate->opts.delim[0]);
+		need_delim = true;
+
+		if (isnull)
+		{
+			CopySendString(cstate, cstate->opts.null_print_client);
+		}
+		else
+		{
+			char	   *string;
+
+			string = OutputFunctionCall(&out_functions[attnum - 1],
+										value);
+
+			/*
+			 * is_csv will be optimized away by compiler, as argument is
+			 * constant at caller.
+			 */
+			if (is_csv)
+				CopyAttributeOutCSV(cstate, string,
+									cstate->opts.force_quote_flags[attnum - 1]);
+			else
+				CopyAttributeOutText(cstate, string);
+		}
+	}
+
+	CopyToTextLikeSendEndOfRow(cstate);
+}
+
+/*
+ * CopyToTextOneRow
+ *
+ * Per-row callback for COPY TO with text format.
+ */
+static void
+CopyToTextOneRow(CopyToState cstate, TupleTableSlot *slot)
+{
+	CopyToTextLikeOneRow(cstate, slot, false);
+}
+
+/*
+ * CopyToTextOneRow
+ *
+ * Per-row callback for COPY TO with CSV format.
+ */
+static void
+CopyToCSVOneRow(CopyToState cstate, TupleTableSlot *slot)
+{
+	CopyToTextLikeOneRow(cstate, slot, true);
+}
+
+/*
+ * CopyToTextLikeEnd
+ *
+ * End of COPY TO for text/CSV format.
+ */
+static void
+CopyToTextLikeEnd(CopyToState cstate)
+{
+	/* Nothing to do here */
+}
+
+/*
+ * CopyToRoutine implementation for "binary".
+ */
+
+/*
+ * CopyToBinaryStart
+ *
+ * Start of COPY TO for binary format.
+ */
+static void
+CopyToBinaryStart(CopyToState cstate, TupleDesc tupDesc)
+{
+	/* Generate header for a binary copy */
+	int32		tmp;
+
+	/* Signature */
+	CopySendData(cstate, BinarySignature, 11);
+	/* Flags field */
+	tmp = 0;
+	CopySendInt32(cstate, tmp);
+	/* No header extension */
+	tmp = 0;
+	CopySendInt32(cstate, tmp);
+}
+
+/*
+ * CopyToBinaryOutFunc
+ *
+ * Assign output function data for a relation's attribute in binary format.
+ */
+static void
+CopyToBinaryOutFunc(CopyToState cstate, Oid atttypid, FmgrInfo *finfo)
+{
+	Oid			func_oid;
+	bool		is_varlena;
+
+	/* Set output function for an attribute */
+	getTypeBinaryOutputInfo(atttypid, &func_oid, &is_varlena);
+	fmgr_info(func_oid, finfo);
+}
+
+/*
+ * CopyToBinaryOneRow
+ *
+ * Process one row for binary format.
+ */
+static void
+CopyToBinaryOneRow(CopyToState cstate, TupleTableSlot *slot)
+{
+	FmgrInfo   *out_functions = cstate->out_functions;
+	ListCell   *cur;
+
+	/* Binary per-tuple header */
+	CopySendInt16(cstate, list_length(cstate->attnumlist));
+
+	foreach(cur, cstate->attnumlist)
+	{
+		int			attnum = lfirst_int(cur);
+		Datum		value = slot->tts_values[attnum - 1];
+		bool		isnull = slot->tts_isnull[attnum - 1];
+
+		if (isnull)
+		{
+			CopySendInt32(cstate, -1);
+		}
+		else
+		{
+			bytea	   *outputbytes;
+
+			outputbytes = SendFunctionCall(&out_functions[attnum - 1],
+										   value);
+			CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ);
+			CopySendData(cstate, VARDATA(outputbytes),
+						 VARSIZE(outputbytes) - VARHDRSZ);
+		}
+	}
+
+	CopySendEndOfRow(cstate);
+}
+
+/*
+ * CopyToBinaryEnd
+ *
+ * End of COPY TO for binary format.
+ */
+static void
+CopyToBinaryEnd(CopyToState cstate)
+{
+	/* Generate trailer for a binary copy */
+	CopySendInt16(cstate, -1);
+	/* Need to flush out the trailer */
+	CopySendEndOfRow(cstate);
+}
+
+/*
+ * CSV and text share the same implementation, at the exception of the
+ * output representation and per-row callbacks.
+ */
+static const CopyToRoutine CopyToRoutineText = {
+	.CopyToStart = CopyToTextLikeStart,
+	.CopyToOutFunc = CopyToTextLikeOutFunc,
+	.CopyToOneRow = CopyToTextOneRow,
+	.CopyToEnd = CopyToTextLikeEnd,
+};
+
+static const CopyToRoutine CopyToRoutineCSV = {
+	.CopyToStart = CopyToTextLikeStart,
+	.CopyToOutFunc = CopyToTextLikeOutFunc,
+	.CopyToOneRow = CopyToCSVOneRow,
+	.CopyToEnd = CopyToTextLikeEnd,
+};
+
+static const CopyToRoutine CopyToRoutineBinary = {
+	.CopyToStart = CopyToBinaryStart,
+	.CopyToOutFunc = CopyToBinaryOutFunc,
+	.CopyToOneRow = CopyToBinaryOneRow,
+	.CopyToEnd = CopyToBinaryEnd,
+};
+
+/*
+ * Define the COPY TO routines to use for a format.  This should be called
+ * after options are parsed.
+ */
+static const CopyToRoutine *
+CopyToGetRoutine(CopyFormatOptions opts)
+{
+	if (opts.csv_mode)
+		return &CopyToRoutineCSV;
+	else if (opts.binary)
+		return &CopyToRoutineBinary;
+
+	/* default is text */
+	return &CopyToRoutineText;
+}
 
 /*
  * Send copy start/stop messages for frontend copies.  These have changed
@@ -195,16 +510,6 @@ CopySendEndOfRow(CopyToState cstate)
 	switch (cstate->copy_dest)
 	{
 		case COPY_FILE:
-			if (!cstate->opts.binary)
-			{
-				/* Default line termination depends on platform */
-#ifndef WIN32
-				CopySendChar(cstate, '\n');
-#else
-				CopySendString(cstate, "\r\n");
-#endif
-			}
-
 			if (fwrite(fe_msgbuf->data, fe_msgbuf->len, 1,
 					   cstate->copy_file) != 1 ||
 				ferror(cstate->copy_file))
@@ -239,10 +544,6 @@ CopySendEndOfRow(CopyToState cstate)
 			}
 			break;
 		case COPY_FRONTEND:
-			/* The FE/BE protocol uses \n as newline for all platforms */
-			if (!cstate->opts.binary)
-				CopySendChar(cstate, '\n');
-
 			/* Dump the accumulated row as one CopyData message */
 			(void) pq_putmessage(PqMsg_CopyData, fe_msgbuf->data, fe_msgbuf->len);
 			break;
@@ -430,6 +731,9 @@ BeginCopyTo(ParseState *pstate,
 	/* Extract options from the statement node tree */
 	ProcessCopyOptions(pstate, &cstate->opts, false /* is_from */ , options);
 
+	/* Set format routine */
+	cstate->routine = CopyToGetRoutine(cstate->opts);
+
 	/* Process the source/target relation or query */
 	if (rel)
 	{
@@ -770,27 +1074,10 @@ DoCopyTo(CopyToState cstate)
 	foreach(cur, cstate->attnumlist)
 	{
 		int			attnum = lfirst_int(cur);
-		Oid			out_func_oid;
-		bool		isvarlena;
 		Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1);
 
-		if (cstate->opts.binary)
-		{
-			getTypeBinaryOutputInfo(attr->atttypid,
-									&out_func_oid,
-									&isvarlena);
-			fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
-		}
-		else if (cstate->routine)
-			cstate->routine->CopyToOutFunc(cstate, attr->atttypid,
-										   &cstate->out_functions[attnum - 1]);
-		else
-		{
-			getTypeOutputInfo(attr->atttypid,
-							  &out_func_oid,
-							  &isvarlena);
-			fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]);
-		}
+		cstate->routine->CopyToOutFunc(cstate, attr->atttypid,
+									   &cstate->out_functions[attnum - 1]);
 	}
 
 	/*
@@ -803,58 +1090,7 @@ DoCopyTo(CopyToState cstate)
 											   "COPY TO",
 											   ALLOCSET_DEFAULT_SIZES);
 
-	if (cstate->opts.binary)
-	{
-		/* Generate header for a binary copy */
-		int32		tmp;
-
-		/* Signature */
-		CopySendData(cstate, BinarySignature, 11);
-		/* Flags field */
-		tmp = 0;
-		CopySendInt32(cstate, tmp);
-		/* No header extension */
-		tmp = 0;
-		CopySendInt32(cstate, tmp);
-	}
-	else if (cstate->routine)
-		cstate->routine->CopyToStart(cstate, tupDesc);
-	else
-	{
-		/*
-		 * For non-binary copy, we need to convert null_print to file
-		 * encoding, because it will be sent directly with CopySendString.
-		 */
-		if (cstate->need_transcoding)
-			cstate->opts.null_print_client = pg_server_to_any(cstate->opts.null_print,
-															  cstate->opts.null_print_len,
-															  cstate->file_encoding);
-
-		/* if a header has been requested send the line */
-		if (cstate->opts.header_line)
-		{
-			bool		hdr_delim = false;
-
-			foreach(cur, cstate->attnumlist)
-			{
-				int			attnum = lfirst_int(cur);
-				char	   *colname;
-
-				if (hdr_delim)
-					CopySendChar(cstate, cstate->opts.delim[0]);
-				hdr_delim = true;
-
-				colname = NameStr(TupleDescAttr(tupDesc, attnum - 1)->attname);
-
-				if (cstate->opts.csv_mode)
-					CopyAttributeOutCSV(cstate, colname, false);
-				else
-					CopyAttributeOutText(cstate, colname);
-			}
-
-			CopySendEndOfRow(cstate);
-		}
-	}
+	cstate->routine->CopyToStart(cstate, tupDesc);
 
 	if (cstate->rel)
 	{
@@ -893,15 +1129,7 @@ DoCopyTo(CopyToState cstate)
 		processed = ((DR_copy *) cstate->queryDesc->dest)->processed;
 	}
 
-	if (cstate->opts.binary)
-	{
-		/* Generate trailer for a binary copy */
-		CopySendInt16(cstate, -1);
-		/* Need to flush out the trailer */
-		CopySendEndOfRow(cstate);
-	}
-	else if (cstate->routine)
-		cstate->routine->CopyToEnd(cstate);
+	cstate->routine->CopyToEnd(cstate);
 
 	MemoryContextDelete(cstate->rowcontext);
 
@@ -917,11 +1145,7 @@ DoCopyTo(CopyToState cstate)
 static void
 CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
 {
-	bool		need_delim = false;
-	FmgrInfo   *out_functions = cstate->out_functions;
 	MemoryContext oldcontext;
-	ListCell   *cur;
-	char	   *string;
 
 	MemoryContextReset(cstate->rowcontext);
 	oldcontext = MemoryContextSwitchTo(cstate->rowcontext);
@@ -929,65 +1153,7 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot)
 	/* Make sure the tuple is fully deconstructed */
 	slot_getallattrs(slot);
 
-	if (cstate->routine)
-	{
-		cstate->routine->CopyToOneRow(cstate, slot);
-		MemoryContextSwitchTo(oldcontext);
-		return;
-	}
-
-	if (cstate->opts.binary)
-	{
-		/* Binary per-tuple header */
-		CopySendInt16(cstate, list_length(cstate->attnumlist));
-	}
-
-	foreach(cur, cstate->attnumlist)
-	{
-		int			attnum = lfirst_int(cur);
-		Datum		value = slot->tts_values[attnum - 1];
-		bool		isnull = slot->tts_isnull[attnum - 1];
-
-		if (!cstate->opts.binary)
-		{
-			if (need_delim)
-				CopySendChar(cstate, cstate->opts.delim[0]);
-			need_delim = true;
-		}
-
-		if (isnull)
-		{
-			if (!cstate->opts.binary)
-				CopySendString(cstate, cstate->opts.null_print_client);
-			else
-				CopySendInt32(cstate, -1);
-		}
-		else
-		{
-			if (!cstate->opts.binary)
-			{
-				string = OutputFunctionCall(&out_functions[attnum - 1],
-											value);
-				if (cstate->opts.csv_mode)
-					CopyAttributeOutCSV(cstate, string,
-										cstate->opts.force_quote_flags[attnum - 1]);
-				else
-					CopyAttributeOutText(cstate, string);
-			}
-			else
-			{
-				bytea	   *outputbytes;
-
-				outputbytes = SendFunctionCall(&out_functions[attnum - 1],
-											   value);
-				CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ);
-				CopySendData(cstate, VARDATA(outputbytes),
-							 VARSIZE(outputbytes) - VARHDRSZ);
-			}
-		}
-	}
-
-	CopySendEndOfRow(cstate);
+	cstate->routine->CopyToOneRow(cstate, slot);
 
 	MemoryContextSwitchTo(oldcontext);
 }
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 141fd48dc10..ccfbdf0ee01 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -104,8 +104,6 @@ extern CopyFromState BeginCopyFrom(ParseState *pstate, Relation rel, Node *where
 extern void EndCopyFrom(CopyFromState cstate);
 extern bool NextCopyFrom(CopyFromState cstate, ExprContext *econtext,
 						 Datum *values, bool *nulls);
-extern bool NextCopyFromRawFields(CopyFromState cstate,
-								  char ***fields, int *nfields);
 extern void CopyFromErrorCallback(void *arg);
 extern char *CopyLimitPrintoutLength(const char *str);
 
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index 509b9e92a18..c11b5ff3cc0 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -187,4 +187,12 @@ typedef struct CopyFromStateData
 extern void ReceiveCopyBegin(CopyFromState cstate);
 extern void ReceiveCopyBinaryHeader(CopyFromState cstate);
 
+/* Callbacks for CopyFromRoutine->CopyFromOneRow */
+extern bool CopyFromTextOneRow(CopyFromState cstate, ExprContext *econtext,
+							   Datum *values, bool *nulls);
+extern bool CopyFromCSVOneRow(CopyFromState cstate, ExprContext *econtext,
+							  Datum *values, bool *nulls);
+extern bool CopyFromBinaryOneRow(CopyFromState cstate, ExprContext *econtext,
+								 Datum *values, bool *nulls);
+
 #endif							/* COPYFROM_INTERNAL_H */
-- 
2.45.2

>From 273918b50a2c64659376e4a9b8e42d2a6480abfb Mon Sep 17 00:00:00 2001
From: Sutou Kouhei <[email-masked]>
Date: Tue, 23 Jul 2024 17:39:41 +0900
Subject: [PATCH v19 3/5] Add support for adding custom COPY TO format

This uses the handler approach like tablesample. The approach creates
an internal function that returns an internal struct. In this case,
a COPY TO handler returns a CopyToRoutine and a COPY FROM handler
returns a CopyFromRoutine.

This uses the same handler for COPY TO and COPY FROM. PostgreSQL calls a
COPY TO/FROM handler with "is_from" argument. It's true for COPY FROM
and false for COPY TO:

    copy_handler(true) returns CopyToRoutine
    copy_handler(false) returns CopyFromRoutine

This also add a test module for custom COPY TO/FROM handler.
---
 src/backend/commands/copy.c                   |  96 ++++++++++++++---
 src/backend/commands/copyfrom.c               |   4 +-
 src/backend/commands/copyto.c                 |   4 +-
 src/backend/nodes/Makefile                    |   1 +
 src/backend/nodes/gen_node_support.pl         |   2 +
 src/backend/utils/adt/pseudotypes.c           |   1 +
 src/include/catalog/pg_proc.dat               |   6 ++
 src/include/catalog/pg_type.dat               |   6 ++
 src/include/commands/copy.h                   |   2 +
 src/include/commands/copyapi.h                |   4 +
 src/include/nodes/meson.build                 |   1 +
 src/test/modules/Makefile                     |   1 +
 src/test/modules/meson.build                  |   1 +
 src/test/modules/test_copy_format/.gitignore  |   4 +
 src/test/modules/test_copy_format/Makefile    |  23 ++++
 .../expected/test_copy_format.out             |  21 ++++
 src/test/modules/test_copy_format/meson.build |  33 ++++++
 .../test_copy_format/sql/test_copy_format.sql |   6 ++
 .../test_copy_format--1.0.sql                 |   8 ++
 .../test_copy_format/test_copy_format.c       | 100 ++++++++++++++++++
 .../test_copy_format/test_copy_format.control |   4 +
 21 files changed, 313 insertions(+), 15 deletions(-)
 create mode 100644 src/test/modules/test_copy_format/.gitignore
 create mode 100644 src/test/modules/test_copy_format/Makefile
 create mode 100644 src/test/modules/test_copy_format/expected/test_copy_format.out
 create mode 100644 src/test/modules/test_copy_format/meson.build
 create mode 100644 src/test/modules/test_copy_format/sql/test_copy_format.sql
 create mode 100644 src/test/modules/test_copy_format/test_copy_format--1.0.sql
 create mode 100644 src/test/modules/test_copy_format/test_copy_format.c
 create mode 100644 src/test/modules/test_copy_format/test_copy_format.control

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index df7a4a21c94..e5137e7bb3d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -32,6 +32,7 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
+#include "parser/parse_func.h"
 #include "parser/parse_relation.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -439,6 +440,87 @@ defGetCopyLogVerbosityChoice(DefElem *def, ParseState *pstate)
 	return COPY_LOG_VERBOSITY_DEFAULT;	/* keep compiler quiet */
 }
 
+/*
+ * Process the "format" option.
+ *
+ * This function checks whether the option value is a built-in format such as
+ * "text" and "csv" or not. If the option value isn't a built-in format, this
+ * function finds a COPY format handler that returns a CopyToRoutine (for
+ * is_from == false) or CopyFromRountine (for is_from == true). If no COPY
+ * format handler is found, this function reports an error.
+ */
+static void
+ProcessCopyOptionFormat(ParseState *pstate,
+						CopyFormatOptions *opts_out,
+						bool is_from,
+						DefElem *defel)
+{
+	char	   *format;
+	Oid			funcargtypes[1];
+	Oid			handlerOid = InvalidOid;
+	Datum		datum;
+	Node	   *routine;
+
+	format = defGetString(defel);
+
+	/* built-in formats */
+	if (strcmp(format, "text") == 0)
+		 /* default format */ return;
+	else if (strcmp(format, "csv") == 0)
+	{
+		opts_out->csv_mode = true;
+		return;
+	}
+	else if (strcmp(format, "binary") == 0)
+	{
+		opts_out->binary = true;
+		return;
+	}
+
+	/* custom format */
+	funcargtypes[0] = INTERNALOID;
+	handlerOid = LookupFuncName(list_make1(makeString(format)), 1,
+								funcargtypes, true);
+	if (!OidIsValid(handlerOid))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("COPY format \"%s\" not recognized", format),
+				 parser_errposition(pstate, defel->location)));
+
+	datum = OidFunctionCall1(handlerOid, BoolGetDatum(is_from));
+	routine = (Node *) DatumGetPointer(datum);
+	if (is_from)
+	{
+		if (routine == NULL || !IsA(routine, CopyFromRoutine))
+			ereport(
+					ERROR,
+					(errcode(
+							 ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("COPY handler function "
+							"%s(%u) did not return a "
+							"CopyFromRoutine struct",
+							format, handlerOid),
+					 parser_errposition(
+										pstate, defel->location)));
+	}
+	else
+	{
+		if (routine == NULL || !IsA(routine, CopyToRoutine))
+			ereport(
+					ERROR,
+					(errcode(
+							 ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("COPY handler function "
+							"%s(%u) did not return a "
+							"CopyToRoutine struct",
+							format, handlerOid),
+					 parser_errposition(
+										pstate, defel->location)));
+	}
+
+	opts_out->routine = routine;
+}
+
 /*
  * Process the statement option list for COPY.
  *
@@ -481,22 +563,10 @@ ProcessCopyOptions(ParseState *pstate,
 
 		if (strcmp(defel->defname, "format") == 0)
 		{
-			char	   *fmt = defGetString(defel);
-
 			if (format_specified)
 				errorConflictingDefElem(defel, pstate);
 			format_specified = true;
-			if (strcmp(fmt, "text") == 0)
-				 /* default format */ ;
-			else if (strcmp(fmt, "csv") == 0)
-				opts_out->csv_mode = true;
-			else if (strcmp(fmt, "binary") == 0)
-				opts_out->binary = true;
-			else
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("COPY format \"%s\" not recognized", fmt),
-						 parser_errposition(pstate, defel->location)));
+			ProcessCopyOptionFormat(pstate, opts_out, is_from, defel);
 		}
 		else if (strcmp(defel->defname, "freeze") == 0)
 		{
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1a59202f5ab..2b48c825a0a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -244,7 +244,9 @@ static const CopyFromRoutine CopyFromRoutineBinary = {
 static const CopyFromRoutine *
 CopyFromGetRoutine(CopyFormatOptions opts)
 {
-	if (opts.csv_mode)
+	if (opts.routine)
+		return (const CopyFromRoutine *) opts.routine;
+	else if (opts.csv_mode)
 		return &CopyFromRoutineCSV;
 	else if (opts.binary)
 		return &CopyFromRoutineBinary;
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index c7f69ba606d..a9e923467dc 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -435,7 +435,9 @@ static const CopyToRoutine CopyToRoutineBinary = {
 static const CopyToRoutine *
 CopyToGetRoutine(CopyFormatOptions opts)
 {
-	if (opts.csv_mode)
+	if (opts.routine)
+		return (const CopyToRoutine *) opts.routine;
+	else if (opts.csv_mode)
 		return &CopyToRoutineCSV;
 	else if (opts.binary)
 		return &CopyToRoutineBinary;
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e0..173ee11811c 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -49,6 +49,7 @@ node_headers = \
 	access/sdir.h \
 	access/tableam.h \
 	access/tsmapi.h \
+	commands/copyapi.h \
 	commands/event_trigger.h \
 	commands/trigger.h \
 	executor/tuptable.h \
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 81df3bdf95f..428ab4f0d93 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -61,6 +61,7 @@ my @all_input_files = qw(
   access/sdir.h
   access/tableam.h
   access/tsmapi.h
+  commands/copyapi.h
   commands/event_trigger.h
   commands/trigger.h
   executor/tuptable.h
@@ -85,6 +86,7 @@ my @nodetag_only_files = qw(
   access/sdir.h
   access/tableam.h
   access/tsmapi.h
+  commands/copyapi.h
   commands/event_trigger.h
   commands/trigger.h
   executor/tuptable.h
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index e189e9b79d2..25f24ab95d2 100644
--- a/src/backend/utils/adt/pseudotypes.c
+++ b/src/backend/utils/adt/pseudotypes.c
@@ -370,6 +370,7 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(fdw_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(index_am_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(tsm_handler);
+PSEUDOTYPE_DUMMY_IO_FUNCS(copy_handler);
 PSEUDOTYPE_DUMMY_IO_FUNCS(internal);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement);
 PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 73d9cf85826..126254473e6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7644,6 +7644,12 @@
 { oid => '3312', descr => 'I/O',
   proname => 'tsm_handler_out', prorettype => 'cstring',
   proargtypes => 'tsm_handler', prosrc => 'tsm_handler_out' },
+{ oid => '8753', descr => 'I/O',
+  proname => 'copy_handler_in', proisstrict => 'f', prorettype => 'copy_handler',
+  proargtypes => 'cstring', prosrc => 'copy_handler_in' },
+{ oid => '8754', descr => 'I/O',
+  proname => 'copy_handler_out', prorettype => 'cstring',
+  proargtypes => 'copy_handler', prosrc => 'copy_handler_out' },
 { oid => '267', descr => 'I/O',
   proname => 'table_am_handler_in', proisstrict => 'f',
   prorettype => 'table_am_handler', proargtypes => 'cstring',
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index ceff66ccde1..14c6c1ea486 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -633,6 +633,12 @@
   typcategory => 'P', typinput => 'tsm_handler_in',
   typoutput => 'tsm_handler_out', typreceive => '-', typsend => '-',
   typalign => 'i' },
+{ oid => '8752',
+  descr => 'pseudo-type for the result of a copy to/from method functoin',
+  typname => 'copy_handler', typlen => '4', typbyval => 't', typtype => 'p',
+  typcategory => 'P', typinput => 'copy_handler_in',
+  typoutput => 'copy_handler_out', typreceive => '-', typsend => '-',
+  typalign => 'i' },
 { oid => '269',
   descr => 'pseudo-type for the result of a table AM handler function',
   typname => 'table_am_handler', typlen => '4', typbyval => 't', typtype => 'p',
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index ccfbdf0ee01..79bd4fb9151 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -84,6 +84,8 @@ typedef struct CopyFormatOptions
 	CopyOnErrorChoice on_error; /* what to do when error happened */
 	CopyLogVerbosityChoice log_verbosity;	/* verbosity of logged messages */
 	List	   *convert_select; /* list of column names (can be NIL) */
+	Node	   *routine;		/* CopyToRoutine or CopyFromRoutine (can be
+								 * NULL) */
 } CopyFormatOptions;
 
 /* These are private in commands/copy[from|to].c */
diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h
index d1289424c67..e049a45a4b1 100644
--- a/src/include/commands/copyapi.h
+++ b/src/include/commands/copyapi.h
@@ -27,6 +27,8 @@ typedef struct CopyToStateData *CopyToState;
  */
 typedef struct CopyFromRoutine
 {
+	NodeTag		type;
+
 	/*
 	 * Called when COPY FROM is started to set up the input functions
 	 * associated with the relation's attributes writing to.  `finfo` can be
@@ -69,6 +71,8 @@ typedef struct CopyFromRoutine
  */
 typedef struct CopyToRoutine
 {
+	NodeTag		type;
+
 	/*
 	 * Called when COPY TO is started to set up the output functions
 	 * associated with the relation's attributes reading from.  `finfo` can be
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index b665e55b657..103df1a7873 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -11,6 +11,7 @@ node_support_input_i = [
   'access/sdir.h',
   'access/tableam.h',
   'access/tsmapi.h',
+  'commands/copyapi.h',
   'commands/event_trigger.h',
   'commands/trigger.h',
   'executor/tuptable.h',
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 256799f520a..b7b46928a19 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -15,6 +15,7 @@ SUBDIRS = \
 		  spgist_name_ops \
 		  test_bloomfilter \
 		  test_copy_callbacks \
+		  test_copy_format \
 		  test_custom_rmgrs \
 		  test_ddl_deparse \
 		  test_dsa \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index d8fe059d236..c42b4b2b31f 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -14,6 +14,7 @@ subdir('spgist_name_ops')
 subdir('ssl_passphrase_callback')
 subdir('test_bloomfilter')
 subdir('test_copy_callbacks')
+subdir('test_copy_format')
 subdir('test_custom_rmgrs')
 subdir('test_ddl_deparse')
 subdir('test_dsa')
diff --git a/src/test/modules/test_copy_format/.gitignore b/src/test/modules/test_copy_format/.gitignore
new file mode 100644
index 00000000000..5dcb3ff9723
--- /dev/null
+++ b/src/test/modules/test_copy_format/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_copy_format/Makefile b/src/test/modules/test_copy_format/Makefile
new file mode 100644
index 00000000000..8497f91624d
--- /dev/null
+++ b/src/test/modules/test_copy_format/Makefile
@@ -0,0 +1,23 @@
+# src/test/modules/test_copy_format/Makefile
+
+MODULE_big = test_copy_format
+OBJS = \
+	$(WIN32RES) \
+	test_copy_format.o
+PGFILEDESC = "test_copy_format - test custom COPY FORMAT"
+
+EXTENSION = test_copy_format
+DATA = test_copy_format--1.0.sql
+
+REGRESS = test_copy_format
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_copy_format
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_copy_format/expected/test_copy_format.out b/src/test/modules/test_copy_format/expected/test_copy_format.out
new file mode 100644
index 00000000000..4ed7c0b12db
--- /dev/null
+++ b/src/test/modules/test_copy_format/expected/test_copy_format.out
@@ -0,0 +1,21 @@
+CREATE EXTENSION test_copy_format;
+CREATE TABLE public.test (a smallint, b integer, c bigint);
+INSERT INTO public.test VALUES (1, 2, 3), (12, 34, 56), (123, 456, 789);
+COPY public.test FROM stdin WITH (format 'test_copy_format');
+NOTICE:  test_copy_format: is_from=true
+NOTICE:  CopyFromInFunc: atttypid=21
+NOTICE:  CopyFromInFunc: atttypid=23
+NOTICE:  CopyFromInFunc: atttypid=20
+NOTICE:  CopyFromStart: natts=3
+NOTICE:  CopyFromOneRow
+NOTICE:  CopyFromEnd
+COPY public.test TO stdout WITH (format 'test_copy_format');
+NOTICE:  test_copy_format: is_from=false
+NOTICE:  CopyToOutFunc: atttypid=21
+NOTICE:  CopyToOutFunc: atttypid=23
+NOTICE:  CopyToOutFunc: atttypid=20
+NOTICE:  CopyToStart: natts=3
+NOTICE:  CopyToOneRow: tts_nvalid=3
+NOTICE:  CopyToOneRow: tts_nvalid=3
+NOTICE:  CopyToOneRow: tts_nvalid=3
+NOTICE:  CopyToEnd
diff --git a/src/test/modules/test_copy_format/meson.build b/src/test/modules/test_copy_format/meson.build
new file mode 100644
index 00000000000..4cefe7b709a
--- /dev/null
+++ b/src/test/modules/test_copy_format/meson.build
@@ -0,0 +1,33 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+test_copy_format_sources = files(
+  'test_copy_format.c',
+)
+
+if host_system == 'windows'
+  test_copy_format_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_copy_format',
+    '--FILEDESC', 'test_copy_format - test custom COPY FORMAT',])
+endif
+
+test_copy_format = shared_module('test_copy_format',
+  test_copy_format_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_copy_format
+
+test_install_data += files(
+  'test_copy_format.control',
+  'test_copy_format--1.0.sql',
+)
+
+tests += {
+  'name': 'test_copy_format',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'test_copy_format',
+    ],
+  },
+}
diff --git a/src/test/modules/test_copy_format/sql/test_copy_format.sql b/src/test/modules/test_copy_format/sql/test_copy_format.sql
new file mode 100644
index 00000000000..e805f7cb011
--- /dev/null
+++ b/src/test/modules/test_copy_format/sql/test_copy_format.sql
@@ -0,0 +1,6 @@
+CREATE EXTENSION test_copy_format;
+CREATE TABLE public.test (a smallint, b integer, c bigint);
+INSERT INTO public.test VALUES (1, 2, 3), (12, 34, 56), (123, 456, 789);
+COPY public.test FROM stdin WITH (format 'test_copy_format');
+\.
+COPY public.test TO stdout WITH (format 'test_copy_format');
diff --git a/src/test/modules/test_copy_format/test_copy_format--1.0.sql b/src/test/modules/test_copy_format/test_copy_format--1.0.sql
new file mode 100644
index 00000000000..d24ea03ce99
--- /dev/null
+++ b/src/test/modules/test_copy_format/test_copy_format--1.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_copy_format/test_copy_format--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_copy_format" to load this file. \quit
+
+CREATE FUNCTION test_copy_format(internal)
+	RETURNS copy_handler
+	AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_copy_format/test_copy_format.c b/src/test/modules/test_copy_format/test_copy_format.c
new file mode 100644
index 00000000000..f6b105659ab
--- /dev/null
+++ b/src/test/modules/test_copy_format/test_copy_format.c
@@ -0,0 +1,100 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_copy_format.c
+ *		Code for testing custom COPY format.
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_copy_format/test_copy_format.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "commands/copyapi.h"
+#include "commands/defrem.h"
+
+PG_MODULE_MAGIC;
+
+static void
+CopyFromInFunc(CopyFromState cstate, Oid atttypid,
+			   FmgrInfo *finfo, Oid *typioparam)
+{
+	ereport(NOTICE, (errmsg("CopyFromInFunc: atttypid=%d", atttypid)));
+}
+
+static void
+CopyFromStart(CopyFromState cstate, TupleDesc tupDesc)
+{
+	ereport(NOTICE, (errmsg("CopyFromStart: natts=%d", tupDesc->natts)));
+}
+
+static bool
+CopyFromOneRow(CopyFromState cstate, ExprContext *econtext, Datum *values, bool *nulls)
+{
+	ereport(NOTICE, (errmsg("CopyFromOneRow")));
+	return false;
+}
+
+static void
+CopyFromEnd(CopyFromState cstate)
+{
+	ereport(NOTICE, (errmsg("CopyFromEnd")));
+}
+
+static const CopyFromRoutine CopyFromRoutineTestCopyFormat = {
+	.type = T_CopyFromRoutine,
+	.CopyFromInFunc = CopyFromInFunc,
+	.CopyFromStart = CopyFromStart,
+	.CopyFromOneRow = CopyFromOneRow,
+	.CopyFromEnd = CopyFromEnd,
+};
+
+static void
+CopyToOutFunc(CopyToState cstate, Oid atttypid, FmgrInfo *finfo)
+{
+	ereport(NOTICE, (errmsg("CopyToOutFunc: atttypid=%d", atttypid)));
+}
+
+static void
+CopyToStart(CopyToState cstate, TupleDesc tupDesc)
+{
+	ereport(NOTICE, (errmsg("CopyToStart: natts=%d", tupDesc->natts)));
+}
+
+static void
+CopyToOneRow(CopyToState cstate, TupleTableSlot *slot)
+{
+	ereport(NOTICE, (errmsg("CopyToOneRow: tts_nvalid=%u", slot->tts_nvalid)));
+}
+
+static void
+CopyToEnd(CopyToState cstate)
+{
+	ereport(NOTICE, (errmsg("CopyToEnd")));
+}
+
+static const CopyToRoutine CopyToRoutineTestCopyFormat = {
+	.type = T_CopyToRoutine,
+	.CopyToOutFunc = CopyToOutFunc,
+	.CopyToStart = CopyToStart,
+	.CopyToOneRow = CopyToOneRow,
+	.CopyToEnd = CopyToEnd,
+};
+
+PG_FUNCTION_INFO_V1(test_copy_format);
+Datum
+test_copy_format(PG_FUNCTION_ARGS)
+{
+	bool		is_from = PG_GETARG_BOOL(0);
+
+	ereport(NOTICE,
+			(errmsg("test_copy_format: is_from=%s", is_from ? "true" : "false")));
+
+	if (is_from)
+		PG_RETURN_POINTER(&CopyFromRoutineTestCopyFormat);
+	else
+		PG_RETURN_POINTER(&CopyToRoutineTestCopyFormat);
+}
diff --git a/src/test/modules/test_copy_format/test_copy_format.control b/src/test/modules/test_copy_format/test_copy_format.control
new file mode 100644
index 00000000000..f05a6362358
--- /dev/null
+++ b/src/test/modules/test_copy_format/test_copy_format.control
@@ -0,0 +1,4 @@
+comment = 'Test code for custom COPY format'
+default_version = '1.0'
+module_pathname = '$libdir/test_copy_format'
+relocatable = true
-- 
2.45.2

>From a6670ca32945b19f5c2a35b6f4094fa8286ed327 Mon Sep 17 00:00:00 2001
From: Sutou Kouhei <[email-masked]>
Date: Tue, 23 Jan 2024 14:54:10 +0900
Subject: [PATCH v19 4/5] Export CopyToStateData and CopyFromStateData

It's for custom COPY TO/FROM format handlers implemented as extension.

This just moves codes. This doesn't change codes except
CopyDest/CopyFrom enum values. CopyDest/CopyFrom enum values such as
COPY_FILE are conflicted each other. So COPY_DEST_ prefix instead of
COPY_ prefix is used for CopyDest enum values and COPY_SOURCE_ prefix
instead of COPY_PREFIX_ is used for CopyFrom enum values. For example,
COPY_FILE in CopyDest is renamed to COPY_DEST_FILE and COPY_FILE in
CopyFrom is renamed to COPY_SOURCE_FILE.

Note that this isn't enough to implement custom COPY TO/FROM format
handlers as extension. We'll do the followings in a subsequent commit:

For custom COPY TO format handler:

1. Add an opaque space for custom COPY TO format handler
2. Export CopySendEndOfRow() to flush buffer

For custom COPY FROM format handler:

1. Add an opaque space for custom COPY FROM format handler
2. Export CopyReadBinaryData() to read the next data
---
 src/backend/commands/copyfrom.c          |   4 +-
 src/backend/commands/copyfromparse.c     |  10 +-
 src/backend/commands/copyto.c            |  77 +-----
 src/include/commands/copy.h              |  78 +-----
 src/include/commands/copyapi.h           | 306 ++++++++++++++++++++++-
 src/include/commands/copyfrom_internal.h | 165 ------------
 6 files changed, 320 insertions(+), 320 deletions(-)

diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 2b48c825a0a..5902172b8df 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1699,7 +1699,7 @@ BeginCopyFrom(ParseState *pstate,
 							pg_encoding_to_char(GetDatabaseEncoding()))));
 	}
 
-	cstate->copy_src = COPY_FILE;	/* default */
+	cstate->copy_src = COPY_SOURCE_FILE;	/* default */
 
 	cstate->whereClause = whereClause;
 
@@ -1827,7 +1827,7 @@ BeginCopyFrom(ParseState *pstate,
 	if (data_source_cb)
 	{
 		progress_vals[1] = PROGRESS_COPY_TYPE_CALLBACK;
-		cstate->copy_src = COPY_CALLBACK;
+		cstate->copy_src = COPY_SOURCE_CALLBACK;
 		cstate->data_source_cb = data_source_cb;
 	}
 	else if (pipe)
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 90824b47785..74844103228 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -180,7 +180,7 @@ ReceiveCopyBegin(CopyFromState cstate)
 	for (i = 0; i < natts; i++)
 		pq_sendint16(&buf, format); /* per-column formats */
 	pq_endmessage(&buf);
-	cstate->copy_src = COPY_FRONTEND;
+	cstate->copy_src = COPY_SOURCE_FRONTEND;
 	cstate->fe_msgbuf = makeStringInfo();
 	/* We *must* flush here to ensure FE knows it can send. */
 	pq_flush();
@@ -248,7 +248,7 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 
 	switch (cstate->copy_src)
 	{
-		case COPY_FILE:
+		case COPY_SOURCE_FILE:
 			bytesread = fread(databuf, 1, maxread, cstate->copy_file);
 			if (ferror(cstate->copy_file))
 				ereport(ERROR,
@@ -257,7 +257,7 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 			if (bytesread == 0)
 				cstate->raw_reached_eof = true;
 			break;
-		case COPY_FRONTEND:
+		case COPY_SOURCE_FRONTEND:
 			while (maxread > 0 && bytesread < minread && !cstate->raw_reached_eof)
 			{
 				int			avail;
@@ -340,7 +340,7 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 				bytesread += avail;
 			}
 			break;
-		case COPY_CALLBACK:
+		case COPY_SOURCE_CALLBACK:
 			bytesread = cstate->data_source_cb(databuf, minread, maxread);
 			break;
 	}
@@ -1188,7 +1188,7 @@ CopyReadLine(CopyFromState cstate, bool is_csv)
 		 * after \. up to the protocol end of copy data.  (XXX maybe better
 		 * not to treat \. as special?)
 		 */
-		if (cstate->copy_src == COPY_FRONTEND)
+		if (cstate->copy_src == COPY_SOURCE_FRONTEND)
 		{
 			int			inbytes;
 
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index a9e923467dc..54aa6cdecaf 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -37,67 +37,6 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 
-/*
- * Represents the different dest cases we need to worry about at
- * the bottom level
- */
-typedef enum CopyDest
-{
-	COPY_FILE,					/* to file (or a piped program) */
-	COPY_FRONTEND,				/* to frontend */
-	COPY_CALLBACK,				/* to callback function */
-} CopyDest;
-
-/*
- * This struct contains all the state variables used throughout a COPY TO
- * operation.
- *
- * Multi-byte encodings: all supported client-side encodings encode multi-byte
- * characters by having the first byte's high bit set. Subsequent bytes of the
- * character can have the high bit not set. When scanning data in such an
- * encoding to look for a match to a single-byte (ie ASCII) character, we must
- * use the full pg_encoding_mblen() machinery to skip over multibyte
- * characters, else we might find a false match to a trailing byte. In
- * supported server encodings, there is no possibility of a false match, and
- * it's faster to make useless comparisons to trailing bytes than it is to
- * invoke pg_encoding_mblen() to skip over them. encoding_embeds_ascii is true
- * when we have to do it the hard way.
- */
-typedef struct CopyToStateData
-{
-	/* format routine */
-	const CopyToRoutine *routine;
-
-	/* low-level state data */
-	CopyDest	copy_dest;		/* type of copy source/destination */
-	FILE	   *copy_file;		/* used if copy_dest == COPY_FILE */
-	StringInfo	fe_msgbuf;		/* used for all dests during COPY TO */
-
-	int			file_encoding;	/* file or remote side's character encoding */
-	bool		need_transcoding;	/* file encoding diff from server? */
-	bool		encoding_embeds_ascii;	/* ASCII can be non-first byte? */
-
-	/* parameters from the COPY command */
-	Relation	rel;			/* relation to copy to */
-	QueryDesc  *queryDesc;		/* executable query to copy from */
-	List	   *attnumlist;		/* integer list of attnums to copy */
-	char	   *filename;		/* filename, or NULL for STDOUT */
-	bool		is_program;		/* is 'filename' a program to popen? */
-	copy_data_dest_cb data_dest_cb; /* function for writing data */
-
-	CopyFormatOptions opts;
-	Node	   *whereClause;	/* WHERE condition (or NULL) */
-
-	/*
-	 * Working state
-	 */
-	MemoryContext copycontext;	/* per-copy execution context */
-
-	FmgrInfo   *out_functions;	/* lookup info for output functions */
-	MemoryContext rowcontext;	/* per-row evaluation context */
-	uint64		bytes_processed;	/* number of bytes processed so far */
-} CopyToStateData;
-
 /* DestReceiver for COPY (query) TO */
 typedef struct
 {
@@ -143,7 +82,7 @@ CopyToTextLikeSendEndOfRow(CopyToState cstate)
 {
 	switch (cstate->copy_dest)
 	{
-		case COPY_FILE:
+		case COPY_DEST_FILE:
 			/* Default line termination depends on platform */
 #ifndef WIN32
 			CopySendChar(cstate, '\n');
@@ -151,7 +90,7 @@ CopyToTextLikeSendEndOfRow(CopyToState cstate)
 			CopySendString(cstate, "\r\n");
 #endif
 			break;
-		case COPY_FRONTEND:
+		case COPY_DEST_FRONTEND:
 			/* The FE/BE protocol uses \n as newline for all platforms */
 			CopySendChar(cstate, '\n');
 			break;
@@ -464,7 +403,7 @@ SendCopyBegin(CopyToState cstate)
 	for (i = 0; i < natts; i++)
 		pq_sendint16(&buf, format); /* per-column formats */
 	pq_endmessage(&buf);
-	cstate->copy_dest = COPY_FRONTEND;
+	cstate->copy_dest = COPY_DEST_FRONTEND;
 }
 
 static void
@@ -511,7 +450,7 @@ CopySendEndOfRow(CopyToState cstate)
 
 	switch (cstate->copy_dest)
 	{
-		case COPY_FILE:
+		case COPY_DEST_FILE:
 			if (fwrite(fe_msgbuf->data, fe_msgbuf->len, 1,
 					   cstate->copy_file) != 1 ||
 				ferror(cstate->copy_file))
@@ -545,11 +484,11 @@ CopySendEndOfRow(CopyToState cstate)
 							 errmsg("could not write to COPY file: %m")));
 			}
 			break;
-		case COPY_FRONTEND:
+		case COPY_DEST_FRONTEND:
 			/* Dump the accumulated row as one CopyData message */
 			(void) pq_putmessage(PqMsg_CopyData, fe_msgbuf->data, fe_msgbuf->len);
 			break;
-		case COPY_CALLBACK:
+		case COPY_DEST_CALLBACK:
 			cstate->data_dest_cb(fe_msgbuf->data, fe_msgbuf->len);
 			break;
 	}
@@ -928,12 +867,12 @@ BeginCopyTo(ParseState *pstate,
 	/* See Multibyte encoding comment above */
 	cstate->encoding_embeds_ascii = PG_ENCODING_IS_CLIENT_ONLY(cstate->file_encoding);
 
-	cstate->copy_dest = COPY_FILE;	/* default */
+	cstate->copy_dest = COPY_DEST_FILE; /* default */
 
 	if (data_dest_cb)
 	{
 		progress_vals[1] = PROGRESS_COPY_TYPE_CALLBACK;
-		cstate->copy_dest = COPY_CALLBACK;
+		cstate->copy_dest = COPY_DEST_CALLBACK;
 		cstate->data_dest_cb = data_dest_cb;
 	}
 	else if (pipe)
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 79bd4fb9151..e2411848e9f 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -14,87 +14,11 @@
 #ifndef COPY_H
 #define COPY_H
 
-#include "nodes/execnodes.h"
+#include "commands/copyapi.h"
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "tcop/dest.h"
 
-/*
- * Represents whether a header line should be present, and whether it must
- * match the actual names (which implies "true").
- */
-typedef enum CopyHeaderChoice
-{
-	COPY_HEADER_FALSE = 0,
-	COPY_HEADER_TRUE,
-	COPY_HEADER_MATCH,
-} CopyHeaderChoice;
-
-/*
- * Represents where to save input processing errors.  More values to be added
- * in the future.
- */
-typedef enum CopyOnErrorChoice
-{
-	COPY_ON_ERROR_STOP = 0,		/* immediately throw errors, default */
-	COPY_ON_ERROR_IGNORE,		/* ignore errors */
-} CopyOnErrorChoice;
-
-/*
- * Represents verbosity of logged messages by COPY command.
- */
-typedef enum CopyLogVerbosityChoice
-{
-	COPY_LOG_VERBOSITY_DEFAULT = 0, /* logs no additional messages, default */
-	COPY_LOG_VERBOSITY_VERBOSE, /* logs additional messages */
-} CopyLogVerbosityChoice;
-
-/*
- * A struct to hold COPY options, in a parsed form. All of these are related
- * to formatting, except for 'freeze', which doesn't really belong here, but
- * it's expedient to parse it along with all the other options.
- */
-typedef struct CopyFormatOptions
-{
-	/* parameters from the COPY command */
-	int			file_encoding;	/* file or remote side's character encoding,
-								 * -1 if not specified */
-	bool		binary;			/* binary format? */
-	bool		freeze;			/* freeze rows on loading? */
-	bool		csv_mode;		/* Comma Separated Value format? */
-	CopyHeaderChoice header_line;	/* header line? */
-	char	   *null_print;		/* NULL marker string (server encoding!) */
-	int			null_print_len; /* length of same */
-	char	   *null_print_client;	/* same converted to file encoding */
-	char	   *default_print;	/* DEFAULT marker string */
-	int			default_print_len;	/* length of same */
-	char	   *delim;			/* column delimiter (must be 1 byte) */
-	char	   *quote;			/* CSV quote char (must be 1 byte) */
-	char	   *escape;			/* CSV escape char (must be 1 byte) */
-	List	   *force_quote;	/* list of column names */
-	bool		force_quote_all;	/* FORCE_QUOTE *? */
-	bool	   *force_quote_flags;	/* per-column CSV FQ flags */
-	List	   *force_notnull;	/* list of column names */
-	bool		force_notnull_all;	/* FORCE_NOT_NULL *? */
-	bool	   *force_notnull_flags;	/* per-column CSV FNN flags */
-	List	   *force_null;		/* list of column names */
-	bool		force_null_all; /* FORCE_NULL *? */
-	bool	   *force_null_flags;	/* per-column CSV FN flags */
-	bool		convert_selectively;	/* do selective binary conversion? */
-	CopyOnErrorChoice on_error; /* what to do when error happened */
-	CopyLogVerbosityChoice log_verbosity;	/* verbosity of logged messages */
-	List	   *convert_select; /* list of column names (can be NIL) */
-	Node	   *routine;		/* CopyToRoutine or CopyFromRoutine (can be
-								 * NULL) */
-} CopyFormatOptions;
-
-/* These are private in commands/copy[from|to].c */
-typedef struct CopyFromStateData *CopyFromState;
-typedef struct CopyToStateData *CopyToState;
-
-typedef int (*copy_data_source_cb) (void *outbuf, int minread, int maxread);
-typedef void (*copy_data_dest_cb) (void *data, int len);
-
 extern void DoCopy(ParseState *pstate, const CopyStmt *stmt,
 				   int stmt_location, int stmt_len,
 				   uint64 *processed);
diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h
index e049a45a4b1..e298b19860c 100644
--- a/src/include/commands/copyapi.h
+++ b/src/include/commands/copyapi.h
@@ -14,12 +14,83 @@
 #ifndef COPYAPI_H
 #define COPYAPI_H
 
+#include "commands/trigger.h"
+#include "executor/execdesc.h"
 #include "executor/tuptable.h"
 #include "nodes/execnodes.h"
 
-/* These are private in commands/copy[from|to].c */
+/*
+ * Represents whether a header line should be present, and whether it must
+ * match the actual names (which implies "true").
+ */
+typedef enum CopyHeaderChoice
+{
+	COPY_HEADER_FALSE = 0,
+	COPY_HEADER_TRUE,
+	COPY_HEADER_MATCH,
+} CopyHeaderChoice;
+
+/*
+ * Represents where to save input processing errors.  More values to be added
+ * in the future.
+ */
+typedef enum CopyOnErrorChoice
+{
+	COPY_ON_ERROR_STOP = 0,		/* immediately throw errors, default */
+	COPY_ON_ERROR_IGNORE,		/* ignore errors */
+} CopyOnErrorChoice;
+
+/*
+ * Represents verbosity of logged messages by COPY command.
+ */
+typedef enum CopyLogVerbosityChoice
+{
+	COPY_LOG_VERBOSITY_DEFAULT = 0, /* logs no additional messages, default */
+	COPY_LOG_VERBOSITY_VERBOSE, /* logs additional messages */
+} CopyLogVerbosityChoice;
+
+/*
+ * A struct to hold COPY options, in a parsed form. All of these are related
+ * to formatting, except for 'freeze', which doesn't really belong here, but
+ * it's expedient to parse it along with all the other options.
+ */
+typedef struct CopyFormatOptions
+{
+	/* parameters from the COPY command */
+	int			file_encoding;	/* file or remote side's character encoding,
+								 * -1 if not specified */
+	bool		binary;			/* binary format? */
+	bool		freeze;			/* freeze rows on loading? */
+	bool		csv_mode;		/* Comma Separated Value format? */
+	CopyHeaderChoice header_line;	/* header line? */
+	char	   *null_print;		/* NULL marker string (server encoding!) */
+	int			null_print_len; /* length of same */
+	char	   *null_print_client;	/* same converted to file encoding */
+	char	   *default_print;	/* DEFAULT marker string */
+	int			default_print_len;	/* length of same */
+	char	   *delim;			/* column delimiter (must be 1 byte) */
+	char	   *quote;			/* CSV quote char (must be 1 byte) */
+	char	   *escape;			/* CSV escape char (must be 1 byte) */
+	List	   *force_quote;	/* list of column names */
+	bool		force_quote_all;	/* FORCE_QUOTE *? */
+	bool	   *force_quote_flags;	/* per-column CSV FQ flags */
+	List	   *force_notnull;	/* list of column names */
+	bool		force_notnull_all;	/* FORCE_NOT_NULL *? */
+	bool	   *force_notnull_flags;	/* per-column CSV FNN flags */
+	List	   *force_null;		/* list of column names */
+	bool		force_null_all; /* FORCE_NULL *? */
+	bool	   *force_null_flags;	/* per-column CSV FN flags */
+	bool		convert_selectively;	/* do selective binary conversion? */
+	CopyOnErrorChoice on_error; /* what to do when error happened */
+	CopyLogVerbosityChoice log_verbosity;	/* verbosity of logged messages */
+	List	   *convert_select; /* list of column names (can be NIL) */
+	Node	   *routine;		/* CopyToRoutine or CopyFromRoutine (can be
+								 * NULL) */
+} CopyFormatOptions;
+
+typedef int (*copy_data_source_cb) (void *outbuf, int minread, int maxread);
+
 typedef struct CopyFromStateData *CopyFromState;
-typedef struct CopyToStateData *CopyToState;
 
 /*
  * API structure for a COPY FROM format implementation.  Note this must be
@@ -65,6 +136,174 @@ typedef struct CopyFromRoutine
 	void		(*CopyFromEnd) (CopyFromState cstate);
 } CopyFromRoutine;
 
+/*
+ * Represents the different source cases we need to worry about at
+ * the bottom level
+ */
+typedef enum CopySource
+{
+	COPY_SOURCE_FILE,			/* from file (or a piped program) */
+	COPY_SOURCE_FRONTEND,		/* from frontend */
+	COPY_SOURCE_CALLBACK,		/* from callback function */
+} CopySource;
+
+/*
+ * Represents the end-of-line terminator type of the input
+ */
+typedef enum EolType
+{
+	EOL_UNKNOWN,
+	EOL_NL,
+	EOL_CR,
+	EOL_CRNL,
+} EolType;
+
+/*
+ * Represents the insert method to be used during COPY FROM.
+ */
+typedef enum CopyInsertMethod
+{
+	CIM_SINGLE,					/* use table_tuple_insert or ExecForeignInsert */
+	CIM_MULTI,					/* always use table_multi_insert or
+								 * ExecForeignBatchInsert */
+	CIM_MULTI_CONDITIONAL,		/* use table_multi_insert or
+								 * ExecForeignBatchInsert only if valid */
+} CopyInsertMethod;
+
+/*
+ * This struct contains all the state variables used throughout a COPY FROM
+ * operation.
+ */
+typedef struct CopyFromStateData
+{
+	/* format routine */
+	const CopyFromRoutine *routine;
+
+	/* low-level state data */
+	CopySource	copy_src;		/* type of copy source */
+	FILE	   *copy_file;		/* used if copy_src == COPY_FILE */
+	StringInfo	fe_msgbuf;		/* used if copy_src == COPY_FRONTEND */
+
+	EolType		eol_type;		/* EOL type of input */
+	int			file_encoding;	/* file or remote side's character encoding */
+	bool		need_transcoding;	/* file encoding diff from server? */
+	Oid			conversion_proc;	/* encoding conversion function */
+
+	/* parameters from the COPY command */
+	Relation	rel;			/* relation to copy from */
+	List	   *attnumlist;		/* integer list of attnums to copy */
+	char	   *filename;		/* filename, or NULL for STDIN */
+	bool		is_program;		/* is 'filename' a program to popen? */
+	copy_data_source_cb data_source_cb; /* function for reading data */
+
+	CopyFormatOptions opts;
+	bool	   *convert_select_flags;	/* per-column CSV/TEXT CS flags */
+	Node	   *whereClause;	/* WHERE condition (or NULL) */
+
+	/* these are just for error messages, see CopyFromErrorCallback */
+	const char *cur_relname;	/* table name for error messages */
+	uint64		cur_lineno;		/* line number for error messages */
+	const char *cur_attname;	/* current att for error messages */
+	const char *cur_attval;		/* current att value for error messages */
+	bool		relname_only;	/* don't output line number, att, etc. */
+
+	/*
+	 * Working state
+	 */
+	MemoryContext copycontext;	/* per-copy execution context */
+
+	AttrNumber	num_defaults;	/* count of att that are missing and have
+								 * default value */
+	FmgrInfo   *in_functions;	/* array of input functions for each attrs */
+	Oid		   *typioparams;	/* array of element types for in_functions */
+	ErrorSaveContext *escontext;	/* soft error trapper during in_functions
+									 * execution */
+	uint64		num_errors;		/* total number of rows which contained soft
+								 * errors */
+	int		   *defmap;			/* array of default att numbers related to
+								 * missing att */
+	ExprState **defexprs;		/* array of default att expressions for all
+								 * att */
+	bool	   *defaults;		/* if DEFAULT marker was found for
+								 * corresponding att */
+	bool		volatile_defexprs;	/* is any of defexprs volatile? */
+	List	   *range_table;	/* single element list of RangeTblEntry */
+	List	   *rteperminfos;	/* single element list of RTEPermissionInfo */
+	ExprState  *qualexpr;
+
+	TransitionCaptureState *transition_capture;
+
+	/*
+	 * These variables are used to reduce overhead in COPY FROM.
+	 *
+	 * attribute_buf holds the separated, de-escaped text for each field of
+	 * the current line.  The CopyReadAttributes functions return arrays of
+	 * pointers into this buffer.  We avoid palloc/pfree overhead by re-using
+	 * the buffer on each cycle.
+	 *
+	 * In binary COPY FROM, attribute_buf holds the binary data for the
+	 * current field, but the usage is otherwise similar.
+	 */
+	StringInfoData attribute_buf;
+
+	/* field raw data pointers found by COPY FROM */
+
+	int			max_fields;
+	char	  **raw_fields;
+
+	/*
+	 * Similarly, line_buf holds the whole input line being processed. The
+	 * input cycle is first to read the whole line into line_buf, and then
+	 * extract the individual attribute fields into attribute_buf.  line_buf
+	 * is preserved unmodified so that we can display it in error messages if
+	 * appropriate.  (In binary mode, line_buf is not used.)
+	 */
+	StringInfoData line_buf;
+	bool		line_buf_valid; /* contains the row being processed? */
+
+	/*
+	 * input_buf holds input data, already converted to database encoding.
+	 *
+	 * In text mode, CopyReadLine parses this data sufficiently to locate line
+	 * boundaries, then transfers the data to line_buf. We guarantee that
+	 * there is a \0 at input_buf[input_buf_len] at all times.  (In binary
+	 * mode, input_buf is not used.)
+	 *
+	 * If encoding conversion is not required, input_buf is not a separate
+	 * buffer but points directly to raw_buf.  In that case, input_buf_len
+	 * tracks the number of bytes that have been verified as valid in the
+	 * database encoding, and raw_buf_len is the total number of bytes stored
+	 * in the buffer.
+	 */
+#define INPUT_BUF_SIZE 65536	/* we palloc INPUT_BUF_SIZE+1 bytes */
+	char	   *input_buf;
+	int			input_buf_index;	/* next byte to process */
+	int			input_buf_len;	/* total # of bytes stored */
+	bool		input_reached_eof;	/* true if we reached EOF */
+	bool		input_reached_error;	/* true if a conversion error happened */
+	/* Shorthand for number of unconsumed bytes available in input_buf */
+#define INPUT_BUF_BYTES(cstate) ((cstate)->input_buf_len - (cstate)->input_buf_index)
+
+	/*
+	 * raw_buf holds raw input data read from the data source (file or client
+	 * connection), not yet converted to the database encoding.  Like with
+	 * 'input_buf', we guarantee that there is a \0 at raw_buf[raw_buf_len].
+	 */
+#define RAW_BUF_SIZE 65536		/* we palloc RAW_BUF_SIZE+1 bytes */
+	char	   *raw_buf;
+	int			raw_buf_index;	/* next byte to process */
+	int			raw_buf_len;	/* total # of bytes stored */
+	bool		raw_reached_eof;	/* true if we reached EOF */
+
+	/* Shorthand for number of unconsumed bytes available in raw_buf */
+#define RAW_BUF_BYTES(cstate) ((cstate)->raw_buf_len - (cstate)->raw_buf_index)
+
+	uint64		bytes_processed;	/* number of bytes processed so far */
+} CopyFromStateData;
+
+
+typedef struct CopyToStateData *CopyToState;
+
 /*
  * API structure for a COPY TO format implementation.   Note this must be
  * allocated in a server-lifetime manner, typically as a static const struct.
@@ -102,4 +341,67 @@ typedef struct CopyToRoutine
 	void		(*CopyToEnd) (CopyToState cstate);
 } CopyToRoutine;
 
+/*
+ * Represents the different dest cases we need to worry about at
+ * the bottom level
+ */
+typedef enum CopyDest
+{
+	COPY_DEST_FILE,				/* to file (or a piped program) */
+	COPY_DEST_FRONTEND,			/* to frontend */
+	COPY_DEST_CALLBACK,			/* to callback function */
+} CopyDest;
+
+typedef void (*copy_data_dest_cb) (void *data, int len);
+
+/*
+ * This struct contains all the state variables used throughout a COPY TO
+ * operation.
+ *
+ * Multi-byte encodings: all supported client-side encodings encode multi-byte
+ * characters by having the first byte's high bit set. Subsequent bytes of the
+ * character can have the high bit not set. When scanning data in such an
+ * encoding to look for a match to a single-byte (ie ASCII) character, we must
+ * use the full pg_encoding_mblen() machinery to skip over multibyte
+ * characters, else we might find a false match to a trailing byte. In
+ * supported server encodings, there is no possibility of a false match, and
+ * it's faster to make useless comparisons to trailing bytes than it is to
+ * invoke pg_encoding_mblen() to skip over them. encoding_embeds_ascii is true
+ * when we have to do it the hard way.
+ */
+typedef struct CopyToStateData
+{
+	/* format routine */
+	const CopyToRoutine *routine;
+
+	/* low-level state data */
+	CopyDest	copy_dest;		/* type of copy source/destination */
+	FILE	   *copy_file;		/* used if copy_dest == COPY_FILE */
+	StringInfo	fe_msgbuf;		/* used for all dests during COPY TO */
+
+	int			file_encoding;	/* file or remote side's character encoding */
+	bool		need_transcoding;	/* file encoding diff from server? */
+	bool		encoding_embeds_ascii;	/* ASCII can be non-first byte? */
+
+	/* parameters from the COPY command */
+	Relation	rel;			/* relation to copy to */
+	QueryDesc  *queryDesc;		/* executable query to copy from */
+	List	   *attnumlist;		/* integer list of attnums to copy */
+	char	   *filename;		/* filename, or NULL for STDOUT */
+	bool		is_program;		/* is 'filename' a program to popen? */
+	copy_data_dest_cb data_dest_cb; /* function for writing data */
+
+	CopyFormatOptions opts;
+	Node	   *whereClause;	/* WHERE condition (or NULL) */
+
+	/*
+	 * Working state
+	 */
+	MemoryContext copycontext;	/* per-copy execution context */
+
+	FmgrInfo   *out_functions;	/* lookup info for output functions */
+	MemoryContext rowcontext;	/* per-row evaluation context */
+	uint64		bytes_processed;	/* number of bytes processed so far */
+} CopyToStateData;
+
 #endif							/* COPYAPI_H */
diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h
index c11b5ff3cc0..3863d26d5b7 100644
--- a/src/include/commands/copyfrom_internal.h
+++ b/src/include/commands/copyfrom_internal.h
@@ -19,171 +19,6 @@
 #include "commands/trigger.h"
 #include "nodes/miscnodes.h"
 
-/*
- * Represents the different source cases we need to worry about at
- * the bottom level
- */
-typedef enum CopySource
-{
-	COPY_FILE,					/* from file (or a piped program) */
-	COPY_FRONTEND,				/* from frontend */
-	COPY_CALLBACK,				/* from callback function */
-} CopySource;
-
-/*
- *	Represents the end-of-line terminator type of the input
- */
-typedef enum EolType
-{
-	EOL_UNKNOWN,
-	EOL_NL,
-	EOL_CR,
-	EOL_CRNL,
-} EolType;
-
-/*
- * Represents the insert method to be used during COPY FROM.
- */
-typedef enum CopyInsertMethod
-{
-	CIM_SINGLE,					/* use table_tuple_insert or ExecForeignInsert */
-	CIM_MULTI,					/* always use table_multi_insert or
-								 * ExecForeignBatchInsert */
-	CIM_MULTI_CONDITIONAL,		/* use table_multi_insert or
-								 * ExecForeignBatchInsert only if valid */
-} CopyInsertMethod;
-
-/*
- * This struct contains all the state variables used throughout a COPY FROM
- * operation.
- */
-typedef struct CopyFromStateData
-{
-	/* format routine */
-	const CopyFromRoutine *routine;
-
-	/* low-level state data */
-	CopySource	copy_src;		/* type of copy source */
-	FILE	   *copy_file;		/* used if copy_src == COPY_FILE */
-	StringInfo	fe_msgbuf;		/* used if copy_src == COPY_FRONTEND */
-
-	EolType		eol_type;		/* EOL type of input */
-	int			file_encoding;	/* file or remote side's character encoding */
-	bool		need_transcoding;	/* file encoding diff from server? */
-	Oid			conversion_proc;	/* encoding conversion function */
-
-	/* parameters from the COPY command */
-	Relation	rel;			/* relation to copy from */
-	List	   *attnumlist;		/* integer list of attnums to copy */
-	char	   *filename;		/* filename, or NULL for STDIN */
-	bool		is_program;		/* is 'filename' a program to popen? */
-	copy_data_source_cb data_source_cb; /* function for reading data */
-
-	CopyFormatOptions opts;
-	bool	   *convert_select_flags;	/* per-column CSV/TEXT CS flags */
-	Node	   *whereClause;	/* WHERE condition (or NULL) */
-
-	/* these are just for error messages, see CopyFromErrorCallback */
-	const char *cur_relname;	/* table name for error messages */
-	uint64		cur_lineno;		/* line number for error messages */
-	const char *cur_attname;	/* current att for error messages */
-	const char *cur_attval;		/* current att value for error messages */
-	bool		relname_only;	/* don't output line number, att, etc. */
-
-	/*
-	 * Working state
-	 */
-	MemoryContext copycontext;	/* per-copy execution context */
-
-	AttrNumber	num_defaults;	/* count of att that are missing and have
-								 * default value */
-	FmgrInfo   *in_functions;	/* array of input functions for each attrs */
-	Oid		   *typioparams;	/* array of element types for in_functions */
-	ErrorSaveContext *escontext;	/* soft error trapper during in_functions
-									 * execution */
-	uint64		num_errors;		/* total number of rows which contained soft
-								 * errors */
-	int		   *defmap;			/* array of default att numbers related to
-								 * missing att */
-	ExprState **defexprs;		/* array of default att expressions for all
-								 * att */
-	bool	   *defaults;		/* if DEFAULT marker was found for
-								 * corresponding att */
-	bool		volatile_defexprs;	/* is any of defexprs volatile? */
-	List	   *range_table;	/* single element list of RangeTblEntry */
-	List	   *rteperminfos;	/* single element list of RTEPermissionInfo */
-	ExprState  *qualexpr;
-
-	TransitionCaptureState *transition_capture;
-
-	/*
-	 * These variables are used to reduce overhead in COPY FROM.
-	 *
-	 * attribute_buf holds the separated, de-escaped text for each field of
-	 * the current line.  The CopyReadAttributes functions return arrays of
-	 * pointers into this buffer.  We avoid palloc/pfree overhead by re-using
-	 * the buffer on each cycle.
-	 *
-	 * In binary COPY FROM, attribute_buf holds the binary data for the
-	 * current field, but the usage is otherwise similar.
-	 */
-	StringInfoData attribute_buf;
-
-	/* field raw data pointers found by COPY FROM */
-
-	int			max_fields;
-	char	  **raw_fields;
-
-	/*
-	 * Similarly, line_buf holds the whole input line being processed. The
-	 * input cycle is first to read the whole line into line_buf, and then
-	 * extract the individual attribute fields into attribute_buf.  line_buf
-	 * is preserved unmodified so that we can display it in error messages if
-	 * appropriate.  (In binary mode, line_buf is not used.)
-	 */
-	StringInfoData line_buf;
-	bool		line_buf_valid; /* contains the row being processed? */
-
-	/*
-	 * input_buf holds input data, already converted to database encoding.
-	 *
-	 * In text mode, CopyReadLine parses this data sufficiently to locate line
-	 * boundaries, then transfers the data to line_buf. We guarantee that
-	 * there is a \0 at input_buf[input_buf_len] at all times.  (In binary
-	 * mode, input_buf is not used.)
-	 *
-	 * If encoding conversion is not required, input_buf is not a separate
-	 * buffer but points directly to raw_buf.  In that case, input_buf_len
-	 * tracks the number of bytes that have been verified as valid in the
-	 * database encoding, and raw_buf_len is the total number of bytes stored
-	 * in the buffer.
-	 */
-#define INPUT_BUF_SIZE 65536	/* we palloc INPUT_BUF_SIZE+1 bytes */
-	char	   *input_buf;
-	int			input_buf_index;	/* next byte to process */
-	int			input_buf_len;	/* total # of bytes stored */
-	bool		input_reached_eof;	/* true if we reached EOF */
-	bool		input_reached_error;	/* true if a conversion error happened */
-	/* Shorthand for number of unconsumed bytes available in input_buf */
-#define INPUT_BUF_BYTES(cstate) ((cstate)->input_buf_len - (cstate)->input_buf_index)
-
-	/*
-	 * raw_buf holds raw input data read from the data source (file or client
-	 * connection), not yet converted to the database encoding.  Like with
-	 * 'input_buf', we guarantee that there is a \0 at raw_buf[raw_buf_len].
-	 */
-#define RAW_BUF_SIZE 65536		/* we palloc RAW_BUF_SIZE+1 bytes */
-	char	   *raw_buf;
-	int			raw_buf_index;	/* next byte to process */
-	int			raw_buf_len;	/* total # of bytes stored */
-	bool		raw_reached_eof;	/* true if we reached EOF */
-
-	/* Shorthand for number of unconsumed bytes available in raw_buf */
-#define RAW_BUF_BYTES(cstate) ((cstate)->raw_buf_len - (cstate)->raw_buf_index)
-
-	uint64		bytes_processed;	/* number of bytes processed so far */
-} CopyFromStateData;
-
 extern void ReceiveCopyBegin(CopyFromState cstate);
 extern void ReceiveCopyBinaryHeader(CopyFromState cstate);
 
-- 
2.45.2

>From 0cb173c99599c0669891642e6f1adb414769ae8c Mon Sep 17 00:00:00 2001
From: Sutou Kouhei <[email-masked]>
Date: Tue, 23 Jan 2024 15:12:43 +0900
Subject: [PATCH v19 5/5] Add support for implementing custom COPY TO/FROM
 format as extension

For custom COPY TO format implementation:

* Add CopyToStateData::opaque that can be used to keep data for custom
  COPY TO format implementation
* Export CopySendEndOfRow() to flush data in CopyToStateData::fe_msgbuf
  as CopyToStateFlush()

For custom COPY FROM format implementation:

* Add CopyFromStateData::opaque that can be used to keep data for
  custom COPY From format implementation
* Export CopyReadBinaryData() to read the next data as
  CopyFromStateRead()
---
 src/backend/commands/copyfromparse.c | 14 ++++++++++++++
 src/backend/commands/copyto.c        | 14 ++++++++++++++
 src/include/commands/copyapi.h       | 10 ++++++++++
 3 files changed, 38 insertions(+)

diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 74844103228..a115d7f9e26 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -739,6 +739,20 @@ CopyReadBinaryData(CopyFromState cstate, char *dest, int nbytes)
 	return copied_bytes;
 }
 
+/*
+ * CopyFromStateRead
+ *
+ * Export CopyReadBinaryData() for extensions. We want to keep
+ * CopyReadBinaryData() as a static function for
+ * optimization. CopyReadBinaryData() calls in this file may be optimized by
+ * a compiler.
+ */
+int
+CopyFromStateRead(CopyFromState cstate, char *dest, int nbytes)
+{
+	return CopyReadBinaryData(cstate, dest, nbytes);
+}
+
 /*
  * Read raw fields in the next line for COPY FROM in text or csv mode.
  * Return false if no more lines.
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 54aa6cdecaf..b8d0e996117 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -500,6 +500,20 @@ CopySendEndOfRow(CopyToState cstate)
 	resetStringInfo(fe_msgbuf);
 }
 
+/*
+ * CopyToStateFlush
+ *
+ * Export CopySendEndOfRow() for extensions. We want to keep
+ * CopySendEndOfRow() as a static function for
+ * optimization. CopySendEndOfRow() calls in this file may be optimized by a
+ * compiler.
+ */
+void
+CopyToStateFlush(CopyToState cstate)
+{
+	CopySendEndOfRow(cstate);
+}
+
 /*
  * These functions do apply some data conversion
  */
diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h
index e298b19860c..5665408eaa0 100644
--- a/src/include/commands/copyapi.h
+++ b/src/include/commands/copyapi.h
@@ -299,8 +299,13 @@ typedef struct CopyFromStateData
 #define RAW_BUF_BYTES(cstate) ((cstate)->raw_buf_len - (cstate)->raw_buf_index)
 
 	uint64		bytes_processed;	/* number of bytes processed so far */
+
+	/* For custom format implementation */
+	void	   *opaque;			/* private space */
 } CopyFromStateData;
 
+extern int	CopyFromStateRead(CopyFromState cstate, char *dest, int nbytes);
+
 
 typedef struct CopyToStateData *CopyToState;
 
@@ -402,6 +407,11 @@ typedef struct CopyToStateData
 	FmgrInfo   *out_functions;	/* lookup info for output functions */
 	MemoryContext rowcontext;	/* per-row evaluation context */
 	uint64		bytes_processed;	/* number of bytes processed so far */
+
+	/* For custom format implementation */
+	void	   *opaque;			/* private space */
 } CopyToStateData;
 
+extern void CopyToStateFlush(CopyToState cstate);
+
 #endif							/* COPYAPI_H */
-- 
2.45.2

---

From: Tomas Vondra <[email-masked]>
Date: Tue, 30 Jul 2024 11:51:37 +0200

> type	n_cols	n_rows	diff	master		patched

I'm on Fedora 40 with gcc 14.1, on Intel i7-9750H. But it's running on
Qubes OS, so it's really in a VM which makes it noisier. I'll try to do
more benchmarks on a regular hw, but that may take a couple days.

I decided to do the benchmark for individual parts of the patch series.
The attached PDF shows results for master (label 0000) and the 0001-0005
patches, along with relative performance difference between the patches.
The color scale is the same as before - red = bad, green = good.

There are pretty clear differences between the patches and "direction"
of the COPY. I'm sure it does depend on the hardware - I tried running
this on rpi5 (with 32-bits), and it looks very different. There might be
a similar behavior difference between Intel and Ryzen, but my point is
that when looking for regressions, looking at these "per patch" charts
can be very useful (as it reduces the scope of changes that might have
caused the regression).

> try the attached v19 patch? It changes the 0005 patch:

Perhaps, that's possible.

> * It adds a new non-static function that just exports

I'll try to benchmark this later, when the other machine is available.

regards

---

From: Sutou Kouhei <[email-masked]>
Date: Thu, 01 Aug 2024 19:54:12 +0900 (JST)

Hi,

In <[email-masked]>
  "Re: Make COPY format extendable: Extract COPY TO format implementations" on Tue, 30 Jul 2024 11:51:37 +0200,

> can be very useful (as it reduces the scope of changes that might have

Thanks.
The numbers on your environment shows that there are
performance problems in the following cases in the v18 patch
set:

1. 0001 + TO
2. 0005 + TO

There are +-~3% differences in FROM cases. They may be noise.
+~6% differences in TO cases may not be noise.

I also tried another benchmark with the v19 (not v18) patch
set with "Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz" not "AMD
Ryzen 9 3900X 12-Core Processor".

The attached PDF visualized my numbers like your PDF but red
= bad, green = good. -30 (blue) means 70% (faster) and 30
(red) means 130% (slower).

0001 + TO is a bit slower like your numbers. Other TO cases
are a bit faster.
0002 + FROM is very slower. Other FROM cases are slower with
less records but a bit faster with many records.

I'll re-run it with "AMD Ryzen 9 3900X 12-Core Processor".

FYI: I've created a repository to push benchmark scripts:
https://gitlab.com/ktou/pg-bench

Thanks,
-- 
kou

---

From: Sutou Kouhei <[email-masked]>
Date: Mon, 05 Aug 2024 07:20:12 +0900 (JST)

Hi,

I re-ran the benchmark(*) with the v19 patch set and the
following CPUs:

1. AMD Ryzen 9 3900X 12-Core Processor
2. Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz

(*)
* Use tables that have {5,10,15,20,25,30} integer columns
* Use tables that have {1,2,3,4,5,6,7,8,9,10}M rows
* Use '/dev/null' for COPY TO
* Use blackhole_am for COPY FROM

See the attached graphs for details.

Notes:
* X-axis is the number of columns
* Y-axis is the number of M rows
* Z-axis is the elapsed time percent (smaller is faster,
  e.g. 99% is a bit faster than the HEAD and 101% is a bit
  slower than the HEAD)
* Z-ranges aren't same (The Ryzen case uses about 79%-121%
  but the Intel case uses about 91%-111%)
* Red means the patch is slower than HEAD
* Blue means the patch is faster than HEAD
* The upper row shows FROM results
* The lower row shows TO results

Here are summaries based on the results:

For FROM:
* With Ryzen: It shows that negative performance impact
* With Intel: It shows that negative performance impact with
  1-5M rows and positive performance impact with 6M-10M rows
For TO:
* With Ryzen: It shows that positive performance impact
* With Intel: It shows that positive performance impact

Here are insights based on the results:

* 0001 (that introduces Copy{From,To}Routine} and adds some
  "if () {...}" for them but the existing formats still
  doesn't use them) has a bit negative performance impact
* 0002 (that migrates the existing codes to
  Copy{From,To}Routine} based implementations) has positive
  performance impact
  * For FROM: Negative impact by 0001 and positive impact by
    0002 almost balanced
    * We should use both of 0001 and 0002 than only 0001
    * With Ryzon: It's a bit slower than HEAD. So we may not
      want to reject this propose for FROM
    * With Intel:
      * With 1-5M rows: It's a bit slower than HEAD
      * With 6-10M rows: It's a bit faster than HEAD
  * For TO: Positive impact by 0002 is larger than negative
    impact by 0002
    * We should use both of 0001 and 0002 than only 0001
* 0003 (that makes Copy{From,To}Routine Node) has a bit
  negative performance impact
  * But I don't know why. This doesn't change per row
    related codes. Increasing Copy{From,To}Routine size
    (NodeTag is added) may be related.
* 0004 (that moves Copy{From,To}StateData to copyapi.h)
  doesn't have impact
  * It makes sense because this doesn't change any
    implementations.
* 0005 (that add "void *opaque" to Copy{From,To}StateData)
  has a bit negative impact for FROM and a bit positive
  impact for TO
  * But I don't know why. This doesn't change per row
    related codes. Increasing Copy{From,To}StateData size
    ("void *opaque" is added) may be related.

How to proceed this proposal?

* Do we need more numbers to judge this proposal?
  * If so, could someone help us?
* There is no negative performance impact for TO with both
  of Ryzen and Intel based on my results. Can we merge only
  the TO part?
  * Can we defer the FROM part? Should we proceed this
    proposal with both of the FROM and TO part?
* Could someone provide a hint why the FROM part is more
  slower with Ryzen?

(If nobody responds to this, this proposal will get stuck
again. If you're interested in this proposal, could you help
us?)

How to run this benchmark on your machine:

$ cd your-postgres
$ git switch -c copy-format-extendable
$ git am v19-*.patch
$ git clone https://gitlab.com/ktou/pg-bench.git ../pg-bench
$ ../pg-bench/bench.sh copy-format-extendable ../pg-bench/copy-format-extendable/run.sh
(This will take about 5 hours...)

If you want to visualize your results on your machine:

$ sudo gem install ruby-gr
$ ../pg-bench/visualize.rb 5

If you share your results to me, I can visualize it and
share.

Thanks,
-- 
kou

---

From: Masahiko Sawada <[email-masked]>
Date: Fri, 27 Sep 2024 16:33:13 -0700

Hi,

>     ("void *opaque" is added) may be related.

I was surprised that the 0005 patch made COPY FROM slower (with fewer
rows) and COPY TO faster overall in spite of just adding one struct
field and some functions.

I'm interested in why the performance trends of COPY FROM are
different between fewer than 6M rows and more than 6M rows.

> * Could someone provide a hint why the FROM part is more

Separating the patches into two parts (one is for COPY TO and another
one is for COPY FROM) could be a good idea. It would help reviews and
investigate performance regression in COPY FROM cases. And I think we
can commit them separately.

Also, could you please rebase the patches as they conflict with the
current HEAD? I'll run some benchmarks on my environment as well.

Regards,

--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com