I've been trying to implement the Visitor Pattern to parse some specific SQL Statements into an internal object structure consisting of TableDefinition and ColumnDefinition objects.
This is a small (stripped down) portion from the grammar:
column_definition
 : column_name datatype? column_constraint*
 ;
 
column_constraint
 : ( K_CONSTRAINT name )?
   ( K_PRIMARY K_KEY ( K_CLUSTERED | K_NONCLUSTERED )? ( K_ASC | K_DESC )? K_AUTOINCREMENT?
   | K_AUTOINCREMENT
   | K_NOT? K_NULL
   )
 ;
datatype
 : K_CHAR ( '(' unsigned_integer ')' )?                                     #char
 | K_DATE                                                                   #date
 ;
And here is one of the derived BaseVisitors which is meant to return ColumnDefinitions:
namespace SqlParser.Visitor
{
    public class DataTypeVisitor: SqlAnywhereParserBaseVisitor<ColumnDefinition>
    {
        public override ColumnDefinition VisitColumn_definition([NotNull] SqlAnywhereParser.Column_definitionContext context)
        {
            var res = VisitChildren(context);
            var constraint = (SqlAnywhereParser.Column_constraintContext[])context.column_constraint();
            if (res != null) // Add NULL attributes
            {
                if (constraint.Any(c => c.K_NULL() != null && c.K_NOT() == null))
                    res.IsNullable = true;
                if (constraint.Any(c => c.K_NULL() != null && c.K_NOT() != null))
                    res.IsNullable = false;
            }
            return res;
        }
        public override ColumnDefinition VisitChar([NotNull] SqlAnywhereParser.CharContext context)
        {
            return new ColumnDefinition()
            {
                DataType = DbType.StringFixedLength,
                Length = int.Parse(context.unsigned_integer()?.GetText() ?? "1") 
            };
        }
   }
}
When I debug the process, I can observe how the call to VisitChildren goes into VisitChar which returns a ColumnDefinition object. When VisitChar completes and the cursor jumps back to continue in VisitColumn_definition the variable res is null.
Did I miss something crucial or did I misunderstand the visitor pattern? Before I tried VisitChildren I used to use the base.VisitColumn_definition(context) call, which basically only calls VisitChildren.
Does anyone have a hint, which mistakes I made? Why doesn't my ColumnDefinition result created at the VisitChar leaf bubble up?
Below is my testinput:
CREATE TABLE "DBA"."pbcattbl" (
    "pbt_tnam"                       char(129) NOT NULL
   ,"pbt_tid"                        char(5) NULL
);