19_pattern_query

command
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 16 Imported by: 0

README

Example 19 — Pattern query

What it demonstrates

The fluent graph/query API at scale: build a labelled property graph, freeze it into an immutable CSR snapshot, then run MATCH-style pattern queries that combine label predicates, property predicates, and a one-hop directed expansion — and read the matched nodes' typed properties back out. It is the same three capabilities the original toy showed, now driven through a seeded, scale-parametrised dataset so per-query latency and live-heap footprint are observable.

Domain / scenario

A software package dependency network. Each node is labelled Package and carries four typed properties — id (string), name (string), ecosystem (string, drawn from go/npm/pypi/cargo/maven), license (string, drawn from MIT/Apache-2.0/…), and downloads (int64). A deterministic subset of packages additionally carries the Deprecated label. Directed Package -[DEPENDS_ON]-> Package edges model a direct dependency, so Out() gives a package's direct dependencies.

The dependency graph is generated by Price's model (preferential attachment on a topological order): nodes are created in index order, every edge runs from a higher to a lower index (i -> j with j < i, so the graph is an acyclic dependency DAG), and each target is chosen with probability proportional to its current in-degree plus one. This reproduces the heavy-tailed in-degree of a real ecosystem — a few foundational libraries depended on by very many packages — while out-degree (a package's own direct-dependency count) stays a small fixed range, as it is in practice. Fixing -seed fixes the shape exactly.

The queries exercise each capability:

  • (:Package) and (:Package:Deprecated) — a label scan and two labels intersected.
  • (:Package) WHERE p.ecosystem = 'go' and (:Package {license: 'MIT'}) — a label predicate combined with a property-equality predicate.
  • (:Package:Deprecated)-->(b) — a one-hop expansion: the distinct direct dependencies of every deprecated package.

The read-back phase then re-reads the downloads and license of the top-by-downloads matched packages (ordered downloads DESC, id ASC, a total order), so the reported values are byte-stable for a fixed seed.

How to run

go run ./examples/19_pattern_query                 # small deterministic default
go run ./examples/19_pattern_query -nodes 1000000 -seed 7  # observable-scale run

Scale and flags

Flag Meaning Default Large
-nodes number of Package nodes 2000 1000000
-out-min minimum direct-dependency count per package 2 2
-out-max maximum direct-dependency count per package 6 6
-deprecated-n 1-in-N packages are also Deprecated 8 8
-seed RNG seed (fixes the data shape) 1 7

The default is small enough to build and query well under the 60 s short-test budget; the large run is where the latency and heap evidence becomes interesting.

Expected output

At the default config the deterministic fact lines are:

config.nodes=2000
config.out_degree=[2,6]
config.deprecated_n=8
config.seed=1
nodes.packages=2000
nodes.deprecated=250
edges.depends_on=7910
q.all_packages=2000
q.deprecated=250
q.ecosystem_go=434
q.license_mit=410
q.deprecated_deps=338
readback.rows=3
readback.0=id:9cdcc59578021851 downloads:9992954 license:MPL-2.0
readback.1=id:7c03af57c90ef764 downloads:9954932 license:MIT
readback.2=id:6ad65415ff4597f3 downloads:9952486 license:GPL-3.0

Interleaved with the facts are volatile telemetry lines, prefixed with # , that vary per run and per machine and are never pinned by the test, for example:

# build.elapsed=3ms
# mem.heap_alloc=1.89 MiB
# csr.freeze=69µs
# q.ecosystem_go.latency=175µs

Evidence it collects

This is a Cypher/query-subject example, so it reports (as # telemetry) per-query wall-clock latency, the CSR freeze time, build throughput (nodes/s, edges/s), and live heap (HeapAlloc). When you scale -nodes up, the interesting observation is the contrast between the index-backed label scans (q.all_packages, q.deprecated), which seed from the LPG NodeIndex bitmaps, and the property-equality predicates (q.ecosystem_go, q.license_mit), which scan and test each matched node — the latter grow markedly slower, demonstrating the difference between a label-seeded plan and a property filter in the v1 fluent API. The matched-row counts are the deterministic facts; the latencies are the evidence.

Key APIs

  • graph/lpg.New — build the mutable labelled property graph.
  • graph/lpg/schema.New / Schema.RegisterLabel / Schema.RegisterProperty — declare the Package/Deprecated labels and the typed property keys.
  • graph/lpg.Graph.SetNodeLabel / SetNodeProperty / AddEdge — populate labels, typed properties, and the directed dependency edges.
  • graph/lpg.Graph.GetNodeProperty — read a matched node's downloads/license back, with PropertyValue.Int64 / PropertyValue.String to unwrap the typed value.
  • graph/csr.BuildFromAdjList — freeze the builder into the immutable CSR snapshot the query engine traverses.
  • graph/query.New / Engine.Match / Pattern.Vertex / Pattern.Out / Pattern.Cardinality / Pattern.Collect — express and run the pattern queries.
  • graph/query.WithLabel / WithProperty — the label and property predicates that seed each pattern.

Further reading

Documentation

Overview

Example 19_pattern_query — the fluent graph/query pattern API at scale.

It builds a labelled property graph that models a software package dependency network, freezes it into an immutable CSR snapshot, and then drives a battery of MATCH-style pattern queries through the fluent github.com/FlavioCFOliveira/GoGraph/graph/query API — the same three capabilities the original toy demonstrated, now exercised at a scale where the engine's behaviour is observable:

  • label predicates, intersected: (:Package) and (:Package:Deprecated);
  • a label predicate combined with a property-equality predicate: (:Package) WHERE p.ecosystem = 'go' and (:Package {license:'MIT'});
  • a one-hop directed expansion: (:Deprecated)-->(b), the direct dependencies of every deprecated package.

For a handful of matched packages the example reads the matched properties back out (downloads, ecosystem, license) with a deterministic order so the read-back values are reproducible.

Model

(:Package {id, name, ecosystem, license, downloads})
(:Package:Deprecated {...})                          // a deterministic subset
(:Package)-[DEPENDS_ON]->(:Package)                  // a direct dependency

id is a 16-char hex string; downloads is an int64; ecosystem and license are drawn from small fixed categorical sets. A package may additionally carry the Deprecated label. The fluent query API matches on node labels and node properties and expands one hop along the directed DEPENDS_ON edges, so the model carries exactly the typed fields and the directed topology those three query shapes exercise.

Topology

The dependency graph is generated by Price's model — preferential attachment on a topological order. Nodes are created in index order; every edge runs from a higher index to a lower one (i -> j with j < i), so the graph is a DAG by construction (no dependency cycles). Each new package picks outDegree dependencies, choosing each target with probability proportional to its current in-degree plus one, which reproduces the heavy-tailed in-degree of a real ecosystem: a few foundational libraries are depended on by very many packages while most packages are depended on by few. Out-degree (a package's own direct dependency count) is a small fixed range, faithful to real ecosystems where the heavy tail lives on in-degree, not out-degree. The selection is driven entirely by a single seeded RNG, so fixing -seed fixes the shape exactly.

Scale

Run with no flags, the example builds a small, deterministic default (a few thousand packages) whose fact lines a regression test pins. Every dimension is a flag, so the same binary scales up to a size where per-query latency and live-heap footprint become interesting:

go run ./examples/19_pattern_query -nodes 1000000 -seed 7

The deterministic data shape and the fact lines are reproducible for a fixed -seed; only the telemetry (lines prefixed with "# ") varies between runs and machines.

Why in-memory

The example targets the read-query API and its latency / live-heap footprint, so it builds the graph in memory through the property-graph API and queries it through the in-memory CSR snapshot. It does not exercise the WAL/recovery stack; the persistence path is demonstrated by examples 04, 17, 24 and 25.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL