1 /*
2   Copyright 2011-2016 David Robillard <d@drobilla.net>
3 
4   Permission to use, copy, modify, and/or distribute this software for any
5   purpose with or without fee is hereby granted, provided that the above
6   copyright notice and this permission notice appear in all copies.
7 
8   THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 
17 #include "serd/serd.h"
18 #include "sord/sord.h"
19 
20 #include <inttypes.h>
21 #include <stdarg.h>
22 #include <stdbool.h>
23 #include <stdint.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #ifdef __GNUC__
29 #  define SORD_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1)))
30 #else
31 #  define SORD_LOG_FUNC(fmt, arg1)
32 #endif
33 
34 static const int      DIGITS        = 3;
35 static const unsigned n_objects_per = 2;
36 
37 static int n_expected_errors = 0;
38 
39 typedef struct {
40   SordQuad query;
41   int      expected_num_results;
42 } QueryTest;
43 
44 #define USTR(s) ((const uint8_t*)(s))
45 
46 static SordNode*
uri(SordWorld * world,int num)47 uri(SordWorld* world, int num)
48 {
49   if (num == 0) {
50     return 0;
51   }
52 
53   char  str[]   = "eg:000";
54   char* uri_num = str + 3; // First `0'
55   snprintf(uri_num, DIGITS + 1, "%0*d", DIGITS, num);
56   return sord_new_uri(world, (const uint8_t*)str);
57 }
58 
59 SORD_LOG_FUNC(1, 2)
60 static int
test_fail(const char * fmt,...)61 test_fail(const char* fmt, ...)
62 {
63   va_list args;
64   va_start(args, fmt);
65   fprintf(stderr, "error: ");
66   vfprintf(stderr, fmt, args);
67   va_end(args);
68   return 1;
69 }
70 
71 static int
generate(SordWorld * world,SordModel * sord,size_t n_quads,SordNode * graph)72 generate(SordWorld* world, SordModel* sord, size_t n_quads, SordNode* graph)
73 {
74   fprintf(stderr,
75           "Generating %zu (S P *) quads with %u objects each\n",
76           n_quads,
77           n_objects_per);
78 
79   for (size_t i = 0; i < n_quads; ++i) {
80     int num = (i * n_objects_per) + 1;
81 
82     SordNode* ids[2 + n_objects_per];
83     for (unsigned j = 0; j < 2 + n_objects_per; ++j) {
84       ids[j] = uri(world, num++);
85     }
86 
87     for (unsigned j = 0; j < n_objects_per; ++j) {
88       SordQuad tup = {ids[0], ids[1], ids[2 + j], graph};
89       if (!sord_add(sord, tup)) {
90         return test_fail("Fail: Failed to add quad\n");
91       }
92     }
93 
94     for (unsigned j = 0; j < 2 + n_objects_per; ++j) {
95       sord_node_free(world, ids[j]);
96     }
97   }
98 
99   // Add some literals
100 
101   // (98 4 "hello") and (98 4 "hello"^^<5>)
102   SordQuad tup = {0, 0, 0, 0};
103   tup[0]       = uri(world, 98);
104   tup[1]       = uri(world, 4);
105   tup[2]       = sord_new_literal(world, 0, USTR("hello"), NULL);
106   tup[3]       = graph;
107   sord_add(sord, tup);
108   sord_node_free(world, (SordNode*)tup[2]);
109   tup[2] = sord_new_literal(world, uri(world, 5), USTR("hello"), NULL);
110   if (!sord_add(sord, tup)) {
111     return test_fail("Failed to add typed literal\n");
112   }
113 
114   // (96 4 "hello"^^<4>) and (96 4 "hello"^^<5>)
115   tup[0] = uri(world, 96);
116   tup[1] = uri(world, 4);
117   tup[2] = sord_new_literal(world, uri(world, 4), USTR("hello"), NULL);
118   tup[3] = graph;
119   sord_add(sord, tup);
120   sord_node_free(world, (SordNode*)tup[2]);
121   tup[2] = sord_new_literal(world, uri(world, 5), USTR("hello"), NULL);
122   if (!sord_add(sord, tup)) {
123     return test_fail("Failed to add typed literal\n");
124   }
125 
126   // (94 5 "hello") and (94 5 "hello"@en-gb)
127   tup[0] = uri(world, 94);
128   tup[1] = uri(world, 5);
129   tup[2] = sord_new_literal(world, 0, USTR("hello"), NULL);
130   tup[3] = graph;
131   sord_add(sord, tup);
132   sord_node_free(world, (SordNode*)tup[2]);
133   tup[2] = sord_new_literal(world, NULL, USTR("hello"), "en-gb");
134   if (!sord_add(sord, tup)) {
135     return test_fail("Failed to add literal with language\n");
136   }
137 
138   // (92 6 "hello"@en-us) and (92 5 "hello"@en-gb)
139   tup[0] = uri(world, 92);
140   tup[1] = uri(world, 6);
141   tup[2] = sord_new_literal(world, 0, USTR("hello"), "en-us");
142   tup[3] = graph;
143   sord_add(sord, tup);
144   sord_node_free(world, (SordNode*)tup[2]);
145   tup[2] = sord_new_literal(world, NULL, USTR("hello"), "en-gb");
146   if (!sord_add(sord, tup)) {
147     return test_fail("Failed to add literal with language\n");
148   }
149 
150   sord_node_free(world, (SordNode*)tup[0]);
151   sord_node_free(world, (SordNode*)tup[2]);
152   tup[0] = uri(world, 14);
153   tup[2] = sord_new_literal(world, 0, USTR("bonjour"), "fr");
154   sord_add(sord, tup);
155   sord_node_free(world, (SordNode*)tup[2]);
156   tup[2] = sord_new_literal(world, 0, USTR("salut"), "fr");
157   sord_add(sord, tup);
158 
159   // Attempt to add some duplicates
160   if (sord_add(sord, tup)) {
161     return test_fail("Fail: Successfully added duplicate quad\n");
162   }
163   if (sord_add(sord, tup)) {
164     return test_fail("Fail: Successfully added duplicate quad\n");
165   }
166 
167   // Add a blank node subject
168   sord_node_free(world, (SordNode*)tup[0]);
169   tup[0] = sord_new_blank(world, USTR("ablank"));
170   sord_add(sord, tup);
171 
172   sord_node_free(world, (SordNode*)tup[1]);
173   sord_node_free(world, (SordNode*)tup[2]);
174   tup[1] = uri(world, 6);
175   tup[2] = uri(world, 7);
176   sord_add(sord, tup);
177   sord_node_free(world, (SordNode*)tup[0]);
178   sord_node_free(world, (SordNode*)tup[1]);
179   sord_node_free(world, (SordNode*)tup[2]);
180 
181   return EXIT_SUCCESS;
182 }
183 
184 #define TUP_FMT "(%6s %6s %6s)"
185 #define TUP_FMT_ARGS(t)                                  \
186   ((t)[0] ? sord_node_get_string((t)[0]) : USTR("*")),   \
187     ((t)[1] ? sord_node_get_string((t)[1]) : USTR("*")), \
188     ((t)[2] ? sord_node_get_string((t)[2]) : USTR("*"))
189 
190 static int
test_read(SordWorld * world,SordModel * sord,SordNode * g,const size_t n_quads)191 test_read(SordWorld* world, SordModel* sord, SordNode* g, const size_t n_quads)
192 {
193   int ret = EXIT_SUCCESS;
194 
195   SordQuad id;
196 
197   SordIter* iter = sord_begin(sord);
198   if (sord_iter_get_model(iter) != sord) {
199     return test_fail("Fail: Iterator has incorrect sord pointer\n");
200   }
201 
202   for (; !sord_iter_end(iter); sord_iter_next(iter)) {
203     sord_iter_get(iter, id);
204   }
205 
206   // Attempt to increment past end
207   if (!sord_iter_next(iter)) {
208     return test_fail("Fail: Successfully incremented past end\n");
209   }
210 
211   sord_iter_free(iter);
212 
213   const uint8_t* s           = USTR("hello");
214   SordNode*      plain_hello = sord_new_literal(world, 0, s, NULL);
215   SordNode*      type4_hello = sord_new_literal(world, uri(world, 4), s, NULL);
216   SordNode*      type5_hello = sord_new_literal(world, uri(world, 5), s, NULL);
217   SordNode*      gb_hello    = sord_new_literal(world, NULL, s, "en-gb");
218   SordNode*      us_hello    = sord_new_literal(world, NULL, s, "en-us");
219 
220 #define NUM_PATTERNS 18
221 
222   QueryTest patterns[NUM_PATTERNS] = {
223     {{0, 0, 0}, (int)(n_quads * n_objects_per) + 12},
224     {{uri(world, 1), 0, 0}, 2},
225     {{uri(world, 9), uri(world, 9), uri(world, 9)}, 0},
226     {{uri(world, 1), uri(world, 2), uri(world, 4)}, 1},
227     {{uri(world, 3), uri(world, 4), uri(world, 0)}, 2},
228     {{uri(world, 0), uri(world, 2), uri(world, 4)}, 1},
229     {{uri(world, 0), uri(world, 0), uri(world, 4)}, 1},
230     {{uri(world, 1), uri(world, 0), uri(world, 0)}, 2},
231     {{uri(world, 1), uri(world, 0), uri(world, 4)}, 1},
232     {{uri(world, 0), uri(world, 2), uri(world, 0)}, 2},
233     {{uri(world, 98), uri(world, 4), plain_hello}, 1},
234     {{uri(world, 98), uri(world, 4), type5_hello}, 1},
235     {{uri(world, 96), uri(world, 4), type4_hello}, 1},
236     {{uri(world, 96), uri(world, 4), type5_hello}, 1},
237     {{uri(world, 94), uri(world, 5), plain_hello}, 1},
238     {{uri(world, 94), uri(world, 5), gb_hello}, 1},
239     {{uri(world, 92), uri(world, 6), gb_hello}, 1},
240     {{uri(world, 92), uri(world, 6), us_hello}, 1}};
241 
242   SordQuad match = {uri(world, 1), uri(world, 2), uri(world, 4), g};
243   if (!sord_contains(sord, match)) {
244     return test_fail("Fail: No match for " TUP_FMT "\n", TUP_FMT_ARGS(match));
245   }
246 
247   SordQuad nomatch = {uri(world, 1), uri(world, 2), uri(world, 9), g};
248   if (sord_contains(sord, nomatch)) {
249     return test_fail("Fail: False match for " TUP_FMT "\n",
250                      TUP_FMT_ARGS(nomatch));
251   }
252 
253   if (sord_get(sord, NULL, NULL, uri(world, 3), g)) {
254     return test_fail("Fail: Get *,*,3 succeeded\n");
255   } else if (!sord_node_equals(
256                sord_get(sord, uri(world, 1), uri(world, 2), NULL, g),
257                uri(world, 3))) {
258     return test_fail("Fail: Get 1,2,* != 3\n");
259   } else if (!sord_node_equals(
260                sord_get(sord, uri(world, 1), NULL, uri(world, 3), g),
261                uri(world, 2))) {
262     return test_fail("Fail: Get 1,*,3 != 2\n");
263   } else if (!sord_node_equals(
264                sord_get(sord, NULL, uri(world, 2), uri(world, 3), g),
265                uri(world, 1))) {
266     return test_fail("Fail: Get *,2,3 != 1\n");
267   }
268 
269   for (unsigned i = 0; i < NUM_PATTERNS; ++i) {
270     QueryTest test = patterns[i];
271     SordQuad  pat  = {test.query[0], test.query[1], test.query[2], g};
272     fprintf(stderr, "Query " TUP_FMT "... ", TUP_FMT_ARGS(pat));
273 
274     iter            = sord_find(sord, pat);
275     int num_results = 0;
276     for (; !sord_iter_end(iter); sord_iter_next(iter)) {
277       sord_iter_get(iter, id);
278       ++num_results;
279       if (!sord_quad_match(pat, id)) {
280         sord_iter_free(iter);
281         return test_fail("Fail: Query result " TUP_FMT
282                          " does not match pattern\n",
283                          TUP_FMT_ARGS(id));
284       }
285     }
286     sord_iter_free(iter);
287     if (num_results != test.expected_num_results) {
288       return test_fail("Fail: Expected %d results, got %d\n",
289                        test.expected_num_results,
290                        num_results);
291     }
292     fprintf(stderr, "OK (%i matches)\n", test.expected_num_results);
293   }
294 
295   // Query blank node subject
296   SordQuad pat = {sord_new_blank(world, USTR("ablank")), 0, 0};
297   if (!pat[0]) {
298     return test_fail("Blank node subject lost\n");
299   }
300   fprintf(stderr, "Query " TUP_FMT "... ", TUP_FMT_ARGS(pat));
301   iter            = sord_find(sord, pat);
302   int num_results = 0;
303   for (; !sord_iter_end(iter); sord_iter_next(iter)) {
304     sord_iter_get(iter, id);
305     ++num_results;
306     if (!sord_quad_match(pat, id)) {
307       sord_iter_free(iter);
308       return test_fail("Fail: Query result " TUP_FMT
309                        " does not match pattern\n",
310                        TUP_FMT_ARGS(id));
311     }
312   }
313   fprintf(stderr, "OK\n");
314   sord_node_free(world, (SordNode*)pat[0]);
315   sord_iter_free(iter);
316   if (num_results != 2) {
317     return test_fail("Blank node subject query failed\n");
318   }
319 
320   // Test nested queries
321   fprintf(stderr, "Nested Queries... ");
322   const SordNode* last_subject = 0;
323   iter                         = sord_search(sord, NULL, NULL, NULL, NULL);
324   for (; !sord_iter_end(iter); sord_iter_next(iter)) {
325     sord_iter_get(iter, id);
326     if (id[0] == last_subject) {
327       continue;
328     }
329 
330     SordQuad  subpat          = {id[0], 0, 0};
331     SordIter* subiter         = sord_find(sord, subpat);
332     unsigned  num_sub_results = 0;
333     if (sord_iter_get_node(subiter, SORD_SUBJECT) != id[0]) {
334       return test_fail("Fail: Incorrect initial submatch\n");
335     }
336     for (; !sord_iter_end(subiter); sord_iter_next(subiter)) {
337       SordQuad subid;
338       sord_iter_get(subiter, subid);
339       if (!sord_quad_match(subpat, subid)) {
340         sord_iter_free(iter);
341         sord_iter_free(subiter);
342         return test_fail("Fail: Nested query result does not match pattern\n");
343       }
344       ++num_sub_results;
345     }
346     sord_iter_free(subiter);
347     if (num_sub_results != n_objects_per) {
348       return test_fail("Fail: Nested query " TUP_FMT " failed"
349                        " (%u results, expected %u)\n",
350                        TUP_FMT_ARGS(subpat),
351                        num_sub_results,
352                        n_objects_per);
353     }
354 
355     uint64_t count = sord_count(sord, id[0], 0, 0, 0);
356     if (count != num_sub_results) {
357       return test_fail("Fail: Query " TUP_FMT " sord_count() %" PRIu64
358                        "does not match result count %u\n",
359                        TUP_FMT_ARGS(subpat),
360                        count,
361                        num_sub_results);
362     }
363 
364     last_subject = id[0];
365   }
366   fprintf(stderr, "OK\n\n");
367   sord_iter_free(iter);
368 
369   return ret;
370 }
371 
372 static SerdStatus
unexpected_error(void * handle,const SerdError * error)373 unexpected_error(void* handle, const SerdError* error)
374 {
375   fprintf(stderr, "unexpected error: ");
376   vfprintf(stderr, error->fmt, *error->args);
377   return SERD_SUCCESS;
378 }
379 
380 static SerdStatus
expected_error(void * handle,const SerdError * error)381 expected_error(void* handle, const SerdError* error)
382 {
383   fprintf(stderr, "expected error: ");
384   vfprintf(stderr, error->fmt, *error->args);
385   ++n_expected_errors;
386   return SERD_SUCCESS;
387 }
388 
389 static int
finished(SordWorld * world,SordModel * sord,int status)390 finished(SordWorld* world, SordModel* sord, int status)
391 {
392   sord_free(sord);
393   sord_world_free(world);
394   return status;
395 }
396 
397 int
main(int argc,char ** argv)398 main(int argc, char** argv)
399 {
400   static const size_t n_quads = 300;
401 
402   sord_free(NULL); // Shouldn't crash
403 
404   SordWorld* world = sord_world_new();
405 
406   // Attempt to create invalid URI
407   fprintf(stderr, "expected ");
408   SordNode* bad_uri = sord_new_uri(world, USTR("noscheme"));
409   if (bad_uri) {
410     return test_fail("Successfully created invalid URI \"noscheme\"\n");
411   }
412   sord_node_free(world, bad_uri);
413 
414   sord_world_set_error_sink(world, expected_error, NULL);
415 
416   // Attempt to create invalid CURIE
417   SerdNode  base = serd_node_from_string(SERD_URI, USTR("http://example.org/"));
418   SerdEnv*  env  = serd_env_new(&base);
419   SerdNode  sbad = serd_node_from_string(SERD_CURIE, USTR("bad:"));
420   SordNode* bad  = sord_node_from_serd_node(world, env, &sbad, NULL, NULL);
421   if (bad) {
422     return test_fail("Successfully created CURIE with bad namespace\n");
423   }
424   sord_node_free(world, bad);
425   serd_env_free(env);
426 
427   // Attempt to create node from garbage
428   SerdNode junk = SERD_NODE_NULL;
429   junk.type     = (SerdType)1234;
430   if (sord_node_from_serd_node(world, env, &junk, NULL, NULL)) {
431     return test_fail("Successfully created node from garbage serd node\n");
432   }
433 
434   // Attempt to create NULL node
435   SordNode* nil_node =
436     sord_node_from_serd_node(world, NULL, &SERD_NODE_NULL, NULL, NULL);
437   if (nil_node) {
438     return test_fail("Successfully created NULL node\n");
439   }
440   sord_node_free(world, nil_node);
441 
442   // Check node flags are set properly
443   SordNode* with_newline = sord_new_literal(world, NULL, USTR("a\nb"), NULL);
444   if (!(sord_node_get_flags(with_newline) & SERD_HAS_NEWLINE)) {
445     return test_fail("Newline flag not set\n");
446   }
447   SordNode* with_quote = sord_new_literal(world, NULL, USTR("a\"b"), NULL);
448   if (!(sord_node_get_flags(with_quote) & SERD_HAS_QUOTE)) {
449     return test_fail("Quote flag not set\n");
450   }
451 
452   // Create with minimal indexing
453   SordModel* sord = sord_new(world, SORD_SPO, false);
454   generate(world, sord, n_quads, NULL);
455 
456   if (test_read(world, sord, NULL, n_quads)) {
457     sord_free(sord);
458     sord_world_free(world);
459     return EXIT_FAILURE;
460   }
461 
462   // Check adding tuples with NULL fields fails
463   sord_world_set_error_sink(world, expected_error, NULL);
464   const size_t initial_num_quads = sord_num_quads(sord);
465   SordQuad     tup               = {0, 0, 0, 0};
466   if (sord_add(sord, tup)) {
467     return test_fail("Added NULL tuple\n");
468   }
469   tup[0] = uri(world, 1);
470   if (sord_add(sord, tup)) {
471     return test_fail("Added tuple with NULL P and O\n");
472   }
473   tup[1] = uri(world, 2);
474   if (sord_add(sord, tup)) {
475     return test_fail("Added tuple with NULL O\n");
476   }
477 
478   if (sord_num_quads(sord) != initial_num_quads) {
479     return test_fail(
480       "Num quads %zu != %zu\n", sord_num_quads(sord), initial_num_quads);
481   }
482 
483   // Check adding tuples with an active iterator fails
484   SordIter* iter = sord_begin(sord);
485   tup[2]         = uri(world, 3);
486   if (sord_add(sord, tup)) {
487     return test_fail("Added tuple with active iterator\n");
488   }
489 
490   // Check removing tuples with several active iterator fails
491   SordIter* iter2 = sord_begin(sord);
492   if (!sord_erase(sord, iter)) {
493     return test_fail("Erased tuple with several active iterators\n");
494   }
495   n_expected_errors = 0;
496   sord_remove(sord, tup);
497   if (n_expected_errors != 1) {
498     return test_fail("Removed tuple with several active iterators\n");
499   }
500   sord_iter_free(iter);
501   sord_iter_free(iter2);
502 
503   sord_world_set_error_sink(world, unexpected_error, NULL);
504 
505   // Check interning merges equivalent values
506   SordNode* uri_id   = sord_new_uri(world, USTR("http://example.org"));
507   SordNode* blank_id = sord_new_blank(world, USTR("testblank"));
508   SordNode* lit_id   = sord_new_literal(world, uri_id, USTR("hello"), NULL);
509   if (sord_node_get_type(uri_id) != SORD_URI) {
510     return test_fail("URI node has incorrect type\n");
511   } else if (sord_node_get_type(blank_id) != SORD_BLANK) {
512     return test_fail("Blank node has incorrect type\n");
513   } else if (sord_node_get_type(lit_id) != SORD_LITERAL) {
514     return test_fail("Literal node has incorrect type\n");
515   }
516 
517   const size_t initial_num_nodes = sord_num_nodes(world);
518 
519   SordNode* uri_id2   = sord_new_uri(world, USTR("http://example.org"));
520   SordNode* blank_id2 = sord_new_blank(world, USTR("testblank"));
521   SordNode* lit_id2   = sord_new_literal(world, uri_id, USTR("hello"), NULL);
522   if (uri_id2 != uri_id || !sord_node_equals(uri_id2, uri_id)) {
523     fprintf(stderr, "Fail: URI interning failed (duplicates)\n");
524     return finished(world, sord, EXIT_FAILURE);
525   } else if (blank_id2 != blank_id || !sord_node_equals(blank_id2, blank_id)) {
526     fprintf(stderr, "Fail: Blank node interning failed (duplicates)\n");
527     return finished(world, sord, EXIT_FAILURE);
528   } else if (lit_id2 != lit_id || !sord_node_equals(lit_id2, lit_id)) {
529     fprintf(stderr, "Fail: Literal interning failed (duplicates)\n");
530     return finished(world, sord, EXIT_FAILURE);
531   }
532 
533   if (sord_num_nodes(world) != initial_num_nodes) {
534     return test_fail(
535       "Num nodes %zu != %zu\n", sord_num_nodes(world), initial_num_nodes);
536   }
537 
538   const uint8_t ni_hao[] = {0xE4, 0xBD, 0xA0, 0xE5, 0xA5, 0xBD, 0};
539   SordNode*     chello   = sord_new_literal(world, NULL, ni_hao, "cmn");
540 
541   // Test literal length
542   size_t         n_bytes;
543   size_t         n_chars;
544   const uint8_t* str = sord_node_get_string_counted(lit_id2, &n_bytes);
545   if (strcmp((const char*)str, "hello")) {
546     return test_fail("Literal node corrupt\n");
547   } else if (n_bytes != strlen("hello")) {
548     return test_fail("ASCII literal byte count incorrect\n");
549   }
550 
551   str = sord_node_get_string_measured(lit_id2, &n_bytes, &n_chars);
552   if (n_bytes != strlen("hello") || n_chars != strlen("hello")) {
553     return test_fail("ASCII literal measured length incorrect\n");
554   } else if (strcmp((const char*)str, "hello")) {
555     return test_fail("ASCII literal string incorrect\n");
556   }
557 
558   str = sord_node_get_string_measured(chello, &n_bytes, &n_chars);
559   if (n_bytes != 6) {
560     return test_fail("Multi-byte literal byte count incorrect\n");
561   } else if (n_chars != 2) {
562     return test_fail("Multi-byte literal character count incorrect\n");
563   } else if (strcmp((const char*)str, (const char*)ni_hao)) {
564     return test_fail("Multi-byte literal string incorrect\n");
565   }
566 
567   // Check interning doesn't clash non-equivalent values
568   SordNode* uri_id3   = sord_new_uri(world, USTR("http://example.orgX"));
569   SordNode* blank_id3 = sord_new_blank(world, USTR("testblankX"));
570   SordNode* lit_id3   = sord_new_literal(world, uri_id, USTR("helloX"), NULL);
571   if (uri_id3 == uri_id || sord_node_equals(uri_id3, uri_id)) {
572     fprintf(stderr, "Fail: URI interning failed (clash)\n");
573     return finished(world, sord, EXIT_FAILURE);
574   } else if (blank_id3 == blank_id || sord_node_equals(blank_id3, blank_id)) {
575     fprintf(stderr, "Fail: Blank node interning failed (clash)\n");
576     return finished(world, sord, EXIT_FAILURE);
577   } else if (lit_id3 == lit_id || sord_node_equals(lit_id3, lit_id)) {
578     fprintf(stderr, "Fail: Literal interning failed (clash)\n");
579     return finished(world, sord, EXIT_FAILURE);
580   }
581 
582   // Check literal interning
583   SordNode* lit4 = sord_new_literal(world, NULL, USTR("hello"), NULL);
584   SordNode* lit5 = sord_new_literal(world, uri_id2, USTR("hello"), NULL);
585   SordNode* lit6 = sord_new_literal(world, NULL, USTR("hello"), "en-ca");
586   if (lit4 == lit5 || sord_node_equals(lit4, lit5) || lit4 == lit6 ||
587       sord_node_equals(lit4, lit6) || lit5 == lit6 ||
588       sord_node_equals(lit5, lit6)) {
589     fprintf(stderr, "Fail: Literal interning failed (type/lang clash)\n");
590     return finished(world, sord, EXIT_FAILURE);
591   }
592 
593   // Check relative URI construction
594   SordNode* reluri =
595     sord_new_relative_uri(world, USTR("a/b"), USTR("http://example.org/"));
596   if (strcmp((const char*)sord_node_get_string(reluri),
597              "http://example.org/a/b")) {
598     fprintf(stderr,
599             "Fail: Bad relative URI constructed: <%s>\n",
600             sord_node_get_string(reluri));
601     return finished(world, sord, EXIT_FAILURE);
602   }
603   SordNode* reluri2 = sord_new_relative_uri(
604     world, USTR("http://drobilla.net/"), USTR("http://example.org/"));
605   if (strcmp((const char*)sord_node_get_string(reluri2),
606              "http://drobilla.net/")) {
607     fprintf(stderr,
608             "Fail: Bad relative URI constructed: <%s>\n",
609             sord_node_get_string(reluri));
610     return finished(world, sord, EXIT_FAILURE);
611   }
612 
613   // Check comparison with NULL
614   sord_node_free(world, uri_id);
615   sord_node_free(world, blank_id);
616   sord_node_free(world, lit_id);
617   sord_node_free(world, uri_id2);
618   sord_node_free(world, blank_id2);
619   sord_node_free(world, lit_id2);
620   sord_node_free(world, uri_id3);
621   sord_node_free(world, blank_id3);
622   sord_node_free(world, lit_id3);
623   sord_free(sord);
624 
625   static const char* const index_names[6] = {
626     "spo", "sop", "ops", "osp", "pso", "pos"};
627 
628   for (int i = 0; i < 6; ++i) {
629     sord = sord_new(world, (1 << i), false);
630     printf("Testing Index `%s'\n", index_names[i]);
631     generate(world, sord, n_quads, 0);
632     if (test_read(world, sord, 0, n_quads)) {
633       return finished(world, sord, EXIT_FAILURE);
634     }
635     sord_free(sord);
636   }
637 
638   static const char* const graph_index_names[6] = {
639     "gspo", "gsop", "gops", "gosp", "gpso", "gpos"};
640 
641   for (int i = 0; i < 6; ++i) {
642     sord = sord_new(world, (1 << i), true);
643     printf("Testing Index `%s'\n", graph_index_names[i]);
644     SordNode* graph = uri(world, 42);
645     generate(world, sord, n_quads, graph);
646     if (test_read(world, sord, graph, n_quads)) {
647       return finished(world, sord, EXIT_FAILURE);
648     }
649     sord_free(sord);
650   }
651 
652   // Test removing
653   sord   = sord_new(world, SORD_SPO, true);
654   tup[0] = uri(world, 1);
655   tup[1] = uri(world, 2);
656   tup[2] = sord_new_literal(world, 0, USTR("hello"), NULL);
657   tup[3] = 0;
658   sord_add(sord, tup);
659   if (!sord_ask(sord, tup[0], tup[1], tup[2], tup[3])) {
660     fprintf(stderr, "Failed to add tuple\n");
661     return finished(world, sord, EXIT_FAILURE);
662   }
663   sord_node_free(world, (SordNode*)tup[2]);
664   tup[2] = sord_new_literal(world, 0, USTR("hi"), NULL);
665   sord_add(sord, tup);
666   sord_remove(sord, tup);
667   if (sord_num_quads(sord) != 1) {
668     fprintf(
669       stderr, "Remove failed (%zu quads, expected 1)\n", sord_num_quads(sord));
670     return finished(world, sord, EXIT_FAILURE);
671   }
672 
673   iter = sord_find(sord, tup);
674   if (!sord_iter_end(iter)) {
675     fprintf(stderr, "Found removed tuple\n");
676     return finished(world, sord, EXIT_FAILURE);
677   }
678   sord_iter_free(iter);
679 
680   // Test double remove (silent success)
681   sord_remove(sord, tup);
682 
683   // Load a couple graphs
684   SordNode* graph42 = uri(world, 42);
685   SordNode* graph43 = uri(world, 43);
686   generate(world, sord, 1, graph42);
687   generate(world, sord, 1, graph43);
688 
689   // Remove one graph via iterator
690   SerdStatus st;
691   iter = sord_search(sord, NULL, NULL, NULL, graph43);
692   while (!sord_iter_end(iter)) {
693     if ((st = sord_erase(sord, iter))) {
694       fprintf(stderr, "Remove by iterator failed (%s)\n", serd_strerror(st));
695       return finished(world, sord, EXIT_FAILURE);
696     }
697   }
698   sord_iter_free(iter);
699 
700   // Erase the first tuple (an element in the default graph)
701   iter = sord_begin(sord);
702   if (sord_erase(sord, iter)) {
703     return test_fail("Failed to erase begin iterator on non-empty model\n");
704   }
705   sord_iter_free(iter);
706 
707   // Ensure only the other graph is left
708   SordQuad quad;
709   SordQuad pat = {0, 0, 0, graph42};
710   for (iter = sord_begin(sord); !sord_iter_end(iter); sord_iter_next(iter)) {
711     sord_iter_get(iter, quad);
712     if (!sord_quad_match(quad, pat)) {
713       fprintf(stderr, "Graph removal via iteration failed\n");
714       return finished(world, sord, EXIT_FAILURE);
715     }
716   }
717   sord_iter_free(iter);
718 
719   // Load file into two separate graphs
720   sord_free(sord);
721   sord               = sord_new(world, SORD_SPO, true);
722   env                = serd_env_new(&base);
723   SordNode*   graph1 = sord_new_uri(world, USTR("http://example.org/graph1"));
724   SordNode*   graph2 = sord_new_uri(world, USTR("http://example.org/graph2"));
725   SerdReader* reader = sord_new_reader(sord, env, SERD_TURTLE, graph1);
726   if ((st = serd_reader_read_string(reader, USTR("<s> <p> <o> .")))) {
727     fprintf(stderr, "Failed to read string (%s)\n", serd_strerror(st));
728     return finished(world, sord, EXIT_FAILURE);
729   }
730   serd_reader_free(reader);
731   reader = sord_new_reader(sord, env, SERD_TURTLE, graph2);
732   if ((st = serd_reader_read_string(reader, USTR("<s> <p> <o> .")))) {
733     fprintf(stderr, "Failed to re-read string (%s)\n", serd_strerror(st));
734     return finished(world, sord, EXIT_FAILURE);
735   }
736   serd_reader_free(reader);
737   serd_env_free(env);
738 
739   // Ensure we only see triple once
740   size_t n_triples = 0;
741   for (iter = sord_begin(sord); !sord_iter_end(iter); sord_iter_next(iter)) {
742     fprintf(stderr,
743             "%s %s %s %s\n",
744             sord_node_get_string(sord_iter_get_node(iter, SORD_SUBJECT)),
745             sord_node_get_string(sord_iter_get_node(iter, SORD_PREDICATE)),
746             sord_node_get_string(sord_iter_get_node(iter, SORD_OBJECT)),
747             sord_node_get_string(sord_iter_get_node(iter, SORD_GRAPH)));
748 
749     ++n_triples;
750   }
751   sord_iter_free(iter);
752   if (n_triples != 1) {
753     fprintf(stderr, "Found duplicate triple\n");
754     return finished(world, sord, EXIT_FAILURE);
755   }
756 
757   // Test SPO iteration on an SOP indexed store
758   sord_free(sord);
759   sord = sord_new(world, SORD_SOP, false);
760   generate(world, sord, 1, graph42);
761   for (iter = sord_begin(sord); !sord_iter_end(iter); sord_iter_next(iter)) {
762     ++n_triples;
763   }
764   sord_iter_free(iter);
765 
766   return finished(world, sord, EXIT_SUCCESS);
767 }
768