https://github.com/dungba88/libra
Java Predicate, supports SQL-like syntax
https://github.com/dungba88/libra
antlr4 predicates sql-predicates
Last synced: 5 months ago
JSON representation
Java Predicate, supports SQL-like syntax
- Host: GitHub
- URL: https://github.com/dungba88/libra
- Owner: dungba88
- License: mit
- Created: 2017-11-10T20:21:51.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2025-05-28T18:06:24.000Z (about 1 year ago)
- Last Synced: 2025-10-31T00:38:19.320Z (8 months ago)
- Topics: antlr4, predicates, sql-predicates
- Language: Java
- Size: 448 KB
- Stars: 37
- Watchers: 5
- Forks: 4
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# libra
[](http://mvnrepository.com/artifact/org.dungba/joo-libra)
[](http://javadoc.io/doc/org.dungba/joo-libra)
[](https://travis-ci.org/dungba88/libra)
[](https://coveralls.io/github/dungba88/libra?branch=master)
Libra is a Java package for creating and evaluating predicate. Java-based and SQL-like predicate are both supported. For SQL predicates, it is using ANTLR to parse the string against a predefined grammar. The Java-based predicates are implementation of Specification pattern and support numeric/text/collection related conditions.
## install
Libra can be easily installed with Maven:
```xml
org.dungba
joo-libra
```
## how to use
By default, you can simply use `SqlPredicate` class for all the functionality, which supports `satisfiedBy` method to perform the evaluation. A `PredicateContext` needs to be passed to the method.
```java
PredicateContext context = new PredicateContext(customer);
SqlPredicate predicate = new SqlPredicate("customer.age > 50 AND customer.isResidence is true");
predicate.satisfiedBy(context);
```
You can optionally check for syntax errors:
```java
if (predicate.hasError()) {
predicate.getCause().printStackTrace();
}
```
or throw the exception if any
```java
predicate.checkForErrorAndThrow();
```
from `2.0.0` you can retrieve the raw value instead of letting Libra convert it to boolean
```java
PredicateContext context = new PredicateContext(customer);
SqlPredicate predicate = new SqlPredicate("customer.asset - customer.liability");
Object rawValue = predicate.calculateLiteralValue(context);
```
## grammar
Libra supports the following syntax for SQL predicates:
- Logic operators: `and`, `or` and `not`
- Comparison operators: >, >=, <, <=, =, ==, !=, `is`, `is not`
- Parenthesises
- List and string operators: `contains` (for both list and string) and `matches` (only for string)
- Array indexing: `a[0]` (this cannot be used to evaluate a `Map`)
- String literals, single quoted, e.g: `'John'`
- Numeric literals: `1`, `1.0`
- Boolean literals: `true`, `false`
- Other literals: `null`, `undefined`, `empty`
- Variables: alphanumerics, `_`, `.` (to denote nested object) and `[`, `]` (to denote array index), must starts with alphabet characters.
- List: `{1, 2, 3}`. Empty list `{ }` is also supported.
- Function: `functionName(arg1, arg2...)` It's also possible to configure custom function in `PredicateContext`. Built-in functions: `sqrt`, `avg`, `sum`, `min`, `max`, `len`.
- Stream matching: See below
- Subset filtering: See below
**stream matching**
Libra `2.0.0` supports stream-like matching, similar to `anyMatch`, `allMatch` and `noneMatch`. The syntax is:
```
ANY IN SATISFIES
ALL IN SATISFIES
NONE IN SATISFIES
```
`listVariableName` is the name of the list variable you want to perform matching on. `indexVariableName` is the name of the temporary variable used in each loop. For example: `ANY $job IN jobs satisfies $job.salary > 1000` will try to find out if there is ANY element in `jobs` which its `salary` property is greater than 1000. Starting from Libra `2.1.0` the temporary variable name must be started with `$`.
**subset filtering**
Libra `2.1.0` supports subset filtering from list:
```
WITH IN SATISFIES
```
For example `WITH $job IN jobs satisfies $job.salary > 1000` will returns a list of jobs which the `salary` attribute is greater than 1000.
You can also transform the returned list element using transformation expression:
For example: `$job.salary WITH $job IN jobs satisfies $job.salary > 1000` will returns a list of *salary* that is greater than 1000 from the job list.
**examples**
Some examples of SQL predicates:
```
name is 'John' and age > 27
employments contains 'LEGO assistant' and name is 'Anh Dzung Bui'
experiences >= 4 or (skills contains 'Java' and projects is not empty)
avg(4, 5, 6) is 5
```
More examples can be seen inside the [test cases](https://github.com/dungba88/libra/tree/master/src/test/java/org/joo/libra/test)
## quirks and limitations
Some special cases or limitations are covered here:
- Literals, if stand alone in their own branch, will be converted into predicate according to their types:
+ String & list literals will be considered as `true` if and only they are *not null* and *not empty*
+ Number literals will be considered as `true` if and only they are *not null* and *not zero*
+ `null` will always be considered as `false`
- If literals are compared with any other type, the comparison will be as normal
+ `0 is false` will be evaluated as `false`, since `0` and `false` have different type
- Variables, if stand alone in their own branch, will be converted into predicate according to their types:
+ String & list variables will be considered as `true` if and only if they are *not null* and *not empty*
+ Number variables will be considered as `true` if and only if they are *not null* and *not zero*
+ Boolean variables will be considered as their own values
+ `null` variables will always be considered as `false`
- When comparing number, they will be converted into `BigDecimal`, so `0.0`, `0` or `0L` are all equal
## optimizers
Libra currently supports a simple [Constant Folding](https://en.wikipedia.org/wiki/Constant_folding) optimization. It will reduces constant-only conditional branches into a single branch. To enable the optimizations, use `OptimizedAntlrSqlPredicateParser` as below:
```java
SqlPredicate predicate = new SqlPredicate(predicateString, new OptimizedAntlrSqlPredicateParser());
```
This will take more time to compile the SQL but will reduce evaluation time.
## extends
The `SqlPredicate` class allows you to use your own `SqlPredicateParser`:
```java
SqlPredicate predicate = new SqlPredicate(predicateString, new MyPredicateParser());
```
you can implement `SqlPredicateParser`, or extend the `AbstractAntlrSqlPredicateParser` to use your own grammar. For the former, the interface has only one method `public Predicate parse(String predicate) throws MalformedSyntaxException`, so you can even use lambda expression to construct it, like:
```java
SqlPredicate predicate = new SqlPredicate(predicateString, predicate -> {
return something;
});
```
or use method reference:
```java
SqlPredicate predicate = new SqlPredicate(predicateString, this::parseSql);
```
## performance considerations
It is better to cache the parsed version of sql and if possible, try to load all of them at startup. If you keep the `SqlPredicate` objects, they will contain the parsed predicate to be reused.
The runtime evaluation is quite fast (2 millions ops/sec with Java object or 5 millions ops/sec with `Map`). You can also consider using `Map` because it's significantly faster.
## license
This library is distributed under MIT license. See [LICENSE](LICENSE)