use of org.jooq.RenderContext in project jOOQ by jOOQ.
the class Tools method renderAndBind.
/**
* Render and bind a list of {@link QueryPart} to plain SQL
* <p>
* This will perform two actions:
* <ul>
* <li>When {@link RenderContext} is provided, it will render plain SQL to
* the context, substituting {numbered placeholders} and bind values if
* {@link RenderContext#inline()} is set</li>
* <li>When {@link BindContext} is provided, it will bind the list of
* {@link QueryPart} according to the {numbered placeholders} and bind
* values in the sql string</li>
* </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();
boolean mysql = asList(MARIADB, MYSQL).contains(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());
characterLoop: for (int i = 0; i < sqlChars.length; i++) {
// where id = ?
if (peek(sqlChars, i, "--") || // http://bugs.mysql.com/bug.php?id=76623
(mysql && 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]);
} else // from t_book where id = ?
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]);
} else // insert into x values ('Hello? Anybody out there?');
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++]);
} else // Consume an escaped apostrophe
if (peek(sqlChars, i, "''")) {
render.sql(sqlChars[i++]);
} else // Break on the terminal string literal delimiter
if (peek(sqlChars, i, "'")) {
break;
}
// Consume string literal content
render.sql(sqlChars[i++]);
}
// Consume the terminal string literal delimiter
render.sql(sqlChars[i]);
} else // update x set v = "Column Name with a ? (question mark)"
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++]);
} else // Break on the terminal identifier delimiter
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]);
}
} else // Inline bind variables only outside of string literals
if (substituteIndex < substitutes.size() && ((sqlChars[i] == '?') || // Watch out for the PostgreSQL cast operator ::
(sqlChars[i] == ':' && i + 1 < sqlChars.length && isJavaIdentifierPart(sqlChars[i + 1]) && (i - 1 < 0 || sqlChars[i - 1] != ':')))) {
// [#5307] Consume PostgreSQL style operators. These aren't bind variables!
if (sqlChars[i] == '?' && i + 1 < sqlChars.length) {
for (String suffix : NON_BIND_VARIABLE_SUFFIXES) {
if (peek(sqlChars, i + 1, suffix)) {
for (int j = i; i - j <= suffix.length(); i++) render.sql(sqlChars[i]);
render.sql(sqlChars[i]);
continue characterLoop;
}
}
}
// [#4131] Consume the named bind variable
if (sqlChars[i] == ':')
while (++i < sqlChars.length && isJavaIdentifierPart(sqlChars[i])) ;
QueryPart substitute = substitutes.get(substituteIndex++);
if (render.paramType() == INLINED || render.paramType() == NAMED || render.paramType() == NAMED_OR_INLINED) {
render.visit(substitute);
} else {
CastMode previous = render.castMode();
render.castMode(CastMode.NEVER).visit(substitute).castMode(previous);
}
if (bind != null) {
bind.visit(substitute);
}
} else // [#1432] Inline substitues for {numbered placeholders} outside of string literals
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]);
} else // Consume the whole token
{
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) {
render.keyword(token);
}
}
} else // Any other character
{
render.sql(sqlChars[i]);
}
}
}
use of org.jooq.RenderContext in project jOOQ by jOOQ.
the class Val method accept.
// ------------------------------------------------------------------------
// XXX: Field API
// ------------------------------------------------------------------------
@Override
public void accept(Context<?> ctx) {
if (ctx instanceof RenderContext) {
ParamType paramType = ctx.paramType();
if (isInline(ctx))
ctx.paramType(ParamType.INLINED);
try {
getBinding().sql(new DefaultBindingSQLContext<T>(ctx.configuration(), ctx.data(), (RenderContext) ctx, value, getBindVariable(ctx)));
} catch (SQLException e) {
throw new DataAccessException("Error while generating SQL for Binding", e);
}
ctx.paramType(paramType);
} else {
// [#1302] Bind value only if it was not explicitly forced to be inlined
if (!isInline(ctx)) {
ctx.bindValue(value, this);
}
}
}
use of org.jooq.RenderContext in project jOOQ by jOOQ.
the class DefaultBinding method toSQL.
/**
* Inlining abstraction
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private final void toSQL(BindingSQLContext<U> ctx, Object val) {
SQLDialect family = ctx.family();
RenderContext render = ctx.render();
if (render.paramType() == INLINED) {
if (val == null) {
render.keyword("null");
} else if (type == Boolean.class) {
// [#1153] Some dialects don't support boolean literals TRUE and FALSE
if (asList(FIREBIRD, SQLITE).contains(family)) {
render.sql(((Boolean) val) ? "1" : "0");
} else {
render.keyword(((Boolean) val).toString());
}
} else // [#1154] Binary data cannot always be inlined
if (type == byte[].class) {
byte[] binary = (byte[]) val;
if (asList().contains(family)) {
render.sql("0x").sql(convertBytesToHex(binary));
} else if (asList(DERBY, H2, HSQLDB, MARIADB, MYSQL, SQLITE).contains(family)) {
render.sql("X'").sql(convertBytesToHex(binary)).sql('\'');
} else if (asList().contains(family)) {
render.keyword("hextoraw('").sql(convertBytesToHex(binary)).sql("')");
} else if (family == POSTGRES) {
render.sql("E'").sql(PostgresUtils.toPGString(binary)).keyword("'::bytea");
} else // This default behaviour is used in debug logging for dialects
// that do not support inlining binary data
{
render.sql("X'").sql(convertBytesToHex(binary)).sql('\'');
}
} else // Interval extends Number, so let Interval come first!
if (Interval.class.isAssignableFrom(type)) {
render.sql('\'').sql(escape(val, render)).sql('\'');
} else // [#5249] Special inlining of special floating point values
if (Double.class.isAssignableFrom(type) && ((Double) val).isNaN()) {
if (POSTGRES == family)
render.visit(inline("NaN")).sql("::").keyword("float8");
else
render.sql(((Number) val).toString());
} else // [#5249] Special inlining of special floating point values
if (Float.class.isAssignableFrom(type) && ((Float) val).isNaN()) {
if (POSTGRES == family)
render.visit(inline("NaN")).sql("::").keyword("float4");
else
render.sql(((Number) val).toString());
} else if (Number.class.isAssignableFrom(type)) {
render.sql(((Number) val).toString());
} else // [#1156] DATE / TIME inlining is very vendor-specific
if (Tools.isDate(type)) {
Date date = getDate(type, val);
// [#1253] SQL Server and Sybase do not implement date literals
if (asList(SQLITE).contains(family)) {
render.sql('\'').sql(escape(date, render)).sql('\'');
} else // [#1253] Derby doesn't support the standard literal
if (family == DERBY) {
render.keyword("date('").sql(escape(date, render)).sql("')");
} else // [#3648] Circumvent a MySQL bug related to date literals
if (family == MYSQL) {
render.keyword("{d '").sql(escape(date, render)).sql("'}");
} else // Most dialects implement SQL standard date literals
{
render.keyword("date '").sql(escape(date, render)).sql('\'');
}
} else if (Tools.isTimestamp(type)) {
Timestamp ts = getTimestamp(type, val);
// [#1253] SQL Server and Sybase do not implement timestamp literals
if (asList(SQLITE).contains(family)) {
render.sql('\'').sql(escape(ts, render)).sql('\'');
} else // [#1253] Derby doesn't support the standard literal
if (family == DERBY) {
render.keyword("timestamp('").sql(escape(ts, render)).sql("')");
} else // CUBRID timestamps have no fractional seconds
if (family == CUBRID) {
render.keyword("datetime '").sql(escape(ts, render)).sql('\'');
} else // [#3648] Circumvent a MySQL bug related to date literals
if (family == MYSQL) {
render.keyword("{ts '").sql(escape(ts, render)).sql("'}");
} else // Most dialects implement SQL standard timestamp literals
{
render.keyword("timestamp '").sql(escape(ts, render)).sql('\'');
}
} else if (Tools.isTime(type)) {
Time time = getTime(type, val);
// [#1253] SQL Server and Sybase do not implement time literals
if (asList(SQLITE).contains(family)) {
render.sql('\'').sql(new SimpleDateFormat("HH:mm:ss").format(time)).sql('\'');
} else // [#1253] Derby doesn't support the standard literal
if (family == DERBY) {
render.keyword("time").sql("('").sql(escape(time, render)).sql("')");
} else // [#3648] Circumvent a MySQL bug related to date literals
if (family == MYSQL) {
render.keyword("{t '").sql(escape(time, render)).sql("'}");
} else // Most dialects implement SQL standard time literals
{
render.keyword("time").sql(" '").sql(escape(time, render)).sql('\'');
}
} else if (type == OffsetDateTime.class) {
String string = format((OffsetDateTime) val);
// Some dialects implement SQL standard time literals
{
render.keyword("timestamp with time zone").sql(" '").sql(escape(string, render)).sql('\'');
}
} else if (type == OffsetTime.class) {
String string = format((OffsetTime) val);
// Some dialects implement SQL standard time literals
{
render.keyword("time with time zone").sql(" '").sql(escape(string, render)).sql('\'');
}
} else if (type.isArray()) {
String separator = "";
// H2 renders arrays as rows
if (family == H2) {
render.sql('(');
for (Object o : ((Object[]) val)) {
render.sql(separator);
new DefaultBinding<Object, Object>(Converters.identity((Class) type.getComponentType()), isLob).sql(new DefaultBindingSQLContext<Object>(ctx.configuration(), ctx.data(), ctx.render(), o));
separator = ", ";
}
render.sql(')');
} else if (family == POSTGRES) {
render.visit(cast(inline(PostgresUtils.toPGArrayString((Object[]) val)), type));
} else // By default, render HSQLDB / POSTGRES syntax
{
render.keyword("ARRAY");
render.sql('[');
for (Object o : ((Object[]) val)) {
render.sql(separator);
new DefaultBinding<Object, Object>(Converters.identity((Class) type.getComponentType()), isLob).sql(new DefaultBindingSQLContext<Object>(ctx.configuration(), ctx.data(), ctx.render(), o));
separator = ", ";
}
render.sql(']');
// [#3214] Some PostgreSQL array type literals need explicit casting
if (family == POSTGRES && EnumType.class.isAssignableFrom(type.getComponentType())) {
pgRenderEnumCast(render, type);
}
}
} else if (EnumType.class.isAssignableFrom(type)) {
String literal = ((EnumType) val).getLiteral();
if (literal == null) {
new DefaultBinding<Object, Object>(Converters.identity((Class) String.class), isLob).sql(new DefaultBindingSQLContext<Object>(ctx.configuration(), ctx.data(), ctx.render(), literal));
} else {
new DefaultBinding<Object, Object>(Converters.identity((Class) String.class), isLob).sql(new DefaultBindingSQLContext<Object>(ctx.configuration(), ctx.data(), ctx.render(), literal));
}
} else if (UDTRecord.class.isAssignableFrom(type)) {
render.sql("[UDT]");
} else // Known fall-through types:
// - Blob, Clob (both not supported by jOOQ)
// - String
// - UUID
{
render.sql('\'').sql(escape(val, render), true).sql('\'');
}
} else // In Postgres, some additional casting must be done in some cases...
if (family == SQLDialect.POSTGRES) {
// Postgres needs explicit casting for enum (array) types
if (EnumType.class.isAssignableFrom(type) || (type.isArray() && EnumType.class.isAssignableFrom(type.getComponentType()))) {
render.sql(ctx.variable());
pgRenderEnumCast(render, type);
} else // ... and also for other array types
if (type.isArray() && byte[].class != type) {
render.sql(ctx.variable());
render.sql("::");
render.keyword(DefaultDataType.getDataType(family, type).getCastTypeName(render.configuration()));
} else {
render.sql(ctx.variable());
}
} else {
render.sql(ctx.variable());
}
}
use of org.jooq.RenderContext in project jOOQ by jOOQ.
the class RowSubqueryCondition method delegate.
private final QueryPartInternal delegate(Context<?> ctx) {
final Configuration configuration = ctx.configuration();
final RenderContext render = ctx instanceof RenderContext ? (RenderContext) ctx : null;
SQLDialect family = configuration.dialect().family();
// [#3505] TODO: Emulate this where it is not supported
if (rightQuantified != null) {
return new Native();
} else // predicates with row value expressions and subqueries:
if (asList(H2, HSQLDB, MARIADB, MYSQL, POSTGRES).contains(family)) {
return new Native();
} else // [#2395] All other configurations have to be emulated
{
String table = render == null ? "t" : render.nextAlias();
List<String> names = new ArrayList<String>();
for (int i = 0; i < left.size(); i++) {
names.add(table + "_" + i);
}
Field<?>[] fields = new Field[names.size()];
for (int i = 0; i < fields.length; i++) {
fields[i] = field(name(table, names.get(i)));
}
Condition condition;
switch(comparator) {
case GREATER:
condition = ((RowN) left).gt(row(fields));
break;
case GREATER_OR_EQUAL:
condition = ((RowN) left).ge(row(fields));
break;
case LESS:
condition = ((RowN) left).lt(row(fields));
break;
case LESS_OR_EQUAL:
condition = ((RowN) left).le(row(fields));
break;
case IN:
case EQUALS:
case NOT_IN:
case NOT_EQUALS:
default:
condition = ((RowN) left).eq(row(fields));
break;
}
Select<Record> subselect = select().from(right.asTable(table, names.toArray(EMPTY_STRING))).where(condition);
switch(comparator) {
case NOT_IN:
case NOT_EQUALS:
return (QueryPartInternal) notExists(subselect);
default:
return (QueryPartInternal) exists(subselect);
}
}
}
Aggregations