View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shardingsphere.sql.parser.sql.common.util;
19  
20  import com.google.common.base.CharMatcher;
21  import com.google.common.base.Strings;
22  import lombok.AccessLevel;
23  import lombok.NoArgsConstructor;
24  import org.apache.shardingsphere.sql.parser.api.ASTNode;
25  import org.apache.shardingsphere.sql.parser.sql.common.enums.Paren;
26  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.expr.ExpressionSegment;
27  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.expr.complex.CommonExpressionSegment;
28  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.expr.simple.LiteralExpressionSegment;
29  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.JoinTableSegment;
30  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.SubqueryTableSegment;
31  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.TableSegment;
32  import org.apache.shardingsphere.sql.parser.sql.common.value.literal.impl.BooleanLiteralValue;
33  import org.apache.shardingsphere.sql.parser.sql.common.value.literal.impl.NullLiteralValue;
34  import org.apache.shardingsphere.sql.parser.sql.common.value.literal.impl.NumberLiteralValue;
35  import org.apache.shardingsphere.sql.parser.sql.common.value.literal.impl.OtherLiteralValue;
36  import org.apache.shardingsphere.sql.parser.sql.common.value.literal.impl.StringLiteralValue;
37  
38  import java.math.BigDecimal;
39  import java.math.BigInteger;
40  import java.util.LinkedList;
41  import java.util.List;
42  
43  /**
44   * SQL utility class.
45   */
46  @NoArgsConstructor(access = AccessLevel.PRIVATE)
47  public final class SQLUtils {
48      
49      private static final String BACKTICK = "`";
50      
51      private static final String SQL_END = ";";
52      
53      private static final String COMMENT_PREFIX = "/*";
54      
55      private static final String COMMENT_SUFFIX = "*/";
56      
57      private static final String EXCLUDED_CHARACTERS = "[]'\"";
58      
59      /**
60       * Get exactly number value and type.
61       *
62       * @param value string to be converted
63       * @param radix radix
64       * @return exactly number value and type
65       */
66      public static Number getExactlyNumber(final String value, final int radix) {
67          try {
68              return getBigInteger(value, radix);
69          } catch (final NumberFormatException ex) {
70              return new BigDecimal(value);
71          }
72      }
73      
74      private static Number getBigInteger(final String value, final int radix) {
75          BigInteger result = new BigInteger(value, radix);
76          if (result.compareTo(new BigInteger(String.valueOf(Integer.MIN_VALUE))) >= 0 && result.compareTo(new BigInteger(String.valueOf(Integer.MAX_VALUE))) <= 0) {
77              return result.intValue();
78          }
79          if (result.compareTo(new BigInteger(String.valueOf(Long.MIN_VALUE))) >= 0 && result.compareTo(new BigInteger(String.valueOf(Long.MAX_VALUE))) <= 0) {
80              return result.longValue();
81          }
82          return result;
83      }
84      
85      /**
86       * Get exactly value for SQL expression.
87       *
88       * <p>remove special char for SQL expression</p>
89       *
90       * @param value SQL expression
91       * @return exactly SQL expression
92       */
93      public static String getExactlyValue(final String value) {
94          return null == value ? null : tryGetRealContentInBackticks(CharMatcher.anyOf(EXCLUDED_CHARACTERS).removeFrom(value));
95      }
96      
97      /**
98       * Get exactly value for SQL expression.
99       *
100      * <p>remove special char for SQL expression</p>
101      *
102      * @param value SQL expression
103      * @param reservedCharacters characters to be reserved
104      * @return exactly SQL expression
105      */
106     public static String getExactlyValue(final String value, final String reservedCharacters) {
107         if (null == value) {
108             return null;
109         }
110         String toBeExcludedCharacters = CharMatcher.anyOf(reservedCharacters).removeFrom(EXCLUDED_CHARACTERS);
111         return CharMatcher.anyOf(toBeExcludedCharacters).removeFrom(value);
112     }
113     
114     /**
115      * Try get exactly value for backticks string.
116      *
117      * <p>try get content containing backticks exactly value</p>
118      *
119      * @param value SQL expression
120      * @return exactly SQL expression
121      */
122     public static String tryGetRealContentInBackticks(final String value) {
123         if (null == value) {
124             return null;
125         }
126         if (value.startsWith(BACKTICK) && value.endsWith(BACKTICK)) {
127             int startIndex = 1;
128             int stopIndex = value.length() - 1;
129             StringBuilder exactlyTableName = new StringBuilder();
130             while (startIndex < stopIndex) {
131                 if (value.charAt(startIndex) == '`' && (startIndex + 1 >= stopIndex || value.charAt(startIndex + 1) != '`')) {
132                     return value;
133                 } else if (value.charAt(startIndex) == '`' && value.charAt(startIndex + 1) == '`') {
134                     startIndex++;
135                 }
136                 exactlyTableName.append(value.charAt(startIndex));
137                 startIndex++;
138             }
139             return 0 == exactlyTableName.length() ? value : exactlyTableName.toString();
140         }
141         return value;
142     }
143     
144     /**
145      * Get exactly SQL expression.
146      *
147      * <p>remove space for SQL expression</p>
148      *
149      * @param value SQL expression
150      * @return exactly SQL expression
151      */
152     public static String getExactlyExpression(final String value) {
153         return Strings.isNullOrEmpty(value) ? value : CharMatcher.anyOf(" ").removeFrom(value);
154     }
155     
156     /**
157      * Get exactly SQL expression without outside parentheses.
158      *
159      * @param value SQL expression
160      * @return exactly SQL expression
161      */
162     public static String getExpressionWithoutOutsideParentheses(final String value) {
163         int parenthesesOffset = getParenthesesOffset(value);
164         return 0 == parenthesesOffset ? value : value.substring(parenthesesOffset, value.length() - parenthesesOffset);
165     }
166     
167     private static int getParenthesesOffset(final String value) {
168         int result = 0;
169         if (Strings.isNullOrEmpty(value)) {
170             return result;
171         }
172         while (Paren.PARENTHESES.getLeftParen() == value.charAt(result)) {
173             result++;
174         }
175         return result;
176     }
177     
178     /**
179      * Get subquery from tableSegment.
180      *
181      * @param tableSegment TableSegment
182      * @return exactly SubqueryTableSegment list
183      */
184     public static List<SubqueryTableSegment> getSubqueryTableSegmentFromTableSegment(final TableSegment tableSegment) {
185         List<SubqueryTableSegment> result = new LinkedList<>();
186         if (tableSegment instanceof SubqueryTableSegment) {
187             result.add((SubqueryTableSegment) tableSegment);
188         }
189         if (tableSegment instanceof JoinTableSegment) {
190             result.addAll(getSubqueryTableSegmentFromJoinTableSegment((JoinTableSegment) tableSegment));
191         }
192         return result;
193     }
194     
195     private static List<SubqueryTableSegment> getSubqueryTableSegmentFromJoinTableSegment(final JoinTableSegment joinTableSegment) {
196         List<SubqueryTableSegment> result = new LinkedList<>();
197         if (joinTableSegment.getLeft() instanceof SubqueryTableSegment) {
198             result.add((SubqueryTableSegment) joinTableSegment.getLeft());
199         } else if (joinTableSegment.getLeft() instanceof JoinTableSegment) {
200             result.addAll(getSubqueryTableSegmentFromJoinTableSegment((JoinTableSegment) joinTableSegment.getLeft()));
201         }
202         if (joinTableSegment.getRight() instanceof SubqueryTableSegment) {
203             result.add((SubqueryTableSegment) joinTableSegment.getRight());
204         } else if (joinTableSegment.getRight() instanceof JoinTableSegment) {
205             result.addAll(getSubqueryTableSegmentFromJoinTableSegment((JoinTableSegment) joinTableSegment.getRight()));
206         }
207         return result;
208     }
209     
210     /**
211      * Create literal expression.
212      *
213      * @param astNode    AST node
214      * @param startIndex start index
215      * @param stopIndex  stop index
216      * @param text       text
217      * @return literal expression segment
218      */
219     public static ExpressionSegment createLiteralExpression(final ASTNode astNode, final int startIndex, final int stopIndex, final String text) {
220         if (astNode instanceof StringLiteralValue) {
221             return new LiteralExpressionSegment(startIndex, stopIndex, ((StringLiteralValue) astNode).getValue());
222         }
223         if (astNode instanceof NumberLiteralValue) {
224             return new LiteralExpressionSegment(startIndex, stopIndex, ((NumberLiteralValue) astNode).getValue());
225         }
226         if (astNode instanceof BooleanLiteralValue) {
227             return new LiteralExpressionSegment(startIndex, stopIndex, ((BooleanLiteralValue) astNode).getValue());
228         }
229         if (astNode instanceof NullLiteralValue) {
230             return new LiteralExpressionSegment(startIndex, stopIndex, null);
231         }
232         if (astNode instanceof OtherLiteralValue) {
233             return new CommonExpressionSegment(startIndex, stopIndex, ((OtherLiteralValue) astNode).getValue());
234         }
235         return new CommonExpressionSegment(startIndex, stopIndex, text);
236     }
237     
238     /**
239      * Trim the semicolon of SQL.
240      *
241      * @param sql SQL to be trim
242      * @return SQL without semicolon
243      */
244     public static String trimSemicolon(final String sql) {
245         return sql.endsWith(SQL_END) ? sql.substring(0, sql.length() - 1) : sql;
246     }
247     
248     /**
249      * Trim the comment of SQL.
250      *
251      * @param sql SQL to be trim
252      * @return remove comment from SQL
253      */
254     public static String trimComment(final String sql) {
255         String result = sql;
256         if (sql.startsWith(COMMENT_PREFIX)) {
257             result = result.substring(sql.indexOf(COMMENT_SUFFIX) + 2);
258         }
259         if (sql.endsWith(SQL_END)) {
260             result = result.substring(0, result.length() - 1);
261         }
262         return result.trim();
263     }
264 }