I adapted the SPARQL Explorer “Snorql,” originally built for Virtuoso / Dydra, to work with Apache Jena Fuseki. While SPARQL is a W3C standard, the behavioral differences between endpoint implementations can be surprisingly large. Here I document the three issues I encountered during Fuseki adaptation and their solutions.

Development Environment

I started Fuseki with Docker and tested locally.

#seodrflfovuuucisicpemskcemoonoeeeekanr-v--l-skrsigttiu:i-::eas"rAFmf-c:i:3oDUeudon0nMSssamse3mIE:etptr0eNKkaoa_:n_Ii:sin3tP_-ena0:ADd./m3SAayje0STtme:"WAalnOS:afRE/-uDTffs=_uuea1sskd=eeimtkkieiinst
d#coucLr-kolH-eadrd-'aXCtctoaoePn-msOtbptSeioTnnsdtaea'-rthTyuatyptp@pet-::ed/s/ttledoxactta/alt.hutortstltl:e3'030/test/data'

1. DESCRIBE Response Format Differs

Symptom

When sending a DESCRIBE query to Fuseki, results were not displayed on screen. The console showed JSON parse errors.

Investigation

SPARQL’s DESCRIBE / CONSTRUCT queries, unlike SELECT, return RDF graphs. The format differs by endpoint.

EndpointDESCRIBE Responseoutput= parameter
Virtuoso / DydraSPARQL Results JSON (proprietary extension)
FusekiRDF format (Turtle, RDF/JSON, etc.)Takes priority over Accept header

Snorql had been appending output=json as a URL parameter. Fuseki interprets this and returns JSON-LD, but Snorql expects RDF/JSON, causing parsing to break.

Using curl to isolate the cause makes it clear.

#c##c#uuWr'Wr'ilhilhttCttCh-toh-tooHpnHpnu:to:tt'/eu'/eA/ntA/nocltpcltuco-uco-tecTtecTppay=payutlpjtlpt:hes:heo:oo:pasnasaptaptarp:pp:pal3pl3pmi0lAi0lec3icc3ita0cca0cet/aet/arittpittoeitoeinsonso/tnh/tnAr//er//cdsradslcfpddfpde+afe+a+pjr+rjrjtsqjsqsolsiolohn?ogn?ne'qnn'oauudertereprydu=ttD=aEjkSRseCeosRtnIu&eBrqfEnuf+see<rchJyttS=tODpNE:-SR/LCe/DRteIuxBraEnm+sp<lhRetD.tFop/r:Jg/S//OpeNexrasmopnl1e>.'org/person1>'

The output=json parameter was interfering in Fuseki. Relying on Accept header content negotiation returns the correct format.

Fix (5 locations)

sparql.js — Conditional sending of output parameter

-+uirfl(Q_uoeurtypSuttr)inugrl+Q=ueroyuSttpruitn=g'+=oouuttppuutt=+''&';output+'&';

If _output is an empty string, output= is no longer sent.

sparql.js — Detect JSON via Content-Type

variisosJuJstsoponunt=?==(oJu'StjOpsNuo.tnp'a=r=?se'((jJxsShoOrnN.'.rpeasrpso(enx(shxerhT.reg.xertte)RsepspnosnesTeeHxeta)der('Content-Type').match(/json/i);

Even when the output parameter is not sent, if the response Content-Type contains json, it uses JSON.parse. This handles the case where Fuseki returns application/rdf+json.

snorql.js — Accept header and output control for DESCRIBE

-++ii}ff((!!ttqqhhppiiaassrr..aahhmmoo..mmaoeecuddcteeepffpu..ttiiss==__vv""iia"rrp;ttpuulooisscooa))tD{ioqonpn'atrradsmfe.+najdcscooenup,ttappu=ptl"ipacaparptalimioecnta/etjrisoonn/"r;df+json,jsonapplication/json";

Fixed the typo (jsonapplication/json to ,application/json) and set output to an empty string to enable Fuseki’s content negotiation.

snorql.js — Properly propagate empty string output

-+iiff((oouuttppuutt)!s=e=rvuincdee.fsienteOdu)tpsuetr(voiuctep.uste)t;Output(output);

JavaScript’s if(output) evaluates the empty string "" as falsy, so it was changed to !== undefined.

snorql_def.js — endpoint_type setting

-+eennddppooiinntt__ttyyppee::""vfiursteukois"o,",

2. Queries Fail Without PREFIX Declarations

Symptom

DESCRIBE now displays correctly, but label retrieval and related resource searches return 400 errors.

Parseerror:Unresolvedprefixedname:rdfs:label

Cause

After displaying DESCRIBE results, Snorql internally issues auxiliary queries for label retrieval, etc. These use prefix names like rdfs:label and schema:name directly.

EndpointPREFIX Declaration
VirtuosoImplicitly resolves rdfs:, etc. (no declaration needed)
FusekiRequires explicit PREFIX declarations for all (W3C compliant)

Code that relied on Virtuoso’s implicit resolution did not work with Fuseki.

Fix

snorql_ldb.js — Auto-prepend PREFIX to auxiliary queries

s}e,t_si}refer(tv!uitttttfrchhhhhaoneiiiiirr:sssssR(t.....envhfsssssgsaiueeeeeirsnrrrrrs=.cvvvvvtpstiiiiietfeicccccrhxroeeeeeivn)...asii({=sssl.ncmeeelaeentttpn;teMROppshweeur.)otqtesdShupfnt)Poeuiqh{AdstxliR(t(e.sQmH"s_.Leejns.tasfaeShdormreoenoevrdr"msiv()pci";naecAac.ecmes("cesetGes;thEppPiTtars""ce.),efe;sin".xdaj(ppspopfilxni,tca)aun;ttsio[o-pnpf/rxse]pp)ae;rnqdl-ornesquuletrsy+jssuobnm,i*s/s*i"o)n;

By registering namespaces with SPARQL.Service.setPrefix(), PREFIX rdfs: <...> is automatically prepended to queries when sent. All prefixes defined in namespaces.js are registered at once.

snorql_ldb.js — null guard

d#coucLr-kolH-eadrd-'aXCtctoaoePn-msOtbptSeioTnnsdtaea'-rthTyuatyptp@pet-::ed/s/ttledoxactta/alt.hutortstltl:e3'030/test/data'

0

This prevents TypeError when undefined properties are passed as undefined.


3. The is_virtuoso Flag Was Serving Two Purposes

Symptom

When trying to run Dydra alongside Fuseki, 400 errors occurred on Dydra.

d#coucLr-kolH-eadrd-'aXCtctoaoePn-msOtbptSeioTnnsdtaea'-rthTyuatyptp@pet-::ed/s/ttledoxactta/alt.hutortstltl:e3'030/test/data'

1

Cause

The is_virtuoso flag was serving two different responsibilities.

ResponsibilityLocationWhen truthy
Response formatsnorql.jsSends output=json
Virtuoso-specific syntaxutil.jsAppends define sql:describe-mode "CBD", etc.

Dydra needs output=json (same response format as Virtuoso) but does not accept Virtuoso-specific define directives. Making is_virtuoso truthy enabled both, causing an error from define.

Fix: Separation of Responsibilities

The single flag was split into three settings.

SettingRole
endpoint_typeON/OFF for Virtuoso define directives
is_virtuosoResponse format (output=json vs Accept header)
describe_as_constructAuto-convert DESCRIBE to CONSTRUCT

util.js — Use endpoint_type for define directives

d#coucLr-kolH-eadrd-'aXCtctoaoePn-msOtbptSeioTnnsdtaea'-rthTyuatyptp@pet-::ed/s/ttledoxactta/alt.hutortstltl:e3'030/test/data'

2

util.js — DESCRIBE to CONSTRUCT auto-conversion

d#coucLr-kolH-eadrd-'aXCtctoaoePn-msOtbptSeioTnnsdtaea'-rthTyuatyptp@pet-::ed/s/ttledoxactta/alt.hutortstltl:e3'030/test/data'

3

JPS’s Virtuoso returns a server error (SR452) for DESCRIBE, so an option was added to convert DESCRIBE <uri> to an equivalent CONSTRUCT.

Settings for Each Endpoint

d#coucLr-kolH-eadrd-'aXCtctoaoePn-msOtbptSeioTnnsdtaea'-rthTyuatyptp@pet-::ed/s/ttledoxactta/alt.hutortstltl:e3'030/test/data'

4


Data Flow (After Fix)

d#coucLr-kolH-eadrd-'aXCtctoaoePn-msOtbptSeioTnnsdtaea'-rthTyuatyptp@pet-::ed/s/ttledoxactta/alt.hutortstltl:e3'030/test/data'

5

Summary

SPARQL is a standard specification, but the differences between implementations are surprisingly numerous.

DifferenceVirtuosoFuseki
DESCRIBE responseProprietary JSONW3C-compliant RDF format
PREFIX resolutionImplicit resolutionExplicit declaration required
Proprietary extensions (define)YesNo (causes parse error)

To absorb these differences, the dual responsibility of is_virtuoso was separated into three settings: endpoint_type, is_virtuoso, and describe_as_construct. When adding a new endpoint type, you only need to add flags to snorql_def.js without breaking existing behavior.

For discovering these differences, checking Content-Type with curl and the browser’s Network tab were most useful. Even with SPARQL that should be the same according to the specification, looking at response headers reveals the individuality of each implementation.