From 85705d0ef7414cdcabfff38d33da51856e94c8da Mon Sep 17 00:00:00 2001
From: Moritz Staudinger <moritz.staudinger@tuwien.ac.at>
Date: Tue, 1 Mar 2022 17:24:25 +0100
Subject: [PATCH] added mapping for escaped strings, allow re-execution again

Former-commit-id: 012d907b59d9d599afe104eb04a7e2978607eecd
---
 .../at/tuwien/endpoint/QueryEndpoint.java     | 10 ++-
 .../QueryEndpointIntegrationTest.java         |  3 +-
 .../java/at/tuwien/mapper/QueryMapper.java    | 10 +++
 .../java/at/tuwien/service/QueryService.java  | 20 ++++++
 .../tuwien/service/impl/QueryServiceImpl.java | 64 ++++++++++++++-----
 5 files changed, 84 insertions(+), 23 deletions(-)

diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
index b9149086af..c52f2b6ea3 100644
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
@@ -10,6 +10,7 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
 import lombok.extern.log4j.Log4j2;
+import net.sf.jsqlparser.JSQLParserException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
+import java.sql.SQLException;
 import java.time.Instant;
 
 @Log4j2
@@ -101,14 +103,10 @@ public class QueryEndpoint {
                                                     @NotNull @PathVariable("databaseId") Long databaseId,
                                                     @NotNull @PathVariable("queryId") Long queryId)
             throws QueryStoreException, QueryNotFoundException, DatabaseNotFoundException, ImageNotSupportedException,
-            TableNotFoundException, QueryMalformedException, ContainerNotFoundException {
+            TableNotFoundException, QueryMalformedException, ContainerNotFoundException, SQLException, JSQLParserException {
         final Query query = storeService.findOne(id, databaseId, queryId);
         log.debug(query.toString());
-        final QueryDto queryDto = queryMapper.queryToQueryDto(query);
-        log.debug(queryDto.toString());
-        final ExecuteStatementDto statement = queryMapper.queryDtoToExecuteStatementDto(queryDto);
-        log.debug(statement.toString());
-        final QueryResultDto result = queryService.execute(id, databaseId, statement);
+        final QueryResultDto result = queryService.reExecute(id, databaseId, query);
         result.setId(queryId);
         return ResponseEntity.status(HttpStatus.ACCEPTED)
                 .body(result);
diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/QueryEndpointIntegrationTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/QueryEndpointIntegrationTest.java
index c199e8459f..3b3db9900d 100644
--- a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/QueryEndpointIntegrationTest.java
+++ b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/QueryEndpointIntegrationTest.java
@@ -18,6 +18,7 @@ import com.github.dockerjava.api.exception.NotModifiedException;
 import com.github.dockerjava.api.model.Bind;
 import com.github.dockerjava.api.model.Network;
 import lombok.extern.log4j.Log4j2;
+import net.sf.jsqlparser.JSQLParserException;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
@@ -131,7 +132,7 @@ public class QueryEndpointIntegrationTest extends BaseUnitTest {
     @Test
     public void reExecute_succeeds() throws TableNotFoundException, QueryStoreException, QueryMalformedException,
             DatabaseNotFoundException, ImageNotSupportedException, QueryNotFoundException, InterruptedException,
-            SQLException, ContainerNotFoundException {
+            SQLException, ContainerNotFoundException, JSQLParserException {
         final QueryResultDto result = QueryResultDto.builder()
                 .id(QUERY_1_ID)
                 .result(List.of(Map.of("MinTemp", 13.4, "Rainfall", 0.6, "id", 1)))
diff --git a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java
index 3ca553167e..65b8491531 100644
--- a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java
+++ b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java
@@ -295,4 +295,14 @@ public interface QueryMapper {
                 throw new IllegalArgumentException("Column type not known");
         }
     }
+
+    @Named("EscapedString")
+    default String stringToEscapedString(String name) throws ImageNotSupportedException {
+        log.debug("StringToEscapedString: {}",name);
+        if(name!=null && !name.startsWith("`") && !name.endsWith("`")) {
+            return "`"+name+"`";
+        }
+        return name;
+    }
+
 }
diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java b/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java
index 268ca3c68b..186d6d0c92 100644
--- a/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java
+++ b/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java
@@ -5,9 +5,12 @@ import at.tuwien.api.database.query.ImportDto;
 import at.tuwien.api.database.query.QueryResultDto;
 import at.tuwien.api.database.table.TableCsvDto;
 import at.tuwien.exception.*;
+import at.tuwien.querystore.Query;
+import net.sf.jsqlparser.JSQLParserException;
 import org.springframework.stereotype.Service;
 
 import java.math.BigInteger;
+import java.sql.SQLException;
 import java.time.Instant;
 
 @Service
@@ -29,6 +32,23 @@ public interface QueryService {
     QueryResultDto execute(Long containerId, Long databaseId, ExecuteStatementDto query) throws TableNotFoundException,
             QueryStoreException, QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException;
 
+    /**
+     * Re-Executes an arbitrary query on the database container. We allow the user to only view the data, therefore the
+     * default "mariadb" user is allowed read-only access "SELECT".
+     *
+     * @param databaseId The database id.
+     * @param query      The query.
+     * @return The result.
+     * @throws TableNotFoundException
+     * @throws QueryStoreException
+     * @throws QueryMalformedException
+     * @throws DatabaseNotFoundException
+     * @throws ImageNotSupportedException
+     */
+    QueryResultDto reExecute(Long containerId, Long databaseId, Query query) throws TableNotFoundException,
+            QueryStoreException, QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, SQLException, JSQLParserException;
+
+
     /**
      * Select all data known in the database-table id tuple at a given time and return a page of specific size, using
      * Instant to better abstract time concept (JDK 8) from SQL. We use the "mariadb" user for this.
diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java
index 2cc4a3a9b1..a34ea76846 100644
--- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java
+++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java
@@ -3,7 +3,6 @@ package at.tuwien.service.impl;
 import at.tuwien.InsertTableRawQuery;
 import at.tuwien.api.database.query.ExecuteStatementDto;
 import at.tuwien.api.database.query.ImportDto;
-import at.tuwien.api.database.query.QueryDto;
 import at.tuwien.api.database.query.QueryResultDto;
 import at.tuwien.api.database.table.TableCsvDto;
 import at.tuwien.entities.database.Database;
@@ -16,7 +15,6 @@ import at.tuwien.repository.jpa.TableColumnRepository;
 import at.tuwien.service.*;
 import lombok.extern.log4j.Log4j2;
 import net.sf.jsqlparser.JSQLParserException;
-import net.sf.jsqlparser.expression.Expression;
 import net.sf.jsqlparser.parser.CCJSqlParserManager;
 import net.sf.jsqlparser.statement.Statement;
 import net.sf.jsqlparser.statement.select.*;
@@ -93,7 +91,7 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
         /* map the result to the tables (with respective columns) from the statement metadata */
         final List<TableColumn> columns = parseColumns(databaseId, statement);
         final QueryResultDto result = queryMapper.resultListToQueryResultDto(columns, query.getResultList());
-        /* Ssave query in the querystore */
+        /* Save query in the querystore */
         Query q = storeService.insert(containerId, databaseId, result, statement, i);
         result.setId(q.getId());
         session.close();
@@ -102,6 +100,43 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
         return result;
     }
 
+    @Override
+    public QueryResultDto reExecute(Long containerId, Long databaseId, Query query) throws TableNotFoundException, QueryStoreException, QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, SQLException, JSQLParserException {
+        /* find */
+        final Database database = databaseService.find(databaseId);
+        if (!database.getContainer().getImage().getRepository().equals("mariadb")) {
+            throw new ImageNotSupportedException("Currently only MariaDB is supported");
+        }
+        /* run query */
+        final long startSession = System.currentTimeMillis();
+        final SessionFactory factory = getSessionFactory(database);
+        final Session session = factory.openSession();
+        log.debug("opened hibernate session in {} ms", System.currentTimeMillis() - startSession);
+        session.beginTransaction();
+        /* prepare the statement */
+        Instant i = Instant.now();
+        final NativeQuery<?> nativeQuery = session.createSQLQuery(queryMapper.queryToRawTimestampedQuery(query.getQuery(), database, query.getExecution()));
+        final int affectedTuples;
+        try {
+            log.debug("execute raw view-only query {}", query);
+            affectedTuples = nativeQuery.executeUpdate();
+            log.info("Execution on database id {} affected {} rows", databaseId, affectedTuples);
+            session.getTransaction()
+                    .commit();
+        } catch (SQLGrammarException e) {
+            session.close();
+            factory.close();
+            throw new QueryMalformedException("Query not valid for this database", e);
+        }
+        /* map the result to the tables (with respective columns) from the statement metadata */
+        final List<TableColumn> columns = parseColumns(query, database);
+        final QueryResultDto result = queryMapper.resultListToQueryResultDto(columns, nativeQuery.getResultList());
+        /* Save query in the querystore */
+        session.close();
+        factory.close();
+        return result;
+    }
+
     @Override
     @Transactional
     public QueryResultDto findAll(Long containerId, Long databaseId, Long tableId, Instant timestamp, Long page,
@@ -270,9 +305,9 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
         return columns;
     }
 
-    private Query parse(Query query, Database database) throws SQLException, ImageNotSupportedException, JSQLParserException {
-        Instant ts = Instant.now();
-        query.setExecution(ts);
+    private List<TableColumn> parseColumns(Query query, Database database) throws SQLException, ImageNotSupportedException, JSQLParserException {
+        final List<TableColumn> columns = new ArrayList<>();
+
         CCJSqlParserManager parserRealSql = new CCJSqlParserManager();
         Statement statement = parserRealSql.parse(new StringReader(query.getQuery()));
         log.debug("Given query {}", query.getQuery());
@@ -298,7 +333,7 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
                 boolean i = false;
                 log.debug("from item iterated through: {}", f);
                 for(Table t : database.getTables()) {
-                    if(f.toString().equals(t.getInternalName()) || f.toString().equals(t.getName())) {
+                    if(queryMapper.stringToEscapedString(f.toString()).equals(queryMapper.stringToEscapedString(t.getInternalName()))) {
                         allColumns.addAll(t.getColumns());
                         i=false;
                         break;
@@ -306,13 +341,14 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
                     i = true;
                 }
                 if(i) {
-                    throw new JSQLParserException("Table "+f.toString() + " does not exist");
+                    throw new JSQLParserException("Table "+queryMapper.stringToEscapedString(f.toString())+ " does not exist");
                 }
             }
 
             //Checking if all columns exist
             for(SelectItem s : selectItems) {
-                String select = s.toString();
+                String select = queryMapper.stringToEscapedString(s.toString());
+                log.debug(select);
                 if(select.trim().equals("*")) {
                     log.debug("Please do not use * to query data");
                     continue;
@@ -325,8 +361,9 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
                 boolean i = false;
                 for(TableColumn tc : allColumns ) {
                     log.debug("{},{},{}", tc.getInternalName(), tc.getName(), s);
-                    if(select.equals(tc.getInternalName()) || select.toString().equals(tc.getName())) {
+                    if(select.equals(queryMapper.stringToEscapedString(tc.getInternalName()))) {
                         i=false;
+                        columns.add(tc);
                         break;
                     }
                     i = true;
@@ -335,12 +372,7 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService
                     throw new JSQLParserException("Column "+s.toString() + " does not exist");
                 }
             }
-            //TODO Future work
-            if(ps.getWhere() != null) {
-                Expression where = ps.getWhere();
-                log.debug("Where clause: {}", where);
-            }
-            return query;
+            return columns;
         }
         else {
             throw new JSQLParserException("SQL Query is not a SELECT statement - please only use SELECT statements");
-- 
GitLab