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

statement.split(";") cannot be used with strings which contains symbo… #24

Merged
merged 14 commits into from
Aug 5, 2018
Merged
Show file tree
Hide file tree
Changes from 12 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 @@ -10,6 +10,10 @@
import java.util.ArrayList;
import java.util.List;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonToken;
import org.antlr.runtime.Token;
import org.apache.cassandra.cql3.CqlLexer;
import org.apache.cassandra.thrift.Cassandra.Client;
import org.apache.cassandra.thrift.Compression;
import org.apache.cassandra.thrift.ConsistencyLevel;
Expand Down Expand Up @@ -42,6 +46,17 @@ public abstract class AbstractCqlExecMojo extends AbstractCassandraMojo
*/
protected String cqlEncoding = Charset.defaultCharset().name();

/**
* Should we use the CqlLexer when loading the cql file. This should be better than than the default behaviour
* which is to just split input on ; since it handles ; in comments and strings.
*
* It is not enabled by default since has not been extensively tested.
*
* @parameter default-value=false
* @since 3.6
Copy link
Contributor

@djarnis73 djarnis73 May 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since should be bumped to 3.7, since 3.6 is already released.

*/
protected boolean useCqlLexer = false;

protected String readFile(File file) throws MojoExecutionException
{
if (!file.isFile())
Expand Down Expand Up @@ -87,6 +102,42 @@ protected List<CqlResult> executeCql(final String statements) throws MojoExecuti
return results;
}

/**
* Best effort to somewhat parse the cql input instead of just splitting on ; which
* breaks badly if you have ; in strings or comments.
* Parsing is done using the CqlLexer class
*/
protected static String[] splitStatementsUsingCqlLexer(String statements) {
ANTLRStringStream stream = new ANTLRStringStream(statements);
CqlLexer lexer = new CqlLexer(stream);
ArrayList<String> statementList = new ArrayList<String>();
StringBuffer currentStatement = new StringBuffer();
// Not the prettiest code i ever wrote, but it gets the job done.
for (Token token = lexer.nextToken(); token.getType() != Token.EOF; token = lexer.nextToken()) {
if (token.getText().equals(";")) {
// when we meet a ; terminate current statement and prepare the next
currentStatement.append(";");
statementList.add(currentStatement.toString());
currentStatement = new StringBuffer();
} else if (token.getType() == CqlLexer.STRING_LITERAL) {
// If we meet a string we should quote it and escape any enclosed ' as ''
currentStatement.append("'");
// TODO: There must be a cassandra util method somewhere that escapes a string for sql
currentStatement.append(token.getText().replaceAll("'", "''"));
currentStatement.append("'");
} else if (token.getType() == CqlLexer.COMMENT) {
// skip
} else {
currentStatement.append(token.getText());
}
}
if (currentStatement.length() > 0 && currentStatement.toString().trim().length() > 0) {
statementList.add(currentStatement.toString());
}
return statementList.toArray(new String[statementList.size()]);
}


private class CqlExecOperation extends ThriftApiOperation
{
private final List<CqlResult> results = new ArrayList<CqlResult>();
Expand All @@ -95,7 +146,13 @@ private class CqlExecOperation extends ThriftApiOperation
private CqlExecOperation(String statements)
{
super(rpcAddress, rpcPort);
this.statements = statements.split(";");
if (useCqlLexer) {
getLog().warn("********************************************************************************");
getLog().warn("Using CqlLexer has not been extensively tested");
this.statements = splitStatementsUsingCqlLexer(statements);
} else {
this.statements = statements.split(";");
}
if (StringUtils.isNotBlank(keyspace))
{
getLog().info("setting keyspace: " + keyspace);
Expand All @@ -112,6 +169,9 @@ void executeOperation(Client client) throws ThriftApiExecutionException
{
if (StringUtils.isNotBlank(statement))
{
if (getLog().isDebugEnabled()) {
getLog().debug("Executing cql statement: " + statement);
}
results.add(executeStatement(client, statement));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.codehaus.mojo.cassandra;

import org.junit.Test;

import static org.junit.Assert.*;

public class AbstractCqlExecMojoTest {

@Test
public void splitStatementsUsingCqlLexerWithSemicolonTest() {
splitStatementsUsingCqlLexerTest("SELECT * FROM tbl1 WHERE col1 = 'abc1;def1';SELECT * FROM tbl2 WHERE col2 = 'abc2;def2';", "SELECT * FROM tbl1 WHERE col1 = 'abc1;def1';", "SELECT * FROM tbl2 WHERE col2 = 'abc2;def2';");
}

@Test
public void splitStatementsUsingCqlLexerWithCommentTest() {
splitStatementsUsingCqlLexerTest("--\nSELECT * FROM tbl1 WHERE col1 = '--';", "SELECT * FROM tbl1 WHERE col1 = '--';");
}

@Test
public void splitStatementsUsingCqlLexerWithEmptyInput() {
splitStatementsUsingCqlLexerTest("");
splitStatementsUsingCqlLexerTest(";;;", ";", ";", ";");
}

public void splitStatementsUsingCqlLexerTest(String input, String...expected) {
String[] split = AbstractCqlExecMojo.splitStatementsUsingCqlLexer(input);
assertEquals("Split does not match expected number of statements", expected.length, split.length);
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], split[i]);
}
}
}