com.google.zetasql.Analyzer Maven / Gradle / Ivy
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.google.zetasql;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.zetasql.LocalService.AnalyzeRequest;
import com.google.zetasql.LocalService.AnalyzeResponse;
import com.google.zetasql.LocalService.BuildSqlRequest;
import com.google.zetasql.LocalService.BuildSqlResponse;
import com.google.zetasql.LocalService.ExtractTableNamesFromNextStatementRequest;
import com.google.zetasql.LocalService.ExtractTableNamesFromNextStatementResponse;
import com.google.zetasql.LocalService.ExtractTableNamesFromStatementRequest;
import com.google.zetasql.LocalService.ExtractTableNamesFromStatementResponse;
import com.google.zetasql.resolvedast.ResolvedNodes.ResolvedExpr;
import com.google.zetasql.resolvedast.ResolvedNodes.ResolvedStatement;
import io.grpc.StatusRuntimeException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/** The Analyzer class provides static methods to analyze ZetaSQL statements or expressions. */
public class Analyzer implements Serializable {
final SimpleCatalog catalog;
final AnalyzerOptions options;
public Analyzer(AnalyzerOptions options, SimpleCatalog catalog) {
this.options = options;
this.catalog = catalog;
}
@CanIgnoreReturnValue // TODO: consider removing this?
public ResolvedStatement analyzeStatement(String sql) {
return analyzeStatement(sql, options, catalog);
}
@CanIgnoreReturnValue // TODO: consider removing this?
public static ResolvedStatement analyzeStatement(
String sql, AnalyzerOptions options, SimpleCatalog catalog) {
AnalyzeRequest.Builder request = AnalyzeRequest.newBuilder().setSqlStatement(sql);
FileDescriptorSetsBuilder fileDescriptorSetsBuilder =
AnalyzerHelper.serializeSimpleCatalog(catalog, options, request);
AnalyzeResponse response;
try {
response = Client.getStub().analyze(request.build());
} catch (StatusRuntimeException e) {
throw new SqlException(e);
}
return AnalyzerHelper.deserializeResolvedStatement(
catalog, fileDescriptorSetsBuilder, response);
}
public static ResolvedExpr analyzeExpression(
String expression, AnalyzerOptions options, SimpleCatalog catalog) {
AnalyzeRequest.Builder request = AnalyzeRequest.newBuilder().setSqlExpression(expression);
FileDescriptorSetsBuilder fileDescriptorSetsBuilder =
AnalyzerHelper.serializeSimpleCatalog(catalog, options, request);
AnalyzeResponse response;
try {
response = Client.getStub().analyze(request.build());
} catch (StatusRuntimeException e) {
throw new SqlException(e);
}
return AnalyzerHelper.deserializeResolvedExpression(
catalog, fileDescriptorSetsBuilder, response);
}
/**
* Renders expression as a sql string.
*
* Note, there is a bug that prevents DatePart enum from serializing correctly when @arg
* catalog is registered.
*
*
TODO: fix DatePart serializing in registered catalogs.
*/
public static String buildExpression(ResolvedExpr expression, SimpleCatalog catalog) {
Map registeredDescriptorPoolIds = new LinkedHashMap<>();
BuildSqlRequest.Builder request = BuildSqlRequest.newBuilder();
FileDescriptorSetsBuilder fileDescriptorSetsBuilder =
AnalyzerHelper.serializeSimpleCatalog(catalog, request, registeredDescriptorPoolIds);
AnyResolvedExprProto.Builder resolvedExpr = AnyResolvedExprProto.newBuilder();
expression.serialize(fileDescriptorSetsBuilder, resolvedExpr);
request.setResolvedExpression(resolvedExpr.build());
request.setDescriptorPoolList(
DescriptorPoolSerializer.createDescriptorPoolListWithRegisteredIds(
fileDescriptorSetsBuilder, registeredDescriptorPoolIds));
BuildSqlResponse response;
try {
response = Client.getStub().buildSql(request.build());
} catch (StatusRuntimeException e) {
throw new SqlException(e);
}
return response.getSql();
}
/**
* Renders statement as a sql string.
*
* Note, there is a bug that prevents DatePart enum from serializing correctly when @arg
* catalog is registered.
*
*
TODO: fix DatePart serializing in registered catalogs.
*/
public static String buildStatement(ResolvedStatement statement, SimpleCatalog catalog) {
BuildSqlRequest.Builder request = BuildSqlRequest.newBuilder();
Map registeredDescriptorPoolIds = new LinkedHashMap<>();
FileDescriptorSetsBuilder fileDescriptorSetsBuilder =
AnalyzerHelper.serializeSimpleCatalog(catalog, request, registeredDescriptorPoolIds);
AnyResolvedStatementProto.Builder resolvedStatement = AnyResolvedStatementProto.newBuilder();
statement.serialize(fileDescriptorSetsBuilder, resolvedStatement);
request.setResolvedStatement(resolvedStatement.build());
request.setDescriptorPoolList(
DescriptorPoolSerializer.createDescriptorPoolListWithRegisteredIds(
fileDescriptorSetsBuilder, registeredDescriptorPoolIds));
BuildSqlResponse response;
try {
response = Client.getStub().buildSql(request.build());
} catch (StatusRuntimeException e) {
throw new SqlException(e);
}
return response.getSql();
}
/**
* Analyze the statement syntax and extract the table names. This function is a one-of which does
* not require an instance of analyzer.
*
* @return List of table names. Every table name is a list of string segments, to retain original
* naming structure.
*/
public static List> extractTableNamesFromStatement(
String sql, AnalyzerOptions options) {
return extractTableNamesFromStatementInternal(sql, options, /*allowScript=*/ false);
}
@CanIgnoreReturnValue // TODO: consider removing this?
public static List> extractTableNamesFromStatement(String sql) {
return extractTableNamesFromStatement(sql, new AnalyzerOptions());
}
public static List> extractTableNamesFromScript(
String sql, AnalyzerOptions options) {
return extractTableNamesFromStatementInternal(sql, options, /*allowScript=*/ true);
}
private static List> extractTableNamesFromStatementInternal(
String sql, AnalyzerOptions options, boolean allowScript) {
ExtractTableNamesFromStatementRequest request =
ExtractTableNamesFromStatementRequest.newBuilder()
.setSqlStatement(sql)
.setOptions(options.getLanguageOptions().serialize())
.setAllowScript(allowScript)
.build();
ExtractTableNamesFromStatementResponse response;
try {
response = Client.getStub().extractTableNamesFromStatement(request);
} catch (StatusRuntimeException e) {
throw new SqlException(e);
}
ArrayList> result = new ArrayList<>(response.getTableNameCount());
for (ExtractTableNamesFromStatementResponse.TableName name : response.getTableNameList()) {
ArrayList nameList = new ArrayList<>(name.getTableNameSegmentCount());
nameList.addAll(name.getTableNameSegmentList());
result.add(nameList);
}
return result;
}
public ResolvedStatement analyzeNextStatement(ParseResumeLocation parseResumeLocation) {
return analyzeNextStatement(parseResumeLocation, options, catalog);
}
/**
* Analyze the next statement in parseResumeLocation, updating its bytePosition to the end of the
* statement.
*/
public static ResolvedStatement analyzeNextStatement(
ParseResumeLocation parseResumeLocation, AnalyzerOptions options, SimpleCatalog catalog) {
FileDescriptorSetsBuilder fileDescriptorSetsBuilder;
AnalyzeResponse response;
synchronized (parseResumeLocation) {
AnalyzeRequest.Builder request =
AnalyzeRequest.newBuilder().setParseResumeLocation(parseResumeLocation.serialize());
fileDescriptorSetsBuilder = AnalyzerHelper.serializeSimpleCatalog(catalog, options, request);
try {
response = Client.getStub().analyze(request.build());
} catch (StatusRuntimeException e) {
throw new SqlException(e);
}
parseResumeLocation.setBytePosition(response.getResumeBytePosition());
}
return AnalyzerHelper.deserializeResolvedStatement(
catalog, fileDescriptorSetsBuilder, response);
}
/**
* Extract the table names from the next statement in parseResumeLocation. This function is a
* one-off which does not require an analyzer instance.
*
* @return List of table names. Every table name is a list of string segments, to retain the
* original naming structure.
*/
public static List> extractTableNamesFromNextStatement(
ParseResumeLocation parseResumeLocation, AnalyzerOptions options) {
ExtractTableNamesFromNextStatementResponse response;
synchronized (parseResumeLocation) {
ExtractTableNamesFromNextStatementRequest request =
ExtractTableNamesFromNextStatementRequest.newBuilder()
.setParseResumeLocation(parseResumeLocation.serialize())
.setOptions(options.getLanguageOptions().serialize())
.build();
try {
response = Client.getStub().extractTableNamesFromNextStatement(request);
} catch (StatusRuntimeException e) {
throw new SqlException(e);
}
parseResumeLocation.setBytePosition(response.getResumeBytePosition());
}
ArrayList> result = new ArrayList<>(response.getTableNameCount());
for (ExtractTableNamesFromNextStatementResponse.TableName name : response.getTableNameList()) {
ArrayList nameList = new ArrayList<>(name.getTableNameSegmentCount());
nameList.addAll(name.getTableNameSegmentList());
result.add(nameList);
}
return result;
}
}