Skip to content

Commit

Permalink
GoogleSql class
Browse files Browse the repository at this point in the history
  • Loading branch information
fluentfuture committed Nov 29, 2023
1 parent c8d6ac5 commit f07afbc
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 11 deletions.
61 changes: 61 additions & 0 deletions mug-guava/src/main/java/com/google/mu/safesql/GoogleSql.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.google.mu.safesql;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

import com.google.errorprone.annotations.CompileTimeConstant;
import com.google.mu.util.StringFormat;

/** Facade class providing {@link SafeQuery} templates for GoogleSQL. */
public final class GoogleSql {
private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSS]");
private static final StringFormat DATE_TIME_EXPRESSION =
new StringFormat("DATETIME('{time}', '{zone}')");
private static final StringFormat TIMESTAMP_EXPRESSION =
new StringFormat("TIMESTAMP('{time}', '{zone}')");
private static final StringFormat DATE_EXPRESSION =
new StringFormat("DATE({year}, {month}, {day})");
private static final ZoneId GOOGLE_ZONE_ID = ZoneId.of("America/Los_Angeles");


/**
* Similar to {@link SafeQuery#template}, except {@link Instant} are translated to `TIMESTAMP()` GoogleSql function,
* {@link ZonedDateTime} are translated to `DTETIME()` GoogleSql function, and {@link LocalDate} are translated
* to `DATE()` GoogleSql function.
*/
public static StringFormat.To<SafeQuery> template(@CompileTimeConstant String formatString) {
return SafeQuery.template(formatString, value -> {
if (value instanceof Instant) {
return timestampExpression((Instant) value);
}
if (value instanceof ZonedDateTime) {
return dateTimeExpression((ZonedDateTime) value);
}
if (value instanceof LocalDate) {
return dateExpression((LocalDate) value);
}
throw new IllegalArgumentException("Unsupported argument type: " + value.getClass().getName());
});
}


private static String dateTimeExpression(ZonedDateTime dateTime) {
return DATE_TIME_EXPRESSION.format(
dateTime.toLocalDateTime().format(LOCAL_DATE_TIME_FORMATTER), dateTime.getZone());
}

private static String dateExpression(LocalDate date) {
return DATE_EXPRESSION.format(date.getYear(), date.getMonthValue(), date.getDayOfMonth());
}

private static String timestampExpression(Instant instant) {
return TIMESTAMP_EXPRESSION.format(
instant.atZone(GOOGLE_ZONE_ID).toLocalDateTime().format(LOCAL_DATE_TIME_FORMATTER), GOOGLE_ZONE_ID);
}

private GoogleSql() {}
}
28 changes: 17 additions & 11 deletions mug-guava/src/main/java/com/google/mu/safesql/SafeQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static java.util.stream.Collectors.mapping;

import java.util.Iterator;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -115,16 +116,23 @@ public static SafeQuery of(@CompileTimeConstant String query) {
* <li>Iterable
* </ul>
*/
public static StringFormat.To<SafeQuery> template(@CompileTimeConstant String template) {
public static StringFormat.To<SafeQuery> template(@CompileTimeConstant String formatString) {
return template(formatString, value -> {
throw new IllegalArgumentException("Unsupported argument type: " + value.getClass().getName());
});
}

static StringFormat.To<SafeQuery> template(
@CompileTimeConstant String formatString, Function<Object, String> defaultConverter) {
return StringFormat.template(
template,
formatString,
(fragments, placeholders) -> {
Iterator<String> it = fragments.iterator();
return new SafeQuery(
placeholders
.collect(
new StringBuilder(),
(b, p, v) -> b.append(it.next()).append(fillInPlaceholder(p, v)))
(b, p, v) -> b.append(it.next()).append(fillInPlaceholder(p, v, defaultConverter)))
.append(it.next())
.toString());
});
Expand All @@ -149,7 +157,8 @@ public int hashCode() {
return query.hashCode();
}

private static String fillInPlaceholder(Substring.Match placeholder, Object value) {
private static String fillInPlaceholder(
Substring.Match placeholder, Object value, Function<Object, String> byDefault) {
validatePlaceholder(placeholder);
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
Expand All @@ -175,7 +184,8 @@ private static String fillInPlaceholder(Substring.Match placeholder, Object valu
if (placeholder.isImmediatelyBetween("`", "`")) {
return backquoted(placeholder, value);
}
return unquoted(placeholder, value);
String result = unquoted(placeholder, value);
return result == null ? byDefault.apply(value) : result;
}

private static String unquoted(Substring.Match placeholder, Object value) {
Expand All @@ -200,7 +210,7 @@ private static String unquoted(Substring.Match placeholder, Object value) {
+ "subqueries must be wrapped in another SafeQuery object;\n"
+ "and string literals must be quoted like '%s'",
TRUSTED_SQL_TYPE_NAME, placeholder);
throw new IllegalArgumentException("Unsupported argument type: " + value.getClass().getName());
return null;
}

private static String quotedBy(char quoteChar, Substring.Match placeholder, Object value) {
Expand Down Expand Up @@ -232,11 +242,7 @@ private static String backquoted(Substring.Match placeholder, Object value) {
}

private static boolean isTrusted(Object value) {
return value instanceof SafeQuery
|| value
.getClass()
.getName()
.equals(TRUSTED_SQL_TYPE_NAME);
return value instanceof SafeQuery || value.getClass().getName().equals(TRUSTED_SQL_TYPE_NAME);
}

private static void validatePlaceholder(Substring.Match placeholder) {
Expand Down

0 comments on commit f07afbc

Please sign in to comment.