@thi.ng/oquery
Version:
Datalog-inspired, optimized pattern/predicate query engine for JS objects & arrays of objects
335 lines (257 loc) • 10.7 kB
Markdown
<!-- This file is generated - DO NOT EDIT! -->
<!-- Please see: https://github.com/thi-ng/umbrella/blob/develop/CONTRIBUTING.md#changes-to-readme-files -->
# 
[](https://www.npmjs.com/package/@thi.ng/oquery)

[](https://mastodon.thi.ng/@toxi)
> [!NOTE]
> This is one of 210 standalone projects, maintained as part
> of the [.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo
> and anti-framework.
>
> 🚀 Please help me to work full-time on these projects by [sponsoring me on
> GitHub](https://github.com/sponsors/postspectacular). Thank you! ❤️
- [About](#about)
- [Status](#status)
- [Planned features](#planned-features)
- [Related packages](#related-packages)
- [Installation](#installation)
- [Dependencies](#dependencies)
- [API](#api)
- [Intersection vs. union queries](#intersection-vs-union-queries)
- [Query patterns](#query-patterns)
- [Querying objects](#querying-objects)
- [Authors](#authors)
- [License](#license)
## About
Datalog-inspired, optimized pattern/predicate query engine for JS objects & arrays of objects.
**IMPORTANT: This README is currently somewhat out-of-date and does not yet
cover new important features introduced with version 2.2.0 onwards, please
consult API docs (and/or source code) to view newly added functions and their
usage...**
This package provides a higher-order function `defQuery()`, which takes a
number of options to configure query behavior and returns an actual query
function. This returned function can then be used for pattern matching of
objects and arrays of objects.
## Status
**STABLE** - used in production
[Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Boquery%5D+in%3Atitle)
### Planned features
Some of the below features are already partially addressed by other
thi.ng/umbrella packages, but would benefit from a more unified approach.
- [ ] optional queries (OR queries)
- [ ] result projection
- [ ] result aggregation/grouping
## Related packages
- [.ng/associative](https://github.com/thi-ng/umbrella/tree/develop/packages/associative) - ES Map/Set-compatible implementations with customizable equality semantics & supporting operations
- [.ng/csv](https://github.com/thi-ng/umbrella/tree/develop/packages/csv) - Customizable, transducer-based CSV parser/object mapper and transformer
- [.ng/egf](https://github.com/thi-ng/umbrella/tree/develop/packages/egf) - Extensible Graph Format
- [.ng/rstream-query](https://github.com/thi-ng/umbrella/tree/develop/packages/rstream-query) - [@thi.ng/rstream](https://github.com/thi-ng/umbrella/tree/develop/packages/rstream) based triple store & reactive query engine
## Installation
```bash
yarn add .ng/oquery
```
ESM import:
```ts
import * as oq from "@thi.ng/oquery";
```
Browser ESM import:
```html
<script type="module" src="https://esm.run/@thi.ng/oquery"></script>
```
[JSDelivr documentation](https://www.jsdelivr.com/)
For Node.js REPL:
```js
const oq = await import("@thi.ng/oquery");
```
Package sizes (brotli'd, pre-treeshake): ESM: 1.69 KB
## Dependencies
- [.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
- [.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
- [.ng/compare](https://github.com/thi-ng/umbrella/tree/develop/packages/compare)
- [.ng/defmulti](https://github.com/thi-ng/umbrella/tree/develop/packages/defmulti)
- [.ng/equiv](https://github.com/thi-ng/umbrella/tree/develop/packages/equiv)
Note: .ng/api is in _most_ cases a type-only import (not used at runtime)
## API
[Generated API docs](https://docs.thi.ng/umbrella/oquery/)
Currently, there are 27 unique & optimized query implementations, each based on
RDF-style
[Subject-Predicate-Object](https://www.w3.org/TR/rdf11-primer/#section-triple)
triple patterns (only without any similar restrictions on query terms data
types), with each of the three terms one of:
- **`null`** - wildcard, any non-null (or `undefined`) value in that position
will be selected
- **Predicate function** - called with all possible terms in that position
- **Literal value** - for subjects and predicates, this can only be a string
or number. For "object" position any value type is allowed
- **Array or `Set`** - multiple choices (literals) for given query term
### Intersection vs. union queries
By default, arrays or sets in O(bject) position are matched in an
elementwise manner using OR-semantics, i.e. a match succeeds with the first
matched element. Since v0.3.0 intersection queries are supported too, i.e. all
elements of the given array/set must match for the query to succeed (see
examples/differences further below).
The behavior can be chosen via the [`intersect` query
option](https://docs.thi.ng/umbrella/oquery/interfaces/QueryOpts.html#intersect).
### Query patterns
Object queries expect an object of the following structure:
```ts
{ subj1: { pred1: "obj1", pred2: 2, pred3: ["a", "b"] }, ... }
```
A concrete example:
```ts id:db
const DB = {
alice: {
age: 33,
knows: ["bob", "charlie", "dori"],
type: "person",
},
bob: {
age: 32,
knows: ["alice"],
type: "person",
spouse: "alice",
},
charlie: {
parent: "alice",
knows: ["alice", "bob", "dori"],
},
dori: {
knows: ["bob"]
}
};
```
To find all answers for the question: Who knows Bob?
```ts tangle:export/readme.ts
import { defQuery } from "@thi.ng/oquery";
// include above DB definition
<<db>>
// create query w/ custom options
// (options explained further below...)
const q = defQuery({ partial: true });
console.log(
q(DB, null, "knows", "bob")
);
// {
// alice: { knows: [ 'bob' ] },
// charlie: { knows: [ 'bob' ] },
// dori: { knows: [ 'bob' ] }
// }
```
For each of the 3 query terms, the following IDs are used:
- `*` = null (wildcard)
- `l` = literal value (or array/set of literals)
- `f` = predicate function
| SPO pattern | Matches... |
|-------------|---------------------------------------------------------------------------|
| `* * *` | everything |
| `* * l` | all objects with ANY property matching given literal |
| `* * f` | all objects with ANY property matching given predicate |
| `* l *` | objects with a property matching given literal |
| `* l l` | objects with a property AND its value matching given literals |
| `* l f` | objects with given property AND its value matching predicate |
| `* f *` | objects with properties matching given predicate |
| `* f l` | objects with properties matching given predicate AND their literal values |
| `* f f` | objects with properties matching given predicate AND their literal values |
Further variations:
(1) If the "subject" term is a literal (or array), then only object(s) for given
key value(s) are matched, using the same logic for the other two terms as in the
table above.
```ts tangle:export/readme.ts
// Who does Alice know?
console.log(
q(DB, "alice", "knows", null)
);
// { alice: { knows: [ 'bob', 'charlie', 'dori' ] } }
```
(2) If the subject is a predicate, then any top-level keys matching the given
predicate will be matched (again using same rules as above for the other query
terms).
```ts tangle:export/readme.ts
// Anyone with initial "A" knows Charlie?
console.log(
q(DB, (s) => s[0] === "a", "knows", "charlie")
);
// { alice: { knows: [ 'charlie' ] } }
```
(3) Instead of a root object (like `DB`), an array of objects can be queried. In
this case, only predicate-object patterns are used (**no subject terms**, aka
array indices in this case).
```ts tangle:export/readme.ts
type Person = { id: string; knows: string[] };
const DBALT: Person[] = [
{ id: "alice", knows: ["bob", "charlie"] },
{ id: "bob", knows: ["alice"] },
{ id: "charlie", knows: ["alice","bob","dori"] },
];
console.log(
defQuery<Person[]>()(DBALT, "knows", "alice")
);
// [
// { id: 'bob', knows: [ 'alice' ] },
// { id: 'charlie', knows: [ 'alice', 'bob', 'dori' ] }
// ]
```
### Querying objects
The following example is using the `DB` object defined [further
above](#query-patterns)...
```ts tangle:export/readme2.ts
import { defQuery } from "@thi.ng/oquery";
// include above DB definition
<<db>>
// using partial result objects option for brevity here
const q = defQuery({ partial: true });
// find all subjects with `type = "person"` relationship
console.log(
q(DB, null, "type", "person")
);
// { alice: { type: 'person' }, bob: { type: 'person' } }
// everyone w/ given min age
console.log(
q(DB, null, "age", (age: number) => age >= 33)
);
// { alice: { age: 33 } }
// select only subjects with A/B initials
console.log(
q(DB, (id) => id >= "a" && id < "c", null, null)
);
// {
// alice: { age: 33, knows: [ 'bob', 'charlie', 'dori' ], type: 'person' },
// bob: { age: 32, knows: [ 'alice' ], type: 'person', spouse: 'alice' }
// }
```
Union vs. intersection queries:
```ts tangle:export/readme2.ts
const union = defQuery();
// who knows bob OR charlie?
console.log(
union(DB, null, "knows", ["bob", "charlie"])
);
// {
// alice: { age: 33, knows: [ 'bob', 'charlie', 'dori' ], type: 'person' },
// charlie: { parent: 'alice', knows: [ 'alice', 'bob', 'dori' ] },
// dori: { knows: [ 'bob' ] }
// }
const isec = defQuery({ intersect: true });
// who knows bob AND charlie?
console.log(
isec(DB, null, "knows", ["bob", "charlie"])
);
// {
// alice: { age: 33, knows: [ 'bob', 'charlie', 'dori' ], type: 'person' }
// }
```
More query examples in [tests](https://github.com/thi-ng/umbrella/blob/develop/packages/oquery/test/index.ts)...
## Authors
- [Karsten Schmidt](https://thi.ng)
If this project contributes to an academic publication, please cite it as:
```bibtex
{thing-oquery,
title = "@thi.ng/oquery",
author = "Karsten Schmidt",
note = "https://thi.ng/oquery",
year = 2020
}
```
## License
© 2020 - 2025 Karsten Schmidt // Apache License 2.0