Skip to content

Commit

Permalink
Alpha release (#12)
Browse files Browse the repository at this point in the history
* Query clauses documentation

* documented statement clauses
documented part of the graph clauses

* graph traversal clauses
supported functions list

* * Renamed 'group' clause to 'into'
* upped phpstan level by 1 + accompanying small code improvements
* added repo info badges
* fixed @Covers references

* * Test improvements & fixes

* Doc structure improvements

* Renamed ArithmeticExpression to MathExpression

* * fixed var

* * tests/Unit/ file restructure to more closely match src/

* added tests

* * added credits to documentation

* simplified sort API

* simplified edgeCollections API

* split bind method in bind and bindCollection

* code cleanup

* documentation improvements

removed unused code

added coverage doc

renamed ci workflow
  • Loading branch information
LaravelFreelancerNL authored Aug 31, 2020
1 parent c340222 commit b16de75
Show file tree
Hide file tree
Showing 49 changed files with 822 additions and 379 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Continuous Integration
name: CI tests

on: [workflow_dispatch, push, pull_request]

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ phpunit.phar
/vendor
.cache
*.cache*
/clover.xml
44 changes: 32 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

Fluent PHP query builder for [ArangoDB’s](https://www.arangodb.com) Query Language ([AQL](https://www.arangodb.com/docs/stable/aql/)).

(badges)
[![Latest Unstable Version](https://poser.pugx.org/laravel-freelancer-nl/fluentaql/v/unstable)](//packagist.org/packages/laravel-freelancer-nl/fluentaql)
![Github CI tests](https://github.com/LaravelFreelancerNL/fluentaql/workflows/Continuous%20Integration/badge.svg)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/LaravelFreelancerNL/aql-query-builder/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/LaravelFreelancerNL/aql-query-builder/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/LaravelFreelancerNL/fluentaql/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/LaravelFreelancerNL/fluentaql/?branch=master)
[![License](https://poser.pugx.org/laravel-freelancer-nl/fluentaql/license)](//packagist.org/packages/laravel-freelancer-nl/fluentaql)

**1.0.0-alpha release: API may change before a stable version is released**

## Table of contents
1. [Use Cases](#purpose)
2. [Requirements](#requirements)
Expand Down Expand Up @@ -34,16 +41,23 @@ The use of a query builder has both pros and cons. It is up to you to decide wha
## Requirements
| FluentAQL | ArangoDB | PHP |
| :------------------ | :---------------- | :---------------- |
| 0.x | 3.x * | ^7.2 |
| 1.x | 3.x * | ^7.2 |

* ArangoDB regularly adds AQL functions and clauses in minor versions. So be sure to check the AQL documentation for the availability of specific features.

## Installation
The easiest way to install FluentAQL is through composer:
You know the drill:
```
composer require laravel-freelancer-nl/fluentaql
```

## Before you begin: safety first!
FluentAQL is a query builder that focuses on making your life as a developer easier while maintaining the strength
and flexibility of AQL. It focuses on syntax checking of the provided expressions
however that is not airtight if you don't bind user input.

**Always bind user input.**

## Usage
First fire up a new query builder then fluently add AQL clauses on top.
### Step 1: create a query builder:
Expand Down Expand Up @@ -78,15 +92,6 @@ The generated AQL for this query is:
FOR i IN 1..100 FILTER i < 50 LIMIT 10 SORT i DESC RETURN i
```

## API
See the following pages on details for the API

- [Query clauses](docs/api/query-clauses.md): how to search, select, sort and limit data
- [Statement clauses](docs/api/statement-clauses.md): data manipulation & variable declaration
- [Graph clauses](docs/api/graph-clauses.md): graph traversals
- [Functions](docs/api/functions.md): a list of all supported AQL functions
- [Subqueries](docs/api/subqueries.md): how to create subqueries, joins etc.

### (Always) bind user input
No matter what, never trust user input and always bind it.
```
Expand All @@ -98,6 +103,18 @@ Binds are registered in order and given an id. If you want to specify the bind n
$qb->bind('your data', 'your-bind-id')
```

## Documentation
- API
- [Query clauses](docs/api/query-clauses.md): how to search, select, sort and limit data
- [Statement clauses](docs/api/statement-clauses.md): data manipulation & variable declaration
- [Graph clauses](docs/api/graph-clauses.md): graph traversals
- [Functions](docs/api/functions.md): a list of all supported AQL functions
- [Subqueries](docs/core-concepts/subqueries.md): how to create subqueries, joins etc.
- Core Concepts
- [Terminology](docs/core-concepts/terminology.md): definitions of terms used in the documentation
- [Data binding](docs/core-concepts/data-binding.md): How to inject external data and collections
- [Subqueries](docs/core-concepts/subqueries.md): Subquery creation

## References & resources

### ArangoDB
Expand All @@ -107,3 +124,6 @@ $qb->bind('your data', 'your-bind-id')
### ArangoDB PHP clients
- [Official ArangoDB PHP driver](https://github.com/arangodb/arangodb-php)
- [Community PHP driver](https://github.com/sandrokeil/arangodb-php-client)

## Credits
- Pluma@arangodb
44 changes: 23 additions & 21 deletions docs/api/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This page gives an overview of the AQL functions supported by this query builder
If you are missing a function please create an issue requesting it. In the meantime you can use the raw clause.

## Using functions
Functions always return expressions to be used in clauses and other functions.
Functions always return expressions to be used in clauses or other expressions.

Example:
```
Expand All @@ -17,44 +17,46 @@ $qb->for('i', '1..100')
->return('i');
```


## Array functions

| Description | AQL Function |
| :-------------------- | :-------------------------------------------------------------------------------------------- |
| count($value) | [COUNT()](https://www.arangodb.com/docs/stable/aql/functions-array.html#count) |
| countDistinct($value) | [COUNT_DISTINCT()](https://www.arangodb.com/docs/stable/aql/functions-array.html#count) |
| first($value) | [FIRST()](https://www.arangodb.com/docs/stable/aql/functions-array.html#first) |
| last($value) | [LAST()](https://www.arangodb.com/docs/stable/aql/functions-array.html#last) |
| length($value) | [LENGTH()](https://www.arangodb.com/docs/stable/aql/functions-array.html#length) |
| count($value) | [COUNT(anyArray)](https://www.arangodb.com/docs/stable/aql/functions-array.html#count) |
| countDistinct($value) | [COUNT_DISTINCT(anyArray)](https://www.arangodb.com/docs/stable/aql/functions-array.html#count) |
| first($value) | [FIRST(anyArray)](https://www.arangodb.com/docs/stable/aql/functions-array.html#first) |
| last($value) | [LAST(anyArray)](https://www.arangodb.com/docs/stable/aql/functions-array.html#last) |
| length($value) | [LENGTH(anyArray)](https://www.arangodb.com/docs/stable/aql/functions-array.html#length) |


## Date functions

| Description | AQL Function |
| :-------------------- | :-------------------------------------------------------------------------------------------- |
| dateNow() | [DATE_NOW()](https://www.arangodb.com/docs/stable/aql/functions-date.html#date_now) |
| dateIso8601(numeric&#124;string&#124;DateTime $date) | [DATE_ISO8601(date)](https://www.arangodb.com/docs/stable/aql/functions-date.html#date_iso8601) |
| dateTimestamp(numeric&#124;string&#124;DateTime $date) | [DATE_TIMESTAMP(date)](https://www.arangodb.com/docs/stable/aql/functions-date.html#date_timestamp) |
| | []() |
| | []() |
| | []() |
| | []() |
| dateYear($date) | [DATE_YEAR(date)](https://www.arangodb.com/docs/stable/aql/functions-date.html#date_year) |
| dateMonth($date) | [DATE_MONTH(date)](https://www.arangodb.com/docs/stable/aql/functions-date.html#date_month) |
| dateDay($date) | [DATE_DAY(date)](https://www.arangodb.com/docs/stable/aql/functions-date.html#date_day) |
| dateHour($date) | [DATE_HOUR(date)](https://www.arangodb.com/docs/stable/aql/functions-date.html#date_hour) |
| dateMinute($date) | [DATE_MINUTE(date)](https://www.arangodb.com/docs/stable/aql/functions-date.html#date_minute) |
| dateSecond($date) | [DATE_SECOND(date)](https://www.arangodb.com/docs/stable/aql/functions-date.html#date_second) |
| dateMillisecond($date) | [DATE_MILLISECOND(date)](https://www.arangodb.com/docs/stable/aql/functions-date.html#date_millisecond) |

## GEO functions

| Description | AQL Function |
| :-------------------- | :-------------------------------------------------------------------------------------------- |
| distance($latitude1, $longitude1, $latitude2, $longitude2) | [DISTANCE(latitude1, longitude1, latitude2, longitude2)](https://www.arangodb.com/docs/stable/aql/functions-geo.html#distance) |

## Miscellaneous functions

| Description | AQL Function |
| :-------------------- | :-------------------------------------------------------------------------------------------- |
| document($collection, $id = null) | [DOCUMENT(collection, id)](https://www.arangodb.com/docs/stable/aql/functions-miscellaneous.html#document) |
| average($value) | []() |
| avg($value) | []() |
| max($value) | []() |
| min($value) | []() |
| rand() | []() |
| sum($value) | []() |

## Numeric functions
| Description | AQL Function |
| :-------------------- | :-------------------------------------------------------------------------------------------- |
| average($value) | [AVERAGE(numArray](https://www.arangodb.com/docs/stable/aql/functions-numeric.html#average) |
| avg($value) | [AVG(numArray](https://www.arangodb.com/docs/stable/aql/functions-numeric.html#average) |
| max($value) | [MAX(anyArray)](https://www.arangodb.com/docs/stable/aql/functions-numeric.html#max) |
| min($value) | [MIN(anyArray)](https://www.arangodb.com/docs/stable/aql/functions-numeric.html#min) |
| rand() | [RAND()](https://www.arangodb.com/docs/stable/aql/functions-numeric.html#rand) |
| sum($value) | [SUM(numArray)](https://www.arangodb.com/docs/stable/aql/functions-numeric.html#sum) |
16 changes: 14 additions & 2 deletions docs/api/graph-clauses.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,12 @@ Resulting AQL: `WITH users, cities FOR v, e, p ANY "users/1" GRAPH "citizens"`

## EDGE COLLECTIONS (unnamed graph)
```
edgeCollections(...$edgeCollections)
edgeCollections(...$edgeCollections)
```
Execute the previously set traversal on the given set of edge collections.
To specify a direction add it before the accompanying edge collection.

**Example:**
**Example 1: multiple edge collections**
```
$qb = new QueryBuilder();
$qb->with('users', 'cities')
Expand All @@ -164,6 +165,17 @@ Execute the previously set traversal on the given set of edge collections.
```
Resulting AQL: `WITH users, cities FOR v, e, p ANY "users/1" edge1, edge2, edge3`

**Example 2: with directions**
```
$qb = new QueryBuilder();
$qb->with('users', 'cities')
->for(['v', 'e', 'p'])
->traverse('users/1', 'ANY')
->edgeCollections('edge1', 'inbound', edge2', 'edge3')
```
Resulting AQL: `WITH users, cities FOR v, e, p ANY "users/1" edge1, INBOUND edge2, edge3`


[ArangoDB UNNAMED GRAPH documentation](https://www.arangodb.com/docs/stable/aql/graphs-traversals.html#working-with-collection-sets)


Expand Down
57 changes: 15 additions & 42 deletions docs/api/query-clauses.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Resulting AQL: `... SEARCH u.age < 18 OR u.age > 65 ...`
```
sort($reference, $direction)
```
Return the result of a (sub)query.
Return the result of a (sub)query. To set a direction add the appropriate string attribute to the method.

**Example 1: default direction (asc)**
```
Expand All @@ -120,7 +120,7 @@ Resulting AQL: `... SORT user.name DESC ...`

**Example 3: sort by multiple attributes**
```
$qb->sort(['user.name', 'desc'], 'user.email');
$qb->sort('user.name', 'desc', 'user.email');
```
Resulting AQL: `... SORT user.name DESC, user.email ...`

Expand Down Expand Up @@ -154,7 +154,7 @@ Resulting AQL: `... LIMIT 5 10 ...`
```
collect($variableName = null, $expression = null)
```
Group an array by one or more group criteria.
Group an array by one or more into criteria.

**Example 1:**
```
Expand All @@ -164,22 +164,20 @@ Resulting AQL: `... COLLECT cities = user.city ...`

[ArangoDB COLLECT documentation](https://www.arangodb.com/docs/stable/aql/operations-collect.html)

### GROUP
### INTO
```
group($groupsVariable, $projectionExpression = null)
into($groupsVariable, $projectionExpression = null)
```
This addition to collect stores all collected elements in $groupsVariable.

**Note 1:** the group clause is only useful following a collect or aggregate clause.
**Note 2:** AQL does not have an actual group clause. Within this query builder we use GROUP to prevent confusing
with the 'INTO' keyword that is used in other places in AQL as well.
**Note:** the into clause is only useful following a collect or aggregate clause.

**Example 1: group the users that are in the collected city**
```
$qb->for('user', 'users')
->let('name', 'user.name')
->collect('cities', 'user.city')
->group('users-in-this-city')
->into('users-in-this-city')
->return(['city' => 'city', 'usersInThisCity' => 'users-in-this-city']);
```
Resulting AQL: `... COLLECT cities = user.city INTO users-in-this-city ...`
Expand All @@ -190,7 +188,7 @@ Resulting AQL: `... COLLECT cities = user.city INTO users-in-this-city ...`
$qb->for('user', 'users')
->let('name', 'user.name')
->collect('cities', 'user.city')
->group('users-in-this-city', 'user.name')
->into('users-in-this-city', 'user.name')
->return(['city' => 'city', 'usersInThisCity' => 'users-in-this-city']);
```
Resulting AQL: `... COLLECT cities = user.city INTO users-in-this-city = user.name ...`
Expand All @@ -200,19 +198,19 @@ Resulting AQL: `... COLLECT cities = user.city INTO users-in-this-city = user.na

### KEEP
```
group($groupsVariable, $projectionExpression = null)
keep($keepVariable)
```
Discard anything but the attributes to keep from the grouped data.

**Note 1:** the keep clause is only useful following the group clause.
**Note 1:** the keep clause is only useful following the into clause.

**Example 1: group the users that are in the collected city. Only keep the name variable**
**Example 1: into the users that are in the collected city. Only keep the name variable**
```
$qb->for('user', 'users')
->let('name', 'user.name')
->let('something', 'else')
->collect('cities', 'user.city')
->group('users-in-this-city')
->into('users-in-this-city')
->keep('name')
->return(['city' => 'city', 'usersInThisCity' => 'users-in-this-city']);
```
Expand All @@ -226,15 +224,15 @@ withCount($countVariableName)
```
Discard anything but the attributes to keep from the grouped data.

**Note:** the with count clause is only be used with group.
**Note:** the with count clause is only be used with into.

**Example: **
```
$qb->for('user', 'users')
->let('name', 'user.name')
->let('something', 'else')
->collect('cities', 'user.city')
->group('users-in-this-city')
->into('users-in-this-city')
->keep('name')
->return(['city' => 'city', 'usersInThisCity' => 'users-in-this-city']);
```
Expand All @@ -245,7 +243,7 @@ $qb->for('user', 'users')
```
aggregate($variableName, $aggregateExpression)
```
Aggregate collected data per group
Aggregate collected data per into

**Note:** the aggregate clause can only be used after the collect clause.

Expand Down Expand Up @@ -301,28 +299,3 @@ $qb->for('user', 'users')
```
Resulting AQL: `... OPTIONS {indexHint: 'byName', forceIndexHint: true} ...`

### RAW
```
raw(string $aql, $binds = null, $collections = null)
```
This clause allows you to write raw AQL. This is particularly useful for AQL features that this query builder doesn't support yet.

Of course that means you are responsible for its safety. As always bind user data.


**Example 1: simple query**
```
$qb->raw('for user in users return user.name');
```

**Example 2: with binds**
```
$qb->raw('for user in users filter user.age >= @min-age && user.age <= @max-age return u.name', ['min-age' => 18, 'max-age' => 65]);
```

**Example 2: with collections for deadlock prevention in cluster graph traversals or transactions**
```
$qb->raw('for user in users return user.name', null, ['read' => ['users']]);
```


38 changes: 38 additions & 0 deletions docs/api/raw-aql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# RAW AQL
You can use the raw and rawExpression commands to bypass limitations of this query builder and insert raw AQL.
For example when FluentAQL doesn't support a feature (yet).

As always: you are responsible for its safety. Always bind external input.

## RAW
```
raw(string $aql, $binds = null, $collections = null)
```
Insert raw AQL clauses.

**Example 1: simple query**
```
$qb->raw('for user in users return user.name');
```

**Example 2: with binds**
```
$qb->raw('for user in users filter user.age >= @min-age && user.age <= @max-age return u.name', ['min-age' => 18, 'max-age' => 65]);
```

**Example 3: with collections for deadlock prevention in cluster graph traversals or transactions**
```
$qb->raw('for user in users return user.name', null, ['read' => ['users']]);
```

## RAW expressions
```
rawExpression(string $aql, $binds = [], $collections = []));
```
Insert raw AQL as an expression.

**Example:**
```
$qb->filter(x.age, '==', $qb->rawExpression('5 * 4'));
```

9 changes: 0 additions & 9 deletions docs/core-concepts/arangodb-and-aql.md

This file was deleted.

Empty file.
Loading

0 comments on commit b16de75

Please sign in to comment.