public static List<Column> generateReferencedColumns(
            String projectionExpression, @Nullable String filterExpression, List<Column> columns) {
        if (isNullOrWhitespaceOnly(projectionExpression)) {
            return new ArrayList<>();
        }

        Set<String> referencedColumnNames = new HashSet<>();

        SqlSelect sqlProject = parseProjectionExpression(projectionExpression);
        if (!sqlProject.getSelectList().isEmpty()) {
            for (SqlNode sqlNode : sqlProject.getSelectList()) {
                if (sqlNode instanceof SqlBasicCall) {
                    SqlBasicCall sqlBasicCall = (SqlBasicCall) sqlNode;
                    if (SqlKind.AS.equals(sqlBasicCall.getOperator().kind)) {
                        referencedColumnNames.addAll(
                                parseColumnNameList(sqlBasicCall.getOperandList().get(0)));
                    } else {
                        throw new ParseException(
                                "Unrecognized projection expression: "
                                        + sqlBasicCall
                                        + ". Should be <EXPR> AS <IDENTIFIER>");
                    }
                } else if (sqlNode instanceof SqlIdentifier) {
                    SqlIdentifier sqlIdentifier = (SqlIdentifier) sqlNode;
                    if (sqlIdentifier.isStar()) {
                        // wildcard star character matches all columns
                        return columns;
                    }
                    referencedColumnNames.add(
                            sqlIdentifier.names.get(sqlIdentifier.names.size() - 1));
                }
            }
        }

        if (!isNullOrWhitespaceOnly(projectionExpression)) {
            SqlSelect sqlFilter = parseFilterExpression(filterExpression);
            referencedColumnNames.addAll(parseColumnNameList(sqlFilter.getWhere()));
        }

        return columns.stream()
                .filter(e -> referencedColumnNames.contains(e.getName()))
                .collect(Collectors.toList());
    }
@Test
    public void testGenerateReferencedColumns() {
        List<Column> testColumns =
                Arrays.asList(
                        Column.physicalColumn("id", DataTypes.INT(), "id"),
                        Column.physicalColumn("name", DataTypes.STRING(), "string"),
                        Column.physicalColumn("age", DataTypes.INT(), "age"),
                        Column.physicalColumn("address", DataTypes.STRING(), "address"),
                        Column.physicalColumn("weight", DataTypes.DOUBLE(), "weight"),
                        Column.physicalColumn("height", DataTypes.DOUBLE(), "height"),
                        Column.physicalColumn("birthday", DataTypes.DATE(), "birthday"));

        List<Column> result =
                TransformParser.generateReferencedColumns(
                        "id, upper(name) as name, age + 1 as newage, weight / (height * height) as bmi",
                        "bmi > 17 and char_length(address) > 10",
                        testColumns);

        List<String> expected =
                Arrays.asList(
                        "`id` INT 'id'",
                        "`name` STRING 'string'",
                        "`age` INT 'age'",
                        "`address` STRING 'address'",
                        "`weight` DOUBLE 'weight'",
                        "`height` DOUBLE 'height'");
        Assertions.assertThat(result.toString()).isEqualTo("[" + String.join(", ", expected) + "]");

        // calculated columns must use AS to provide an alias name
        Assertions.assertThatThrownBy(
                        () ->
                                TransformParser.generateReferencedColumns(
                                        "id, 1 + 1", null, testColumns))
                .isExactlyInstanceOf(ParseException.class);
    }