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