OtterMind/Chat2DB · error · IllegalArgumentException

Unsafe H2 column default from metadata: {columnDefault}

Error message

Unsafe H2 column default from metadata: {columnDefault}

What it means

Thrown by H2SqlGuards.escapeColumnDefault when a non-null JDBC metadata column default does not match any accepted literal/expression pattern (string literal, numeric literal, NULL/TRUE/FALSE, CURRENT_TEMPORAL, no-arg function, typed literal, binary literal, or NEXT VALUE FOR sequence). Null defaults return ""; everything else unrecognized is rejected rather than silently re-quoted into a semantically different string literal.

Source

Thrown at chat2db-community-server/chat2db-community-plugins/chat2db-community-h2/src/main/java/ai/chat2db/plugin/h2/H2SqlGuards.java:125

     * silently converted into a string literal with different semantics.
     * Returns an empty string for {@code null}.
     */
    public static String escapeColumnDefault(String columnDefault) {
        if (columnDefault == null) {
            return "";
        }
        String trimmed = columnDefault.trim();
        if (STRING_LITERAL.matcher(trimmed).matches()
            || NUMERIC_LITERAL.matcher(trimmed).matches()
            || SIMPLE_CONSTANT.matcher(trimmed).matches()
            || CURRENT_TEMPORAL.matcher(trimmed).matches()
            || SAFE_NO_ARG_FUNCTION.matcher(trimmed).matches()
            || TYPED_LITERAL.matcher(trimmed).matches()
            || BINARY_LITERAL.matcher(trimmed).matches()
            || SEQUENCE_EXPRESSION.matcher(trimmed).matches()) {
            return trimmed;
        }
        throw new IllegalArgumentException("Unsafe H2 column default from metadata: " + columnDefault);
    }
}

View on GitHub (pinned to 5ee1e990e7)

Solutions

  1. Verify the default expression is one of the allow-listed forms; if it is a function with arguments, extend the guard pattern or handle that column manually.
  2. Leave the default null/blank to emit no DEFAULT clause.
  3. If a legitimate H2 default is rejected, report it so the allow-list pattern can be widened rather than bypassing the guard.

Example fix

// before
// metadata default = "NOW(0)" works, but "MYFUNC(1)" fails
H2SqlGuards.escapeColumnDefault("MYFUNC(1)");

// after
// drop the unsupported default for this column, or whitelist MYFUNC
String d = column.getDefaultValue();
String safe = (d == null || isKnownSafeDefault(d)) ? H2SqlGuards.escapeColumnDefault(d) : null;
Defensive patterns

Strategy: validation

Validate before calling

// Reuse the same allow-list checks H2SqlGuards applies internally is not exposed,
// so validate structurally before calling escapeColumnDefault:
static boolean looksLikeSafeH2Default(String d) {
    if (d == null) return true;
    String t = d.trim();
    return t.isEmpty()
        || t.matches("'(?:''|[^'])*'")
        || t.matches("^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$")
        || t.matches("(?i)^(?:NULL|TRUE|FALSE)$");
}

Try / catch

try {
    ddl.append(H2SqlGuards.escapeColumnDefault(def));
} catch (IllegalArgumentException e) {
    // Drop the DEFAULT clause for this column and surface a warning;
    // do NOT stringify the value into quotes (it changes semantics).
    log.warn("Skipping unsafe H2 default for column: {}", def);
}

Prevention

When it happens

Trigger: Calling escapeColumnDefault(columnDefault) where columnDefault is a complex expression, function call with arguments, arithmetic, CASE/CAST, or any text outside the eight allow-listed forms.

Common situations: H2 metadata reporting a DEFAULT that references a user function or expression; a schema using DEFAULT CURRENT_TIMESTAMP... that doesn't match the strict pattern; metadata from a newer H2 that emits a default format the guard doesn't recognize.

Related errors


AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14). Data as JSON: /api/errors/562577be4622e20c. Report an issue: GitHub.