Skip to content

Releases: konnektr-io/graph

v1.30.0 — Konnektr Graph: semantic graph for Postgres

Choose a tag to compare

@nikoraes nikoraes released this 20 Oct 06:45
a36c637

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-api and …/graph-events.
    The previous pg-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, AgeDigitalTwinsClient and the
    ADT REST surface, AGE labels, FROM DIGITALTWINS and $dtId payloads.

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

Choose a tag to compare

@nikoraes nikoraes released this 12 Oct 15:14
58bec4d

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' produced T.T['$dtId'] (empty result); it now emits T['$dtId']. The implicit alias is also taken from the query (FROM DIGITALTWINS DT → DT) instead of always assuming T.
  • Literal operands were rewritten as properties. AND enabled = true produced AND T.enabled = T.true (42601 invalid indirection syntax); boolean literals and negative numbers are now excluded from the property-prefix pass.
  • ORDER BY is 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 containing ORDER BY now returns a clear 400 naming the reason, instead of the greedy WHERE (.+) regex swallowing the clause and rewriting its keywords into ... T.ORDER T.BY T.order (which surfaced as an opaque 42601). 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 a MATCH/JOIN query (genuinely ambiguous — the query names its own twin variables) used to throw IndexOutOfRangeException (HTTP 500); it now raises InvalidAdtQueryException (HTTP 400) with an actionable message.
  • An ADT query containing ORDER BY failed 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 BY rejection.
  • 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_model tests pass. The new test fails against the previous release's code with Expected: 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

Choose a tag to compare

@nikoraes nikoraes released this 01 Oct 16:53
86fbe4f

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 item
  • RETURN 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:

  • CypherPaginationRewriter extracted from QueryAsync so the SKIP/LIMIT rewrite is unit-tested
    (15 tests, no database required)
  • OrderByTests covering 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)

Choose a tag to compare

@nikoraes nikoraes released this 18 Sep 13:51
4e8fbb1

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

Choose a tag to compare

@nikoraes nikoraes released this 18 Sep 11:05
0ce2423

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 into ModelReferencesNotDeletedException.
  • 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

Choose a tag to compare

@nikoraes nikoraes released this 18 Sep 10:05
94f0a8f

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-search ranks twins by L2 distance with scope predicates (model allow-list, property equality filters, related-twin predicate) applied in the database before ranking and LIMIT, 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/index creates the HNSW index for a twin embedding property (idempotent, optional m / ef_construction tuning); query vectors are validated against the indexed dimension.
    • GET /digitaltwins/memory-search/capability reports pgvector availability; search and index calls fail with a clear 503 capability 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 new scoped-vector-memory-search how-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

Choose a tag to compare

@nikoraes nikoraes released this 14 Sep 07:45
c953de7

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; Schema defaults to public, 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 on time_stamp) when the timescaledb extension is present — plain tables with an informational log otherwise. Events are grouped per CloudEvent type and written with binary COPY (no per-row inserts).

  • snake_case defaults — adt_property_events, adt_twin_lifecycle_events, adt_relationship_lifecycle_events with time_stamp / source_time_stamp as timestamptz, value as jsonb (the PostgreSQL equivalent of Kusto's dynamic), plus service_id, id, model_id, key, relationship_target, relationship_id and action. Table names, Schema, TrackLastUpdatedBy (adds updated_by) and EventTypeMappings are 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

Choose a tag to compare

@nikoraes nikoraes released this 04 Sep 19:51
52f76b9

What's Changed

  • test: nested referenced schemas resolve via DTDL parser (#98) by @nikoraes in #100
  • fix: pull Alpine security updates in Events image final stage by @nikoraes in #103

Full Changelog: v1.27.1...v1.27.2

v1.27.1

Choose a tag to compare

@nikoraes nikoraes released this 07 Aug 08:26
a62eca2

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 notstarted contract.

Full Changelog: v1.27.0...v1.27.1

v1.27.0

Choose a tag to compare

@nikoraes nikoraes released this 06 Aug 13:59
adc7ee8

Features

  • Batch twin/relationship endpoints accept >100 items and chunk internally — CreateOrReplaceDigitalTwinsAsync and CreateOrReplaceRelationshipsAsync no longer hard-reject batches above 100 items. Oversized payloads are split internally into BatchChunkSize-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 MaxBatchSize option (default 2000, env Parameters:MaxBatchSize) caps a single batch request. Payloads above the ceiling fail fast with DigitalTwinBatchLimitExceededException → HTTP 400 problem details stating the limit; larger imports must use an import job. The internal chunk size is configurable via BatchChunkSize (env Parameters:BatchChunkSize).

Closes #93