protected static int readColumn(String args, int startIndex, int len, String name, List<ClickHouseColumn> list) {
        ClickHouseColumn column = null;

        StringBuilder builder = new StringBuilder();

        int brackets = 0;
        boolean nullable = false;
        boolean lowCardinality = false;
        int i = startIndex;

        boolean fixedLength = true;
        int estimatedLength = 0;

        if (args.startsWith(KEYWORD_LOW_CARDINALITY, i)) {
            lowCardinality = true;
            int index = args.indexOf('(', i + KEYWORD_LOW_CARDINALITY.length());
            if (index < i) {
                throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE);
            }
            i = index + 1;
            brackets++;
        }
        if (args.startsWith(KEYWORD_NULLABLE, i)) {
            nullable = true;
            int index = args.indexOf('(', i + KEYWORD_NULLABLE.length());
            if (index < i) {
                throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE);
            }
            i = index + 1;
            brackets++;
        }

        String matchedKeyword;
        if (args.startsWith((matchedKeyword = KEYWORD_AGGREGATE_FUNCTION), i)
                || args.startsWith((matchedKeyword = KEYWORD_SIMPLE_AGGREGATE_FUNCTION), i)) {
            int index = args.indexOf('(', i + matchedKeyword.length());
            if (index < i) {
                throw new IllegalArgumentException("Missing function parameters");
            }
            List<String> params = new LinkedList<>();
            i = ClickHouseUtils.readParameters(args, index, len, params);

            ClickHouseAggregateFunction aggFunc = null;
            boolean isFirst = true;
            List<ClickHouseColumn> nestedColumns = new LinkedList<>();
            for (String p : params) {
                if (isFirst) {
                    if (matchedKeyword == KEYWORD_AGGREGATE_FUNCTION) { // NOSONAR
                        int pIndex = p.indexOf('(');
                        aggFunc = ClickHouseAggregateFunction.of(pIndex > 0 ? p.substring(0, pIndex) : p);
                    }
                    isFirst = false;
                } else {
                    nestedColumns.add(ClickHouseColumn.of("", p));
                }
            }
            column = new ClickHouseColumn(ClickHouseDataType.valueOf(matchedKeyword), name,
                    args.substring(startIndex, i), nullable, lowCardinality, params, nestedColumns);
            column.aggFuncType = aggFunc;
            fixedLength = false;
            estimatedLength++;
        } else if (args.startsWith(KEYWORD_ARRAY, i)) {
            int index = args.indexOf('(', i + KEYWORD_ARRAY.length());
            if (index < i) {
                throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE);
            }
            int endIndex = ClickHouseUtils.skipBrackets(args, index, len, '(');
            List<ClickHouseColumn> nestedColumns = new LinkedList<>();
            readColumn(args, index + 1, endIndex - 1, "", nestedColumns);
            if (nestedColumns.size() != 1) {
                throw new IllegalArgumentException(
                        "Array can have one and only one nested column, but we got: " + nestedColumns.size());
            }
            column = new ClickHouseColumn(ClickHouseDataType.Array, name, args.substring(startIndex, endIndex),
                    nullable, lowCardinality, null, nestedColumns);
            i = endIndex;
            fixedLength = false;
            estimatedLength++;
        } else if (args.startsWith(KEYWORD_MAP, i)) {
            int index = args.indexOf('(', i + KEYWORD_MAP.length());
            if (index < i) {
                throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE);
            }
            int endIndex = ClickHouseUtils.skipBrackets(args, index, len, '(');
            List<ClickHouseColumn> nestedColumns = new LinkedList<>();
            for (i = index + 1; i < endIndex; i++) {
                char c = args.charAt(i);
                if (c == ')') {
                    break;
                } else if (c != ',' && !Character.isWhitespace(c)) {
                    i = readColumn(args, i, endIndex, "", nestedColumns) - 1;
                }
            }
            if (nestedColumns.size() != 2) {
                throw new IllegalArgumentException(
                        "Map should have two nested columns(key and value), but we got: " + nestedColumns.size());
            }
            column = new ClickHouseColumn(ClickHouseDataType.Map, name, args.substring(startIndex, endIndex), nullable,
                    lowCardinality, null, nestedColumns);
            i = endIndex;
            fixedLength = false;
            estimatedLength++;
        } else if (args.startsWith(KEYWORD_NESTED, i)) {
            int index = args.indexOf('(', i + KEYWORD_NESTED.length());
            if (index < i) {
                throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE);
            }
            i = ClickHouseUtils.skipBrackets(args, index, len, '(');
            String originalTypeName = args.substring(startIndex, i);
            List<ClickHouseColumn> nestedColumns = parse(args.substring(index + 1, i - 1));
            if (nestedColumns.isEmpty()) {
                throw new IllegalArgumentException("Nested should have at least one nested column");
            }
            column = new ClickHouseColumn(ClickHouseDataType.Nested, name, originalTypeName, nullable, lowCardinality,
                    null, nestedColumns);
            fixedLength = false;
            estimatedLength++;
        } else if (args.startsWith(matchedKeyword = KEYWORD_TUPLE, i)
                || args.startsWith(matchedKeyword = KEYWORD_OBJECT, i)
                || args.startsWith(matchedKeyword = KEYWORD_VARIANT, i)) {
            int index = args.indexOf('(', i + matchedKeyword.length());
            if (index < i) {
                throw new IllegalArgumentException(ERROR_MISSING_NESTED_TYPE);
            }
            int endIndex = ClickHouseUtils.skipBrackets(args, index, len, '(') - 1;
            List<ClickHouseColumn> nestedColumns = new LinkedList<>();
            for (i = index + 1; i <= endIndex; i++) {
                char c = args.charAt(i);
                if (c == ')') {
                    break;
                } else if (c != ',' && !Character.isWhitespace(c)) {
                    String columnName = "";
                    i = readColumn(args, i, endIndex, "", nestedColumns);
                }
            }
            if (nestedColumns.isEmpty()) {
                throw new IllegalArgumentException("Tuple should have at least one nested column");
            }

            List<ClickHouseDataType> variantDataTypes = new ArrayList<>();
            if (matchedKeyword.equals(KEYWORD_VARIANT)) {
                nestedColumns.sort(Comparator.comparing(o -> o.getDataType().name()));
                nestedColumns.forEach(c -> {
                    c.columnName = "v." + c.getDataType().name();
                    variantDataTypes.add(c.dataType);
                });
            }
            column = new ClickHouseColumn(ClickHouseDataType.valueOf(matchedKeyword), name,
                    args.substring(startIndex, endIndex + 1), nullable, lowCardinality, null, nestedColumns);
            for (ClickHouseColumn n : nestedColumns) {
                estimatedLength += n.estimatedByteLength;
                if (!n.fixedByteLength) {
                    fixedLength = false;
                }
            }
            column.classToVariantOrdNumMap = ClickHouseDataType.buildVariantMapping(variantDataTypes);

            for (int ordNum = 0; ordNum < nestedColumns.size(); ordNum++) {
                ClickHouseColumn nestedColumn = nestedColumns.get(ordNum);
                if (nestedColumn.getDataType() == ClickHouseDataType.Array) {
                    Set<Class<?>> classSet = ClickHouseDataType.DATA_TYPE_TO_CLASS.get(nestedColumn.arrayBaseColumn.dataType);
                    if (classSet != null) {
                        if (column.arrayToVariantOrdNumMap == null) {
                            column.arrayToVariantOrdNumMap = new HashMap<>();
                        }
                        for (Class<?> c : classSet) {
                            column.arrayToVariantOrdNumMap.put(c, ordNum);
                        }
                    }
                } else if (nestedColumn.getDataType() == ClickHouseDataType.Map) {
                    Set<Class<?>> keyClassSet = ClickHouseDataType.DATA_TYPE_TO_CLASS.get(nestedColumn.getKeyInfo().getDataType());
                    Set<Class<?>> valueClassSet = ClickHouseDataType.DATA_TYPE_TO_CLASS.get(nestedColumn.getValueInfo().getDataType());
                    if (keyClassSet != null && valueClassSet != null) {
                        if (column.mapKeyToVariantOrdNumMap == null) {
                            column.mapKeyToVariantOrdNumMap = new HashMap<>();
                        }
                        if (column.mapValueToVariantOrdNumMap == null) {
                            column.mapValueToVariantOrdNumMap = new HashMap<>();
                        }
                        for (Class<?> c : keyClassSet) {
                            column.mapKeyToVariantOrdNumMap.put(c, ordNum);
                        }
                        for (Class<?> c : valueClassSet) {
                            column.mapValueToVariantOrdNumMap.put(c, ordNum);
                        }
                    }
                }
            }
        }

        if (column == null) {
            i = ClickHouseUtils.readNameOrQuotedString(args, i, len, builder);
            List<String> params = new LinkedList<>();
            for (; i < len; i++) {
                char ch = args.charAt(i);
                if (ch == '(') {
                    i = ClickHouseUtils.readParameters(args, i, len, params) - 1;
                } else if (ch == ')') {
                    if (brackets <= 0) {
                        break;
                    } else {
                        if (--brackets <= 0) {
                            i++;
                            break;
                        }
                    }
                } else if (ch == ',') {
                    break;
                } else if (!Character.isWhitespace(ch)) {
                    StringBuilder sb = new StringBuilder();
                    i = ClickHouseUtils.readNameOrQuotedString(args, i, len, sb);
                    String modifier = sb.toString();
                    String normalizedModifier = modifier.toUpperCase();
                    sb.setLength(0);
                    boolean startsWithNot = false;
                    if ("NOT".equals(normalizedModifier)) {
                        startsWithNot = true;
                        i = ClickHouseUtils.readNameOrQuotedString(args, i, len, sb);
                        modifier = sb.toString();
                        normalizedModifier = modifier.toUpperCase();
                        sb.setLength(0);
                    }

                    if ("NULL".equals(normalizedModifier)) {
                        if (nullable) {
                            throw new IllegalArgumentException("Nullable and NULL cannot be used together");
                        }
                        nullable = !startsWithNot;
                        i = ClickHouseUtils.skipContentsUntil(args, i, len, ',') - 1;
                        break;
                    } else if (startsWithNot) {
                        throw new IllegalArgumentException("Expect keyword NULL after NOT");
                    } else if ("ALIAS".equals(normalizedModifier) || "CODEC".equals(normalizedModifier)
                            || "DEFAULT".equals(normalizedModifier) || "MATERIALIZED".equals(normalizedModifier)
                            || "TTL".equals(normalizedModifier)) { // stop words
                        i = ClickHouseUtils.skipContentsUntil(args, i, len, ',') - 1;
                        break;
                    } else {
                        if ((name == null || name.isEmpty())
                                && !ClickHouseDataType.mayStartWith(builder.toString(), normalizedModifier)) {
                            return readColumn(args, i - modifier.length(), len, builder.toString(), list);
                        } else {
                            builder.append(' ');
                        }
                        builder.append(modifier);
                        i--;
                    }
                }
            }
            column = new ClickHouseColumn(ClickHouseDataType.of(builder.toString()), name,
                    args.substring(startIndex, i), nullable, lowCardinality, params, null);
            builder.setLength(0);
        }

        if (nullable) {
            fixedLength = false;
            estimatedLength++;
        } else if (column.dataType == ClickHouseDataType.FixedString) {
            fixedLength = true;
            estimatedLength = column.precision;
        } else if (column.dataType.getByteLength() == 0) {
            fixedLength = false;
        } else {
            estimatedLength += column.dataType.getByteLength();
        }
        column.fixedByteLength = fixedLength;
        column.estimatedByteLength = estimatedLength;
        list.add(update(column));

        return i;
    }
This class represents a column defined in database.
clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseColumn.java