CREATE EXTENSION pgproto;

-- Create table with custom type
CREATE TABLE pb_test (id serial, data protobuf);

-- Register test schema first to avoid session caching issues in tests
INSERT INTO pb_schemas (name, data) VALUES ('test_data_new/test.proto', decode('0AFE010A12736372617463682F746573742E70726F746F22170A05496E6E6572120E0A0269641801200128055202696422C6010A054F75746572121C0A05696E6E657218012001280B32062E496E6E65725205696E6E657212240A047461677318022003280B32102E4F757465722E54616773456E74727952047461677312160A0673636F726573180320032805520673636F726573120E0A01611804200128054800520161120E0A016218052001280948005201621A370A0954616773456E74727912100A036B657918012001280952036B657912140A0576616C7565180220012805520576616C75653A02380142080A0663686F696365620670726F746F33', 'hex'));
INSERT INTO pb_schemas (name, data) VALUES ('test_data_new/coverage_test.proto', decode('0ae3020a13636f7665726167655f746573742e70726f746f22c3020a0b436f7665726167654d7367120c0a0166180120012802520166120c0a016218022001280852016212170a077374725f617272180320032809520673747241727212310a077374725f6d617018042003280b32182e436f7665726167654d73672e5374724d6170456e74727952067374724d6170121b0a09666c6f61745f6172721805200328025208666c6f617441727212370a09666c6f61745f6d617018062003280b321a2e436f7665726167654d73672e466c6f61744d6170456e7472795208666c6f61744d61701a390a0b5374724d6170456e74727912100a036b657918012001280952036b657912140a0576616c7565180220012809520576616c75653a0238011a3b0a0d466c6f61744d6170456e74727912100a036b657918012001280952036b657912140a0576616c7565180220012802520576616c75653a023801620670726f746f33', 'hex'));


-- Insert valid protobuf (Tag 1, Varint 42 => 0x08 0x2a)
INSERT INTO pb_test (data) VALUES ('\x082a');

-- Select it back (Hex output)
SELECT data FROM pb_test;

-- Extract Tag 1
SELECT pb_get_int32(data, 1) FROM pb_test;

-- Extract Tag 2 (it doesn't exist, should return NULL)
SELECT pb_get_int32(data, 2) FROM pb_test;

-- Insert multi-field protobuf: Tag 1 = 42, Tag 2 = Varint 120 (0x10 0x78)
-- Payload: 0x08 0x2a 0x10 0x78
INSERT INTO pb_test (data) VALUES ('\x082a1078');

-- Extract from multi-field
SELECT pb_get_int32(data, 1) AS tag1, pb_get_int32(data, 2) AS tag2 FROM pb_test WHERE id = 2;

-- Negative test for name-based extraction (should fail because message is not registered or found)
-- We wrap it in a function or just run it and expect error. In pg_regress, we can just run it and let the output match expected errors!
SELECT pb_get_int32_by_name(data, 'UnregisteredMessage', 'id') FROM pb_test WHERE id = 1;

-- Test shorthand operator -> (Dot Notation)
SELECT data -> 'UnregisteredMessage.id'::text FROM pb_test WHERE id = 1;


-- Test GIN Indexing (Purity)
CREATE TABLE pb_test_gin (id serial, data protobuf);

-- Insert row with Tag 1 = 42
INSERT INTO pb_test_gin (data) VALUES ('\x082a');

-- Insert row with Tag 1 = 100
INSERT INTO pb_test_gin (data) VALUES ('\x0864');

-- Create GIN index
CREATE INDEX pb_gin_idx ON pb_test_gin USING gin (data);

-- Verify contains operator @> sequentially (or via index if small enough)
SELECT * FROM pb_test_gin WHERE data @> '\x082a'::protobuf;


-- Nested Path Navigation Test

-- Insert valid nested protobuf (Outer.inner.id = 42)
INSERT INTO pb_test (data) VALUES ('\x0a02082a'::protobuf);

-- Test #> with valid path
SELECT data #> '{Outer, inner, id}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Test #> with invalid path (unknown field)
SELECT data #> '{Outer, inner, unknown}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Test #> with invalid message name
SELECT data #> '{UnknownMessage, id}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;-- Map and Repeated Field Navigation Tests

-- Insert expanded protobuf data
-- Text format: inner { id: 42 } tags { key: "foo", value: 100 } tags { key: "bar", value: 200 } scores: 10 scores: 20 choice { a: 30 }
-- Hex: \x0A02082A12070A03666F6F106412080A0362617210C8011A020A14201E
INSERT INTO pb_test (data) VALUES ('\x0A02082A12070A03666F6F106412080A0362617210C8011A020A14201E'::protobuf);

-- Test array index (scores[0] => 10)
SELECT data #> '{Outer, scores, 0}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Test array index (scores[1] => 20)
SELECT data #> '{Outer, scores, 1}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Test map key lookup (tags["foo"] => 100)
SELECT data #> '{Outer, tags, foo}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Test map key lookup (tags["bar"] => 200)
SELECT data #> '{Outer, tags, bar}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Test oneof (choice a => 30)
SELECT data #> '{Outer, a}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Edge Case: Out of bounds array index
SELECT data #> '{Outer, scores, 5}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Edge Case: Typo in map key (should return NULL / empty)
SELECT data #> '{Outer, tags, unknown_key}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Edge Case: Invalid path length (one element only - message name, should return NULL if we expect field)
SELECT data #> '{Outer}'::text[] FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Edge Case: Corrupt protobuf binary (short read / truncated)
INSERT INTO pb_test (data) VALUES ('\x0A0208'::protobuf); -- Truncated inner message
SELECT pb_get_int32(data, 1) FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;


-- 📈 Additional Coverage Tests

-- 1. Test pb_to_json
SELECT pb_to_json(data, 'Outer'::text) FROM pb_test WHERE id = 1;
SELECT pb_to_json(data, 'Outer'::text) FROM pb_test WHERE id = 3;

-- 2. Test pb_register_schema (Explicit)
SELECT pb_register_schema('test_data_new/explicit_test.proto', decode('0AFE010A12736372617463682F746573742E70726F746F22170A05496E6E6572120E0A0269641801200128055202696422C6010A054F75746572121C0A05696E6E657218012001280B32062E496E6E65725205696E6E657212240A047461677318022003280B32102E4F757465722E54616773456E74727952047461677312160A0673636F726573180320032805520673636F726573120E0A01611804200128054800520161120E0A016218052001280948005201621A370A0954616773456E74727912100A036B657918012001280952036B657912140A0576616C7565180220012805520576616C75653A02380142080A0663686F696365620670726F746F33', 'hex'));

-- 3. Test Navigation Edge cases & Error Paths
SELECT pb_get_int32(data, 100) FROM pb_test WHERE id = 1; -- Tag not in message
SELECT data -> 'Outer.inner'::text FROM pb_test WHERE id = 1; -- Field is a message, not a primitive
SELECT data -> 'Outer..id'::text FROM pb_test WHERE id = 1; -- Empty field name
SELECT data -> 'Outer.unknown_field'::text FROM pb_test WHERE id = 1; -- Unknown field


-- 📊 GIN Coverage Tests

-- Insert multiple wire types
-- 0x09: Tag 1, Wire 1 (64-bit) => \x090102030405060708
INSERT INTO pb_test_gin (data) VALUES ('\x090102030405060708');
-- 0x0a: Tag 1, Wire 2 (Length-delimited) => \x0a03666f6f
INSERT INTO pb_test_gin (data) VALUES ('\x0a03666f6f');
-- 0x0d: Tag 1, Wire 5 (32-bit) => \x0d01020304
INSERT INTO pb_test_gin (data) VALUES ('\x0d01020304');

-- Force GIN index scan to trigger gin_consistent and query extractors
SET enable_seqscan = off;

-- Re-run query to hit GIN index consistent logic
SELECT * FROM pb_test_gin WHERE data @> '\x082a'::protobuf;

-- Multi-key query to trigger consistent looping (And query extraction)
-- Query matches Tag 1=42 AND Tag 2=120
SELECT * FROM pb_test_gin WHERE data @> '\x082a1078'::protobuf;

-- Reset seqscan setting
RESET enable_seqscan;

-- Re-allocate logic (Message with > 8 fields)
INSERT INTO pb_test_gin (data) VALUES ('\x0801\x1002\x1803\x2004\x2805\x3006\x3807\x4008\x4809\x500a');
SELECT * FROM pb_test_gin WHERE data @> '\x0801'::protobuf;


-- 🧭 Navigation Skipping & Contains Edge Cases

-- Test skipping Length-delimited field (Field 2) to get Field 3
-- Model: Field 1 (Varint), Field 2 (Length-delimited), Field 3 (Varint)
-- Payload: \x0801 (Tag 1=1) \x1203666f6f (Tag 2="foo") \x182a (Tag 3=42)
INSERT INTO pb_test (data) VALUES ('\x08011203666f6f182a'::protobuf);
SELECT pb_get_int32(data, 3) FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Test skipping Fixed64 field (Field 1) to get Field 2
-- Payload: \x090102030405060708 (Tag 1=Fixed64) \x102a (Tag 2=42)
INSERT INTO pb_test (data) VALUES ('\x090102030405060708102a'::protobuf);
SELECT pb_get_int32(data, 2) FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Test skipping Fixed32 field (Field 1) to get Field 2
-- Payload: \x0d01020304 (Tag 1=Fixed32) \x102a (Tag 2=42)
INSERT INTO pb_test (data) VALUES ('\x0d01020304102a'::protobuf);
SELECT pb_get_int32(data, 2) FROM (SELECT data FROM pb_test ORDER BY id DESC LIMIT 1) s;

-- Test contains (@>) with non-Varint query
SELECT * FROM pb_test_gin WHERE data @> '\x0a03666f6f'::protobuf; -- Length-delimited string "foo"
SELECT * FROM pb_test_gin WHERE data @> '\x090102030405060708'::protobuf; -- Fixed64
SELECT * FROM pb_test_gin WHERE data @> '\x0d01020304'::protobuf; -- Fixed32


-- 🎯 Name-based Success Paths & Dot Skipping

-- Test pb_get_int32_by_name success
SELECT pb_get_int32_by_name(data, 'Inner', 'id') FROM pb_test WHERE id = 1;

-- Test pb_get_int32_by_name_dot success and skipping
-- Querying 'Outer.a' (Tag 4) on row 4 which has inner, tags, scores before it.
SELECT data -> 'Outer.a'::text FROM pb_test WHERE id = 4;


-- 🧩 Path Navigation Edge Cases & Error Paths

-- 1. Access beyond primitive in array (should error)
SELECT data #> '{Outer, scores, 0, sub_field}'::text[] FROM pb_test WHERE id = 4;

-- 2. Access beyond primitive in map (should error)
SELECT data #> '{Outer, tags, foo, sub_field}'::text[] FROM pb_test WHERE id = 4;

-- 3. Query submessage as primitive (expects varint, gets length-delimited => should error)
SELECT data #> '{Outer, inner}'::text[] FROM pb_test WHERE id = 4;

-- 4. Query invalid wire type for submessage (expects length-delimited, gets varint => should error)
SELECT data #> '{Outer, inner, id}'::text[] FROM pb_test WHERE id = 1;


-- 📊 JSON Error Coverage Tests

-- 1. Unregistered message name
SELECT pb_to_json(data, 'UnregisteredMessage'::text) FROM pb_test WHERE id = 1;

-- 2. Corrupt binary data (Truncated message)
SELECT pb_to_json('\x0A0208'::protobuf, 'Outer'::text);


-- 🎯 Dynamic Registration Success & Querying
-- 1. Register NewOuter schema dynamically
SELECT pb_register_schema('test_data_new/new_test.proto'::text, decode('0a6f0a1c746573745f646174615f6e65772f6e65775f746573742e70726f746f221a0a084e6577496e6e6572120e0a02696418012001280552026964222b0a084e65774f75746572121f0a05696e6e657218012001280b32092e4e6577496e6e65725205696e6e6572620670726f746f33', 'hex'));

-- 2. Query NewOuter using JSON conversion
SELECT pb_to_json('\x0a02082a'::protobuf, 'NewOuter'::text);

-- 3. Query NewOuter using navigation
SELECT '\x0a02082a'::protobuf -> 'NewOuter.inner.id'::text;


-- 🧭 Name-based Skipping of Fixed types & Unsupported Wire Types
-- 1. Register MixedFields schema
SELECT pb_register_schema('test_data_new/mixed_test.proto'::text, decode('0abc010a1e746573745f646174615f6e65772f6d697865645f746573742e70726f746f221c0a0a4d69786564496e6e6572120e0a02696418012001280552026964222f0a0a4d697865644f7574657212210a05696e6e657218012001280b320b2e4d69786564496e6e65725205696e6e657222430a0b4d697865644669656c647312100a03663634180120012806520366363412100a03663332180220012807520366333212100a0376616c180320012805520376616c620670726f746f33', 'hex'));

-- 🧭 Name-based Skipping of All Wire Types
-- 1. Query 'val' (Tag 3) by name, skipping f64 (Tag 1, Wire 1) and f32 (Tag 2, Wire 5)
SELECT pb_get_int32_by_name('\x0901000000000000001502000000182a'::protobuf, 'MixedFields', 'val');

-- 2. Query 'val' (Tag 3) by name, skipping v0 (Tag 1, Wire 0) and v1 (Tag 2, Wire 2)
SELECT pb_get_int32_by_name('\x082a120568656c6c6f1864'::protobuf, 'SkipFields', 'val');

-- 3. Test Unsupported Wire Type error (Wire type 3) in pb_get_int32
SELECT pb_get_int32('\x0b0102'::protobuf, 1);

-- 🛠️ Mutation Tests
-- 1. Test pb_set for int32 field 'a' in Outer
SELECT pb_to_json(pb_set(data, ARRAY['Outer', 'a'], '123'), 'Outer') FROM pb_test WHERE id = 1;

-- 2. Test pb_set for string field 'b' in Outer (oneof override)
SELECT pb_to_json(pb_set(data, ARRAY['Outer', 'b'], 'hello_world'), 'Outer') FROM pb_test WHERE id = 1;

-- 🛠️ pb_insert Tests
-- 1. Test pb_insert for array field 'scores' in Outer (append to empty)
SELECT pb_to_json(pb_insert('\x'::protobuf, ARRAY['Outer', 'scores', '0'], '100'), 'Outer');

-- 2. Test pb_insert for array field 'scores' in Outer (insert at middle)
SELECT pb_to_json(pb_insert(pb_insert('\x'::protobuf, ARRAY['Outer', 'scores', '0'], '100'), ARRAY['Outer', 'scores', '0'], '50'), 'Outer');

-- 3. Test pb_insert for map field 'tags' in Outer (insert new key)
SELECT pb_to_json(pb_insert('\x'::protobuf, ARRAY['Outer', 'tags', 'key1'], '200'), 'Outer');

-- 4. Test pb_insert for map field 'tags' in Outer (error on existing key)
SELECT pb_to_json(pb_insert(pb_insert('\x'::protobuf, ARRAY['Outer', 'tags', 'key1'], '200'), ARRAY['Outer', 'tags', 'key1'], '300'), 'Outer');

-- 5. Test pb_insert error: Array index out of bounds
SELECT pb_insert('\x'::protobuf, ARRAY['Outer', 'scores', '1'], '100');

-- 6. Test pb_insert error: Field not found
SELECT pb_insert('\x'::protobuf, ARRAY['Outer', 'nonexistent', '0'], '100');

-- 7. Test pb_insert error: Not a repeated or map field
SELECT pb_insert('\x'::protobuf, ARRAY['Outer', 'inner', '0'], '100');

-- 8. Test pb_delete for array field 'scores' in Outer
SELECT pb_to_json(pb_delete(pb_insert(pb_insert('\x'::protobuf, ARRAY['Outer', 'scores', '0'], '100'), ARRAY['Outer', 'scores', '1'], '200'), ARRAY['Outer', 'scores', '0']), 'Outer');

-- 9. Test pb_delete for map field 'tags' in Outer
SELECT pb_to_json(pb_delete(pb_insert('\x'::protobuf, ARRAY['Outer', 'tags', 'key1'], '200'), ARRAY['Outer', 'tags', 'key1']), 'Outer');

-- 10. Test pb_delete to clear field
SELECT pb_to_json(pb_delete(pb_set('\x'::protobuf, ARRAY['Outer', 'a'], '42'), ARRAY['Outer', 'a']), 'Outer');

-- 11. Test pb_merge (||) for scalar fields
SELECT pb_to_json(pb_set('\x'::protobuf, ARRAY['Outer', 'a'], '10') || pb_set('\x'::protobuf, ARRAY['Outer', 'a'], '20'), 'Outer');

-- 12. Test pb_merge (||) for arrays (append)
SELECT pb_to_json(pb_insert('\x'::protobuf, ARRAY['Outer', 'scores', '0'], '10') || pb_insert('\x'::protobuf, ARRAY['Outer', 'scores', '0'], '20'), 'Outer');

-- 13. Test pb_merge (||) for maps (different keys)
SELECT pb_to_json(pb_insert('\x'::protobuf, ARRAY['Outer', 'tags', 'k1'], '100') || pb_insert('\x'::protobuf, ARRAY['Outer', 'tags', 'k2'], '200'), 'Outer')::jsonb;

-- 14. Test pb_merge (||) for maps (overlapping keys)
SELECT pb_to_json(pb_insert('\x'::protobuf, ARRAY['Outer', 'tags', 'k1'], '100') || pb_insert('\x'::protobuf, ARRAY['Outer', 'tags', 'k1'], '200'), 'Outer');

-- 15. Test pb_delete error: Array index out of bounds
SELECT pb_delete('\x'::protobuf, ARRAY['Outer', 'scores', '0']);

-- 16. Test pb_delete error: Not a repeated or map field
SELECT pb_delete('\x'::protobuf, ARRAY['Outer', 'a', '0']);

-- 17. Test pb_delete error: Field not found
SELECT pb_delete('\x'::protobuf, ARRAY['Outer', 'nonexistent']);

-- 18. Test pb_set error: Unsupported type for modification
SELECT pb_set('\x'::protobuf, ARRAY['Outer', 'tags'], 'some_value');

-- 19. Test pb_set for float field 'f' in CoverageMsg
SELECT pb_to_json(pb_set('\x'::protobuf, ARRAY['CoverageMsg', 'f'], '1.23'), 'CoverageMsg');

-- 20. Test pb_set for bool field 'b' in CoverageMsg (true)
SELECT pb_to_json(pb_set('\x'::protobuf, ARRAY['CoverageMsg', 'b'], 'true'), 'CoverageMsg');

-- 21. Test pb_set for bool field 'b' in CoverageMsg (false)
SELECT pb_to_json(pb_set('\x'::protobuf, ARRAY['CoverageMsg', 'b'], 'false'), 'CoverageMsg');

-- 22. Test pb_set for bool field 'b' in CoverageMsg (invalid)
SELECT pb_set('\x'::protobuf, ARRAY['CoverageMsg', 'b'], 'invalid');

-- 23. Test pb_insert for string array 'str_arr' in CoverageMsg
SELECT pb_to_json(pb_insert('\x'::protobuf, ARRAY['CoverageMsg', 'str_arr', '0'], 'hello'), 'CoverageMsg');

-- 24. Test pb_insert for string map 'str_map' in CoverageMsg
SELECT pb_to_json(pb_insert('\x'::protobuf, ARRAY['CoverageMsg', 'str_map', 'key1'], 'value1'), 'CoverageMsg');

-- 25. Test pb_set error: Decode failure (invalid data)
SELECT pb_set('\xff'::protobuf, ARRAY['CoverageMsg', 'f'], '1.23');

-- 26. Test pb_set error: Invalid path length (too short)
SELECT pb_set('\x'::protobuf, ARRAY['CoverageMsg'], '123');

-- 27. Test pb_insert error: Invalid path length (too short)
SELECT pb_insert('\x'::protobuf, ARRAY['CoverageMsg', 'str_arr'], '123');

-- 28. Test pb_delete error: Invalid path length (too short)
SELECT pb_delete('\x'::protobuf, ARRAY['CoverageMsg']);

-- 29. Test pb_insert error: Unsupported type for array insertion
SELECT pb_insert('\x'::protobuf, ARRAY['CoverageMsg', 'float_arr', '0'], '1.23');

-- 30. Test pb_insert error: Unsupported map value type
SELECT pb_insert('\x'::protobuf, ARRAY['CoverageMsg', 'float_map', 'key1'], '1.23');

-- 31. Test pb_get_int32_by_path with field not found (returns NULL)
SELECT pb_get_int32_by_path('\x'::protobuf, ARRAY['Outer', 'nonexistent']);

-- 32. Test pb_get_int32_by_path error: Map value traversal beyond int32 not supported
SELECT pb_get_int32_by_path(pb_insert('\x'::protobuf, ARRAY['Outer', 'tags', 'key'], '100'), ARRAY['Outer', 'tags', 'key', 'something_else']);

-- 33. Test pb_set error: Empty path
SELECT pb_set('\x'::protobuf, ARRAY[]::text[], '123');

-- 34. Test pb_set error: Field not found
SELECT pb_set('\x'::protobuf, ARRAY['CoverageMsg', 'nonexistent'], '123');

-- 35. Test pb_insert error: Message not found
SELECT pb_insert('\x'::protobuf, ARRAY['NonExistentMessage', 'f', '0'], '1.23');




