* </ul>
*/
@SuppressWarnings("null")
static final void renderAndBind(Context<?> ctx, String sql, List<QueryPart> substitutes) {
RenderContext render = (RenderContext) ((ctx instanceof RenderContext) ? ctx : null);
BindContext bind = (BindContext) ((ctx instanceof BindContext) ? ctx : null);
int substituteIndex = 0;
char[] sqlChars = sql.toCharArray();
// [#1593] Create a dummy renderer if we're in bind mode
if (render == null) render = new DefaultRenderContext(bind.configuration());
SQLDialect dialect = render.configuration().dialect();
SQLDialect family = dialect.family();
String[][] quotes = QUOTES.get(family);
// [#3630] Depending on this setting, we need to consider backslashes as escape characters within string literals.
boolean needsBackslashEscaping = needsBackslashEscaping(ctx.configuration());
for (int i = 0; i < sqlChars.length; i++) {
// [#1797] Skip content inside of single-line comments, e.g.
// select 1 x -- what's this ?'?
// from t_book -- what's that ?'?
// where id = ?
if (peek(sqlChars, i, "--")) {
// Consume the complete comment
for (; i < sqlChars.length && sqlChars[i] != '\r' && sqlChars[i] != '\n'; render.sql(sqlChars[i++]));
// Consume the newline character
if (i < sqlChars.length) render.sql(sqlChars[i]);
}
// [#1797] Skip content inside of multi-line comments, e.g.
// select 1 x /* what's this ?'?
// I don't know ?'? */
// from t_book where id = ?
else if (peek(sqlChars, i, "/*")) {
// Consume the complete comment
for (; !peek(sqlChars, i, "*/"); render.sql(sqlChars[i++]));
// Consume the comment delimiter
render.sql(sqlChars[i++]);
render.sql(sqlChars[i]);
}
// [#1031] [#1032] Skip ? inside of string literals, e.g.
// insert into x values ('Hello? Anybody out there?');
else if (sqlChars[i] == '\'') {
// Consume the initial string literal delimiter
render.sql(sqlChars[i++]);
// Consume the whole string literal
for (;;) {
// [#3000] [#3630] Consume backslash-escaped characters if needed
if (sqlChars[i] == '\\' && needsBackslashEscaping) {
render.sql(sqlChars[i++]);
}
// Consume an escaped apostrophe
else if (peek(sqlChars, i, "''")) {
render.sql(sqlChars[i++]);
}
// Break on the terminal string literal delimiter
else if (peek(sqlChars, i, "'")) {
break;
}
// Consume string literal content
render.sql(sqlChars[i++]);
}
// Consume the terminal string literal delimiter
render.sql(sqlChars[i]);
}
// [#3297] Skip ? inside of quoted identifiers, e.g.
// update x set v = "Column Name with a ? (question mark)"
else if (peekAny(sqlChars, i, quotes[QUOTE_START_DELIMITER])) {
// Main identifier delimiter or alternative one?
int delimiter = 0;
for (int d = 0; d < quotes[QUOTE_START_DELIMITER].length; d++) {
if (peek(sqlChars, i, quotes[QUOTE_START_DELIMITER][d])) {
delimiter = d;
break;
}
}
// Consume the initial identifier delimiter
for (int d = 0; d < quotes[QUOTE_START_DELIMITER][delimiter].length(); d++)
render.sql(sqlChars[i++]);
// Consume the whole identifier
for (;;) {
// Consume an escaped quote
if (peek(sqlChars, i, quotes[QUOTE_END_DELIMITER_ESCAPED][delimiter])) {
for (int d = 0; d < quotes[QUOTE_END_DELIMITER_ESCAPED][delimiter].length(); d++)
render.sql(sqlChars[i++]);
}
// Break on the terminal identifier delimiter
else if (peek(sqlChars, i, quotes[QUOTE_END_DELIMITER][delimiter])) {
break;
}
// Consume identifier content
render.sql(sqlChars[i++]);
}
// Consume the terminal identifier delimiter
for (int d = 0; d < quotes[QUOTE_END_DELIMITER][delimiter].length(); d++) {
if (d > 0)
i++;
render.sql(sqlChars[i]);
}
}
// Inline bind variables only outside of string literals
else if (sqlChars[i] == '?' && substituteIndex < substitutes.size()) {
QueryPart substitute = substitutes.get(substituteIndex++);
if (render.paramType() == INLINED || render.paramType() == NAMED || render.paramType() == NAMED_OR_INLINED) {
render.visit(substitute);
}
else {
render.sql(sqlChars[i]);
}
if (bind != null) {
bind.visit(substitute);
}
}
// [#1432] Inline substitues for {numbered placeholders} outside of string literals
else if (sqlChars[i] == '{') {
// [#1461] Be careful not to match any JDBC escape syntax
if (peekAny(sqlChars, i, JDBC_ESCAPE_PREFIXES, true)) {
render.sql(sqlChars[i]);
}
// Consume the whole token
else {
int start = ++i;
for (; i < sqlChars.length && sqlChars[i] != '}'; i++);
int end = i;
String token = sql.substring(start, end);
// Try getting the {numbered placeholder}
try {
QueryPart substitute = substitutes.get(Integer.valueOf(token));
render.visit(substitute);
if (bind != null) {
bind.visit(substitute);
}
}
// If the above failed, then we're dealing with a {keyword}
catch (NumberFormatException e) {