|
18 | 18 | import com.google.common.collect.ImmutableMap; |
19 | 19 | import com.google.common.collect.ImmutableSet; |
20 | 20 | import com.google.common.collect.Maps; |
| 21 | +import io.netty.util.collection.IntObjectHashMap; |
21 | 22 | import org.slf4j.Logger; |
22 | 23 | import org.slf4j.LoggerFactory; |
23 | 24 |
|
@@ -45,7 +46,7 @@ public class Metadata { |
45 | 46 | final ReentrantLock lock = new ReentrantLock(); |
46 | 47 |
|
47 | 48 | // See https://github.com/apache/cassandra/blob/trunk/doc/cql3/CQL.textile#appendixA |
48 | | - private static final Set<String> RESERVED_KEYWORDS = ImmutableSet.of( |
| 49 | + private static final IntObjectHashMap<List<char[]>> RESERVED_KEYWORDS = indexByCaseInsensitiveHash( |
49 | 50 | "add", "allow", "alter", "and", "any", "apply", "asc", "authorize", "batch", "begin", "by", |
50 | 51 | "columnfamily", "create", "delete", "desc", "drop", "each_quorum", "from", "grant", "in", |
51 | 52 | "index", "inet", "infinity", "insert", "into", "keyspace", "keyspaces", "limit", "local_one", |
@@ -125,25 +126,32 @@ static String handleId(String id) { |
125 | 126 | if (id == null) |
126 | 127 | return null; |
127 | 128 |
|
128 | | - if (isAlphanumeric(id)) |
129 | | - return id.toLowerCase(); |
130 | | - |
131 | | - // Check if it's enclosed in quotes. If it is, remove them and unescape internal double quotes |
132 | | - return ParseUtils.unDoubleQuote(id); |
133 | | - } |
134 | | - |
135 | | - private static boolean isAlphanumeric(String s) { |
136 | | - for (int i = 0; i < s.length(); i++) { |
137 | | - char c = s.charAt(i); |
138 | | - if (!( |
| 129 | + boolean isAlphanumericLowCase = true; |
| 130 | + boolean isAlphanumeric = true; |
| 131 | + for (int i = 0; i < id.length(); i++) { |
| 132 | + char c = id.charAt(i); |
| 133 | + if (c >= 65 && c <= 90) { // A-Z |
| 134 | + isAlphanumericLowCase = false; |
| 135 | + } else if (!( |
139 | 136 | (c >= 48 && c <= 57) // 0-9 |
140 | | - || (c >= 65 && c <= 90) // A-Z |
141 | 137 | || (c == 95) // _ (underscore) |
142 | 138 | || (c >= 97 && c <= 122) // a-z |
143 | | - )) |
144 | | - return false; |
| 139 | + )) { |
| 140 | + isAlphanumeric = false; |
| 141 | + isAlphanumericLowCase = false; |
| 142 | + break; |
| 143 | + } |
145 | 144 | } |
146 | | - return true; |
| 145 | + |
| 146 | + if (isAlphanumericLowCase) { |
| 147 | + return id; |
| 148 | + } |
| 149 | + if (isAlphanumeric) { |
| 150 | + return id.toLowerCase(); |
| 151 | + } |
| 152 | + |
| 153 | + // Check if it's enclosed in quotes. If it is, remove them and unescape internal double quotes |
| 154 | + return ParseUtils.unDoubleQuote(id); |
147 | 155 | } |
148 | 156 |
|
149 | 157 | /** |
@@ -273,8 +281,73 @@ public static String quote(String id) { |
273 | 281 | * @return {@code true} if the given identifier is a known reserved |
274 | 282 | * CQL keyword, {@code false} otherwise. |
275 | 283 | */ |
| 284 | + |
276 | 285 | public static boolean isReservedCqlKeyword(String id) { |
277 | | - return id != null && RESERVED_KEYWORDS.contains(id.toLowerCase()); |
| 286 | + if (id == null) { |
| 287 | + return false; |
| 288 | + } |
| 289 | + int hash = caseInsensitiveHash(id); |
| 290 | + List<char[]> keywords = RESERVED_KEYWORDS.get(hash); |
| 291 | + if (keywords == null) { |
| 292 | + return false; |
| 293 | + } else { |
| 294 | + for (char[] keyword : keywords) { |
| 295 | + if (equalsIgnoreCaseAscii(id, keyword)) { |
| 296 | + return true; |
| 297 | + } |
| 298 | + } |
| 299 | + return false; |
| 300 | + } |
| 301 | + } |
| 302 | + |
| 303 | + private static int caseInsensitiveHash(String str) { |
| 304 | + int hashCode = 17; |
| 305 | + for (int i = 0; i < str.length(); i++) { |
| 306 | + char c = toLowerCaseAscii(str.charAt(i)); |
| 307 | + hashCode = 31 * hashCode + c; |
| 308 | + } |
| 309 | + return hashCode; |
| 310 | + } |
| 311 | + |
| 312 | + // keyword is expected as a second argument always in low case |
| 313 | + private static boolean equalsIgnoreCaseAscii(String str1, char[] str2LowCase) { |
| 314 | + if (str1.length() != str2LowCase.length) return false; |
| 315 | + |
| 316 | + for (int i = 0; i < str1.length(); i++) { |
| 317 | + char c1 = str1.charAt(i); |
| 318 | + char c2Low = str2LowCase[i]; |
| 319 | + if (c1 == c2Low) { |
| 320 | + continue; |
| 321 | + } |
| 322 | + char low1 = toLowerCaseAscii(c1); |
| 323 | + if (low1 == c2Low) { |
| 324 | + continue; |
| 325 | + } |
| 326 | + return false; |
| 327 | + } |
| 328 | + return true; |
| 329 | + } |
| 330 | + |
| 331 | + private static char toLowerCaseAscii(char c) { |
| 332 | + if (c >= 65 && c <= 90) { // A-Z |
| 333 | + c ^= 0x20; // convert to low case |
| 334 | + } |
| 335 | + return c; |
| 336 | + } |
| 337 | + |
| 338 | + private static IntObjectHashMap<List<char[]>> indexByCaseInsensitiveHash(String... words) { |
| 339 | + IntObjectHashMap<List<char[]>> result = new IntObjectHashMap<List<char[]>>(); |
| 340 | + for (String word : words) { |
| 341 | + char[] wordAsCharArray = word.toLowerCase().toCharArray(); |
| 342 | + int hash = caseInsensitiveHash(word); |
| 343 | + List<char[]> list = result.get(hash); |
| 344 | + if (list == null) { |
| 345 | + list = new ArrayList<char[]>(); |
| 346 | + result.put(hash, list); |
| 347 | + } |
| 348 | + list.add(wordAsCharArray); |
| 349 | + } |
| 350 | + return result; |
278 | 351 | } |
279 | 352 |
|
280 | 353 | /** |
|
0 commit comments