All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.chengyuxing.sql.utils.SqlHighlighter Maven / Gradle / Ivy

Go to download

Light wrapper of JDBC, support ddl, dml, query, plsql/procedure/function, transaction and manage sql file.

There is a newer version: 9.0.2
Show newest version
package com.github.chengyuxing.sql.utils;

import com.github.chengyuxing.common.console.Color;
import com.github.chengyuxing.common.console.Printer;
import com.github.chengyuxing.common.script.expression.Patterns;
import com.github.chengyuxing.common.tuple.Pair;
import com.github.chengyuxing.common.utils.StringUtil;
import com.github.chengyuxing.sql.Keywords;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Sql highlight util.
 */
public final class SqlHighlighter {
    private static final Logger log = LoggerFactory.getLogger(SqlHighlighter.class);
    private static final Pattern BLOCK_ANNOTATION_PATTERN = Pattern.compile("(/\\*.*?\\*/)", Pattern.DOTALL | Pattern.MULTILINE);

    public enum TAG {
        FUNCTION,
        KEYWORD,
        NUMBER,
        /**
         * $$
         * body
         * $$
         */
        POSTGRESQL_FUNCTION_BODY_SYMBOL,
        ASTERISK,
        SINGLE_QUOTE_STRING,
        LINE_ANNOTATION,
        BLOCK_ANNOTATION,
        NAMED_PARAMETER,
        OTHER
    }

    /**
     * Build highlight sql for console if console is active.
     *
     * @param sql sql string
     * @return normal sql string or highlight sql string
     */
    public static String highlightIfAnsiCapable(String sql) {
        if (System.console() != null && System.getenv().get("TERM") != null) {
            return ansi(sql);
        }
        return sql;
    }

    /**
     * Highlight sql string with ansi color.
     *
     * @param sql sql string
     * @return highlighted sql
     */
    public static String ansi(String sql) {
        return highlight(sql, (tag, content) -> {
            switch (tag) {
                case FUNCTION:
                    return Printer.colorful(content, Color.BLUE);
                case KEYWORD:
                    return Printer.colorful(content, Color.DARK_PURPLE);
                case NUMBER:
                    return Printer.colorful(content, Color.DARK_CYAN);
                case POSTGRESQL_FUNCTION_BODY_SYMBOL:
                case SINGLE_QUOTE_STRING:
                    return Printer.colorful(content, Color.DARK_GREEN);
                case ASTERISK:
                    return Printer.colorful(content, Color.YELLOW);
                case LINE_ANNOTATION:
                    return Printer.colorful(content, Color.SILVER);
                case BLOCK_ANNOTATION:
                    return Printer.colorful(content.replaceAll("\033\\[\\d{2}m|\033\\[0m", ""), Color.SILVER);
                case NAMED_PARAMETER:
                    return Printer.colorful(content, Color.CYAN);
                default:
                    return content;
            }
        });
    }

    /**
     * Custom highlight sql string.
     *
     * @param sql      sql string
     * @param replacer colored content function: ({@link TAG tag}, content) -> colored content
     * @return highlighted sql
     */
    public static String highlight(String sql, BiFunction replacer) {
        try {
            Pair> r = SqlUtil.escapeSubstring(sql);
            String rSql = r.getItem1();
            Pair, List> x = StringUtil.regexSplit(rSql, "(?[\\s,\\[\\]():;{}]+)", "d");
            List words = x.getItem1();
            List delimiters = x.getItem2();
            StringBuilder sb = new StringBuilder();
            for (int i = 0, j = words.size(); i < j; i++) {
                String word = words.get(i);
                String replacement = word;
                if (!word.trim().isEmpty()) {
                    // functions highlight
                    if (!StringUtil.equalsAnyIgnoreCase(word, Keywords.STANDARD) && detectFunction(word, i, j, delimiters)) {
                        replacement = replacer.apply(TAG.FUNCTION, word);
                        // named parameter
                    } else if (detectNamedParameter(word, i, delimiters)) {
                        replacement = replacer.apply(TAG.NAMED_PARAMETER, word);
                        // keywords highlight
                    } else if (StringUtil.equalsAnyIgnoreCase(word, Keywords.STANDARD)) {
                        replacement = replacer.apply(TAG.KEYWORD, word);
                        // number highlight
                    } else if (StringUtil.isNumeric(word)) {
                        replacement = replacer.apply(TAG.NUMBER, word);
                        // PostgreSQL function body block highlight
                    } else if (word.equals("$$")) {
                        replacement = replacer.apply(TAG.POSTGRESQL_FUNCTION_BODY_SYMBOL, word);
                        // symbol '*' highlight
                    } else if (word.equals("*")) {
                        replacement = replacer.apply(TAG.ASTERISK, word);
                    } else {
                        replacement = replacer.apply(TAG.OTHER, word);
                    }
                }
                sb.append(replacement);
                if (i < j - 1) {
                    sb.append(delimiters.get(i));
                }
            }
            String colorfulSql = sb.toString();
            // reinsert the sub string
            Map subStr = r.getItem2();
            for (String key : subStr.keySet()) {
                colorfulSql = colorfulSql.replace(key, replacer.apply(TAG.SINGLE_QUOTE_STRING, subStr.get(key)));
            }
            // resolve single annotation
            String[] sqlLines = colorfulSql.split("\n");
            for (int i = 0; i < sqlLines.length; i++) {
                String line = sqlLines[i];
                if (line.trim().startsWith("--")) {
                    sqlLines[i] = replacer.apply(TAG.LINE_ANNOTATION, line);
                } else if (line.contains("--")) {
                    int idx = line.indexOf("--");
                    sqlLines[i] = line.substring(0, idx) + replacer.apply(TAG.LINE_ANNOTATION, line.substring(idx));
                }
            }
            colorfulSql = String.join("\n", sqlLines);
            // resolve block annotation
            StringBuilder parsedSql = new StringBuilder();
            Matcher matcher = BLOCK_ANNOTATION_PATTERN.matcher(colorfulSql);
            int lastMatchEnd = 0;
            while (matcher.find()) {
                String match = matcher.group();
                parsedSql.append(colorfulSql, lastMatchEnd, matcher.start());
                parsedSql.append(replacer.apply(TAG.BLOCK_ANNOTATION, match));
                lastMatchEnd = matcher.end();
            }
            parsedSql.append(colorfulSql.substring(lastMatchEnd));
            return parsedSql.toString();
        } catch (Exception e) {
            log.error("highlight sql error.", e);
            return sql;
        }
    }

    /**
     * Detect probably is function or not.
     *
     * @param i          word index
     * @param j          max word length
     * @param delimiters delimiters
     * @return true or false
     */
    private static boolean detectFunction(String word, int i, int j, List delimiters) {
        if (!word.matches("^[a-zA-Z][\\w.]+")) {
            return false;
        }
        if (i < j - 1) {
            return delimiters.get(i).trim().startsWith("(");
        }
        return false;
    }

    private static boolean detectNamedParameter(String word, int i, List delimiters) {
        if (!word.matches(Patterns.VAR_KEY_PATTERN)) {
            return false;
        }
        int idx = i > 0 ? i - 1 : i;
        String prefix = delimiters.get(idx).trim();
        return prefix.endsWith(":") && !prefix.endsWith("::");
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy