in src/main/user-impl/java/com/mysql/cj/xdevapi/ExprParser.java [769:913]
Expr atomicExpr() { // constant, identifier, variable, function call, etc
if (this.tokenPos >= this.tokens.size()) {
throw new WrongArgumentException("No more tokens when expecting one at token position " + this.tokenPos);
}
Token t = this.tokens.get(this.tokenPos);
this.tokenPos++; // consume
switch (t.type) {
case EROTEME:
case COLON: {
String placeholderName;
if (currentTokenTypeEquals(TokenType.LNUM_INT)) {
// int pos = Integer.parseInt(consumeToken(TokenType.LNUM_INT));
// return Expr.newBuilder().setType(Expr.Type.PLACEHOLDER).setPosition(pos).build();
placeholderName = consumeToken(TokenType.LNUM_INT);
} else if (currentTokenTypeEquals(TokenType.IDENT)) {
placeholderName = consumeToken(TokenType.IDENT);
} else if (t.type == TokenType.EROTEME) {
placeholderName = String.valueOf(this.positionalPlaceholderCount);
} else {
throw new WrongArgumentException("Invalid placeholder name at token position " + this.tokenPos);
}
Expr.Builder placeholder = Expr.newBuilder().setType(Expr.Type.PLACEHOLDER);
if (this.placeholderNameToPosition.containsKey(placeholderName)) {
placeholder.setPosition(this.placeholderNameToPosition.get(placeholderName));
} else {
placeholder.setPosition(this.positionalPlaceholderCount);
this.placeholderNameToPosition.put(placeholderName, this.positionalPlaceholderCount);
this.positionalPlaceholderCount++;
}
return placeholder.build();
}
case LPAREN: {
Expr e = expr();
consumeToken(TokenType.RPAREN);
return e;
}
case LCURLY: { // JSON object
Object.Builder builder = Object.newBuilder();
if (currentTokenTypeEquals(TokenType.LSTRING)) {
parseCommaSeparatedList(() -> {
String key = consumeToken(TokenType.LSTRING);
consumeToken(TokenType.COLON);
Expr value = expr();
return Collections.singletonMap(key, value);
}).stream().map(pair -> pair.entrySet().iterator().next()).map(e -> ObjectField.newBuilder().setKey(e.getKey()).setValue(e.getValue()))
.forEach(builder::addFld);
}
consumeToken(TokenType.RCURLY);
return Expr.newBuilder().setType(Expr.Type.OBJECT).setObject(builder.build()).build();
}
case LSQBRACKET: { // Array
Array.Builder builder = Expr.newBuilder().setType(Expr.Type.ARRAY).getArrayBuilder();
if (!currentTokenTypeEquals(TokenType.RSQBRACKET)) {
parseCommaSeparatedList(this::expr).stream().forEach(builder::addValue);
}
consumeToken(TokenType.RSQBRACKET);
return Expr.newBuilder().setType(Expr.Type.ARRAY).setArray(builder).build();
}
case CAST: {
consumeToken(TokenType.LPAREN);
Operator.Builder builder = Operator.newBuilder().setName(TokenType.CAST.toString().toLowerCase());
builder.addParam(expr());
consumeToken(TokenType.AS);
StringBuilder typeStr = new StringBuilder(this.tokens.get(this.tokenPos).value.toUpperCase());
// ensure next token is a valid type argument to CAST
if (currentTokenTypeEquals(TokenType.DECIMAL)) {
this.tokenPos++;
if (currentTokenTypeEquals(TokenType.LPAREN)) {
typeStr.append(consumeToken(TokenType.LPAREN));
typeStr.append(consumeToken(TokenType.LNUM_INT));
if (currentTokenTypeEquals(TokenType.COMMA)) {
typeStr.append(consumeToken(TokenType.COMMA));
typeStr.append(consumeToken(TokenType.LNUM_INT));
}
typeStr.append(consumeToken(TokenType.RPAREN));
}
} else if (currentTokenTypeEquals(TokenType.CHAR) || currentTokenTypeEquals(TokenType.BINARY)) {
this.tokenPos++;
if (currentTokenTypeEquals(TokenType.LPAREN)) {
typeStr.append(consumeToken(TokenType.LPAREN));
typeStr.append(consumeToken(TokenType.LNUM_INT));
typeStr.append(consumeToken(TokenType.RPAREN));
}
} else if (currentTokenTypeEquals(TokenType.UNSIGNED) || currentTokenTypeEquals(TokenType.SIGNED)) {
this.tokenPos++;
if (currentTokenTypeEquals(TokenType.INTEGER)) {
// don't add optional INTEGER to type string argument
consumeToken(TokenType.INTEGER);
}
} else if (currentTokenTypeEquals(TokenType.JSON) || currentTokenTypeEquals(TokenType.DATE) || currentTokenTypeEquals(TokenType.DATETIME)
|| currentTokenTypeEquals(TokenType.TIME)) {
this.tokenPos++;
} else {
throw new WrongArgumentException("Expected valid CAST type argument at " + this.tokenPos);
}
consumeToken(TokenType.RPAREN);
// TODO charset?
builder.addParam(ExprUtil.buildLiteralScalar(typeStr.toString().getBytes()));
return Expr.newBuilder().setType(Expr.Type.OPERATOR).setOperator(builder.build()).build();
}
case PLUS:
case MINUS:
if (currentTokenTypeEquals(TokenType.LNUM_INT) || currentTokenTypeEquals(TokenType.LNUM_DOUBLE)) {
// unary operators are handled inline making positive or negative numeric literals
this.tokens.get(this.tokenPos).value = t.value + this.tokens.get(this.tokenPos).value;
return atomicExpr();
}
return buildUnaryOp(t.value, atomicExpr());
case NOT:
case NEG:
case BANG:
return buildUnaryOp(t.value, atomicExpr());
case LSTRING:
return ExprUtil.buildLiteralScalar(t.value);
case NULL:
return ExprUtil.buildLiteralNullScalar();
case LNUM_INT:
return ExprUtil.buildLiteralScalar(Long.parseLong(t.value));
case LNUM_DOUBLE:
return ExprUtil.buildLiteralScalar(Double.parseDouble(t.value));
case TRUE:
case FALSE:
return ExprUtil.buildLiteralScalar(t.type == TokenType.TRUE);
case DOLLAR:
return documentField();
case STAR:
// special "0-ary" consideration of "*" as an operator (for COUNT(*), etc)
return starOperator();
case IDENT:
this.tokenPos--; // stay on the identifier
// check for function call which may be: func(...) or schema.func(...)
if (nextTokenTypeEquals(TokenType.LPAREN) || posTokenTypeEquals(this.tokenPos + 1, TokenType.DOT)
&& posTokenTypeEquals(this.tokenPos + 2, TokenType.IDENT) && posTokenTypeEquals(this.tokenPos + 3, TokenType.LPAREN)) {
return functionCall();
}
if (this.allowRelationalColumns) {
return columnIdentifier();
}
return documentField();
default:
break;
}
throw new WrongArgumentException("Cannot find atomic expression at token position " + (this.tokenPos - 1));
}