tina4stack / ultipa
Thin gRPC client for the Ultipa graph database (ultipa-gqldb) — the Ultipa driver behind Tina4's graph layer. Zero-runtime-dep on protobuf.
This package is auto-updated.
Last update: 2026-08-22 08:46:00 UTC
README
tina4-ultipa
The Ultipa graph driver for PHP
A full-fidelity gRPC client for the Ultipa graph database — every GQL type decoded, no Tina4 lock-in.
Quick Start • Type Support • Ultipa Docs • Protocol • tina4.com
A thin, standalone gRPC driver for the Ultipa graph
database (ultipa-gqldb), written in PHP. It is the Ultipa driver behind
Tina4's graph data layer — but has no Tina4 dependency and
is perfectly usable on its own. Tina4's core stays zero-dependency; this driver is
an optional package loaded only for ultipa:// connections, speaking gRPC directly
(the grpc PECL extension plus grpc/grpc for framing, and this package's own
hand-rolled proto3 codec for the payloads — no google/protobuf runtime dependency).
Ultipa is a high-performance graph database with a GQL (ISO/IEC 39075) query surface. Learn more at ultipa.com · docs at ultipa.com/docs.
Why this driver
- Complete value decoding. Every gqldb
PropertyTypeis decoded to a natural PHP value — including the composites and temporals most thin clients skip (see Type support). - Real gRPC, no protobuf runtime. Uses the
grpcPECL extension for HTTP/2 + gRPC framing, and this package's own hand-rolled proto3 codec (src/Pb/) for the payloads — socomposer requirenever drags ingoogle/protobufand its version churn. - Fails loud. A bad statement throws; it never returns a falsy value you might
miss. An unreachable host throws within your
connectTimeout. - Cross-language parity. The same driver exists for Python, Node.js, Ruby and PHP, decoding byte-for-byte identically.
Requirements
- PHP >= 8.1
- The
grpcPECL extension (ext-grpc) at runtime- Linux/macOS:
pecl install grpc - Windows: install a matching
php_grpc.dlland addextension=grpctophp.ini
- Linux/macOS:
- Composer package
grpc/grpc(pulled in automatically); nogoogle/protobuf
Install
composer require tina4stack/ultipa
Quick start
require 'vendor/autoload.php'; use Tina4\Ultipa\Client; // from a URL... $db = Client::fromUrl("ultipa://admin:password@localhost:60061/mygraph")->connect(); // ...or explicitly $db = new Client( host: "localhost", port: 60061, username: "admin", password: "password", graph: "mygraph" ); // read — GQL text + optional named params foreach ($db->query( "MATCH (n:Person) WHERE n.age > \$min RETURN n.name AS name", params: ["min" => 21] ) as $row) { echo $row["name"], "\n"; } // write — dml stats on success, throws on a bad statement $r = $db->execute("INSERT (:Person {name: 'Alice', age: 30})"); echo $r->dmlStats["inserted_nodes"], "\n"; // 1 $db->close();
query() runs a read, execute() a write — both take GQL and optional params
and return a Result:
| Member | Meaning |
|---|---|
->columns |
column names |
->rows |
decoded rows (lists of PHP values) |
->dicts() |
rows as associative arrays keyed by column |
->scalar() |
first cell of the first row |
->rowsAffected, ->dmlStats, ->warnings |
write metadata |
Result is iterable and countable, so foreach ($result as $row) yields the
associative rows directly. Errors: a bad statement throws
Tina4\Ultipa\UltipaError; an unreachable host throws Tina4\Ultipa\ConnectError
within connectTimeout, naming host, port and elapsed seconds.
Type support
Every gqldb PropertyType decodes to a natural PHP value:
| Category | Types | Decodes to |
|---|---|---|
| Numeric | int32/uint32/int64/uint64, float, double | int / float |
| Text | string, text | string |
| Boolean / null | bool, null, unset | bool / null |
| Binary | blob | binary string (e.g. an image, round-trips intact) |
| Decimal | decimal | string (precision-preserving) |
| Temporal | date, local/zoned time, local/zoned datetime, timestamp | ISO-8601 string |
| Interval | year-to-month, day-to-second | ['months' => …] / ['seconds' => …, 'nanoseconds' => …] |
| Spatial | point, point3d | ['x', 'y'[, 'z'], 'srid'] assoc array |
| Vector | vector | array of float |
| Graph | node, edge, path | assoc array (_kind node/edge/path; nodes/edges carry id, labels/type, properties, internal uuid) |
| Tabular | list, set, map, record, table | array (list) / assoc array |
| Error | error | ['code' => …, 'message' => …] |
Node/edge arrays include the 8-byte internal-id (uuid) trailer emitted by
gqldb 6.1.147+. Binary parameters go the other way with Codec::blob($bytes).
Protocol
ultipa-gqldb speaks gRPC (default port 60061, protobuf package gqldb). The
driver authenticates with SessionService.Login, passes the returned session_id
on every call as the session-id metadata header (as an unsigned decimal), then
runs QueryService.Gql. Each value is a TypedValue{type, bytes}; all multi-byte
integers are little-endian and strings are UTF-8. The full byte-level encoding is
documented in PROTOCOL.md — grounded in Ultipa's official gqldb
SDK and proto, and verified live against gqldb-grpc CE.
Because PHP gRPC needs the grpc extension (ext-grpc), this is the heaviest of
the Tina4 language drivers to install — but only when you actually use Ultipa.
Sample data pack
Get a real graph into your community-edition instance in seconds — 10 people (with photos), 3 companies, 3 projects, 5 skills and 62 relationships:
php examples/seed.php ultipa://admin:PASSWORD@HOST:60061
# graph defaults to "default"; override with TINA4_ULTIPA_GRAPH
Then explore it:
MATCH (n:Person)-[e]->(m) RETURN n, e, m LIMIT 100 RETURN db.overview()
The pack lives in examples/ — sample_data/people.json describes the
graph and sample_data/faces/*.jpg are 64px portraits of AI-generated, non-existent
people (thispersondoesnotexist.com) stored as
BLOB properties (via Codec::blob()), so it also demonstrates binary
round-tripping through gqldb. Re-running is safe — it clears the demo labels
(Person/Company/Project/Skill) first.
Testing
Real, no-mock tests run against a live server. They use the existing default
graph with a dedicated Tina4TestPhp label they insert and delete, so they never
touch real data:
composer install TINA4_TEST_ULTIPA_URL=ultipa://admin:password@host:60061 php tests/test_driver.php
The suite covers scalar round-trip, node write/read with a bound parameter, a bad
statement throwing UltipaError, and a connect timeout throwing ConnectError.
Links
- Ultipa — website ultipa.com · docs ultipa.com/docs · GQL query language reference in the docs
- Tina4 — tina4.com · the framework this driver powers
- This driver — github.com/tina4stack/ultipa-php · sibling drivers for Python, Node.js, Ruby
License
MIT. Note: the Ultipa community-edition server is licensed by Ultipa for personal / non-commercial use — review Ultipa's terms before you deploy it. This driver is an independent client and may not be officially supported by Ultipa.
Sponsored by Code Infinity · part of the Tina4 stack.