Skip to content
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 @@ -74,11 +74,11 @@ static boolean isAutoIncrementColumn(String createSql, String columnName) {
if (createSql == null || columnName == null) {
return false;
}
List<List<Token>> definitions = tableDefinitions(createSql);
List<List<SqlTokenizer.Token>> definitions = tableDefinitions(createSql);
if (definitions == null) {
return false;
}
for (List<Token> definition : definitions) {
for (List<SqlTokenizer.Token> definition : definitions) {
if (columnDefinitionIsAutoIncrement(definition, columnName)
|| tableConstraintIsAutoIncrement(definition, columnName)) {
return true;
Expand Down Expand Up @@ -526,14 +526,14 @@ private static TableInfo toTableInfo(StmtResult result, String fallbackName) {
return new TableInfo(tableName != null ? tableName : fallbackName, createSql, columns);
}

private static List<List<Token>> tableDefinitions(String createSql) {
List<Token> tokens = tokenize(createSql);
private static List<List<SqlTokenizer.Token>> tableDefinitions(String createSql) {
List<SqlTokenizer.Token> tokens = SqlTokenizer.tokenize(createSql);
if (tokens == null) {
return null;
}
int open = -1;
for (int i = 0; i < tokens.size(); i++) {
if (isSymbol(tokens.get(i), "(")) {
if (tokens.get(i).isSymbol("(")) {
open = i;
break;
}
Expand All @@ -542,17 +542,17 @@ private static List<List<Token>> tableDefinitions(String createSql) {
return null;
}

List<List<Token>> definitions = new ArrayList<>();
List<Token> current = new ArrayList<>();
List<List<SqlTokenizer.Token>> definitions = new ArrayList<>();
List<SqlTokenizer.Token> current = new ArrayList<>();
for (int i = open + 1; i < tokens.size(); i++) {
Token token = tokens.get(i);
if (isSymbol(token, ")") && token.depth() == 0) {
SqlTokenizer.Token token = tokens.get(i);
if (token.isSymbol(")") && token.depth() == 0) {
if (!current.isEmpty()) {
definitions.add(current);
}
return definitions;
}
if (isSymbol(token, ",") && token.depth() == 1) {
if (token.isSymbol(",") && token.depth() == 1) {
definitions.add(current);
current = new ArrayList<>();
continue;
Expand All @@ -562,31 +562,33 @@ private static List<List<Token>> tableDefinitions(String createSql) {
return null;
}

private static boolean columnDefinitionIsAutoIncrement(List<Token> definition, String columnName) {
private static boolean columnDefinitionIsAutoIncrement(
List<SqlTokenizer.Token> definition, String columnName) {
int start = skipConstraintName(definition);
if (start >= definition.size()) {
return false;
}
if (isConstraintKeyword(definition.get(start))) {
return false;
}
String name = identifier(definition.get(start));
String name = definition.get(start).identifier();
if (name == null || !name.equalsIgnoreCase(columnName)) {
return false;
}
return containsKeyword(definition, start + 1, definition.size(), "AUTOINCREMENT", 1);
}

private static boolean tableConstraintIsAutoIncrement(List<Token> definition, String columnName) {
private static boolean tableConstraintIsAutoIncrement(
List<SqlTokenizer.Token> definition, String columnName) {
int start = skipConstraintName(definition);
if (start + 1 >= definition.size()
|| !isWord(definition.get(start), "PRIMARY")
|| !isWord(definition.get(start + 1), "KEY")) {
|| !definition.get(start).isWord("PRIMARY")
|| !definition.get(start + 1).isWord("KEY")) {
return false;
}
int open = -1;
for (int i = start + 2; i < definition.size(); i++) {
if (isSymbol(definition.get(i), "(")) {
if (definition.get(i).isSymbol("(")) {
open = i;
break;
}
Expand All @@ -597,8 +599,9 @@ private static boolean tableConstraintIsAutoIncrement(List<Token> definition, St

int partStart = open + 1;
for (int i = open + 1; i < definition.size(); i++) {
Token token = definition.get(i);
if ((isSymbol(token, ",") && token.depth() == 2) || (isSymbol(token, ")") && token.depth() == 1)) {
SqlTokenizer.Token token = definition.get(i);
if ((token.isSymbol(",") && token.depth() == 2)
|| (token.isSymbol(")") && token.depth() == 1)) {
if (indexedColumnIsAutoIncrement(definition, partStart, i, columnName)) {
return true;
}
Expand All @@ -609,167 +612,41 @@ private static boolean tableConstraintIsAutoIncrement(List<Token> definition, St
}

private static boolean indexedColumnIsAutoIncrement(
List<Token> definition, int start, int end, String columnName) {
List<SqlTokenizer.Token> definition, int start, int end, String columnName) {
if (start >= end) {
return false;
}
String name = identifier(definition.get(start));
String name = definition.get(start).identifier();
return name != null
&& name.equalsIgnoreCase(columnName)
&& containsKeyword(definition, start + 1, end, "AUTOINCREMENT", 2);
}

private static int skipConstraintName(List<Token> definition) {
if (definition.size() >= 2 && isWord(definition.get(0), "CONSTRAINT")) {
private static int skipConstraintName(List<SqlTokenizer.Token> definition) {
if (definition.size() >= 2 && definition.get(0).isWord("CONSTRAINT")) {
return 2;
}
return 0;
}

private static boolean isConstraintKeyword(Token token) {
return isWord(token, "PRIMARY")
|| isWord(token, "UNIQUE")
|| isWord(token, "CHECK")
|| isWord(token, "FOREIGN");
private static boolean isConstraintKeyword(SqlTokenizer.Token token) {
return token.isWord("PRIMARY")
|| token.isWord("UNIQUE")
|| token.isWord("CHECK")
|| token.isWord("FOREIGN");
}

private static boolean containsKeyword(
List<Token> tokens, int start, int end, String keyword, int depth) {
List<SqlTokenizer.Token> tokens, int start, int end, String keyword, int depth) {
for (int i = start; i < end; i++) {
Token token = tokens.get(i);
if (token.depth() == depth && isWord(token, keyword)) {
SqlTokenizer.Token token = tokens.get(i);
if (token.depth() == depth && token.isWord(keyword)) {
return true;
}
}
return false;
}

private static boolean isWord(Token token, String word) {
return token.type() == TokenType.WORD && token.text().equalsIgnoreCase(word);
}

private static boolean isSymbol(Token token, String symbol) {
return token.type() == TokenType.SYMBOL && token.text().equals(symbol);
}

private static String identifier(Token token) {
return switch (token.type()) {
case IDENT, WORD -> token.text();
case SYMBOL -> null;
};
}

private static List<Token> tokenize(String sql) {
List<Token> tokens = new ArrayList<>();
int depth = 0;
int i = 0;
while (i < sql.length()) {
char c = sql.charAt(i);
if (Character.isWhitespace(c)) {
i++;
continue;
}
if (c == '-' && i + 1 < sql.length() && sql.charAt(i + 1) == '-') {
i += 2;
while (i < sql.length() && sql.charAt(i) != '\n' && sql.charAt(i) != '\r') {
i++;
}
continue;
}
if (c == '/' && i + 1 < sql.length() && sql.charAt(i + 1) == '*') {
int end = sql.indexOf("*/", i + 2);
if (end < 0) {
return null;
}
i = end + 2;
continue;
}
if (c == '\'') {
i = skipQuoted(sql, i, c);
if (i < 0) {
return null;
}
continue;
}
if (c == '"' || c == '`') {
int end = skipQuoted(sql, i, c);
if (end < 0) {
return null;
}
tokens.add(new Token(TokenType.IDENT, unescape(sql.substring(i + 1, end - 1), c), depth));
i = end;
continue;
}
if (c == '[') {
int end = sql.indexOf(']', i + 1);
if (end < 0) {
return null;
}
tokens.add(new Token(TokenType.IDENT, sql.substring(i + 1, end), depth));
i = end + 1;
continue;
}
if (c == '(') {
tokens.add(new Token(TokenType.SYMBOL, "(", depth));
depth++;
i++;
continue;
}
if (c == ')') {
if (depth == 0) {
return null;
}
depth--;
tokens.add(new Token(TokenType.SYMBOL, ")", depth));
i++;
continue;
}
if (isWordChar(c)) {
int start = i;
while (i < sql.length() && isWordChar(sql.charAt(i))) {
i++;
}
tokens.add(new Token(TokenType.WORD, sql.substring(start, i), depth));
continue;
}
tokens.add(new Token(TokenType.SYMBOL, String.valueOf(c), depth));
i++;
}
if (depth != 0) {
return null;
}
return tokens;
}

private static int skipQuoted(String sql, int start, char quote) {
int i = start + 1;
while (i < sql.length()) {
if (sql.charAt(i) == quote) {
if (i + 1 < sql.length() && sql.charAt(i + 1) == quote) {
i += 2;
continue;
}
return i + 1;
}
i++;
}
return -1;
}

private static String unescape(String content, char quote) {
String doubled = new String(new char[] {quote, quote});
return content.replace(doubled, String.valueOf(quote));
}

private static boolean isWordChar(char c) {
return c >= 0x80
|| (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '_'
|| c == '$';
}

private static String text(Value value) {
return value instanceof Value.Text t ? t.value() : null;
}
Expand All @@ -786,14 +663,6 @@ private enum SampleType {
NULL
}

private enum TokenType {
WORD,
IDENT,
SYMBOL
}

private record Token(TokenType type, String text, int depth) {}

private record ColumnInfo(boolean notNull, boolean primaryKey) {}

private record TableInfo(String name, String createSql, Map<String, ColumnInfo> columns) {}
Expand Down
Loading
Loading