Repository: rdfjs/N3.js

Issue:
Bug (Error thrown) getting 'size' from 'match()' result on different Named Graph
Hi,

I hit this bug when simply performing a 'size' call on the dataset result of a 'match()' call when passing a Named Graph with no quads. It's super easy to re-produce - just this code will do it:

```bash
mkdir bug && cd bug && npm init -y && npm i n3 && vi index.js
```

Cut-and-Paste the following into `index.js`:

```javascript
const { DataFactory, Store } = require("n3");

const subject = DataFactory.namedNode("https://ex.com/Subject");
const predicate = DataFactory.namedNode("https://ex.com/Predicate");
const object = DataFactory.literal("Ooh la la!");
const namedGraph = DataFactory.namedNode("https://ex.com/NamedGraph");
const differentNamedGraph = DataFactory.namedNode(
  "https://ex.com/DifferentNamedGraph"
);

const store = new Store();

// Add any quad to a Named Graph.
store.add(DataFactory.quad(subject, predicate, object, namedGraph));

// Matching and 'size' work fine.
const matchesSame = store.match(subject, predicate, null, namedGraph);
console.log(`Matches same: [${matchesSame.size}]`);

// But match using a *DIFFERENT* Named Graph...
const matches = store.match(subject, predicate, null, differentNamedGraph);

// ...and 'size' now throws (it should just return zero)...
console.log(`Matches different: [${matches.size}]`);
```

And simply run with `node index.js`. I get the following output:

```
[bug]$ node index.js 
Matches same: [1]
/home/pmcb/Work/tmp/n3-test/node_modules/n3/lib/N3Store.js:1098
        } else if (subjects = indexMatch(graphs[graphKey].subjects, [subjectId, predicateId, objectId])) {
                                                          ^

TypeError: Cannot read properties of undefined (reading 'subjects')
    at get filtered (/home/pmcb/Work/tmp/n3-test/node_modules/n3/lib/N3Store.js:1098:59)
    at get size (/home/pmcb/Work/tmp/n3-test/node_modules/n3/lib/N3Store.js:1114:17)
    at Object.<anonymous> (/home/pmcb/Work/tmp/n3-test/index.js:23:44)
    at Module._compile (node:internal/modules/cjs/loader:1562:14)
    at Object..js (node:internal/modules/cjs/loader:1699:10)
    at Module.load (node:internal/modules/cjs/loader:1313:32)
    at Function._load (node:internal/modules/cjs/loader:1123:12)
    at TracingChannel.traceSync (node:diagnostics_channel:322:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5)

Node.js v22.13.1
```

Hints from issue comments:
@jeswr: Thanks for raising @pmcb55 - fixed in #498. CI should cut a new release shortly.@github-actions: :tada: This issue has been resolved in version 1.23.2 :tada:

The release is available on:
- [npm package (@latest dist-tag)](https://www.npmjs.com/package/n3/v/1.23.2)
- [GitHub release](https://github.com/rdfjs/N3.js/releases/tag/v1.23.2)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:@pmcb55: Wow Jesse - that was an unexpectedly quick turn-around - great job (and I can confirm it fixed my issue)!
Thanks a million 💯 !

Solution patch:
diff --git a/src/N3Store.js b/src/N3Store.js
index 1c79380..8eaac9e 100644
--- a/src/N3Store.js
+++ b/src/N3Store.js
@@ -1134,27 +1134,28 @@ class DatasetCoreAndReadableStream extends Readable {
 
       const graphs = n3Store._getGraphs(graph);
       for (const graphKey in graphs) {
-        let subjects, predicates, objects;
-
-        if (!subjectId && predicateId) {
-          if (predicates = indexMatch(graphs[graphKey].predicates, [predicateId, objectId, subjectId])) {
-            subjects = indexMatch(graphs[graphKey].subjects, [subjectId, predicateId, objectId]);
-            objects = indexMatch(graphs[graphKey].objects, [objectId, subjectId, predicateId]);
+        let subjects, predicates, objects, content;
+        if (content = graphs[graphKey]) {
+          if (!subjectId && predicateId) {
+            if (predicates = indexMatch(content.predicates, [predicateId, objectId, subjectId])) {
+              subjects = indexMatch(content.subjects, [subjectId, predicateId, objectId]);
+              objects = indexMatch(content.objects, [objectId, subjectId, predicateId]);
+            }
           }
-        }
-        else if (objectId) {
-          if (objects = indexMatch(graphs[graphKey].objects, [objectId, subjectId, predicateId])) {
-            subjects = indexMatch(graphs[graphKey].subjects, [subjectId, predicateId, objectId]);
-            predicates = indexMatch(graphs[graphKey].predicates, [predicateId, objectId, subjectId]);
+          else if (objectId) {
+            if (objects = indexMatch(content.objects, [objectId, subjectId, predicateId])) {
+              subjects = indexMatch(content.subjects, [subjectId, predicateId, objectId]);
+              predicates = indexMatch(content.predicates, [predicateId, objectId, subjectId]);
+            }
+          }
+          else if (subjects = indexMatch(content.subjects, [subjectId, predicateId, objectId])) {
+            predicates = indexMatch(content.predicates, [predicateId, objectId, subjectId]);
+            objects = indexMatch(content.objects, [objectId, subjectId, predicateId]);
           }
-        }
-        else if (subjects = indexMatch(graphs[graphKey].subjects, [subjectId, predicateId, objectId])) {
-          predicates = indexMatch(graphs[graphKey].predicates, [predicateId, objectId, subjectId]);
-          objects = indexMatch(graphs[graphKey].objects, [objectId, subjectId, predicateId]);
-        }
 
-        if (subjects)
-          newStore._graphs[graphKey] = { subjects, predicates, objects };
+          if (subjects)
+            newStore._graphs[graphKey] = { subjects, predicates, objects };
+        }
       }
       newStore._size = null;
     }



Test patch:
diff --git a/test/N3Store-test.js b/test/N3Store-test.js
index 245dd04..4a88aca 100644
--- a/test/N3Store-test.js
+++ b/test/N3Store-test.js
@@ -150,6 +150,17 @@ describe('Store', () => {
 
     it('should have size 5', () => {
       expect(store.size).toEqual(5);
+
+      expect(store.match(namedNode('s2'), namedNode('p2'), namedNode('o2'), namedNode('g1')).size).toEqual(1);
+      expect(store.match(null, namedNode('p2'), namedNode('o2'), namedNode('g1')).size).toEqual(1);
+      expect(store.match(namedNode('s2'), null, namedNode('o2'), namedNode('g1')).size).toEqual(1);
+      expect(store.match(namedNode('s2'), namedNode('p2'), null, namedNode('g1')).size).toEqual(1);
+      expect(store.match(namedNode('s2'), namedNode('p2'), namedNode('o2'), null).size).toEqual(1);
+
+      expect(store.match(namedNode('s2'), namedNode('p2'), namedNode('o2'), namedNode('g2')).size).toEqual(0);
+      expect(store.match(null, namedNode('p2'), namedNode('o2'), namedNode('g2')).size).toEqual(0);
+      expect(store.match(namedNode('s2'), null, namedNode('o2'), namedNode('g2')).size).toEqual(0);
+      expect(store.match(namedNode('s2'), namedNode('p2'), null, namedNode('g2')).size).toEqual(0);
     });
 
     describe('adding a triple that already exists', () => {