static SearchProtocol.SearchRequest convertFromQuery(Query query, int hits, String serverId, double requestTimeout) {
        var builder = SearchProtocol.SearchRequest.newBuilder().setHits(hits).setOffset(query.getOffset())
                .setTimeout((int) (requestTimeout * 1000));

        var documentDb = query.getModel().getDocumentDb();
        if (documentDb != null) {
            builder.setDocumentType(documentDb);
        }
        GrowableByteBuffer scratchPad = threadLocalBuffer.get();
        builder.setQueryTreeBlob(serializeQueryTree(query.getModel().getQueryTree(), scratchPad));

        if (query.getGroupingSessionCache() || query.getRanking().getQueryCache()) {
            // TODO verify that the session key is included whenever rank properties would have been
            builder.setSessionKey(query.getSessionId(serverId).toString());
        }
        if (query.properties().getBoolean(Model.ESTIMATE)) {
            builder.setHits(0);
        }
        if (GroupingExecutor.hasGroupingList(query)) {
            List<Grouping> groupingList = GroupingExecutor.getGroupingList(query);
            scratchPad.clear();
            BufferSerializer gbuf = new BufferSerializer(scratchPad);
            gbuf.putInt(null, groupingList.size());
            for (Grouping g : groupingList) {
                g.serialize(gbuf);
            }
            gbuf.getBuf().flip();
            builder.setGroupingBlob(ByteString.copyFrom(gbuf.getBuf().getByteBuffer()));
        }
        if (query.getGroupingSessionCache()) {
            builder.setCacheGrouping(true);
        }

        int traceLevel = getTraceLevelForBackend(query);
        builder.setTraceLevel(traceLevel);
        builder.setProfileDepth(query.getTrace().getProfileDepth());
        if (traceLevel > 0) {
            mergeToSearchRequestFromProfiling(query.getTrace().getProfiling(), builder);
        }

        mergeToSearchRequestFromRanking(query.getRanking(), scratchPad, builder);

        return builder.build();
    }
@Test
    void testQuerySerialization() {
        CompiledQueryProfileRegistry registry = new QueryProfileXMLReader().read("src/test/java/com/yahoo/search/query/profile/config/test/tensortypes").compile();
        Query query = new Query.Builder().setQueryProfile(registry.getComponent("profile1"))
                .setRequest("?query=test&ranking.features.query(tensor_1)=[1.200]")
                .build();

        SearchProtocol.SearchRequest request1 = ProtobufSerialization.convertFromQuery(query, 9, "serverId", 0.5);
        assertEquals(9, request1.getHits());
        assertEquals(0, request1.getRankPropertiesCount());
        assertEquals(0, request1.getTensorRankPropertiesCount());
        assertEquals(0, request1.getFeatureOverridesCount());
        assertEquals(2, request1.getTensorFeatureOverridesCount());
        assertEquals("\"\\001\\001\\003key\\001\\rpre_key1_post?\\360\\000\\000\\000\\000\\000\\000\"",
                contentsOf(request1.getTensorFeatureOverrides(0).getValue()));
        assertEquals("\"\\006\\001\\001\\001x\\001?\\231\\231\\232\"",
                contentsOf(request1.getTensorFeatureOverrides(1).getValue()));
        assertFalse(request1.hasProfiling());

        query.prepare(); // calling prepare() moves "overrides" to "features" - content stays the same
        SearchProtocol.SearchRequest request2 = ProtobufSerialization.convertFromQuery(query, 9, "serverId", 0.5);
        assertEquals(9, request2.getHits());
        assertEquals(0, request2.getRankPropertiesCount());
        assertEquals(2, request2.getTensorRankPropertiesCount());
        assertEquals("\"\\001\\001\\003key\\001\\rpre_key1_post?\\360\\000\\000\\000\\000\\000\\000\"",
                contentsOf(request2.getTensorRankProperties(0).getValue()));
        assertEquals("\"\\006\\001\\001\\001x\\001?\\231\\231\\232\"",
                contentsOf(request2.getTensorRankProperties(1).getValue()));
        assertEquals(0, request2.getFeatureOverridesCount());
        assertEquals(0, request2.getTensorFeatureOverridesCount());
    }