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
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ A full-featured [FoundationDB](https://www.foundationdb.org/) client for PHP, bu
## Installation

```bash
composer require crazy-goat/foundationdb
composer require crazy-goat/fdb-php
```

Make sure `libfdb_c.so` is installed and accessible. On Ubuntu/Debian:
Expand Down Expand Up @@ -150,17 +150,23 @@ $dir->remove($db, ['app', 'orders']);
### Atomic Operations

```php
$db->transact(function (Transaction $tr) {
// Increment a little-endian counter
$tr->add('counter', pack('P', 1));

// Bitwise operations
$tr->bitXor('flag', pack('C', 1));
$tr->bitOr('permissions', pack('C', 0b00001100));

// Compare and clear
$tr->compareAndClear('temp', pack('P', 0));
});
// Integer atomic ops accept int directly — no pack() needed
$db->add('counter', 1);
$db->add('counter', 10);
$db->max('high_score', 999);
$db->min('response_time', 42);

// Bitwise operations
$db->bitOr('flags', 0b00001100);
$db->bitAnd('flags', 0b11110011);
$db->bitXor('flags', 0b00000001);

// Read integer values — no unpack() needed
$count = $db->getInt('counter'); // ?int
$flags = $db->getInt('flags'); // ?int

// Compare and clear (raw bytes)
$db->compareAndClear('temp', 'expected_value');
```

### Snapshot Reads
Expand Down
142 changes: 93 additions & 49 deletions docs/atomic-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,106 +6,150 @@

Atomic operations modify values without reading them first, avoiding read-write conflicts. They're performed as part of a transaction but don't require a read conflict range.

## Available Operations

| Method | MutationType | Description |
|--------|-------------|-------------|
| `add($key, $param)` | Add | Interprets both values as little-endian integers and adds them |
| `bitAnd($key, $param)` | BitAnd | Bitwise AND |
| `bitOr($key, $param)` | BitOr | Bitwise OR |
| `bitXor($key, $param)` | BitXor | Bitwise XOR |
| `max($key, $param)` | Max | Keeps the lexicographically greater value |
| `min($key, $param)` | Min | Keeps the lexicographically lesser value |
| `byteMax($key, $param)` | ByteMax | Byte-string maximum |
| `byteMin($key, $param)` | ByteMin | Byte-string minimum |
| `compareAndClear($key, $param)` | CompareAndClear | Clears key if current value equals param |
| `setVersionstampedKey($key, $value)` | SetVersionstampedKey | Sets key with versionstamp |
| `setVersionstampedValue($key, $param)` | SetVersionstampedValue | Sets value with versionstamp |
## Integer Operations

The `add()`, `max()`, `min()`, `bitAnd()`, `bitOr()`, and `bitXor()` methods accept `int` parameters directly. The library handles the little-endian encoding internally.

| Method | Param Type | Description |
|--------|-----------|-------------|
| `add($key, int $param)` | `int` | Adds the integer to the stored value |
| `max($key, int $param)` | `int` | Keeps the larger value |
| `min($key, int $param)` | `int` | Keeps the smaller value |
| `bitAnd($key, int $param)` | `int` | Bitwise AND |
| `bitOr($key, int $param)` | `int` | Bitwise OR |
| `bitXor($key, int $param)` | `int` | Bitwise XOR |

## Byte Operations

These operate on raw byte strings and keep `string` parameters:

| Method | Param Type | Description |
|--------|-----------|-------------|
| `byteMax($key, string $param)` | `string` | Byte-string maximum |
| `byteMin($key, string $param)` | `string` | Byte-string minimum |
| `compareAndClear($key, string $param)` | `string` | Clears key if current value equals param |
| `setVersionstampedKey($key, string $value)` | `string` | Sets key with versionstamp |
| `setVersionstampedValue($key, string $param)` | `string` | Sets value with versionstamp |

## Counters

The most common use case for atomic operations is implementing counters:
The most common use case — no `pack()` / `unpack()` needed:

```php
use CrazyGoat\FoundationDB\Transaction;

// Increment a counter by 1
$db->transact(function (Transaction $tr) {
$tr->add('page_views', pack('P', 1)); // P = unsigned 64-bit LE
});
// Increment a counter
$db->add('page_views', 1);

// Increment by 10
$db->add('page_views', 10);

// Read the counter — returns int directly
$count = $db->getInt('page_views');
echo "Page views: {$count}\n"; // 11

// Inside a transaction
$db->transact(function (Transaction $tr) {
$tr->add('page_views', pack('P', 10));
$tr->add('page_views', 1);
$count = $tr->getInt('page_views');
});
```

## Reading Integer Values

Use `getInt()` to read values stored by integer atomic operations:

```php
// Database level
$value = $db->getInt('counter'); // ?int — null if key doesn't exist

// Read the counter
// Transaction level
$db->transact(function (Transaction $tr) {
$raw = $tr->get('page_views')->await();
$count = unpack('P', $raw)[1];
echo "Page views: {$count}\n";
$value = $tr->getInt('counter'); // ?int
});

// Snapshot level
$db->readTransact(function ($snap) {
$value = $snap->getInt('counter'); // ?int
});
```

**Note:** `pack('P', $n)` creates an unsigned 64-bit little-endian integer.
`getInt()` decodes the stored bytes as an unsigned 64-bit little-endian integer. It returns `null` if the key does not exist. If the stored value is shorter than 8 bytes, it is zero-padded.

## Bitwise Operations

```php
$db->transact(function (Transaction $tr) {
// Toggle a flag
$tr->bitXor('flags', pack('C', 0b00000001));

// Set specific bits
$tr->bitOr('permissions', pack('C', 0b00001100));

// Clear specific bits
$tr->bitAnd('permissions', pack('C', 0b11110011));
});
// Set specific bits
$db->bitOr('permissions', 0b00001100);

// Clear specific bits
$db->bitAnd('permissions', 0b11110011);

// Toggle a flag
$db->bitXor('flags', 0b00000001);

// Read the result
$flags = $db->getInt('flags');
```

## Max and Min

```php
// Track high score — only updates if new value is larger
$db->max('high_score', 999);

// Track minimum — only updates if new value is smaller
$db->min('response_time_ms', 42);
```

## Compare and Clear

Atomically clears a key only if its current value matches the expected value:

```php
$db->transact(function (Transaction $tr) {
// Clear key only if its value is the zero sentinel
$tr->compareAndClear('temp_lock', pack('P', 0));
});
$db->compareAndClear('temp_lock', 'expected_value');
```

Note: `compareAndClear()` uses `string` parameters since it compares raw byte values.

## Generic atomicOp()

For cases where you need to use a mutation type dynamically:
For cases where you need raw byte-level control or a mutation type dynamically:

```php
use CrazyGoat\FoundationDB\Enum\MutationType;

$tr->atomicOp(MutationType::Add, 'counter', pack('P', 1));
```

The low-level `atomicOp()` always takes `string` parameters.

## Database-Level Convenience Methods

The Database class provides convenience methods that automatically wrap operations in a transaction:
All integer atomic operations are available directly on the Database:

```php
$db->add('counter', pack('P', 1));
$db->bitXor('flag', pack('C', 1));
$db->compareAndClear('temp', pack('P', 0));
// Also: bitAnd, bitOr, max, min
$db->add('counter', 1);
$db->max('high_score', 999);
$db->min('lowest', 1);
$db->bitOr('flags', 0b00000001);
$db->bitAnd('flags', 0b11111110);
$db->bitXor('flags', 0b00000001);
$db->getInt('counter'); // ?int
```

## Versionstamped Operations

Versionstamped operations allow you to embed the commit version into keys or values:
Versionstamped operations embed the commit version into keys or values:

```php
$db->transact(function (Transaction $tr) {
// Key with versionstamp placeholder
$tr->setVersionstampedKey($keyWithPlaceholder, 'value');

// Value with versionstamp placeholder
$tr->setVersionstampedValue('key', $valueWithPlaceholder);

// Get the versionstamp after commit
$vs = $tr->getVersionstamp(); // FutureKey, await after commit
});
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ A `docker-compose.yml` file is available in this repository for easy local devel
Install the library via Composer:

```bash
composer require crazy-goat/foundationdb
composer require crazy-goat/fdb-php
```

## Verifying PHP Extensions
Expand Down
Loading
Loading