Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# API Reference

The Expression DSL library has a concise API. This document highlights the most important classes and methods you will interact with.

For a complete, detailed reference, please consult the Javadoc for the library.

## Core Classes

### `com.aerospike.dsl.api.DSLParser`

This is the main interface and entry point for the library.

* **`ParsedExpression parseExpression(ExpressionContext input)`**
* **Description**: The primary method used to parse a DSL string when no secondary indexes are provided. It returns a `ParsedExpression` object that contains the compiled result, which can be reused.
* **`ParsedExpression parseExpression(ExpressionContext input, IndexContext indexContext)`**
* **Description**: The primary method used to parse a DSL string. It returns a `ParsedExpression` object that contains the compiled result, which can be reused.
* **Parameters**:
* `input`: An `ExpressionContext` object containing the DSL string and any placeholder values.
* **Description**: The primary method used to parse a DSL string. It returns a `ParsedExpression` object that contains the compiled result, which can be reused.
* **Parameters**:
* `input`: An `ExpressionContext` object containing the DSL string and any placeholder values.
* `indexContext`: An optional `IndexContext` object containing a list of available secondary indexes for query optimization. Can be `null`.
* **Returns**: A `ParsedExpression` object representing the compiled expression tree.

### `com.aerospike.dsl.ExpressionContext`

This class is a container for the DSL string and any values to be substituted for placeholders.

* **`static ExpressionContext of(String dslString)`**: Creates a context for a DSL string without placeholders.
* **`static ExpressionContext of(String dslString, PlaceholderValues values)`**: Creates a context for a DSL string that uses `?` placeholders, providing the values to be substituted.

### `com.aerospike.dsl.ParsedExpression`

This object represents the compiled, reusable result of a parsing operation. It is thread-safe.

* **`ParseResult getResult()`**: Returns the final `ParseResult` for an expression that does not contain placeholders.
* **`ParseResult getResult(PlaceholderValues values)`**: Returns the final `ParseResult` by substituting the given placeholder values into the compiled expression tree. This is highly efficient as it bypasses the parsing step.

### `com.aerospike.dsl.ParseResult`

This class holds the final, concrete outputs of the parsing and substitution process.

* **`Filter getFilter()`**: Returns an Aerospike `Filter` object if the parser was able to optimize a portion of the DSL string into a secondary index query. Returns `null` if no optimization was possible.
* **`com.aerospike.client.exp.Expression.Exp getExp()`**: Returns the Aerospike `Exp` object representing the DSL filter logic. This is the part of the expression that will be executed on the server for records that pass the secondary index filter. If the entire DSL string was converted into a `Filter`, this may be `null`.

### `com.aerospike.dsl.IndexContext`

A container for the information required for automatic secondary index optimization.

* **`static IndexContext of(String namespace, Collection<Index> indexes)`**: Creates a context.
* `namespace`: The namespace the query will be run against.
* `indexes`: A collection of `Index` objects representing the available secondary indexes for that namespace.

## Example API Flow

Here is a recap of how the classes work together in a typical use case:

```java
// 1. Get a parser instance
DSLParser parser = new DSLParserImpl();

// 2. Define the context for the expression and placeholders
String dsl = "$.age > ?0";
ExpressionContext context = ExpressionContext.of(dsl, PlaceholderValues.of(30));

// (Optional) Define the index context for optimization
IndexContext indexContext = IndexContext.of("namespace", availableIndexes);

// 3. Parse the expression once to get a reusable object
ParsedExpression parsedExpression = parser.parseExpression(context, indexContext);

// 4. Get the final result by substituting values
// This step can be repeated many times with different values
ParseResult result = parsedExpression.getResult();

// 5. Extract the Filter and Expression for use in a QueryPolicy
Filter siFilter = result.getFilter();
Expression filterExp = Exp.build(result.getExp());

QueryPolicy policy = new QueryPolicy();
policy.filterExp = filterExp;
// Note: The Java client does not have a separate field for the secondary index filter.
// The filter is applied by the client before sending the query.
```
146 changes: 146 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Getting Started with Aerospike Expression DSL

Welcome to the Aerospike Expression DSL for Java! Let's walk you through the first steps, from setup to running your first query.

## What is the Expression DSL?

The Aerospike Expression DSL is a Java library that provides a simple, string-based language to create powerful server-side filters. Instead of building complex filter objects in Java, you can write an intuitive expression string, which the library translates into a native Aerospike Expression.

For example, instead of writing this in Java:

```java
Expression exp = Exp.build(
Exp.and(
Exp.gt(Exp.intBin("age"), Exp.val(30)),
Exp.eq(Exp.stringBin("country"), Exp.val("US"))
)
);
```

You can simply write this string:

```
"$.age > 30 and $.country == 'US'"
```

This makes your filter logic easier to write, read, and even store as configuration.

## Quickstart: Your First Filtered Query

Let's build and run a complete example.

### Prerequisites

1. **Java 17+**: Ensure you have a compatible JDK installed.
2. **Maven or Gradle**: For managing dependencies.
3. **Aerospike Database**: An Aerospike server instance must be running. The easiest way to get one is with Docker:
```sh
docker run -d --name aerospike -p 3000:3000 -p 3001:3001 -p 3002:3002 aerospike/aerospike-server-enterprise
```

### 1. Project Setup

Add the Expression DSL and the Aerospike Java Client as dependencies to your project.

**Maven (`pom.xml`):**
```xml
<dependencies>
<dependency>
<groupId>com.aerospike</groupId>
<artifactId>aerospike-expression-dsl</artifactId>
<version>0.1.0</version> <!-- Check for the latest version -->
</dependency>
<dependency>
<groupId>com.aerospike</groupId>
<artifactId>aerospike-client-jdk8</artifactId>
<version>8.1.1</version> <!-- Check for a compatible version -->
</dependency>
</dependencies>
```

### 2. Write the Code

Here is a Java example. It connects to a local Aerospike instance, writes a few sample records, and then uses a DSL expression to query for a subset of that data.

```java
import com.aerospike.client.*;
import com.aerospike.client.exp.Exp;
import com.aerospike.client.exp.Expression;
import com.aerospike.client.policy.QueryPolicy;
import com.aerospike.client.query.RecordSet;
import com.aerospike.client.query.Statement;
import com.aerospike.dsl.ExpressionContext;
import com.aerospike.dsl.ParsedExpression;
import com.aerospike.dsl.api.DSLParser;
import com.aerospike.dsl.impl.DSLParserImpl;

public class DslQuickstart {

public static void main(String[] args) {
// 1. Connect to Aerospike
try (AerospikeClient client = new AerospikeClient("127.0.0.1", 3000)) {
// 2. Write some sample data
writeSampleData(client);

// 3. Define and Parse the DSL Expression
DSLParser parser = new DSLParserImpl();
String dslString = "$.age > 30 and $.city == 'New York'";
System.out.println("Using DSL Expression: " + dslString);

ParsedExpression parsedExpression = parser.parseExpression(ExpressionContext.of(dslString));
Expression filterExpression = Exp.build(parsedExpression.getResult().getExp());

// 4. Create and Execute the Query
QueryPolicy queryPolicy = new QueryPolicy();
queryPolicy.filterExp = filterExpression;

Statement stmt = new Statement();
stmt.setNamespace("test");
stmt.setSetName("users");

System.out.println("\nQuery Results:");
try (RecordSet rs = client.query(queryPolicy, stmt)) {
while (rs.next()) {
System.out.println(rs.getRecord());
}
}
}
}

private static void writeSampleData(AerospikeClient client) {
String namespace = "test";
String setName = "users";

client.put(null, new Key(namespace, setName, "user1"),
new Bin("name", "Alice"), new Bin("age", 28), new Bin("city", "San Francisco"));
client.put(null, new Key(namespace, setName, "user2"),
new Bin("name", "Bob"), new Bin("age", 35), new Bin("city", "New York"));
client.put(null, new Key(namespace, setName, "user3"),
new Bin("name", "Charlie"), new Bin("age", 42), new Bin("city", "New York"));
client.put(null, new Key(namespace, setName, "user4"),
new Bin("name", "Diana"), new Bin("age", 29), new Bin("city", "Chicago"));

System.out.println("Sample data written.");
}
}
```

### 3. Run and Verify

When you run this code, you will see the following output. Notice that only the two records matching the DSL filter (`age > 30` AND `city == 'New York'`) are returned.

```
Sample data written.
Using DSL Expression: $.age > 30 and $.city == 'New York'

Query Results:
(gen:1),(exp:486523),(bins:(name:Bob),(age:35),(city:New York))
(gen:1),(exp:486523),(bins:(name:Charlie),(age:42),(city:New York))
```

Congratulations! You've successfully used the Expression DSL to filter records in Aerospike.

### Next Steps

* Explore the **Core Concepts in How-To Guides** to learn about more advanced filtering capabilities.
* Check out the **Installation & Setup Guide** for detailed configuration options.
136 changes: 136 additions & 0 deletions docs/guides/01-writing-your-first-expression.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Guide: Writing Your First Expression

This guide covers the fundamental syntax of the Aerospike Expression DSL. You will learn how to filter records based on bin values and combine multiple conditions.

## Anatomy of the DSL

The Expression DSL is a functional language for applying predicates to Aerospike bin data and record metadata. Here’s a breakdown of a simple example:

```
$.binName > 100
^ ^ ^ ^
| | | |
| | | +---- Value (can be integer, float, or 'string')
| | +------- Comparison Operator (==, !=, >, <, etc.)
| +----------- Bin Name and / or Function
+---------------- Path Operator (always starts with $.)
```

### Path Operator (`$.`)

All expressions start with `$.` to signify the root of the record. You follow it with the name of a bin (e.g., `$.name`) or a metadata function (e.g., `$.lastUpdate()`).

### Operators

The DSL supports a rich set of operators.

* **Comparison**: `==`, `!=`, `>`, `>=`, `<`, `<=`
* **Logical**: `and`, `or`, `not()`, `exclusive()`
* **Arithmetic**: `+`, `-`, `*`, `/`, `%`

### Values

* **Integers**: `100`, `-50`
* **Floats**: `123.45`
* **Strings**: Must be enclosed in single quotes, e.g., `'hello'`, `'US'`.
* **Booleans**: `true`, `false`

## Filtering on Bin Values

Here are some examples of basic filters on different data types.

### Numeric Bins

To filter on a bin containing an integer or float, use standard comparison operators.

**DSL String:**
```
"$.age >= 30"
```

**Java Usage:**
```java
ExpressionContext context = ExpressionContext.of("$.age >= 30");
ParsedExpression parsed = parser.parseExpression(context);
QueryPolicy queryPolicy = new QueryPolicy();
queryPolicy.filterExp = Exp.build(parsed.getResult().getExp());
```

### String Bins

Remember to enclose string literals in single quotes.

**DSL String:**
```
"$.country == 'US'"
```

**Java Usage:**
```java
ExpressionContext context = ExpressionContext.of("$.country == 'US'");
// ...
```

### Boolean Bins

**DSL String:**
```
"$.active == true"
```

## Combining Conditions with Logical Operators

You can build complex filters by combining conditions with `and` and `or`. Use parentheses `()` to control the order of evaluation.

### `and` Operator

Returns records that match **all** conditions.

**DSL String:**
```
"$.age > 30 and $.country == 'US'"
```

### `or` Operator

Returns records that match **at least one** of the conditions.

**DSL String:**
```
"$.tier == 'premium' or $.logins > 100"
```

### `not()` Operator

Negates a condition.

**DSL String:**
```
"not($.country == 'US')"
```

### `exclusive()` Operator

Creates an expression that returns true if only one of its parts is true.

**DSL String:**
```
"exclusive($.x < '5', $.x > '5')"
```

### Controlling Precedence with Parentheses

Just like in mathematics, you can use parentheses to group expressions and define the order of operations. The `and` operator has a higher precedence than `or`.

Consider this expression:
```
"$.age > 65 or $.age < 18 and $.isStudent == true"
```

This is evaluated as `$.age > 65 or ($.age < 18 and $.isStudent == true)`.

To get the intended logic, use parentheses:
```
"($.age > 65 or $.age < 18) and $.isStudent == true"
```
This expression correctly filters for users who are either over 65 or under 18, and who are also students.
Loading
Loading