Repository navigation
Conversation
* Convert Define into an enum * Comment explaining design * Make Undef falsy * Re-run CI * Add tests for UNDEF
SUBSTR converted the 1-based start to a Python slice index without clamping,
so a start of 0 or below became a negative index counted from the end.
SUBSTR("foobar", 0) returned "r" and SUBSTR("foobar", 0, 2) returned "",
where the SPARQL and XPath definition gives "foobar" and "f".
jdsika
force-pushed
the
feat/envited-x-pipeline
branch
from
October 14, 2026 12:14
4bf22dd to
980b7d6
Compare
Converter.to_collection walked an rdf:List chain checking only that each cell carries one rdf:first/rdf:rest pair and that the chain is acyclic. It did not check how many statements point into the chain. The @list form inlines the whole chain at a single reference and writes its cells nowhere else, so when a cell is referenced from more than one place each referring list rendered the shared cells again. The round trip then gained triples: _:tail rdf:first "b" ; rdf:rest rdf:nil . _:head rdf:first "a" ; rdf:rest _:tail . ex:s1 ex:p _:head . ex:s2 ex:p _:tail . # second reference into the chain serialized as two independent @list values, so the tail was emitted twice: 6 triples in, 8 out, not isomorphic. A head referenced twice was duplicated the same way: 4 in, 6 out. The Serialize RDF as JSON-LD Algorithm (JSON-LD 1.1 Processing Algorithms and API, section 8.4) only turns a chain into @list when every cell, the head included, is a blank node that is "referenced once", counted over the whole dataset. The converter now applies that condition. It counts blank-node references once per conversion, over every graph it writes, and a chain with a cell referenced more than once is written with explicit rdf:first/rdf:rest, which can express any arrangement of sharing. Counting across graphs also covers a cell that another named graph points at, whose reference was otherwise left dangling. add_to_node offered a term declared with "@container": "@list" for any object with an rdf:first, then iterated to_collection without checking for None. That already raised TypeError for a chain the converter refuses, such as one whose cell carries an extra property, and would have for every shared chain. The term is now chosen from to_collection's result instead. This is the defect RDFLib#3504 fixed in the Turtle and longturtle serializers; the JSON-LD converter has its own chain walk. Fixes RDFLib#3542.
…eter The N-Triples serializer writes statements in the order the store iterates them, which follows set iteration and so varies between processes. The same graph therefore serializes to different bytes in different runs, which shows up as diff noise in version-controlled RDF. RDFLib#3008 solved this for longturtle (RDFLib#1890) by canonicalizing with RGDA1 and sorting N-Triples rows, and RDFLib#3197 put that behind an opt-in `canon` parameter because of its cost (RDFLib#3196). This gives NTSerializer the same parameter, with the same meaning, the same canonicalization and the same default of False, so ordinary serialization is untouched and still streams statement by statement. `canon=True` canonicalizes with rdflib.compare.to_canonical_graph and writes the rendered rows in sorted order. Sorting the rendered rows rather than the triples keeps the order total: two distinct terms can share a str(), which would leave their relative order down to the store iteration order this is meant to remove, whereas identical rows mean identical statements. For a Dataset or ConjunctiveGraph it canonicalizes the statements the default path writes, the triples of every graph; a Dataset iterates quads, which the canonicalizer does not accept. The docstring states that this is not RDFC-1.0, that the labels may change between rdflib versions, and what it costs. Serializing one graph under six PYTHONHASHSEED values gives six distinct documents by default and one with canon=True. NT11Serializer inherits the parameter by subclassing.
RelationalExpression evaluated IN / NOT IN membership with Python `==`, which
is strict RDF term equality, while `=` and `!=` use Identifier.eq, which is
value equality. SPARQL 1.1 17.4.1.9 defines IN as exactly
(lhs = expression1) || (lhs = expression2) || ...
("The test is done with "=" operator, which tests for the same value"), and
17.4.1.10 defines NOT IN as its negation, so the two must never disagree.
They did. RDF 1.1 treats a simple literal and an xsd:string-typed literal as
the same term, and XSD type promotion makes 1 and 1.0 equal in value space,
so these evaluated incorrectly while the equivalent `=` comparisons were
correct:
"x"^^xsd:string IN ("x") false, should be true
1 IN (1.0) false, should be true
2 IN (<http://example/iri>, "str", 2.0) false, spec example says true
FILTER(?v NOT IN (...)) therefore admitted values that are in the list. It was
found through a SHACL sh:sparql constraint whose data reached the query as
xsd:string-typed literals while the shape listed plain ones.
Membership now uses Identifier.eq, with the error handling `=` already has: a
NotImplemented result (truthy, and a TypeError in a boolean context on Python
3.14) and a comparison TypeError are recorded as errors. Errors follow
17.4.1.9, "Errors in comparisons cause the IN expression to raise an error if
the RDF term being tested is not found elsewhere in the list", and now do so
uniformly: an erroring list entry is an error for an IRI on the left as well
as for a literal, and an erroring left-hand side is an error rather than a
result.
2 NOT IN (1/0) true -> error
<urn:x> NOT IN (1/0) true -> error
(1/0) NOT IN (2) true -> error
With rdflib.DAWG_LITERAL_COLLATION set, `=` raises a type error for literals
that share no value space (RDFterm-equal, 17.4.1.7), and IN now does too.
The W3C SPARQL 1.0 and 1.1 test suites give the same results before and
after.
jdsika
force-pushed
the
feat/envited-x-pipeline
branch
from
October 14, 2026 12:54
980b7d6 to
b3021e5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration branch. Tracking only, do not merge.
What this branch is
feat/envited-x-pipelineis upstreamRDFLib/rdflib@mainwith every contribution thisorganisation is still carrying cherry-picked on top, and nothing else. It is the branch to
check out when you need an rdflib that behaves the way our pipeline expects before
upstream has merged the relevant PRs.
It is rebased on upstream
main, never merged into. An empty cherry-pick list is the goalstate: it would mean upstream has merged everything, and the branch could be deleted.
Same convention as
ASCS-eV/linkml#14.What it currently carries
c565778a31caa09dcanonparameterb3021e5bIN/NOT INmust use value equality, not term equalityAll three cherry-pick cleanly onto upstream
mainat9099a782with no conflicts. Eachcommit is patch-identical to its upstream PR head.
Last verified on this branch, Python 3.12: black, ruff and mypy are clean for the whole
project, and the full test suite gives 7754 passed, 1807 skipped, 327 xfailed, 36 xpassed, no failures. CI: Validate run, started by hand: push and pull-request events do not start workflows in this fork.
What consumes it
Nothing yet, deliberately.
asam-openx-standardspins releasedrdflib==7.6.0through
scripts/requirements.txt, anddiffable-rdfdepends on released rdflib too.That pin is the point: rdflib performs the final serialization of every generated OWL and
SHACL artifact, so a change here moves artifact bytes. Consuming this branch would mean
building from a fork, which the pipeline's reproducibility rules do not allow: the lock
records released versions precisely so a third party can obtain them.
The relationship is therefore one-directional. What lands upstream here eventually arrives
as a released rdflib that the pipeline can bump to, with regenerated artifacts in the same
change. The merged #3504 (the Turtle variant
of the RDFLib#3543 defect) is the precedent:
diffable-rdfhad to work around it until it wasreleased.
When a release contains RDFLib#3543,
diffable-rdf's sentinel testtest_rdflib_jsonld_serializer_does_not_preserve_shared_list_tailwill fail by design;that is the signal to retire the workaround.