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 }
|