private void addTableSettings(final List<AbstractToken> tokens,
                                      final List<TokenType> consumedTokens,
                                      final List<ResultRequest> resultRequests,
                                      final Query.Builder queryBuilder) {
            final Map<String, Sort> sortMap = new HashMap<>();
            final Map<String, Integer> groupMap = new HashMap<>();
            final Map<String, IncludeExcludeFilter> filterMap = new HashMap<>();
            int groupDepth = 0;

            final TableSettings.Builder tableSettingsBuilder = TableSettings.builder();
            TableSettings visTableSettings = null;

            List<AbstractToken> remaining = new LinkedList<>(tokens);
            while (!remaining.isEmpty()) {
                final AbstractToken token = remaining.getFirst();

                if (token instanceof final KeywordGroup keywordGroup) {
                    switch (keywordGroup.getTokenType()) {
                        case FROM -> {
                            checkTokenOrder(token, consumedTokens);
                            remaining = addDataSource(remaining, queryBuilder::dataSource, false);
                        }
                        case WHERE -> {
                            checkTokenOrder(token, consumedTokens);
                            remaining =
                                    addExpression(remaining,
                                            consumedTokens,
                                            Set.of(TokenType.FROM),
                                            TokenType.WHERE,
                                            queryBuilder::expression);
                        }
                        case EVAL -> {
                            checkTokenOrder(token, consumedTokens);
                            processEval(keywordGroup);
                            remaining.removeFirst();
                        }
                        case WINDOW -> {
                            checkTokenOrder(token, consumedTokens);
                            processWindow(
                                    keywordGroup,
                                    tableSettingsBuilder);
                            remaining.removeFirst();
                        }
                        case FILTER -> {
                            checkTokenOrder(token, consumedTokens);
                            remaining =
                                    addExpression(remaining,
                                            consumedTokens,
                                            Set.of(
                                                    TokenType.FROM,
                                                    TokenType.WHERE,
                                                    TokenType.EVAL,
                                                    TokenType.WINDOW),
                                            TokenType.FILTER,
                                            tableSettingsBuilder::valueFilter);
                        }
                        case SORT -> {
                            checkTokenOrder(token, consumedTokens);
                            processSortBy(
                                    keywordGroup,
                                    sortMap);
                            remaining.removeFirst();
                        }
                        case GROUP -> {
                            checkTokenOrder(token, consumedTokens);
                            processGroupBy(
                                    keywordGroup,
                                    groupMap,
                                    groupDepth);
                            groupDepth++;
                            remaining.removeFirst();
                        }
                        case HAVING -> {
                            inHaving = true;
                            checkTokenOrder(token, consumedTokens);
                            remaining =
                                    addExpression(remaining,
                                            consumedTokens,
                                            inverse(Set.of(TokenType.LIMIT, TokenType.SELECT, TokenType.SHOW)),
                                            TokenType.HAVING,
                                            tableSettingsBuilder::aggregateFilter);
                            inHaving = false;
                        }
                        case SELECT -> {
                            checkTokenOrder(token, consumedTokens);
                            processSelect(
                                    keywordGroup,
                                    sortMap,
                                    groupMap,
                                    filterMap,
                                    tableSettingsBuilder,
                                    queryBuilder);
                            remaining.removeFirst();
                        }
                        case LIMIT -> {
                            checkTokenOrder(token, consumedTokens);
                            processLimit(
                                    keywordGroup,
                                    tableSettingsBuilder);
                            remaining.removeFirst();
                        }
                        case SHOW -> {
                            checkTokenOrder(token, consumedTokens);
                            final TableSettings parentTableSettings = tableSettingsBuilder.build();
                            visTableSettings = visualisationTokenConsumer
                                    .processVis(keywordGroup, parentTableSettings);
                            remaining.removeFirst();
                        }
                        default -> throw new TokenException(token, "Unexpected token");
                    }
                } else {
                    throw new TokenException(token, "Unexpected token");
                }

                consumedTokens.add(token.getTokenType());
            }

            // Ensure StreamId and EventId fields exist if there is no grouping.
            if (groupDepth == 0) {
                if (!addedFields.contains(SpecialColumns.RESERVED_ID)) {
                    tableSettingsBuilder.addColumns(SpecialColumns.RESERVED_ID_COLUMN);
                    addedFields.add(SpecialColumns.RESERVED_ID);
                }
                if (!addedFields.contains(SpecialColumns.RESERVED_STREAM_ID)) {
                    tableSettingsBuilder.addColumns(SpecialColumns.RESERVED_STREAM_ID_COLUMN);
                    addedFields.add(SpecialColumns.RESERVED_STREAM_ID);
                }
                if (!addedFields.contains(SpecialColumns.RESERVED_EVENT_ID)) {
                    tableSettingsBuilder.addColumns(SpecialColumns.RESERVED_EVENT_ID_COLUMN);
                    addedFields.add(SpecialColumns.RESERVED_EVENT_ID);
                }
            }

            // Add missing fields if needed.
            for (final AbstractToken token : additionalFields) {
                final String fieldName = token.getUnescapedText();
                if (!addedFields.contains(fieldName)) {
                    final String id = "__" + fieldName.replaceAll("\\s", "_") + "__";
                    tableSettingsBuilder.addColumns(createColumn(token,
                            id,
                            fieldName,
                            fieldName,
                            false,
                            true,
                            sortMap,
                            groupMap,
                            filterMap));
                }
            }

            final TableSettings tableSettings = tableSettingsBuilder
                    .extractValues(true)
                    .build();

            final ResultRequest tableResultRequest = new ResultRequest(TABLE_COMPONENT_ID,
                    Collections.singletonList(tableSettings),
                    null,
                    null,
                    null,
                    ResultStyle.TABLE,
                    Fetch.ALL);
            resultRequests.add(tableResultRequest);

            if (visTableSettings != null) {
                final List<TableSettings> tableSettingsList = new ArrayList<>();
                tableSettingsList.add(tableSettings);
                tableSettingsList.add(visTableSettings);
                final ResultRequest qlVisResultRequest = new ResultRequest(VIS_COMPONENT_ID,
                        tableSettingsList,
                        null,
                        null,
                        null,
                        ResultStyle.QL_VIS,
                        Fetch.ALL);
                resultRequests.add(qlVisResultRequest);
            }
        }
@param isLenient Ignores any additional child tokens found. This is to support getting just
                 the data source from a partially written query, which is likely to not be
                 a valid query. For example, you may type
                 <pre>{@code from View sel}</pre> and at this point do code completion on 'sel'
                 so if this method is called it will treat 'sel as an extra child.
stroom-query/stroom-query-common/src/main/java/stroom/query/language/SearchRequestFactory.java