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

Support for fuction call #137

Merged
merged 15 commits into from
Feb 15, 2024
Merged
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,4 +404,61 @@ You must use the [ISO 8601](https://json-schema.org/understanding-json-schema/re
>If your request conform timezone pattern, the library will use `jsonb_path_exists_tz.
>Then consider the timezone consideration of the [official documentation](https://www.postgresql.org/docs/current/functions-json.html)

## Stored procedure

RSQL can call a stored procedure with the following syntax for both search and sort.
In order to be authorized to call a stored procedure, it must be `whitelisted` and not `blacklisted`.
The only way to whitelist or blacklist a stored procedure is to use the `QuerySupport` when performing the search or the `SortSupport` when performing the sort.

```java
String rsql = "@concat[greetings|#123]=='HELLO123'";
QuerySupport querySupport = QuerySupport.builder()
.rsqlQuery(rsql)
.procedureWhiteList(List.of("concat", "upper"))
.build();
List<Item> companies = itemRepository.findAll(toSpecification(querySupport));
```

>Regex like expression can be used to whitelist or blacklist stored procedure.

### Syntax

A procedure must be prefixed with `@` and called with `[]` for arguments.

```
@procedure_name[arg1|arg2|...]
```

### Arguments

Arguments are separated by `|` and can be:
* constant (null, boolean, number, string), prefixed with `#`
* column name
* other procedure call

```
@procedure_name[arg1|arg2|...]
@procedure_name[column1|column2|...]
@procedure_name[@function_name[arg1|arg2|...]|column1|#textvalue|#123|#true|#false|#null]
```

For text value, since space is not supported by RSQL, you can use `\t` to replace space.


### Usage

#### Search

```java
String rsql1 = "@upper[code]==HELLO";
String rsql2 = "@concat[@upper[code]|name]=='TESTTest Lab'";
String rsql3 = "@concat[@upper[code]|#123]=='HELLO123'";
```

#### Sort

```java
String sort1 = "@upper[code],asc";
String sort2 = "@concat[@upper[code]|name],asc";
String sort3 = "@concat[@upper[code]|#123],asc";
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.perplexhub.rsql;

public class FunctionBlackListedException extends RSQLException {

public FunctionBlackListedException(String functionName) {
super(String.format("Function '%s' is blacklisted", functionName));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.perplexhub.rsql;

public class FunctionNotWhiteListedException extends RSQLException {

public FunctionNotWhiteListedException(String functionName) {
super(String.format("Function '%s' is not whitelisted", functionName));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import java.util.Optional;

public class PathUtils {

private PathUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

/**
* If the beginning of the property path is mapped, replace it with the mapped value.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,14 @@ protected Map<Class, ManagedType> getManagedTypeMap() {
return managedTypeMap != null ? managedTypeMap : Collections.emptyMap();
}

protected Map<String, EntityManager> getEntityManagerMap() {
public static Map<String, EntityManager> getEntityManagerMap() {
return entityManagerMap != null ? entityManagerMap : Collections.emptyMap();
}

public static Database getDatabase(EntityManager entityManager) {
return entityManagerDatabase.get(entityManager);
}

protected abstract Map<String, String> getPropertyPathMapper();

public Map<Class<?>, Map<String, String>> getPropertyRemapping() {
Expand Down Expand Up @@ -220,13 +224,11 @@ protected <T> boolean hasPropertyName(String property, ManagedType<T> classMetad
}

@SneakyThrows
protected Class getElementCollectionGenericType(Class type, Attribute attribute) {
protected static Class getElementCollectionGenericType(Class type, Attribute attribute) {
Member member = attribute.getJavaMember();
if (member instanceof Field) {
Field field = (Field) member;
if (member instanceof Field field) {
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType rawType = (ParameterizedType) genericType;
if (genericType instanceof ParameterizedType rawType) {
Class elementCollectionClass = Class.forName(rawType.getActualTypeArguments()[0].getTypeName());
log.info("Map element collection generic type [{}] to [{}]", attribute.getName(), elementCollectionClass);
return elementCollectionClass;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import lombok.Data;

import jakarta.persistence.criteria.JoinType;

import java.util.Collection;
import java.util.List;
import java.util.Map;

Expand All @@ -23,6 +25,8 @@ public class QuerySupport {
private Map<String, JoinType> joinHints;
private Map<Class<?>, List<String>> propertyWhitelist;
private Map<Class<?>, List<String>> propertyBlacklist;
private Collection<String> procedureWhiteList;
private Collection<String> procedureBlackList;

public static class QuerySupportBuilder {}

Expand Down
Loading
Loading