Skip to content
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

DATAJDBC-1953 Introduced DialectCriteriaCondition interface #1981

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

mipo256
Copy link
Contributor

@mipo256 mipo256 commented Jan 16, 2025

This PR addresses the following issue #1953.

The core of the problem, I think, is that we had the Comparator as a Java enum, which is not extendable. I thought that, from the future perspective, as @schauder pointed out, we cannot really foresee all the possible conditions that can be used. Arrays in PostgreSQL is just a very concrete case.

So, the issue that is pointed out by the author, I think, should be solved in the following way, from the API's perspective:

Criteria.where("tags").custom(() -> "@> ARRAY['electronics']::text[]");

and that would render to the SQL that we expect:

SELECT * FROM products WHERE tags @> ARRAY['electronics']::text[]

This approach allows users to customize the condition in any possible way for any given column. This is achieved by introducing the Comparator interface, rather than the Comparator enum.

P.S: I've tried to minimize the backwards incompatibility as much as possible, but still, some migration would be needed here. Therefore, I guess, we need to assign a breaking-change tag

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jan 16, 2025
@schauder
Copy link
Contributor

Thanks for the contribution. We need a Developer Certificate of Origin (DCO) for new PRs.

For details about how and why see https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring

@schauder schauder added status: waiting-for-feedback We need additional information before we can continue type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 16, 2025
@schauder schauder self-assigned this Jan 16, 2025
@schauder schauder linked an issue Jan 16, 2025 that may be closed by this pull request
@mipo256 mipo256 force-pushed the DATAJDBC-1953 branch 2 times, most recently from 74172af to 6692442 Compare January 16, 2025 18:52
@mipo256
Copy link
Contributor Author

mipo256 commented Jan 16, 2025

Hey @schauder, yeah, I am aware of this, I've added the sign off.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 16, 2025
@mp911de
Copy link
Member

mp911de commented Jan 17, 2025

I don't think this is a good design direction. We should have rather some way to express predicates in a way that allows to determine bindings, mapping of values and to generate SQL text. I'll leave some thoughts in the original ticket #1953

@mipo256 mipo256 force-pushed the DATAJDBC-1953 branch 3 times, most recently from 1f54016 to 295c9f5 Compare February 7, 2025 11:36
@mipo256
Copy link
Contributor Author

mipo256 commented Feb 7, 2025

Hey @mp911de, @schauder!

I've revisited the solution according to the comment left in here: #1953 (comment)

The usage sample for the issue looks like this:

where("foo").is("bar").and("baz").satisfies(Postgres.arrayContains("first", "second"))

This would render into the following:

foo = 'bar' AND baz @> ARRAY['first','second']::text[]

I've introduced a new interface - DialectCriteriaCondition, that represents a condition that would eventually render into the part of the SQL condition that is vendor specific.

Also, the API, as it is now created, allows users to define their own DialectCriteriaCondition, in case we do not have them yet, like this:

Criteria criteria = where("foo").satisfies(() -> "~* '*.example.*'"); // renders into foo ~* '*.example.*'

@mipo256 mipo256 changed the title DATAJDBC-1953 Introduced Comparator interface DATAJDBC-1953 Introduced DialectCriteriaCondition interface Feb 7, 2025
*
* @author Mikhail Polivakha
*/
public interface DialectCriteriaCondition {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit too little, especially given the later variant of concatenating values into SQL as it opens loopholes for SQL injection.

An operator should have a method accepting a MappingContext and likely a PersistentProperty that applies to it and an abstraction like R2DBC's BindMarkers. In return, we need a tuple of bind markers associated with their value and the SQL fragment. Maybe even a slimmer variant of BindTarget (without the binding identifier) would suit fit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mp911de Am I correct that the desired interface signature should look like this:

public interface DialectCriteriaCondition {

   BindTarget render(MappingContext context, PersistentProperty property);
}

If so, then I have a couple of questions here:

  1. Criteria API by itself is an open loophole to SQL injection, like it is how it is designed, since we're build query literally by concatinating values into SQL. Maybe if we're going to address this problem, then we can do it in a separate issue, what do you think?
  2. The Criteria API is a top level API designed for end users, do we want to expose MappingContext, PersistentProperty an other abstraction? They are public, but it does not seem to be desirable to expose them to users.
  3. What is the custom operator is not performed on a single PersistentProperty?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I tend to disagree. Combinator and Comparator enums are closed types that do not allow for extensions. In return, Spring Data Relational controls what enum values are available to ensure we only specify safe operators. At some point, we have to translate criteria elements into a String. Specifically referring to the linked method, rendering of criteria elements inside of Criteria is limited to toString() usage. The actual mapping of CriteriaDefinition to Condition (the SQL DSL) happens in QueryMapper.
  2. It depends on how close we want to bring MappingContext towards users. Ideally, we have an API that allows building comparators/query elements without leading users into Spring Data's Mapping infrastructure. Something like Postgres.arrayContains("first", "second") where the resulting value is used in Criteria so that users don't have to interact with methods exposed by the result object of Postgres.arrayContains(…). We have a similar arrangement in MongoDB with AggregationOperation. AggregationOperationContext handles some abstraction for mapping and I think we might end up doing something similar.
  1. What is the custom operator is not performed on a single PersistentProperty?

This also crossed my mind. There could be variants where two functions compare against each other or the property is specified on the right side. So far, we have to consider this in some way. Let's however start from a simpler side first so that we can implement a first proof of concept before digging into the more complex things. It is typically easier to build something (starting from the side how things should be expressed in code) and then we can iterate on it towards a variant that handles an increasing number of usecases.

@mipo256 mipo256 requested a review from mp911de February 24, 2025 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: feedback-provided Feedback has been provided type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce abstractions to define custom criteria predicates
4 participants