From e97c180272ee997514f2c76b2283127d07d76928 Mon Sep 17 00:00:00 2001 From: Nguyen Tan Anh Date: Tue, 17 Sep 2024 23:38:27 +0300 Subject: [PATCH] parameterized sql templates --- .../jdbc/config/JdbcConfiguration.java | 36 ++++++++++ .../jdbc/rest/sql/SQLTemplateController.java | 69 +++++++++++++++++++ api-rest/src/main/resources/application.yml | 2 + rdbms-support/pom.xml | 5 ++ .../db2rest/jdbc/JdbcOperationService.java | 15 ++-- .../db2rest/jdbc/core/DbOperationService.java | 6 +- .../core/service/JinJavaTemplateService.java | 50 ++++++++++++++ .../jdbc/core/service/SQLTemplateService.java | 10 +++ .../config/Db2RestConfigProperties.java | 2 + 9 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 api-rest/src/main/java/com/homihq/db2rest/jdbc/rest/sql/SQLTemplateController.java create mode 100644 rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/service/JinJavaTemplateService.java create mode 100644 rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/service/SQLTemplateService.java diff --git a/api-rest/src/main/java/com/homihq/db2rest/jdbc/config/JdbcConfiguration.java b/api-rest/src/main/java/com/homihq/db2rest/jdbc/config/JdbcConfiguration.java index 74b29161..0dfe646b 100644 --- a/api-rest/src/main/java/com/homihq/db2rest/jdbc/config/JdbcConfiguration.java +++ b/api-rest/src/main/java/com/homihq/db2rest/jdbc/config/JdbcConfiguration.java @@ -20,11 +20,13 @@ import com.homihq.db2rest.jdbc.rest.rpc.FunctionController; import com.homihq.db2rest.jdbc.rest.rpc.ProcedureController; import com.homihq.db2rest.jdbc.rest.schema.SchemaController; +import com.homihq.db2rest.jdbc.rest.sql.SQLTemplateController; import com.homihq.db2rest.jdbc.rest.update.UpdateController; import com.homihq.db2rest.jdbc.sql.SqlCreatorTemplate; import com.homihq.db2rest.jdbc.tsid.TSIDProcessor; import com.homihq.db2rest.multidb.DatabaseConnectionDetail; import com.homihq.db2rest.multidb.DatabaseProperties; +import com.hubspot.jinjava.Jinjava; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import lombok.RequiredArgsConstructor; @@ -34,6 +36,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; +import org.springframework.util.AntPathMatcher; import org.thymeleaf.spring6.SpringTemplateEngine; import javax.sql.DataSource; @@ -121,6 +124,16 @@ public SqlCreatorTemplate sqlCreatorTemplate(SpringTemplateEngine templateEngine } + @Bean + public Jinjava jinjava() { + return new Jinjava(); + } + + @Bean + public AntPathMatcher antPathMatcher() { + return new AntPathMatcher(); + } + //START ::: Processors @Bean public TSIDProcessor tsidProcessor() { @@ -243,6 +256,21 @@ public ProcedureService procedureService(JdbcManager jdbcManager) { return new JdbcProcedureService(jdbcManager); } + @Bean + public SQLTemplateService templateService( + Jinjava jinjava, + Db2RestConfigProperties db2RestConfigProperties, + DbOperationService dbOperationService, + JdbcManager jdbcManager + ) { + return new JinJavaTemplateService( + jinjava, + db2RestConfigProperties, + dbOperationService, + jdbcManager + ); + } + //END ::: Services //START ::: API @@ -322,6 +350,14 @@ public SchemaController schemaController(JdbcManager jdbcManager) { return new SchemaController(jdbcManager); } + @Bean + @ConditionalOnBean(SQLTemplateService.class) + public SQLTemplateController sqlTemplateController( + SQLTemplateService sqlTemplateService, + AntPathMatcher antPathMatcher + ) { + return new SQLTemplateController(sqlTemplateService, antPathMatcher); + } //END ::: API } diff --git a/api-rest/src/main/java/com/homihq/db2rest/jdbc/rest/sql/SQLTemplateController.java b/api-rest/src/main/java/com/homihq/db2rest/jdbc/rest/sql/SQLTemplateController.java new file mode 100644 index 00000000..6293f8b8 --- /dev/null +++ b/api-rest/src/main/java/com/homihq/db2rest/jdbc/rest/sql/SQLTemplateController.java @@ -0,0 +1,69 @@ +package com.homihq.db2rest.jdbc.rest.sql; + +import com.homihq.db2rest.jdbc.core.service.SQLTemplateService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.HandlerMapping; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.homihq.db2rest.jdbc.rest.RdbmsRestApi.VERSION; + +@Slf4j +@RestController +@RequiredArgsConstructor +@Tag(name = "Parameterized SQL Template ", description = "Details about schemas and tables") +public class SQLTemplateController { + private final SQLTemplateService sqlTemplateService; + private final AntPathMatcher antPathMatcher; + + @GetMapping(VERSION + "/{dbId}/sql/{fileName}/**") + public Object sqlTemplate(@PathVariable String dbId, + @PathVariable String fileName, + @RequestParam Map requestParams, + @RequestHeader Map requestHeaders, + @MatrixVariable Map matrixVariables, + HttpServletRequest request + ) throws IOException { + Map context = getContext(requestParams, requestHeaders, matrixVariables, request); + + String renderedSqlQuery = sqlTemplateService.renderTemplate(fileName, context); + + return sqlTemplateService.execute(renderedSqlQuery, dbId); + } + + private Map getContext( + Map requestParams, + Map requestHeaders, + Map matrixVariables, + HttpServletRequest request + ) { + Map context = new HashMap<>(); + context.put("params", requestParams); + context.put("headers", requestHeaders); + context.put("matrix", matrixVariables); + + var pathVariables = getContextFromPath(request); + context.put("paths", pathVariables); + + return context; + } + + private List getContextFromPath(HttpServletRequest request) { + String path = (String) request.getAttribute( + HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); + + String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); + + String userPathVariable = antPathMatcher.extractPathWithinPattern(bestMatchPattern, path); + + return List.of(userPathVariable.split("/")); + } +} diff --git a/api-rest/src/main/resources/application.yml b/api-rest/src/main/resources/application.yml index 409961ee..e14009b4 100644 --- a/api-rest/src/main/resources/application.yml +++ b/api-rest/src/main/resources/application.yml @@ -57,6 +57,8 @@ db2rest: enabled: ${MULTI_TENANCY_ENABLED:false} mode: ${MULTI_TENANCY_MODE:'NONE'} + templates: ${SQL_TEMPLATE_PATH} + logging: level: com.homihq.db2rest.rest: INFO diff --git a/rdbms-support/pom.xml b/rdbms-support/pom.xml index adddbb36..bc173401 100644 --- a/rdbms-support/pom.xml +++ b/rdbms-support/pom.xml @@ -50,6 +50,11 @@ compile + + com.hubspot.jinjava + jinjava + 2.7.2 + diff --git a/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/JdbcOperationService.java b/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/JdbcOperationService.java index 5acaa131..be906a1c 100644 --- a/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/JdbcOperationService.java +++ b/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/JdbcOperationService.java @@ -1,14 +1,14 @@ package com.homihq.db2rest.jdbc; +import com.homihq.db2rest.core.dto.CountResponse; +import com.homihq.db2rest.core.dto.CreateBulkResponse; +import com.homihq.db2rest.core.dto.CreateResponse; +import com.homihq.db2rest.core.dto.ExistsResponse; import com.homihq.db2rest.core.exception.GenericDataAccessException; import com.homihq.db2rest.jdbc.config.dialect.Dialect; import com.homihq.db2rest.jdbc.config.model.ArrayTypeValueHolder; -import com.homihq.db2rest.jdbc.core.DbOperationService; import com.homihq.db2rest.jdbc.config.model.DbTable; -import com.homihq.db2rest.core.dto.CreateBulkResponse; -import com.homihq.db2rest.core.dto.CreateResponse; -import com.homihq.db2rest.core.dto.CountResponse; -import com.homihq.db2rest.core.dto.ExistsResponse; +import com.homihq.db2rest.jdbc.core.DbOperationService; import com.homihq.db2rest.jdbc.core.SimpleRowMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -146,4 +146,9 @@ public CreateBulkResponse batchUpdate( return new CreateBulkResponse(updateCounts, null); } + + public Object query(NamedParameterJdbcTemplate namedParameterJdbcTemplate, String sql, Dialect dialect) { + return namedParameterJdbcTemplate + .query(sql, new SimpleRowMapper(dialect)); + } } diff --git a/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/DbOperationService.java b/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/DbOperationService.java index 6c266703..bebb6425 100644 --- a/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/DbOperationService.java +++ b/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/DbOperationService.java @@ -1,12 +1,11 @@ package com.homihq.db2rest.jdbc.core; import com.homihq.db2rest.core.dto.CountResponse; +import com.homihq.db2rest.core.dto.CreateBulkResponse; +import com.homihq.db2rest.core.dto.CreateResponse; import com.homihq.db2rest.core.dto.ExistsResponse; import com.homihq.db2rest.jdbc.config.dialect.Dialect; import com.homihq.db2rest.jdbc.config.model.DbTable; -import com.homihq.db2rest.core.dto.CreateBulkResponse; -import com.homihq.db2rest.core.dto.CreateResponse; -import com.homihq.db2rest.jdbc.dto.BindVariable; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import java.util.List; @@ -35,4 +34,5 @@ List> read(NamedParameterJdbcTemplate namedParameterJdbcTemp CreateBulkResponse batchUpdate(NamedParameterJdbcTemplate namedParameterJdbcTemplate, List> dataList, String sql); + Object query(NamedParameterJdbcTemplate namedParameterJdbcTemplate, String sql, Dialect dialect); } diff --git a/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/service/JinJavaTemplateService.java b/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/service/JinJavaTemplateService.java new file mode 100644 index 00000000..2750cf3f --- /dev/null +++ b/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/service/JinJavaTemplateService.java @@ -0,0 +1,50 @@ +package com.homihq.db2rest.jdbc.core.service; + +import com.homihq.db2rest.config.Db2RestConfigProperties; +import com.homihq.db2rest.jdbc.JdbcManager; +import com.homihq.db2rest.jdbc.core.DbOperationService; +import com.hubspot.jinjava.Jinjava; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; + +@Slf4j +@RequiredArgsConstructor +public class JinJavaTemplateService implements SQLTemplateService { + + private final Jinjava jinjava; + private final Db2RestConfigProperties db2RestConfigProperties; + private final DbOperationService dbOperationService; + private final JdbcManager jdbcManager; + + @Override + public String renderTemplate(String templateName, Map context) throws IOException { + log.debug("Rendering query from template {}", templateName); + String userTemplateLocation = db2RestConfigProperties.getTemplates(); + Path templatePath = Paths.get(userTemplateLocation, templateName + ".sql"); + if (!Files.exists(templatePath)) { + throw new IllegalArgumentException("Template not found: " + templateName); + } + try { + String templateContent = Files.readString(templatePath); + return jinjava.render(templateContent, context); + } catch (IOException ioe) { + throw new IOException("Could not read SQL template at: " + templatePath); + } + } + + @Override + public Object execute(String sql, String dbId) { + log.debug("Execute: {}", sql); + return dbOperationService.query( + jdbcManager.getNamedParameterJdbcTemplate(dbId), + sql, + jdbcManager.getDialect(dbId) + ); + } +} diff --git a/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/service/SQLTemplateService.java b/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/service/SQLTemplateService.java new file mode 100644 index 00000000..a2480856 --- /dev/null +++ b/rdbms-support/src/main/java/com/homihq/db2rest/jdbc/core/service/SQLTemplateService.java @@ -0,0 +1,10 @@ +package com.homihq.db2rest.jdbc.core.service; + +import java.io.IOException; +import java.util.Map; + +public interface SQLTemplateService { + String renderTemplate(String template, Map context) throws IOException; + + Object execute(String sql, String dbId); +} diff --git a/rest-common/src/main/java/com/homihq/db2rest/config/Db2RestConfigProperties.java b/rest-common/src/main/java/com/homihq/db2rest/config/Db2RestConfigProperties.java index d2e1d26a..83cc1d59 100644 --- a/rest-common/src/main/java/com/homihq/db2rest/config/Db2RestConfigProperties.java +++ b/rest-common/src/main/java/com/homihq/db2rest/config/Db2RestConfigProperties.java @@ -29,6 +29,8 @@ public class Db2RestConfigProperties { private DateTimeConfigProperties dateTime; + private String templates; + public boolean isAllSchema() { if(Objects.isNull(includeSchemas)) {