package com.zendesk.maxwell.schema.ddl;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import com.zendesk.maxwell.MaxwellFilter;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zendesk.maxwell.schema.Schema;
public abstract class SchemaChange {
final static Logger LOGGER = LoggerFactory.getLogger(SchemaChange.class);
public abstract ResolvedSchemaChange resolve(Schema schema) throws InvalidSchemaError;
private static final Set<Pattern> SQL_BLACKLIST = new HashSet<Pattern>();
static {
SQL_BLACKLIST.add(Pattern.compile("^\\s*BEGIN", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*COMMIT", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*FLUSH", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*GRANT", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*REVOKE\\s+", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*SAVEPOINT", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*CREATE\\s+(AGGREGATE)?\\s+FUNCTION", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*(ALTER|CREATE)\\s+(DEFINER=[^\\s]+\\s+)?(EVENT|FUNCTION|TRIGGER|PROCEDURE)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*DROP\\s+(EVENT|FUNCTION|TRIGGER|PROCEDURE|VIEW)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*(ALTER|CREATE|DROP)\\s+((ONLINE|OFFLINE|UNIQUE|FULLTEXT|SPATIAL)\\s+)*(INDEX)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*ANALYZE\\s+TABLE", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*SET\\s+PASSWORD", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*(ALTER|CREATE|DROP|RENAME)\\s+USER", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*(ALTER|CREATE|DROP)\\s+TEMPORARY\\s+TABLE", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*TRUNCATE\\s+", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*OPTIMIZE\\s+", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
SQL_BLACKLIST.add(Pattern.compile("^\\s*REPAIR\\s+", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE));
}
private static final Pattern DELETE_BLACKLIST = Pattern.compile("^\\s*DELETE\\s*FROM", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
private static boolean matchesBlacklist(String sql) {
// first *include* /*50032 CREATE EVENT */ style sql
sql = sql.replaceAll("/\\*!\\d+\\s*(.*)\\*/", "$1");
// now strip out comments
sql = sql.replaceAll("/\\*.*?\\*/", "");
sql = sql.replaceAll("\\-\\-.*", "");
for (Pattern p : SQL_BLACKLIST) {
if (p.matcher(sql).find())
return true;
}
if ( DELETE_BLACKLIST.matcher(sql).find() ) {
LOGGER.info("Ignoring DELETE statement: " + sql);
LOGGER.info("You may ignore this warning if this is a MEMORY table.");
LOGGER.info("Otherwise you should make sure your binlog_format setting is correct, and that your clients have all reconnected.");
return true;
}
return false;
}
private static List<SchemaChange> parseSQL(String currentDB, String sql) {
ANTLRInputStream input = new ANTLRInputStream(sql);
mysqlLexer lexer = new mysqlLexer(input);
lexer.removeErrorListeners();
TokenStream tokens = new CommonTokenStream(lexer);
LOGGER.debug("SQL_PARSE <- \"" + sql + "\"");
mysqlParser parser = new mysqlParser(tokens);
parser.removeErrorListeners();
MysqlParserListener listener = new MysqlParserListener(currentDB, tokens);
ParseTree tree = parser.parse();
ParseTreeWalker.DEFAULT.walk(listener, tree);
LOGGER.debug("SQL_PARSE -> " + tree.toStringTree(parser));
return listener.getSchemaChanges();
}
public static List<SchemaChange> parse(String currentDB, String sql) {
if ( matchesBlacklist(sql) ) {
return null;
}
while ( true ) {
try {
return parseSQL(currentDB, sql);
} catch ( ReparseSQLException e ) {
sql = e.getSQL();
LOGGER.debug("rewrote SQL to " + sql);
// re-enter loop
} catch ( ParseCancellationException e ) {
LOGGER.debug("Parse cancelled: " + e);
return null;
} catch ( MaxwellSQLSyntaxError e) {
LOGGER.error("Error parsing SQL: '" + sql + "'");
throw (e);
}
}
}
public abstract boolean isBlacklisted(MaxwellFilter filter);
}