001/*
002 *  Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.core.util;
017
018import java.lang.reflect.Array;
019import java.time.LocalDateTime;
020import java.time.format.DateTimeFormatter;
021import java.util.Date;
022import java.util.StringJoiner;
023import java.util.regex.Matcher;
024
025import static com.mybatisflex.core.constant.SqlConsts.*;
026
027public class SqlUtil {
028
029    private SqlUtil() {
030    }
031
032    public static void keepColumnSafely(String column) {
033        if (StringUtil.isBlank(column)) {
034            throw new IllegalArgumentException("Column must not be empty");
035        } else {
036            column = column.trim();
037        }
038
039        int strLen = column.length();
040        for (int i = 0; i < strLen; ++i) {
041            char ch = column.charAt(i);
042            if (Character.isWhitespace(ch)) {
043                throw new IllegalArgumentException("Column must not has space char.");
044            }
045            if (isUnSafeChar(ch)) {
046                throw new IllegalArgumentException("Column has unsafe char: [" + ch + "].");
047            }
048        }
049    }
050
051
052    /**
053     * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
054     */
055    private static final String SQL_ORDER_BY_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
056
057    public static void keepOrderBySqlSafely(String value) {
058        if (!value.matches(SQL_ORDER_BY_PATTERN)) {
059            throw new IllegalArgumentException("Order By sql not safe, order by string: " + value);
060        }
061    }
062
063
064    private static final char[] UN_SAFE_CHARS = "'`\"<>&+=#-;".toCharArray();
065
066    private static boolean isUnSafeChar(char ch) {
067        for (char c : UN_SAFE_CHARS) {
068            if (c == ch) {
069                return true;
070            }
071        }
072        return false;
073    }
074
075
076    /**
077     * 根据数据库响应结果判断数据库操作是否成功。
078     *
079     * @param result 数据库操作返回影响条数
080     * @return {@code true} 操作成功,{@code false} 操作失败。
081     */
082    public static boolean toBool(int result) {
083        return result > 0 || result == -2;
084    }
085
086    public static boolean toBool(long result) {
087        return result > 0;
088    }
089
090
091    /**
092     * 根据数据库响应结果判断数据库操作是否成功。
093     * 有 1 条数据成功便算成功
094     *
095     * @param results 操作数据的响应成功条数
096     * @return {@code true} 操作成功,{@code false} 操作失败。
097     */
098    public static boolean toBool(int[] results) {
099        for (int result : results) {
100            if (toBool(result)) {
101                return true;
102            }
103        }
104        return false;
105    }
106
107
108    /**
109     * 替换 sql 中的问号 ?
110     *
111     * @param sql    sql 内容
112     * @param params 参数
113     * @return 完整的 sql
114     */
115    public static String replaceSqlParams(String sql, Object[] params) {
116        if (params == null || params.length == 0) {
117            return sql;
118        }
119
120        StringBuilder sqlBuilder = new StringBuilder();
121        char quote = 0;
122        int index = 0;
123        for (int i = 0; i < sql.length(); ++i) {
124            char ch = sql.charAt(i);
125            if (ch == '\'') {
126                if (quote == 0) {
127                    quote = ch;
128                } else if (quote == '\'') {
129                    quote = 0;
130                }
131            } else if (ch == '"') {
132                if (quote == 0) {
133                    quote = ch;
134                } else if (quote == '"') {
135                    quote = 0;
136                }
137            }
138            if (quote == 0 && ch == '?' && index < params.length) {
139                sqlBuilder.append(getParamString(params, index++));
140            } else {
141                sqlBuilder.append(ch);
142            }
143        }
144
145        return sqlBuilder.toString();
146    }
147
148
149    private static String getParamString(Object[] params, int index) {
150        Object value = params[index];
151        if (value == null) {
152            return "null";
153        }
154        // number or bool
155        else if (value instanceof Number || value instanceof Boolean) {
156            return value.toString();
157        }
158        // array
159        else if (ClassUtil.isArray(value.getClass())) {
160            StringJoiner joiner = new StringJoiner(",", "[", "]");
161            for (int i = 0; i < Array.getLength(value); i++) {
162                joiner.add(String.valueOf(Array.get(value, i)));
163            }
164            return joiner.toString();
165        }
166        // other
167        else {
168            StringBuilder sb = new StringBuilder();
169            sb.append("'");
170            if (value instanceof Date) {
171                sb.append(DateUtil.toDateTimeString((Date) value));
172            } else if (value instanceof LocalDateTime) {
173                sb.append(((LocalDateTime) value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
174            } else {
175                sb.append(value);
176            }
177            sb.append("'");
178            return Matcher.quoteReplacement(sb.toString());
179        }
180    }
181
182
183    public static String buildSqlParamPlaceholder(int count) {
184        StringBuilder sb = new StringBuilder(BRACKET_LEFT);
185        for (int i = 0; i < count; i++) {
186            sb.append(PLACEHOLDER);
187            if (i != count - 1) {
188                sb.append(DELIMITER);
189            }
190        }
191        sb.append(BRACKET_RIGHT);
192        return sb.toString();
193    }
194
195}