Repository: ad-freiburg/qlever (language: python)

Issue:
`application/sparql-results+json` query result is wrong for`SELECT` with `OFFSET`
This error occours for `SELECT` queries with a non-zero `OFFSET` and the result format `application/sparql-results+json`. The result json has an extra `,` before the first binding. AFAIK this was introduced with #1438 and the bug is located [here](https://github.com/ad-freiburg/qlever/blob/18d102f807f84d18c5b584577c1b3246f2086747/src/engine/ExportQueryExecutionTrees.cpp#L629-L637) in `ExportQueryExecutionTrees.cpp`.

Example:
```sparql
REFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?element_number ?label WHERE {
   ?v rdf:type <http://vvz.tf.uni-freiburg.de/schema#Veranstaltung> .
   ?v <http://vvz.tf.uni-freiburg.de/prop/element_number> ?element_number .
 }
OFFSET 1 LIMIT 1
```
leads to
```json
{"head":{"vars":["element_number","label"]},"results":{"bindings":[,{"element_number":{"type":"literal","value":"11LE68V-5370 PO 2021"}}]}}
```


Discussion / hints:

Fix patch:
diff --git a/src/engine/ExportQueryExecutionTrees.cpp b/src/engine/ExportQueryExecutionTrees.cpp
index 391311701..873bd21ae 100644
--- a/src/engine/ExportQueryExecutionTrees.cpp
+++ b/src/engine/ExportQueryExecutionTrees.cpp
@@ -626,13 +626,15 @@ ad_utility::streams::stream_generator ExportQueryExecutionTrees::
     return binding.dump();
   };
 
+  bool isFirstRow = true;
   for (const auto& [idTable, range] : getRowIndices(limitAndOffset, *result)) {
     for (uint64_t i : range) {
-      if (i != 0) [[likely]] {
+      if (!isFirstRow) [[likely]] {
         co_yield ",";
       }
       co_yield getBinding(idTable, i);
       cancellationHandle->throwIfCancelled();
+      isFirstRow = false;
     }
   }
 


Test patch:
diff --git a/test/ExportQueryExecutionTreesTest.cpp b/test/ExportQueryExecutionTreesTest.cpp
index ddef90a02..69c4e7d8d 100644
--- a/test/ExportQueryExecutionTreesTest.cpp
+++ b/test/ExportQueryExecutionTreesTest.cpp
@@ -1053,6 +1053,53 @@ TEST(ExportQueryExecutionTrees, MultipleVariables) {
   runSelectQueryTestCase(testCaseMultipleVariables);
 }
 
+// ____________________________________________________________________________
+TEST(ExportQueryExecutionTrees, LimitOffset) {
+  std::string kg = "<a> <b> <c> . <d> <e> <f> . <g> <h> <i> . <j> <k> <l>";
+  std::string objectQuery =
+      "SELECT ?s WHERE { ?s ?p ?o } ORDER BY ?s LIMIT 2 OFFSET 1";
+  std::string expectedXml = makeXMLHeader({"s"}) +
+                            R"(
+  <result>
+    <binding name="s"><uri>d</uri></binding>
+  </result>
+  <result>
+    <binding name="s"><uri>g</uri></binding>
+  </result>)" + xmlTrailer;
+  TestCaseSelectQuery testCaseLimitOffset{
+      kg, objectQuery, 2,
+      // TSV
+      "?s\n"
+      "<d>\n"
+      "<g>\n",
+      // CSV
+      "s\n"
+      "d\n"
+      "g\n",
+      []() {
+        nlohmann::json j;
+        j.push_back(std::vector{
+            "<d>"s,
+        });
+        j.push_back(std::vector{
+            "<g>"s,
+        });
+        return j;
+      }(),
+      []() {
+        nlohmann::json j;
+        j["head"]["vars"].push_back("s");
+        auto& bindings = j["results"]["bindings"];
+        bindings.emplace_back();
+        bindings.back()["s"] = makeJSONBinding(std::nullopt, "uri", "d");
+        bindings.emplace_back();
+        bindings.back()["s"] = makeJSONBinding(std::nullopt, "uri", "g");
+        return j;
+      }(),
+      expectedXml};
+  runSelectQueryTestCase(testCaseLimitOffset);
+}
+
 // ____________________________________________________________________________
 TEST(ExportQueryExecutionTrees, BinaryExport) {
   std::string kg = "
 <p> 31 . 
 <o> 42";