Repository navigation
Releases: konnektr-io/graph
Release list
v1.30.0 — Konnektr Graph: semantic graph for Postgres
Repositioning release. The managed/hosted Konnektr service is retired; Konnektr Graph is self-hosted.
Changed
- Repository renamed
pg-age-digitaltwins→graph; container images are now
ghcr.io/konnektr-io/graph/graph-apiand…/graph-events.
The previouspg-age-digitaltwins/agedigitaltwins-*paths keep receiving this
release for one transition window. /entities/*is now an alias of/digitaltwins/*: same handlers, byte-identical
payloads, rewritten before routing so the two surfaces cannot drift.- Documentation rewritten for self-hosted only: real OCI chart install, corrected
values keys, and every reference to the retired hosted environment removed.
Unchanged (compatibility is a hard requirement)
- NuGet package
Konnektr.AgeDigitalTwins, namespaces,AgeDigitalTwinsClientand the
ADT REST surface, AGE labels,FROM DIGITALTWINSand$dtIdpayloads.
Deployment note: Helm chart 0.14.x still points at the old image path and keeps
working. Charts 0.15+ will use the new path; do not bump the chart until the new
images are published (this release).
v1.29.4: ADT query fixes — two-argument IS_OF_MODEL, alias-qualified properties, ORDER BY rejected
ADT-compat query surface fixes (issue #118)
Two-argument IS_OF_MODEL(twin, 'model') silently returned an empty result set
IS_OF_MODEL(twin, 'model') — valid ADT SQL, and the form used in Microsoft's own examples — was translated with the twin collection argument as the model id:
IN : SELECT * FROM DIGITALTWINS T WHERE IS_OF_MODEL(T, 'dtmi:com:example:Thing;1')
OUT: MATCH (T:Twin) WHERE digitaltwins.is_of_model(T,T.T) RETURN * -- matches nothing
That predicate matches no twin, so the query answered HTTP 200 with an empty value list — indistinguishable from "there are no twins of this model". One argument parser now handles every documented form: the model id is taken from the quoted literal, the exact keyword is recognised wherever it appears, and an explicit twin collection is taken from the query rather than the implicit alias.
Also fixed in the same translator
- Alias-qualified properties were prefixed a second time.
SELECT * FROM DIGITALTWINS T WHERE T.$dtId = 'thing-1'producedT.T['$dtId'](empty result); it now emitsT['$dtId']. The implicit alias is also taken from the query (FROM DIGITALTWINS DT→DT) instead of always assumingT. - Literal operands were rewritten as properties.
AND enabled = trueproducedAND T.enabled = T.true(42601 invalid indirection syntax); boolean literals and negative numbers are now excluded from the property-prefix pass. ORDER BYis rejected instead of mangled. It is not part of the ADT query language — a real ADT instance fails its own SQL parser on it — so an ADT query containingORDER BYnow returns a clear400naming the reason, instead of the greedyWHERE (.+)regex swallowing the clause and rewriting its keywords into... T.ORDER T.BY T.order(which surfaced as an opaque42601). Ordering remains available in Cypher queries, which support it.
Compatibility
- Every ADT query shape that worked before produces the same Cypher; this release only changes shapes that previously failed.
- A single-argument
IS_OF_MODEL('model')inside aMATCH/JOINquery (genuinely ambiguous — the query names its own twin variables) used to throwIndexOutOfRangeException(HTTP 500); it now raisesInvalidAdtQueryException(HTTP 400) with an actionable message. - An ADT query containing
ORDER BYfailed before and fails now — only the message is now specific.
Verification
- Conversion unit tests: 84/84, including 19 new cases (one per reported shape) and 7 asserting the
ORDER BYrejection. - Integration tests against a throwaway AGE 1.6.0 / PG16 instance (the CI matrix image): the new end-to-end result-set test and all 11
is_of_modeltests pass. The new test fails against the previous release's code withExpected: 2, Actual: 0— the silent empty set itself. - CI on the merged commit: full PG16/17/18 × apache/cnpg matrix green.
Reported with a precise reproduction table in #118; fixed in #119.
v1.29.3: Konnektr.Npgsql.Age 2.1.0 — fixes #115
Bumps Konnektr.Npgsql.Age to 2.1.0, which fixes #115.
On 2.0.0 the driver emitted an empty column-definition list for certain RETURN items:
SELECT * FROM ag_catalog.cypher('graph', $$ ... $$) as ( agtype)
-- ^ empty → 42601 syntax error at or near ")"That made it impossible to read a single scalar property off a twin, and broke the ADT query
path too — SELECT T.name FROM DIGITALTWINS translates to a bare scalar RETURN T.name.
What works now
RETURN t.returnPeriod AS period— bare scalar itemRETURN t { .returnPeriod } AS twin— map projection without.*- multiple scalar items in one RETURN
All covered by ReturnItemColumnListTests, four of which are unconditional so the fix is proven
on every supported AGE version including 1.6.0. A fifth sorts on a RETURN alias and is
version-gated, since that inherits the separate AGE 1.6.0 limitation (apache/age#2269).
Also in this release
Test coverage from #113 and #116 — no runtime behaviour change:
CypherPaginationRewriterextracted fromQueryAsyncso the SKIP/LIMIT rewrite is unit-tested
(15 tests, no database required)OrderByTestscovering ORDER BY over twin properties, with the AGE version boundary pinned
from both sides
Upgrade note
No configuration or API changes. The dependency bump is transparent apart from the two query
shapes above, which move from 42601 to working.
v1.29.2: is_of_model per-twin fallback removed (#101)
What's changed
- perf: drop per-twin inheritance fallback in is_of_model (#110) — resolves #101
- The legacy traversal ran a nested Cypher query for every candidate twin when the requested model had no precomputed property, making an scan cost O(twins × models) and appear to hang on graphs with no matching twins.
- Now resolves inheritance solely from the requested model's precomputed array and returns when it is absent. Pre-fix: 8 952 ms over 2 000 twins (zero matches). Post-fix: 59 ms.
- Behaviour change (deliberate): models without a property (graphs written by an older library version, or externally) no longer resolve inheritance — twins match on exact model id only. Re-submitting the affected models' DTDL via restores the property.
- Regression test added (2000 twins, 2 s budget + 60 s hang guard); CI green across PG16/17/18 × apache/cnpg.
Compatibility
- PG 16 / 17 / 18, apache/age and ghcr.io/konnektr-io/age CNPG flavors.
v1.29.1: PG18 / AGE 1.8.0 compatibility
Fixes
- DeleteModel on AGE 1.8.0 — the vertex-has-edges guard moved to
process_edges_by_index(same XX000/message); the client translates both old and new surfaces intoModelReferencesNotDeletedException. - Events replication on AGE 1.8.0 — UNWIND+MERGE+SET now emits all INSERTs before all UPDATEs per transaction; the decoder merges per-transaction events keyed by (table, id), and the consumer isolates single malformed events instead of dropping whole batches.
Fully backwards compatible (PG16/AGE 1.6.0 green). Closes #108.
v1.29.0: scoped vector memory search
Features
- Scoped vector memory search — a dedicated, generic pgvector-backed search surface for callers that must only ever see a subset of records, separate from the existing generic hybrid search (which is unchanged):
POST /digitaltwins/memory-searchranks twins by L2 distance with scope predicates (model allow-list, property equality filters, related-twin predicate) applied in the database before ranking andLIMIT, so out-of-scope records can never surface. Responses carry stable ID, model ID, distance, a bounded JSON excerpt (embedding excluded) and last-updated time.POST /digitaltwins/memory-search/indexcreates the HNSW index for a twin embedding property (idempotent, optionalm/ef_constructiontuning); query vectors are validated against the indexed dimension.GET /digitaltwins/memory-search/capabilityreports pgvector availability; search and index calls fail with a clear503capability error when the extension is absent instead of degrading silently.- Scope attributes are caller-managed twin properties passed back as
propertyFilters. Ownership/authorization and cross-graph fan-out deliberately stay outside the API and are documented as such in the newscoped-vector-memory-searchhow-to guide.
- Nothing in the API is tied to a particular domain model: embedding property, scope keys and model filters are all caller-supplied.
Closes #106.
v1.28.0
Features
-
PostgreSQL / TimescaleDB data history sink — the events service can now archive data history to PostgreSQL or TimescaleDB, next to the existing Kusto sink. Configure it under
EventSinks:Postgres: the connection string falls back to the graph connection string, so history can be pushed to the same database as the graph or to a separate one;Schemadefaults topublic, and schema/table identifiers are validated before they ever reach a statement. Tables, schema and indexes are created on first use, and the tables become TimescaleDB hypertables (partitioned ontime_stamp) when thetimescaledbextension is present — plain tables with an informational log otherwise. Events are grouped per CloudEvent type and written with binaryCOPY(no per-row inserts). -
snake_case defaults —
adt_property_events,adt_twin_lifecycle_events,adt_relationship_lifecycle_eventswithtime_stamp/source_time_stampastimestamptz,valueasjsonb(the PostgreSQL equivalent of Kusto'sdynamic), plusservice_id,id,model_id,key,relationship_target,relationship_idandaction. Table names,Schema,TrackLastUpdatedBy(addsupdated_by) andEventTypeMappingsare all configurable. -
Documentation — new Data History with PostgreSQL / TimescaleDB guide: schema and column reference, example temporal SQL queries, TimescaleDB compression/retention/continuous aggregates, performance tuning, and troubleshooting. Also linked from the event routing and configuration reference pages.
Closes #104
Full Changelog: v1.27.2...v1.28.0
v1.27.2
v1.27.1
What's Changed
- fix: initial status for import jobs is notstarted, not running (#97, closes #96) — ImportGraphAsync no longer eagerly flips a background job to Running; initial status now matches ADT's
notstartedcontract.
Full Changelog: v1.27.0...v1.27.1
v1.27.0
Features
-
Batch twin/relationship endpoints accept >100 items and chunk internally —
CreateOrReplaceDigitalTwinsAsyncandCreateOrReplaceRelationshipsAsyncno longer hard-reject batches above 100 items. Oversized payloads are split internally intoBatchChunkSize-sized chunks (default 100) over a single connection, with per-item results aggregated into one batch result in input order — mirroring the chunking import jobs already used. Twins and relationships now behave identically. -
Configurable hard ceiling with a structured 400 — new
MaxBatchSizeoption (default 2000, envParameters:MaxBatchSize) caps a single batch request. Payloads above the ceiling fail fast withDigitalTwinBatchLimitExceededException→ HTTP 400 problem details stating the limit; larger imports must use an import job. The internal chunk size is configurable viaBatchChunkSize(envParameters:BatchChunkSize).
Closes #93