MicronautBigQueryExtensions.java
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2020-2022 Agorapulse.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     https://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package com.agorapulse.micronaut.bigquery.groovy;
019 
020 import com.agorapulse.micronaut.bigquery.BigQueryService;
021 import com.agorapulse.micronaut.bigquery.ParameterizedSql;
022 import groovy.lang.Closure;
023 import groovy.lang.GString;
024 import groovy.transform.stc.ClosureParams;
025 import groovy.transform.stc.FromString;
026 import io.reactivex.Flowable;
027 import space.jasan.support.groovy.closure.FunctionWithDelegate;
028 
029 import java.util.LinkedHashMap;
030 import java.util.List;
031 import java.util.Map;
032 import java.util.Optional;
033 import java.util.regex.Pattern;
034 import java.util.stream.Collectors;
035 import java.util.stream.Stream;
036 
037 public class MicronautBigQueryExtensions {
038 
039     private static final List<Pattern> PLACEHOLDERS_ALLOWED_AFTER_KEYWORD = Stream.of("where""on""set""values")
040         .map(keyword -> Pattern.compile(".*\\s" + keyword + "\\s.*", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL))
041         .collect(Collectors.toList());
042 
043     /**
044      * Runs a SQL query against the BigQuery warehouse and map the results into an object.
045      @param sql the SQL query {@link GString} which is automatically turning its values into the named parameters
046      @param builder the closure mapping the result into an object
047      @param <T> type of the result objects
048      @return the flowable of objects mapped using the builder
049      */
050     public static <T> Flowable<T> query(
051         BigQueryService self,
052         GString sql,
053         @ClosureParams(value = FromString.class, options = "com.agorapulse.micronaut.bigquery.RowResult"Closure<T> builder
054     ) {
055         ParameterizedSql parameterizedSql = from(self, sql);
056         return self.query(parameterizedSql.getNamedParameters(), parameterizedSql.getSql(), FunctionWithDelegate.create(builder));
057     }
058 
059     /**
060      * Runs a SQL query against the BigQuery warehouse and map the results into an object.
061      @param namedParameters the named parameters for the SQL query
062      @param sql the SQL query, must contain <code>@</code> as named parameter prefix
063      @param builder the closure mapping the result into an object
064      @param <T> type of the result objects
065      @return the flowable of objects mapped using the builder
066      */
067     public static <T> Flowable<T> query(
068         BigQueryService self,
069         Map<String, ?> namedParameters,
070         String sql,
071         @ClosureParams(value = FromString.class, options = "com.agorapulse.micronaut.bigquery.RowResult"Closure<T> builder
072     ) {
073         return self.query(namedParameters, sql, FunctionWithDelegate.create(builder));
074     }
075 
076     /**
077      * Run a SQL query against the BigQuery warehouse and map the results into an single object if present.
078      @param sql the SQL query {@link GString} which is automatically turning its values into the named parameters
079      @param builder the closure mapping the result into an object
080      @param <T> type of the result objects
081      @return the optional holding the first returned result or an empty optinal
082      */
083     public static <T> Optional<T> querySingle(
084         BigQueryService self,
085         GString sql,
086         @ClosureParams(value = FromString.class, options = "com.agorapulse.micronaut.bigquery.RowResult"Closure<T> builder
087     ) {
088         ParameterizedSql parameterizedSql = from(self, sql);
089         return self.querySingle(parameterizedSql.getNamedParameters(), parameterizedSql.getSql(), FunctionWithDelegate.create(builder));
090     }
091 
092     /**
093      * Run a SQL query against the BigQuery warehouse and map the results into an single object if present.
094      @param namedParameters the named parameters for the SQL query
095      @param sql the SQL query, must contain <code>@</code> as named parameter prefix
096      @param builder the closure mapping the result into an object
097      @param <T> type of the result objects
098      @return the optional holding the first returned result or an empty optinal
099      */
100     public static <T> Optional<T> querySingle(
101         BigQueryService self,
102         Map<String, ?> namedParameters,
103         String sql,
104         @ClosureParams(value = FromString.class, options = "com.agorapulse.micronaut.bigquery.RowResult"Closure<T> builder
105     ) {
106         return self.querySingle(namedParameters, sql, FunctionWithDelegate.create(builder));
107     }
108 
109     /**
110      * Runs a SQL statement against the BigQuery warehouse.
111      @param sql the SQL query {@link GString} which is automatically turning its values into the named parameters
112      */
113     public static void execute(BigQueryService self, GString sql) {
114         ParameterizedSql preparedSql = from(self, sql);
115         self.execute(preparedSql.getNamedParameters(), preparedSql.getSql());
116     }
117 
118     private static ParameterizedSql from(BigQueryService service, GString gString) {
119         StringBuilder builder = new StringBuilder();
120         Map<String, Object> namedParameters = new LinkedHashMap<>();
121         for (int i = 0; i < gString.getStrings().length; i++){
122             builder.append(gString.getStrings()[i]);
123             String current = builder.toString().toLowerCase();
124             if (i != gString.getStrings().length - 1) {
125                 if (PLACEHOLDERS_ALLOWED_AFTER_KEYWORD.stream().anyMatch(keyword -> keyword.matcher(current).matches())) {
126                     String varName = "var" + i;
127                     builder.append("@").append(varName);
128                     namedParameters.put(varName, service.convertIfNecessary(gString.getValues()[i]));
129                 else {
130                     builder.append(gString.getValues()[i]);
131                 }
132             }
133         }
134 
135         return ParameterizedSql.from(namedParameters, builder.toString());
136     }
137 
138 }