SET datestyle = 'ISO';
CREATE SERVER binary_inserts_loopback FOREIGN DATA WRAPPER clickhouse_fdw OPTIONS(dbname 'binary_inserts_test', driver 'binary');
CREATE USER MAPPING FOR CURRENT_USER SERVER binary_inserts_loopback;

SELECT clickhouse_raw_query('drop database if exists binary_inserts_test');
SELECT clickhouse_raw_query('create database binary_inserts_test');
SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.ints (
    c1 Int8, c2 Int16, c3 Int32, c4 Int64
) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1);
');

SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.uints (
    c1 UInt8, c2 UInt16, c3 UInt32, c4 UInt64, c5 Bool
) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1);
');

SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.floats (
    c1 Float32, c2 Float64
) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1) SETTINGS allow_floating_point_partition_key=1;
');

SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.null_ints (
    c1 Int8, c2 Nullable(Int32)
) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1);
');

SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.complex (
    c1 Int32, c2 Date, c3 DateTime, c4 String, c5 FixedString(10), c6 LowCardinality(String), c7 Date32, c8 DateTime64(3)
) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1);
');

SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.arrays (
    c1 Int32, c2 Array(Int32), c3 Array(String)
) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1);
');

SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.addr (
    c1 UUID, c2 IPv4, c3 IPv6
) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1);
');

SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.bytes (
	c1 Int8, c2 String, c3 FixedString(16)
) ENGINE = MergeTree PARTITION BY c1 ORDER BY (c1);');

SELECT clickhouse_raw_query('CREATE TABLE binary_inserts_test.not_nullable (
	c1 Int8, c2 Nullable(Enum(''x''=1)), c3 Nullable(Enum16(''x''=1)), c4 LowCardinality(Nullable(String))
) ENGINE = MergeTree ORDER BY (c1);');

CREATE SCHEMA binary_inserts_test;
IMPORT FOREIGN SCHEMA binary_inserts_test FROM SERVER binary_inserts_loopback INTO binary_inserts_test;
SET search_path = binary_inserts_test, public;

/* ints */
INSERT INTO ints
	SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(1, 3) i;
SELECT * FROM ints ORDER BY c1;
INSERT INTO ints (c1, c4, c3, c2)
	SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(4, 6) i;
SELECT * FROM ints ORDER BY c1;

/* check dropping columns (that will change attnums) */
ALTER TABLE ints DROP COLUMN c1;
ALTER TABLE ints ADD COLUMN c1 SMALLINT;
INSERT INTO ints (c1, c2, c3, c4)
	SELECT i, i + 1, i + 2, i+ 3 FROM generate_series(7, 8) i;
SELECT c1, c2, c3, c4 FROM ints ORDER BY c1;

/* check other number types */
INSERT INTO uints
	SELECT i, i + 1, i + 2, i+ 3, (i % 2)::bool FROM generate_series(1, 3) i;
SELECT * FROM uints ORDER BY c1;
INSERT INTO floats
	SELECT i * 1.1, i + 2.1 FROM generate_series(1, 3) i;
SELECT * FROM floats ORDER BY c1;

/* check nullable */
INSERT INTO null_ints SELECT i, case WHEN i % 2 = 0 THEN NULL ELSE i END FROM generate_series(1, 10) i;
INSERT INTO null_ints(c1) SELECT i FROM generate_series(11, 13) i;
SELECT * FROM null_ints ORDER BY c1;
SELECT * FROM null_ints ORDER BY c1;

/* check dates and strings */
ALTER TABLE complex ALTER COLUMN c8 SET DATA TYPE timestamp(3);
\d+ complex
INSERT INTO complex VALUES
	(1, '2020-06-01', '2020-06-02 10:01:02', 't1', 'fix_t1', 'low1', '2020-06-01', '2020-06-02 10:01:02.123'),
	(2, '2020-06-02', '2020-06-03 10:01:02', 5, 'fix_t2', 'low2', '2020-06-02', '2020-06-03 11:01:02.234'),
	(3, '2020-06-03', '2020-06-04 10:01:02', 5, 'fix_t3', 'low3', '2020-06-03', '2020-06-04 12:01:02');
	
SET session timezone = 'UTC';
INSERT INTO complex VALUES
	(4, '1970-01-01', '1970-01-01 00:00:00', 5, 'fix_t4', 'low4', '1970-01-01', '1970-01-01 00:00:00'),
	(5, '2000-01-01', '2000-01-01 00:00:00', 5, 'fix_t5', 'low5', '2000-01-01', '2000-01-01 00:00:00');
SELECT * FROM complex ORDER BY c1;

RESET timezone;
SELECT * FROM complex ORDER BY c1;

/* check arrays */
INSERT INTO arrays VALUES
	(1, ARRAY[1,2], ARRAY['x']),
	(2, ARRAY[3,4,5], ARRAY['😀', '🦁']),
	(3, ARRAY[6,4], ARRAY['Zippy', 'Lisa "Skippy" Taylor']),
	(4, '{}'::int[], '{"[bracket''d]"}'::text[]),
	(5, ARRAY[0], ARRAY['[]']),
	(5, ARRAY[0], ARRAY[E'\\\b\f\r\n\t\a\'']);
SELECT * FROM arrays ORDER BY c1;

/* Check UUIDs and IPs */
\d addr
INSERT INTO addr VALUES
	('61f0c404-5cb3-11e7-907b-a6006ad3dba0', '116.106.34.242', '2001:44c8:129:2632:33:0:252:2'),
	('00000000-0000-0000-0000-000000000000', '127.0.0.1',      '::ffff:127.0.0.1'),
	('C62848ED-7316-4D15-92F3-9BB71EB69640', '183.247.232.58', '2a02:e980:1e::1')
;
SELECT * FROM addr ORDER BY c1;

/* Check BYTEA */
CREATE FOREIGN TABLE bbytes(
    c1 int,
    c2 BYTEA,
    c3 BYTEA
) SERVER binary_inserts_loopback OPTIONS (table_name 'bytes');
INSERT INTO bbytes
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
  FROM generate_series(1, 4) n;

-- Should have full binary values, including nul bytes, from BYTEA columns.
SELECT * FROM bbytes ORDER BY c1;

-- Nul bytes should truncate TEXT columns.
SELECT c1, encode(c2::bytea, 'hex'), encode(c3::bytea, 'hex') FROM bytes ORDER BY c1;

SELECT clickhouse_raw_query('TRUNCATE binary_inserts_test.bytes');

-- Should fail.
INSERT INTO bytes
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
  FROM generate_series(1, 4) n;

-- Remove FixedString length.
ALTER FOREIGN TABLE bytes ALTER c3 TYPE TEXT;
SELECT clickhouse_raw_query('ALTER TABLE binary_inserts_test.bytes MODIFY COLUMN c3 String');

-- Should succeed.
INSERT INTO bytes
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
  FROM generate_series(1, 4) n;

SELECT * FROM bbytes ORDER BY c1;
SELECT c1, encode(c2::bytea, 'hex'), encode(c3::bytea, 'hex') FROM bytes ORDER BY c1;

-- Test NULL values.
SELECT clickhouse_raw_query($$
	CREATE TABLE binary_inserts_test.null_vals (
		c1 UInt8,
		-- INT2OID
		c2 Nullable(UInt8), c3 Nullable(Int8), c4 Nullable(Int16),
		-- INT4OID
		c5 Nullable(Int32), c6 Nullable(UInt16),
		-- INT8OID
		c7 Nullable(Int64), c8 Nullable(UInt32), c9 Nullable(UInt64),
		-- FLOAT4OID, FLOAT8OID
		c10 Nullable(Float32), c11 Nullable(Float64),
		-- NUMERICOID
		c12 Nullable(Decimal128(1)), c13 Nullable(Decimal64(1)),
		c14 Nullable(Decimal32(1)), c15 Nullable(Decimal(1, 0)),
		-- TEXTOID
		c16 Nullable(String), c17 Nullable(FixedString(1)),
		c18 Nullable(Enum('x'=1)), c19 Nullable(Enum16('x'=1)),
		-- c20 LowCardinality(Nullable(String)),
		-- DATEOID
		c21 Nullable(Date), c22 Nullable(Date32),
		-- TIMESTAMPOID, TIMESTAMPTZOID
		c23 Nullable(DateTime), c24 Nullable(DateTime64),
		-- ANYARRAYOID
		-- c25 Array(Nullable(Int32)), -- pg_clickhouse: nested Nullable is not supported
		-- UUIDOID
		c26 Nullable(UUID),
		-- INETOID
		c27 Nullable(IPv4), c28 Nullable(IPv6)
	) ENGINE = MergeTree ORDER BY (c1)
$$);

IMPORT FOREIGN SCHEMA binary_inserts_test LIMIT TO (null_vals)
FROM SERVER binary_inserts_loopback INTO binary_inserts_test;

INSERT INTO null_vals VALUES(
	1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
	   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, -- NULL,
	   NULL, NULL, NULL, NULL, -- ARRAY[NULL]::int[],
	   NULL, NULL, NULL
);

SELECT * FROM null_vals;

-- Test default values.
SELECT clickhouse_raw_query($$
	CREATE TABLE binary_inserts_test.default_vals (
		c1 UInt8,
		-- INT2OID
		c2 UInt8, c3 Int8, c4 Int16,
		-- INT4OID
		c5 Int32, c6 UInt16,
		-- INT8OID
		c7 Int64, c8 UInt32, c9 UInt64,
		-- FLOAT4OID, FLOAT8OID
		c10 Float32, c11 Float64,
		-- NUMERICOID
		c12 Decimal128(1), c13 Decimal64(1),
		c14 Decimal32(1), c15 Decimal(1, 0),
		-- TEXTOID
		c16 String, c17 FixedString(1),
		c18 Enum('x'=1), c19 Enum16('x'=1),
		c20 LowCardinality(String),
		-- DATEOID
		c21 Date, c22 Date32,
		-- TIMESTAMPOID, TIMESTAMPTZOID
		c23 DateTime, c24 DateTime64,
		-- ANYARRAYOID
		c25 Array(Int32), -- pg_clickhouse: nested Nullable is not supported
		-- UUIDOID
		c26 UUID,
		-- INETOID
		c27 IPv4, c28 IPv6
	) ENGINE = MergeTree ORDER BY (c1)
$$);

IMPORT FOREIGN SCHEMA binary_inserts_test LIMIT TO (default_vals)
FROM SERVER binary_inserts_loopback INTO binary_inserts_test;

-- Fails on c2, the first column we try to set to NULL. Will cease to fail if
-- the `if (isnull && !nullable)` block is removed from column_append(). See
-- its comment for details.
INSERT INTO default_vals VALUES(
	1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
	   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
	   NULL, NULL, NULL, NULL, ARRAY[NULL]::int[],
	   NULL, NULL, NULL
);

SELECT * FROM default_vals;

-- Test unsupported Nullables.
INSERT INTO not_nullable VALUES (1, 'x', 'x', NULL);

DROP USER MAPPING FOR CURRENT_USER SERVER binary_inserts_loopback;
SELECT clickhouse_raw_query('DROP DATABASE binary_inserts_test');
DROP SERVER binary_inserts_loopback CASCADE;
