Skip to content
Merged
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ uv add arcadedb-jvm # JVM, for heavy long-running loads
<!-- REMOVE WHEN: the old arcadedb-embedded* shims are retired and two releases have shipped with the two-package layout allow-old-name -->
> Old names: `arcadedb-jvm` was formerly `arcadedb-embedded`, and `arcadedb` was formerly `arcadedb-embedded-native` (archived on PyPI). A last `arcadedb-embedded` release will be a deprecation shim that installs `arcadedb`, the native build.

- Python 3.11 to 3.14. CI builds and tests all four versions on every platform below for `main`, every night and each release. A pull request runs a smaller selection.
- Python: `arcadedb` (native) is tested on 3.11 to 3.14 and also installs on 3.15 (`requires-python` is `>=3.11` with no upper bound; the CI runs a subset of the tests on 3.15). `arcadedb-jvm` needs 3.11 to 3.14 (`requires-python` is `>=3.11,<3.15`; its dependency JPype has no 3.15 wheel yet). Each release builds and tests both packages on Python 3.11 to 3.14 on all four platforms below. Between releases, `main` and the nightly run test `arcadedb` on 3.12 and 3.14 on every platform (with a short subset on 3.11, 3.13 and 3.15), and `arcadedb-jvm` on 3.11 to 3.14 on Linux x86-64 and on 3.11 and 3.14 on the other three. A pull request runs a smaller selection.
- Linux x86_64 and ARM64, macOS on Apple Silicon, Windows x86_64, for both packages. The Windows wheel of `arcadedb` is the newest.
- Import name: `arcadedb`. Check which package you have with `python -c "import arcadedb; print(arcadedb.__backend__)"`. It prints `native` or `jvm`.
- Optional extras: `arrow`, `pandas`, `vector` for `arcadedb-jvm`; `numpy`, `arrow`, `pandas`, `polars` for `arcadedb`.
- Optional extras: `arrow`, `pandas`, `polars`, `vector` for `arcadedb-jvm`; `numpy`, `arrow`, `pandas`, `polars` for `arcadedb`.
- `arcadedb-jvm` bundles its own Java 25 runtime, so you install no Java. `arcadedb` needs no Java at all.

## Quick start
Expand Down Expand Up @@ -88,7 +88,7 @@ Releases from this repository start with 26.11.1. The 26.10.x wheels were built

## Examples

[`examples/`](https://github.com/ArcadeData/arcadedb-python/tree/main/examples) holds 27 runnable scripts, from a first document store to vector search, time series, graph algorithms and server mode. CI runs them ([`test-python-examples.yml`](https://github.com/ArcadeData/arcadedb-python/blob/main/.github/workflows/test-python-examples.yml)).
[`examples/`](https://github.com/ArcadeData/arcadedb-python/tree/main/examples) holds 29 runnable scripts, from a first document store to vector search, time series, graph algorithms and server mode. CI runs them ([`test-python-examples.yml`](https://github.com/ArcadeData/arcadedb-python/blob/main/.github/workflows/test-python-examples.yml)).

## Build and test from source

Expand Down
6 changes: 3 additions & 3 deletions docs/modules/ROOT/pages/how-to/bulk-loads.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
= Bulk loads and commit size
:description: The default commit size of insert_many, graph_batch and create_vertices on the arcadedb (native) and arcadedb-jvm packages, what it means for a failed load, what to do after an interrupted graph batch, how to change it, how insert_many treats Python's cyclic garbage collector on arcadedb-jvm, which property shapes a graph batch loads in bulk on the JVM build, how to pass time-series columns to append_samples on arcadedb (native), and a known limitation: a graph batch does not reuse the space of deleted edges.
:description: The default commit size of insert_many, graph_batch and create_vertices on the arcadedb (native) and arcadedb-jvm packages, what it means for a failed load, what to do after an interrupted graph batch, how to change it, how insert_many treats Python's cyclic garbage collector on arcadedb-jvm, which property shapes a graph batch loads in bulk on the JVM build, how to pass time-series columns to append_samples (the address path is native only), and a known limitation: a graph batch does not reuse the space of deleted edges.

`insert_many`, `graph_batch` and `GraphBatch.create_vertices` load data in transactions. The commit size is the number of rows in one transaction. It decides how much work the engine holds in memory before it commits, and how many rows stay in the database when a load fails.

Expand Down Expand Up @@ -111,9 +111,9 @@ The table is one laptop study of the `arcadedb-jvm` package: per-row time of thi

|===

== Time-series samples with `append_samples` (`arcadedb`, native)
== Time-series samples with `append_samples`

`db.async_executor().append_samples(type_name, timestamps, *columns)` loads a columnar batch into a TIMESERIES type: the timestamps, then one column for each tag and field in the order of the type's declaration. Call `wait_completion()` before you read the samples back. How to declare the type is in the main ArcadeDB docs: see {arcadedb-docs-url}/concepts/timeseries[Time series].
`db.async_executor().append_samples(type_name, timestamps, *columns)` loads a columnar batch into a TIMESERIES type: the timestamps, then one column for each tag and field in the order of the type's declaration. Call `wait_completion()` before you read the samples back. How to declare the type is in the main ArcadeDB docs: see {arcadedb-docs-url}/concepts/timeseries[Time series]. The call exists on both packages. The rest of this section describes the fast path of the `arcadedb` (native) package. On `arcadedb-jvm`, a NumPy array for the timestamps or for a numeric column crosses as one buffer copy, and any other sequence converts value by value.

On the `arcadedb` (native) package, a NumPy column crosses to the engine by its address, with no JSON text, when it is one of these:

Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/how-to/concurrency.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Every number on this page, except the ratios in <<reads-fewer-calls,Reads with f

// REMOVE WHEN: the old arcadedb-embedded* shims are retired and two releases have shipped with the two-package layout allow-old-name
| Native
| The native wheel (formerly `arcadedb-embedded-native`) 26.10.2.dev1, GraalVM CE 25.4.4.1.1 native image. A preview of alpha quality, measured before the rename
| The native wheel (formerly `arcadedb-embedded-native`) 26.10.2.dev1, GraalVM CE 25.4.4.1.1 native image. A preview, measured before the rename (the package is beta now)
| 3.12.13, GIL on

| Native, Python 3.14t
Expand Down
25 changes: 25 additions & 0 deletions docs/modules/ROOT/pages/how-to/databases-and-queries.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ Notes on the script:
* `db.drop()` deletes all files of the database. It cannot be undone.
* `db.is_transaction_active()` is `True` only inside `with db.transaction():`.

=== Read-only open

`open_database(path, read_only=True)` opens an existing database for reading only; `DatabaseFactory(path).open(read_only=True)` does the same. Queries, counts and record reads work as usual and `db.is_read_only()` is `True`. Every write raises `ArcadeDBError` with "read only" in the message: an `INSERT`, a schema change such as `CREATE DOCUMENT TYPE`, or `save()` on a record. Reads inside `with db.transaction():` work too; on ArcadeDB 26.11.1 and later the commit or rollback of such a transaction returns, while earlier engines raise the same read-only error there.

Use it when a process only reads a finished database, so that a stray write fails instead of changing the data. It is a safety setting, not a speed setting: no measurement here shows reads getting faster.

=== Locking

The open database holds an operating-system lock on `database.lck` in its directory. A second process cannot open the same directory while the first holds it; open it in one process and share the handle between that process's threads.
Expand All @@ -88,6 +94,12 @@ Each Python process starts one JVM, on the first database or server it creates.

== Write and query

=== Which query language

Both languages run through the same two calls, so the choice depends on the question you ask. Use openCypher for graph patterns and traversals: who knows whom, friends of friends, paths of a given length. Use SQL for document and table queries, schema, inserts, filters, aggregates, and pagination. The older SQL graph syntax (`MATCH {type: Person, as: p}` patterns, `out()`, `in()`, `outE()`, `inE()`, and `TRAVERSE`) is supported but not recommended for new code: openCypher is a graph pattern language shared by many databases and close to the ISO GQL standard (ISO/IEC 39075), so queries and skills carry over. This page shows both languages on one database. The languages themselves are documented in the main ArcadeDB manual, see the {arcadedb-docs-url}/reference/sql/sql-introduction[SQL reference] and the {arcadedb-docs-url}/reference/cypher/chapter[openCypher reference]; this guide does not repeat them.

=== Write and read

[source,python,subs="attributes+"]
----
{import-stmt}
Expand Down Expand Up @@ -263,6 +275,19 @@ On the JVM build, `Document.to_dict()`, `Result.to_dict()` and `get_property_nam

The control times `json.dumps` of rows already fetched, which uses no query and no JPype call. Its ratios on the four workloads are 0.996, 0.987, 0.993 and 1.000, and the same build measured twice gave ratios whose intervals contain 1. The figures come from one Linux x86_64 laptop with two cores for the process, Python 3.12, the median of 21 timed passes per run, and builds alternated in both orders. They cover the JVM build and these four workloads only, not wider rows, other value types, `to_list()`, or the native build.

[[result-set-single-pass]]
=== A result set is read once

A result set is a lazy cursor, and you read it once. The engine computes the rows as you read them, not in `db.query(...)`. Both packages behave the same way:

* A second pass over a set that you read to its end returns nothing. The set closes itself at its end, and it reads as empty afterwards.
* `first()` and `one()` close the set after they take their row. `close()` closes it too. A set closed before its end raises `ArcadeDBError` when you read it again, because the rows it had not returned are gone. Leaving a `with` block closes the set too.
* `count()` reads and counts the remaining rows, so the set is empty after it.
* An error in the statement can surface while you read the rows, not in `db.query(...)`. Every way of reading raises it as `ArcadeDBError`.
* If you stop reading early, close the set, or use it in a `with` block, so the engine releases its scan. An unclosed set keeps the threads of the engine's parallel scan parked for up to `arcadedb.parallelScanAbandonedTimeout` (10 minutes by default), and a few of them can stall the next query. Reading to the end also closes it.

Use `to_list()` when you need the rows twice. It reads the whole result into a list of dicts, which you can loop over as often as you like.

=== Record ids in rows

A record id inside a row value (`SELECT @rid AS r`, a `LINK` property, a list of links such as `out('Knows').@rid`) comes back as an RID object, not as text. `str(rid)` is the text (`"#10:5"`), so is an f-string, and `get_rid()` always returns text or `None`. Two RIDs of the same record are equal and hash alike, so they work in sets and as dict keys, and RIDs sort by bucket, then position. `getBucketId()`, `getPosition()`, `toString()` and `isValid()` work on the object. It goes back in as a query parameter (`WHERE @rid = ?`, or a list passed by name) and to `db.lookup_by_rid()`, as itself or as its text. `to_json_list()` is the exception: a record id in its rows is the text (`"#10:5"`), on both builds, and so is a map key that is not a string, because a JSON object has no other kind of key.
Expand Down
8 changes: 8 additions & 0 deletions docs/modules/ROOT/pages/how-to/vectors.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,19 @@ print([row["name"] for row in rows])
['python-ish', 'python']
----

On ArcadeDB 26.11.1 and later, openCypher also creates and searches vector indexes: `CREATE VECTOR INDEX ... FOR (n:Label) ON n.property OPTIONS {...}` creates an `LSM_VECTOR` index, and `SEARCH ... IN (VECTOR INDEX ...)` queries it. The syntax is in the main manual: {arcadedb-docs-url}/reference/cypher/cypher-clauses#cypher-create-vector-index[CREATE VECTOR INDEX] and {arcadedb-docs-url}/reference/cypher/cypher-clauses#cypher-search[SEARCH].

[[find-nearest]]
=== find_nearest

`VectorIndex.find_nearest(query_vector, k=10, ef_search=None, allowed_rids=None, *, include_vector=True)` searches without a statement. It takes a list or a NumPy array and returns a list of `(record, distance)` tuples, nearest first. Use it when you already hold the index object, or when you have a list of record ids to search within.

With `ef_search=None` the search width comes from the index's `efSearch` metadata when it is set, otherwise from the engine: `max(k, 100)` candidates on an index of under 10,000 vectors and `max(2k, 100)` from 10,000 vectors on. Before ArcadeDB 26.11.1 the larger indexes searched `max(2k, 20)`, which gave recall@10 0.929 on SIFT 100k against 0.998 now (https://github.com/ArcadeData/arcadedb/issues/10704[ArcadeData/arcadedb#10704]), so a search on a large index is more accurate and slower than on earlier releases. Pass a smaller `ef_search` to trade recall for speed, or a larger one for more recall.

The second element of each tuple is a distance, and lower is closer, for every similarity function (`COSINE`, `EUCLIDEAN` and `DOT_PRODUCT`) and on both packages. `find_nearest` sorts the hits by it, ascending. It is the `distance` column of `vectorNeighbors`, not the `score` of the Cypher procedure above, where higher is closer.

`VectorIndex.find_nearest_by_key(key, k=10, ef_search=None, allowed_rids=None)` searches with the stored vector of an existing record as the query, so you do not fetch the vector first. `key` is the value of the index's id property for that record. `get_metadata()["id_property"]` names that property, and `create_vector_index(..., id_property=...)` sets it. The other arguments and the result are those of `find_nearest`. The call raises `ArcadeDBError` when no record has that key, or when the record's vector property is null.

On the native package the query vector crosses as one `float32` buffer whatever its form (a list, a `float64` array, or a `float32` array), so you do not need to convert it first. A vector index stores `float32`, so the numbers are rounded the same way as before. The table gives the median `find_nearest` latency with this change over the median without it (95% interval, below 1 is faster), measured on the native wheel `26.11.1.dev0` on one Linux x86_64 laptop with 11 to 12 clean pairs per figure. The wheel built before this change was the baseline.

[cols="3,2,2",options="header"]
Expand Down
12 changes: 5 additions & 7 deletions docs/modules/ROOT/pages/installation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ Pick one of the two packages. Both import as `{import-name}`. If you are not sur
| Package | Install | Pick this if

| `{native-pkg-name}` (native, the default)
| `uv add {native-pkg-name}` +
`uv add {native-pkg-name}`
| `uv add {native-pkg-name}`
| You write scripts, notebooks, tests, CLIs, serverless functions or agents. You want a fast start and a small footprint, and you need no JVM.

| `{jvm-pkg-name}` (JVM)
| `uv add {jvm-pkg-name}` +
`uv add {jvm-pkg-name}`
| `uv add {jvm-pkg-name}`
| Your load is heavy and long-running, so the JIT compiler pays off. Or you need the Java-object APIs, JVM options, `jvm_kwargs` or `start_jvm`.
|===

Expand Down Expand Up @@ -74,7 +72,7 @@ Each native wheel is about 38 MB. These platforms are not built, and the install

* The ArcadeDB engine with all models (graph, document, key/value, vector, time series) and both query languages, SQL and openCypher.
* Vector indexes, and data import for CSV, XML and ArcadeDB JSONL.
* The optional in-process HTTP server and, from 26.11.1, the Studio web UI. They cost nothing at run time until you call `create_server()`.
* The optional in-process HTTP server, which costs nothing at run time until you call `create_server()`. In the JVM package, from 26.11.1, it also serves the Studio web UI. The native package has no Studio.
* In the JVM package: a bundled Java 25 runtime, trimmed with `jlink`. In the native package: the engine as a GraalVM native-image shared library.

Engine modules the bindings do not use are left out to keep the wheel small. These include the gRPC and MongoDB wire protocols, Raft HA, and Gremlin.
Expand Down Expand Up @@ -114,7 +112,7 @@ The engine also writes log lines to the standard error stream. The output on thi
[[jvm-options]]
== JVM options (`{jvm-pkg-name}` only)

The native package has no JVM, so `start_jvm`, `jvm_kwargs` and the flags below do not exist there. With `{jvm-pkg-name}`, configure the bundled JVM inside Python, before the first database is created:
The native package has no JVM, so `start_jvm` and the flags below do not exist there, and `jvm_kwargs` is accepted and ignored: `create_database` and `open_database` log a warning that names the keys and go on. With `{jvm-pkg-name}`, configure the bundled JVM inside Python, before the first database is created:

[source,python,subs="attributes+"]
----
Expand Down Expand Up @@ -263,7 +261,7 @@ What the native package cannot do, or does differently from `{jvm-pkg-name}`. Re

* *Serial garbage collector.* GraalVM Community Edition offers one garbage collector for native images, the serial one (the G1 collector is part of Oracle GraalVM only). A collection stops all threads, and its pause grows with the amount of live data. For a heavy, long-running load, a large heap or many busy threads, use `{jvm-pkg-name}`.
* *Heap limit of 50% by default.* The package sets `-XX:MaximumHeapSizePercent=50`, so the process holds at most half of the memory it can see. See <<native-heap>> to change it.
* *No Java objects, no JVM options.* `get_java_database`, `get_java_document` and the other `get_java_*` calls raise `NotOnNativeYet`. `start_jvm`, `jvm_kwargs` and the flags in <<jvm-options>> do not exist.
* *No Java objects, no JVM options.* `get_java_database`, `get_java_document` and the other `get_java_*` calls raise `NotOnNativeYet`. `start_jvm` and the flags in <<jvm-options>> do not exist, and `jvm_kwargs` is ignored with a logged warning.
* *No console log on Windows by default.* The log goes to the file only. See xref:how-to/logs.adoc[Where the logs go] for the reason and for `ARCADEDB_LOG_CONSOLE`.
* *Engine modules left out.* The gRPC and MongoDB wire protocols, Raft HA, Gremlin and the polyglot languages (JavaScript, Python as a query language) are not in the image. See <<what-is-included>>.
* *Platforms.* Intel Macs, Alpine Linux (musl) and Windows on ARM have no wheel. See <<requirements>>.
Expand Down
21 changes: 21 additions & 0 deletions docs/modules/ROOT/pages/quickstart.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,27 @@ HTTP port: 2480

The server runs only while your Python process runs. See xref:index.adoc#embedded-vs-http[Embedded bindings or the HTTP driver] for when to run the regular ArcadeDB server (the official Docker image `arcadedata/arcadedb`) instead.

The server takes a `config` dictionary. These keys matter first:

* `host` defaults to `localhost`, so only clients on the same machine connect. A client in a Docker container or on another machine needs `config={"host": "0.0.0.0"}` (all IPv4 interfaces, or `"::"` for IPv6). `host` applies to the HTTP server and to every wire protocol that you enable. A per-protocol key such as `postgres_host` overrides it for that protocol.
* `http_port` (default 2480) and `mode` (default `development`) are read by the bindings.
* Any other key becomes the engine setting `arcadedb.<key with _ replaced by .>`. For example, `postgres_port` sets `arcadedb.postgres.port`.

Both packages carry the Postgres, Redis and Bolt wire protocol plugins, and none starts unless you name it in `server_plugins`:

[source,python]
----
config = {
"host": "0.0.0.0",
"server_plugins": "Postgres:com.arcadedb.postgres.PostgresProtocolPlugin",
"postgres_port": 5432, # sets arcadedb.postgres.port
}
----

The class names of the other two are `com.arcadedb.redis.RedisProtocolPlugin` (port key `redis_port`) and `com.arcadedb.bolt.BoltProtocolPlugin` (port key `bolt_port`).

The native server has no Studio web UI.

// REMOVE WHEN: the old arcadedb-embedded* shims are retired and two releases have shipped with the two-package layout allow-old-name
In the `{jvm-pkg-name}` wheels from 26.11.1 the server also serves the Studio web UI, and `get_studio_url()` returns its address. The earlier 26.10.x wheels do not contain Studio: see the note in xref:installation.adoc#what-is-included[What is included].

Expand Down
Loading
Loading