Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions .github/workflows/ci-hibernate-dialect-v7.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,10 @@ jobs:
distribution: 'temurin'
cache: maven

- name: Extract Hibernate Dialect version
working-directory: ./hibernate-dialect-v7
run: |
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
echo "HIBERNATE_DIALECT_VERSION=$VERSION" >> "$GITHUB_ENV"

- name: Download Hibernate Dialect dependencies
working-directory: ./hibernate-dialect-v7
run: mvn $MAVEN_ARGS dependency:go-offline

- name: Build Hibernate Dialect
working-directory: ./hibernate-dialect-v7
run: mvn $MAVEN_ARGS install

- uses: actions/checkout@v5
with:
repository: ydb-platform/ydb-java-examples
ref: master
path: examples

- name: Download dependencies
working-directory: ./examples/jdbc/spring-data-jpa
run: mvn $MAVEN_ARGS -Dhibernate.ydb.dialect.version=$HIBERNATE_DIALECT_VERSION dependency:go-offline

- name: Test examples with Maven
working-directory: ./examples/jdbc/spring-data-jpa
run: mvn $MAVEN_ARGS -Dhibernate.ydb.dialect.version=$HIBERNATE_DIALECT_VERSION test
8 changes: 8 additions & 0 deletions hibernate-dialect-v6/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 1.7.0 ##

- Extended `use_index:` query hint with table-and-column-aware format
`use_index:<index-name>:<table-name>(<column>[,<column>...])` so the same table joined multiple times under different aliases can be pinned to different indexes via `VIEW`
- `HINT_COMMENT` hints separated by `;` are now passed to each handler as a single batch, so the
best-match-wins rule for competing `use_index` hints works the same as via `Query#addQueryHint`
- Fixed the identifier regex so quoted (`` `Table` `` / `"Table"`) FROM tables are rewritten correctly

## 1.6.0 ##

- Add YDB constraint violation exception mapping to hibernate specific exception
Expand Down
50 changes: 50 additions & 0 deletions hibernate-dialect-v6/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,56 @@ Use this custom dialect just like any other Hibernate dialect.
Map your entity classes to database tables and use Hibernate's
session factory to perform database operations.

## Query hints

The dialect parses the JPA `HibernateHints.HINT_COMMENT` value (or the
`org.hibernate.query.Query#addQueryHint` value) and rewrites the generated SQL
before it is sent to YDB. Multiple hints can be combined in one comment by
separating them with `;`.

| Hint | Effect |
| --- | --- |
| `use_index:<index-name>` | Rewrites a simple `select … from <table> … where …` so that the FROM table uses the given secondary index (`VIEW <index-name>`). |
| `use_index:<index-name>:<table-name>(<column>[,<column>…])` | Table- and column-aware hint. For every `FROM`/`JOIN` occurrence of `<table-name>`, the dialect rewrites the segment to `<table-name> view <index-name> <alias>` **only if every column listed in the hint is referenced by that alias** inside the relevant scope (the `ON` clause for a `JOIN`, the `WHERE` clause for the `FROM` table). When several hints fully match the same table reference the most specific one (largest number of listed columns) wins. Table and column identifiers in the hint may be written bare, in `` ` `` backticks, or in `"…"` double quotes. |
| `use_scan` | Wraps the query in `scan`. |
| `add_pragma:<pragma>` | Prepends `PRAGMA <pragma>;` to the query. |

### Why the table/column form?

YDB does not always pick a secondary index automatically when the same table
is joined several times under different aliases (e.g. fetching parent and
child accounts in one query). The auto-picker does not currently consider
JOIN conditions, so heavy queries can fall back to a full scan. The
table/column form lets you pin a specific index per JOIN without rewriting
the JPQL query:

```java
@QueryHints({
@QueryHint(name = HibernateHints.HINT_COMMENT,
value = "use_index:bank_account_code_idx:bank_account(code);"
+ "use_index:bank_account_parent_idx:bank_account(parent)")
})
Optional<Account> findById(Long id);
```

For a Hibernate-generated query like

```sql
left join bank_account a1_0 on a1_0.code=source.code
left join bank_account a3_0 on a3_0.parent=source.parent
```

the dialect rewrites the joins to

```sql
left join bank_account view bank_account_code_idx a1_0 on a1_0.code=source.code
left join bank_account view bank_account_parent_idx a3_0 on a3_0.parent=source.parent
```

Multiple columns can be listed inside the parentheses (`bank_account(code,parent)`)
to apply the same index hint to whichever of those columns appears in the
join condition.

## Integration with Spring Data JPA

Configure Spring Data JPA with Hibernate to use custom YDB dialect
Expand Down
22 changes: 21 additions & 1 deletion hibernate-dialect-v6/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>tech.ydb.dialects</groupId>
<artifactId>hibernate-ydb-dialect</artifactId>
<version>1.6.0</version>
<version>1.7.0</version>

<packaging>jar</packaging>

Expand Down Expand Up @@ -149,6 +149,26 @@
</environmentVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.hibernate.boot.model.FunctionContributions;
Expand Down Expand Up @@ -288,17 +289,22 @@ public String addSqlHintOrComment(String sql, QueryOptions queryOptions, boolean
}

if (queryOptions.getComment() != null) {
boolean commentIsHint = false;

var hints = queryOptions.getComment().split(";");
List<String> comments = Arrays.stream(queryOptions.getComment().split(";"))
.map(String::trim)
.filter(comment -> !comment.isEmpty())
.toList();

boolean commentIsHint = false;
// Feed every handler the full list of hints it owns in a single call. Passing all
// of a handler's hints at once is what lets IndexQueryHintHandler compare competing
// use_index hints (best-match-wins) instead of applying them one by one.
for (var queryHintHandler : QUERY_HINT_HANDLERS) {
for (var hint : hints) {
hint = hint.trim();
if (queryHintHandler.commentIsHint(hint)) {
commentIsHint = true;
sql = queryHintHandler.addQueryHints(sql, List.of(hint));
}
List<String> handlerHints = comments.stream()
.filter(queryHintHandler::commentIsHint)
.toList();
if (!handlerHints.isEmpty()) {
commentIsHint = true;
sql = queryHintHandler.addQueryHints(sql, handlerHints);
}
}

Expand Down
Loading