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

Feature/improving tokenizer issue 15 #17

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ hs_err_pid*
/.settings/org.eclipse.jdt.core.prefs
/.settings/org.eclipse.m2e.core.prefs
/target/test-classes
/target/
21 changes: 10 additions & 11 deletions src/main/java/co/aurasphere/gomorrasql/GomorraSqlInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.StringTokenizer;
import java.util.stream.Collectors;

import co.aurasphere.gomorrasql.model.CaggiaFaException;
Expand Down Expand Up @@ -42,14 +41,11 @@ public static String toSqlQuery(String gomorraQuery) {

private static QueryInfo parseQuery(String query) {
AbstractState currentState = new InitialState();
// TODO: bug: whitespaces inside quotes should be ignored
StringTokenizer tokenizer = new StringTokenizer(query, ", ", true);
while (tokenizer.hasMoreTokens()) {
String nextToken = tokenizer.nextToken().trim();
if (!nextToken.isEmpty()) {
currentState = currentState.transitionToNextState(nextToken);
}
}

List<String> result = SQLTokenizer.tokenize(query);
for( String token : result ){
currentState = currentState.transitionToNextState(token);
}

if (!currentState.isFinalState()) {
throw new CaggiaFaException("Unexpected end of query");
Expand Down Expand Up @@ -113,7 +109,7 @@ private static String buildUpdateQuery(QueryInfo queryInfo) {
private static String buildInsertQuery(QueryInfo queryInfo) {
StringBuilder query = new StringBuilder("INSERT INTO ").append(queryInfo.getTableName());
if (!queryInfo.getColumnNames().isEmpty()) {
query.append(" ( ").append(queryInfo.getColumnNames().stream().collect(Collectors.joining(", ")))
query.append(" ( ").append(queryInfo.getColumnNames().stream().collect( Collectors.joining(", ")))
.append(" )");
}
query.append(" VALUES ( ").append(queryInfo.getValues().stream().collect(Collectors.joining(", ")))
Expand All @@ -125,7 +121,10 @@ private static String buildSelectQuery(QueryInfo queryInfo) {
String query = "SELECT ";

// Column names
query += queryInfo.getColumnNames().stream().collect(Collectors.joining(", "));
String columns = queryInfo.getColumnNames().stream().collect(Collectors.joining(", "));

// AS trick
query += columns.replace("|", " AS ");

// Table name
query += " FROM " + queryInfo.getTableName();
Expand Down
36 changes: 19 additions & 17 deletions src/main/java/co/aurasphere/gomorrasql/Keywords.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,27 @@
*/
public class Keywords {

public final static String SELECT_KEYWORD = "ripigliammo";
public final static String UPDATE_KEYWORD = "rifacimm";
public final static String[] INSERT_KEYWORDS = { "nzipp", "'ngoppa" };
public static final String SELECT_KEYWORD = "ripigliammo";
public static final String UPDATE_KEYWORD = "rifacimm";
public static final String[] INSERT_KEYWORDS = { "nzipp", "'ngoppa" };
public static final String[] DELETE_KEYWORDS = { "facimm", "na'", "strage" };
public static final String[] JOIN_KEYWORDS = { "pesc", "e", "pesc" };
public static final String[] FROM_KEYWORDS = { "mmiez", "'a" };
public final static String[] ASTERISK_KEYWORDS = { "tutto", "chillo", "ch'era", "'o", "nuostro" };
public final static String WHERE_KEYWORD = "ar�";
public final static String[] BEGIN_TRANSACTION_KEYWORDS = { "ua", "uagli�" };
public final static String[] COMMIT_KEYWORDS = { "iamme", "bello", "ia'" };
public final static String ROLLBACK_KEYWORD = "sfaccimm";
public final static String AND_KEYWORD = "e";
public final static String OR_KEYWORD = "o";
public final static String NULL_KEYWORD = "nisciun";
public final static String IS_KEYWORD = "�";
public final static String VALUES_KEYWORD = "chist";
public final static String[] IS_NOT_KEYWORDS = { "nun", "�" };
public final static String SET_KEYWORD = "accunza";
public final static List<String> WHERE_OPERATORS = Arrays.asList(">", "<", "=", "!=", "<>", ">=", "<=",
public static final String[] ASTERISK_KEYWORDS = { "tutto", "chillo", "ch'era", "'o", "nuostro" };
public static final String WHERE_KEYWORD = "ar�";
public static final String[] BEGIN_TRANSACTION_KEYWORDS = { "ua", "uagli�" };
public static final String[] COMMIT_KEYWORDS = { "iamme", "bello", "ia'" };
public static final String ROLLBACK_KEYWORD = "sfaccimm";
public static final String AND_KEYWORD = "e";
public static final String OR_KEYWORD = "o";
public static final String NULL_KEYWORD = "nisciun";
public static final String IS_KEYWORD = "�";
public static final String VALUES_KEYWORD = "chist";
public static final String[] IS_NOT_KEYWORDS = { "nun", "�" };
public static final String SET_KEYWORD = "accunza";
public static final List<String> WHERE_OPERATORS = Arrays.asList(">", "<", "=", "!=", "<>", ">=", "<=",
Keywords.IS_KEYWORD, Keywords.IS_NOT_KEYWORDS[0]);
public final static String SET_EQUAL_KEYWORD = "accuss�";
public static final String SET_EQUAL_KEYWORD = "accuss�";
public static final String LIMIT_KEYWORD = "� pccirill ashpiett";
public static final String AS_KEYWORD = "cumme";
}
60 changes: 60 additions & 0 deletions src/main/java/co/aurasphere/gomorrasql/SQLTokenizer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package co.aurasphere.gomorrasql;

import java.util.ArrayList;
import java.util.List;

/**
*
* @author Matteo
*/
public final class SQLTokenizer {

/**
* Private constructor for utility class.
*/
private SQLTokenizer() {

}

/**
* Tokenize the input query in single usable tokens.
*
* @param query the query to tokenize
* @return the arrya list of String tokens
*/
static List<String> tokenize(final String query) {
List<String> result = new ArrayList<>();

boolean quote = false;
StringBuilder currentToken = new StringBuilder();
for (int n = 0; n < query.length(); n++) {
char chr = query.charAt(n);

// Quote check
if (!quote && chr == '"') {
quote = true;
} else if (quote && chr == '"') {
quote = false;
}

if (quote) {
currentToken.append(chr);
} else if (chr == ' ' || chr == ',') {
if (currentToken.length() > 0) {
result.add(currentToken.toString());
currentToken = new StringBuilder();
}
if (chr == ',') {
result.add("" + chr);
}
} else {
currentToken.append(chr);
}
}
if (currentToken.length() > 0) {
result.add(currentToken.toString());
}

return result;
}
}
21 changes: 14 additions & 7 deletions src/main/java/co/aurasphere/gomorrasql/model/QueryInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ public enum QueryType {

private String tableName;

private List<String> columnNames = new ArrayList<>();
private final List<String> columnNames = new ArrayList<>();

private List<String> values = new ArrayList<>();
private final List<String> columnAliases = new ArrayList<>();

private List<WhereCondition> whereConditions = new ArrayList<>();
private final List<String> values = new ArrayList<>();

private List<String> joinedTables = new ArrayList<>();
private final List<WhereCondition> whereConditions = new ArrayList<>();

private List<String> whereConditionsJoinOperators = new ArrayList<>();
private final List<String> joinedTables = new ArrayList<>();

private final List<String> whereConditionsJoinOperators = new ArrayList<>();

public QueryType getType() {
return type;
Expand All @@ -49,8 +51,13 @@ public List<String> getColumnNames() {
return columnNames;
}

public void addColumnName(String columnName) {
this.columnNames.add(columnName);
public List<String> getColumnAliases() {
return columnAliases;
}

public void addColumnName(String columnName) {
this.columnNames.add( columnName );
this.columnAliases.add( columnName );
}

public List<String> getValues() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/
public class AnyTokenConsumerState extends AbstractState {

private Consumer<String> tokenConsumer;
private Function<QueryInfo, AbstractState> transitionFunction;
private final Consumer<String> tokenConsumer;
private final Function<QueryInfo, AbstractState> transitionFunction;

public AnyTokenConsumerState(QueryInfo queryInfo, Consumer<String> tokenConsumer,
Function<QueryInfo, AbstractState> transitionFunction) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package co.aurasphere.gomorrasql.states;

import co.aurasphere.gomorrasql.Keywords;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

import co.aurasphere.gomorrasql.model.CaggiaFaException;
import co.aurasphere.gomorrasql.model.QueryInfo;

/**
* State that parses a list of values, separed by a comma. It stops when there's
* no more comma and checks if the token after is the expected one (set in the
* constructor). It can be configured to be a final state.
*
* @author Matteo Baccan
*
*/
public class CommaSeparedValuesWithAliasState extends AbstractState {

private boolean lastWasComma = false;
private List<String> collector;
private String nextToken;
private Function<QueryInfo, AbstractState> transitionFunction;
private String expectedToken;
private boolean canBeFinalState = false;
private boolean optionalValues = false;
private boolean lastWasAS = false;

public CommaSeparedValuesWithAliasState(QueryInfo queryInfo, List<String> collector, String nextToken, String expectedToken,
Function<QueryInfo, AbstractState> transitionFunction) {
super(queryInfo);
this.collector = collector;
this.nextToken = nextToken;
this.transitionFunction = transitionFunction;
this.expectedToken = expectedToken;
}

public CommaSeparedValuesWithAliasState(QueryInfo queryInfo, List<String> collector, String nextToken, String expectedToken,
boolean lastWasComma, boolean canBeFinalState, Function<QueryInfo, AbstractState> transitionFunction) {
this(queryInfo, collector, nextToken, expectedToken, transitionFunction);

// Used when the first token is not consumed by the previous state.
this.lastWasComma = lastWasComma;
this.canBeFinalState = canBeFinalState;
this.optionalValues = true;
}

@Override
public AbstractState transitionToNextState(String token) throws CaggiaFaException {
if (token.equals(",")) {
if (lastWasComma) {
// Case ", ,"
throw new CaggiaFaException(expectedToken, token);
} else {
// Case "%expectedToken% ,"
lastWasComma = true;
return this;
}
}

// Case ", %expectedToken%"
if (lastWasComma) {
if (optionalValues && token.equalsIgnoreCase(nextToken)) {
return transitionFunction.apply(queryInfo);
} else {
optionalValues = false;
}
collector.add(token);
lastWasComma = false;
return this;
}

if( token.equalsIgnoreCase(Keywords.AS_KEYWORD) ){
lastWasAS = true;
return this;
}

if (lastWasAS) {
int pos = collector.size()-1;
collector.set( pos, collector.get( pos ) + "|" + token );
lastWasAS = false;
return this;
}

// Case "%expectedToken% %nextToken%"
if (token.equalsIgnoreCase(nextToken)) {
return transitionFunction.apply(queryInfo);
}

// Case "%expectedToken% %WRONG_TOKEN%"
throw new CaggiaFaException(Arrays.asList(",", nextToken), token);
}

@Override
public boolean isFinalState() {
return canBeFinalState;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
public class GreedyMatchKeywordState extends AbstractState {

private int currentIndex = 1;
private String[] keywords;
private Function<QueryInfo, AbstractState> nextStateTransition;
private final String[] keywords;
private final Function<QueryInfo, AbstractState> nextStateTransition;

public GreedyMatchKeywordState(QueryInfo queryInfo, String[] keywords,
Function<QueryInfo, AbstractState> nextStateTransition) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ public AbstractState transitionToNextState(String token) throws CaggiaFaExceptio
queryInfo.setType(QueryType.INSERT);
return new GreedyMatchKeywordState(queryInfo, Keywords.INSERT_KEYWORDS,
q -> new AnyTokenConsumerState(q, q::setTableName,
q2 -> new CommaSeparedValuesState(q2, q2.getColumnNames(), Keywords.VALUES_KEYWORD,
"%COLUMN_NAME%", true, false, q3 -> new CommaSeparedValuesState(q3, q3.getValues(),
null, "%VALUE%", true, true, FinalState::new))));
q2 -> new CommaSeparedValuesState(q2, q2.getColumnNames(), Keywords.VALUES_KEYWORD, "%COLUMN_NAME%", true, false,
q3 -> new CommaSeparedValuesState(q3, q3.getValues(), null, "%VALUE%", true, true, FinalState::new))));
}
if (token.equalsIgnoreCase(Keywords.COMMIT_KEYWORDS[0])) {
queryInfo.setType(QueryType.COMMIT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import co.aurasphere.gomorrasql.model.QueryInfo;
import co.aurasphere.gomorrasql.states.AbstractState;
import co.aurasphere.gomorrasql.states.AnyTokenConsumerState;
import co.aurasphere.gomorrasql.states.CommaSeparedValuesState;
import co.aurasphere.gomorrasql.states.CommaSeparedValuesWithAliasState;
import co.aurasphere.gomorrasql.states.GreedyMatchKeywordState;

/**
Expand All @@ -32,8 +32,8 @@ public AbstractState transitionToNextState(String token) throws CaggiaFaExceptio
} else {
// Token is a column name, we continue until there are none
queryInfo.addColumnName(token);
return new CommaSeparedValuesState(queryInfo, queryInfo.getColumnNames(), Keywords.FROM_KEYWORDS[0],
"%COLUMN_NAME%", q -> new GreedyMatchKeywordState(queryInfo, Keywords.FROM_KEYWORDS,
return new CommaSeparedValuesWithAliasState(queryInfo, queryInfo.getColumnNames(), Keywords.FROM_KEYWORDS[0], "%COLUMN_NAME%",
q -> new GreedyMatchKeywordState(queryInfo, Keywords.FROM_KEYWORDS,
q2 -> new AnyTokenConsumerState(q2, q2::setTableName, OptionalWhereState::new)));
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/test/java/co/aurasphere/gomorrasql/TestSelect.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,19 @@ public void testSelectWhereIsNot() throws SQLException {
Assert.assertEquals(4, counter);
}

@Test
public void testSelectAs() throws SQLException {
GomorraSqlInterpreter gsi = new GomorraSqlInterpreter(connection);
GomorraSqlQueryResult result = gsi.execute("ripigliammo first_name cumme nome, last_name cumme cognome mmiez 'a user ar� address nun � nisciun");
ResultSet resultSet = result.getResultSet();
int counter = 1;
String[] names = { "PINCO", "PAOLINO", "FRED" };
while (resultSet.next()) {
String name = resultSet.getString("nome");
Assert.assertEquals(names[counter - 1], name);
counter++;
}
Assert.assertEquals(4, counter);
}

}
Loading