Accepted input is one non-locking, non-output {@code SELECT} whose table + * references are unqualified or belong to the configured application schema. + * Comments, statement separators, set operations, write/control keywords, and + * prohibited database functions are rejected.
+ * + * @since 2026-08-06 + */ +public final class QueryByExampleSqlValidator { + public static final int MAX_SQL_CHARACTERS = 16_384; + + private static final Pattern LOCKING_SELECT = Pattern.compile( + "\\bfor\\s+(?:update|share)\\b|\\block\\s+in\\s+share\\s+mode\\b", + Pattern.CASE_INSENSITIVE); + private static final Pattern OUTPUT_OR_PROCEDURE_OPERATION = Pattern.compile( + "\\b(?:into|procedure)\\b", Pattern.CASE_INSENSITIVE); + + /** + * Tables containing reusable authentication material or private cryptographic keys. + * Query-by-Example is a reporting surface, so even administrators must use the + * purpose-built management flows rather than exporting these records as report data. + */ + static final Set| "); - sb.append(Encode.forHtml(columnNames[i])); - sb.append(" | "); + while (!stopRendering && rowCount < maxRows && rs.next()) { + rowCount++; + stopRendering = !appendRow(html, rs, columnTypes, rowColor); + rowColor = rowColor.equals("rowColor1") ? "rowColor2" : "rowColor1"; } - while (rs.next()) { - sb.append("
|---|
| "); - sb.append(Encode.forHtml(Misc.getString(rs, columnNames[j]))); - sb.append(" | "); + boolean rowLimitReached = !stopRendering && rowCount == maxRows && rs.next(); + html.appendClosingMarkup(TABLE_END); + return new StructuredResult(html.toString(), rowCount, html.isTruncated(), rowLimitReached); + } + + private static void validateLimits(int maxOutputCharacters, int maxRows) { + if (maxOutputCharacters < MIN_OUTPUT_CHARACTERS) { + throw new IllegalArgumentException("HTML output limit is too small"); + } + if (maxRows < 1) { + throw new IllegalArgumentException("Row limit must be positive"); + } + } + private static boolean appendHeaders(LimitedHtmlBuilder html, ResultSetMetaData metadata, int columns) + throws SQLException { + for (int i = 1; i <= columns; i++) { + if (!appendHeader(html, metadata.getColumnLabel(i))) { + return false; } - rowColor = rowColor.compareTo("rowColor1") == 0 ? "rowColor2" : "rowColor1"; - sb.append("
+
Migrated from legacy {@code ReportByExamplesFavoriteDaoTest} (JUnit 4 / DaoTestFixtures).
* @@ -92,32 +92,21 @@ void shouldFindFavorite_whenValidIdProvided() { } @Nested - @DisplayName("findByQuery") - class FindByQuery { + @DisplayName("findByProviderAndQuery") + class FindByProviderAndQuery { @Test @Tag("query") - @DisplayName("should return favorites with matching query string using LIKE") - void shouldReturnFavorites_whenQueryMatches() { - createFavorite("200001", "Fav1", "SELECT demographics"); - createFavorite("200002", "Fav2", "SELECT appointments"); - createFavorite("200003", "Fav3", "INSERT something"); + @DisplayName("should not return another provider's matching query") + void shouldReturnOnlyCurrentProviderFavorites_whenQueryMatches() { + createFavorite("200010", "Mine", "SELECT appointments"); + createFavorite("200011", "Theirs", "SELECT appointments"); - List