Add semrack index postgresql
All checks were successful
Maven build / build (push) Successful in 3m17s
All checks were successful
Maven build / build (push) Successful in 3m17s
This commit is contained in:
97
modules/semrack-index-postgres/runtime/pom.xml
Normal file
97
modules/semrack-index-postgres/runtime/pom.xml
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>fr.codeanddata.semrack</groupId>
|
||||
<artifactId>semrack-index-postgres-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>semrack-index-postgres</artifactId>
|
||||
<name>Semrack Index Postgres - Runtime</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>fr.codeanddata.semrack</groupId>
|
||||
<artifactId>semrack-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-arc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-vertx</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-reactive-panache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-reactive-pg-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-flyway</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-database-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-extension-maven-plugin</artifactId>
|
||||
<version>${quarkus.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>extension-descriptor</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}</deployment>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-compile</id>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-extension-processor</artifactId>
|
||||
<version>${quarkus.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<annotationProcessorPathsUseDepMgmt>true</annotationProcessorPathsUseDepMgmt>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
package fr.codeanddata.semrack.index.postgres;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class EqualLookupParams {
|
||||
String field;
|
||||
Object value;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package fr.codeanddata.semrack.index.postgres;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class InLookupParams {
|
||||
String field;
|
||||
List<Object> values;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package fr.codeanddata.semrack.index.postgres;
|
||||
|
||||
import fr.codeanddata.semrack.core.SemdocIndex;
|
||||
import fr.codeanddata.semrack.core.SemdocStorage;
|
||||
import fr.codeanddata.semrack.core.models.*;
|
||||
import fr.codeanddata.semrack.core.services.SemrackLookupService;
|
||||
import fr.codeanddata.semrack.core.utils.Traverser;
|
||||
import fr.codeanddata.semrack.index.postgres.entities.IndexDocumentEntity;
|
||||
import fr.codeanddata.semrack.index.postgres.entities.IndexKeyEntity;
|
||||
import fr.codeanddata.semrack.index.postgres.repositories.IndexDocumentRepository;
|
||||
import fr.codeanddata.semrack.index.postgres.repositories.IndexKeyRepository;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepository;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
|
||||
import io.quarkus.vertx.ConsumeEvent;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import io.vertx.core.eventbus.EventBus;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.hibernate.query.Page;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@ApplicationScoped
|
||||
public class SemdocJpaIndex implements SemdocIndex, PanacheRepository<IndexKeyEntity> {
|
||||
|
||||
public static final String INDEX_CONSUMER = "semdoc-jpa-index";
|
||||
|
||||
@Inject
|
||||
Traverser traverser;
|
||||
|
||||
@Inject
|
||||
SemrackLookupService lookupService;
|
||||
|
||||
@Inject
|
||||
SemdocStorage storage;
|
||||
|
||||
@Inject
|
||||
IndexDocumentRepository indexDocumentRepository;
|
||||
|
||||
@Inject
|
||||
IndexKeyRepository indexKeyRepository;
|
||||
|
||||
@Inject
|
||||
EventBus bus;
|
||||
|
||||
@Override
|
||||
@WithSession
|
||||
public Uni<Long> count(SearchRequest request) {
|
||||
final StringBuilder query = new StringBuilder("SELECT DISTINCT count(d.id) FROM index_document d JOIN index_key idx on d.id = idx.index_document_id");
|
||||
|
||||
final String lookup = lookupService.lookup(request.getFilter());
|
||||
final String whereClause = lookup.isEmpty() ? "" : " WHERE " + lookup;
|
||||
query.append(whereClause);
|
||||
return getSession()
|
||||
.chain(s -> s.createNativeQuery(query.toString(), Long.class).getSingleResultOrNull())
|
||||
.map(count -> count == null ? 0 : count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uni<Boolean> exist(SearchRequest query) {
|
||||
return count(query).map(count -> count > 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithTransaction
|
||||
public Uni<Void> index(String documentId) {
|
||||
return storage.get(documentId)
|
||||
.call(document -> clear(documentId))
|
||||
.call(document -> indexDocumentRepository.persist(IndexDocumentEntity.builder()
|
||||
.uid(documentId)
|
||||
.build())
|
||||
.call(index -> {
|
||||
final List<TraverserPath> annotationPaths = traverser.apply(document.getAnnotations()).stream().peek(x -> x.setFullPath("annotations" + x.getFullPath())).toList();
|
||||
final List<TraverserPath> metadataPaths = traverser.apply(document.getMetadata()).stream().peek(x -> x.setFullPath("metadata" + x.getFullPath())).toList();
|
||||
|
||||
final List<TraverserPath> allPaths = new ArrayList<>();
|
||||
allPaths.addAll(annotationPaths);
|
||||
allPaths.addAll(metadataPaths);
|
||||
return indexKeyRepository.persist(allPaths
|
||||
.stream()
|
||||
.map(path -> {
|
||||
final Map<String, Object> values = new HashMap<>();
|
||||
values.put("v", path.getValue());
|
||||
return IndexKeyEntity.builder()
|
||||
.index(index)
|
||||
.type(path.getType())
|
||||
.fullPath(path.getFullPath())
|
||||
.value(values)
|
||||
.build();
|
||||
}));
|
||||
}))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithSession
|
||||
public Uni<IndexSearchResult> search(SearchRequest request) {
|
||||
final IndexSearchResult result = new IndexSearchResult();
|
||||
final StringBuilder query = new StringBuilder("SELECT DISTINCT d.uid FROM index_document d JOIN index_key idx on d.id = idx.index_document_id");
|
||||
|
||||
final String lookup = lookupService.lookup(request.getFilter());
|
||||
final String whereClause = lookup.isEmpty() ? "" : " WHERE " + lookup;
|
||||
query.append(whereClause);
|
||||
query.append(" GROUP BY d.uid");
|
||||
|
||||
return getSession()
|
||||
.chain(s -> queryPaginationInfo(request)
|
||||
.invoke(result::setPagination)
|
||||
.call(() -> s
|
||||
.createNativeQuery(query.toString(), String.class)
|
||||
.setPage(Page.page(Integer.parseInt(result.getPagination().getSize() + ""), Integer.parseInt(result.getPagination().getPage() + "")))
|
||||
.getResultList()
|
||||
.invoke(result::setUids))
|
||||
.map(count -> result));
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithTransaction
|
||||
public Uni<Void> clear(String documentId) {
|
||||
return Uni.createFrom().nullItem()
|
||||
.call(() -> IndexKeyEntity.delete("index.uid = ?1", documentId))
|
||||
.call(() -> IndexDocumentEntity.delete("uid = ?1", documentId))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
Uni<PaginationInfo> queryPaginationInfo(SearchRequest query) {
|
||||
return count(query)
|
||||
.map(count -> PaginationInfo.builder()
|
||||
.page(Optional.ofNullable(query.getPaginate()).map(SemrackPagination::getPage).orElse(0))
|
||||
.size(Optional.ofNullable(query.getPaginate()).map(SemrackPagination::getSize).orElse(10))
|
||||
.total(count)
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package fr.codeanddata.semrack.index.postgres.entities;
|
||||
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Entity(name = "IndexDocument")
|
||||
@Table(name = "index_document")
|
||||
public class IndexDocumentEntity extends PanacheEntity {
|
||||
|
||||
@Column(nullable = false, unique = true, columnDefinition = "varchar(256)")
|
||||
String uid;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package fr.codeanddata.semrack.index.postgres.entities;
|
||||
|
||||
import fr.codeanddata.semrack.core.utils.Traverser;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheEntity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Entity(name = "IndexAnnotation")
|
||||
@Table(name = "index_key")
|
||||
public class IndexKeyEntity extends PanacheEntity {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "index_document_id", nullable = false)
|
||||
IndexDocumentEntity index;
|
||||
|
||||
@Column(name = "fullpath")
|
||||
String fullPath;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
Traverser.PathTypes type;
|
||||
|
||||
@JdbcTypeCode(SqlTypes.JSON)
|
||||
Map<String, Object> value;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package fr.codeanddata.semrack.index.postgres.operators;
|
||||
|
||||
import fr.codeanddata.semrack.core.services.SemrackLookupService;
|
||||
import io.smallrye.common.annotation.Identifier;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Identifier("and")
|
||||
@ApplicationScoped
|
||||
public class AndLookup implements SemrackJpaLookupExpression<List<Map<String, Object>>> {
|
||||
|
||||
@Inject
|
||||
SemrackLookupService lookupService;
|
||||
|
||||
@Override
|
||||
public String apply(Object expressions) {
|
||||
final List<Map<String, Object>> typedExpressions = convert(expressions);
|
||||
return String.join(" AND ", typedExpressions.stream().map(lookupService::lookup).toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package fr.codeanddata.semrack.index.postgres.operators;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import fr.codeanddata.semrack.core.exceptions.SemrackRuntimeException;
|
||||
import fr.codeanddata.semrack.index.postgres.EqualLookupParams;
|
||||
import io.smallrye.common.annotation.Identifier;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
@Identifier("equal")
|
||||
@ApplicationScoped
|
||||
public class EqualLookup implements SemrackJpaLookupExpression<EqualLookupParams> {
|
||||
|
||||
@Inject
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public String apply(Object params) {
|
||||
final EqualLookupParams equalLookupParams = convert(params, EqualLookupParams.class);
|
||||
|
||||
if (equalLookupParams.getField() == null || equalLookupParams.getField().isEmpty()) {
|
||||
throw new SemrackRuntimeException("Field expected");
|
||||
}
|
||||
|
||||
try {
|
||||
return "(idx.fullpath = '"+ equalLookupParams.getField() + "' AND idx.value->'v' = '"+ objectMapper.writeValueAsString(equalLookupParams.getValue())+"'::jsonb)";
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package fr.codeanddata.semrack.index.postgres.operators;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import fr.codeanddata.semrack.core.exceptions.SemrackRuntimeException;
|
||||
import fr.codeanddata.semrack.index.postgres.InLookupParams;
|
||||
import io.smallrye.common.annotation.Identifier;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Identifier("in")
|
||||
@ApplicationScoped
|
||||
public class InLookup implements SemrackJpaLookupExpression<InLookupParams> {
|
||||
|
||||
@Inject
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public String apply(Object params) {
|
||||
final InLookupParams inLookupParams = convert(params, InLookupParams.class);
|
||||
|
||||
if (inLookupParams.getField() == null || inLookupParams.getField().isEmpty()) {
|
||||
throw new SemrackRuntimeException("Field expected");
|
||||
}
|
||||
|
||||
try {
|
||||
final List<String> inValues = new ArrayList<>();
|
||||
for (Object value : inLookupParams.getValues()) {
|
||||
inValues.add("'" + objectMapper.writeValueAsString(value) + "'::jsonb");
|
||||
}
|
||||
return "(idx.fullpath = '" + inLookupParams.getField() + "' AND idx.value->'v' in (" + String.join(",", inValues) + "))";
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package fr.codeanddata.semrack.index.postgres.operators;
|
||||
|
||||
import fr.codeanddata.semrack.core.services.SemrackLookupService;
|
||||
import io.smallrye.common.annotation.Identifier;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Identifier("or")
|
||||
@ApplicationScoped
|
||||
public class OrLookup implements SemrackJpaLookupExpression<List<Map<String, Object>>> {
|
||||
|
||||
@Inject
|
||||
SemrackLookupService lookupService;
|
||||
|
||||
@Override
|
||||
public String apply(Object expressions) {
|
||||
final List<Map<String, Object>> typedExpressions = convert(expressions);
|
||||
return String.join(" OR ", typedExpressions.stream().map(lookupService::lookup).toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package fr.codeanddata.semrack.index.postgres.operators;
|
||||
|
||||
import fr.codeanddata.semrack.core.SemrackLookupExpression;
|
||||
|
||||
public interface SemrackJpaLookupExpression<T> extends SemrackLookupExpression<T> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package fr.codeanddata.semrack.index.postgres.repositories;
|
||||
|
||||
import fr.codeanddata.semrack.index.postgres.entities.IndexDocumentEntity;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class IndexDocumentRepository implements PanacheRepository<IndexDocumentEntity> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package fr.codeanddata.semrack.index.postgres.repositories;
|
||||
|
||||
import fr.codeanddata.semrack.index.postgres.entities.IndexKeyEntity;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class IndexKeyRepository implements PanacheRepository<IndexKeyEntity> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
name: Semrack Index Postgres
|
||||
#description: Do something useful.
|
||||
metadata:
|
||||
# keywords:
|
||||
# - semrack-index-postgres
|
||||
# guide: ... # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension
|
||||
# categories:
|
||||
# - "miscellaneous"
|
||||
# status: "preview"
|
||||
@@ -0,0 +1,9 @@
|
||||
quarkus.datasource.db-kind=postgresql
|
||||
quarkus.flyway.enabled=true
|
||||
quarkus.flyway.active=false
|
||||
quarkus.flyway.migrate-at-start=true
|
||||
quarkus.flyway.out-of-order=true
|
||||
quarkus.flyway.ignore-missing-migrations=true
|
||||
|
||||
quarkus.native.resources.includes=src/main/resources/db/migration/**
|
||||
quarkus.native.additional-build-args=-H:SerializationConfigurationResources=serialization-config.json
|
||||
@@ -0,0 +1,20 @@
|
||||
create table index_document
|
||||
(
|
||||
id bigint not null,
|
||||
uid varchar(256) not null,
|
||||
primary key (id)
|
||||
);
|
||||
create table index_key
|
||||
(
|
||||
id bigint not null,
|
||||
fullpath varchar(255),
|
||||
type varchar(255) check (type in ('UNKNOWN', 'STRING', 'NUMBER', 'BOOLEAN', 'LIST', 'OBJECT')),
|
||||
value jsonb,
|
||||
index_document_id bigint not null,
|
||||
primary key (id)
|
||||
);
|
||||
alter table if exists index_document drop constraint if exists UKc9q9jn623dprjanh1v3wvh86;
|
||||
alter table if exists index_document add constraint UKc9q9jn623dprjanh1v3wvh86 unique (uid);
|
||||
create sequence index_document_SEQ start with 1 increment by 50;
|
||||
create sequence index_key_SEQ start with 1 increment by 50;
|
||||
alter table if exists index_key add constraint FKgy9d5xg0nf2gsvxu58hnw8fa2 foreign key (index_document_id) references index_document;
|
||||
@@ -0,0 +1,3 @@
|
||||
[
|
||||
{"name": "java.lang.String"}
|
||||
]
|
||||
Reference in New Issue
Block a user