Skip to content

Literal equality fails for datatypes None and XSD.string #2123

Open

Description

@jfschaefer

Example:

>>> from rdflib import Literal
>>> from rdflib.namespace import XSD
>>> Literal('foo') == Literal('foo', datatype=XSD.string)
False

According to https://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal, every literal has a datatype (and a simple literal without a datatype should be considered syntactic sugar for literals of datatype http://www.w3.org/2001/XMLSchema#string).

The implementation in rdflib seems to follow the original RDF (not RDF 1.1), which allowed missing datatypes.

Related issues:

PS:
A similar problem would presumably occur with RDF.langString, except that setting both lang and datatype results in an error (which might also be an issue).

Activity

  1. added
    bugSomething isn't working
    backwards incompatiblewill affect backwards compatibility, this includes things like breaking our interface
    needs discussionThis issue needs further discussion to find an optimal way to resolve it.
    and removed
    backwards incompatiblewill affect backwards compatibility, this includes things like breaking our interface
    on Mar 20, 2023
  2. aucampia commented on Mar 20, 2023

    @aucampia
    Member

    This is a bug, but fixing it without a major version bump may be somewhat controversial.

  3. added
    backwards incompatiblewill affect backwards compatibility, this includes things like breaking our interface
    and removed
    backwards incompatiblewill affect backwards compatibility, this includes things like breaking our interface
    on Mar 20, 2023
  4. added
    coreRelates to core functionality of RDFLib, i.e. `rdflib.{graph,store,term}`
    breaking changeThis involves or proposes breaking RDFLib's public API.
    on May 22, 2023
  5. aucampia commented on Jun 9, 2023

    @aucampia
    Member

    One option to solve this is to enforce that rdflib.terms.Literal always has a datatype, but then we won't be able to support RDF 1.0 anymore. I'm somewhat okay with this, I think it would be nice to be able to support 1.0 and 1.1 - but I think 1.1. support is more important.

  6. aucampia commented on Jun 9, 2023

    @aucampia
    Member

    I think the best option is to just not allow null literals.

  7. 4 remaining items

  8. aucampia commented on Jun 9, 2023

    @aucampia
    Member

    One option to solve this is to enforce that rdflib.terms.Literal always has a datatype, but then we won't be able to support RDF 1.0 anymore. I'm somewhat okay with this, I think it would be nice to be able to support 1.0 and 1.1 - but I think 1.1. support is more important.

    The alternative is to maybe create a Literal abstract class which has optional data type and then have Literal with mandatory Datatype but also RDF10Literal with optional datatype, but I think that is going to be too much work.

  9. Tpt commented on Jun 10, 2023

    @Tpt
    Contributor

    I think the best option is to just not allow null literals.

    An other option: allow None datatype but for __eq__ and __hash__ use self.datatype or XSD.string to get the datatype. This should ensure proper equality support while keeping the "plain literal" information.

    A similar problem would presumably occur with RDF.langString, except that setting both lang and datatype results in an error (which might also be an issue).

    It might be nice to tweak the constructor to fail only if datatype is not RDF.langString.

  10. aucampia commented on Jun 10, 2023

    @aucampia
    Member

    An other option: allow None datatype but for __eq__ and __hash__ use self.datatype or XSD.string to get the datatype.

    This may be quite unexpected if someone is not familiar with RDF, and even for me, I would expect that the property has a stable value and that value is used during equality and hashing.

    I think eliminating the possibility of None datatypes is simpler for users to understand.

    For the constructor, I'm thinking of having 3 overloads:

    • def __new__(value: str, datatype: str = XSD.string) -> Created with lang=None
    • def __new__(value: str, lang: str) -> Created with datatype=XSD.langString
    • def __new__(value: str, lang: str, datatype: str) -> Create with the supplied datatype and lang, potentially with some validation.

    One obvious problem with validation is that it sometimes it may be undesirable, but I think we can maybe in future add a static variable on the Literal class to control its strictness (e.g. Literal.strict), even though that is not the nicest thing.

    I think at some point we should look at making factories that can control strictness in a way that does not involve global state, but right now, being overly strict may be best as it is easier to make it more lax later, I think.

  11. aucampia commented on Jun 10, 2023

    @aucampia
    Member
  12. Tpt commented on Jun 10, 2023

    @Tpt
    Contributor

    This may be quite unexpected if someone is not familiar with RDF, and even for me, I would expect that the property has a stable value and that value is used during equality and hashing.

    Indeed. It makes perfect sense.

    def __new__(value: str, lang: str, datatype: str) -> Create with the supplied datatype and lang, potentially with some validation.

    This variant seems dangerous because it indeed requires some validation. What about only allowing the first two overloads, making sure that datatype can not be set at the same time with lang? This way no validation would be needed.

  13. aucampia commented on Jun 10, 2023

    @aucampia
    Member

    def __new__(value: str, lang: str, datatype: str) -> Create with the supplied datatype and lang, potentially with some validation.

    This variant seems dangerous because it indeed requires some validation. What about only allowing the first two overloads, making sure that datatype can not be set at the same time with lang? This way no validation would be needed.

    The only potential use may be if we have a stream of data where we get Literals as value, datatype and language, a bit similar to data from HexT, but I will try to avoid adding this overload as I agree it is best to not have it if we don't need it, and if we really do need it we can add it later.

  14. self-assigned this
    on Jun 11, 2023
  15. aucampia commented on Jun 20, 2023

    @aucampia
    Member

    One problem is that SPARQL 1.1 defines Simple Literal:

    18.1.2. Simple Literal

    Definition: Simple Literal

    The set of Simple Literals is the set of all RDF Literals with no language tag or datatype IRI.

    I check RDF4J and Jena, Jena's Literal does support null datatypes:

    https://www.javadoc.io/doc/org.apache.jena/jena-core/latest/org.apache.jena.core/org/apache/jena/rdf/model/impl/LiteralImpl.html

    getDatatype

    public RDFDatatype getDatatype()

    Return the datatype of the literal. This will be null in the case of plain literals.

    RDF4J on the other hand defaults to xsd:string for datatype as the RDF 1.1 standard defines.

    getDatatype

    IRI getDatatype()

    Gets the datatype for this literal.

    If getLanguage() returns a non-empty value than this must return rdf:langString. If no datatype was assigned to this literal by the creator, then this method must return xsd:string.

    Probably on balance still right to use the RDF 1.1 spec, but worth highlighting this caveat.

  16. aucampia commented on Jun 20, 2023

    @aucampia
    Member

    One problem is that SPARQL 1.1 defines Simple Literal:

    18.1.2. Simple Literal
    Definition: Simple Literal
    The set of Simple Literals is the set of all RDF Literals with no language tag or datatype IRI.

    I guess the right option is to just defer to the RDF 1.1 spec.

  17. aucampia commented on Jun 20, 2023

    @aucampia
    Member

    What we can also do is have a simple attribute on Literal which is true if the Literal is initialized with a null datatype 🤔

    But we can maybe add that later also.

  18. aucampia commented on Jun 20, 2023

    @aucampia
    Member

    What we can also do is have a simple attribute on Literal which is true if the Literal is initialized with a null datatype thinking

    This would complicate equality about as much as having datatype=None being equal to datatype=XSD.string

  19. aucampia commented on Jun 20, 2023

    @aucampia
    Member

    I made a draft PR (#2460) that still has some work left, I think some tests will fail, but one thing which is quite apparent is that there are cases where passing datatype=None and having it be interpreted as xsd:string is helpful, mainly because there is quite often cases where the passed parameter is of type Optional[str].

  20. added
    feedback wantedFeedback from RDFLib users and contributors is wanted.
    on Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

accepting PRPRs for this issue will be considered.breaking changeThis involves or proposes breaking RDFLib's public API.bugSomething isn't workingconcept: RDF Literalconcept: datatypeRelates to the RDF literal datatype concept.coreRelates to core functionality of RDFLib, i.e. `rdflib.{graph,store,term}`feedback wantedFeedback from RDFLib users and contributors is wanted.needs discussionThis issue needs further discussion to find an optimal way to resolve it.parsingRelated to a parsing.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions