# `memcached`

Portable Memcached client implementing the text (ASCII) protocol. This library uses the `sockets` library and supports all backend Prolog systems supported by that library: ECLiPSe, GNU Prolog, SICStus Prolog, SWI-Prolog, Trealla Prolog, and XVM.

## API documentation

Open the ../../apis/library_index.html#memcached link in a web browser.

## Loading

To load this library, load the `loader.lgt` file:

    | ?- logtalk_load(memcached(loader)).

## Testing

To test this library predicates, load the `tester.lgt` file:

    | ?- logtalk_load(memcached(tester)).

Note: Tests require a running Memcached server on `localhost` port `11211` (the default). To install and start Memcached:

On macOS:

    $ brew install memcached
    $ memcached -d

On Linux (Debian/Ubuntu):

    $ sudo apt-get install memcached
    $ sudo systemctl start memcached

On Linux (RHEL/Fedora):

    $ sudo dnf install memcached
    $ sudo systemctl start memcached

## Protocol Version

This library implements the Memcached text (ASCII) protocol as documented in the official specification:

https://github.com/memcached/memcached/blob/master/doc/protocol.txt

The text protocol is the standard protocol supported by all Memcached clients and servers. All commands use simple text lines terminated by `\r\n`.

## Features

- Full storage command support: `set`, `add`, `replace`, `append`, `prepend`, `cas`

- Retrieval commands: `get`, `gets` (with CAS token), multi-get, `gat`, `gats`

- Key deletion with `delete`

- Atomic increment/decrement with `incr`/`decr`

- Item expiration management with `touch`, `gat`, `gats`

- Server management: `flush_all`, `version`, `stats`

- Graceful disconnect with `quit`

- Proper handling of all protocol response codes

## Usage

### Connecting to a Memcached server

Basic connection to localhost on the default port (11211):

    ?- memcached::connect(localhost, Connection).

Connection to a specific host and port:

    ?- memcached::connect('cache.example.com', 11211, Connection).

### Storing data

Store a value unconditionally:

    ?- memcached::set(Connection, my_key, 'Hello, World!').

Store with flags and expiration time (in seconds):

    ?- memcached::set(Connection, my_key, 'Hello!', 0, 3600).

Store only if the key does not exist:

    ?- memcached::add(Connection, new_key, 'New value', 0, 60).

Store only if the key already exists:

    ?- memcached::replace(Connection, existing_key, 'Updated', 0, 60).

Append data to an existing value:

    ?- memcached::append(Connection, my_key, ' World!').

Prepend data to an existing value:

    ?- memcached::prepend(Connection, my_key, 'Hello ').

### Retrieving data

Get a value by key:

    ?- memcached::get(Connection, my_key, Value).

Get a value with flags:

    ?- memcached::get(Connection, my_key, Value, Flags).

Get a value with CAS token (for optimistic locking):

    ?- memcached::gets(Connection, my_key, Value, CasUnique).

Get multiple keys at once:

    ?- memcached::mget(Connection, [key1, key2, key3], Items).

### Check-and-Set (CAS)

CAS provides optimistic locking for cache updates:

    ?- memcached::gets(Connection, my_key, Value, CasUnique),
       % ... process value ...
       memcached::cas(Connection, my_key, 'New value', 0, 60, CasUnique).

If another client modifies the value between `gets` and `cas`, the `cas` command will throw `error(memcached_error(exists), _)`.

### Deleting data

Delete an item by key:

    ?- memcached::delete(Connection, my_key).

Fails if the key does not exist.

### Increment/Decrement

Atomically increment a numeric value:

    ?- memcached::set(Connection, counter, '100'),
       memcached::incr(Connection, counter, 5, NewValue).
    % NewValue = 105

Atomically decrement a numeric value:

    ?- memcached::decr(Connection, counter, 3, NewValue).
    % NewValue = 102

Note: The item must already exist and contain a decimal representation of a 64-bit unsigned integer. Decrementing below 0 yields 0.

### Touch and Get-and-Touch

Update expiration time without fetching:

    ?- memcached::touch(Connection, my_key, 3600).

Fetch and update expiration time in one operation:

    ?- memcached::gat(Connection, my_key, 3600, Value).

Fetch with CAS and update expiration:

    ?- memcached::gats(Connection, my_key, 3600, Value, CasUnique).

### Server management

Invalidate all items:

    ?- memcached::flush_all(Connection).

Invalidate all items after a delay (in seconds):

    ?- memcached::flush_all(Connection, 30).

Get server version:

    ?- memcached::version(Connection, Version).

Get server statistics:

    ?- memcached::stats(Connection, Stats).

Get specific statistics:

    ?- memcached::stats(Connection, items, Stats).

### Disconnecting

    ?- memcached::disconnect(Connection).

## API Summary

### Connection management

- `connect(+Host, +Port, -Connection)` - Connect to server

- `connect(+Host, -Connection)` - Connect to server on default port

- `disconnect(+Connection)` - Disconnect from server

### Storage commands

- `set(+Connection, +Key, +Value, +Flags, +ExpTime)` - Store unconditionally

- `set(+Connection, +Key, +Value)` - Store with default flags and no expiration

- `add(+Connection, +Key, +Value, +Flags, +ExpTime)` - Store only if new

- `replace(+Connection, +Key, +Value, +Flags, +ExpTime)` - Store only if exists

- `append(+Connection, +Key, +Value)` - Append to existing value

- `prepend(+Connection, +Key, +Value)` - Prepend to existing value

- `cas(+Connection, +Key, +Value, +Flags, +ExpTime, +CasUnique)` - Check-and-set

### Retrieval commands

- `get(+Connection, +Key, -Value)` - Get value

- `get(+Connection, +Key, -Value, -Flags)` - Get value and flags

- `gets(+Connection, +Key, -Value, -CasUnique)` - Get with CAS token

- `gets(+Connection, +Key, -Value, -Flags, -CasUnique)` - Get with flags and CAS

- `mget(+Connection, +Keys, -Items)` - Multi-get

### Deletion

- `delete(+Connection, +Key)` - Delete item

### Increment/Decrement

- `incr(+Connection, +Key, +Amount, -NewValue)` - Increment

- `decr(+Connection, +Key, +Amount, -NewValue)` - Decrement

### Touch

- `touch(+Connection, +Key, +ExpTime)` - Update expiration

- `gat(+Connection, +Key, +ExpTime, -Value)` - Get and touch

- `gats(+Connection, +Key, +ExpTime, -Value, -CasUnique)` - Get-and-touch with CAS

### Server commands

- `flush_all(+Connection)` - Invalidate all items

- `flush_all(+Connection, +Delay)` - Invalidate all items after delay

- `version(+Connection, -Version)` - Get server version

- `stats(+Connection, -Stats)` - Get general statistics

- `stats(+Connection, +Argument, -Stats)` - Get specific statistics

## Error Handling

Most predicates throw `error(memcached_error(Reason), Context)` on failure. Common error reasons:

- `connection_failed` - Could not establish TCP connection

- `not_stored` - Storage condition not met (e.g. `add` on existing key)

- `exists` - CAS conflict (item was modified by another client)

- `not_found` - CAS on non-existent key

- `server_error(Message)` - Server returned an error

Retrieval predicates (`get`, `gets`, `gat`, `gats`) fail (rather than throw) when the requested key is not found. The `delete`, `incr`, `decr`, and `touch` predicates also fail when the key is not found.

## Key Format

- Keys are atoms up to 250 characters

- Keys must not contain control characters or whitespace

- Keys are case-sensitive

## Expiration Times

- `0` means the item never expires (may still be evicted by LRU)

- Values up to 2592000 (30 days) are treated as relative seconds

- Values over 2592000 are treated as absolute Unix timestamps

- Negative values cause immediate expiration