Skip to content

feat: added scan query hint #161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 5, 2024
Merged
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
4 changes: 4 additions & 0 deletions hibernate-dialect/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.1.0 ##

- Added hint for scan queries

## 1.0.0 ##

- Fixed: data time type converters
Expand Down
2 changes: 1 addition & 1 deletion hibernate-dialect/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.0.0</version>
<version>1.1.0</version>

<packaging>jar</packaging>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
import tech.ydb.hibernate.dialect.exporter.EmptyExporter;
import tech.ydb.hibernate.dialect.exporter.YdbIndexExporter;
import tech.ydb.hibernate.dialect.hint.IndexQueryHintHandler;
import tech.ydb.hibernate.dialect.hint.QueryHintHandler;
import tech.ydb.hibernate.dialect.hint.ScanQueryHintHandler;
import tech.ydb.hibernate.dialect.translator.YdbSqlAstTranslatorFactory;
import tech.ydb.hibernate.dialect.types.InstantJavaType;
import tech.ydb.hibernate.dialect.types.InstantJdbcType;
Expand All @@ -72,6 +74,10 @@ public class YdbDialect extends Dialect {

private static final Exporter<ForeignKey> FOREIGN_KEY_EMPTY_EXPORTER = new EmptyExporter<>();
private static final Exporter<Constraint> UNIQUE_KEY_EMPTY_EXPORTER = new EmptyExporter<>();
private static final List<QueryHintHandler> QUERY_HINT_HANDLERS = List.of(
IndexQueryHintHandler.INSTANCE,
ScanQueryHintHandler.INSTANCE
);

public YdbDialect(DialectResolutionInfo dialectResolutionInfo) {
super(dialectResolutionInfo);
Expand Down Expand Up @@ -144,11 +150,29 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
@Override
public String addSqlHintOrComment(String sql, QueryOptions queryOptions, boolean commentsEnabled) {
if (queryOptions.getDatabaseHints() != null) {
sql = IndexQueryHintHandler.addQueryHints(sql, queryOptions.getDatabaseHints());
for (var queryHintHandler : QUERY_HINT_HANDLERS) {
sql = queryHintHandler.addQueryHints(sql, queryOptions.getDatabaseHints());
}
}

if (queryOptions.getComment() != null && IndexQueryHintHandler.commentIsHint(queryOptions.getComment())) {
return IndexQueryHintHandler.addQueryHints(sql, List.of(queryOptions.getComment()));
if (queryOptions.getComment() != null) {
boolean commentIsHint = false;

var hints = queryOptions.getComment().split(",");

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));
}
}
}

if (commentIsHint) {
return sql;
}
}

if (commentsEnabled && queryOptions.getComment() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,24 @@
/**
* @author Kirill Kurdyukov
*/
public class IndexQueryHintHandler {
public class IndexQueryHintHandler implements QueryHintHandler {
public static final IndexQueryHintHandler INSTANCE = new IndexQueryHintHandler();

private static final Pattern SELECT_FROM_WHERE_QUERY_PATTERN = Pattern
.compile("^\\s*(select.+?from\\s+\\w+)(.+where.+)$", Pattern.CASE_INSENSITIVE);

public static final String HINT_USE_INDEX = "use_index:";
private static final String HINT_USE_INDEX = "use_index:";

private IndexQueryHintHandler() {
}

public static boolean commentIsHint(String comment) {
@Override
public boolean commentIsHint(String comment) {
return comment.startsWith(HINT_USE_INDEX);
}

public static String addQueryHints(String query, List<String> hints) {
@Override
public String addQueryHints(String query, List<String> hints) {
if (hints.isEmpty()) {
return query;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package tech.ydb.hibernate.dialect.hint;

import java.util.List;

/**
* @author Kirill Kurdyukov
*/
public interface QueryHintHandler {

String addQueryHints(String query, List<String> hints);

boolean commentIsHint(String comment);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package tech.ydb.hibernate.dialect.hint;

import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* @author Kirill Kurdyukov
*/
public class ScanQueryHintHandler implements QueryHintHandler {
public static final ScanQueryHintHandler INSTANCE = new ScanQueryHintHandler();

private static final String HINT_USE_SCAN = "use_scan";

private ScanQueryHintHandler() {

}

@Override
public String addQueryHints(String query, List<String> hints) {
if (hints.isEmpty()) {
return query;
}

AtomicBoolean useScan = new AtomicBoolean(false);
hints.forEach(hint -> {
if (hint.equals(HINT_USE_SCAN)) {
useScan.set(true);
}
});

if (useScan.get()) {
return "scan " + query;
}

return query;
}

@Override
public boolean commentIsHint(String comment) {
return comment.startsWith(HINT_USE_SCAN);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,100 @@ void sumAvgByStudentIdTest() {
}
);
}

@Test
void useScanQueryHintTest() {
/*
scan select
c1_0.CourseId,
c1_0.CourseName
from
Courses c1_0
order by
c1_0.CourseId
*/
inTransaction(
session -> {
var courses = session.createQuery("FROM Course c ORDER BY c.id", Course.class)
.addQueryHint("use_scan")
.getResultList();

checkCourses(courses);
}
);

/*
scan select
c1_0.CourseId,
c1_0.CourseName
from
Courses c1_0
order by
c1_0.CourseId
*/
inTransaction(
session -> {
var courses = session.createQuery("FROM Course c ORDER BY c.id", Course.class)
.setHint(HibernateHints.HINT_COMMENT, "use_scan")
.getResultList();

checkCourses(courses);
}
);
}

private static void checkCourses(List<Course> courses) {
assertEquals(6, courses.size());
assertEquals("Базы данных", courses.get(0).getName());
assertEquals("Управление проектами", courses.get(1).getName());
assertEquals("ППО", courses.get(2).getName());
assertEquals("Теория информации", courses.get(3).getName());
assertEquals("Математический анализ", courses.get(4).getName());
assertEquals("Технологии Java", courses.get(5).getName());
}

@Test
void useIndexAndUseScanHintsTogetherTest() {
/*
scan select
g1_0.GroupId,
g1_0.GroupName
from
Groups view group_name_index g1_0
where
g1_0.GroupName='M3439'
*/
inTransaction(
session -> {
Group group = session
.createQuery("FROM Group g WHERE g.name = 'M3439'", Group.class)
.addQueryHint("use_index:group_name_index") // Hibernate
.addQueryHint("use_scan")
.getSingleResult();

assertEquals("M3439", group.getName());
}
);


/*
scan select
g1_0.GroupId,
g1_0.GroupName
from
Groups view group_name_index g1_0
where
g1_0.GroupName='M3439'
*/
inTransaction(
session -> {
Group group = session
.createQuery("FROM Group g WHERE g.name = 'M3439'", Group.class)
.setHint(HibernateHints.HINT_COMMENT, "use_index:group_name_index, use_scan") // JPA
.getSingleResult();

assertEquals("M3439", group.getName());
}
);
}
}