DbConstraint.java

package jasper.util;

import org.hibernate.exception.ConstraintViolationException;

/**
 * Utility for identifying database constraint violations across different
 * database backends (PostgreSQL and SQLite).
 *
 * PostgreSQL reports constraint names via ConstraintViolationException.
 * SQLite reports constraint violations via error messages with column names.
 */
public class DbConstraint {

	/**
	 * Check if the exception (or its cause chain) represents a primary key
	 * violation for the given table. The PK columns differ per table:
	 * ref=(url, origin), ext/plugin/template/users=(tag, origin).
	 */
	public static boolean isPkViolation(Throwable e, String table) {
		var firstCol = "ref".equals(table) ? "url" : "tag";
		return isConstraint(e, table + "_pkey",
			table + "." + firstCol + ", " + table + ".origin");
	}

	/**
	 * Check if the exception (or its cause chain) represents a unique constraint
	 * violation on (modified, origin) for the given table.
	 */
	public static boolean isUniqueModifiedOriginViolation(Throwable e, String table) {
		return isConstraint(e,
			table + "_modified_origin_key",
			table + ".modified, " + table + ".origin");
	}

	private static boolean isConstraint(Throwable e, String pgConstraintName, String sqliteColumns) {
		if (e == null) return false;
		// Walk the full cause chain
		for (Throwable t = e; t != null; t = t.getCause()) {
			if (matchesConstraint(t, pgConstraintName, sqliteColumns)) return true;
		}
		return false;
	}

	private static boolean matchesConstraint(Throwable t, String pgConstraintName, String sqliteColumns) {
		if (t == null) return false;
		// PostgreSQL: ConstraintViolationException with named constraint
		if (t instanceof ConstraintViolationException c) {
			if (pgConstraintName.equals(c.getConstraintName())) return true;
		}
		// SQLite: error message contains "UNIQUE constraint failed: <columns>"
		String msg = t.getMessage();
		if (msg != null && msg.contains("UNIQUE constraint failed: " + sqliteColumns)) return true;
		return false;
	}
}