Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: reduce parsing of sql string #79

Merged
merged 2 commits into from
Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class ListCommand extends Command {
+ " pg_catalog\\.pg_encoding_to_char\\(d\\.encoding\\) as \"Encoding\",\n"
+ " pg_catalog\\.array_to_string\\(d\\.datacl, '\\\\n'\\) AS \"Access privileges\"\n"
+ "FROM pg_catalog\\.pg_database d\n.*\n?"
+ "ORDER BY 1;$");
+ "ORDER BY 1;?$");

private static final String OUTPUT_QUERY = "SELECT '%s' AS Name;";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
import com.google.cloud.spanner.connection.AutocommitDmlMode;
import com.google.cloud.spanner.connection.Connection;
import com.google.cloud.spanner.connection.StatementResult.ResultType;
Expand All @@ -30,7 +31,6 @@
import com.google.cloud.spanner.pgadapter.parsers.copy.TokenMgrError;
import com.google.cloud.spanner.pgadapter.utils.MutationWriter;
import com.google.cloud.spanner.pgadapter.utils.MutationWriter.CopyTransactionMode;
import com.google.cloud.spanner.pgadapter.utils.StatementParser;
import com.google.common.base.Strings;
import com.google.spanner.v1.TypeCode;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -61,11 +61,9 @@ public class CopyStatement extends IntermediateStatement {
private Future<Long> updateCount;
private final ExecutorService executor = Executors.newSingleThreadExecutor();

public CopyStatement(OptionsMetadata options, String sql, Connection connection) {
super(options, sql);
this.sql = sql;
this.command = StatementParser.parseCommand(sql);
this.connection = connection;
public CopyStatement(
OptionsMetadata options, ParsedStatement parsedStatement, Connection connection) {
super(options, parsedStatement, connection);
}

@Override
Expand Down Expand Up @@ -332,7 +330,7 @@ private CopyTransactionMode getTransactionMode() {

private void parseCopyStatement() throws Exception {
try {
parse(sql, this.options);
parse(parsedStatement.getSqlWithoutComments(), this.options);
} catch (Exception | TokenMgrError e) {
throw SpannerExceptionFactory.newSpannerException(
ErrorCode.INVALID_ARGUMENT, "Invalid COPY statement syntax: " + e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
import com.google.cloud.spanner.connection.Connection;
import com.google.cloud.spanner.pgadapter.metadata.DescribeMetadata;
import com.google.cloud.spanner.pgadapter.metadata.DescribePortalMetadata;
Expand All @@ -38,9 +39,10 @@ public class IntermediatePortalStatement extends IntermediatePreparedStatement {
protected List<Short> parameterFormatCodes;
protected List<Short> resultFormatCodes;

public IntermediatePortalStatement(OptionsMetadata options, String sql, Connection connection) {
super(options, sql, connection);
this.statement = Statement.of(sql);
public IntermediatePortalStatement(
OptionsMetadata options, ParsedStatement parsedStatement, Connection connection) {
super(options, parsedStatement, connection);
this.statement = Statement.of(parsedStatement.getSqlWithoutComments());
this.parameterFormatCodes = new ArrayList<>();
this.resultFormatCodes = new ArrayList<>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
import com.google.cloud.spanner.connection.Connection;
import com.google.cloud.spanner.connection.StatementResult;
import com.google.cloud.spanner.pgadapter.metadata.DescribeMetadata;
import com.google.cloud.spanner.pgadapter.metadata.DescribeStatementMetadata;
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
import com.google.cloud.spanner.pgadapter.parsers.Parser;
import com.google.cloud.spanner.pgadapter.parsers.Parser.FormatCode;
import com.google.cloud.spanner.pgadapter.utils.StatementParser;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Set;
import org.postgresql.core.Oid;
Expand All @@ -37,15 +35,12 @@
*/
public class IntermediatePreparedStatement extends IntermediateStatement {

private static final Charset UTF8 = StandardCharsets.UTF_8;
protected List<Integer> parameterDataTypes;
protected Statement statement;

public IntermediatePreparedStatement(OptionsMetadata options, String sql, Connection connection) {
super(options, sql);
this.sql = replaceKnownUnsupportedQueries(sql);
this.command = StatementParser.parseCommand(this.sql);
this.connection = connection;
public IntermediatePreparedStatement(
OptionsMetadata options, ParsedStatement parsedStatement, Connection connection) {
super(options, parsedStatement, connection);
this.parameterDataTypes = null;
}

Expand Down Expand Up @@ -96,10 +91,10 @@ public void execute() {
public IntermediatePortalStatement bind(
byte[][] parameters, List<Short> parameterFormatCodes, List<Short> resultFormatCodes) {
IntermediatePortalStatement portal =
new IntermediatePortalStatement(this.options, this.sql, this.connection);
new IntermediatePortalStatement(this.options, this.parsedStatement, this.connection);
portal.setParameterFormatCodes(parameterFormatCodes);
portal.setResultFormatCodes(resultFormatCodes);
Statement.Builder builder = Statement.newBuilder(sql);
Statement.Builder builder = Statement.newBuilder(this.parsedStatement.getSqlWithoutComments());
for (int index = 0; index < parameters.length; index++) {
short formatCode = portal.getParameterFormatCode(index);
int type = this.parseType(parameters, index);
Expand All @@ -114,8 +109,8 @@ public IntermediatePortalStatement bind(

@Override
public DescribeMetadata describe() {
if (PARSER.isQuery(this.sql)) {
Statement statement = Statement.of(this.sql);
if (this.parsedStatement.isQuery()) {
Statement statement = Statement.of(this.parsedStatement.getSqlWithoutComments());
try (ResultSet resultSet = connection.analyzeQuery(statement, QueryAnalyzeMode.PLAN)) {
// TODO: Remove ResultSet.next() call once this is supported in the client library.
// See https://github.com/googleapis/java-spanner/pull/1691
Expand All @@ -132,7 +127,8 @@ public DescribeMetadata describe() {
* parameter types.
*/
private int[] getParameterTypes() {
Set<String> parameters = PARSER.getQueryParameters(this.sql);
Set<String> parameters =
PARSER.getQueryParameters(this.parsedStatement.getSqlWithoutComments());
return new int[parameters.size()];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.connection.AbstractStatementParser;
import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
import com.google.cloud.spanner.connection.Connection;
import com.google.cloud.spanner.connection.PostgreSQLStatementParser;
import com.google.cloud.spanner.connection.StatementResult;
import com.google.cloud.spanner.connection.StatementResult.ResultType;
import com.google.cloud.spanner.pgadapter.metadata.DescribeMetadata;
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
import com.google.cloud.spanner.pgadapter.utils.StatementParser;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import com.google.common.collect.ImmutableList;
import java.util.List;

/**
Expand All @@ -46,89 +46,110 @@ public class IntermediateStatement {
protected ResultSet statementResult;
protected boolean hasMoreData;
protected SpannerException exception;
protected String sql;
protected String command;
protected final ParsedStatement parsedStatement;
protected final String command;
protected boolean executed;
protected Connection connection;
protected final Connection connection;
protected Long updateCount;
protected List<String> statements;
protected final ImmutableList<String> statements;

private static final char STATEMENT_DELIMITER = ';';
private static final char SINGLE_QUOTE = '\'';

public IntermediateStatement(OptionsMetadata options, String sql, Connection connection) {
public IntermediateStatement(
OptionsMetadata options, ParsedStatement parsedStatement, Connection connection) {
this(
options,
parsedStatement,
connection,
parseStatements(parsedStatement.getSqlWithoutComments()));
}

protected IntermediateStatement(
OptionsMetadata options,
ParsedStatement parsedStatement,
Connection connection,
ImmutableList<String> statements) {
this.options = options;
this.sql = replaceKnownUnsupportedQueries(sql);
this.statements = parseStatements(sql);
this.command = StatementParser.parseCommand(this.sql);
this.parsedStatement = replaceKnownUnsupportedQueries(parsedStatement);
this.statements = statements;
this.command = StatementParser.parseCommand(this.parsedStatement.getSqlWithoutComments());
this.connection = connection;
// Note: This determines the result type based on the first statement in the SQL statement. That
// means that it assumes that if this is a batch of statements, all the statements in the batch
// will have the same type of result (that is; they are all DML statements, all DDL statements,
// all queries, etc.). That is a safe assumption for now, as PgAdapter currently only supports
// all-DML and all-DDL batches.
this.resultType = determineResultType(this.sql);
}

protected IntermediateStatement(OptionsMetadata options, String sql) {
this.options = options;
this.resultType = determineResultType(sql);
this.resultType = determineResultType(this.parsedStatement);
}

protected String replaceKnownUnsupportedQueries(String sql) {
protected ParsedStatement replaceKnownUnsupportedQueries(ParsedStatement parsedStatement) {
if (this.options.isReplaceJdbcMetadataQueries()
&& JdbcMetadataStatementHelper.isPotentialJdbcMetadataStatement(sql)) {
return JdbcMetadataStatementHelper.replaceJdbcMetadataStatement(sql);
&& JdbcMetadataStatementHelper.isPotentialJdbcMetadataStatement(
parsedStatement.getSqlWithoutComments())) {
return PARSER.parse(
Statement.of(
JdbcMetadataStatementHelper.replaceJdbcMetadataStatement(
parsedStatement.getSqlWithoutComments())));
}
return sql;
return parsedStatement;
}

/**
* Determines the result type based on the given sql string. The sql string must already been
* stripped of any comments that might precede the actual sql string.
*
* @param sql The sql string to determine the type of result for
* @param parsedStatement The parsed statement to determine the type of result for
* @return The {@link ResultType} that the given sql string will produce
*/
protected static ResultType determineResultType(String sql) {
if (PARSER.isUpdateStatement(sql)) {
protected static ResultType determineResultType(ParsedStatement parsedStatement) {
if (parsedStatement.isUpdate()) {
return ResultType.UPDATE_COUNT;
} else if (PARSER.isQuery(sql)) {
} else if (parsedStatement.isQuery()) {
return ResultType.RESULT_SET;
} else {
return ResultType.NO_RESULT;
}
}

// Split statements by ';' delimiter, but ignore anything that is nested with '' or "".
private List<String> splitStatements(String sql) {
List<String> statements = new ArrayList<>();
boolean quoteEsacpe = false;
private static ImmutableList<String> splitStatements(String sql) {
// First check trivial cases with only one statement.
int firstIndexOfDelimiter = sql.indexOf(STATEMENT_DELIMITER);
if (firstIndexOfDelimiter == -1) {
return ImmutableList.of(sql);
}
if (firstIndexOfDelimiter == sql.length() - 1) {
return ImmutableList.of(sql.substring(0, sql.length() - 1));
}

ImmutableList.Builder<String> builder = ImmutableList.builder();
// TODO: Fix this parsing, as it does not take all types of quotes into consideration.
boolean quoteEscape = false;
int index = 0;
for (int i = 0; i < sql.length(); ++i) {
if (sql.charAt(i) == SINGLE_QUOTE) {
quoteEsacpe = !quoteEsacpe;
quoteEscape = !quoteEscape;
}
if (sql.charAt(i) == STATEMENT_DELIMITER && !quoteEsacpe) {
String stmt = sql.substring(index, i + 1).trim();
if (sql.charAt(i) == STATEMENT_DELIMITER && !quoteEscape) {
String stmt = sql.substring(index, i).trim();
// Statements with only ';' character are empty and dropped.
if (stmt.length() > 1) {
statements.add(stmt);
if (stmt.length() > 0) {
builder.add(stmt);
}
index = i + 1;
}
}

if (index < sql.length()) {
statements.add(sql.substring(index, sql.length()).trim());
builder.add(sql.substring(index).trim());
}
return statements;
return builder.build();
}

protected List<String> parseStatements(String sql) {
protected static ImmutableList<String> parseStatements(String sql) {
Preconditions.checkNotNull(sql);
List<String> statements = splitStatements(sql);
return statements;
return splitStatements(sql);
}

/**
Expand Down Expand Up @@ -205,7 +226,7 @@ public ResultType getResultType() {
}

public String getSql() {
return this.sql;
return this.parsedStatement.getSqlWithoutComments();
}

public Exception getException() {
Expand Down Expand Up @@ -282,14 +303,17 @@ public void execute() {
long[] updateCounts = connection.runBatch();
updateBatchResultCount(updateCounts);
} else {
StatementResult result = connection.execute(Statement.of(this.sql));
StatementResult result =
connection.execute(Statement.of(this.parsedStatement.getSqlWithoutComments()));
updateResultCount(result);
}
} catch (SpannerException e) {
if (statements.size() > 1) {
SpannerException exception =
SpannerExceptionFactory.newSpannerException(
e.getErrorCode(), e.getMessage() + " \"" + this.sql + "\"", e);
e.getErrorCode(),
e.getMessage() + " \"" + this.parsedStatement.getSqlWithoutComments() + "\"",
e);
handleExecutionException(exception);
} else {
handleExecutionException(e);
Expand Down
Loading