0

I'm trying to implement the use of vectors in my application.

I raised the version of Spring boot to 4.0.0-M1. This also gives me hibernate 7.

Added EXTENSION vector to Postgres

Added dependency:

    <dependency>
      <groupId>org.hibernate.orm</groupId>
      <artifactId>hibernate-vector</artifactId>
      <version>${hibernate.version}</version>
    </dependency>

My entity:

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "embedding")
public class Embedding {

    @Id
    @Column(name = "id")
    private UUID id;

    @JdbcTypeCode(SqlTypes.VECTOR)
    @Array(length = 1536)
    @Column(name = "vector", columnDefinition = "vector(1536)")
    private double[] vector;

}

This setting allows saving to the database from the box but not selection. Spring JPA named queries don't work. SQL doesn't work too.

For example native query not working too:

    @Query(value = """
        SELECT * FROM embedding ORDER BY vector <=> CAST(:vector AS vector) LIMIT :limit
        """, nativeQuery = true)
    List<Embedding> findNearest(@Param("vector") double[] vector, @Param("limit") int limit);

This query gives me: Caused by: org.springframework.orm.jpa.JpaSystemException: org.postgresql.util.PSQLException: No results were returned by the query. Caused by: org.hibernate.HibernateException: org.postgresql.util.PSQLException: No results were returned by the query. Caused by: org.postgresql.util.PSQLException: No results were returned by the query.

Simple findById(id) gives the same error.

The only way that allows me to use findById(id) and query from example to get record from db is using custom AttributeConverter for vector field that i got from GPT now i use @Convert instead of @JdbcTypeCode and @Array:

    @Convert(converter = VectorConverter.class)
    private double[] vector;

And converteer:

@Converter(autoApply = true)
public class VectorConverter implements AttributeConverter<double[], String> {

    @Override
    public String convertToDatabaseColumn(double[] attribute) {
        if (attribute == null) return null;
        return Arrays.stream(attribute)
            .mapToObj(Double::toString)
            .collect(Collectors.joining(",", "[", "]"));
    }

    @Override
    public double[] convertToEntityAttribute(String dbData) {
        if (dbData == null) return null;
        String clean = dbData.replaceAll("[\\[\\]\\s]", "");
        if (clean.isEmpty()) return new double[0];
        String[] parts = clean.split(",");
        return Arrays.stream(parts).mapToDouble(Double::parseDouble).toArray();
    }
}

But with this converter i can't save data with other error: Caused by: org.postgresql.util.PSQLException: ERROR: column "vector" is of type vector but expression is of type character varying

Also i was trying to use PGobject instead of String -> AttributeConverter<double[], PGobject> and got similar error: Caused by: org.postgresql.util.PSQLException: ERROR: column "vector" is of type vector but expression is of type bytea

Different types didn't bring changes:

    @JdbcTypeCode(SqlTypes.VECTOR)
    @JdbcTypeCode(SqlTypes.VECTOR_FLOAT64)
    @JdbcTypeCode(SqlTypes.ARRAY)

Please help me set it up. Thank you.

1 Answer 1

1

SOLVED!

Lets do the trick
We need to use @Converter just for stub that do nothing

@Converter(autoApply = true)
public class VectorConverter implements AttributeConverter<double[], double[]> {

    @Override
    public double[] convertToDatabaseColumn(double[] attribute) {
        return attribute; // Hibernate will convert himself throw VectorJdbcType
    }

    @Override
    public double[] convertToEntityAttribute(double[] dbData) {
        return dbData; // This will be used directly
    }

}

And use both approaches for Entity field at the same time - @Convert and @JdbcTypeCode

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "embedding")
public class Embedding {

    @Id
    @Column(name = "id")
    private UUID id;

    @Convert(converter = VectorConverter.class) // -> Important
    @JdbcTypeCode(SqlTypes.VECTOR). // -> Important
    @Column(name = "vector", columnDefinition = "vector(1536)")
    private double[] vector;

}

Congratulations! Now we can insert Embeddings just with repo:

embeddingRepo.save(embedding)

And search with native SQL like this:

    @Query(value = """
        SELECT * FROM embedding ORDER BY vector <=> CAST(:vector AS vector) LIMIT :limit
        """, nativeQuery = true)
    List<Embedding> findNearest(@Param("vector") double[] vector, @Param("limit") int limit);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.