________________________________________________________________________

This file is part of Logtalk <https://logtalk.org/>  
SPDX-FileCopyrightText: 1998-2026 Paulo Moura <pmoura@logtalk.org>  
SPDX-License-Identifier: Apache-2.0

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
________________________________________________________________________


`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](../../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
