@@ -615,6 +615,140 @@ def test_macro_type_enum_survival(self) -> None:
615615 restored = MacroInfo .deserialize_from_batch (batch )
616616 assert restored .macro_type == macro_type
617617
618+ def test_arguments_schema_round_trip (self ) -> None :
619+ """arguments_schema carries vgi_doc per documented param; absent doc -> no key."""
620+ from vgi .argument_spec import VGI_DOC_KEY , macro_arguments_schema , macro_parameter_docs_from_schema
621+
622+ defaults = pa .RecordBatch .from_pydict ({"lo" : pa .array ([0 ], type = pa .int64 ())})
623+ args = macro_arguments_schema (
624+ parameters = ["val" , "lo" , "hi" ],
625+ parameter_default_values = defaults ,
626+ parameter_docs = {"val" : "value to clamp" , "hi" : "upper bound" },
627+ )
628+ original = MacroInfo (
629+ name = "clamp" ,
630+ schema_name = "main" ,
631+ macro_type = MacroType .SCALAR ,
632+ parameters = ["val" , "lo" , "hi" ],
633+ parameter_default_values = defaults ,
634+ definition = "GREATEST(lo, LEAST(hi, val))" ,
635+ comment = None ,
636+ tags = {},
637+ arguments_schema = args ,
638+ )
639+ serialized = original .serialize_to_bytes ()
640+ batch , _ = deserialize_record_batch (serialized )
641+ restored = MacroInfo .deserialize_from_batch (batch )
642+
643+ assert restored .arguments_schema is not None
644+ rs = restored .arguments_schema
645+ # One field per parameter, in order.
646+ assert rs .names == ["val" , "lo" , "hi" ]
647+ # Field type tracks the default value type when known, else null.
648+ assert rs .field ("lo" ).type == pa .int64 ()
649+ assert rs .field ("val" ).type == pa .null ()
650+ assert rs .field ("hi" ).type == pa .null ()
651+ # Documented params carry vgi_doc; undocumented (lo) has no key.
652+ assert (rs .field ("val" ).metadata or {}).get (VGI_DOC_KEY ) == b"value to clamp"
653+ assert (rs .field ("hi" ).metadata or {}).get (VGI_DOC_KEY ) == b"upper bound"
654+ assert VGI_DOC_KEY not in (rs .field ("lo" ).metadata or {})
655+ # Convenience extractor returns only documented params.
656+ assert macro_parameter_docs_from_schema (rs ) == {"val" : "value to clamp" , "hi" : "upper bound" }
657+
658+ def test_none_arguments_schema (self ) -> None :
659+ """arguments_schema defaults to None (older workers) and survives round-trip."""
660+ original = MacroInfo (
661+ name = "simple" ,
662+ schema_name = "main" ,
663+ macro_type = MacroType .SCALAR ,
664+ parameters = ["x" ],
665+ definition = "x" ,
666+ comment = None ,
667+ tags = {},
668+ )
669+ serialized = original .serialize_to_bytes ()
670+ batch , _ = deserialize_record_batch (serialized )
671+ restored = MacroInfo .deserialize_from_batch (batch )
672+ assert restored .arguments_schema is None
673+
674+
675+ class TestMacroArgumentsSchemaWire :
676+ """Macro per-parameter docs flow over create/list wire types."""
677+
678+ def test_declarative_macro_to_info_carries_docs (self ) -> None :
679+ """Declarative Macro.parameter_docs -> MacroInfo.arguments_schema vgi_doc."""
680+ from vgi .argument_spec import macro_parameter_docs_from_schema
681+ from vgi .catalog .descriptors import Macro
682+
683+ m = Macro (
684+ name = "clamp" ,
685+ macro_type = MacroType .SCALAR ,
686+ parameters = ["x" , "lo" , "hi" ],
687+ parameter_default_values = pa .RecordBatch .from_pydict (
688+ {"lo" : pa .array ([0 ], type = pa .int64 ()), "hi" : pa .array ([100 ], type = pa .int64 ())}
689+ ),
690+ parameter_docs = {"x" : "value to clamp" },
691+ definition = "GREATEST(lo, LEAST(hi, x))" ,
692+ )
693+ info = m .to_macro_info ("main" )
694+ assert info .arguments_schema is not None
695+ assert info .arguments_schema .names == ["x" , "lo" , "hi" ]
696+ assert macro_parameter_docs_from_schema (info .arguments_schema ) == {"x" : "value to clamp" }
697+
698+ def test_declarative_macro_rejects_unknown_doc_param (self ) -> None :
699+ """parameter_docs keys must be in parameters (validated like defaults)."""
700+ from vgi .catalog .descriptors import Macro
701+
702+ with pytest .raises (ValueError , match = "documented parameter 'bogus' not found" ):
703+ Macro (
704+ name = "bad" ,
705+ macro_type = MacroType .SCALAR ,
706+ parameters = ["x" ],
707+ parameter_docs = {"bogus" : "nope" },
708+ definition = "x" ,
709+ )
710+
711+ def test_macro_create_request_round_trip (self ) -> None :
712+ """MacroCreateRequest carries arguments_schema over the wire."""
713+ from vgi .argument_spec import macro_arguments_schema , macro_parameter_docs_from_schema
714+ from vgi .catalog import OnConflict
715+ from vgi .protocol import MacroCreateRequest
716+
717+ args = macro_arguments_schema (
718+ parameters = ["x" , "y" ],
719+ parameter_docs = {"x" : "first" , "y" : "second" },
720+ )
721+ req = MacroCreateRequest (
722+ attach_opaque_data = b"attach" ,
723+ schema_name = "main" ,
724+ name = "add" ,
725+ macro_type = MacroType .SCALAR ,
726+ parameters = ["x" , "y" ],
727+ definition = "x + y" ,
728+ on_conflict = OnConflict .ERROR ,
729+ arguments_schema = args ,
730+ )
731+ restored = MacroCreateRequest .deserialize_from_bytes (req .serialize_to_bytes ())
732+ assert restored .arguments_schema is not None
733+ assert macro_parameter_docs_from_schema (restored .arguments_schema ) == {"x" : "first" , "y" : "second" }
734+
735+ def test_macro_create_request_none_arguments_schema (self ) -> None :
736+ """MacroCreateRequest.arguments_schema defaults to None and round-trips."""
737+ from vgi .catalog import OnConflict
738+ from vgi .protocol import MacroCreateRequest
739+
740+ req = MacroCreateRequest (
741+ attach_opaque_data = b"attach" ,
742+ schema_name = "main" ,
743+ name = "add" ,
744+ macro_type = MacroType .SCALAR ,
745+ parameters = ["x" , "y" ],
746+ definition = "x + y" ,
747+ on_conflict = OnConflict .ERROR ,
748+ )
749+ restored = MacroCreateRequest .deserialize_from_bytes (req .serialize_to_bytes ())
750+ assert restored .arguments_schema is None
751+
618752
619753class TestFunctionInfoSerialization :
620754 """Test FunctionInfo serialization round-trip."""
0 commit comments