Skip to content

Commit

Permalink
Added support for large array modifications (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
karelklima authored Nov 24, 2023
1 parent 9c42634 commit e5bd229
Show file tree
Hide file tree
Showing 14 changed files with 1,046 additions and 159 deletions.
3 changes: 2 additions & 1 deletion docs/table-of-contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"v2": {
"title": "Version 2 (not yet released)",
"pages": [
["pagination", "Pagination"]
["pagination", "Pagination"],
["working-with-arrays", "Working with arrays"]
]
}
}
119 changes: 119 additions & 0 deletions docs/v2/working-with-arrays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Working with arrays

The LDkit library provides a simple and intuitive way to work with arrays in
linked data contexts. This section focuses on the manipulation of array
elements, including adding and removing elements.

## Initializing and Defining Models

Before working with arrays, initialize your data source and define your data
schema. Examples below make use of a Director schema, having a director name and
a list of movies:

```typescript
import { type Context, createLens, createNamespace } from "ldkit";

// Create a custom namespace
const ns = createNamespace(
{
"iri": "http://ns/",
"prefix": "ns",
"terms": [
"name",
"movie",
],
} as const,
);

// Create a schema
const DirectorSchema = {
name: ns.name,
movies: {
"@id": ns.movie,
"@array": true,
},
} as const;

const Directors = createLens(DirectorSchema);

// Add a director with a an empty list of movies
await Directors.insert({
$id: "https://Quentin_Tarantino",
name: "Quentin Tarantino",
movies: [],
});
```

## Updating arrays

To modify array elements, use the `Lens.update` method. This method supports
different operations.

1. **Setting an Array**: Replace the entine array with a new set of elements

```typescript
await Directors.update({
$id: "https://Quentin_Tarantino",
movies: {
$set: ["Pulp Fiction", "Reservoir Dogs"],
},
});
```

2. **Adding Elements**: Append new elements to the array

```typescript
await Directors.update({
$id: "https://Quentin_Tarantino",
movies: {
$add: ["Kill Bill", "Kill Bill 2"],
},
}); // The `movies` is now ["Pulp Fiction", "Reservoir Dogs", "Kill Bill", "Kill Bill 2"]
```

3. **Removing Elements**: Remove specific elements from the array

```typescript
await Directors.update({
$id: "https://Quentin_Tarantino",
movies: {
$remove: ["Reservoir Dogs"],
},
}); // The `movies` is now ["Pulp Fiction", "Kill Bill", "Kill Bill 2"]
```

4. **Setting an Empty Array**: Clear all elements from the array

```typescript
await Directors.update({
$id: "https://Quentin_Tarantino",
movies: {
$set: [], // Remove all movies
},
});
```

## Working with Multiple Entitites

You can also perform array updates on multiple entities simultaneously using a
single SPARQL query.

```typescript
await Directors.insert({
$id: "https://David_Fincher",
name: "David Fincher",
movies: ["Fight Club", "The Social Network"],
});

await Directors.update({
$id: "https://Quentin_Tarantino",
movies: {
$set: ["Inglorious Basterds"],
},
}, {
$id: "https://David_Fincher",
movies: {
$add: ["The Curious Case of Benjamin Button"],
},
});
```
87 changes: 87 additions & 0 deletions examples/basic/arrays.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { type Context, createLens, createNamespace } from "ldkit";
import { DataFactory, N3 } from "ldkit/rdf";
import { QueryEngine as Comunica } from "npm:@comunica/[email protected]";

// Create a custom namespace
const ns = createNamespace(
{
"iri": "http://ns/",
"prefix": "ns",
"terms": [
"name",
"movie",
],
} as const,
);

// Create a schema
const DirectorSchema = {
name: ns.name,
movies: {
"@id": ns.movie,
"@array": true,
},
} as const;

// Create in memory data store and context for query engine
const store = new N3.Store(undefined, {
factory: new DataFactory(),
});
const context: Context = {
sources: [store],
};
const engine = new Comunica();

// Create a resource using the data schema and context above
const Directors = createLens(DirectorSchema, context, engine);

// Add a director with a list of some movies
await Directors.insert({
$id: "https://Quentin_Tarantino",
name: "Quentin Tarantino",
movies: ["Pulp Fiction", "Reservoir Dogs"],
});

// Add a movie to the list of movies
await Directors.update({
$id: "https://Quentin_Tarantino",
movies: {
$add: ["Kill Bill", "Kill Bill 2"],
},
});

// Remove a movie from the list of movies
await Directors.update({
$id: "https://Quentin_Tarantino",
movies: {
$remove: ["Reservoir Dogs"],
},
});

// Print the list of movies
const tarantino = await Directors.findByIri("https://Quentin_Tarantino");
console.log("Tarantino movies", tarantino?.movies);

// Add another director with a list of some movies
await Directors.insert({
$id: "https://David_Fincher",
name: "David Fincher",
movies: ["Fight Club", "The Social Network"],
});

// Modify the list of movies for both directors
await Directors.update({
$id: "https://Quentin_Tarantino",
movies: {
$set: [], // Remove all movies
},
}, {
$id: "https://David_Fincher",
movies: {
$add: ["The Curious Case of Benjamin Button"],
},
});

// Print the list of movies of the other director
const fincher = await Directors.findByIri("https://David_Fincher");
console.log("Fincher movies", fincher?.movies);
3 changes: 2 additions & 1 deletion examples/basic/deno.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"importMap": "../import_map.json",
"tasks": {
"main": "deno run --allow-net --allow-env ./main.ts"
"main": "deno run --allow-net --allow-env ./main.ts",
"arrays": "deno run -A ./arrays.ts"
},
"lock": false
}
29 changes: 24 additions & 5 deletions library/encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ export const encode = (
node: DecodedNode,
schema: Schema,
context: Context,
includeType = true,
variableInitCounter = 0,
) => {
return Encoder.encode(node, schema, context, variableInitCounter);
return Encoder.encode(
node,
schema,
context,
includeType,
variableInitCounter,
);
};

class Encoder {
private context: Context;
private readonly context: Context;
private readonly includeType: boolean;

private df: DataFactory = new DataFactory({
blankNodePrefix: "b",
Expand All @@ -27,18 +35,27 @@ class Encoder {

private output: RDF.Quad[] = [];

private constructor(context: Context, variableInitCounter: number) {
private constructor(
context: Context,
includeType: boolean,
variableInitCounter: number,
) {
this.context = context;
this.includeType = includeType;
this.variableCounter = variableInitCounter;
}

static encode(
node: DecodedNode,
schema: Schema,
context: Context,
includeType: boolean,
variableInitCounter: number,
) {
return new Encoder(context, variableInitCounter).encode(node, schema);
return new Encoder(context, includeType, variableInitCounter).encode(
node,
schema,
);
}

encode(node: DecodedNode, schema: Schema) {
Expand Down Expand Up @@ -71,7 +88,9 @@ class Encoder {
}

encodeNode(node: DecodedNode, schema: Schema, nodeId: NodeId) {
this.encodeNodeType(node, schema["@type"], nodeId);
if (this.includeType) {
this.encodeNodeType(node, schema["@type"], nodeId);
}

Object.keys(schema).forEach((key) => {
if (key === "@type") {
Expand Down
13 changes: 9 additions & 4 deletions library/lens/lens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type SchemaInterface,
type SchemaInterfaceIdentity,
type SchemaPrototype,
type SchemaUpdateInterface,
} from "../schema/mod.ts";
import { decode } from "../decoder.ts";

Expand Down Expand Up @@ -39,7 +40,11 @@ export const createResource = <T extends SchemaPrototype>(
engine?: IQueryEngine,
) => new Lens(schema, context, engine);

export class Lens<S extends SchemaPrototype, I = SchemaInterface<S>> {
export class Lens<
S extends SchemaPrototype,
I = SchemaInterface<S>,
U = SchemaUpdateInterface<S>,
> {
private readonly schema: Schema;
private readonly context: Context;
private readonly engine: QueryEngineProxy;
Expand Down Expand Up @@ -113,9 +118,9 @@ export class Lens<S extends SchemaPrototype, I = SchemaInterface<S>> {
return this.updateQuery(q);
}

update(...entities: Entity<I>[]) {
const q = this.queryBuilder.updateQuery(entities);

update(...entities: U[]) {
const q = this.queryBuilder.updateQuery(entities as Entity[]);
// TODO: console.log(q);
return this.updateQuery(q);
}

Expand Down
28 changes: 9 additions & 19 deletions library/lens/query_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import rdf from "../namespaces/rdf.ts";
import { encode } from "../encoder.ts";

import { type Entity } from "./types.ts";
import { QueryHelper } from "./query_helper.ts";
import { UpdateHelper } from "./update_helper.ts";

export class QueryBuilder {
private readonly schema: Schema;
Expand Down Expand Up @@ -163,23 +163,13 @@ export class QueryBuilder {
}

updateQuery(entities: Entity[]) {
const deleteQuads: RDF.Quad[] = [];
const insertQuads: RDF.Quad[] = [];
const whereQuads: RDF.Quad[] = [];

entities.forEach((entity, index) => {
const helper = new QueryHelper(
entity,
this.schema,
this.context,
1000 * index,
);
deleteQuads.push(...helper.getDeleteQuads());
insertQuads.push(...helper.getInsertQuads());
whereQuads.push(...helper.getWhereQuads());
});

return DELETE`${deleteQuads}`.INSERT`${insertQuads}`
.WHERE`${deleteQuads}`.build();
const helper = new UpdateHelper(this.schema, this.context);

for (const entity of entities) {
helper.process(entity);
}

return DELETE`${helper.deleteQuads}`.INSERT`${helper.insertQuads}`
.WHERE`${helper.deleteQuads}`.build();
}
}
6 changes: 6 additions & 0 deletions library/lens/query_helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import type { Property, Schema } from "../schema/mod.ts";
import { encode } from "../encoder.ts";
import type { Entity } from "./types.ts";

/**
* @deprecated
* TODO: Remove this class
*/
export class QueryHelper {
private readonly entity: Entity;
private readonly schema: Schema;
Expand Down Expand Up @@ -30,6 +34,7 @@ export class QueryHelper {
this.entity,
this.schema,
this.context,
true,
this.variableInitCounter,
);
}
Expand All @@ -42,6 +47,7 @@ export class QueryHelper {
this.getEntityWithReplacedVariables(),
this.schema,
this.context,
true,
this.variableInitCounter,
);
}
Expand Down
Loading

0 comments on commit e5bd229

Please sign in to comment.