@thomasfosterau/effect-rdf 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2251 -2
- package/dist/Algebra-CGG4oae3.mjs +433 -0
- package/dist/Algebra-CGG4oae3.mjs.map +1 -0
- package/dist/Algebra-uGsIEOUA.d.mts +446 -0
- package/dist/Ast-CPRZmBvk.mjs +552 -0
- package/dist/Ast-CPRZmBvk.mjs.map +1 -0
- package/dist/Ast-vcw2W-lf.d.mts +827 -0
- package/dist/Bindings-Ck3m7JRC.mjs +249 -0
- package/dist/Bindings-Ck3m7JRC.mjs.map +1 -0
- package/dist/Bindings-DiKpBd7f.d.mts +153 -0
- package/dist/ClientErrors-CpXdFX5u.mjs +128 -0
- package/dist/ClientErrors-CpXdFX5u.mjs.map +1 -0
- package/dist/ClientErrors-D4IZHUM-.d.mts +101 -0
- package/dist/ClientFormats-CXomkXLV.mjs +73 -0
- package/dist/ClientFormats-CXomkXLV.mjs.map +1 -0
- package/dist/ClientFormats-Dm57ZPwR.d.mts +36 -0
- package/dist/Csv2Json-DynEcwCh.mjs +194 -0
- package/dist/Csv2Json-DynEcwCh.mjs.map +1 -0
- package/dist/Csv2Json-aXNVDoXQ.d.mts +40 -0
- package/dist/Csv2Rdf-8maXP6mt.d.mts +41 -0
- package/dist/Csv2Rdf-DuSEkaKE.mjs +161 -0
- package/dist/Csv2Rdf-DuSEkaKE.mjs.map +1 -0
- package/dist/Dataset-BlmlcI7J.mjs +782 -0
- package/dist/Dataset-BlmlcI7J.mjs.map +1 -0
- package/dist/Dataset-DS3ESOcO.d.mts +426 -0
- package/dist/Dataset.d.mts +2 -0
- package/dist/Dataset.mjs +1 -0
- package/dist/Datatype-CxMpamHC.d.mts +212 -0
- package/dist/Datatype-DQvkNkiH.mjs +1001 -0
- package/dist/Datatype-DQvkNkiH.mjs.map +1 -0
- package/dist/Dialect-8ChFj5EX.d.mts +113 -0
- package/dist/Dialect-CzVH38uo.mjs +165 -0
- package/dist/Dialect-CzVH38uo.mjs.map +1 -0
- package/dist/DocumentLoader-D3lZREnB.mjs +346 -0
- package/dist/DocumentLoader-D3lZREnB.mjs.map +1 -0
- package/dist/DocumentLoader-ZUCEVrW5.d.mts +178 -0
- package/dist/DublinCore-AnwqxRx6.mjs +137 -0
- package/dist/DublinCore-AnwqxRx6.mjs.map +1 -0
- package/dist/DublinCore-Dq-tVA5d.d.mts +119 -0
- package/dist/Engine-CYPB3g4h.d.mts +144 -0
- package/dist/Engine-DF8PN7g-.mjs +872 -0
- package/dist/Engine-DF8PN7g-.mjs.map +1 -0
- package/dist/Errors-CMdV1RhU.mjs +37 -0
- package/dist/Errors-CMdV1RhU.mjs.map +1 -0
- package/dist/Errors-u2bDjUeM.d.mts +30 -0
- package/dist/Evaluator-DEsU4d1o.d.mts +50 -0
- package/dist/Evaluator-DOY2-guC.mjs +281 -0
- package/dist/Evaluator-DOY2-guC.mjs.map +1 -0
- package/dist/Exif-CpqniYQd.d.mts +78 -0
- package/dist/Exif-DjkCfEB-.mjs +89 -0
- package/dist/Exif-DjkCfEB-.mjs.map +1 -0
- package/dist/Expression-BIMMvduQ.mjs +230 -0
- package/dist/Expression-BIMMvduQ.mjs.map +1 -0
- package/dist/Expression-W8A9cyX7.d.mts +89 -0
- package/dist/Extract-Bop85jES.mjs +220 -0
- package/dist/Extract-Bop85jES.mjs.map +1 -0
- package/dist/Extract-C_pCvuTC.d.mts +88 -0
- package/dist/Federation-BcNrozc1.d.mts +52 -0
- package/dist/Federation-CHScBkaK.mjs +130 -0
- package/dist/Federation-CHScBkaK.mjs.map +1 -0
- package/dist/Functions-AvjNcId_.mjs +1346 -0
- package/dist/Functions-AvjNcId_.mjs.map +1 -0
- package/dist/Functions-OQJZP-F6.d.mts +111 -0
- package/dist/Generator-C52mmq01.d.mts +50 -0
- package/dist/Generator-D3utCJC7.mjs +205 -0
- package/dist/Generator-D3utCJC7.mjs.map +1 -0
- package/dist/Grammar-CPlSxGH6.mjs +200 -0
- package/dist/Grammar-CPlSxGH6.mjs.map +1 -0
- package/dist/Grammar-CRhnRoCu.d.mts +148 -0
- package/dist/Grammar-D6jZdJQ3.d.mts +169 -0
- package/dist/Grammar-mQ8CgE1M.mjs +258 -0
- package/dist/Grammar-mQ8CgE1M.mjs.map +1 -0
- package/dist/GraphStore-DhUa4nFO.d.mts +45 -0
- package/dist/GraphStore-DvbqwKy1.mjs +212 -0
- package/dist/GraphStore-DvbqwKy1.mjs.map +1 -0
- package/dist/Iri-BB8Ks-BF.d.mts +84 -0
- package/dist/Iri-Cr92gsQV.mjs +232 -0
- package/dist/Iri-Cr92gsQV.mjs.map +1 -0
- package/dist/Iri.d.mts +2 -0
- package/dist/Iri.mjs +1 -0
- package/dist/Isomorphism-B6qTY-xy.d.mts +28 -0
- package/dist/Isomorphism-BmqxhKyJ.mjs +232 -0
- package/dist/Isomorphism-BmqxhKyJ.mjs.map +1 -0
- package/dist/Isomorphism-DInKnWIy.mjs +390 -0
- package/dist/Isomorphism-DInKnWIy.mjs.map +1 -0
- package/dist/Isomorphism-DvSfZXLR.d.mts +37 -0
- package/dist/Isomorphism.d.mts +2 -0
- package/dist/Isomorphism.mjs +1 -0
- package/dist/JsonLd-DOwS2FHg.mjs +2836 -0
- package/dist/JsonLd-DOwS2FHg.mjs.map +1 -0
- package/dist/JsonLd-DobFrQNa.d.mts +242 -0
- package/dist/JsonLdError-C9J-BxfG.mjs +106 -0
- package/dist/JsonLdError-C9J-BxfG.mjs.map +1 -0
- package/dist/JsonLdError-DSOfHkOy.d.mts +43 -0
- package/dist/LanguageTag-BCu8rcmX.d.mts +38 -0
- package/dist/LanguageTag-BcOpMGZ4.mjs +89 -0
- package/dist/LanguageTag-BcOpMGZ4.mjs.map +1 -0
- package/dist/LanguageTag.d.mts +2 -0
- package/dist/LanguageTag.mjs +1 -0
- package/dist/Lexical-Cg0JHrf6.mjs +243 -0
- package/dist/Lexical-Cg0JHrf6.mjs.map +1 -0
- package/dist/Lexical-D3zySYVS.d.mts +163 -0
- package/dist/Manifest-B6xf6mUc.mjs +148 -0
- package/dist/Manifest-B6xf6mUc.mjs.map +1 -0
- package/dist/Manifest-DQUMRBFr.d.mts +98 -0
- package/dist/Mapping-BgG74x3U.d.mts +63 -0
- package/dist/Mapping-Dc1ZGirk.mjs +259 -0
- package/dist/Mapping-Dc1ZGirk.mjs.map +1 -0
- package/dist/Mcp-1DwTTKOK.mjs +48 -0
- package/dist/Mcp-1DwTTKOK.mjs.map +1 -0
- package/dist/Mcp-d-pX9erx.d.mts +25 -0
- package/dist/Message-CK_DGQuu.mjs +95 -0
- package/dist/Message-CK_DGQuu.mjs.map +1 -0
- package/dist/Message-fIvTJSXM.d.mts +49 -0
- package/dist/Metadata-BbffwGDZ.mjs +445 -0
- package/dist/Metadata-BbffwGDZ.mjs.map +1 -0
- package/dist/Metadata-CEt3yc1-.d.mts +901 -0
- package/dist/Metadata-D4HsQNXr.mjs +1273 -0
- package/dist/Metadata-D4HsQNXr.mjs.map +1 -0
- package/dist/Metadata-nzS7QKRj.d.mts +350 -0
- package/dist/Model-DaMmxurZ.d.mts +293 -0
- package/dist/Model-DlPzRYhF.mjs +528 -0
- package/dist/Model-DlPzRYhF.mjs.map +1 -0
- package/dist/N3-BzyV-KIH.mjs +974 -0
- package/dist/N3-BzyV-KIH.mjs.map +1 -0
- package/dist/N3-DSLnonDn.d.mts +100 -0
- package/dist/NQuads-C1VBOJ3-.mjs +117 -0
- package/dist/NQuads-C1VBOJ3-.mjs.map +1 -0
- package/dist/NQuads-FjFZikFg.d.mts +76 -0
- package/dist/NTriples-D-kLSEAf.mjs +120 -0
- package/dist/NTriples-D-kLSEAf.mjs.map +1 -0
- package/dist/NTriples-D34OspfW.d.mts +76 -0
- package/dist/Namespace-ClTUO_ya.d.mts +42 -0
- package/dist/Namespace-DHFIsORd.mjs +75 -0
- package/dist/Namespace-DHFIsORd.mjs.map +1 -0
- package/dist/Namespace.d.mts +2 -0
- package/dist/Namespace.mjs +1 -0
- package/dist/Namespaces-Bz85vpjW.mjs +235 -0
- package/dist/Namespaces-Bz85vpjW.mjs.map +1 -0
- package/dist/Namespaces-C1jW6EDo.d.mts +178 -0
- package/dist/Optimizer-BWWQRQPY.mjs +168 -0
- package/dist/Optimizer-BWWQRQPY.mjs.map +1 -0
- package/dist/Optimizer-DUpUow6k.d.mts +26 -0
- package/dist/Options-BLBGL4HE.mjs +72 -0
- package/dist/Options-BLBGL4HE.mjs.map +1 -0
- package/dist/Options-BommQwP9.d.mts +127 -0
- package/dist/Packet-CA76wVX3.mjs +197 -0
- package/dist/Packet-CA76wVX3.mjs.map +1 -0
- package/dist/Packet-Cy5g6XuW.d.mts +130 -0
- package/dist/Parser-p1Gyr0QN.d.mts +85 -0
- package/dist/Parser-xARsOWXk.mjs +1317 -0
- package/dist/Parser-xARsOWXk.mjs.map +1 -0
- package/dist/Path-B20Yp8KE.mjs +284 -0
- package/dist/Path-B20Yp8KE.mjs.map +1 -0
- package/dist/Path-DRB8e-Zn.d.mts +181 -0
- package/dist/Pdf-B9RZn--8.d.mts +77 -0
- package/dist/Pdf-CKK6dkZn.mjs +89 -0
- package/dist/Pdf-CKK6dkZn.mjs.map +1 -0
- package/dist/PdfA-CUjSuntT.d.mts +109 -0
- package/dist/PdfA-CleOhuL6.mjs +124 -0
- package/dist/PdfA-CleOhuL6.mjs.map +1 -0
- package/dist/Photoshop-CiHo80qV.mjs +233 -0
- package/dist/Photoshop-CiHo80qV.mjs.map +1 -0
- package/dist/Photoshop-udKZ0I3N.d.mts +204 -0
- package/dist/PrefixMap-BHFPDLO-.mjs +103 -0
- package/dist/PrefixMap-BHFPDLO-.mjs.map +1 -0
- package/dist/PrefixMap-BMlXrWNd.d.mts +64 -0
- package/dist/PrefixMap.d.mts +2 -0
- package/dist/PrefixMap.mjs +1 -0
- package/dist/Processor-CMmecFsb.mjs +198 -0
- package/dist/Processor-CMmecFsb.mjs.map +1 -0
- package/dist/Processor-FJPvO0CB.d.mts +435 -0
- package/dist/Protocol-BA7OkNgp.mjs +202 -0
- package/dist/Protocol-BA7OkNgp.mjs.map +1 -0
- package/dist/Protocol-DlSoiIKB.d.mts +108 -0
- package/dist/ProtocolErrors-Ckfejnl6.d.mts +84 -0
- package/dist/ProtocolErrors-czDPVcX5.mjs +129 -0
- package/dist/ProtocolErrors-czDPVcX5.mjs.map +1 -0
- package/dist/Query-D9A6LOpP.mjs +364 -0
- package/dist/Query-D9A6LOpP.mjs.map +1 -0
- package/dist/Query-KqSkTYyG.d.mts +96 -0
- package/dist/QueryBuilder-BLK9Vfbz.mjs +674 -0
- package/dist/QueryBuilder-BLK9Vfbz.mjs.map +1 -0
- package/dist/QueryBuilder-DUqh4ECe.d.mts +529 -0
- package/dist/RdfJs-D6-49XB5.mjs +86 -0
- package/dist/RdfJs-D6-49XB5.mjs.map +1 -0
- package/dist/RdfJs-DXFqdWrd.d.mts +76 -0
- package/dist/RdfJs.d.mts +2 -0
- package/dist/RdfJs.mjs +1 -0
- package/dist/RdfJson-5aKPDlPS.d.mts +297 -0
- package/dist/RdfJson-BNocgW4f.mjs +548 -0
- package/dist/RdfJson-BNocgW4f.mjs.map +1 -0
- package/dist/RdfXml-DJ9G4gOq.mjs +1014 -0
- package/dist/RdfXml-DJ9G4gOq.mjs.map +1 -0
- package/dist/RdfXml-_N7yxWEp.d.mts +149 -0
- package/dist/Rdfs-BRVPsAjc.d.mts +84 -0
- package/dist/Rdfs-CyY2wVHI.mjs +167 -0
- package/dist/Rdfs-CyY2wVHI.mjs.map +1 -0
- package/dist/Rdfs.d.mts +2 -0
- package/dist/Rdfs.mjs +1 -0
- package/dist/Reasoner-BxYhx9s7.d.mts +94 -0
- package/dist/Reasoner-Cw90zrjv.mjs +798 -0
- package/dist/Reasoner-Cw90zrjv.mjs.map +1 -0
- package/dist/Reification-BmN4EfnM.d.mts +40 -0
- package/dist/Reification-dpqQvjDE.mjs +82 -0
- package/dist/Reification-dpqQvjDE.mjs.map +1 -0
- package/dist/Reification.d.mts +2 -0
- package/dist/Reification.mjs +1 -0
- package/dist/Report-XHs8htzK.mjs +121 -0
- package/dist/Report-XHs8htzK.mjs.map +1 -0
- package/dist/Report-hcXsc_19.d.mts +115 -0
- package/dist/Resource-B1cI8FyJ.d.mts +218 -0
- package/dist/Resource-sIEeNh15.mjs +374 -0
- package/dist/Resource-sIEeNh15.mjs.map +1 -0
- package/dist/Resource.d.mts +2 -0
- package/dist/Resource.mjs +1 -0
- package/dist/Results-BVNEC-Wt.d.mts +277 -0
- package/dist/Results-peJjGA2V.mjs +621 -0
- package/dist/Results-peJjGA2V.mjs.map +1 -0
- package/dist/ResultsStream-BhF0OcNn.mjs +639 -0
- package/dist/ResultsStream-BhF0OcNn.mjs.map +1 -0
- package/dist/ResultsStream-toYMXrOe.d.mts +100 -0
- package/dist/SemAct-CDchtk49.d.mts +96 -0
- package/dist/SemAct-CmyT4yI8.mjs +118 -0
- package/dist/SemAct-CmyT4yI8.mjs.map +1 -0
- package/dist/Serialization-DP9H5bYQ.mjs +514 -0
- package/dist/Serialization-DP9H5bYQ.mjs.map +1 -0
- package/dist/Serialization-Df8iVauT.d.mts +392 -0
- package/dist/Serialization.d.mts +2 -0
- package/dist/Serialization.mjs +1 -0
- package/dist/ServerFormats-CrRnjjf6.mjs +64 -0
- package/dist/ServerFormats-CrRnjjf6.mjs.map +1 -0
- package/dist/ServerFormats-DjC9NsO_.d.mts +36 -0
- package/dist/ServiceDescription-BERsvg-k.d.mts +54 -0
- package/dist/ServiceDescription-Dq5hxeGy.mjs +97 -0
- package/dist/ServiceDescription-Dq5hxeGy.mjs.map +1 -0
- package/dist/ShExC-Ctf-06CL.d.mts +67 -0
- package/dist/ShExC-DJbB19i3.mjs +1181 -0
- package/dist/ShExC-DJbB19i3.mjs.map +1 -0
- package/dist/ShExError-BeYFD9rv.d.mts +61 -0
- package/dist/ShExError-CVtq2NnM.mjs +64 -0
- package/dist/ShExError-CVtq2NnM.mjs.map +1 -0
- package/dist/ShExJ-C1u_wgyD.d.mts +531 -0
- package/dist/ShExJ-COiPd9Xw.mjs +532 -0
- package/dist/ShExJ-COiPd9Xw.mjs.map +1 -0
- package/dist/ShExR-ByR4mCh-.mjs +480 -0
- package/dist/ShExR-ByR4mCh-.mjs.map +1 -0
- package/dist/ShExR-CqONNNBz.d.mts +40 -0
- package/dist/ShapeMap-C422pxKl.d.mts +140 -0
- package/dist/ShapeMap-CsgKxB2G.mjs +181 -0
- package/dist/ShapeMap-CsgKxB2G.mjs.map +1 -0
- package/dist/Shapes-18GjfyBV.mjs +597 -0
- package/dist/Shapes-18GjfyBV.mjs.map +1 -0
- package/dist/Shapes-BQFDFc4Q.d.mts +306 -0
- package/dist/Sparql-BiYFMSpl.mjs +29 -0
- package/dist/Sparql-BiYFMSpl.mjs.map +1 -0
- package/dist/Sparql-DecpWTPh.d.mts +97 -0
- package/dist/SparqlClient-C_CjXbwj.mjs +333 -0
- package/dist/SparqlClient-C_CjXbwj.mjs.map +1 -0
- package/dist/SparqlClient-UPc88SHy.d.mts +290 -0
- package/dist/SparqlServer-CbUW-pVj.d.mts +71 -0
- package/dist/SparqlServer-Db7w7Cll.mjs +308 -0
- package/dist/SparqlServer-Db7w7Cll.mjs.map +1 -0
- package/dist/SqlStore-BBqFx-Wl.mjs +684 -0
- package/dist/SqlStore-BBqFx-Wl.mjs.map +1 -0
- package/dist/SqlStore-CCJo5I-e.d.mts +117 -0
- package/dist/Store-D0jmVfb3.d.mts +166 -0
- package/dist/Store-DH3Ow1GO.mjs +162 -0
- package/dist/Store-DH3Ow1GO.mjs.map +1 -0
- package/dist/Structure-BjMo4Bkc.mjs +334 -0
- package/dist/Structure-BjMo4Bkc.mjs.map +1 -0
- package/dist/Structure-n4CmDiab.d.mts +63 -0
- package/dist/Table-BGgm9nbM.mjs +538 -0
- package/dist/Table-BGgm9nbM.mjs.map +1 -0
- package/dist/Table-C3reiVrb.d.mts +261 -0
- package/dist/Term-B_4ezyXq.d.mts +619 -0
- package/dist/Term-CFLG1qu4.mjs +991 -0
- package/dist/Term-CFLG1qu4.mjs.map +1 -0
- package/dist/Term.d.mts +2 -0
- package/dist/Term.mjs +1 -0
- package/dist/TermCodec-CDZiEztW.mjs +397 -0
- package/dist/TermCodec-CDZiEztW.mjs.map +1 -0
- package/dist/TermCodec-_03bDXr0.d.mts +209 -0
- package/dist/TermCodec.d.mts +2 -0
- package/dist/TermCodec.mjs +1 -0
- package/dist/TermJson-BOPe0yve.d.mts +232 -0
- package/dist/TermJson-FAg3lvjl.mjs +142 -0
- package/dist/TermJson-FAg3lvjl.mjs.map +1 -0
- package/dist/TermJson.d.mts +2 -0
- package/dist/TermJson.mjs +1 -0
- package/dist/TermSchema-Bn9CoaYA.mjs +30 -0
- package/dist/TermSchema-Bn9CoaYA.mjs.map +1 -0
- package/dist/TermSchema-DBO6MroX.d.mts +6 -0
- package/dist/Testing-B-aPCpmu.mjs +63 -0
- package/dist/Testing-B-aPCpmu.mjs.map +1 -0
- package/dist/Testing-CF6s8P3O.d.mts +53 -0
- package/dist/Tiff-CR-2QeSI.d.mts +148 -0
- package/dist/Tiff-DlsOlZPM.mjs +169 -0
- package/dist/Tiff-DlsOlZPM.mjs.map +1 -0
- package/dist/Tools-7MdRKccr.d.mts +583 -0
- package/dist/Tools-ryxphZbI.mjs +329 -0
- package/dist/Tools-ryxphZbI.mjs.map +1 -0
- package/dist/Translate-B0YKKANS.d.mts +37 -0
- package/dist/Translate-gzWPq0Ia.mjs +308 -0
- package/dist/Translate-gzWPq0Ia.mjs.map +1 -0
- package/dist/Translation-DC9rLm2y.mjs +1535 -0
- package/dist/Translation-DC9rLm2y.mjs.map +1 -0
- package/dist/Translation-K4L9XBqX.d.mts +103 -0
- package/dist/TriG-BkqtqZ6Y.mjs +125 -0
- package/dist/TriG-BkqtqZ6Y.mjs.map +1 -0
- package/dist/TriG-xDjdDatp.d.mts +86 -0
- package/dist/Turtle-Bv1_bisW.d.mts +85 -0
- package/dist/Turtle-CZVUOtwl.mjs +133 -0
- package/dist/Turtle-CZVUOtwl.mjs.map +1 -0
- package/dist/ValidationReport-DBdJshW5.mjs +285 -0
- package/dist/ValidationReport-DBdJshW5.mjs.map +1 -0
- package/dist/ValidationReport-Dx3WxY0h.d.mts +192 -0
- package/dist/Validator-227ff9Rn.d.mts +179 -0
- package/dist/Validator-BzXMKDeJ.mjs +1162 -0
- package/dist/Validator-BzXMKDeJ.mjs.map +1 -0
- package/dist/Validator-Dl8cih5V.d.mts +207 -0
- package/dist/Validator-DmUfMbpL.mjs +568 -0
- package/dist/Validator-DmUfMbpL.mjs.map +1 -0
- package/dist/Vocab-DceKywpq.mjs +168 -0
- package/dist/Vocab-DceKywpq.mjs.map +1 -0
- package/dist/Vocab-Df-L-IwZ.d.mts +46 -0
- package/dist/Vocab-Do56UgBX.d.mts +81 -0
- package/dist/Vocab-TfpWG1Ne.mjs +118 -0
- package/dist/Vocab-TfpWG1Ne.mjs.map +1 -0
- package/dist/Vocab.d.mts +2 -0
- package/dist/Vocab.mjs +1 -0
- package/dist/Vocabulary-C6-GPOy4.d.mts +22 -0
- package/dist/Vocabulary-CnQl-_Z4.mjs +179 -0
- package/dist/Vocabulary-CnQl-_Z4.mjs.map +1 -0
- package/dist/Xml-B7iihYCS.mjs +976 -0
- package/dist/Xml-B7iihYCS.mjs.map +1 -0
- package/dist/Xml-CmfHS7SW.d.mts +267 -0
- package/dist/Xmp-D7CKA7MZ.d.mts +111 -0
- package/dist/Xmp-DCGN9pOD.mjs +307 -0
- package/dist/Xmp-DCGN9pOD.mjs.map +1 -0
- package/dist/XmpBasic-CP38jpR_.mjs +73 -0
- package/dist/XmpBasic-CP38jpR_.mjs.map +1 -0
- package/dist/XmpBasic-Cmbe53rQ.d.mts +64 -0
- package/dist/XmpMM-BfL_TgJF.mjs +73 -0
- package/dist/XmpMM-BfL_TgJF.mjs.map +1 -0
- package/dist/XmpMM-Dqm3IX8p.d.mts +63 -0
- package/dist/XmpRights-BKPtoI5L.mjs +73 -0
- package/dist/XmpRights-BKPtoI5L.mjs.map +1 -0
- package/dist/XmpRights-CaALZQz2.d.mts +63 -0
- package/dist/Xsd-Cdgx5zRB.mjs +405 -0
- package/dist/Xsd-Cdgx5zRB.mjs.map +1 -0
- package/dist/Xsd-D2elNhSm.d.mts +187 -0
- package/dist/Xsd.d.mts +2 -0
- package/dist/Xsd.mjs +1 -0
- package/dist/accessors-CEjaojuQ.mjs +28 -0
- package/dist/accessors-CEjaojuQ.mjs.map +1 -0
- package/dist/codec-Lb2T96rv.d.mts +20 -0
- package/dist/codec-QNo7IdJF.mjs +389 -0
- package/dist/codec-QNo7IdJF.mjs.map +1 -0
- package/dist/csvw/Csv2Json.d.mts +2 -0
- package/dist/csvw/Csv2Json.mjs +1 -0
- package/dist/csvw/Csv2Rdf.d.mts +2 -0
- package/dist/csvw/Csv2Rdf.mjs +1 -0
- package/dist/csvw/Datatype.d.mts +2 -0
- package/dist/csvw/Datatype.mjs +1 -0
- package/dist/csvw/Metadata.d.mts +2 -0
- package/dist/csvw/Metadata.mjs +1 -0
- package/dist/csvw/Processor.d.mts +2 -0
- package/dist/csvw/Processor.mjs +1 -0
- package/dist/csvw/Report.d.mts +2 -0
- package/dist/csvw/Report.mjs +1 -0
- package/dist/csvw/Table.d.mts +2 -0
- package/dist/csvw/Table.mjs +1 -0
- package/dist/csvw/index.d.mts +8 -0
- package/dist/csvw/index.mjs +8 -0
- package/dist/index-Cms2TYfC.d.mts +16 -0
- package/dist/index-Ct8xFmRs.d.mts +7 -0
- package/dist/index-D4QFwOjm.d.mts +28 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.mjs +17 -0
- package/dist/jsonld/DocumentLoader.d.mts +2 -0
- package/dist/jsonld/DocumentLoader.mjs +1 -0
- package/dist/jsonld/JsonLd.d.mts +2 -0
- package/dist/jsonld/JsonLd.mjs +1 -0
- package/dist/jsonld/JsonLdError.d.mts +2 -0
- package/dist/jsonld/JsonLdError.mjs +1 -0
- package/dist/jsonld/index.d.mts +4 -0
- package/dist/jsonld/index.mjs +4 -0
- package/dist/lists-D-OpQ0Ir.mjs +77 -0
- package/dist/lists-D-OpQ0Ir.mjs.map +1 -0
- package/dist/loop-D5-gNx9K.mjs +21 -0
- package/dist/loop-D5-gNx9K.mjs.map +1 -0
- package/dist/loop-DZ2hXlr_.mjs +21 -0
- package/dist/loop-DZ2hXlr_.mjs.map +1 -0
- package/dist/n3/Isomorphism.d.mts +2 -0
- package/dist/n3/Isomorphism.mjs +1 -0
- package/dist/n3/Mapping.d.mts +2 -0
- package/dist/n3/Mapping.mjs +1 -0
- package/dist/n3/Model.d.mts +2 -0
- package/dist/n3/Model.mjs +1 -0
- package/dist/n3/N3.d.mts +2 -0
- package/dist/n3/N3.mjs +1 -0
- package/dist/n3/Reasoner.d.mts +2 -0
- package/dist/n3/Reasoner.mjs +2 -0
- package/dist/n3/Vocab.d.mts +2 -0
- package/dist/n3/Vocab.mjs +1 -0
- package/dist/n3/index.d.mts +7 -0
- package/dist/n3/index.mjs +7 -0
- package/dist/ntriples/Grammar.d.mts +2 -0
- package/dist/ntriples/Grammar.mjs +1 -0
- package/dist/ntriples/Lexical.d.mts +2 -0
- package/dist/ntriples/Lexical.mjs +1 -0
- package/dist/ntriples/NQuads.d.mts +2 -0
- package/dist/ntriples/NQuads.mjs +1 -0
- package/dist/ntriples/NTriples.d.mts +2 -0
- package/dist/ntriples/NTriples.mjs +1 -0
- package/dist/ntriples/index.d.mts +5 -0
- package/dist/ntriples/index.mjs +5 -0
- package/dist/rdfjson/RdfJson.d.mts +2 -0
- package/dist/rdfjson/RdfJson.mjs +1 -0
- package/dist/rdfjson/index.d.mts +2 -0
- package/dist/rdfjson/index.mjs +1 -0
- package/dist/rdfxml/RdfXml.d.mts +2 -0
- package/dist/rdfxml/RdfXml.mjs +1 -0
- package/dist/rdfxml/Xml.d.mts +2 -0
- package/dist/rdfxml/Xml.mjs +1 -0
- package/dist/rdfxml/index.d.mts +3 -0
- package/dist/rdfxml/index.mjs +3 -0
- package/dist/rolldown-runtime-D8jQpDG7.mjs +12 -0
- package/dist/serialize-96KBvKaf.mjs +1463 -0
- package/dist/serialize-96KBvKaf.mjs.map +1 -0
- package/dist/serialize-DsoXdM6n.d.mts +37 -0
- package/dist/shacl/Message.d.mts +2 -0
- package/dist/shacl/Message.mjs +1 -0
- package/dist/shacl/Path.d.mts +2 -0
- package/dist/shacl/Path.mjs +1 -0
- package/dist/shacl/Shapes.d.mts +2 -0
- package/dist/shacl/Shapes.mjs +1 -0
- package/dist/shacl/Sparql.d.mts +2 -0
- package/dist/shacl/Sparql.mjs +1 -0
- package/dist/shacl/ValidationReport.d.mts +2 -0
- package/dist/shacl/ValidationReport.mjs +1 -0
- package/dist/shacl/Validator.d.mts +2 -0
- package/dist/shacl/Validator.mjs +1 -0
- package/dist/shacl/Vocabulary.d.mts +2 -0
- package/dist/shacl/Vocabulary.mjs +1 -0
- package/dist/shacl/index.d.mts +9 -0
- package/dist/shacl/index.mjs +8 -0
- package/dist/shex/SemAct.d.mts +2 -0
- package/dist/shex/SemAct.mjs +1 -0
- package/dist/shex/ShExC.d.mts +2 -0
- package/dist/shex/ShExC.mjs +1 -0
- package/dist/shex/ShExError.d.mts +2 -0
- package/dist/shex/ShExError.mjs +1 -0
- package/dist/shex/ShExJ.d.mts +2 -0
- package/dist/shex/ShExJ.mjs +2 -0
- package/dist/shex/ShExR.d.mts +2 -0
- package/dist/shex/ShExR.mjs +1 -0
- package/dist/shex/ShapeMap.d.mts +2 -0
- package/dist/shex/ShapeMap.mjs +1 -0
- package/dist/shex/Structure.d.mts +2 -0
- package/dist/shex/Structure.mjs +2 -0
- package/dist/shex/Validator.d.mts +2 -0
- package/dist/shex/Validator.mjs +4 -0
- package/dist/shex/index.d.mts +9 -0
- package/dist/shex/index.mjs +9 -0
- package/dist/sparql/Algebra.d.mts +2 -0
- package/dist/sparql/Algebra.mjs +1 -0
- package/dist/sparql/Ast.d.mts +2 -0
- package/dist/sparql/Ast.mjs +1 -0
- package/dist/sparql/Bindings.d.mts +2 -0
- package/dist/sparql/Bindings.mjs +1 -0
- package/dist/sparql/ClientErrors.d.mts +2 -0
- package/dist/sparql/ClientErrors.mjs +1 -0
- package/dist/sparql/ClientFormats.d.mts +2 -0
- package/dist/sparql/ClientFormats.mjs +1 -0
- package/dist/sparql/Engine.d.mts +2 -0
- package/dist/sparql/Engine.mjs +1 -0
- package/dist/sparql/Errors.d.mts +2 -0
- package/dist/sparql/Errors.mjs +1 -0
- package/dist/sparql/Expression.d.mts +2 -0
- package/dist/sparql/Expression.mjs +1 -0
- package/dist/sparql/Federation.d.mts +2 -0
- package/dist/sparql/Federation.mjs +1 -0
- package/dist/sparql/Functions.d.mts +2 -0
- package/dist/sparql/Functions.mjs +1 -0
- package/dist/sparql/Generator.d.mts +2 -0
- package/dist/sparql/Generator.mjs +1 -0
- package/dist/sparql/GraphStore.d.mts +2 -0
- package/dist/sparql/GraphStore.mjs +1 -0
- package/dist/sparql/Optimizer.d.mts +2 -0
- package/dist/sparql/Optimizer.mjs +1 -0
- package/dist/sparql/Options.d.mts +2 -0
- package/dist/sparql/Options.mjs +1 -0
- package/dist/sparql/Parser.d.mts +2 -0
- package/dist/sparql/Parser.mjs +1 -0
- package/dist/sparql/Protocol.d.mts +2 -0
- package/dist/sparql/Protocol.mjs +1 -0
- package/dist/sparql/ProtocolErrors.d.mts +2 -0
- package/dist/sparql/ProtocolErrors.mjs +1 -0
- package/dist/sparql/QueryBuilder.d.mts +2 -0
- package/dist/sparql/QueryBuilder.mjs +1 -0
- package/dist/sparql/Results.d.mts +2 -0
- package/dist/sparql/Results.mjs +1 -0
- package/dist/sparql/ResultsStream.d.mts +2 -0
- package/dist/sparql/ResultsStream.mjs +1 -0
- package/dist/sparql/ServerFormats.d.mts +2 -0
- package/dist/sparql/ServerFormats.mjs +1 -0
- package/dist/sparql/ServiceDescription.d.mts +2 -0
- package/dist/sparql/ServiceDescription.mjs +1 -0
- package/dist/sparql/SparqlClient.d.mts +2 -0
- package/dist/sparql/SparqlClient.mjs +1 -0
- package/dist/sparql/SparqlServer.d.mts +2 -0
- package/dist/sparql/SparqlServer.mjs +1 -0
- package/dist/sparql/Store.d.mts +2 -0
- package/dist/sparql/Store.mjs +1 -0
- package/dist/sparql/TermSchema.d.mts +3 -0
- package/dist/sparql/TermSchema.mjs +2 -0
- package/dist/sparql/Testing.d.mts +2 -0
- package/dist/sparql/Testing.mjs +1 -0
- package/dist/sparql/Translate.d.mts +2 -0
- package/dist/sparql/Translate.mjs +1 -0
- package/dist/sparql/ai/Mcp.d.mts +2 -0
- package/dist/sparql/ai/Mcp.mjs +1 -0
- package/dist/sparql/ai/Tools.d.mts +2 -0
- package/dist/sparql/ai/Tools.mjs +1 -0
- package/dist/sparql/ai/index.d.mts +3 -0
- package/dist/sparql/ai/index.mjs +3 -0
- package/dist/sparql/index.d.mts +30 -0
- package/dist/sparql/index.mjs +29 -0
- package/dist/sparql/shacl/Evaluator.d.mts +2 -0
- package/dist/sparql/shacl/Evaluator.mjs +1 -0
- package/dist/sparql/shacl/Query.d.mts +2 -0
- package/dist/sparql/shacl/Query.mjs +1 -0
- package/dist/sparql/shacl/index.d.mts +3 -0
- package/dist/sparql/shacl/index.mjs +3 -0
- package/dist/sparql/sql/Dialect.d.mts +2 -0
- package/dist/sparql/sql/Dialect.mjs +1 -0
- package/dist/sparql/sql/SqlStore.d.mts +2 -0
- package/dist/sparql/sql/SqlStore.mjs +1 -0
- package/dist/sparql/sql/Translation.d.mts +2 -0
- package/dist/sparql/sql/Translation.mjs +1 -0
- package/dist/sparql/sql/index.d.mts +4 -0
- package/dist/sparql/sql/index.mjs +4 -0
- package/dist/stringMap-D5YIpYtR.mjs +38 -0
- package/dist/stringMap-D5YIpYtR.mjs.map +1 -0
- package/dist/tokens-C6BzeodQ.mjs +24 -0
- package/dist/tokens-C6BzeodQ.mjs.map +1 -0
- package/dist/turtle/Grammar.d.mts +3 -0
- package/dist/turtle/Grammar.mjs +3 -0
- package/dist/turtle/Manifest.d.mts +2 -0
- package/dist/turtle/Manifest.mjs +1 -0
- package/dist/turtle/TriG.d.mts +2 -0
- package/dist/turtle/TriG.mjs +1 -0
- package/dist/turtle/Turtle.d.mts +2 -0
- package/dist/turtle/Turtle.mjs +1 -0
- package/dist/turtle/index.d.mts +5 -0
- package/dist/turtle/index.mjs +5 -0
- package/dist/xmp/DublinCore.d.mts +2 -0
- package/dist/xmp/DublinCore.mjs +1 -0
- package/dist/xmp/Exif.d.mts +2 -0
- package/dist/xmp/Exif.mjs +1 -0
- package/dist/xmp/Extract.d.mts +2 -0
- package/dist/xmp/Extract.mjs +1 -0
- package/dist/xmp/Metadata.d.mts +2 -0
- package/dist/xmp/Metadata.mjs +1 -0
- package/dist/xmp/Namespaces.d.mts +2 -0
- package/dist/xmp/Namespaces.mjs +1 -0
- package/dist/xmp/Packet.d.mts +2 -0
- package/dist/xmp/Packet.mjs +1 -0
- package/dist/xmp/Pdf.d.mts +2 -0
- package/dist/xmp/Pdf.mjs +1 -0
- package/dist/xmp/PdfA.d.mts +2 -0
- package/dist/xmp/PdfA.mjs +1 -0
- package/dist/xmp/Photoshop.d.mts +2 -0
- package/dist/xmp/Photoshop.mjs +1 -0
- package/dist/xmp/Tiff.d.mts +2 -0
- package/dist/xmp/Tiff.mjs +1 -0
- package/dist/xmp/Xmp.d.mts +2 -0
- package/dist/xmp/Xmp.mjs +3 -0
- package/dist/xmp/XmpBasic.d.mts +2 -0
- package/dist/xmp/XmpBasic.mjs +1 -0
- package/dist/xmp/XmpMM.d.mts +2 -0
- package/dist/xmp/XmpMM.mjs +1 -0
- package/dist/xmp/XmpRights.d.mts +2 -0
- package/dist/xmp/XmpRights.mjs +1 -0
- package/dist/xmp/index.d.mts +15 -0
- package/dist/xmp/index.mjs +15 -0
- package/package.json +195 -3
- package/src/Dataset.ts +1085 -0
- package/src/Iri.ts +322 -0
- package/src/Isomorphism.ts +319 -0
- package/src/LanguageTag.ts +98 -0
- package/src/Namespace.ts +100 -0
- package/src/PrefixMap.ts +117 -0
- package/src/RdfJs.ts +146 -0
- package/src/Rdfs.ts +241 -0
- package/src/Reification.ts +103 -0
- package/src/Resource.ts +873 -0
- package/src/Serialization.ts +775 -0
- package/src/Term.ts +1300 -0
- package/src/TermCodec.ts +505 -0
- package/src/TermJson.ts +313 -0
- package/src/Vocab.ts +161 -0
- package/src/Xsd.ts +573 -0
- package/src/csvw/Csv2Json.ts +265 -0
- package/src/csvw/Csv2Rdf.ts +268 -0
- package/src/csvw/Datatype.ts +589 -0
- package/src/csvw/Metadata.ts +1523 -0
- package/src/csvw/Processor.ts +276 -0
- package/src/csvw/Report.ts +135 -0
- package/src/csvw/Table.ts +759 -0
- package/src/csvw/index.ts +22 -0
- package/src/csvw/internal/dateFormat.ts +202 -0
- package/src/csvw/internal/json.ts +104 -0
- package/src/csvw/internal/numberFormat.ts +369 -0
- package/src/csvw/internal/object.ts +23 -0
- package/src/csvw/internal/uriTemplate.ts +130 -0
- package/src/csvw/internal/url.ts +93 -0
- package/src/csvw/internal/vocab.ts +282 -0
- package/src/index.ts +40 -0
- package/src/internal/arbitrary.ts +148 -0
- package/src/internal/loop.ts +24 -0
- package/src/internal/pending.ts +20 -0
- package/src/internal/textDecoder.ts +183 -0
- package/src/jsonld/DocumentLoader.ts +512 -0
- package/src/jsonld/JsonLd.ts +847 -0
- package/src/jsonld/JsonLdError.ts +105 -0
- package/src/jsonld/index.ts +13 -0
- package/src/jsonld/internal/compact.ts +852 -0
- package/src/jsonld/internal/context.ts +781 -0
- package/src/jsonld/internal/expand.ts +614 -0
- package/src/jsonld/internal/flatten.ts +243 -0
- package/src/jsonld/internal/frame.ts +559 -0
- package/src/jsonld/internal/iri.ts +52 -0
- package/src/jsonld/internal/json.ts +19 -0
- package/src/jsonld/internal/linkHeader.ts +60 -0
- package/src/jsonld/internal/rdf.ts +481 -0
- package/src/jsonld/internal/util.ts +367 -0
- package/src/n3/Isomorphism.ts +476 -0
- package/src/n3/Mapping.ts +332 -0
- package/src/n3/Model.ts +696 -0
- package/src/n3/N3.ts +237 -0
- package/src/n3/Reasoner.ts +568 -0
- package/src/n3/Vocab.ts +111 -0
- package/src/n3/index.ts +21 -0
- package/src/n3/internal/builtins.ts +668 -0
- package/src/n3/internal/guards.ts +23 -0
- package/src/n3/internal/lists.ts +106 -0
- package/src/n3/internal/loop.ts +28 -0
- package/src/n3/internal/parser.ts +1032 -0
- package/src/n3/internal/writer.ts +344 -0
- package/src/ntriples/Grammar.ts +317 -0
- package/src/ntriples/Lexical.ts +321 -0
- package/src/ntriples/NQuads.ts +140 -0
- package/src/ntriples/NTriples.ts +143 -0
- package/src/ntriples/index.ts +14 -0
- package/src/ntriples/internal/codec.ts +229 -0
- package/src/ntriples/internal/lineParser.ts +326 -0
- package/src/ntriples/internal/loop.ts +14 -0
- package/src/rdfjson/RdfJson.ts +806 -0
- package/src/rdfjson/index.ts +9 -0
- package/src/rdfjson/internal/json.ts +19 -0
- package/src/rdfxml/RdfXml.ts +1408 -0
- package/src/rdfxml/Xml.ts +419 -0
- package/src/rdfxml/index.ts +11 -0
- package/src/rdfxml/internal/chars.ts +73 -0
- package/src/rdfxml/internal/loop.ts +24 -0
- package/src/rdfxml/internal/parser.ts +1265 -0
- package/src/shacl/Message.ts +102 -0
- package/src/shacl/Path.ts +483 -0
- package/src/shacl/Shapes.ts +913 -0
- package/src/shacl/Sparql.ts +128 -0
- package/src/shacl/ValidationReport.ts +438 -0
- package/src/shacl/Validator.ts +1156 -0
- package/src/shacl/Vocabulary.ts +180 -0
- package/src/shacl/index.ts +22 -0
- package/src/shacl/internal/compare.ts +128 -0
- package/src/shacl/internal/regex.ts +56 -0
- package/src/shex/SemAct.ts +197 -0
- package/src/shex/ShExC.ts +415 -0
- package/src/shex/ShExError.ts +71 -0
- package/src/shex/ShExJ.ts +893 -0
- package/src/shex/ShExR.ts +640 -0
- package/src/shex/ShapeMap.ts +385 -0
- package/src/shex/Structure.ts +467 -0
- package/src/shex/Validator.ts +395 -0
- package/src/shex/index.ts +24 -0
- package/src/shex/internal/engine.ts +682 -0
- package/src/shex/internal/iterate.ts +14 -0
- package/src/shex/internal/json.ts +12 -0
- package/src/shex/internal/lexer.ts +56 -0
- package/src/shex/internal/nodeConstraint.ts +368 -0
- package/src/shex/internal/shexcLexer.ts +387 -0
- package/src/shex/internal/shexcParser.ts +1312 -0
- package/src/shex/internal/tokens.ts +41 -0
- package/src/shex/internal/xpathRegex.ts +290 -0
- package/src/sparql/Algebra.ts +914 -0
- package/src/sparql/Ast.ts +1344 -0
- package/src/sparql/Bindings.ts +272 -0
- package/src/sparql/ClientErrors.ts +156 -0
- package/src/sparql/ClientFormats.ts +70 -0
- package/src/sparql/Engine.ts +699 -0
- package/src/sparql/Errors.ts +30 -0
- package/src/sparql/Expression.ts +360 -0
- package/src/sparql/Federation.ts +187 -0
- package/src/sparql/Functions.ts +689 -0
- package/src/sparql/Generator.ts +426 -0
- package/src/sparql/GraphStore.ts +341 -0
- package/src/sparql/Optimizer.ts +230 -0
- package/src/sparql/Options.ts +191 -0
- package/src/sparql/Parser.ts +173 -0
- package/src/sparql/Protocol.ts +342 -0
- package/src/sparql/ProtocolErrors.ts +113 -0
- package/src/sparql/QueryBuilder.ts +953 -0
- package/src/sparql/Results.ts +844 -0
- package/src/sparql/ResultsStream.ts +771 -0
- package/src/sparql/ServerFormats.ts +75 -0
- package/src/sparql/ServiceDescription.ts +130 -0
- package/src/sparql/SparqlClient.ts +947 -0
- package/src/sparql/SparqlServer.ts +528 -0
- package/src/sparql/Store.ts +311 -0
- package/src/sparql/TermSchema.ts +20 -0
- package/src/sparql/Testing.ts +114 -0
- package/src/sparql/Translate.ts +407 -0
- package/src/sparql/ai/Mcp.ts +43 -0
- package/src/sparql/ai/Tools.ts +481 -0
- package/src/sparql/ai/index.ts +11 -0
- package/src/sparql/index.ts +64 -0
- package/src/sparql/internal/evaluator.ts +947 -0
- package/src/sparql/internal/hash.ts +418 -0
- package/src/sparql/internal/json.ts +12 -0
- package/src/sparql/internal/lexer.ts +488 -0
- package/src/sparql/internal/loop.ts +24 -0
- package/src/sparql/internal/parser.ts +2215 -0
- package/src/sparql/internal/prefetch.ts +46 -0
- package/src/sparql/internal/stringMap.ts +39 -0
- package/src/sparql/internal/textResults.ts +226 -0
- package/src/sparql/internal/values.ts +550 -0
- package/src/sparql/shacl/Evaluator.ts +478 -0
- package/src/sparql/shacl/Query.ts +574 -0
- package/src/sparql/shacl/index.ts +12 -0
- package/src/sparql/sql/Dialect.ts +246 -0
- package/src/sparql/sql/SqlStore.ts +1080 -0
- package/src/sparql/sql/Translation.ts +1898 -0
- package/src/sparql/sql/index.ts +17 -0
- package/src/sparql/sql/internal/encoding.ts +313 -0
- package/src/turtle/Grammar.ts +286 -0
- package/src/turtle/Manifest.ts +215 -0
- package/src/turtle/TriG.ts +158 -0
- package/src/turtle/Turtle.ts +166 -0
- package/src/turtle/index.ts +14 -0
- package/src/turtle/internal/loop.ts +23 -0
- package/src/turtle/internal/parse.ts +112 -0
- package/src/turtle/internal/parser.ts +1303 -0
- package/src/turtle/internal/serialize.ts +79 -0
- package/src/turtle/internal/streamWriter.ts +128 -0
- package/src/turtle/internal/writer.ts +898 -0
- package/src/xmp/DublinCore.ts +147 -0
- package/src/xmp/Exif.ts +109 -0
- package/src/xmp/Extract.ts +351 -0
- package/src/xmp/Metadata.ts +703 -0
- package/src/xmp/Namespaces.ts +228 -0
- package/src/xmp/Packet.ts +297 -0
- package/src/xmp/Pdf.ts +99 -0
- package/src/xmp/PdfA.ts +280 -0
- package/src/xmp/Photoshop.ts +275 -0
- package/src/xmp/Tiff.ts +196 -0
- package/src/xmp/Xmp.ts +430 -0
- package/src/xmp/XmpBasic.ts +90 -0
- package/src/xmp/XmpMM.ts +82 -0
- package/src/xmp/XmpRights.ts +83 -0
- package/src/xmp/index.ts +36 -0
- package/src/xmp/internal/accessors.ts +72 -0
- package/src/xmp/internal/xml.ts +63 -0
|
@@ -0,0 +1,1898 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPARQL algebra to SQL translation.
|
|
3
|
+
*
|
|
4
|
+
* `compile` translates an algebra tree into a single SQL `SELECT` over the
|
|
5
|
+
* store's tables (term dictionary + quads of term ids), or reports that the
|
|
6
|
+
* tree is not (exactly) translatable — the store then lets the generic engine
|
|
7
|
+
* evaluate that node and offers its children again.
|
|
8
|
+
*
|
|
9
|
+
* Solutions are rows whose columns hold term ids (`NULL` = unbound). Terms
|
|
10
|
+
* that are not in the dictionary but are needed by the query (constants of
|
|
11
|
+
* `VALUES`, `BIND`, …) get negative, query-local ids. Integer results of
|
|
12
|
+
* `COUNT` are carried as plain integers. The final `SELECT` joins the
|
|
13
|
+
* dictionary to decode the ids.
|
|
14
|
+
*
|
|
15
|
+
* Translated: BGPs (self-joins of the quads table, triple term patterns
|
|
16
|
+
* through the dictionary), `Join`, `LeftJoin` (with the filter in the `ON`
|
|
17
|
+
* clause), `Union` (`UNION ALL` with `NULL` padding), `Minus` and
|
|
18
|
+
* `[NOT] EXISTS` (correlated `NOT EXISTS` / `EXISTS`), `Filter`, `Graph`
|
|
19
|
+
* (constant and variable), `Extend` with term-valued expressions, `Values`,
|
|
20
|
+
* `Project`, `Distinct`, `Reduced`, `Slice` and `Group` with `COUNT` /
|
|
21
|
+
* `SAMPLE`. Expressions: `&&`, `||`, `!`, `=`, `!=`, `<`, `>`, `<=`, `>=`,
|
|
22
|
+
* `IN`, `NOT IN`, `BOUND`, `sameTerm`, `isIRI`, `isBlank`, `isLiteral`,
|
|
23
|
+
* `isNumeric`, `isTriple`, `hasLang`, `hasLangDir`, `STR`, `LANG`,
|
|
24
|
+
* `DATATYPE`, `STRLEN`, `STRSTARTS`, `STRENDS`, `CONTAINS`, `LANGMATCHES`,
|
|
25
|
+
* `IF`, `COALESCE` and `EXISTS`, with SPARQL's error semantics (an error is
|
|
26
|
+
* SQL `NULL`; SPARQL's `&&`/`||`/`!` coincide with SQL's three-valued logic).
|
|
27
|
+
*
|
|
28
|
+
* Comparisons are evaluated per row by the comparison class of the terms
|
|
29
|
+
* (stored in the dictionary). Combinations of classes that SQL cannot compare
|
|
30
|
+
* exactly (e.g. dates, arbitrary-precision decimals, float/double promotion)
|
|
31
|
+
* are reported as `risks`: before running the query the store checks which
|
|
32
|
+
* classes exist and falls back to the engine if a risk can occur.
|
|
33
|
+
*
|
|
34
|
+
* @since 0.1.0
|
|
35
|
+
*/
|
|
36
|
+
import * as Arr from 'effect/Array';
|
|
37
|
+
import * as Equal from 'effect/Equal';
|
|
38
|
+
import * as Match from 'effect/Match';
|
|
39
|
+
import * as MutableHashMap from 'effect/MutableHashMap';
|
|
40
|
+
import * as MutableHashSet from 'effect/MutableHashSet';
|
|
41
|
+
import * as Option from 'effect/Option';
|
|
42
|
+
import * as Result from 'effect/Result';
|
|
43
|
+
import * as Term from '../../Term.ts';
|
|
44
|
+
import type * as Algebra from '../Algebra.ts';
|
|
45
|
+
import type * as Ast from '../Ast.ts';
|
|
46
|
+
import * as Expression from '../Expression.ts';
|
|
47
|
+
import type * as Store from '../Store.ts';
|
|
48
|
+
import type * as Dialect from './Dialect.ts';
|
|
49
|
+
import * as Enc from './internal/encoding.ts';
|
|
50
|
+
import * as Predicate from 'effect/Predicate';
|
|
51
|
+
|
|
52
|
+
// -----------------------------------------------------------------------------
|
|
53
|
+
// Models
|
|
54
|
+
// -----------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* What the translation needs to know besides the algebra.
|
|
58
|
+
*
|
|
59
|
+
* @since 0.1.0
|
|
60
|
+
* @category models
|
|
61
|
+
*/
|
|
62
|
+
export interface TranslationContext {
|
|
63
|
+
readonly dialect: Dialect.Dialect;
|
|
64
|
+
readonly tables: Dialect.Tables;
|
|
65
|
+
readonly dataset: Store.DatasetSpec;
|
|
66
|
+
readonly activeGraph: Store.ActiveGraph;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* An output column of a compiled query: `id` columns hold term ids (decoded
|
|
71
|
+
* from the dictionary columns that follow them), `int` columns integers
|
|
72
|
+
* (decoded as `xsd:integer`).
|
|
73
|
+
*
|
|
74
|
+
* @since 0.1.0
|
|
75
|
+
* @category models
|
|
76
|
+
*/
|
|
77
|
+
export interface OutputColumn {
|
|
78
|
+
readonly variable: string;
|
|
79
|
+
readonly type: 'id' | 'int';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A combination of comparison classes that the SQL cannot compare exactly.
|
|
84
|
+
* The query is only exact if the combination cannot occur: a class marked
|
|
85
|
+
* static is certainly present (it is the class of a constant operand);
|
|
86
|
+
* otherwise it occurs if some term of the store (or a query-local constant)
|
|
87
|
+
* has it.
|
|
88
|
+
*
|
|
89
|
+
* @since 0.1.0
|
|
90
|
+
* @category models
|
|
91
|
+
*/
|
|
92
|
+
export interface Risk {
|
|
93
|
+
readonly a: number;
|
|
94
|
+
readonly aStatic: boolean;
|
|
95
|
+
readonly b: number;
|
|
96
|
+
readonly bStatic: boolean;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A compiled query.
|
|
101
|
+
*
|
|
102
|
+
* @since 0.1.0
|
|
103
|
+
* @category models
|
|
104
|
+
*/
|
|
105
|
+
export interface CompiledQuery {
|
|
106
|
+
/** The SQL text (with dialect placeholders). */
|
|
107
|
+
readonly sql: string;
|
|
108
|
+
readonly params: ReadonlyArray<unknown>;
|
|
109
|
+
/**
|
|
110
|
+
* The output columns. Each `id` column is followed in the result rows by
|
|
111
|
+
* the dictionary columns `kind, value, datatype, lang, dir`.
|
|
112
|
+
*/
|
|
113
|
+
readonly columns: ReadonlyArray<OutputColumn>;
|
|
114
|
+
/** Query-local terms (by negative id). */
|
|
115
|
+
readonly locals: ReadonlyMap<number, Term.GroundTerm>;
|
|
116
|
+
/** The comparison classes of the query-local terms. */
|
|
117
|
+
readonly localClasses: ReadonlySet<number>;
|
|
118
|
+
readonly risks: ReadonlyArray<Risk>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Whether the risks of a compiled query can occur, given the comparison
|
|
123
|
+
* classes present in the store.
|
|
124
|
+
*
|
|
125
|
+
* @since 0.1.0
|
|
126
|
+
* @category utils
|
|
127
|
+
*/
|
|
128
|
+
export const isRisky = (compiled: CompiledQuery, storeClasses: ReadonlySet<number>): boolean => {
|
|
129
|
+
const present = (c: number) => storeClasses.has(c) || compiled.localClasses.has(c);
|
|
130
|
+
return compiled.risks.some((r) => (r.aStatic || present(r.a)) && (r.bStatic || present(r.b)));
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
class Unsupported {
|
|
134
|
+
readonly reason: string;
|
|
135
|
+
constructor(reason: string) {
|
|
136
|
+
this.reason = reason;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const unsupported = (reason: string): never => {
|
|
141
|
+
throw new Unsupported(reason);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// -----------------------------------------------------------------------------
|
|
145
|
+
// Constants
|
|
146
|
+
// -----------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/** Terms by key. */
|
|
149
|
+
type TermsByKey = MutableHashMap.MutableHashMap<string, Term.GroundTerm>;
|
|
150
|
+
|
|
151
|
+
/** Whether a term (pattern) is a triple term with no variables. */
|
|
152
|
+
const isGroundTriple = (t: Ast.TermPattern | Term.Term): t is Term.Triple =>
|
|
153
|
+
Term.isTriple(t) && Term.isGround(t);
|
|
154
|
+
|
|
155
|
+
const collectTerm = (term: Ast.TermPattern | Term.Term, out: TermsByKey): void => {
|
|
156
|
+
return Match.value(term).pipe(
|
|
157
|
+
Match.tag('NamedNode', 'Literal', (t) => {
|
|
158
|
+
MutableHashMap.set(out, t.key, t);
|
|
159
|
+
return;
|
|
160
|
+
}),
|
|
161
|
+
Match.tag('Triple', (t) => {
|
|
162
|
+
if (isGroundTriple(t)) {
|
|
163
|
+
MutableHashMap.set(out, t.key, t);
|
|
164
|
+
} else {
|
|
165
|
+
collectTerm(t.subject, out);
|
|
166
|
+
collectTerm(t.predicate, out);
|
|
167
|
+
collectTerm(t.object, out);
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}),
|
|
171
|
+
Match.orElse(() => undefined),
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const collectExpression = (e: Algebra.Expression, out: TermsByKey): void => {
|
|
176
|
+
Match.value(e).pipe(
|
|
177
|
+
Match.tag('Operation', 'FunctionCall', (call) =>
|
|
178
|
+
call.args.forEach((a) => collectExpression(a, out)),
|
|
179
|
+
),
|
|
180
|
+
Match.tag('Exists', (x) => collectOperation(x.input, out)),
|
|
181
|
+
Match.orElse((t) => collectTerm(t, out)),
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const collectOperation = (operation: Algebra.Operation, out: TermsByKey): void => {
|
|
186
|
+
return Match.value(operation).pipe(
|
|
187
|
+
Match.tag('Bgp', (op) =>
|
|
188
|
+
op.patterns.forEach((p) => {
|
|
189
|
+
collectTerm(p.subject, out);
|
|
190
|
+
collectTerm(p.predicate, out);
|
|
191
|
+
collectTerm(p.object, out);
|
|
192
|
+
}),
|
|
193
|
+
),
|
|
194
|
+
Match.tag('Path', () => undefined),
|
|
195
|
+
Match.tag('Join', 'Union', 'Minus', (op) => {
|
|
196
|
+
collectOperation(op.left, out);
|
|
197
|
+
collectOperation(op.right, out);
|
|
198
|
+
return;
|
|
199
|
+
}),
|
|
200
|
+
Match.tag('LeftJoin', (op) => {
|
|
201
|
+
collectOperation(op.left, out);
|
|
202
|
+
collectOperation(op.right, out);
|
|
203
|
+
if (op.expression !== undefined) collectExpression(op.expression, out);
|
|
204
|
+
return;
|
|
205
|
+
}),
|
|
206
|
+
Match.tag('Filter', (op) => {
|
|
207
|
+
collectExpression(op.expression, out);
|
|
208
|
+
collectOperation(op.input, out);
|
|
209
|
+
return;
|
|
210
|
+
}),
|
|
211
|
+
Match.tag('Extend', (op) => {
|
|
212
|
+
collectExpression(op.expression, out);
|
|
213
|
+
collectOperation(op.input, out);
|
|
214
|
+
return;
|
|
215
|
+
}),
|
|
216
|
+
Match.tag('Graph', (op) => {
|
|
217
|
+
collectTerm(op.name, out);
|
|
218
|
+
collectOperation(op.input, out);
|
|
219
|
+
return;
|
|
220
|
+
}),
|
|
221
|
+
Match.tag('Values', (op) =>
|
|
222
|
+
op.bindings.forEach((b) => Arr.forEach(b, ([, t]) => collectTerm(t, out))),
|
|
223
|
+
),
|
|
224
|
+
Match.tag('Group', 'OrderBy', 'Project', 'Distinct', 'Reduced', 'Slice', 'Service', (op) => {
|
|
225
|
+
collectOperation(op.input, out);
|
|
226
|
+
return;
|
|
227
|
+
}),
|
|
228
|
+
Match.exhaustive,
|
|
229
|
+
);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The constant terms of an algebra tree (and of the dataset) whose
|
|
234
|
+
* dictionary ids the translation needs.
|
|
235
|
+
*
|
|
236
|
+
* @since 0.1.0
|
|
237
|
+
* @category utils
|
|
238
|
+
*/
|
|
239
|
+
export const constants = (
|
|
240
|
+
op: Algebra.Operation,
|
|
241
|
+
context: Pick<TranslationContext, 'dataset' | 'activeGraph'>,
|
|
242
|
+
): ReadonlyArray<Term.GroundTerm> => {
|
|
243
|
+
const out: TermsByKey = MutableHashMap.empty();
|
|
244
|
+
collectOperation(op, out);
|
|
245
|
+
const { defaultGraph, namedGraphs } = context.dataset;
|
|
246
|
+
if (!Predicate.isString(defaultGraph)) {
|
|
247
|
+
defaultGraph.forEach((g) => {
|
|
248
|
+
if (!Term.isDefaultGraph(g)) MutableHashMap.set(out, g.key, g);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (!Predicate.isString(namedGraphs)) {
|
|
252
|
+
namedGraphs.forEach((g) => MutableHashMap.set(out, g.key, g));
|
|
253
|
+
}
|
|
254
|
+
if (Predicate.isTagged(context.activeGraph, 'NamedGraph')) {
|
|
255
|
+
MutableHashMap.set(out, context.activeGraph.graph.key, context.activeGraph.graph);
|
|
256
|
+
}
|
|
257
|
+
MutableHashMap.set(out, TRUE.key, TRUE);
|
|
258
|
+
MutableHashMap.set(out, FALSE.key, FALSE);
|
|
259
|
+
return Arr.fromIterable(MutableHashMap.values(out));
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// -----------------------------------------------------------------------------
|
|
263
|
+
// Blocks
|
|
264
|
+
// -----------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
interface Col {
|
|
267
|
+
readonly sql: string;
|
|
268
|
+
readonly nullable: boolean;
|
|
269
|
+
readonly type: 'id' | 'int';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** The columns of a block, by variable (in order of binding). */
|
|
273
|
+
type Cols = MutableHashMap.MutableHashMap<string, Col>;
|
|
274
|
+
|
|
275
|
+
/** Column types without their SQL, by variable. */
|
|
276
|
+
type ColTypes = MutableHashMap.MutableHashMap<string, Omit<Col, 'sql'>>;
|
|
277
|
+
|
|
278
|
+
interface Item {
|
|
279
|
+
readonly source: string;
|
|
280
|
+
readonly alias: string;
|
|
281
|
+
/** The `ON` condition of a `LEFT JOIN`; inner joins have none. */
|
|
282
|
+
readonly left: Option.Option<string>;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** A FROM item joined unconditionally. */
|
|
286
|
+
const inner = (source: string, alias: string): Item => ({ source, alias, left: Option.none() });
|
|
287
|
+
|
|
288
|
+
/** A `SELECT` under construction: FROM items, WHERE conditions and variables. */
|
|
289
|
+
interface Block {
|
|
290
|
+
readonly items: ReadonlyArray<Item>;
|
|
291
|
+
readonly where: ReadonlyArray<string>;
|
|
292
|
+
readonly cols: Cols;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** A block selecting from one subquery. */
|
|
296
|
+
interface Wrapped extends Block {
|
|
297
|
+
readonly items: readonly [Item];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const EMPTY: Block = { items: [], where: [], cols: MutableHashMap.empty() };
|
|
301
|
+
|
|
302
|
+
/** Whether a block is the unit (no items, conditions or columns). */
|
|
303
|
+
const isUnit = (block: Block): boolean =>
|
|
304
|
+
Arr.isReadonlyArrayEmpty(block.items) &&
|
|
305
|
+
Arr.isReadonlyArrayEmpty(block.where) &&
|
|
306
|
+
MutableHashMap.isEmpty(block.cols);
|
|
307
|
+
|
|
308
|
+
type GraphCtx =
|
|
309
|
+
| { readonly kind: 'default' }
|
|
310
|
+
| { readonly kind: 'const'; readonly id: number }
|
|
311
|
+
| { readonly kind: 'var'; readonly hidden: string };
|
|
312
|
+
|
|
313
|
+
const isHidden = (name: string): boolean => name.startsWith('#');
|
|
314
|
+
|
|
315
|
+
/** The distinct values of `items`, in order of first occurrence. */
|
|
316
|
+
const distinct = <A extends string | number>(items: Iterable<A>): Array<A> =>
|
|
317
|
+
Arr.fromIterable(MutableHashSet.fromIterable(items));
|
|
318
|
+
|
|
319
|
+
const XSD = 'http://www.w3.org/2001/XMLSchema#';
|
|
320
|
+
const TRUE = new Term.Literal('true', Term.namedNode(`${XSD}boolean`));
|
|
321
|
+
const FALSE = new Term.Literal('false', Term.namedNode(`${XSD}boolean`));
|
|
322
|
+
|
|
323
|
+
// -----------------------------------------------------------------------------
|
|
324
|
+
// Expression operands
|
|
325
|
+
// -----------------------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
type Scalar = string | number | boolean;
|
|
328
|
+
|
|
329
|
+
/** A statically known value: none is SQL `NULL`. */
|
|
330
|
+
type Static = Option.Option<Scalar>;
|
|
331
|
+
|
|
332
|
+
/** A field of an operand: statically known or a SQL expression. */
|
|
333
|
+
type F = { readonly s: Static } | { readonly q: string };
|
|
334
|
+
|
|
335
|
+
type C = boolean | string;
|
|
336
|
+
|
|
337
|
+
/** The join condition of two columns of one variable, and their merged column. */
|
|
338
|
+
interface JoinCompat {
|
|
339
|
+
readonly cond: string;
|
|
340
|
+
readonly merged: Col;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Numeric-promotion branches of a comparison, and the class pairs they handle. */
|
|
344
|
+
interface Promotions {
|
|
345
|
+
readonly branches: Array<readonly [C, string]>;
|
|
346
|
+
readonly handled: MutableHashSet.MutableHashSet<string>;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
interface Fields {
|
|
350
|
+
/** Possible comparison classes (none = any). */
|
|
351
|
+
readonly classes: Option.Option<ReadonlyArray<number>>;
|
|
352
|
+
readonly isNull: F;
|
|
353
|
+
readonly cls: F;
|
|
354
|
+
readonly kind: F;
|
|
355
|
+
readonly value: F;
|
|
356
|
+
readonly datatype: F;
|
|
357
|
+
readonly lang: F;
|
|
358
|
+
readonly dir: F;
|
|
359
|
+
readonly num: F;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
type Operand =
|
|
363
|
+
/** A term-valued expression (`id` is its term id). */
|
|
364
|
+
| (Fields & { readonly tag: 'term'; readonly id: F })
|
|
365
|
+
/** A constant term. */
|
|
366
|
+
| (Fields & { readonly tag: 'const'; readonly id: F; readonly term: Term.GroundTerm })
|
|
367
|
+
/** A boolean (`bool` is its SQL boolean). */
|
|
368
|
+
| (Fields & { readonly tag: 'bool'; readonly bool: string })
|
|
369
|
+
| (Fields & { readonly tag: 'int' })
|
|
370
|
+
| (Fields & { readonly tag: 'str' })
|
|
371
|
+
| (Fields & { readonly tag: 'iri' })
|
|
372
|
+
| (Fields & { readonly tag: 'null' });
|
|
373
|
+
|
|
374
|
+
/** The term id of a term-valued operand. */
|
|
375
|
+
const operandId = (o: Operand): Option.Option<F> =>
|
|
376
|
+
o.tag === 'term' || o.tag === 'const' ? Option.some(o.id) : Option.none();
|
|
377
|
+
|
|
378
|
+
const S = (s: Scalar): F => ({ s: Option.some(s) });
|
|
379
|
+
const SO = (s: Static): F => ({ s });
|
|
380
|
+
const SNULL: F = { s: Option.none() };
|
|
381
|
+
const Q = (q: string): F => ({ q });
|
|
382
|
+
const isStatic = (f: F): f is { readonly s: Static } => 's' in f;
|
|
383
|
+
|
|
384
|
+
/** The statically known number of a field. */
|
|
385
|
+
const staticNumber = (f: F): Option.Option<number> =>
|
|
386
|
+
isStatic(f) ? Option.filter(f.s, Predicate.isNumber) : Option.none();
|
|
387
|
+
|
|
388
|
+
/** Whether a field is statically the value `v`. */
|
|
389
|
+
const isStaticValue = (f: F, v: Scalar): boolean =>
|
|
390
|
+
isStatic(f) && Option.exists(f.s, (s) => s === v);
|
|
391
|
+
|
|
392
|
+
const RISK_ORDER: ReadonlyArray<readonly [number, number]> = [
|
|
393
|
+
[1, 3],
|
|
394
|
+
[2, 3],
|
|
395
|
+
[8, 1],
|
|
396
|
+
[8, 2],
|
|
397
|
+
[8, 3],
|
|
398
|
+
[8, 8],
|
|
399
|
+
[11, 1],
|
|
400
|
+
[11, 2],
|
|
401
|
+
[11, 3],
|
|
402
|
+
[11, 8],
|
|
403
|
+
[11, 11],
|
|
404
|
+
[7, 7],
|
|
405
|
+
];
|
|
406
|
+
const RISK_EQUAL: ReadonlyArray<readonly [number, number]> = [...RISK_ORDER, [10, 10]];
|
|
407
|
+
|
|
408
|
+
/** The SQL conditions of `cs` (dropping the statically known ones). */
|
|
409
|
+
const conditions = (cs: ReadonlyArray<C>): Array<string> => cs.filter(Predicate.isString);
|
|
410
|
+
|
|
411
|
+
const and = (...cs: ReadonlyArray<C>): C => {
|
|
412
|
+
if (cs.includes(false)) return false;
|
|
413
|
+
const parts = conditions(cs);
|
|
414
|
+
return Arr.match(parts, {
|
|
415
|
+
onEmpty: () => true,
|
|
416
|
+
onNonEmpty: ([first, ...rest]) =>
|
|
417
|
+
Arr.isReadonlyArrayEmpty(rest) ? first : `(${parts.join(' AND ')})`,
|
|
418
|
+
});
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const or = (...cs: ReadonlyArray<C>): C => {
|
|
422
|
+
if (cs.includes(true)) return true;
|
|
423
|
+
const parts = conditions(cs);
|
|
424
|
+
return Arr.match(parts, {
|
|
425
|
+
onEmpty: () => false,
|
|
426
|
+
onNonEmpty: ([first, ...rest]) =>
|
|
427
|
+
Arr.isReadonlyArrayEmpty(rest) ? first : `(${parts.join(' OR ')})`,
|
|
428
|
+
});
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const condSql = (c: C): string => (c === true ? 'TRUE' : c === false ? 'FALSE' : c);
|
|
432
|
+
|
|
433
|
+
const isLive = (branch: readonly [C, string]): branch is readonly [string, string] =>
|
|
434
|
+
Predicate.isString(branch[0]);
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* A `CASE` of the branches up to the first that always holds (whose value is
|
|
438
|
+
* then the `ELSE`), dropping the branches that never hold.
|
|
439
|
+
*/
|
|
440
|
+
const caseOf = (branches: ReadonlyArray<readonly [C, string]>, otherwise: string): string => {
|
|
441
|
+
const fallback = Option.match(
|
|
442
|
+
Arr.findFirst(branches, ([c]) => c === true),
|
|
443
|
+
{ onNone: () => otherwise, onSome: ([, v]) => v },
|
|
444
|
+
);
|
|
445
|
+
const live = Arr.takeWhile(branches, ([c]) => c !== true).filter(isLive);
|
|
446
|
+
if (Arr.isReadonlyArrayEmpty(live)) return fallback;
|
|
447
|
+
return `(CASE ${live.map(([w, r]) => `WHEN ${w} THEN ${r}`).join(' ')} ELSE ${fallback} END)`;
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// -----------------------------------------------------------------------------
|
|
451
|
+
// Compiler
|
|
452
|
+
// -----------------------------------------------------------------------------
|
|
453
|
+
|
|
454
|
+
interface Env {
|
|
455
|
+
readonly cols: Cols;
|
|
456
|
+
readonly graph: GraphCtx;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** A query-local term and its (negative) id. */
|
|
460
|
+
interface Local {
|
|
461
|
+
readonly id: number;
|
|
462
|
+
readonly term: Term.GroundTerm;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Query-local terms by key. */
|
|
466
|
+
type Locals = MutableHashMap.MutableHashMap<string, Local>;
|
|
467
|
+
|
|
468
|
+
/** An aggregate of a `GROUP`: the SQL of its value over the grouped subquery `s`. */
|
|
469
|
+
interface Aggregate {
|
|
470
|
+
readonly target: string;
|
|
471
|
+
readonly sql: (s: string) => string;
|
|
472
|
+
readonly col: Omit<Col, 'sql'>;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** A group key variable (`source`, bound to `col`) and the variable it is projected as. */
|
|
476
|
+
interface GroupKey {
|
|
477
|
+
readonly source: string;
|
|
478
|
+
readonly target: string;
|
|
479
|
+
readonly col: Col;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** The input of a `GRAPH ?var` and the column of its graph ids. */
|
|
483
|
+
interface GraphInput {
|
|
484
|
+
readonly input: Block;
|
|
485
|
+
readonly gcol: Col;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/** A variable of both sides of a `MINUS`, with its column on each side. */
|
|
489
|
+
interface Shared {
|
|
490
|
+
readonly name: string;
|
|
491
|
+
readonly left: Col;
|
|
492
|
+
readonly right: Col;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
class Compiler {
|
|
496
|
+
readonly dialect: Dialect.Dialect;
|
|
497
|
+
readonly T: string;
|
|
498
|
+
readonly Q: string;
|
|
499
|
+
readonly G: string;
|
|
500
|
+
counter = 0;
|
|
501
|
+
readonly colNames = MutableHashMap.empty<string, string>();
|
|
502
|
+
readonly params: Array<unknown> = [];
|
|
503
|
+
readonly paramIndex = MutableHashMap.empty<string, number>();
|
|
504
|
+
readonly risks: Array<Risk> = [];
|
|
505
|
+
readonly localClasses = new Set<number>();
|
|
506
|
+
|
|
507
|
+
readonly context: TranslationContext;
|
|
508
|
+
readonly resolve: (term: Term.GroundTerm) => Option.Option<number>;
|
|
509
|
+
/** Query-local terms, by key (stable across the two compilation passes). */
|
|
510
|
+
readonly locals: Locals;
|
|
511
|
+
/** Whether the query-local terms are complete (second pass). */
|
|
512
|
+
readonly localsKnown: boolean;
|
|
513
|
+
|
|
514
|
+
constructor(
|
|
515
|
+
context: TranslationContext,
|
|
516
|
+
resolve: (term: Term.GroundTerm) => Option.Option<number>,
|
|
517
|
+
locals: Locals,
|
|
518
|
+
localsKnown: boolean,
|
|
519
|
+
) {
|
|
520
|
+
this.context = context;
|
|
521
|
+
this.resolve = resolve;
|
|
522
|
+
this.locals = locals;
|
|
523
|
+
this.localsKnown = localsKnown;
|
|
524
|
+
this.dialect = context.dialect;
|
|
525
|
+
this.T = context.tables.terms;
|
|
526
|
+
this.Q = context.tables.quads;
|
|
527
|
+
this.G = context.tables.graphs;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
alias(prefix: string): string {
|
|
531
|
+
return `${prefix}${this.counter++}`;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
col(name: string): string {
|
|
535
|
+
return Option.getOrElse(MutableHashMap.get(this.colNames, name), () => {
|
|
536
|
+
const c = `c${MutableHashMap.size(this.colNames)}`;
|
|
537
|
+
MutableHashMap.set(this.colNames, name, c);
|
|
538
|
+
return c;
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
param(value: string): string {
|
|
543
|
+
const i = Option.getOrElse(MutableHashMap.get(this.paramIndex, value), () => {
|
|
544
|
+
this.params.push(value);
|
|
545
|
+
const n = this.params.length;
|
|
546
|
+
MutableHashMap.set(this.paramIndex, value, n);
|
|
547
|
+
return n;
|
|
548
|
+
});
|
|
549
|
+
return this.dialect.castText(`\ue000${i}\ue000`);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
lit(v: Scalar): string {
|
|
553
|
+
if (Predicate.isBoolean(v)) return v ? 'TRUE' : 'FALSE';
|
|
554
|
+
if (Predicate.isNumber(v)) {
|
|
555
|
+
if (!Number.isFinite(v)) return 'NULL';
|
|
556
|
+
return Number.isInteger(v) && Math.abs(v) < 1e15
|
|
557
|
+
? String(v)
|
|
558
|
+
: this.dialect.castDouble(String(v));
|
|
559
|
+
}
|
|
560
|
+
return this.param(v);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** A statically known value as SQL (none is `NULL`). */
|
|
564
|
+
litOf(v: Static): string {
|
|
565
|
+
return Option.match(v, { onNone: () => 'NULL', onSome: (s) => this.lit(s) });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
f(field: F): string {
|
|
569
|
+
return isStatic(field) ? this.litOf(field.s) : field.q;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** The id of a constant term (a query-local negative id if it is not in the dictionary). */
|
|
573
|
+
idOf(term: Term.GroundTerm | Term.DefaultGraph): number {
|
|
574
|
+
if (Term.isDefaultGraph(term)) return 0;
|
|
575
|
+
return Option.getOrElse(this.resolve(term), () => this.localId(term));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** The query-local id of a term that is not in the dictionary. */
|
|
579
|
+
localId(term: Term.GroundTerm): number {
|
|
580
|
+
const local = Option.getOrElse(MutableHashMap.get(this.locals, term.key), () => {
|
|
581
|
+
const created: Local = { id: -(MutableHashMap.size(this.locals) + 1), term };
|
|
582
|
+
MutableHashMap.set(this.locals, term.key, created);
|
|
583
|
+
return created;
|
|
584
|
+
});
|
|
585
|
+
this.localClasses.add(Enc.classify(term).cls);
|
|
586
|
+
return local.id;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// --- rendering --------------------------------------------------------------
|
|
590
|
+
|
|
591
|
+
from(items: ReadonlyArray<Item>): string {
|
|
592
|
+
if (!Arr.isReadonlyArrayNonEmpty(items)) return '';
|
|
593
|
+
const [first, ...rest] = items;
|
|
594
|
+
const joins = rest.map((it) =>
|
|
595
|
+
Option.match(it.left, {
|
|
596
|
+
onNone: () => ` JOIN ${it.source} ${it.alias} ON TRUE`,
|
|
597
|
+
onSome: (on) => ` LEFT JOIN ${it.source} ${it.alias} ON ${on}`,
|
|
598
|
+
}),
|
|
599
|
+
);
|
|
600
|
+
return ` FROM ${first.source} ${first.alias}${joins.join('')}`;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
whereClause(where: ReadonlyArray<string>): string {
|
|
604
|
+
return Arr.isReadonlyArrayEmpty(where) ? '' : ` WHERE ${where.join(' AND ')}`;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
nullCol(type: 'id' | 'int'): string {
|
|
608
|
+
return type === 'id' ? this.dialect.nullId : this.dialect.castId('NULL');
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
render(
|
|
612
|
+
block: Block,
|
|
613
|
+
names: ReadonlyArray<string> = Arr.fromIterable(MutableHashMap.keys(block.cols)),
|
|
614
|
+
options: {
|
|
615
|
+
readonly distinct?: boolean;
|
|
616
|
+
readonly types?: MutableHashMap.MutableHashMap<string, 'id' | 'int'>;
|
|
617
|
+
} = {},
|
|
618
|
+
): string {
|
|
619
|
+
const { types } = options;
|
|
620
|
+
const select = names.map((n) => {
|
|
621
|
+
const sql = Option.match(MutableHashMap.get(block.cols, n), {
|
|
622
|
+
onNone: () =>
|
|
623
|
+
this.nullCol(
|
|
624
|
+
types === undefined
|
|
625
|
+
? 'id'
|
|
626
|
+
: Option.getOrElse(MutableHashMap.get(types, n), () => 'id' as const),
|
|
627
|
+
),
|
|
628
|
+
onSome: (c) => c.sql,
|
|
629
|
+
});
|
|
630
|
+
return `${sql} AS ${this.col(n)}`;
|
|
631
|
+
});
|
|
632
|
+
return `SELECT ${options.distinct === true ? 'DISTINCT ' : ''}${Arr.isReadonlyArrayEmpty(select) ? '1 AS one' : select.join(', ')}${this.from(block.items)}${this.whereClause(block.where)}`;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Wraps SQL as a subquery item. */
|
|
636
|
+
wrapSql(sql: string, cols: ColTypes): Wrapped {
|
|
637
|
+
const alias = this.alias('s');
|
|
638
|
+
const out: Cols = MutableHashMap.empty();
|
|
639
|
+
MutableHashMap.forEach(cols, (c, n) => {
|
|
640
|
+
MutableHashMap.set(out, n, { ...c, sql: `${alias}.${this.col(n)}` });
|
|
641
|
+
});
|
|
642
|
+
return { items: [inner(`(${sql})`, alias)], where: [], cols: out };
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
wrap(block: Block, options: { readonly distinct?: boolean } = {}): Wrapped {
|
|
646
|
+
return this.wrapSql(this.render(block, undefined, options), block.cols);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// --- joins ------------------------------------------------------------------
|
|
650
|
+
|
|
651
|
+
compat(a: Col, b: Col): JoinCompat {
|
|
652
|
+
if (a.type !== b.type) return unsupported('Joining integer and term columns');
|
|
653
|
+
if (!a.nullable && !b.nullable) return { cond: `${a.sql} = ${b.sql}`, merged: a };
|
|
654
|
+
if (!a.nullable) return { cond: `(${b.sql} IS NULL OR ${b.sql} = ${a.sql})`, merged: a };
|
|
655
|
+
if (!b.nullable) return { cond: `(${a.sql} IS NULL OR ${a.sql} = ${b.sql})`, merged: b };
|
|
656
|
+
return {
|
|
657
|
+
cond: `(${a.sql} IS NULL OR ${b.sql} IS NULL OR ${a.sql} = ${b.sql})`,
|
|
658
|
+
merged: { sql: `COALESCE(${a.sql}, ${b.sql})`, nullable: true, type: a.type },
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
join(a: Block, b: Block): Block {
|
|
663
|
+
if (isUnit(b)) return a;
|
|
664
|
+
if (isUnit(a)) return b;
|
|
665
|
+
const cols = MutableHashMap.fromIterable(a.cols);
|
|
666
|
+
const where = [...a.where, ...b.where];
|
|
667
|
+
MutableHashMap.forEach(b.cols, (cb, n) =>
|
|
668
|
+
Option.match(MutableHashMap.get(cols, n), {
|
|
669
|
+
onNone: () => {
|
|
670
|
+
MutableHashMap.set(cols, n, cb);
|
|
671
|
+
},
|
|
672
|
+
onSome: (ca) => {
|
|
673
|
+
const { cond, merged } = this.compat(ca, cb);
|
|
674
|
+
where.push(cond);
|
|
675
|
+
MutableHashMap.set(cols, n, merged);
|
|
676
|
+
},
|
|
677
|
+
}),
|
|
678
|
+
);
|
|
679
|
+
return { items: [...a.items, ...b.items], where, cols };
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** Ensures a block has a FROM item (so that a LEFT JOIN can be appended). */
|
|
683
|
+
anchored(block: Block): Block {
|
|
684
|
+
if (Arr.isReadonlyArrayNonEmpty(block.items)) return block;
|
|
685
|
+
return { ...block, items: [inner('(SELECT 1 AS one)', this.alias('u'))] };
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// --- operators --------------------------------------------------------------
|
|
689
|
+
|
|
690
|
+
op(operation: Algebra.Operation, g: GraphCtx, unit: Block): Block {
|
|
691
|
+
return Match.value(operation).pipe(
|
|
692
|
+
Match.tag('Bgp', (op) =>
|
|
693
|
+
Arr.isReadonlyArrayEmpty(op.patterns) ? unit : this.bgp(op.patterns, g),
|
|
694
|
+
),
|
|
695
|
+
Match.tag('Join', (op) => this.join(this.op(op.left, g, unit), this.op(op.right, g, unit))),
|
|
696
|
+
Match.tag('LeftJoin', (op) => this.leftJoin(op, g, unit)),
|
|
697
|
+
Match.tag('Filter', (op) => {
|
|
698
|
+
const input = this.op(op.input, g, unit);
|
|
699
|
+
const cond = this.ebv(this.operand(op.expression, { cols: input.cols, graph: g }));
|
|
700
|
+
if (cond === 'TRUE') return input;
|
|
701
|
+
return { ...input, where: [...input.where, cond] };
|
|
702
|
+
}),
|
|
703
|
+
Match.tag('Union', (op) => this.union(this.op(op.left, g, unit), this.op(op.right, g, unit))),
|
|
704
|
+
Match.tag('Graph', (op) => this.graph(op, g, unit)),
|
|
705
|
+
Match.tag('Extend', (op) => {
|
|
706
|
+
const input = this.op(op.input, g, unit);
|
|
707
|
+
if (MutableHashMap.has(input.cols, op.variable.value)) {
|
|
708
|
+
return unsupported('BIND to a bound variable');
|
|
709
|
+
}
|
|
710
|
+
const col = this.termValued(this.operand(op.expression, { cols: input.cols, graph: g }));
|
|
711
|
+
const cols = MutableHashMap.fromIterable(input.cols);
|
|
712
|
+
MutableHashMap.set(cols, op.variable.value, col);
|
|
713
|
+
return { ...input, cols };
|
|
714
|
+
}),
|
|
715
|
+
Match.tag('Minus', (op) => this.minus(this.op(op.left, g, unit), this.op(op.right, g, unit))),
|
|
716
|
+
Match.tag('Group', (op) => this.group(op, g, unit)),
|
|
717
|
+
Match.tag('Project', (op) => {
|
|
718
|
+
const input = this.op(op.input, g, unit);
|
|
719
|
+
const keep = MutableHashSet.fromIterable(op.variables.map((v) => v.value));
|
|
720
|
+
const cols: Cols = MutableHashMap.empty();
|
|
721
|
+
MutableHashMap.forEach(input.cols, (c, n) => {
|
|
722
|
+
if (MutableHashSet.has(keep, n) || isHidden(n)) MutableHashMap.set(cols, n, c);
|
|
723
|
+
});
|
|
724
|
+
return { ...input, cols };
|
|
725
|
+
}),
|
|
726
|
+
Match.tag('Distinct', (op) => this.wrap(this.op(op.input, g, unit), { distinct: true })),
|
|
727
|
+
Match.tag('Reduced', (op) => this.op(op.input, g, unit)),
|
|
728
|
+
Match.tag('Slice', (op) => {
|
|
729
|
+
if (g.kind === 'var') return unsupported('Slice inside GRAPH ?var');
|
|
730
|
+
const input = this.op(op.input, g, unit);
|
|
731
|
+
return this.wrapSql(
|
|
732
|
+
`${this.render(input)}${this.dialect.limitOffset(op.limit, op.offset)}`,
|
|
733
|
+
input.cols,
|
|
734
|
+
);
|
|
735
|
+
}),
|
|
736
|
+
Match.tag('Values', (op) => {
|
|
737
|
+
// Like the engine, inline data does not see the context solution.
|
|
738
|
+
return this.values(op);
|
|
739
|
+
}),
|
|
740
|
+
Match.tag('OrderBy', () => unsupported('ORDER BY is evaluated by the engine')),
|
|
741
|
+
Match.tag('Path', () => unsupported('Property paths are evaluated by the engine')),
|
|
742
|
+
Match.tag('Service', () => unsupported('SERVICE is evaluated by the engine')),
|
|
743
|
+
Match.exhaustive,
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// --- BGP --------------------------------------------------------------------
|
|
748
|
+
|
|
749
|
+
bgp(patterns: ReadonlyArray<Ast.TriplePattern>, ctx: GraphCtx): Block {
|
|
750
|
+
const items: Array<Item> = [];
|
|
751
|
+
const where: Array<string> = [];
|
|
752
|
+
const cols: Cols = MutableHashMap.empty();
|
|
753
|
+
const bind = (name: string, sql: string) =>
|
|
754
|
+
Option.match(MutableHashMap.get(cols, name), {
|
|
755
|
+
onNone: () => {
|
|
756
|
+
MutableHashMap.set(cols, name, { sql, nullable: false, type: 'id' });
|
|
757
|
+
},
|
|
758
|
+
onSome: (c) => {
|
|
759
|
+
where.push(`${c.sql} = ${sql}`);
|
|
760
|
+
},
|
|
761
|
+
});
|
|
762
|
+
const position = (t: Ast.TermPattern, sql: string, bound: Array<string>): void => {
|
|
763
|
+
return Match.value(t).pipe(
|
|
764
|
+
Match.tag('Variable', (pattern) => {
|
|
765
|
+
bind(pattern.value, sql);
|
|
766
|
+
return;
|
|
767
|
+
}),
|
|
768
|
+
Match.tag('BlankNode', () => unsupported('Blank node in a pattern')),
|
|
769
|
+
Match.tag('NamedNode', 'Literal', (pattern) => {
|
|
770
|
+
bound.push(`${sql} = ${this.idOf(pattern)}`);
|
|
771
|
+
return;
|
|
772
|
+
}),
|
|
773
|
+
Match.tag('Triple', (pattern) => {
|
|
774
|
+
if (isGroundTriple(pattern)) {
|
|
775
|
+
bound.push(`${sql} = ${this.idOf(pattern)}`);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const ta = this.alias('t');
|
|
779
|
+
items.push(inner(this.T, ta));
|
|
780
|
+
where.push(`${ta}.id = ${sql}`, `${ta}.kind = ${Enc.Kind.Triple}`);
|
|
781
|
+
position(pattern.subject, `${ta}.sub`, where);
|
|
782
|
+
position(pattern.predicate, `${ta}.pre`, where);
|
|
783
|
+
position(pattern.object, `${ta}.obj`, where);
|
|
784
|
+
return;
|
|
785
|
+
}),
|
|
786
|
+
Match.exhaustive,
|
|
787
|
+
);
|
|
788
|
+
};
|
|
789
|
+
const { defaultGraph } = this.context.dataset;
|
|
790
|
+
patterns.forEach((p) => {
|
|
791
|
+
const a = this.alias('q');
|
|
792
|
+
const merged =
|
|
793
|
+
ctx.kind === 'default' &&
|
|
794
|
+
(defaultGraph === 'union' || (Array.isArray(defaultGraph) && defaultGraph.length > 1));
|
|
795
|
+
if (merged) {
|
|
796
|
+
// The default graph is a merge of several graphs: match each triple once.
|
|
797
|
+
const conds: Array<string> = [];
|
|
798
|
+
if (!Predicate.isString(defaultGraph)) {
|
|
799
|
+
conds.push(`g IN (${defaultGraph.map((x) => this.idOf(x)).join(', ')})`);
|
|
800
|
+
}
|
|
801
|
+
const deferred: Array<() => void> = [];
|
|
802
|
+
const positionIn = (t: Ast.TermPattern, column: string) => {
|
|
803
|
+
if (Term.isVariable(t) || (Term.isTriple(t) && !Term.isGround(t))) {
|
|
804
|
+
deferred.push(() => position(t, `${a}.${column}`, where));
|
|
805
|
+
} else position(t, column, conds);
|
|
806
|
+
};
|
|
807
|
+
positionIn(p.subject, 's');
|
|
808
|
+
positionIn(p.predicate, 'p');
|
|
809
|
+
positionIn(p.object, 'o');
|
|
810
|
+
items.push(
|
|
811
|
+
inner(
|
|
812
|
+
`(SELECT DISTINCT s, p, o FROM ${this.Q}${Arr.isReadonlyArrayNonEmpty(conds) ? ` WHERE ${conds.join(' AND ')}` : ''})`,
|
|
813
|
+
a,
|
|
814
|
+
),
|
|
815
|
+
);
|
|
816
|
+
deferred.forEach((f) => f());
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
items.push(inner(this.Q, a));
|
|
820
|
+
Match.value(ctx).pipe(
|
|
821
|
+
Match.when({ kind: 'default' }, () => {
|
|
822
|
+
if (defaultGraph === 'default') where.push(`${a}.g = 0`);
|
|
823
|
+
else if (!Predicate.isString(defaultGraph) && Predicate.isTupleOf(defaultGraph, 1)) {
|
|
824
|
+
where.push(`${a}.g = ${this.idOf(defaultGraph[0])}`);
|
|
825
|
+
} else where.push('1 = 0');
|
|
826
|
+
}),
|
|
827
|
+
Match.when({ kind: 'const' }, (graph) => {
|
|
828
|
+
where.push(`${a}.g = ${graph.id}`);
|
|
829
|
+
}),
|
|
830
|
+
Match.when({ kind: 'var' }, (graph) => {
|
|
831
|
+
bind(graph.hidden, `${a}.g`);
|
|
832
|
+
where.push(this.namedGraphCond(`${a}.g`));
|
|
833
|
+
}),
|
|
834
|
+
Match.exhaustive,
|
|
835
|
+
);
|
|
836
|
+
position(p.subject, `${a}.s`, where);
|
|
837
|
+
position(p.predicate, `${a}.p`, where);
|
|
838
|
+
position(p.object, `${a}.o`, where);
|
|
839
|
+
});
|
|
840
|
+
return { items, where, cols };
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/** The condition that the graph id column `g` of a quad is a named graph of the dataset. */
|
|
844
|
+
namedGraphCond(g: string): string {
|
|
845
|
+
const named = this.context.dataset.namedGraphs;
|
|
846
|
+
if (named === 'all') return `${g} <> 0`;
|
|
847
|
+
if (Arr.isReadonlyArrayEmpty(named)) return '1 = 0';
|
|
848
|
+
return `${g} IN (${named.map((n) => this.idOf(n)).join(', ')})`;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/** A subquery listing the named graphs of the dataset (column `g`). */
|
|
852
|
+
namedGraphsSource(): string {
|
|
853
|
+
const named = this.context.dataset.namedGraphs;
|
|
854
|
+
if (named === 'all') {
|
|
855
|
+
return `(SELECT id AS g FROM ${this.G} UNION SELECT DISTINCT g FROM ${this.Q} WHERE g <> 0)`;
|
|
856
|
+
}
|
|
857
|
+
if (Arr.isReadonlyArrayEmpty(named)) return `(SELECT ${this.dialect.nullId} AS g WHERE 1 = 0)`;
|
|
858
|
+
const alias = this.alias('n');
|
|
859
|
+
return `(SELECT column1 AS g FROM (VALUES ${named.map((n) => `(${this.idOf(n)})`).join(', ')}) AS ${alias})`;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// --- other operators ---------------------------------------------------------
|
|
863
|
+
|
|
864
|
+
values(op: Algebra.Values): Block {
|
|
865
|
+
const vars = op.variables.map((v) => v.value);
|
|
866
|
+
if (Arr.isReadonlyArrayEmpty(op.bindings)) {
|
|
867
|
+
const cols = MutableHashMap.fromIterable(
|
|
868
|
+
vars.map((v): readonly [string, Col] => [
|
|
869
|
+
v,
|
|
870
|
+
{ sql: this.dialect.nullId, nullable: true, type: 'id' },
|
|
871
|
+
]),
|
|
872
|
+
);
|
|
873
|
+
return { items: [], where: ['1 = 0'], cols };
|
|
874
|
+
}
|
|
875
|
+
const rows = op.bindings.map((b) =>
|
|
876
|
+
Arr.isReadonlyArrayEmpty(vars)
|
|
877
|
+
? '(1)'
|
|
878
|
+
: `(${vars
|
|
879
|
+
.map((v) => {
|
|
880
|
+
const t = b.map.get(v);
|
|
881
|
+
return t === undefined ? this.dialect.nullId : String(this.idOf(t));
|
|
882
|
+
})
|
|
883
|
+
.join(', ')})`,
|
|
884
|
+
);
|
|
885
|
+
const a = this.alias('v');
|
|
886
|
+
const select = Arr.isReadonlyArrayEmpty(vars)
|
|
887
|
+
? 'column1 AS one'
|
|
888
|
+
: vars.map((v, i) => `column${i + 1} AS ${this.col(v)}`);
|
|
889
|
+
const cols = MutableHashMap.fromIterable(
|
|
890
|
+
vars.map((v): readonly [string, Col] => [
|
|
891
|
+
v,
|
|
892
|
+
{
|
|
893
|
+
sql: `${a}.${this.col(v)}`,
|
|
894
|
+
nullable: !op.bindings.every((b) => b.map.has(v)),
|
|
895
|
+
type: 'id',
|
|
896
|
+
},
|
|
897
|
+
]),
|
|
898
|
+
);
|
|
899
|
+
return {
|
|
900
|
+
items: [
|
|
901
|
+
inner(
|
|
902
|
+
`(SELECT ${Predicate.isString(select) ? select : select.join(', ')} FROM (VALUES ${rows.join(', ')}) AS ${a}_rows)`,
|
|
903
|
+
a,
|
|
904
|
+
),
|
|
905
|
+
],
|
|
906
|
+
where: [],
|
|
907
|
+
cols,
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
leftJoin(op: Algebra.LeftJoin, g: GraphCtx, unit: Block): Block {
|
|
912
|
+
const left = this.op(op.left, g, unit);
|
|
913
|
+
const right = this.op(op.right, g, unit);
|
|
914
|
+
MutableHashMap.forEach(right.cols, (c, n) => {
|
|
915
|
+
const leftNullable = Option.match(MutableHashMap.get(left.cols, n), {
|
|
916
|
+
onNone: () => true,
|
|
917
|
+
onSome: (l) => l.nullable,
|
|
918
|
+
});
|
|
919
|
+
if (isHidden(n) && leftNullable && !c.nullable) {
|
|
920
|
+
unsupported('OPTIONAL graph-dependent pattern under GRAPH ?var');
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
if (op.expression === undefined && isUnit(right)) return left;
|
|
924
|
+
const a = this.anchored(left);
|
|
925
|
+
const r = this.wrap(right);
|
|
926
|
+
const conds: Array<string> = [];
|
|
927
|
+
const merged = MutableHashMap.fromIterable(a.cols);
|
|
928
|
+
const out = MutableHashMap.fromIterable(a.cols);
|
|
929
|
+
MutableHashMap.forEach(r.cols, (cr, n) =>
|
|
930
|
+
Option.match(MutableHashMap.get(a.cols, n), {
|
|
931
|
+
onNone: () => {
|
|
932
|
+
MutableHashMap.set(merged, n, cr);
|
|
933
|
+
MutableHashMap.set(out, n, { ...cr, nullable: true });
|
|
934
|
+
},
|
|
935
|
+
onSome: (ca) => {
|
|
936
|
+
const { cond, merged: m } = this.compat(ca, cr);
|
|
937
|
+
conds.push(cond);
|
|
938
|
+
MutableHashMap.set(merged, n, m);
|
|
939
|
+
MutableHashMap.set(
|
|
940
|
+
out,
|
|
941
|
+
n,
|
|
942
|
+
ca.nullable
|
|
943
|
+
? { sql: `COALESCE(${ca.sql}, ${cr.sql})`, nullable: true, type: ca.type }
|
|
944
|
+
: ca,
|
|
945
|
+
);
|
|
946
|
+
},
|
|
947
|
+
}),
|
|
948
|
+
);
|
|
949
|
+
if (op.expression !== undefined) {
|
|
950
|
+
const e = this.ebv(this.operand(op.expression, { cols: merged, graph: g }));
|
|
951
|
+
if (e !== 'TRUE') conds.push(e);
|
|
952
|
+
}
|
|
953
|
+
const on = Arr.isReadonlyArrayEmpty(conds) ? 'TRUE' : conds.join(' AND ');
|
|
954
|
+
const [item] = r.items;
|
|
955
|
+
return {
|
|
956
|
+
items: [...a.items, { source: item.source, alias: item.alias, left: Option.some(on) }],
|
|
957
|
+
where: a.where,
|
|
958
|
+
cols: out,
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
union(a: Block, b: Block): Block {
|
|
963
|
+
const names = distinct([...MutableHashMap.keys(a.cols), ...MutableHashMap.keys(b.cols)]);
|
|
964
|
+
const cols: ColTypes = MutableHashMap.empty();
|
|
965
|
+
const types = MutableHashMap.empty<string, 'id' | 'int'>();
|
|
966
|
+
names.forEach((n) => {
|
|
967
|
+
const ca = MutableHashMap.get(a.cols, n);
|
|
968
|
+
const cb = MutableHashMap.get(b.cols, n);
|
|
969
|
+
if (Option.isSome(ca) && Option.isSome(cb) && ca.value.type !== cb.value.type) {
|
|
970
|
+
unsupported('UNION of integer and term columns');
|
|
971
|
+
}
|
|
972
|
+
const nullable =
|
|
973
|
+
Option.isNone(ca) || Option.isNone(cb) || ca.value.nullable || cb.value.nullable;
|
|
974
|
+
if (isHidden(n) && nullable) {
|
|
975
|
+
unsupported('UNION of graph-dependent and independent patterns under GRAPH ?var');
|
|
976
|
+
}
|
|
977
|
+
// `n` is a column of `a` or of `b`.
|
|
978
|
+
const type = Option.isSome(ca) ? ca.value.type : Option.isSome(cb) ? cb.value.type : 'id';
|
|
979
|
+
MutableHashMap.set(types, n, type);
|
|
980
|
+
MutableHashMap.set(cols, n, { nullable, type });
|
|
981
|
+
});
|
|
982
|
+
const sql = `${this.render(a, names, { types })} UNION ALL ${this.render(b, names, { types })}`;
|
|
983
|
+
return this.wrapSql(sql, cols);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
minus(a: Block, b: Block): Block {
|
|
987
|
+
const bNames = Arr.fromIterable(MutableHashMap.keys(b.cols));
|
|
988
|
+
const shared = bNames.filter((n) => MutableHashMap.has(a.cols, n));
|
|
989
|
+
if (bNames.some((n) => isHidden(n) && !MutableHashMap.has(a.cols, n))) {
|
|
990
|
+
return unsupported('MINUS of a graph-dependent pattern under GRAPH ?var');
|
|
991
|
+
}
|
|
992
|
+
const overlap = shared.filter((n) => !isHidden(n));
|
|
993
|
+
if (Arr.isReadonlyArrayEmpty(overlap)) return a;
|
|
994
|
+
const m = this.wrap(b);
|
|
995
|
+
const pairs = Arr.filterMap(shared, (name): Result.Result<Shared, void> => {
|
|
996
|
+
const left = MutableHashMap.get(a.cols, name);
|
|
997
|
+
const right = MutableHashMap.get(m.cols, name);
|
|
998
|
+
return Option.isSome(left) && Option.isSome(right)
|
|
999
|
+
? Result.succeed({ name, left: left.value, right: right.value })
|
|
1000
|
+
: Result.failVoid;
|
|
1001
|
+
});
|
|
1002
|
+
const conds = pairs.map(({ left, right }) => this.compat(left, right).cond);
|
|
1003
|
+
const overlapCond = or(
|
|
1004
|
+
...pairs
|
|
1005
|
+
.filter(({ name }) => !isHidden(name))
|
|
1006
|
+
.map(({ left, right }) =>
|
|
1007
|
+
and(
|
|
1008
|
+
left.nullable ? `${left.sql} IS NOT NULL` : true,
|
|
1009
|
+
right.nullable ? `${right.sql} IS NOT NULL` : true,
|
|
1010
|
+
),
|
|
1011
|
+
),
|
|
1012
|
+
);
|
|
1013
|
+
if (overlapCond === false) return a;
|
|
1014
|
+
if (overlapCond !== true) conds.push(overlapCond);
|
|
1015
|
+
const [item] = m.items;
|
|
1016
|
+
return {
|
|
1017
|
+
...a,
|
|
1018
|
+
where: [
|
|
1019
|
+
...a.where,
|
|
1020
|
+
`NOT EXISTS (SELECT 1 FROM ${item.source} ${item.alias} WHERE ${conds.join(' AND ')})`,
|
|
1021
|
+
],
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
graph(op: Algebra.Graph, g: GraphCtx, unit: Block): Block {
|
|
1026
|
+
const name = op.name;
|
|
1027
|
+
if (Term.isNamedNode(name)) {
|
|
1028
|
+
const id = this.idOf(name);
|
|
1029
|
+
const input = this.op(op.input, { kind: 'const', id }, unit);
|
|
1030
|
+
const named = this.context.dataset.namedGraphs;
|
|
1031
|
+
const cond =
|
|
1032
|
+
named === 'all'
|
|
1033
|
+
? `(EXISTS (SELECT 1 FROM ${this.G} WHERE id = ${id}) OR EXISTS (SELECT 1 FROM ${this.Q} WHERE g = ${id}))`
|
|
1034
|
+
: named.some((n) => n.key === name.key)
|
|
1035
|
+
? 'TRUE'
|
|
1036
|
+
: '1 = 0';
|
|
1037
|
+
return cond === 'TRUE' ? input : { ...input, where: [...input.where, cond] };
|
|
1038
|
+
}
|
|
1039
|
+
const hidden = `#${this.alias('g')}`;
|
|
1040
|
+
const body = this.op(op.input, { kind: 'var', hidden }, unit);
|
|
1041
|
+
const bound = MutableHashMap.get(body.cols, hidden);
|
|
1042
|
+
if (Option.isSome(bound) && bound.value.nullable) {
|
|
1043
|
+
return unsupported('Graph-independent solutions under GRAPH ?var');
|
|
1044
|
+
}
|
|
1045
|
+
const { input, gcol } = Option.match(bound, {
|
|
1046
|
+
onSome: (c): GraphInput => ({ input: body, gcol: c }),
|
|
1047
|
+
onNone: (): GraphInput => {
|
|
1048
|
+
const alias = this.alias('n');
|
|
1049
|
+
return {
|
|
1050
|
+
input: { ...body, items: [...body.items, inner(this.namedGraphsSource(), alias)] },
|
|
1051
|
+
gcol: { sql: `${alias}.g`, nullable: false, type: 'id' },
|
|
1052
|
+
};
|
|
1053
|
+
},
|
|
1054
|
+
});
|
|
1055
|
+
const cols = MutableHashMap.fromIterable(input.cols);
|
|
1056
|
+
MutableHashMap.remove(cols, hidden);
|
|
1057
|
+
const where = [...input.where];
|
|
1058
|
+
Option.match(MutableHashMap.get(cols, name.value), {
|
|
1059
|
+
onNone: () => undefined,
|
|
1060
|
+
onSome: (existing) => {
|
|
1061
|
+
where.push(this.compat(existing, gcol).cond);
|
|
1062
|
+
},
|
|
1063
|
+
});
|
|
1064
|
+
MutableHashMap.set(cols, name.value, gcol);
|
|
1065
|
+
void g;
|
|
1066
|
+
return { items: input.items, where, cols };
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
group(op: Algebra.Group, g: GraphCtx, unit: Block): Block {
|
|
1070
|
+
if (g.kind === 'var') return unsupported('GROUP inside GRAPH ?var');
|
|
1071
|
+
const input = this.op(op.input, g, unit);
|
|
1072
|
+
const needed = MutableHashSet.empty<string>();
|
|
1073
|
+
const keyVars = op.keys.map((k): GroupKey => {
|
|
1074
|
+
const e = k.expression;
|
|
1075
|
+
if (!Term.isVariable(e)) return unsupported('Non-variable group key');
|
|
1076
|
+
const col = Option.getOrElse(MutableHashMap.get(input.cols, e.value), () =>
|
|
1077
|
+
unsupported('Group key is never bound'),
|
|
1078
|
+
);
|
|
1079
|
+
MutableHashSet.add(needed, e.value);
|
|
1080
|
+
return { source: e.value, target: k.variable.value, col };
|
|
1081
|
+
});
|
|
1082
|
+
const aggs = op.aggregates.map(({ variable, aggregate }): Aggregate => {
|
|
1083
|
+
const e = aggregate.expression;
|
|
1084
|
+
if (aggregate.name === 'COUNT') {
|
|
1085
|
+
if (e === '*') {
|
|
1086
|
+
if (aggregate.distinct) return unsupported('COUNT(DISTINCT *)');
|
|
1087
|
+
return {
|
|
1088
|
+
target: variable.value,
|
|
1089
|
+
sql: () => 'COUNT(*)',
|
|
1090
|
+
col: { nullable: false, type: 'int' },
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
if (!Term.isVariable(e)) return unsupported('COUNT of an expression');
|
|
1094
|
+
const name = e.value;
|
|
1095
|
+
if (!MutableHashMap.has(input.cols, name)) {
|
|
1096
|
+
return { target: variable.value, sql: () => '0', col: { nullable: false, type: 'int' } };
|
|
1097
|
+
}
|
|
1098
|
+
MutableHashSet.add(needed, name);
|
|
1099
|
+
return {
|
|
1100
|
+
target: variable.value,
|
|
1101
|
+
sql: (s) => `COUNT(${aggregate.distinct ? 'DISTINCT ' : ''}${s}.${this.col(name)})`,
|
|
1102
|
+
col: { nullable: false, type: 'int' },
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
if (aggregate.name === 'SAMPLE' && Term.isVariable(e)) {
|
|
1106
|
+
const name = e.value;
|
|
1107
|
+
return Option.match(MutableHashMap.get(input.cols, name), {
|
|
1108
|
+
onNone: (): Aggregate => ({
|
|
1109
|
+
target: variable.value,
|
|
1110
|
+
sql: () => this.dialect.nullId,
|
|
1111
|
+
col: { nullable: true, type: 'id' },
|
|
1112
|
+
}),
|
|
1113
|
+
onSome: (c): Aggregate => {
|
|
1114
|
+
MutableHashSet.add(needed, name);
|
|
1115
|
+
return {
|
|
1116
|
+
target: variable.value,
|
|
1117
|
+
sql: (s) => `MIN(${s}.${this.col(name)})`,
|
|
1118
|
+
col: { nullable: true, type: c.type },
|
|
1119
|
+
};
|
|
1120
|
+
},
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
return unsupported(`Aggregate ${aggregate.name}`);
|
|
1124
|
+
});
|
|
1125
|
+
const s = this.alias('a');
|
|
1126
|
+
const cols: ColTypes = MutableHashMap.empty();
|
|
1127
|
+
const select = [
|
|
1128
|
+
...keyVars.map((k) => {
|
|
1129
|
+
const sql = `${s}.${this.col(k.source)} AS ${this.col(k.target)}`;
|
|
1130
|
+
MutableHashMap.set(cols, k.target, { nullable: k.col.nullable, type: k.col.type });
|
|
1131
|
+
return sql;
|
|
1132
|
+
}),
|
|
1133
|
+
...aggs.map((agg) => {
|
|
1134
|
+
const sql = `${agg.sql(s)} AS ${this.col(agg.target)}`;
|
|
1135
|
+
MutableHashMap.set(cols, agg.target, agg.col);
|
|
1136
|
+
return sql;
|
|
1137
|
+
}),
|
|
1138
|
+
];
|
|
1139
|
+
const innerSql = this.render(input, Arr.fromIterable(needed));
|
|
1140
|
+
const groupBy = Arr.isReadonlyArrayEmpty(keyVars)
|
|
1141
|
+
? ''
|
|
1142
|
+
: ` GROUP BY ${distinct(keyVars.map((k) => `${s}.${this.col(k.source)}`)).join(', ')}`;
|
|
1143
|
+
const sql = `SELECT ${Arr.isReadonlyArrayEmpty(select) ? '1 AS one' : select.join(', ')} FROM (${innerSql}) ${s}${groupBy}`;
|
|
1144
|
+
return this.wrapSql(sql, cols);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// --- expressions ------------------------------------------------------------
|
|
1148
|
+
|
|
1149
|
+
/** A dictionary attribute of a term id expression. */
|
|
1150
|
+
attr(id: string, field: 'kind' | 'value' | 'datatype' | 'lang' | 'dir' | 'cls' | 'num'): string {
|
|
1151
|
+
const lookup = `(SELECT ${field} FROM ${this.T} WHERE id = ${id})`;
|
|
1152
|
+
if (MutableHashMap.isEmpty(this.locals)) return lookup;
|
|
1153
|
+
if (!this.localsKnown) return lookup;
|
|
1154
|
+
const branches = Arr.fromIterable(MutableHashMap.values(this.locals)).map(
|
|
1155
|
+
({ id: localId, term }) => {
|
|
1156
|
+
const row = Enc.encode(term, [0, 0, 0]);
|
|
1157
|
+
const v = field === 'num' ? this.litOf(row.num) : this.lit(row[field]);
|
|
1158
|
+
// Typed values, so that the branches agree with the dictionary column.
|
|
1159
|
+
const typed =
|
|
1160
|
+
field === 'num'
|
|
1161
|
+
? this.dialect.castDouble(v)
|
|
1162
|
+
: field === 'kind' || field === 'cls'
|
|
1163
|
+
? this.dialect.castInt(v)
|
|
1164
|
+
: this.dialect.castText(v);
|
|
1165
|
+
return `WHEN ${localId} THEN ${typed}`;
|
|
1166
|
+
},
|
|
1167
|
+
);
|
|
1168
|
+
return `(CASE WHEN ${id} < 0 THEN (CASE ${id} ${branches.join(' ')} END) ELSE ${lookup} END)`;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
termOperand(id: string, nullable: boolean): Operand {
|
|
1172
|
+
return {
|
|
1173
|
+
tag: 'term',
|
|
1174
|
+
classes: Option.none(),
|
|
1175
|
+
isNull: nullable ? Q(`${id} IS NULL`) : S(false),
|
|
1176
|
+
cls: Q(this.attr(id, 'cls')),
|
|
1177
|
+
kind: Q(this.attr(id, 'kind')),
|
|
1178
|
+
value: Q(this.attr(id, 'value')),
|
|
1179
|
+
datatype: Q(this.attr(id, 'datatype')),
|
|
1180
|
+
lang: Q(this.attr(id, 'lang')),
|
|
1181
|
+
dir: Q(this.attr(id, 'dir')),
|
|
1182
|
+
num: Q(this.attr(id, 'num')),
|
|
1183
|
+
id: Q(id),
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
constOperand(constant: Term.GroundTerm): Operand {
|
|
1188
|
+
const { cls, num } = Enc.classify(constant);
|
|
1189
|
+
const base = {
|
|
1190
|
+
tag: 'const' as const,
|
|
1191
|
+
classes: Option.some([cls]),
|
|
1192
|
+
isNull: S(false),
|
|
1193
|
+
cls: S(cls),
|
|
1194
|
+
num: SO(num),
|
|
1195
|
+
id: S(this.idOf(constant)),
|
|
1196
|
+
term: constant,
|
|
1197
|
+
};
|
|
1198
|
+
return Match.value(constant).pipe(
|
|
1199
|
+
Match.withReturnType<Operand>(),
|
|
1200
|
+
Match.tag('NamedNode', 'BlankNode', (term) => ({
|
|
1201
|
+
...base,
|
|
1202
|
+
kind: S(Term.isNamedNode(term) ? Enc.Kind.NamedNode : Enc.Kind.BlankNode),
|
|
1203
|
+
value: S(term.value),
|
|
1204
|
+
datatype: S(''),
|
|
1205
|
+
lang: S(''),
|
|
1206
|
+
dir: S(''),
|
|
1207
|
+
})),
|
|
1208
|
+
Match.tag('Literal', (term) => ({
|
|
1209
|
+
...base,
|
|
1210
|
+
kind: S(Enc.Kind.Literal),
|
|
1211
|
+
value: S(term.value),
|
|
1212
|
+
datatype: S(term.datatype.value),
|
|
1213
|
+
lang: S(term.language),
|
|
1214
|
+
dir: S(term.direction ?? ''),
|
|
1215
|
+
})),
|
|
1216
|
+
Match.tag('Triple', () => ({
|
|
1217
|
+
...base,
|
|
1218
|
+
kind: S(Enc.Kind.Triple),
|
|
1219
|
+
value: SNULL,
|
|
1220
|
+
datatype: S(''),
|
|
1221
|
+
lang: S(''),
|
|
1222
|
+
dir: S(''),
|
|
1223
|
+
})),
|
|
1224
|
+
Match.exhaustive,
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
intOperand(sql: string, nullable: boolean): Operand {
|
|
1229
|
+
return {
|
|
1230
|
+
tag: 'int',
|
|
1231
|
+
classes: Option.some([Enc.Cls.SafeNumber]),
|
|
1232
|
+
isNull: nullable ? Q(`${sql} IS NULL`) : S(false),
|
|
1233
|
+
cls: S(Enc.Cls.SafeNumber),
|
|
1234
|
+
kind: S(Enc.Kind.Literal),
|
|
1235
|
+
value: Q(this.dialect.castText(sql)),
|
|
1236
|
+
datatype: S(`${XSD}integer`),
|
|
1237
|
+
lang: S(''),
|
|
1238
|
+
dir: S(''),
|
|
1239
|
+
num: Q(sql),
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
strOperand(sql: string): Operand {
|
|
1244
|
+
return {
|
|
1245
|
+
tag: 'str',
|
|
1246
|
+
classes: Option.some([Enc.Cls.String]),
|
|
1247
|
+
isNull: Q(`${sql} IS NULL`),
|
|
1248
|
+
cls: S(Enc.Cls.String),
|
|
1249
|
+
kind: S(Enc.Kind.Literal),
|
|
1250
|
+
value: Q(sql),
|
|
1251
|
+
datatype: S(Term.XSD_STRING),
|
|
1252
|
+
lang: S(''),
|
|
1253
|
+
dir: S(''),
|
|
1254
|
+
num: SNULL,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
boolOperand(sql: string): Operand {
|
|
1259
|
+
if (sql === 'TRUE' || sql === 'FALSE') return this.constOperand(sql === 'TRUE' ? TRUE : FALSE);
|
|
1260
|
+
return {
|
|
1261
|
+
tag: 'bool',
|
|
1262
|
+
classes: Option.some([Enc.Cls.Boolean]),
|
|
1263
|
+
isNull: Q(`${sql} IS NULL`),
|
|
1264
|
+
cls: S(Enc.Cls.Boolean),
|
|
1265
|
+
kind: S(Enc.Kind.Literal),
|
|
1266
|
+
value: Q(`(CASE WHEN ${sql} THEN 'true' WHEN NOT ${sql} THEN 'false' END)`),
|
|
1267
|
+
datatype: S(`${XSD}boolean`),
|
|
1268
|
+
lang: S(''),
|
|
1269
|
+
dir: S(''),
|
|
1270
|
+
num: Q(`(CASE WHEN ${sql} THEN 1 WHEN NOT ${sql} THEN 0 END)`),
|
|
1271
|
+
bool: sql,
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
iriOperand(sql: string): Operand {
|
|
1276
|
+
return {
|
|
1277
|
+
tag: 'iri',
|
|
1278
|
+
classes: Option.some([Enc.Cls.Resource]),
|
|
1279
|
+
isNull: Q(`${sql} IS NULL`),
|
|
1280
|
+
cls: S(Enc.Cls.Resource),
|
|
1281
|
+
kind: S(Enc.Kind.NamedNode),
|
|
1282
|
+
value: Q(sql),
|
|
1283
|
+
datatype: S(''),
|
|
1284
|
+
lang: S(''),
|
|
1285
|
+
dir: S(''),
|
|
1286
|
+
num: SNULL,
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
readonly NULL: Operand = {
|
|
1291
|
+
tag: 'null',
|
|
1292
|
+
classes: Option.some([]),
|
|
1293
|
+
isNull: S(true),
|
|
1294
|
+
cls: SNULL,
|
|
1295
|
+
kind: SNULL,
|
|
1296
|
+
value: SNULL,
|
|
1297
|
+
datatype: SNULL,
|
|
1298
|
+
lang: SNULL,
|
|
1299
|
+
dir: SNULL,
|
|
1300
|
+
num: SNULL,
|
|
1301
|
+
};
|
|
1302
|
+
|
|
1303
|
+
/** A term-valued expression as a column (for `BIND`). */
|
|
1304
|
+
termValued(operand: Operand): Col {
|
|
1305
|
+
return Match.value(operand).pipe(
|
|
1306
|
+
Match.withReturnType<Col>(),
|
|
1307
|
+
Match.when({ tag: 'term' }, (o) => ({
|
|
1308
|
+
sql: this.f(o.id),
|
|
1309
|
+
nullable: !isStaticValue(o.isNull, false),
|
|
1310
|
+
type: 'id',
|
|
1311
|
+
})),
|
|
1312
|
+
Match.when({ tag: 'const' }, (o) => ({
|
|
1313
|
+
sql: String(this.idOf(o.term)),
|
|
1314
|
+
nullable: false,
|
|
1315
|
+
type: 'id',
|
|
1316
|
+
})),
|
|
1317
|
+
Match.when({ tag: 'int' }, (o) => ({
|
|
1318
|
+
sql: this.f(o.num),
|
|
1319
|
+
nullable: !isStaticValue(o.isNull, false),
|
|
1320
|
+
type: 'int',
|
|
1321
|
+
})),
|
|
1322
|
+
Match.when({ tag: 'bool' }, (o) => ({
|
|
1323
|
+
sql: `(CASE WHEN ${o.bool} THEN ${this.idOf(TRUE)} WHEN NOT ${o.bool} THEN ${this.idOf(FALSE)} END)`,
|
|
1324
|
+
nullable: true,
|
|
1325
|
+
type: 'id',
|
|
1326
|
+
})),
|
|
1327
|
+
Match.when({ tag: 'null' }, () => ({ sql: this.dialect.nullId, nullable: true, type: 'id' })),
|
|
1328
|
+
Match.orElse(() => unsupported('Expression value is not a stored term')),
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/** Compiles an expression to an operand. */
|
|
1333
|
+
operand(expr: Algebra.Expression, env: Env): Operand {
|
|
1334
|
+
return Match.value(expr).pipe(
|
|
1335
|
+
Match.tag('Variable', (e) =>
|
|
1336
|
+
Option.match(MutableHashMap.get(env.cols, e.value), {
|
|
1337
|
+
onNone: () => this.NULL,
|
|
1338
|
+
onSome: (c) =>
|
|
1339
|
+
c.type === 'int'
|
|
1340
|
+
? this.intOperand(c.sql, c.nullable)
|
|
1341
|
+
: this.termOperand(c.sql, c.nullable),
|
|
1342
|
+
}),
|
|
1343
|
+
),
|
|
1344
|
+
Match.tag('NamedNode', 'Literal', (e) => this.constOperand(e)),
|
|
1345
|
+
Match.tag('BlankNode', () => unsupported('Blank node in an expression')),
|
|
1346
|
+
Match.tag('Triple', (e) =>
|
|
1347
|
+
isGroundTriple(e)
|
|
1348
|
+
? this.constOperand(e)
|
|
1349
|
+
: unsupported('Triple term pattern in an expression'),
|
|
1350
|
+
),
|
|
1351
|
+
Match.tag('FunctionCall', (e) => unsupported(`Function <${e.function.value}>`)),
|
|
1352
|
+
Match.tag('Exists', (e) => this.boolOperand(this.exists(e, env))),
|
|
1353
|
+
Match.tag('Operation', (e) => this.operation(e, env)),
|
|
1354
|
+
Match.exhaustive,
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
operation(expr: Algebra.OperationExpression, env: Env): Operand {
|
|
1359
|
+
const args = expr.args;
|
|
1360
|
+
const arg = (i: number) =>
|
|
1361
|
+
Option.match(Arr.get(args, i), {
|
|
1362
|
+
onNone: () => unsupported(`Operator ${expr.operator} without argument ${i + 1}`),
|
|
1363
|
+
onSome: (a) => this.operand(a, env),
|
|
1364
|
+
});
|
|
1365
|
+
const bool = (sql: string) => this.boolOperand(sql);
|
|
1366
|
+
return Match.value(expr)
|
|
1367
|
+
.pipe(
|
|
1368
|
+
Match.when({ operator: '&&' }, () => bool(`(${this.ebv(arg(0))} AND ${this.ebv(arg(1))})`)),
|
|
1369
|
+
Match.when({ operator: '||' }, () => bool(`(${this.ebv(arg(0))} OR ${this.ebv(arg(1))})`)),
|
|
1370
|
+
Match.when({ operator: '!' }, () => bool(`(NOT ${this.ebv(arg(0))})`)),
|
|
1371
|
+
Match.when({ operator: '=' }, () => bool(this.equals(arg(0), arg(1)))),
|
|
1372
|
+
Match.when({ operator: '!=' }, () => bool(`(NOT ${this.equals(arg(0), arg(1))})`)),
|
|
1373
|
+
Match.when({ operator: Match.is('<', '>', '<=', '>=') }, (e) =>
|
|
1374
|
+
bool(this.compare(arg(0), arg(1), e.operator)),
|
|
1375
|
+
),
|
|
1376
|
+
Match.when({ operator: Match.is('IN', 'NOT IN') }, (e) => {
|
|
1377
|
+
const value = arg(0);
|
|
1378
|
+
const tests = args.slice(1).map((_, i) => this.equals(value, arg(i + 1)));
|
|
1379
|
+
const any = Arr.isReadonlyArrayEmpty(tests) ? 'FALSE' : `(${tests.join(' OR ')})`;
|
|
1380
|
+
return bool(e.operator === 'IN' ? any : `(NOT ${any})`);
|
|
1381
|
+
}),
|
|
1382
|
+
Match.when({ operator: 'BOUND' }, () => {
|
|
1383
|
+
const v = args[0];
|
|
1384
|
+
if (!Term.isVariable(v)) return unsupported('BOUND of a non-variable');
|
|
1385
|
+
return bool(
|
|
1386
|
+
Option.match(MutableHashMap.get(env.cols, v.value), {
|
|
1387
|
+
onNone: () => 'FALSE',
|
|
1388
|
+
onSome: (c) => (c.nullable ? `(${c.sql} IS NOT NULL)` : 'TRUE'),
|
|
1389
|
+
}),
|
|
1390
|
+
);
|
|
1391
|
+
}),
|
|
1392
|
+
Match.when({ operator: 'SAMETERM' }, () => {
|
|
1393
|
+
const a = arg(0);
|
|
1394
|
+
const b = arg(1);
|
|
1395
|
+
if (a.tag === 'null' || b.tag === 'null') return bool('NULL');
|
|
1396
|
+
const ia = operandId(a);
|
|
1397
|
+
const ib = operandId(b);
|
|
1398
|
+
if (Option.isNone(ia) || Option.isNone(ib)) {
|
|
1399
|
+
return unsupported('sameTerm of computed values');
|
|
1400
|
+
}
|
|
1401
|
+
return bool(`(${this.f(ia.value)} = ${this.f(ib.value)})`);
|
|
1402
|
+
}),
|
|
1403
|
+
Match.when({ operator: Match.is('ISIRI', 'ISURI') }, () =>
|
|
1404
|
+
bool(this.kindTest(arg(0), Enc.Kind.NamedNode)),
|
|
1405
|
+
),
|
|
1406
|
+
Match.when({ operator: 'ISBLANK' }, () => bool(this.kindTest(arg(0), Enc.Kind.BlankNode))),
|
|
1407
|
+
Match.when({ operator: 'ISLITERAL' }, () => bool(this.kindTest(arg(0), Enc.Kind.Literal))),
|
|
1408
|
+
Match.when({ operator: 'ISTRIPLE' }, () => bool(this.kindTest(arg(0), Enc.Kind.Triple))),
|
|
1409
|
+
Match.when({ operator: 'ISNUMERIC' }, () => {
|
|
1410
|
+
const a = arg(0);
|
|
1411
|
+
return bool(this.guard(a, condSql(this.clsIn(a, [1, 2, 3, 8, 11]))));
|
|
1412
|
+
}),
|
|
1413
|
+
Match.when({ operator: 'HASLANG' }, () => {
|
|
1414
|
+
const a = arg(0);
|
|
1415
|
+
return bool(this.guard(a, condSql(this.clsIn(a, [Enc.Cls.LangString]))));
|
|
1416
|
+
}),
|
|
1417
|
+
Match.when({ operator: 'HASLANGDIR' }, () => {
|
|
1418
|
+
const a = arg(0);
|
|
1419
|
+
return bool(
|
|
1420
|
+
this.guard(a, condSql(and(this.clsIn(a, [Enc.Cls.LangString]), this.ne(a.dir, S(''))))),
|
|
1421
|
+
);
|
|
1422
|
+
}),
|
|
1423
|
+
Match.when({ operator: 'STR' }, () => {
|
|
1424
|
+
const a = arg(0);
|
|
1425
|
+
if (a.tag === 'const') {
|
|
1426
|
+
const t = a.term;
|
|
1427
|
+
if (Term.isNamedNode(t) || Term.isLiteral(t)) {
|
|
1428
|
+
return this.constOperand(Term.literal(t.value));
|
|
1429
|
+
}
|
|
1430
|
+
return this.NULL;
|
|
1431
|
+
}
|
|
1432
|
+
if (a.tag === 'str' || a.tag === 'null') return a;
|
|
1433
|
+
return this.strOperand(
|
|
1434
|
+
caseOf(
|
|
1435
|
+
[
|
|
1436
|
+
[this.isNullC(a), 'NULL'],
|
|
1437
|
+
[
|
|
1438
|
+
or(this.eqC(a.kind, Enc.Kind.NamedNode), this.eqC(a.kind, Enc.Kind.Literal)),
|
|
1439
|
+
this.f(a.value),
|
|
1440
|
+
],
|
|
1441
|
+
],
|
|
1442
|
+
'NULL',
|
|
1443
|
+
),
|
|
1444
|
+
);
|
|
1445
|
+
}),
|
|
1446
|
+
Match.when({ operator: 'LANG' }, () => {
|
|
1447
|
+
const a = arg(0);
|
|
1448
|
+
if (a.tag === 'const') {
|
|
1449
|
+
const t = a.term;
|
|
1450
|
+
return Term.isLiteral(t) ? this.constOperand(Term.literal(t.language)) : this.NULL;
|
|
1451
|
+
}
|
|
1452
|
+
if (a.tag === 'null') return a;
|
|
1453
|
+
return this.strOperand(
|
|
1454
|
+
caseOf(
|
|
1455
|
+
[
|
|
1456
|
+
[this.isNullC(a), 'NULL'],
|
|
1457
|
+
[this.eqC(a.kind, Enc.Kind.Literal), this.f(a.lang)],
|
|
1458
|
+
],
|
|
1459
|
+
'NULL',
|
|
1460
|
+
),
|
|
1461
|
+
);
|
|
1462
|
+
}),
|
|
1463
|
+
)
|
|
1464
|
+
.pipe(
|
|
1465
|
+
Match.when({ operator: 'DATATYPE' }, () => {
|
|
1466
|
+
const a = arg(0);
|
|
1467
|
+
if (a.tag === 'const') {
|
|
1468
|
+
const t = a.term;
|
|
1469
|
+
return Term.isLiteral(t) ? this.constOperand(t.datatype) : this.NULL;
|
|
1470
|
+
}
|
|
1471
|
+
if (a.tag === 'null') return a;
|
|
1472
|
+
return this.iriOperand(
|
|
1473
|
+
caseOf(
|
|
1474
|
+
[
|
|
1475
|
+
[this.isNullC(a), 'NULL'],
|
|
1476
|
+
[this.eqC(a.kind, Enc.Kind.Literal), this.f(a.datatype)],
|
|
1477
|
+
],
|
|
1478
|
+
'NULL',
|
|
1479
|
+
),
|
|
1480
|
+
);
|
|
1481
|
+
}),
|
|
1482
|
+
Match.when({ operator: 'STRLEN' }, () => {
|
|
1483
|
+
const a = arg(0);
|
|
1484
|
+
if (a.tag === 'null') return a;
|
|
1485
|
+
return this.intOperand(
|
|
1486
|
+
caseOf(
|
|
1487
|
+
[
|
|
1488
|
+
[
|
|
1489
|
+
this.clsIn(a, [Enc.Cls.String, Enc.Cls.LangString]),
|
|
1490
|
+
this.dialect.length(this.f(a.value)),
|
|
1491
|
+
],
|
|
1492
|
+
],
|
|
1493
|
+
'NULL',
|
|
1494
|
+
),
|
|
1495
|
+
true,
|
|
1496
|
+
);
|
|
1497
|
+
}),
|
|
1498
|
+
Match.when({ operator: Match.is('STRSTARTS', 'STRENDS', 'CONTAINS') }, (e) => {
|
|
1499
|
+
const a = arg(0);
|
|
1500
|
+
const b = arg(1);
|
|
1501
|
+
const fn =
|
|
1502
|
+
e.operator === 'STRSTARTS'
|
|
1503
|
+
? this.dialect.startsWith
|
|
1504
|
+
: e.operator === 'STRENDS'
|
|
1505
|
+
? this.dialect.endsWith
|
|
1506
|
+
: this.dialect.contains;
|
|
1507
|
+
const strings = [Enc.Cls.String, Enc.Cls.LangString];
|
|
1508
|
+
const ok = and(
|
|
1509
|
+
this.clsIn(a, strings),
|
|
1510
|
+
this.clsIn(b, strings),
|
|
1511
|
+
or(
|
|
1512
|
+
this.clsIn(b, [Enc.Cls.String]),
|
|
1513
|
+
and(this.eqFF(a.lang, b.lang), this.eqFF(a.dir, b.dir)),
|
|
1514
|
+
),
|
|
1515
|
+
);
|
|
1516
|
+
return bool(caseOf([[ok, fn(this.f(a.value), this.f(b.value))]], 'NULL'));
|
|
1517
|
+
}),
|
|
1518
|
+
Match.when({ operator: 'LANGMATCHES' }, () => {
|
|
1519
|
+
const a = arg(0);
|
|
1520
|
+
const b = arg(1);
|
|
1521
|
+
if (b.tag !== 'const') return unsupported('LANGMATCHES with a non-constant range');
|
|
1522
|
+
const range = b.term;
|
|
1523
|
+
if (!Term.isLiteral(range) || Enc.classify(range).cls !== Enc.Cls.String) {
|
|
1524
|
+
return bool('NULL');
|
|
1525
|
+
}
|
|
1526
|
+
// eslint-disable-next-line no-control-regex
|
|
1527
|
+
if (!/^[\x00-\x7f]*$/.test(range.value)) return unsupported('Non-ASCII language range');
|
|
1528
|
+
const r = range.value.toLowerCase();
|
|
1529
|
+
const tag = `lower(${this.f(a.value)})`;
|
|
1530
|
+
const test =
|
|
1531
|
+
r === '*'
|
|
1532
|
+
? `(${this.f(a.value)} <> '')`
|
|
1533
|
+
: `(${tag} = ${this.lit(r)} OR ${this.dialect.startsWith(tag, this.lit(`${r}-`))})`;
|
|
1534
|
+
return bool(caseOf([[this.clsIn(a, [Enc.Cls.String]), test]], 'NULL'));
|
|
1535
|
+
}),
|
|
1536
|
+
Match.when({ operator: 'IF' }, () => {
|
|
1537
|
+
const c = this.ebv(arg(0));
|
|
1538
|
+
const a = arg(1);
|
|
1539
|
+
const b = arg(2);
|
|
1540
|
+
const ca = this.termValued(a);
|
|
1541
|
+
const cb = this.termValued(b);
|
|
1542
|
+
if (ca.type !== cb.type) return unsupported('IF of integer and term values');
|
|
1543
|
+
const sql = `(CASE WHEN ${c} THEN ${ca.sql} WHEN NOT ${c} THEN ${cb.sql} END)`;
|
|
1544
|
+
return ca.type === 'int' ? this.intOperand(sql, true) : this.termOperand(sql, true);
|
|
1545
|
+
}),
|
|
1546
|
+
Match.when({ operator: 'COALESCE' }, () => {
|
|
1547
|
+
const parts = args.map((_, i) => arg(i)).filter((o) => o.tag !== 'null');
|
|
1548
|
+
if (!Arr.isReadonlyArrayNonEmpty(parts)) return this.NULL;
|
|
1549
|
+
const cols = Arr.map(parts, (o) => this.termValued(o));
|
|
1550
|
+
const [first, ...rest] = cols;
|
|
1551
|
+
const type = first.type;
|
|
1552
|
+
if (rest.some((c) => c.type !== type)) {
|
|
1553
|
+
return unsupported('COALESCE of integer and term values');
|
|
1554
|
+
}
|
|
1555
|
+
const sql = Arr.isReadonlyArrayEmpty(rest)
|
|
1556
|
+
? first.sql
|
|
1557
|
+
: `COALESCE(${cols.map((c) => c.sql).join(', ')})`;
|
|
1558
|
+
const nullable = cols.every((c) => c.nullable);
|
|
1559
|
+
return type === 'int' ? this.intOperand(sql, nullable) : this.termOperand(sql, nullable);
|
|
1560
|
+
}),
|
|
1561
|
+
Match.orElse((e) => unsupported(`Operator ${e.operator}`)),
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// --- conditions -------------------------------------------------------------
|
|
1566
|
+
|
|
1567
|
+
isNullC(o: Operand): C {
|
|
1568
|
+
return isStatic(o.isNull) ? Option.exists(o.isNull.s, (s) => s === true) : o.isNull.q;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
eqC(f: F, v: Scalar): C {
|
|
1572
|
+
if (isStatic(f)) return Option.exists(f.s, (s) => s === v);
|
|
1573
|
+
return `${f.q} = ${this.lit(v)}`;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
clsIn(o: Operand, classes: ReadonlyArray<number>): C {
|
|
1577
|
+
const f = o.cls;
|
|
1578
|
+
if (isStatic(f)) return Option.exists(f.s, (s) => Predicate.isNumber(s) && classes.includes(s));
|
|
1579
|
+
return classes.length === 1 ? `${f.q} = ${classes[0]}` : `${f.q} IN (${classes.join(', ')})`;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
eqFF(a: F, b: F): C {
|
|
1583
|
+
if (isStatic(a) && isStatic(b)) return Equal.equals(a.s, b.s);
|
|
1584
|
+
return `${this.f(a)} = ${this.f(b)}`;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
ne(a: F, b: F): C {
|
|
1588
|
+
if (isStatic(a) && isStatic(b)) return !Equal.equals(a.s, b.s);
|
|
1589
|
+
return `${this.f(a)} <> ${this.f(b)}`;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
/** `NULL` if the operand is unbound, otherwise the test. */
|
|
1593
|
+
guard(o: Operand, test: string): string {
|
|
1594
|
+
return caseOf([[this.isNullC(o), 'NULL']], test);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
kindTest(o: Operand, kind: number): string {
|
|
1598
|
+
return this.guard(o, condSql(this.eqC(o.kind, kind)));
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
/** Records the class combinations of a comparison that SQL cannot evaluate exactly. */
|
|
1602
|
+
risk(
|
|
1603
|
+
pairs: ReadonlyArray<readonly [number, number]>,
|
|
1604
|
+
a: Operand,
|
|
1605
|
+
b: Operand,
|
|
1606
|
+
handled: MutableHashSet.MutableHashSet<string> = MutableHashSet.empty(),
|
|
1607
|
+
): void {
|
|
1608
|
+
const possible = (o: Operand, c: number) =>
|
|
1609
|
+
Option.match(o.classes, { onNone: () => true, onSome: (classes) => classes.includes(c) });
|
|
1610
|
+
pairs.forEach(([x, y]) => {
|
|
1611
|
+
const orders: ReadonlyArray<readonly [number, number]> =
|
|
1612
|
+
x === y
|
|
1613
|
+
? [[x, y]]
|
|
1614
|
+
: [
|
|
1615
|
+
[x, y],
|
|
1616
|
+
[y, x],
|
|
1617
|
+
];
|
|
1618
|
+
orders.forEach(([p, q]) => {
|
|
1619
|
+
if (!possible(a, p) || !possible(b, q) || MutableHashSet.has(handled, `${p},${q}`)) return;
|
|
1620
|
+
const r: Risk = {
|
|
1621
|
+
a: p,
|
|
1622
|
+
aStatic: Option.isSome(a.classes),
|
|
1623
|
+
b: q,
|
|
1624
|
+
bStatic: Option.isSome(b.classes),
|
|
1625
|
+
};
|
|
1626
|
+
if (r.aStatic && r.bStatic) unsupported('Comparison that SQL cannot evaluate exactly');
|
|
1627
|
+
this.risks.push(r);
|
|
1628
|
+
});
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
/**
|
|
1633
|
+
* Exact comparisons of a float-or-double term with a numeric constant,
|
|
1634
|
+
* following SPARQL's type promotion (integers and decimals are promoted to
|
|
1635
|
+
* float, floats to double). Returns the branches and the class pairs they
|
|
1636
|
+
* handle.
|
|
1637
|
+
*/
|
|
1638
|
+
promoted(a: Operand, b: Operand, op: string): Promotions {
|
|
1639
|
+
const branches: Array<readonly [C, string]> = [];
|
|
1640
|
+
const handled = MutableHashSet.empty<string>();
|
|
1641
|
+
const one = (t: Operand, c: Operand, flip: boolean) => {
|
|
1642
|
+
if (c.tag !== 'const' || t.tag === 'const') return;
|
|
1643
|
+
const value = staticNumber(c.num);
|
|
1644
|
+
const constClass = staticNumber(c.cls);
|
|
1645
|
+
if (Option.isNone(value) || Option.isNone(constClass)) return;
|
|
1646
|
+
const v = value.value;
|
|
1647
|
+
const cc = constClass.value;
|
|
1648
|
+
const add = (cls: number, n: number) => {
|
|
1649
|
+
if (!Number.isFinite(n)) return;
|
|
1650
|
+
const cmp = flip
|
|
1651
|
+
? `${this.lit(n)} ${op} ${this.f(t.num)}`
|
|
1652
|
+
: `${this.f(t.num)} ${op} ${this.lit(n)}`;
|
|
1653
|
+
branches.push([this.clsIn(t, [cls]), cmp]);
|
|
1654
|
+
MutableHashSet.add(handled, flip ? `${cc},${cls}` : `${cls},${cc}`);
|
|
1655
|
+
};
|
|
1656
|
+
if (cc === Enc.Cls.SafeNumber) add(Enc.Cls.Float, Math.fround(v));
|
|
1657
|
+
else if (cc === Enc.Cls.Double) add(Enc.Cls.Float, v);
|
|
1658
|
+
else if (cc === Enc.Cls.Float) add(Enc.Cls.Double, v);
|
|
1659
|
+
};
|
|
1660
|
+
one(a, b, false);
|
|
1661
|
+
one(b, a, true);
|
|
1662
|
+
return { branches, handled };
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
/**
|
|
1666
|
+
* The branches comparing two numbers of the same SQL-comparable kind
|
|
1667
|
+
* (integers, decimals and doubles together; floats with floats) by `num`.
|
|
1668
|
+
*/
|
|
1669
|
+
numericBranches(a: Operand, b: Operand, num: string): Array<readonly [C, string]> {
|
|
1670
|
+
return [
|
|
1671
|
+
[and(this.clsIn(a, [1, 2]), this.clsIn(b, [1, 2])), num],
|
|
1672
|
+
[and(this.clsIn(a, [3]), this.clsIn(b, [3])), num],
|
|
1673
|
+
];
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
/** SPARQL `=` (value equality, with errors as `NULL`). */
|
|
1677
|
+
equals(a: Operand, b: Operand): string {
|
|
1678
|
+
if (a.tag === 'null' || b.tag === 'null') return 'NULL';
|
|
1679
|
+
if (a.tag === 'const' && b.tag === 'const') {
|
|
1680
|
+
const r = Expression.equals(a.term, b.term);
|
|
1681
|
+
return Result.isSuccess(r) ? (r.success ? 'TRUE' : 'FALSE') : 'NULL';
|
|
1682
|
+
}
|
|
1683
|
+
const promoted = this.promoted(a, b, '=');
|
|
1684
|
+
this.risk(RISK_EQUAL, a, b, promoted.handled);
|
|
1685
|
+
const ia = operandId(a);
|
|
1686
|
+
const ib = operandId(b);
|
|
1687
|
+
const bothIds = Option.isSome(ia) && Option.isSome(ib);
|
|
1688
|
+
const sameId: C =
|
|
1689
|
+
Option.isSome(ia) && Option.isSome(ib) ? this.eqFF(ia.value, ib.value) : false;
|
|
1690
|
+
const num = `${this.f(a.num)} = ${this.f(b.num)}`;
|
|
1691
|
+
return caseOf(
|
|
1692
|
+
[
|
|
1693
|
+
[or(this.isNullC(a), this.isNullC(b)), 'NULL'],
|
|
1694
|
+
[sameId, 'TRUE'],
|
|
1695
|
+
...promoted.branches,
|
|
1696
|
+
...this.numericBranches(a, b, num),
|
|
1697
|
+
[and(this.clsIn(a, [6]), this.clsIn(b, [6])), num],
|
|
1698
|
+
[and(this.clsIn(a, [4]), this.clsIn(b, [4])), `${this.f(a.value)} = ${this.f(b.value)}`],
|
|
1699
|
+
[
|
|
1700
|
+
and(this.clsIn(a, [0]), this.clsIn(b, [0])),
|
|
1701
|
+
bothIds ? 'FALSE' : condSql(and(this.eqFF(a.kind, b.kind), this.eqFF(a.value, b.value))),
|
|
1702
|
+
],
|
|
1703
|
+
[or(this.clsIn(a, [0, 10]), this.clsIn(b, [0, 10])), 'FALSE'],
|
|
1704
|
+
[or(this.clsIn(a, [5]), this.clsIn(b, [5])), 'FALSE'],
|
|
1705
|
+
[
|
|
1706
|
+
and(
|
|
1707
|
+
this.clsIn(a, Enc.WELL_FORMED_KNOWN),
|
|
1708
|
+
this.clsIn(b, Enc.WELL_FORMED_KNOWN),
|
|
1709
|
+
this.ne(a.datatype, b.datatype),
|
|
1710
|
+
),
|
|
1711
|
+
'FALSE',
|
|
1712
|
+
],
|
|
1713
|
+
],
|
|
1714
|
+
'NULL',
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
/** SPARQL `<`, `>`, `<=`, `>=`. */
|
|
1719
|
+
compare(a: Operand, b: Operand, op: '<' | '>' | '<=' | '>='): string {
|
|
1720
|
+
if (a.tag === 'null' || b.tag === 'null') return 'NULL';
|
|
1721
|
+
const promoted = this.promoted(a, b, op);
|
|
1722
|
+
this.risk(RISK_ORDER, a, b, promoted.handled);
|
|
1723
|
+
const num = `${this.f(a.num)} ${op} ${this.f(b.num)}`;
|
|
1724
|
+
const str = `${this.dialect.binary(this.f(a.value))} ${op} ${this.dialect.binary(this.f(b.value))}`;
|
|
1725
|
+
return caseOf(
|
|
1726
|
+
[
|
|
1727
|
+
[or(this.isNullC(a), this.isNullC(b)), 'NULL'],
|
|
1728
|
+
...promoted.branches,
|
|
1729
|
+
...this.numericBranches(a, b, num),
|
|
1730
|
+
[and(this.clsIn(a, [4]), this.clsIn(b, [4])), str],
|
|
1731
|
+
[
|
|
1732
|
+
and(
|
|
1733
|
+
this.clsIn(a, [5]),
|
|
1734
|
+
this.clsIn(b, [5]),
|
|
1735
|
+
this.eqFF(a.lang, b.lang),
|
|
1736
|
+
this.eqFF(a.dir, b.dir),
|
|
1737
|
+
),
|
|
1738
|
+
str,
|
|
1739
|
+
],
|
|
1740
|
+
[and(this.clsIn(a, [6]), this.clsIn(b, [6])), num],
|
|
1741
|
+
],
|
|
1742
|
+
'NULL',
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
/** The effective boolean value (SQL boolean, `NULL` = error). */
|
|
1747
|
+
ebv(operand: Operand): string {
|
|
1748
|
+
return Match.value(operand).pipe(
|
|
1749
|
+
Match.when({ tag: 'bool' }, (o) => o.bool),
|
|
1750
|
+
Match.when({ tag: Match.is('null', 'iri') }, () => 'NULL'),
|
|
1751
|
+
Match.when({ tag: 'const' }, (o) => {
|
|
1752
|
+
const r = Expression.effectiveBooleanValue(o.term);
|
|
1753
|
+
return Result.isSuccess(r) ? (r.success ? 'TRUE' : 'FALSE') : 'NULL';
|
|
1754
|
+
}),
|
|
1755
|
+
Match.when({ tag: 'int' }, (o) => `(${this.f(o.num)} <> 0)`),
|
|
1756
|
+
Match.when({ tag: 'str' }, (o) => `(${this.f(o.value)} <> '')`),
|
|
1757
|
+
Match.when({ tag: 'term' }, (o) => {
|
|
1758
|
+
const num = `${this.f(o.num)} <> 0`;
|
|
1759
|
+
const value = this.f(o.value);
|
|
1760
|
+
return caseOf(
|
|
1761
|
+
[
|
|
1762
|
+
[this.clsIn(o, [6, 1, 2, 3]), num],
|
|
1763
|
+
[this.clsIn(o, [4]), `${value} <> ''`],
|
|
1764
|
+
[this.clsIn(o, [8]), this.dialect.hasNonZeroDigit(value)],
|
|
1765
|
+
[this.clsIn(o, [11]), `${value} <> 'NaN'`],
|
|
1766
|
+
[this.clsIn(o, [12]), 'FALSE'],
|
|
1767
|
+
],
|
|
1768
|
+
'NULL',
|
|
1769
|
+
);
|
|
1770
|
+
}),
|
|
1771
|
+
Match.exhaustive,
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
/** `EXISTS` / `NOT EXISTS` as a correlated subquery. */
|
|
1776
|
+
exists(e: Algebra.ExistsExpression, env: Env): string {
|
|
1777
|
+
const context: Block = { items: [], where: [], cols: env.cols };
|
|
1778
|
+
const body = this.op(e.input, env.graph, context);
|
|
1779
|
+
const where = [...body.where];
|
|
1780
|
+
if (env.graph.kind === 'var') {
|
|
1781
|
+
const hidden = env.graph.hidden;
|
|
1782
|
+
const ic = MutableHashMap.get(body.cols, hidden);
|
|
1783
|
+
const oc = MutableHashMap.get(env.cols, hidden);
|
|
1784
|
+
if (Option.isSome(ic)) {
|
|
1785
|
+
if (Option.isNone(oc) || oc.value.nullable) {
|
|
1786
|
+
return unsupported('EXISTS in a graph-independent pattern');
|
|
1787
|
+
}
|
|
1788
|
+
if (ic.value.sql !== oc.value.sql) where.push(`${ic.value.sql} = ${oc.value.sql}`);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
const sql = `EXISTS (SELECT 1${this.from(body.items)}${this.whereClause(where)})`;
|
|
1792
|
+
return e.negated ? `(NOT ${sql})` : sql;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// -----------------------------------------------------------------------------
|
|
1797
|
+
// Entry points
|
|
1798
|
+
// -----------------------------------------------------------------------------
|
|
1799
|
+
|
|
1800
|
+
const compileOnce = (
|
|
1801
|
+
op: Algebra.Operation,
|
|
1802
|
+
context: TranslationContext,
|
|
1803
|
+
resolve: (term: Term.GroundTerm) => Option.Option<number>,
|
|
1804
|
+
locals: Locals,
|
|
1805
|
+
localsKnown: boolean,
|
|
1806
|
+
): CompiledQuery => {
|
|
1807
|
+
const c = new Compiler(context, resolve, locals, localsKnown);
|
|
1808
|
+
const g: GraphCtx = Predicate.isTagged(context.activeGraph, 'NamedGraph')
|
|
1809
|
+
? { kind: 'const', id: c.idOf(context.activeGraph.graph) }
|
|
1810
|
+
: { kind: 'default' };
|
|
1811
|
+
const block = c.op(op, g, EMPTY);
|
|
1812
|
+
const visible = Arr.fromIterable(block.cols).filter(([n]) => !isHidden(n));
|
|
1813
|
+
const names = visible.map(([n]) => n);
|
|
1814
|
+
const innerSql = c.render(block, names);
|
|
1815
|
+
const select: Array<string> = [];
|
|
1816
|
+
const joins: Array<string> = [];
|
|
1817
|
+
const columns: Array<OutputColumn> = [];
|
|
1818
|
+
visible.forEach(([n, col], i) => {
|
|
1819
|
+
const name = `r.${c.col(n)}`;
|
|
1820
|
+
columns.push({ variable: n, type: col.type });
|
|
1821
|
+
if (col.type === 'int') {
|
|
1822
|
+
select.push(name);
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
const t = `d${i}`;
|
|
1826
|
+
select.push(name, `${t}.kind`, `${t}.value`, `${t}.datatype`, `${t}.lang`, `${t}.dir`);
|
|
1827
|
+
joins.push(` LEFT JOIN ${c.T} ${t} ON ${t}.id = ${name}`);
|
|
1828
|
+
});
|
|
1829
|
+
const marked = `SELECT ${Arr.isReadonlyArrayEmpty(select) ? '1 AS one' : select.join(', ')} FROM (${innerSql}) r${joins.join('')}`;
|
|
1830
|
+
// Number the parameters in order of use (fragments of discarded branches
|
|
1831
|
+
// may have allocated parameters that are not used).
|
|
1832
|
+
const params: Array<unknown> = [];
|
|
1833
|
+
const numbering = MutableHashMap.empty<string, number>();
|
|
1834
|
+
const sql = marked.replace(/\ue000(\d+)\ue000/g, (_, i: string) => {
|
|
1835
|
+
if (context.dialect.anonymousParameters) {
|
|
1836
|
+
params.push(c.params[Number(i) - 1]);
|
|
1837
|
+
return context.dialect.placeholder(params.length);
|
|
1838
|
+
}
|
|
1839
|
+
const n = Option.getOrElse(MutableHashMap.get(numbering, i), () => {
|
|
1840
|
+
params.push(c.params[Number(i) - 1]);
|
|
1841
|
+
MutableHashMap.set(numbering, i, params.length);
|
|
1842
|
+
return params.length;
|
|
1843
|
+
});
|
|
1844
|
+
return context.dialect.placeholder(n);
|
|
1845
|
+
});
|
|
1846
|
+
// `CompiledQuery.locals` is a `ReadonlyMap`.
|
|
1847
|
+
const byId = new Map(
|
|
1848
|
+
Arr.fromIterable(MutableHashMap.values(locals)).map(({ id, term }) => [id, term] as const),
|
|
1849
|
+
);
|
|
1850
|
+
return {
|
|
1851
|
+
sql,
|
|
1852
|
+
params,
|
|
1853
|
+
columns,
|
|
1854
|
+
locals: byId,
|
|
1855
|
+
localClasses: c.localClasses,
|
|
1856
|
+
risks: c.risks,
|
|
1857
|
+
};
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
/**
|
|
1861
|
+
* Translates an algebra tree to SQL. `resolve` gives the dictionary ids of
|
|
1862
|
+
* constant terms (see `constants`); terms it does not know get query-local
|
|
1863
|
+
* ids. Fails with a reason if the tree is not translatable.
|
|
1864
|
+
*
|
|
1865
|
+
* @since 0.1.0
|
|
1866
|
+
* @category compilation
|
|
1867
|
+
*/
|
|
1868
|
+
export const compile = (
|
|
1869
|
+
op: Algebra.Operation,
|
|
1870
|
+
context: TranslationContext,
|
|
1871
|
+
resolve: (term: Term.GroundTerm) => number | undefined,
|
|
1872
|
+
): Result.Result<CompiledQuery, string> => {
|
|
1873
|
+
const resolveId = (term: Term.GroundTerm) => Option.fromNullishOr(resolve(term));
|
|
1874
|
+
return Result.try({
|
|
1875
|
+
try: () => {
|
|
1876
|
+
const locals: Locals = MutableHashMap.empty();
|
|
1877
|
+
const first = compileOnce(op, context, resolveId, locals, false);
|
|
1878
|
+
if (MutableHashMap.isEmpty(locals)) return first;
|
|
1879
|
+
// Second pass: dictionary attributes of query-local terms are inlined.
|
|
1880
|
+
return compileOnce(op, context, resolveId, locals, true);
|
|
1881
|
+
},
|
|
1882
|
+
// An `Unsupported` is the expected failure; anything else is a defect and
|
|
1883
|
+
// keeps propagating.
|
|
1884
|
+
catch: (err) => {
|
|
1885
|
+
if (err instanceof Unsupported) return err.reason;
|
|
1886
|
+
throw err;
|
|
1887
|
+
},
|
|
1888
|
+
});
|
|
1889
|
+
};
|
|
1890
|
+
|
|
1891
|
+
/**
|
|
1892
|
+
* Whether an algebra tree is translatable to SQL (independent of the data).
|
|
1893
|
+
*
|
|
1894
|
+
* @since 0.1.0
|
|
1895
|
+
* @category compilation
|
|
1896
|
+
*/
|
|
1897
|
+
export const supports = (op: Algebra.Operation, context: TranslationContext): boolean =>
|
|
1898
|
+
Result.isSuccess(compile(op, context, () => undefined));
|