in squangle/mysql_client/Query.cpp [464:621]
folly::fbstring Query::render(
MYSQL* conn,
const std::vector<QueryArgument>& params) const {
auto querySp = query_text_.getQuery();
if (unsafe_query_) {
return querySp.to<folly::fbstring>();
}
auto offset = querySp.find_first_of(";'\"`");
if (offset != folly::StringPiece::npos) {
parseError(querySp, offset, "Saw dangerous characters in SQL query");
}
folly::fbstring ret;
ret.reserve(querySp.size() + 8 * params.size());
auto current_param = params.begin();
bool after_percent = false;
size_t idx;
// Walk our string, watching for % values.
for (idx = 0; idx < querySp.size(); ++idx) {
char c = querySp[idx];
if (!after_percent) {
if (c != '%') {
ret.push_back(c);
} else {
after_percent = true;
}
continue;
}
after_percent = false;
if (c == '%') {
ret.push_back('%');
continue;
}
if (current_param == params.end()) {
parseError(querySp, idx, "too few parameters for query");
}
const auto& param = *current_param++;
if (c == 'd' || c == 's' || c == 'f' || c == 'u') {
appendValue(&ret, idx, c, param, conn);
} else if (c == 'm') {
if (!(param.isString() || param.isInt() || param.isDouble() ||
param.isBool() || param.isNull())) {
parseError(querySp, idx, "%m expects int/float/string/bool");
}
appendValue(&ret, idx, c, param, conn);
} else if (c == 'K') {
ret.append("/*");
appendComment(&ret, param);
ret.append("*/");
} else if (c == 'T' || c == 'C') {
appendColumnTableName(&ret, param);
} else if (c == '=') {
folly::StringPiece type = advance(querySp, &idx, 1);
if (type != "d" && type != "s" && type != "f" && type != "u" &&
type != "m") {
parseError(querySp, idx, "expected %=d, %=f, %=s, %=u, or %=m");
}
if (param.isNull()) {
ret.append(" IS NULL");
} else {
ret.append(" = ");
appendValue(&ret, idx, type[0], param, conn);
}
} else if (c == 'V') {
if (param.isQuery()) {
parseError(querySp, idx, "%V doesn't allow subquery");
}
size_t col_idx;
size_t row_len = 0;
bool first_row = true;
bool first_in_row = true;
for (const auto& row : param.getList()) {
first_in_row = true;
col_idx = 0;
if (!first_row) {
ret.append(", ");
}
ret.append("(");
for (const auto& col : row.getList()) {
if (!first_in_row) {
ret.append(", ");
}
appendValue(&ret, idx, 'v', col, conn);
col_idx++;
first_in_row = false;
if (first_row) {
row_len++;
}
}
ret.append(")");
if (first_row) {
first_row = false;
} else if (col_idx != row_len) {
parseError(
querySp,
idx,
"not all rows provided for %V formatter are the same size");
}
}
} else if (c == 'L') {
folly::StringPiece type = advance(querySp, &idx, 1);
if (type == "O" || type == "A") {
ret.append("(");
const char* sep = (type == "O") ? " OR " : " AND ";
appendValueClauses(&ret, &idx, sep, param, conn);
ret.append(")");
} else {
if (!param.isList()) {
parseError(querySp, idx, "expected array for %L formatter");
}
bool first_param = true;
for (const auto& val : param.getList()) {
if (!first_param) {
ret.append(", ");
}
first_param = false;
if (type == "C") {
appendColumnTableName(&ret, val);
} else {
appendValue(&ret, idx, type[0], val, conn);
}
}
}
} else if (c == 'U' || c == 'W') {
if (c == 'W') {
appendValueClauses(&ret, &idx, " AND ", param, conn);
} else {
appendValueClauses(&ret, &idx, ", ", param, conn);
}
} else if (c == 'Q') {
if (param.isQuery()) {
ret.append(param.getQuery().render(conn));
} else {
ret.append((param).asString());
}
} else {
parseError(querySp, idx, "unknown % code");
}
}
if (after_percent) {
parseError(querySp, idx, "string ended with unfinished % code");
}
if (current_param != params.end()) {
parseError(querySp, 0, "too many parameters specified for query");
}
return ret;
}