Attean-0.034/ 000755 000765 000024 00000000000 14636711140 013026 5 ustar 00greg staff 000000 000000 Attean-0.034/inc/ 000755 000765 000024 00000000000 14636711137 013605 5 ustar 00greg staff 000000 000000 Attean-0.034/SIGNATURE 000644 000765 000024 00000047156 14636711140 014327 0 ustar 00greg staff 000000 000000 This file contains message digests of all files listed in MANIFEST,
signed via the Module::Signature module, version 0.88.
To verify the content in this distribution, first make sure you have
Module::Signature installed, then type:
% cpansign -v
It will check each file's integrity, as well as the signature's
validity. If "==> Signature verified OK! <==" is not displayed,
the distribution may already have been compromised, and you should
not run its Makefile.PL or Build.PL.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: RIPEMD160
SHA256 487035f339ab736e78a76a560f4e8ad820c24bd025feeab9c494d2bd6d9445b1 CONTRIBUTING
SHA256 6c28c91ad03dd02bc314213cdc5289369c0d48dc84a9087e717f8b0f8c451239 Changes
SHA256 e8d538bc7bfdb00f243ba45c94a03bc10a0720477ab473868679c4ba3c65906e MANIFEST
SHA256 f69798ed9edaefcd6ca13002c92739cb3404011c4d30329a854b8594bc25c0a0 META.yml
SHA256 8bd6be673f8d9f3be118bd8e152dbd02152f270951da9fae12462140ae8a3b96 Makefile.PL
SHA256 949d49d547f195f95b54383c8f2baff39fe4e035c3a1847416f3351f34d52de6 README.md
SHA256 0989c0007d1742b3c91f59ff267f7a8ec62543c2dba09bf447f7d44fc316a3c4 bin/attean_parse
SHA256 bb5f36b5b9a46d93d167fac3770172f95fb1fb888aefc282cbbeb0dd1f1bc67a bin/attean_query
SHA256 7a079f36ccbe5ca289fa7591875ff392fc35e4cb946b773d98c4efad7eba17c4 bin/canonicalize_bgp.pl
SHA256 cd5397bbe618f5bbd4e12a33b0cf5d21114e771c2dbd0ce28e2135beb52c35a8 inc/Module/Install.pm
SHA256 1b5430a46a35142ef8914d8c745196fca825defc9dfa7e389299bf294613825e inc/Module/Install/AuthorTests.pm
SHA256 798836f9ccb8d204b1be31fc3835631f57e9d818b21a8f0d14bfcfb82ff4a72a inc/Module/Install/Base.pm
SHA256 d64cd4c16f83c5baf11f64a44bea3a0abc060a49da5aba040f0eb01394bf75ab inc/Module/Install/Can.pm
SHA256 668306ae2fad17b3049f885251b8679497c4eb8d5c4b0d13f5c95bda331d1f00 inc/Module/Install/DOAPChangeSets.pm
SHA256 65d7a6098bf3f829e8c1c2865476d3537aa6f0ad0ffc9149e10812c856529043 inc/Module/Install/Fetch.pm
SHA256 70c4b77acab3ff51dfb318110369607cb109e1c319459249623b787cf3859750 inc/Module/Install/Makefile.pm
SHA256 14556386168007ce913e669fc08a332ccdb6140246fd55a90c879b5190c1b57a inc/Module/Install/Metadata.pm
SHA256 63ec8405523ae67c33823dcf4b136a46f0711fb17a8b58b49ddd922ed6b69611 inc/Module/Install/Scripts.pm
SHA256 4c746c02c5cc19bed4c352e76205b4adff4c45ce8310d71294e1b83c059659c2 inc/Module/Install/Win32.pm
SHA256 d3d9b4583243c470ae895defa4c44564485b53693cba1c50ab0320768f443e97 inc/Module/Install/WriteAll.pm
SHA256 2b309fbcdd748897f83ea10e0cf81d4ef2cbd237a49d3eade3e35b11b90d29f9 lib/Attean.pm
SHA256 ebbfe8da89505c8de2c1a188460a2e7400265959e5806a52221f25e333e93ac7 lib/Attean/API.pm
SHA256 690e7aa796009beab9d91c418d6723b7979b935e96176e035f10326a974af300 lib/Attean/API/AbbreviatingParser.pod
SHA256 33a7532c75d09d3a428db38df3aa5e8cc21af7dc9ae109851a13dbb4d8e8349d lib/Attean/API/AbbreviatingSerializer.pod
SHA256 df392e2f8f9503664cebbe9ffb3cbc0cd0c087853f26b9484c156c2f1d36a15e lib/Attean/API/AggregateExpression.pod
SHA256 3557b16a800205e7b6e79c086994dab7f0e15f9ad08e1e89b704e1d5f678ae9b lib/Attean/API/AppendableSerializer.pod
SHA256 4346abe29a62972633e90fee4ffcc8c523da430c33db9762a6536ab76599581e lib/Attean/API/AtOnceParser.pod
SHA256 1368cd08a39f0c3285531589857263abf1ea565ce4d10af29eaa9fba8b054d89 lib/Attean/API/Binding.pm
SHA256 8052361d85748b06353f55ad3f091a5e594b4d418469ebf5a90d6b89c6660c8a lib/Attean/API/Blank.pod
SHA256 3bd03853fa902867e8802316aea0f7e6c11383a28dbb7be062d933de7a9c8140 lib/Attean/API/BlankOrIRI.pod
SHA256 6d9cc3b1162f29a7b738cf45816fe86810750ffde7485a19ee87cf6ce866dd0c lib/Attean/API/BulkUpdatableModel.pod
SHA256 a8a09292a7e2da4f2f8b55bdbd06a15c9cfa2940fdce39f39c8f5ac0e667b405 lib/Attean/API/Expression.pm
SHA256 6f9aa0c7c77edca42e89f06a5077ed48607ac139fa065b449f7bfbb5b1050f6a lib/Attean/API/IRI.pod
SHA256 a946558b79c485cd9bea551944c34691cd076b2afabdc50d0d93183d0801708e lib/Attean/API/Iterator.pm
SHA256 59d7ca8c2f709241f03e3f8ce84b5d2143f0e13f2401fe67fe6aa889bc9659c5 lib/Attean/API/Literal.pod
SHA256 bf537a44bf883ecbb241ec44dc86f3161fb89989c6b3ef3c9d1e0049563edd9d lib/Attean/API/MixedStatementParser.pod
SHA256 cea0cc652511a1fdc84a87a4f16c61374e5a03dae5c009d438b4a6c2dc199daa lib/Attean/API/MixedStatementSerializer.pod
SHA256 f1069d249a5647a96915b4ce4aaf59328d98ef0caa040fc665eb6b15df97befa lib/Attean/API/Model.pm
SHA256 e16c1e24bbf2d81b6733bd1e3df6155add0d7a1369054a50aebc6185e2ec0c3e lib/Attean/API/MutableModel.pod
SHA256 7a62b0e20aa93d07abae6d2cc3d8d4374e73643212287f1b550a3eaaf3ff53b5 lib/Attean/API/MutableTripleStore.pod
SHA256 e8d5a17ee18bfa01bbdb58a5e5cea98ee1e41bfebdc8c4c4b1d2e439490d4352 lib/Attean/API/Parser.pm
SHA256 81856794b6eb84e6359543cd91bcddfb15d2a7809d1be29975cbb7870ff24c6b lib/Attean/API/Plan.pm
SHA256 fb7cb4e0aba9638e7eede27ea39012a89857d5ead00e5050a98d0194537e1de9 lib/Attean/API/PullParser.pod
SHA256 16a778f0cf13255643a52c4b61789b85ada745c82473f47bb5b96dbfd454687b lib/Attean/API/PushParser.pod
SHA256 2b774feabffe21c74f25d22187c5abe79116ed26c45065bd857a74007f94630f lib/Attean/API/Quad.pod
SHA256 45625551709585fbe2557cdd7494995fdedab0fd0f67658b53831d3cd9fdda28 lib/Attean/API/QuadParser.pod
SHA256 7b48ed0d9e4becfb7179748b9f72c7734b3a6720d3aaa246dfe524ce63b2c374 lib/Attean/API/QuadPattern.pod
SHA256 52792079800f0496b7bf34e4b7d90dfc7d508bfd366b97524db68cc20d6e8532 lib/Attean/API/QuadSerializer.pod
SHA256 d90a9d9d2936c416a3847b224db5a50b684e39454d1a085cc4d3ee6a18d838b1 lib/Attean/API/Query.pm
SHA256 c3343bf4ad611be1c3f3c7b2680d8ae7c816f07378ed3a8a10439f2afa2b08f1 lib/Attean/API/QueryPlanner.pm
SHA256 2259ef5b0a240704b396c7dce8184aa437d6e8249a29b8671cc8ff6a3ef4d40e lib/Attean/API/RepeatableIterator.pod
SHA256 007aa6736186bd1370fb6b79c20cc3d15660fb2321eca1d99186ef38cafd6a79 lib/Attean/API/Result.pod
SHA256 02b3b16ac897b5a096de1b49b4165fda5ddc0dd27547876a6855c5e32d210b0e lib/Attean/API/ResultParser.pod
SHA256 2303d8a0e608c1a13656acbb2a2387830d23c8c3e12f6f0e6310fbec6cf9f31f lib/Attean/API/ResultSerializer.pod
SHA256 2a5a7558d25d71f89edad27907f5a4d7d651fbe9fb17f8318cecd11a9692e3d6 lib/Attean/API/Serializer.pm
SHA256 243202bfc9d3b88a97640c3e8d4c7c3e8d58dcd2950632ec40a94a19ce0def10 lib/Attean/API/Store.pm
SHA256 19eb418592fa17dffeea861e268aa352687074134b33f3749079f8d018813c6d lib/Attean/API/Term.pm
SHA256 29ceb246500a7f14ec8363f3f1faa247bb676977a0c12beaefb6e08286bfa6de lib/Attean/API/TermOrVariable.pod
SHA256 ba32c6ab36ea8d87483aa6aef7af940238fcf1036d15080faf28cb3c74d53625 lib/Attean/API/TermParser.pod
SHA256 e6e455d0745e02db98930d604aacaea6ab71bafec6e239a5b9e77f9376c24c8a lib/Attean/API/TermSerializer.pod
SHA256 3e278707693964a221a01cf57e3844649d122d7e06c065d80da2a729dec646c2 lib/Attean/API/Triple.pod
SHA256 6b89cfad1d7d7a9bafe634cf80bda98032f5b4d83f9057ea61b026b5413cb1fd lib/Attean/API/TripleOrQuad.pod
SHA256 48abdde7486cee171387154650da46bff19e31d0c7114b27432fa9d4aa8f40ea lib/Attean/API/TripleParser.pod
SHA256 952a9e6d5bb129426dc2842a8199d5de2cf404877cb5053bee02d07a9c1447db lib/Attean/API/TriplePattern.pod
SHA256 08072bc3798394ffd1706dc51b4ac15bc43ac6f7755f4606cd479cd83b3a0de9 lib/Attean/API/TripleSerializer.pod
SHA256 55d29f53817ba5abc553f817e663827e1c879701485d7c1b3d302a406f328a65 lib/Attean/API/Variable.pod
SHA256 4952a7ee8d6da39fca9abd74a6352041e6a7fae98b95fbdf6411f716105de861 lib/Attean/AggregateExpression.pod
SHA256 cdbe1066ed9f6f3ba062dc39c5e193b8f520b2b9b14abc6be9e1e1d2ddd91d1d lib/Attean/Algebra.pm
SHA256 55eb51248c10aaca05c9c7a493d7e389f4e68a60faba82dc08be13987418530b lib/Attean/BindingEqualityTest.pm
SHA256 e6934053b2b4b67ce99f5b3810327cbe9b8e4c29f668fc175ac6bf47105dbf14 lib/Attean/Blank.pm
SHA256 ca193d60d75c441de64aa68396f3f144387d83477593eeee046e0a2ee8f7e846 lib/Attean/CodeIterator.pm
SHA256 e1d889b203f89efcd276070ab69a33ecc3f47c9f75600f1db8ce71142c6ce9b4 lib/Attean/Expression.pm
SHA256 cfc0c3987020b3f1c942d38cf9a59dd5041507d960ee2c941eb2fef34bcfce6e lib/Attean/IDPQueryPlanner.pm
SHA256 621bafe3f7231e7ed622440efbe4f56902af5b69294daa327ad784b7ed6ba470 lib/Attean/IRI.pm
SHA256 d315c14bf6a9494854df106e61a4d52e345486626872aca48e4908ec3d989759 lib/Attean/IteratorSequence.pm
SHA256 6ac140fd6114e9d7482d6b9aba6cdbb36be6d5a27b213861c08609ff7df0239c lib/Attean/ListIterator.pm
SHA256 38bc4f68a1dea147cd3856c045b6178e4e2b5181789d3c12d016e25c8303e850 lib/Attean/Literal.pm
SHA256 6c59db2ef0fd86a5bdee7e6df861e373f4d80cbd66e318a410ca462331e38679 lib/Attean/Plan.pm
SHA256 1a7f964981ebe9cc1fad8c38f8a06dbd93543af49932b1d037d38212415331d6 lib/Attean/Quad.pm
SHA256 2b73d0fc72bdf86c8534648914bc0071292f7e945101e52df764cb7b9ceccee1 lib/Attean/QuadModel.pm
SHA256 0d7fce1c43655e4ce2b8374c9d7851d25755f2df68712bb2a447b26106485850 lib/Attean/QueryPlanner.pm
SHA256 92c0aecdaa3c32aa2ae86c86bb3e90cccd74616caf2de321d210b94af1389ad2 lib/Attean/RDF.pm
SHA256 60f22ed9f380e3d18f38a25a0340be2a9b4a616e4d8654dd42b796f8f31062ff lib/Attean/Result.pm
SHA256 39bf29916743e07431c1a2f3c26c7c25ddf3d9f6f6680ed50e27d59c00157764 lib/Attean/SPARQLClient.pm
SHA256 f21407c83123f1fabca5fa3933d4d62bec2abd3168f7a40780e35ae4caa63d41 lib/Attean/SimpleQueryEvaluator.pm
SHA256 e3ba4a977fcc62e3825ac781c2e62ae8f614f738b5b0e57c9bb7e04adb98cdcd lib/Attean/TermMap.pm
SHA256 76555f03d579082d4e7e9fbd3e145578a2c4c843025a4616c39434aa7c445bea lib/Attean/TreeRewriter.pm
SHA256 bfaaa7776da39617d5e9bf8ae54885a3039d731f4e5e9c6232c73686750c704d lib/Attean/Triple.pm
SHA256 9724aa4edc26608992249128141f59f71e2c9171979c59264499df155770f44c lib/Attean/TripleModel.pm
SHA256 bcfd2da01b1ad16b150717a109607ac901efdbe47e78fc1976c44e98ebc4a3fc lib/Attean/Variable.pm
SHA256 98ff2e10922b046b4c98e55d1a77b57ca203cc4fbc4191fbf4222872ee88b3d2 lib/AtteanX/API/JoinRotatingPlanner.pm
SHA256 2c7c4940bb5903b1821256a6021852572743cc5710befa4f90051673baf6c622 lib/AtteanX/API/Lexer.pm
SHA256 f126dcbd4cef6c736c6bf29fb2dfc68c78711230c8febe15fdaa1add3f5c6393 lib/AtteanX/Functions/CompositeLists.pm
SHA256 4354757e70ca77881ffae3b0c4267c0ef454eb0041247879e06572d585c34054 lib/AtteanX/Functions/CompositeMaps.pm
SHA256 aa214b1b342daaeb696322a35366e598af423127963d1c211a3b905eabc9e8bf lib/AtteanX/Parser/NQuads.pm
SHA256 0590407b459d9933ae747059e9b1cb2bc98414bb851941e7923817055b37d9db lib/AtteanX/Parser/NTriples.pm
SHA256 26c2160ec9d475294f0df7e2e523b4662700d922206ceffb2e6d5692d4eefc0b lib/AtteanX/Parser/NTuples.pm
SHA256 4ecea129a6bcdbf8fba168ef2cca0134277dd7c0c08eb06905e0a5865d0caaca lib/AtteanX/Parser/RDFXML.pm
SHA256 0a155ef35a2be1cb04dce299f183d06d0e744c929db0f50ce3824b737c7ed8eb lib/AtteanX/Parser/SPARQL.pm
SHA256 dd362f93d630768f57b8ffd7beab33a0a28ddd13bc3aa4f214d5f04b262de115 lib/AtteanX/Parser/SPARQLJSON.pm
SHA256 edef8be880f8baa91af57a64e4ebc5c8f9177273ff10cf514783e6728bcfe8c4 lib/AtteanX/Parser/SPARQLLex.pm
SHA256 10fd44ee15ee300d7b95b80184fdeaa7fada79a30c438d5197dc827aa2940120 lib/AtteanX/Parser/SPARQLTSV.pm
SHA256 647a5ae01e407e1a2879ac2a765d640df3ea4a9fdc54b55960055e5ec8af3d0b lib/AtteanX/Parser/SPARQLXML.pm
SHA256 f8e46fdd9799a1f8a53987ca9f0667bd3f5e32de35b8a46a81b9a5c021e67214 lib/AtteanX/Parser/SPARQLXML/SAXHandler.pm
SHA256 30ed17bc3639531a4d616b446de2f5a2e89651bd8b612529cdee17b012af5d97 lib/AtteanX/Parser/Trig.pm
SHA256 012809296b9569b65152c77d3437f192394c9cdc9530caab1877fbd32dd9c293 lib/AtteanX/Parser/Turtle.pm
SHA256 45abbb87b1b081703ef611f957c74ab631a22f4b6b3925f443a490a0bff184d7 lib/AtteanX/Parser/Turtle/Constants.pm
SHA256 c7ef874390279c827d90124bff0113597694e65db56ab06a9ea55c047d599e6e lib/AtteanX/Parser/Turtle/Lexer.pm
SHA256 e28f34b01175da7c345426e2799e8032f1031f694fca4637fd58f517cde70ec5 lib/AtteanX/Parser/Turtle/Token.pm
SHA256 80577ce636e039df4ac98f362dc1e584c4f9673aa0b6d2ec0de9c81accf1a2d4 lib/AtteanX/SPARQL/Constants.pm
SHA256 06ed8ffe9c59427d3a8ac46df6c3bdae1df6b1c5c1c112ff78aab5038fb736f0 lib/AtteanX/SPARQL/Token.pm
SHA256 a02b65f4082306a7d63143aed6b5828394ab702497ce60f1731196ee7a42b158 lib/AtteanX/Serializer/CanonicalNTriples.pm
SHA256 741d4b74bc65c39b5c85afa89db4ac08cac2c238d5ada0f455f8c64ecac92dd7 lib/AtteanX/Serializer/NQuads.pm
SHA256 e4db8655c0efed6bc67ef505f9d7126418c0c61874fb2b5fa44d246a741c2fcc lib/AtteanX/Serializer/NTriples.pm
SHA256 f0d6829c5c1fb53fd88b5d36ab738699723d5585bd655cd3359a7db40e4737ea lib/AtteanX/Serializer/NTuples.pm
SHA256 984395e5c108263d397635f4e4e2a35d5c95bf56d4fcead617ed7ad1e73dbf00 lib/AtteanX/Serializer/RDFXML.pm
SHA256 140d905edf8a703013f36ad0c4487368637c123a01450b0b5562468de00bf2fd lib/AtteanX/Serializer/SPARQL.pm
SHA256 6a89f86d6803ac67c5d296e13865c337b6a3c2c6db801e5f00bb846a361fc0e9 lib/AtteanX/Serializer/SPARQLCSV.pm
SHA256 12e69db4d7a7e2460eec03391022db25634c1473118d1f1d4542fd6ed3d803a5 lib/AtteanX/Serializer/SPARQLHTML.pm
SHA256 31b39f7121d78ae04afc7a3a184dae09ccec9d9a5b344cd3a2b9635978e72fc2 lib/AtteanX/Serializer/SPARQLJSON.pm
SHA256 85395925980d3c772541850288f0b23c4001a3c661a6838383f5e95688aabeee lib/AtteanX/Serializer/SPARQLTSV.pm
SHA256 8d6c0651d36ea3766ae74ebe198229b11988683893e3cec5347bbebb452b0240 lib/AtteanX/Serializer/SPARQLXML.pm
SHA256 3fbc0e87ade2071ac1584fd58a96f38e5de4a6ec234ae15bb4f61c75aab5d897 lib/AtteanX/Serializer/TextTable.pm
SHA256 ba9cf74cb6112f184315cf039428febb34dfd4fc76f166b6c56ac0674428e93b lib/AtteanX/Serializer/Turtle.pm
SHA256 0674cfc913174f49f605a3eccdb371beaad5aff7a12cc07f1ada1df5aa7dc6b7 lib/AtteanX/Serializer/TurtleTokens.pm
SHA256 e765a66bb35281308273f70b80c967cca3b3378708632b6dc93b50a11ce7cf23 lib/AtteanX/Store/Memory.pm
SHA256 cc1ba2abb8f06ea4391f94ec9fa1ac54b7882828c5232004358c70ec4c7b0c7a lib/AtteanX/Store/Simple.pm
SHA256 fe3d82ddfafd58cc501e9f3d0183292fc0c702c57b1b75b19e1a6a913408244d lib/AtteanX/Store/SimpleTripleStore.pm
SHA256 b9f77d0fb0a9aaad104bc869f3aeec1064ffa1d813c8aba1e7aef44e59da0519 lib/Test/Attean/ETagCacheableQuadStore.pm
SHA256 c0fac9b24dea93536f0a236140ae63d4107f068f7b8071068150460f4276f4d1 lib/Test/Attean/MutableETagCacheableQuadStore.pm
SHA256 b0e086a72b39626e27b07b84668d8a9c54d1798a419679cf1634cf98c22a6837 lib/Test/Attean/MutableQuadStore.pm
SHA256 3599fe030b21f71cbcc4f8a3f2d5f5dc55cdf5412753bfbb22f6b419da90c14f lib/Test/Attean/MutableTimeCacheableQuadStore.pm
SHA256 6aabbccaa730533bccc360db4eb61db357541e40f33eef03ef2557134aaf023d lib/Test/Attean/MutableTripleStore.pm
SHA256 6034d9d2921c11ba6f93807e01cce1fff6c2aeb02cd59a83600ebeef6ee6ad19 lib/Test/Attean/QuadStore.pm
SHA256 f42635dfbfe26d09fcadc8e6b42b5ae7509c207cc8133ed413f26601b6c08e68 lib/Test/Attean/SPARQLStarSuite.pm
SHA256 4390bc29a9dd66b5480cbe234b69ef6f8bcc0922bfca15d994808d8b24388361 lib/Test/Attean/SPARQLSuite.pm
SHA256 460142176b6e3b84c3e3adf5b354ac095729aaddaced60bf631b6a1519fc4ca5 lib/Test/Attean/StoreCleanup.pm
SHA256 512a054baea037a14c67a0a2d8294ca458764f49da13868f94737073c58e8698 lib/Test/Attean/TimeCacheableQuadStore.pm
SHA256 0bea5aeeb41c69ac6b36b1737b918bae744268cdbd165dc7b2223746bbc2e3ba lib/Test/Attean/TripleStore.pm
SHA256 d76d6e956801d3ab4370a527b2ab44909bc37feada1f2d018b447b2d1c52804b lib/Test/Attean/W3CManifestTestSuite.pm
SHA256 b918c81b3ad707940a5c3448b56449b322e2fa002d2a72419a77f30cbb5b5464 lib/Types/Attean.pm
SHA256 8af0524608505de31f40cbf051101e0a66bfebe6a5cac4ec03e3b1b787e764c8 meta/changes.ttl
SHA256 1dc79bb2b9cdf890004c1760ece495500ae624a3759fa24cd5fe888bec58db52 t/00.load.t
SHA256 b9478f6e0d48e753304792448c6440a7974d13ceac602b5b634ac62ce3f7d9ec t/algebra.t
SHA256 e247d21a6674074ba2620a7a804deb98893ba72b84ef0e2e6d20f356d1194430 t/binding-equality.t
SHA256 4ca870ddb09a1ff622da1e36c4ee6dc19f47cf62a7d36db8ea2f4fd006f74b76 t/binding.t
SHA256 b9fb362268d464199e9966a871fc8a43fc0e97af1dc1b7ea57851a5cf333d6ec t/convenience.t
SHA256 1a1d1ef2a8fa1836d221e47a5a6501396c5f8aa34e6442c71d0113bfd5e83e46 t/cost_planner.t
SHA256 ce9233f9d79f3c6d247850d3563093898b301fe094d4ac10528f2a38d8c40e33 t/export-functions.t
SHA256 560cc1589d84209231b9bf72ff2c1d7dcb181801da060ef3ed0d1e27d89f8ec6 t/expression.t
SHA256 50f5e2e35383ef9e6e5dfc85065bcfc8cd8820c44d209938029f6cba5b2959cf t/http-negotiation.t
SHA256 32e8eef05c67002cb124105540f18c01825e743c1fb8e3c3be5575fb5a78cc41 t/idp_planner.t
SHA256 c91d27d6a4adcf3a15a7f6deff359c0f19b7613c53a909af27ed3b646948793f t/iter.t
SHA256 5d562c2b9445f744947ecd6e0e0a5bd067b5c593ecb61307e8f7c85437c7843b t/join_rotating_planner.t
SHA256 88ff05bc7a3b3bdaeb3bb97b631ae3029dcdf320fd057471752291a4edeae425 t/model-quad.t
SHA256 78e95af88a2280528015d6dfb51c0a52e28d0ccf6b878feb74a75275e4aeac11 t/model-triple.t
SHA256 16917197261d7fee09be08b8a35b8fa29e12454aea025fa65d8b8850b8a34e71 t/naive_planner.t
SHA256 defb6bed4c81808342ff65257e9a5f5d0ef3fea2ba5d6dcc0d3d056bcf0b2df2 t/parser-nquads.t
SHA256 ac0738aa575954860f2bb7e80e28fd5f7ded05dfeadb40dc35ed2e4f936b832e t/parser-ntriples.t
SHA256 3054f885baa580cb480a89de3960de3bd7d81a7f146b0bd17657ae017d6c7dda t/parser-rdfxml.t
SHA256 6d7faba1079d1365d3c4d70c87ed935e6b77696f65678088de96c82bb3c16ed3 t/parser-sparql-star.t
SHA256 bd8c72a77f58984fc117d9a9840ab4d4e6b71d1b07ca5cec77ece90cca5ae92e t/parser-sparql.t
SHA256 cebcc5c75cf316d48df99abbc0df6c2d2a978320cb7aae0aa217a1018e1f3d8f t/parser-sparqljson.t
SHA256 884b39ca9083ef1f3fea577dc2cd57b8de60836e82dcff1bff6d21452a7303ec t/parser-sparqltsv.t
SHA256 11e5be8b14c5cb0aeb7e3256688aaa8bd27b8e241ea6ed5d104d91e4957abbd5 t/parser-sparqlxml.t
SHA256 3b3773a53599d84845383dee130c773c2b50a73ebd1309b4089ffb879cc279a6 t/parser-turtle-star.t
SHA256 2e0db8b74089fe5212a8bcacb91e2a16342ddb6370d491686e50e62930989138 t/parser-turtle.t
SHA256 49d3adff4d64a797ce302f7307d4e126690f74b3faf67911ac840ff89c746f0f t/parser.t
SHA256 972045f0d6db1e95e3ba5af360d6897257df7a0b07320a99bcdd82b393936f22 t/parser_serializer_api.t
SHA256 5ac6fefb729bca37648c388a20c9e49a436e64ccc58062ae8bff4ad49fc50f11 t/plan.t
SHA256 b3c643f68881fc5e7ee89dc26d50846a99f1eb7d4d238853561dfbf55b060e1e t/plans.t
SHA256 85c1e0994d2e1a7c83e16e51cc5d9f267176a7f137b525ee8cf64a508236200e t/serializer-canonicalntriples.t
SHA256 32be8d68a6ca07edbf4cd80cc2f3cda6520459885fbc2740be770e049161711c t/serializer-nquads.t
SHA256 d497b0324e6b634636fbcc1b6c306a0e4b085aa3ab953a28aed9c7fed57a4d23 t/serializer-ntriples.t
SHA256 ebf0041a9028693861ab3050cbcce8f4399211f08a53f5291db5225d2d8c8f90 t/serializer-rdfxml.t
SHA256 0a6f28944cecbc09e8adb74e3e8c79b9867d30c676e4b803b13947e17dde9375 t/serializer-sparql.t
SHA256 776f7fcece5befff17c6bb4f3b62222d773e393a6ef35ba7be52a782a109c03d t/serializer-sparqlcsv.t
SHA256 87e3e51c20d9b2fd89c571ec4ff10ed0879cd3310c670dfcedfcc31cb41dd9c2 t/serializer-sparqlhtml.t
SHA256 4c63b97ce6b45dbc1decabc6bed0f8220a91c2855c2965fed8c0c14fe75d8504 t/serializer-sparqljson.t
SHA256 fd557f136413c2aa9fd4fb37ad98cc675b00bc9198c4c847fbaabc1c5cb5019a t/serializer-sparqltsv.t
SHA256 7c67054f8972ed39121b00232d5ec56d6c2a3aa6cb6783b111b0749455aa6754 t/serializer-sparqlxml.t
SHA256 17beabdc87423cc5f48fab93bf1e76ed86ae0c080960331f3f807583224cad67 t/serializer-turtle.t
SHA256 c612288c1607ce563d87b7e30201ae5187be830beb7a01c72aa6b7925c9b601b t/serializer.t
SHA256 15b9787049ce24c70a8184d9986c0cc8d0603cd678c193333724579e552e1bf2 t/simple-eval.t
SHA256 4c7e65106d909f992a8acd478cafa012bb96c44985e4d425a9a60b327d130e57 t/simple.t
SHA256 80c9444f53b85b70a7ca6c34c5483610dcfd8c078f3f8c13542a0fbd362217db t/store-memory.t
SHA256 b5ef5fc3e12c7f2338185d600ed70a441bdb4757877651e72a929007fe6b8f14 t/store-simple.t
SHA256 f6567e7f55c4031d6987ce33e06ecf9d08adc6ed11b8631d711a3635fddc7663 t/store-simpletriple.t
SHA256 410ab17e0316f548675ee815e00d0801eb9d7a0ca0714847ce432578c0618c72 t/term-map.t
SHA256 8d54ad4dc5f5bb60ba29fea8b1ab0a7334dfe7c624b07ba440d6259ea3fa3960 t/term.t
SHA256 09757d8fcc25b8f3c92179c2439fb7e60c61d75988fa69c1e35c6dc14f786433 t/treerewrite.t
SHA256 e1020bec7b7fbff97e0e9ec8b7fe91fc02ecdd9a2452cd6654fb484212a387ac t/types-general.t
SHA256 5cfcacd72b4d27998c3e6b0167d00c630a7f4815362c0ec346daf205a5bf228a t/types-iri.t
SHA256 73f708492cbd60615994f32861d017c67f96224ba6a5fe0b1e6f9231e7cbd2c0 xt/dawg11-memory.t
SHA256 738067ae3b8cb02b23ad4e29dc30034bc9d8f7ba32b1b75b8694549e07ff5fe3 xt/eval-sparql-star-memory-simpleeval.t
SHA256 c7d5660216beaca18bc60da47b897ceed2645c8e8106794ccde0e08d53da4b52 xt/eval-sparql-star-memory.t
SHA256 81dae5e652e694c2acc7d105e32ba5f69edc343b22b2e1d47f95fdef8b441cd3 xt/pod-coverage.t
SHA256 2b04b20ff767801fde2fb6435361dc8ca0a9b60dda6cb0bbd18dd52962e1c1a3 xt/pod.t
Attean-0.034/bin/ 000755 000765 000024 00000000000 14636711137 013604 5 ustar 00greg staff 000000 000000 Attean-0.034/CONTRIBUTING 000644 000765 000024 00000001501 13235706150 014654 0 ustar 00greg staff 000000 000000 # How to contribute
## Reporting Issues
* [Create an issue](https://github.com/kasei/attean/issues), assuming one does not already exist.
* Add relevant labels
## Submitting Changes
* Try to follow the existing whitespace and brace style
* [1TBS](https://en.wikipedia.org/wiki/Indent_style#Variant:_1TBS)
* Tabs used for indentation and aligning of comments (with a tabstop width of 4-characters)
* Ensure the test suite passes (`perl Makefile.PL && make && prove -l t xt`)
* Consider using the [pre-push hook](https://gist.github.com/kasei/0819f25cee79b3597576) to prevent pushing if the test suite is failing
* Submit a Pull Request
## Getting Help
* [IRC in the #perlrdf channel on irc.perl.org](irc://irc.perl.org/perlrdf)
* [@kasei](http://twitter.com/kasei/) or [@perlrdf](http://twitter.com/perlrdf/) on Twitter
Attean-0.034/PaxHeader/Changes 000644 000765 000024 00000000460 14636711132 016273 x ustar 00greg staff 000000 000000 30 mtime=1719374426.343038508
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
85 LIBARCHIVE.xattr.com.apple.FinderInfo=VEVYVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
70 SCHILY.xattr.com.apple.FinderInfo=TEXT
Attean-0.034/Changes 000644 000765 000024 00000063627 14636711132 014340 0 ustar 00greg staff 000000 000000 Attean
======
Created: 2014-09-27
Home page:
Bug tracker:
Maintainer: Gregory Todd Williams
0.034 2024-06-25
- (Addition) Add registry to allow extension literal datatypes to map to
Moo roles.
- (Addition) Add support for composite types (CDTs).
- (Addition) Allow extension functions to register as functional forms.
- (Update) Add Attean::API::NumericLiteral->equals.
- (Update) Add GitHub workflow using perlrdf/devops actions (#163 from
@zmughal).
- (Update) Add HTTP::Headers to test requirements.
- (Update) Add types for RDF triple/quad and their terms (#166 from
@zmughal).
- (Update) Change in `import()` behaviour for Perl > 5.39.1 (#168 from
@zmughal).
- (Update) Fix bit-rotten code in W3C test suite harnesses.
- (Update) Fix bugs discovered based on run of updated W3C test suite
harnesses.
- (Update) Fix casing for AtteanIRI type (#165 from @zmughal).
- (Update) Fix handling of BOUND and error-causing INVOKE expressions in
Attean::Plan.
- (Update) Impove error reporting in
Attean::API::MutableModel->load_urls_into_graph.
- (Update) Improve Attean::API::CanonicalizingLiteral to have strict and
non-strict c14n variants.
0.033 2022-10-02
- (Addition) Add new Attean::SPARQLClient protocol implementation.
- (Update) Fixed handling of endpoint URLs containing query parameters.
- (Update) Protocol HTTP requests can now be signed by specifying a
'request_signer'.
- (Update) Update SERVICE evaluation classes to use Attean::SPARQLClient.
0.032 2022-08-14
- (Update) Fix for bug caused by newly added TermOrVariableOrTriplePattern
role.
0.031 2022-08-04
- (Addition) Add initial implementation for TriG-star parser.
- (Addition) Add support for parsing and evaluating SPARQL-star queries.
- (Update) Improve implementation, docs, and tests for accessing parsers
and serializers by file extension.
- (Update) Update Turtle, SPARQL-XML, and SPARQL-JSON parsers to support
RDF-star.
- (Update) Update docs and add tests for handling of base URIs in parsers
(#158).
0.030 2021-02-06
- (Update) Fix bug in attean_parse for parsers that are not either pull or
push parsers.
0.029 2021-02-01
- (Addition) Add Attean::API::MutableModel->load_triples_from_io (#157).
- (Addition) Added -n CLI argument to attean_parse to allow numbering of
results.
- (Update) Fix bug in Attean::API::ResultSerializer->serialize_list_to_io.
- (Update) Update Attean get_parser and get_serializer to allow searching
file extensions and media types for 1-arg calls.
- (Update) Updated Attean::API::Serializer to require file_extensions.
- (Update) Updated AtteanX::Serializer::TextTable to print table borders
and rules.
0.027 2020-11-06
- (Addition) Add canonicalization support for xsd:negativeInteger.
- (Addition) Added TextTable SPARQL results serializer.
- (Update) Fix SPARQL lexer to accept variables using the $ sigil.
- (Update) Fix evaluation of aggregates over empty groups.
- (Update) Fix handling of utf-8 encoding in AtteanX::Parser::SPARQLXML.
- (Update) Improve Attean::API::Result->apply_map handling of unbound
variables.
- (Update) Improve Test::Attean::SPARQLSuite.
- (Update) Improve handling of XPath Constructor (casting) functions.
- (Update) Update module metadata URLs (#155 from @szabgab).
0.028 2020-11-02
- (Addition) Add uniq method on iterators over objects with an as_string
method.
- (Update) Added Attean::API::RepeatableIterator->size method (#89).
- (Update) Fix Attean::QuadModel->get_quads when called with an empty term
set in some position.
- (Update) Fix utf8 handling of syntax tests in dawg test harness.
- (Update) Improve documentation about statement projection accessors
(e.g. subjects) not being unique (#152).
- (Update) Remove AtteanX::Store::DBI which was not a real DBI store and
was accidentally checked-in (#134).
- (Update) Switch UUID dependency from Data::UUID to UUID::Tiny (#145).
0.026 2020-02-20
- (Addition) Added Attean::API::Model->evaluate convenience method (#149,
#150).
- (Update) Fix typo in Attean::Plan::Service POD (#146).
- (Update) Improve type coercions (#148 from @kjetilk).
0.025 2019-10-25
- (Update) Fix Moo::Role/Role::Tiny imports (#141, #142 from @haarg).
0.024 2019-09-22
- (Addition) Add attribute in AbbreviatingSerializer to omit base
declaration to have all relative URIs (#135 from @kjetilk).
- (Update) Added ground_blanks attribute to Attean::SimpleQueryEvaluator.
- (Update) Fixed bug in AtteanX::API::Lexer that caused infinite recursion
when finding EOF in the middle of an escape sequence.
- (Update) Updates to use namespace types, available in Types::Attean
(#129, #137 from @kjetilk).
0.024 2019-04-30
- (Addition) Add a simple factory for temporary models (#132 from
@kjetilk).
- (Update) Document how to check whether a term looks like the head of an
rdf:List (#133 from @kjetilk).
- (Update) Removed the deprecated parse_term_from_string method from
NTuples and Turtle parsers (#131).
0.022 2019-03-21
- (Addition) Add Attean::API::TermOrVariable->is_bound method (#129 from
@kjetilk).
- (Addition) Added statement matching functionality for iterators.
0.021 2019-02-12
- (Addition) Added Attean::API::Model->algebra_holds method.
0.020 2019-01-09
- (Addition) Add holds handle to Model (from @kjetilk).
- (Addition) Added bgp export function in Attean::RDF with associated
tests (#125 from @kjetilk).
- (Update) Export using Exporter::Tiny instead of Exporter.pm (#122 from
@tobyink).
- (Update) Expose count_quads_estimate method at the model level.
- (Update) Make count_quad_estimate accessible from TripleModel (#124 from
@kjetilk).
0.019 2018-02-04
- (Update) Documentation updates (#120, #121 from @kjetilk).
- (Update) Fix incorrect URI for langString (#119 from @kjetilk).
0.018 2018-01-06
- (Update) Added tests for turtle parser escape handling (#55).
- (Update) Allow UUIDs to have lowercase hex digits (#102).
- (Update) Documentation fixes (#105 from @Varadinsky).
- (Update) Fixed as_string serialization of CONSTRUCT algebras (#97).
- (Update) Improve code coverage for Attean::TermMap (#107 from
@Varadinsky).
- (Update) Improvements to HashJoin query planning (#103 from @KjetilK).
- (Update) Removed LICENSE file and updated licensing statement in
individual modules (#116).
- (Update) Updated Makefile.PL for perl 5.26.
- (Update) Updated required version of IRI (#118).
- (Update) Use Moo::Role instead of namespace::clean to cleanup namespaces
(#112 from @baby-gnu).
0.017 2016-06-09
- (Addition) Port SPARQL-JSON serializer to Attean (#20, #101 from
@cakirke).
- (Update) Add a .gitignore file (#99 from @cakirke).
- (Update) Changed use of binmode to `use open` in attean_parse and
attean_query.
- (Update) Fix Construct plan string serialization.
- (Update) Fix declared arity of various algebra classes.
- (Update) Fixed bug in handling of restricted available named graphs
during query planning.
- (Update) Fixed documentation in Attean::QueryPlanner.
- (Update) Improved handling of unexpected EOF in AtteanX::Parser::SPARQL.
- (Update) Improved test coverage.
- (Update) Improved use of Travis CI (#100 from @cakirke).
- (Update) Make parse_term_from_string deprecations noisy.
- (Update) Removed default implementation of
Attean::API::Plan->plan_as_string.
- (Update) Updated SPARQL parser to produce Attean::Algebra::Reduced
algebra objects for REDUCED queries.
- (Update) Updated required versions of Moo and Test::Modern.
0.016 2016-05-04
- (Addition) Ported RDF::Trine::Serializer::RDFXML to
AtteanX::Serializer::RDFXML (#22).
- (Update) Add serialization of SPARQL PREFIX declarations and prefixnames
when namespaces are set (#53).
- (Update) Added Test::Attean::QuadStore->cleanup_store method.
- (Update) Added Test::Attean::StoreCleanup role and added store cleanup
to store tests.
- (Update) Changed Attean::TriplePattern->as_quadpattern to delegate to
Attean::API::TriplePattern->as_quad_pattern.
- (Update) Fix overly aggressive code that attempted to turn IRIs into
prefix names during Turtle serialization.
- (Update) Fixed bug in SPARQL parsing of NIL tokens.
- (Update) Fixes to POD, test, and metadata issues reported by
jonassmedegaard (#93, #94, #95, #96).
- (Update) Improve Attean::SimpleQueryEvaluator to handle updated algebra
classes and iterator API.
- (Update) Improved test suite (includes #92 from KjetilK, #53).
- (Update) Removed AtteanX::RDFQueryTranslator (split into a new package)
and all other references to RDF::Query.
- (Update) Removed default implementation of Attean::API::Term->ebv (now
required of consumers).
- (Update) Serialize SPARQL and Turtle namespace declarations in a stable
order.
- (Update) Updated Attean::API::AbbreviatingParser->base definition to be
a consumer of Attean::API::IRI.
- (Update) Updated Attean::API::SPARQLSerializable->as_sparql to return a
unicode string, not bytes.
0.015 2016-04-09
- (Update) Fixed metadata used to generate README files.
0.014 2016-04-09
- (Addition) Add a size estimate attribute to Attean::Plan::Iterator (#90
from KjetilK).
- (Addition) Added Attean::Plan::Iterator for cases where there is too
much data for Attean::Plan::Table (#88).
- (Update) Add ability for parsers to construct lazy IRIs.
- (Update) Add type checking to serialize_iter_* methods.
- (Update) Added Attean::ListIterator->size method (#89).
- (Update) Fix cases where result iterators were constructed without a
variables list.
- (Update) Improve error message generated for some SPARQL syntax errors.
- (Update) Update Attean::FunctionExpression to canonicalize ISURI to
ISIRI.
0.013 2016-03-19
- (Addition) Added Attean::API::BulkUpdatableStore role.
- (Addition) Added Attean::API::MutableModel->load_urls_into_graph method.
- (Addition) Added Attean::API::QuadPattern->as_triple_pattern method.
- (Addition) Added Attean::API::TripleOrQuadPattern->parse and
AtteanX::Parser::SPARQL->parse_nodes methods (#82).
- (Addition) Added Attean::Algebra::Query to indicate a full query trees
and aid in serialization (#67).
- (Addition) Added AtteanX::SPARQL::Token->integer constructor.
- (Addition) Added parsing, algebra, planning, and test support for SPARQL
1.1 Updates.
- (Update) Add and use Attean::Algebra::Query->subquery flag when
appropriate and stop generating needless unary join algebras.
- (Update) Add child accessor to Attean::API::UnaryQueryTree.
- (Update) Added CONTRIBUTING file.
- (Update) Allow producing short blank node labels in attean_query
results.
- (Update) Check types of invocant and model objects in calls to
cost_for_plan planning method (#77).
- (Update) Fix Attean::API::IDPJoinPlanner->cost_for_plan to pass the
planner object in calls to the model.
- (Update) Fix Attean::Algebra::Update->blank_nodes (#70).
- (Update) Fix Attean::QueryPlanner active_graphs argument during
recursive call to plans_for_algebra.
- (Update) Fix lost in-scope variables in aggregation algebra and plans
(#78).
- (Update) Fix result iterator generation for quad patterns to keep
associated variable names.
- (Update) Fix serialization of SILENT flag on Service queries.
- (Update) Fix sparql_tokens generation for quad patterns to use SPARQL
GRAPH syntax, not N-Quads syntax.
- (Update) Fixed bug in Attean::Literal that was returning rdf:string
instead of rdf:langString for language literals.
- (Update) Improve error messages in Attean::CodeIterator and
Attean::API::Binding.
- (Update) Improve errors and logging in SPARQL parser (#84 from KjetilK).
- (Update) Improve handling of utf8 encoding in SPARQL/XML, algebra, and
plan serializations.
- (Update) Improve temporary variable names in aggregates generated during
parsing.
- (Update) Improved Attean::Plan::Union to handle plans with zero
children.
- (Update) Improved error message in query planners (#76 from KjetilK).
- (Update) Pass tree depth as argument to algebra_as_string.
- (Update) Refactored SPARQL 1.1 test harness into a testing role (#80).
- (Update) Update bin/attean_query to allow dryruns to avoid generating
query plans when appropriate.
- (Update) Updated attean_query to allow updates.
0.012 2016-02-04
- (Addition) Added Attean::API::TermOrVariable->apply_binding method.
- (Addition) Added AtteanX::Store::SimpleTripleStore.
- (Update) Die on attempts to add non-ground triples/quads to stores
(#66).
- (Update) Fixed Attean::Algebra::Table to consume
Attean::API::NullaryQueryTree instead of Attean::API::UnaryQueryTree.
- (Update) Fixed type checks performed when ATTEAN_TYPECHECK is set.
- (Update) Improve error reporting for unexpected EOF in
AtteanX::Parser::SPARQL.
- (Update) Throwing an error when Triple or Quad objects gets passed a
variable (#65 from KjetilK).
- (Update) Add planning support for DESCRIBE queries (#45).
- (Update) Add type checking to store get_triples and get_quads methods
(#61).
- (Update) Added logging in QueryPlanner and TreeRewriter (#64 from
KjetilK).
- (Update) Avoid attempting to parse empty XML documents when passed in as
a scalar (#60).
- (Update) Fix Attean::CodeIterator type checking to handle non-blessed
items properly.
- (Update) Fix AtteanX::Parser::RDFXML to properly use caller-supplied
base IRI.
- (Update) Fix algebra generation for describe queries in SPARQL parser.
- (Update) Fix bug in Attean::Plan::Aggregate handling of COUNT(*)
queries.
- (Update) Fix bugs in SPARQL CSV and TSV serializers.
- (Update) Fix sparql_tokens generation for integer and datatyped
literals.
- (Update) Fixed AtteanX::Parser::SPARQL to maintain its URI::NamespaceMap
on prefix declarations.
- (Update) Improve POD and test coverage (#55; #61 from KjetilK).
- (Update) Improve attean_parse and attean_parse including preservation of
prefix declarations where possible.
- (Update) Improve regex escaping in t/algebra.t to silence warnings in
perl 5.22.
- (Update) Improve use of SPARQL and Turtle token objects.
- (Update) Improved triple model classes to allow adding and droping
triple store graphs.
- (Update) Merge code paths for canonical NTriples serializer.
- (Update) Preserve in-scope variables in result iterators.
- (Update) Serialize SPARQL/XML bindings in a stable order.
- (Update) Simplify cost estimation code for hash joins in
Attean::API::QueryPlanner (#59 from KjetilK).
- (Update) Update SPARQL parser to die on unimplemented Update syntax.
- (Update) Update SPARQL/HTML serializer to implement
AbbreviatingSerializer (#54, #63 from Zoran Varadinsky).
- (Update) Update turtle serializer to consume
Attean::API::AppendableSerializer.
- (Update) Updated prerequisites in Makefile.PL and .travis.yml.
- (Update) Use Test::Modern.
0.011 2016-01-16
- (Addition) Add initial implementation for Attean::MutableTripleModel.
- (Addition) Add logging of costs to query planner (#56 from KjetilK).
- (Addition) Add use of MooX::Log::Any (from KjetilK).
- (Addition) Added
Attean::API::Plan->subplans_of_type_are_variable_connected method.
- (Addition) Added Attean::API::Plan->children_are_variable_connected.
- (Addition) Added AtteanX::Parser::SPARQL->parse convenience method.
- (Addition) Added RDF/XML parser tests.
- (Addition) Added Turtle serializer.
- (Addition) Added exportable quadpattern constructor.
- (Addition) Added tests for get_sequence model accessor method (#3).
- (Update) Change API for Attean::API::CostPlanner->cost_for_plan to pass
in the query planner.
- (Update) Fix bug in handling unbound join variables in hash join
evaluation.
- (Update) Fix use of blank and variable shortcut constructors (#57 from
KjetilK).
- (Update) Fixed bug in
AtteanX::Serializer::SPARQLHTML->serialize_iter_to_bytes.
- (Update) Implementation of canonicalize method for triple and quad
patterns (#43 from KjetilK).
- (Update) Improve Attean::ExistsExpression->as_string.
- (Update) Improve cost estimation for cartesian joins in
Attean::API::QueryPlanner.
- (Update) Improved SPARQL serialization of algebra and expression trees
(including #51).
- (Update) Improved error handling in Attean::ListIterator->BUILD.
- (Update) Improved recognition of invalid aggregation queries.
- (Update) Make regexes used for prefixname parsing publicly accessibly.
- (Update) Merged shared constants for Turtle and SPARQL tokens.
- (Update) Moved subpatterns_of_type from Attean::API::Algebra to
Attean::API::DirectedAcyclicGraph.
- (Update) Renamed parse_term_from_string methods to parse_term_from_bytes
(adding delegating methods that should be decprecated in the future).
- (Update) Silence XML::Parser warnings on empty input documents.
- (Update) Update AtteanX::Parser::RDFXML to populate a namespace map
during parsing.
- (Update) Updated Attean::API::CanonicalizingBindingSet to produce the
same type of object as are input.
- (Update) Updated copyright years.
0.010 2015-12-22
- (Addition) Add INVOKE function expression to allow representing
IRI-defined functions.
- (Addition) Added Attean::API::Algebra methods blank_nodes and
subpatterns_of_type.
- (Addition) Added Attean::API::SimpleCostPlanner.
- (Addition) Added Attean::API::UnionScopeVariablesPlan role to handle
common computation of in-scope variables (Github issue #38).
- (Addition) Added Attean::Algebra::Sequence class.
- (Addition) Added AtteanX::API::JoinRotatingPlanner role.
- (Addition) Added SPARQL parsing support for RANK operator (Github issue
#35).
- (Addition) Added initial algebra and plan support for group ranking
(Github issue #34).
- (Addition) Added simple SPARQL HTML serializer (ported from
RDF::Endpoint; Github issue #27).
- (Addition) Added simple SPARQL serializer implementation (Github issue
#36).
- (Update) Added ability to turn some query algebras into SPARQL token
interators.
- (Update) Compute in-scope variables in Attean::Plan::Quad instead of
relying on calling code (Github issue #39).
- (Update) Ensure query plan costs are integers, fixing a bug when running
on perl with long doubles (#42).
- (Update) Fixed attean_query to support custom output serializers.
- (Update) Fixed bug in Attean::Algebra::Project->in_scope_variables.
- (Update) Fixed bug in t/http-negotiation.t that caused false failures
when negotiation led to the Canonical NTriples serializer.
- (Update) Fixed mis-named method call in AtteanX::Store::Memory.
- (Update) Improve error messages in query planning code (manual patch
from #41).
- (Update) Improve serializer negotiation to support multiple classes that
handle the same media type.
- (Update) Ported RDF::Query SPARQL parser to Attean.
- (Update) Refactored query planner to separate IDP code from the core
planning code.
- (Update) Renamed Attean::API::Planner to Attean::API::QueryPlanner and
re-organized planning code.
- (Update) Update Changes metadata handling to use
Module::Instal::DOAPChangeSets (Github issue #25).
- (Update) Updated Attean::Algebra::Join to be n-ary, not binary.
- (Update) Updated attean_query to use the native SPARQL parser.
0.009 2015-11-04
- (Addition) Added Attean::API::Result->shared_domain method.
- (Update) Improve handling on unicode data in SPARQL TSV parser.
- (Update) Improve query planner and plan implementations to support
SPARQL 1.1 test suite.
- (Update) Removed HeapSort plan implementation and use of Array::Heap due
to packaging concerns (issue #32).
0.008 2015-08-18
- (Addition) Added Attean::API::Plan::Join role.
- (Addition) Added apply_triple and apply_quad methods to triple and quad
pattern classes to produce Result objects.
- (Addition) Added heap sort plan implementation.
- (Update) Attean::API::TripleOrQuadPattern constructors accept
non-existent parameters (#13).
- (Update) Consolidated BUILDARGS handling in
Attean::API::TripleOrQuadPattern.
- (Update) Moved computation of in_scope_variables from calling code to to
Plan class BUILDARGS.
0.007 2015-07-16
- (Addition) Added Attean::API::Binding->apply_bindings to bind additional
variables.
- (Addition) Added Attean::API::Binding->is_ground.
- (Addition) Added Attean::API::TriplePattern->as_triple,
Attean::API::QuadPattern->as_quad.
- (Update) Added evaluation support for REGEX functions.
- (Update) Fix Attean plugin loading to allow non-plugins nested below the
plugin namespace.
- (Update) Improve SPARQL serialization for IRIs and triple patterns.
- (Update) Improve SPARQL serialization of OPTIONAL and boolean literals.
- (Update) POD improvements (PR #15 from Kjetil Kjernsmo).
0.006 2015-06-30
- (Addition) Added
Attean::API::DirectedAcyclicGraph->has_only_subtree_types method.
- (Addition) Added Attean->acceptable_parsers method (GH issue #11).
- (Addition) Added methods to test terms and variables for common term
role consumption.
- (Update) Added HSP heuristics to Attean::IDPQueryPlanner (patch from
Kjetil Kjernsmo).
- (Update) Added documentation (patches from Kjetil Kjernsmo).
- (Update) Disable stable sortint in Attean::IDPQueryPlanner where it is
unnecessary (patch from Kjetil Kjernsmo).
- (Update) Fixed handling of blank nodes in BGPs in
Attean::IDPQueryPlanner.
- (Update) Updated Attean::IDPQueryPlanner->join_plans API to allow easier
extensibility.
- (Update) Updated attean_query to use the IDPQueryPlanner.
0.005 2015-05-27
- (Update) Add initial code to support interesting orders in
Attean::IDPQueryPlanner.
- (Update) Added Attean::Plan::Unique class.
- (Update) Added POD description of each Attean::Plan class.
- (Update) Added evaluation support for type checking functions (ISIRI,
ISLITERAL, etc.).
- (Update) Added planning support for Extend and Ask algebra operations.
- (Update) Added planning support for Unique plans for DISTINCT queries
which are already ordered.
- (Update) Added query planning tests.
- (Update) Added use Set::Scalar in lib/Attean/Algebra.pm.
- (Update) Allow store-planning of more than just BGPs in
Attean::TripleModel.
- (Update) Change use of ListIterator to CodeIterator in plan classes that
can be pipelined.
- (Update) Changed Attean::Plan::Filter to check the EBV of a single,
named variable binding.
- (Update) Fixed bug in IDPQueryPlanner->cost_for_plan to reflect recently
changed Attean::Plan::Quad API.
- (Update) Improve propagation of distinct and ordered attributes during
query planning.
- (Update) Improved query planning.
- (Update) Removed references to Attean::QueryEvaluator (obviated by
$plan->evaluate).
- (Update) Removed unused/unnecessary code and comments.
- (Update) Rename Attean::Plan::Distinct to Attean::Plan::HashDistinct
(making room for different implementation strategies).
- (Update) Renamed Attean::Plan::Filter to Attean::Plan::EBVFilter.
- (Update) Simplified implementation of Attean::Plan::Unique.
- (Update) Split handling of BGP and GGP join planning in
Attean::IDPQueryPlanner for easier subclass overriding.
- (Update) Updated Attean::Plan::Quad to consume Attean::API::QuadPattern.
- (Update) Updated IDP query planner to produce correct plans for empty
BGPs.
0.004 2015-05-18
- (Addition) Add Attean::ValueExpression->in_scope_variables method.
- (Addition) Add initial implementation of Attean::TripleModel.
- (Addition) Added Attean::API::Binding->values_consuming_role method.
- (Addition) Added Attean::TriplePattern->as_quadpattern method.
- (Addition) Added SPARQL CSV and XML serializers.
- (Addition) Added Test::Attean roles for caching quadstores.
- (Addition) Added Test::Attean::MutableTripleStore.
- (Addition) Added an IDP-based query planner and associated classes and
roles.
- (Addition) Added initial support for representing, translating, and
evaluating SERVICE patterns.
- (Update) Add SPARQL serialization support for Expression classes.
- (Update) Add algebra_as_string methods for some algebra classes missing
an implementation.
- (Update) Add variables to result iterators.
- (Update) Added Math::Cartesian::Product to prerequisite list.
- (Update) Added Test::Roo-based store tests.
- (Update) Added comments about handling of graphs in
Test::Attean::MutableQuadStore.
- (Update) Added missing use statements.
- (Update) Fix documentation of serialize_iter_to_io method.
- (Update) Fixed Attean->get_parser to accept media types with parameters.
- (Update) Fixed required version of perl in store test roles to be v5.14.
- (Update) Fixed serialization bug in
Attean::FunctionExpression->as_sparql.
- (Update) Improve SPARQL serialization for projection, slicing, ordering,
and distinct/reduced modifiers.
- (Update) Improve SPARQL serialization of algebra trees.
- (Update) Update Attean::API::Expression to consume
Attean::API::UnionScopeVariables.
- (Update) Updated AtteanX::Store::Memory to conform to both etag and time
caching roles.
- (Update) Updated Memory store matching methods to accept node arrays for
any quad pattern position.
0.003 2015-02-19
- (Addition) Added Attean::TreeRewriter class.
- (Addition) Added count estimate methods to TripleStore QuadStore roles
(in lieu of github pull request #6).
- (Addition) Added missing algebra_as_string impelementations in
Attean::API::Query and Attean::Algebra.
- (Addition) Added tree_attributes methods to tree classes.
- (Update) Fixed method name typo in
Attean::API::TimeCacheableTripleStore.
- (Update) Split Cacheable roles into ETagCacheable and TimeCacheable
variants.
0.002 2014-10-15
- (Addition) Added Attean->negotiate_serializer method.
- (Addition) Added POD for many classes and roles.
- (Update) Changed media_type attributes to class methods in Serializer
classes.
- (Update) Moved RDF::Query algebra translator to
AtteanX::RDFQueryTranslator.
- (Update) Switched from Sub::Name to Sub::Util (github issue #5).
- (Update) Updated Attean->get_serializer to support media_type argument.
- (Update) Wrap mutating methods in a single bulk-update.
0.001 2014-09-27
- (Addition) Initial release.
Attean-0.034/MANIFEST 000644 000765 000024 00000012413 14636711137 014166 0 ustar 00greg staff 000000 000000 bin/attean_parse
bin/attean_query
bin/canonicalize_bgp.pl
Changes
CONTRIBUTING
inc/Module/Install.pm
inc/Module/Install/AuthorTests.pm
inc/Module/Install/Base.pm
inc/Module/Install/Can.pm
inc/Module/Install/DOAPChangeSets.pm
inc/Module/Install/Fetch.pm
inc/Module/Install/Makefile.pm
inc/Module/Install/Metadata.pm
inc/Module/Install/Scripts.pm
inc/Module/Install/Win32.pm
inc/Module/Install/WriteAll.pm
lib/Attean.pm
lib/Attean/AggregateExpression.pod
lib/Attean/Algebra.pm
lib/Attean/API.pm
lib/Attean/API/AbbreviatingParser.pod
lib/Attean/API/AbbreviatingSerializer.pod
lib/Attean/API/AggregateExpression.pod
lib/Attean/API/AppendableSerializer.pod
lib/Attean/API/AtOnceParser.pod
lib/Attean/API/Binding.pm
lib/Attean/API/Blank.pod
lib/Attean/API/BlankOrIRI.pod
lib/Attean/API/BulkUpdatableModel.pod
lib/Attean/API/Expression.pm
lib/Attean/API/IRI.pod
lib/Attean/API/Iterator.pm
lib/Attean/API/Literal.pod
lib/Attean/API/MixedStatementParser.pod
lib/Attean/API/MixedStatementSerializer.pod
lib/Attean/API/Model.pm
lib/Attean/API/MutableModel.pod
lib/Attean/API/MutableTripleStore.pod
lib/Attean/API/Parser.pm
lib/Attean/API/Plan.pm
lib/Attean/API/PullParser.pod
lib/Attean/API/PushParser.pod
lib/Attean/API/Quad.pod
lib/Attean/API/QuadParser.pod
lib/Attean/API/QuadPattern.pod
lib/Attean/API/QuadSerializer.pod
lib/Attean/API/Query.pm
lib/Attean/API/QueryPlanner.pm
lib/Attean/API/RepeatableIterator.pod
lib/Attean/API/Result.pod
lib/Attean/API/ResultParser.pod
lib/Attean/API/ResultSerializer.pod
lib/Attean/API/Serializer.pm
lib/Attean/API/Store.pm
lib/Attean/API/Term.pm
lib/Attean/API/TermOrVariable.pod
lib/Attean/API/TermParser.pod
lib/Attean/API/TermSerializer.pod
lib/Attean/API/Triple.pod
lib/Attean/API/TripleOrQuad.pod
lib/Attean/API/TripleParser.pod
lib/Attean/API/TriplePattern.pod
lib/Attean/API/TripleSerializer.pod
lib/Attean/API/Variable.pod
lib/Attean/BindingEqualityTest.pm
lib/Attean/Blank.pm
lib/Attean/CodeIterator.pm
lib/Attean/Expression.pm
lib/Attean/IDPQueryPlanner.pm
lib/Attean/IRI.pm
lib/Attean/IteratorSequence.pm
lib/Attean/ListIterator.pm
lib/Attean/Literal.pm
lib/Attean/Plan.pm
lib/Attean/Quad.pm
lib/Attean/QuadModel.pm
lib/Attean/QueryPlanner.pm
lib/Attean/RDF.pm
lib/Attean/Result.pm
lib/Attean/SimpleQueryEvaluator.pm
lib/Attean/SPARQLClient.pm
lib/Attean/TermMap.pm
lib/Attean/TreeRewriter.pm
lib/Attean/Triple.pm
lib/Attean/TripleModel.pm
lib/Attean/Variable.pm
lib/AtteanX/API/JoinRotatingPlanner.pm
lib/AtteanX/API/Lexer.pm
lib/AtteanX/Functions/CompositeMaps.pm
lib/AtteanX/Functions/CompositeLists.pm
lib/AtteanX/Parser/NQuads.pm
lib/AtteanX/Parser/NTriples.pm
lib/AtteanX/Parser/NTuples.pm
lib/AtteanX/Parser/RDFXML.pm
lib/AtteanX/Parser/SPARQL.pm
lib/AtteanX/Parser/SPARQLJSON.pm
lib/AtteanX/Parser/SPARQLLex.pm
lib/AtteanX/Parser/SPARQLTSV.pm
lib/AtteanX/Parser/SPARQLXML.pm
lib/AtteanX/Parser/SPARQLXML/SAXHandler.pm
lib/AtteanX/Parser/Trig.pm
lib/AtteanX/Parser/Turtle.pm
lib/AtteanX/Parser/Turtle/Constants.pm
lib/AtteanX/Parser/Turtle/Lexer.pm
lib/AtteanX/Parser/Turtle/Token.pm
lib/AtteanX/Serializer/CanonicalNTriples.pm
lib/AtteanX/Serializer/NQuads.pm
lib/AtteanX/Serializer/NTriples.pm
lib/AtteanX/Serializer/NTuples.pm
lib/AtteanX/Serializer/RDFXML.pm
lib/AtteanX/Serializer/SPARQL.pm
lib/AtteanX/Serializer/SPARQLCSV.pm
lib/AtteanX/Serializer/SPARQLHTML.pm
lib/AtteanX/Serializer/SPARQLJSON.pm
lib/AtteanX/Serializer/SPARQLTSV.pm
lib/AtteanX/Serializer/SPARQLXML.pm
lib/AtteanX/Serializer/TextTable.pm
lib/AtteanX/Serializer/Turtle.pm
lib/AtteanX/Serializer/TurtleTokens.pm
lib/AtteanX/SPARQL/Constants.pm
lib/AtteanX/SPARQL/Token.pm
lib/AtteanX/Store/Memory.pm
lib/AtteanX/Store/Simple.pm
lib/AtteanX/Store/SimpleTripleStore.pm
lib/Test/Attean/ETagCacheableQuadStore.pm
lib/Test/Attean/MutableETagCacheableQuadStore.pm
lib/Test/Attean/MutableQuadStore.pm
lib/Test/Attean/MutableTimeCacheableQuadStore.pm
lib/Test/Attean/MutableTripleStore.pm
lib/Test/Attean/QuadStore.pm
lib/Test/Attean/SPARQLStarSuite.pm
lib/Test/Attean/SPARQLSuite.pm
lib/Test/Attean/StoreCleanup.pm
lib/Test/Attean/TimeCacheableQuadStore.pm
lib/Test/Attean/TripleStore.pm
lib/Test/Attean/W3CManifestTestSuite.pm
lib/Types/Attean.pm
Makefile.PL
MANIFEST This list of files
META.yml
meta/changes.ttl
README.md
t/00.load.t
t/algebra.t
t/binding-equality.t
t/binding.t
t/convenience.t
t/cost_planner.t
t/export-functions.t
t/expression.t
t/http-negotiation.t
t/idp_planner.t
t/iter.t
t/join_rotating_planner.t
t/model-quad.t
t/model-triple.t
t/naive_planner.t
t/parser_serializer_api.t
t/parser-nquads.t
t/parser-ntriples.t
t/parser-rdfxml.t
t/parser-sparql-star.t
t/parser-sparql.t
t/parser-sparqljson.t
t/parser-sparqltsv.t
t/parser-sparqlxml.t
t/parser-turtle-star.t
t/parser-turtle.t
t/parser.t
t/plan.t
t/plans.t
t/serializer-canonicalntriples.t
t/serializer-nquads.t
t/serializer-ntriples.t
t/serializer-rdfxml.t
t/serializer-sparql.t
t/serializer-sparqlcsv.t
t/serializer-sparqlhtml.t
t/serializer-sparqljson.t
t/serializer-sparqltsv.t
t/serializer-sparqlxml.t
t/serializer-turtle.t
t/serializer.t
t/simple-eval.t
t/simple.t
t/store-memory.t
t/store-simple.t
t/store-simpletriple.t
t/term-map.t
t/term.t
t/treerewrite.t
t/types-general.t
t/types-iri.t
xt/dawg11-memory.t
xt/eval-sparql-star-memory-simpleeval.t
xt/eval-sparql-star-memory.t
xt/pod-coverage.t
xt/pod.t
SIGNATURE Public-key signature (added by MakeMaker)
Attean-0.034/meta/ 000755 000765 000024 00000000000 14636711137 013762 5 ustar 00greg staff 000000 000000 Attean-0.034/t/ 000755 000765 000024 00000000000 14636711137 013277 5 ustar 00greg staff 000000 000000 Attean-0.034/xt/ 000755 000765 000024 00000000000 14636711137 013467 5 ustar 00greg staff 000000 000000 Attean-0.034/README.md 000644 000765 000024 00000002253 14273070744 014314 0 ustar 00greg staff 000000 000000 Attean Semantic Web Framework
=============================
Attean is a Perl framework for working with RDF data and SPARQL queries.
It features parsers and serializers for many different RDF formats including
RDF/XML, Turtle, N-Triples and N-Quads, as well as SPARQL formats like
SPARQL-XML, SPARQL-JSON, SPARQL-CSV and SPARQL-TSV.
Attean features support for SPARQL 1.1 queries, and a set of APIs and command
line tools to parse, transform, query, and serialize RDF data.
Getting Attean
--------------
Attean is available from:
* [GitHub](https://github.com/kasei/attean/)
* [CPAN](https://metacpan.org/release/Attean)
And is also available as [Debian packages](https://packages.qa.debian.org/liba/libattean-perl.html).
Getting Help
------------
A group of perl-rdf developers are usually available in the
[perlrdf IRC channel](irc://irc.perl.org/perlrdf) where we're happy to answer
questions.
You can also:
* Create a new [GitHub Issue](https://github.com/kasei/attean/issues) or submit a pull request
Licensing
---------
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
Attean-0.034/META.yml 000644 000765 000024 00000003017 14636711132 014301 0 ustar 00greg staff 000000 000000 ---
abstract: 'A Semantic Web Framework'
author:
- 'Gregory Todd Williams C<< >>'
- 'Gregory Todd Williams '
build_requires:
ExtUtils::MakeMaker: 6.59
HTTP::Headers: 0
HTTP::Message::PSGI: 0
Regexp::Common: 0
Test::Exception: 0
Test::LWP::UserAgent: 0
Test::More: 0.88
Test::Requires: 0
XML::Simple: 0
configure_requires:
ExtUtils::MakeMaker: 6.59
distribution_type: module
dynamic_config: 1
generated_by: 'Module::Install version 1.21'
license: perl
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: 1.4
name: Attean
no_index:
directory:
- inc
- t
- xt
requires:
Algorithm::Combinatorics: 0
DateTime::Format::W3CDTF: 0
Exporter::Tiny: 1
File::Slurp: 0
HTTP::Negotiate: 0
IRI: 0.005
JSON: 0
LWP::UserAgent: 0
List::MoreUtils: 0
Math::Cartesian::Product: 1.008
Module::Pluggable: 0
Moo: 2.000002
MooX::Log::Any: 0
PerlIO::Layers: 0
Role::Tiny: 2.000003
Set::Scalar: 0
Sub::Install: 0
Sub::Util: 1.4
Test::Modern: 0.012
Test::Moose: 0
Test::Roo: 0
Test::TypeTiny: 0
Text::CSV: 0
Text::Table: 0
Try::Tiny: 0
Type::Tiny: 0
URI::Escape: 1.36
URI::NamespaceMap: 0.12
UUID::Tiny: 0
XML::SAX: 0
namespace::clean: 0
perl: 5.14.0
resources:
IRC: irc://irc.perl.org/#perlrdf
bugtracker: https://github.com/kasei/attean/issues
homepage: https://metacpan.org/release/Attean
license: http://dev.perl.org/licenses/
repository: https://github.com/kasei/attean/
version: '0.034'
Attean-0.034/lib/ 000755 000765 000024 00000000000 14636711137 013602 5 ustar 00greg staff 000000 000000 Attean-0.034/Makefile.PL 000644 000765 000024 00000003735 14632645502 015014 0 ustar 00greg staff 000000 000000 use strict;
use warnings;
use lib '.';
use inc::Module::Install;
name 'Attean';
all_from 'lib/Attean.pm';
author 'Gregory Todd Williams ';
license 'perl';
test_requires 'HTTP::Message::PSGI' => 0;
test_requires 'Regexp::Common' => 0;
test_requires 'Test::Exception' => 0;
test_requires 'Test::Requires' => 0;
test_requires 'Test::LWP::UserAgent' => 0;
test_requires 'Test::More' => 0.88;
test_requires 'XML::Simple' => 0;
test_requires 'HTTP::Headers' => 0;
perl_version '5.014';
requires 'Algorithm::Combinatorics' => 0;
requires 'UUID::Tiny' => 0;
requires 'DateTime::Format::W3CDTF' => 0;
requires 'Exporter::Tiny' => 1.000000;
requires 'File::Slurp' => 0;
requires 'HTTP::Negotiate' => 0;
requires 'IRI' => 0.005;
requires 'JSON' => 0;
requires 'List::MoreUtils' => 0;
requires 'LWP::UserAgent' => 0;
requires 'Math::Cartesian::Product' => 1.008;
requires 'Module::Pluggable' => 0;
requires 'Moo' => 2.000002;
requires 'MooX::Log::Any' => 0;
requires 'namespace::clean' => 0;
requires 'PerlIO::Layers' => 0;
requires 'Role::Tiny' => 2.000003;
requires 'Set::Scalar' => 0;
requires 'Sub::Install' => 0;
requires 'Sub::Util' => 1.40;
requires 'Test::Modern' => 0.012;
requires 'Test::Moose' => 0;
requires 'Test::Roo' => 0;
requires 'Test::TypeTiny' => 0;
requires 'Text::CSV' => 0;
requires 'Text::Table' => 0;
requires 'Try::Tiny' => 0;
requires 'Type::Tiny' => 0;
requires 'URI::Escape' => 1.36;
requires 'URI::NamespaceMap' => 0.12;
requires 'XML::SAX' => 0;
resources(
'homepage' => "https://metacpan.org/release/Attean",
'repository' => "https://github.com/kasei/attean/",
'bugtracker' => "https://github.com/kasei/attean/issues",
'IRC' => "irc://irc.perl.org/#perlrdf",
);
author_tests('xt');
install_script glob('bin/attean_*');
write_doap_changes "meta/changes.ttl", "Changes", "turtle";
sign if ! exists $ENV{CI};
WriteAll;
Attean-0.034/lib/Types/ 000755 000765 000024 00000000000 14636711137 014706 5 ustar 00greg staff 000000 000000 Attean-0.034/lib/Test/ 000755 000765 000024 00000000000 14636711137 014521 5 ustar 00greg staff 000000 000000 Attean-0.034/lib/PaxHeader/Attean.pm 000644 000765 000024 00000000225 14636707632 017331 x ustar 00greg staff 000000 000000 30 mtime=1719373722.497833324
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean.pm 000644 000765 000024 00000035321 14636707632 015365 0 ustar 00greg staff 000000 000000 =head1 NAME
Attean - A Semantic Web Framework
=head1 VERSION
This document describes Attean version 0.034
=head1 SYNOPSIS
use Attean;
use Attean::RDF qw(iri);
my $store = Attean->get_store('Memory')->new();
my $parser = Attean->get_parser('NTriples')->new();
# iterator of triples and quads
my $iter = $parser->parse_iter_from_io(\*STDIN);
# add a graph name to all triples
my $graph = iri('http://graph-name/');
my $quads = $iter->as_quads($graph);
$store->add_iter($quads);
my $model = Attean::QuadModel->new( store => $store );
my $iter = $model->get_quads();
while (my $quad = $iter->next) {
say $quad->object->ntriples_string;
}
# run a SPARQL query and iterate over the results
my $sparql = 'SELECT * WHERE { ?s ?p ?o }';
my $s = Attean->get_parser('SPARQL')->new();
my ($algebra) = $s->parse($sparql);
my $results = $model->evaluate($algebra, $graph);
while (my $r = $results->next) {
say $r->as_string;
}
=head1 DESCRIPTION
Attean provides APIs for parsing, storing, querying, and serializing
Semantic Web (RDF and SPARQL) data.
=head1 METHODS
=over 4
=cut
package Attean {
use v5.14;
use warnings;
our $VERSION = '0.034';
use Attean::API;
use Attean::Blank;
use Attean::Literal;
use Attean::Variable;
use Attean::IRI;
use Attean::Triple;
use Attean::Quad;
use Attean::Result;
use Attean::QuadModel;
use Attean::TripleModel;
use Attean::BindingEqualityTest;
use Attean::CodeIterator;
use Attean::ListIterator;
use Attean::IteratorSequence;
use Attean::IDPQueryPlanner;
use Attean::TermMap;
use HTTP::Negotiate qw(choose);
use List::MoreUtils qw(any all);
use Module::Load::Conditional qw(can_load);
use Role::Tiny ();
use Sub::Util qw(set_subname);
use namespace::clean;
use Module::Pluggable search_path => 'AtteanX::Parser', sub_name => 'parsers', max_depth => 3;
use Module::Pluggable search_path => 'AtteanX::Serializer', sub_name => 'serializers', max_depth => 3;
use Module::Pluggable search_path => 'AtteanX::Store', sub_name => 'stores', max_depth => 3;
sub import {
my $class = shift;
if (scalar(@_)) {
my %args = @_;
foreach my $p (@{ $args{parsers} || [] }) {
# warn "Loading $p parser...";
$class->get_parser($p) || die "Failed to load parser: $p";
}
foreach my $s (@{ $args{serializers} || [] }) {
# warn "Loading $s serializer...";
$class->get_serializer($s) || die "Failed to load serializer: $s";
}
foreach my $s (@{ $args{stores} || [] }) {
# warn "Loading $s store...";
$class->get_store($s) || die "Failed to load store: $s";
}
}
}
=item C<< get_store( $NAME ) >>
Attempts to find a L implementation with the
given C<< $NAME >>. This is done using L and will generally
be searching for class names C<< AtteanX::Store::$NAME >>.
Returns the full class name if a matching implementation is found, otherwise
returns undef.
=cut
sub get_store {
my $self = shift;
return $self->_get_plugin('stores', shift);
}
=item C<< temporary_model >>
Returns a temporary, mutable quad model based on a L store.
=cut
sub temporary_model {
my $self = shift;
return Attean::MutableQuadModel->new( store => $self->get_store('Memory')->new() )
}
=item C<< get_serializer( $NAME ) >>
=item C<< get_serializer( filename => $FILENAME ) >>
=item C<< get_serializer( media_type => $MEDIA_TYPE ) >>
Attempts to find a L serializer class with the given
C<< $NAME >>, or that can serialize files with the C<< $MEDIA_TYPE >> media
type.
Returns the full class name if a matching implementation is found, otherwise
returns undef.
=cut
sub get_serializer {
my $self = shift;
my $role = 'Attean::API::Serializer';
if (scalar(@_) == 1) {
my $name = shift;
my $p = $self->_get_plugin('serializers', $name, $role);
return $p if $p;
foreach my $type (qw'filename media_type') {
my $p = $self->get_serializer($type => $name);
return $p if $p;
}
return;
}
my $type = shift;
my %method = (filename => 'file_extensions', media_type => 'media_types');
if (my $method = $method{ $type }) {
my $value = shift;
$value =~ s/^.*[.]// if ($type eq 'filename');
$value =~ s/;.*$// if ($type eq 'media_type');
foreach my $p ($self->serializers()) {
if (can_load( modules => { $p => 0 })) {
next unless ($p->does($role));
my @exts = @{ $p->$method() };
return $p if (any { $value eq $_ } @exts);
}
}
return;
} else {
die "Not a valid constraint in get_serializer call: $type";
}
}
=item C<< get_parser( $NAME ) >>
=item C<< get_parser( filename => $FILENAME ) >>
=item C<< get_parser( media_type => $MEDIA_TYPE ) >>
Attempts to find a L parser class with the given
C<< $NAME >>, or that can parse files with the same extension as
C<< $FILENAME >>, or that can parse files with the C<< $MEDIA_TYPE >> media
type.
Returns the full class name if a matching implementation is found, otherwise
returns undef.
=cut
sub get_parser {
my $self = shift;
my $role = 'Attean::API::Parser';
if (scalar(@_) == 1) {
my $name = shift;
my $p = $self->_get_plugin('parsers', $name, $role);
return $p if $p;
foreach my $type (qw'filename media_type') {
my $p = $self->get_parser($type => $name);
return $p if $p;
}
return;
}
while (my $type = shift) {
my %method = (filename => 'file_extensions', media_type => 'media_types');
if (my $method = $method{ $type }) {
my $value = shift;
$value =~ s/^.*[.]// if ($type eq 'filename');
$value =~ s/;.*$// if ($type eq 'media_type');
foreach my $p ($self->parsers()) {
if (can_load( modules => { $p => 0 })) {
next unless ($p->can('does') and $p->does($role));
my @exts = @{ $p->$method() };
return $p if (any { $value eq $_ } @exts);
}
}
} else {
die "Not a valid constraint in get_parser call: $type";
}
}
return;
}
{
my %roles = (
serializers => 'Attean::API::Serializer',
parsers => 'Attean::API::Parser',
stores => 'Attean::API::Store',
);
for my $method (keys %roles) {
my $role = $roles{$method};
my $code = sub {
my $self = shift;
my @classes;
foreach my $class ($self->$method()) {
next unless (can_load( modules => { $class => 0 }));
push(@classes, $class) if ($class->can('does') and $class->does($role));
}
return @classes;
};
Sub::Install::install_sub({
code => set_subname("list_${method}", $code),
as => "list_${method}"
});
}
}
sub _get_plugin {
my $self = shift;
my $type = shift;
my $name = shift;
my @roles = @_;
foreach my $p ($self->$type()) {
if (lc(substr($p, -(length($name)+2))) eq lc("::$name")) {
unless (can_load( modules => { $p => 0 })) {
warn $Module::Load::Conditional::ERROR;
return;
}
foreach (@roles) {
unless ($p->does($_)) {
die ucfirst($type) . " class $p failed validation for role $_";
}
}
return $p;
}
}
}
=item C<< negotiate_serializer ( request_headers => $request_headers, restrict => \@serializer_names, extend => \%media_types ) >>
Returns a two-element list containing an appropriate media type and
L class as decided by L. If the
C<< 'request_headers' >> key-value is supplied, the C<< $request_headers >> is
passed to C<< HTTP::Negotiate::choose >>. The option C<< 'restrict' >>, set to
a list of serializer names, can be used to limit the serializers to choose from.
Finally, an C<<'extend'>> option can be set to a hashref that contains
MIME-types as keys and a custom variant as value. This will enable the user to
use this negotiator to return a type that isn't supported by any serializers.
The subsequent code will have to find out how to return a representation.
=cut
sub negotiate_serializer {
my $class = shift;
my %options = @_;
my $headers = delete $options{ 'request_headers' };
my $restrict = delete $options{ 'restrict' };
my $extend = delete $options{ 'extend' } || {};
my %serializer_names;
my %media_types;
foreach my $sclass ($class->list_serializers) {
my $name = $sclass =~ s/^.*://r;
$serializer_names{lc($name)} = $sclass;
for (@{ $sclass->media_types }) {
push(@{ $media_types{$_} }, $sclass);
}
}
my %sclasses;
if (ref($restrict) && ref($restrict) eq 'ARRAY') {
foreach (@$restrict) {
if (my $sclass = $serializer_names{lc($_)}) {
$sclasses{ $sclass } = 1;
}
}
} else {
%sclasses = reverse %serializer_names;
}
my @default_variants;
while (my($type, $sclasses) = each(%media_types)) {
foreach my $sclass (@$sclasses) {
next unless $sclasses{$sclass};
my $qv;
# slightly prefer turtle as a readable format to others
# try hard to avoid using ntriples as 'text/plain' isn't very useful for conneg
if ($type eq 'application/n-triples') {
$qv = 1.0;
} elsif ($type eq 'text/plain') {
$qv = 0.2;
} else {
$qv = 0.99;
$qv -= 0.01 if ($type =~ m#/x-#); # prefer non experimental media types
$qv -= 0.01 if ($type =~ m#^application/(?!rdf[+]xml)#); # prefer standard rdf/xml to other application/* formats
}
push(@default_variants, [$type, $qv, $type]);
}
}
my %custom_thunks;
my @custom_variants;
while (my($type,$thunk) = each(%$extend)) {
push(@custom_variants, [$thunk, 1.0, $type]);
$custom_thunks{ $thunk } = [$type, $thunk];
}
# remove variants with media types that are in custom_variants from @variants
my @variants = grep { not exists $extend->{ $_->[2] } } @default_variants;
push(@variants, @custom_variants);
my $stype = choose( \@variants, $headers );
if (defined($stype) and $custom_thunks{ $stype }) {
my $thunk = $stype;
my $type = $custom_thunks{ $stype }[0];
return ($type, $thunk);
}
if (defined($stype) and my $sclasses = $media_types{ $stype }) {
return ($stype, $sclasses->[0]);
} else {
die "No appropriate serializer found for content-negotiation: " . Data::Dumper->Dump([$headers, $restrict, $extend], [qw(headers restrict extend)]);
}
}
=item C<< acceptable_parsers ( handles => $item_role, prefer => $parser_role ) >>
Returns a string value expressing the media types that are acceptable to the
parsers available to the system. This string may be used as an 'Accept' HTTP
header value.
If a C<< handles >> role is supplied, only parsers that produce objects that
conform to C<< $item_role >> will be included.
If a C<< prefer >> role is supplied, only parsers that conform to
C<< $parser_role >> will be included.
Parsers are given a quality-value (expressing a preferred order or use) based
on the roles each parser consumes. Parsers consuming L
are preferred, while those consuming L are not
preferred. An exact ordering between parsers consuming similar roles is
currently undefined.
=cut
sub acceptable_parsers {
my $class = shift;
my %options = @_;
my $handles = delete $options{ 'handles' };
my $prefer = delete $options{ 'prefer' };
if (defined($handles) and $handles !~ /::/) {
$handles = ucfirst(lc($handles));
$handles = "Attean::API::$handles";
}
if (defined($prefer) and $prefer !~ /::/) {
$prefer = "Attean::API::" . ucfirst($prefer);
$prefer = "${prefer}Parser" unless ($prefer =~ /Parser$/);
}
my %media_types;
foreach my $pclass ($class->list_parsers) {
if (defined($handles)) {
my $type = $pclass->handled_type;
next unless ($type->can('role'));
my $role = $type->role;
next unless Role::Tiny::does_role($handles, $role);
}
if (defined($prefer)) {
next unless ($pclass->does($prefer));
}
my $q = 0.5;
if ($pclass->does('Attean::API::PullParser')) {
$q += 0.25;
} elsif ($pclass->does('Attean::API::AtOnceParser')) {
$q -= 0.25;
}
for (@{ $pclass->media_types }) {
my $mt = "$_;q=$q";
$media_types{$mt} = $q;
}
}
my @sorted = sort { $media_types{$b} <=> $media_types{$a} } keys %media_types;
return join(',', @sorted);
}
# Global registry for extension functions that can be invoked via BIND, FILTER, etc.
{
our %global_functions;
=item C<< register_global_function( %uri_to_func ) >>
=cut
sub register_global_function {
my $class = shift;
my %args = @_;
foreach my $uri (keys %args) {
my $func = $args{ $uri };
$global_functions{ $uri } = $func;
}
}
=item C<< get_global_function( $uri ) >>
=cut
sub get_global_function {
my $class = shift;
my $uri = shift;
return $global_functions{ $uri };
}
}
# Global registry for extension "functional forms" that can be invoked via BIND, FILTER, etc.
# These differ from extension "functions" in that they can be passed undef values as arguments
# for expressions whose evaluation resulted in an error.
{
our %global_functional_forms;
=item C<< register_global_functional_form( %uri_to_func ) >>
=cut
sub register_global_functional_form {
my $class = shift;
my %args = @_;
foreach my $uri (keys %args) {
my $func = $args{ $uri };
$global_functional_forms{ $uri } = $func;
}
}
=item C<< get_global_functional_form( $uri ) >>
=cut
sub get_global_functional_form {
my $class = shift;
my $uri = shift;
return $global_functional_forms{ $uri };
}
}
# Global registry for extension aggregates
{
our %global_aggregates;
=item C<< register_global_aggregate( %uri_to_hash ) >>
=cut
sub register_global_aggregate {
my $class = shift;
my %args = @_;
foreach my $uri (keys %args) {
my $funcs = $args{ $uri };
$global_aggregates{ $uri } = $funcs;
}
}
=item C<< get_global_aggregate( $uri ) >>
=cut
sub get_global_aggregate {
my $class = shift;
my $uri = shift;
return $global_aggregates{ $uri };
}
}
# Global registry for extension datatypes.
# When literals of this datatype are constructed, they will have the registered Moo role applied to them.
{
our %datatype_roles;
=item C<< register_datatype_role( %uri_to_role ) >>
=cut
sub register_datatype_role {
my $class = shift;
my %args = @_;
foreach my $uri (keys %args) {
my $func = $args{ $uri };
$datatype_roles{ $uri } = $func;
}
}
=item C<< get_datatype_role( $uri ) >>
=cut
sub get_datatype_role {
my $class = shift;
my $uri = shift;
return $datatype_roles{ $uri };
}
}
}
use AtteanX::Functions::CompositeLists;
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/AtteanX/ 000755 000765 000024 00000000000 14636711137 015146 5 ustar 00greg staff 000000 000000 Attean-0.034/lib/Attean/ 000755 000765 000024 00000000000 14636711137 015016 5 ustar 00greg staff 000000 000000 Attean-0.034/lib/Attean/PaxHeader/IRI.pm 000644 000765 000024 00000000225 14636707547 017761 x ustar 00greg staff 000000 000000 30 mtime=1719373671.877313917
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/IRI.pm 000644 000765 000024 00000004233 14636707547 016013 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::IRI - RDF Internationalized Resource Identifiers (IRIs)
=head1 VERSION
This document describes Attean::IRI version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $term = Attean::IRI->new('http://example.org/');
$term->ntriples_string; #
=head1 DESCRIPTION
The Attean::IRI class represents RDF IRIs.
It conforms to the L role
and extends the L class.
=head1 METHODS
=over 4
=cut
package Attean::IRI 0.034 {
use Moo;
use Types::Standard qw(Str);
use IRI 0.005;
use namespace::clean;
extends 'IRI';
has 'ntriples_string' => (is => 'ro', isa => Str, lazy => 1, builder => '_ntriples_string');
=item C<< equals ( $iri ) >>
Returns true if C<< $iri >> is equal to the invocant, false otherwise.
=cut
sub equals {
# This overrides the Attean::API::TermOrVariable::equals implementation
# to allow lazy IRIs to remain unparsed for the case where neither has
# a base IRI.
my ($a, $b) = @_;
if ($b->isa('Attean::IRI')) {
unless ($a->has_base or $b->has_base) {
return ($a->value eq $b->value);
}
}
return ($a->as_string eq $b->as_string);
}
with 'Attean::API::IRI';
with 'Attean::API::BlankOrIRI';
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
my $args;
if (scalar(@_) == 1) {
$args = $class->$orig(value => shift);
} else {
$args = $class->$orig(@_);
}
if (exists $args->{base}) {
# fully qualify IRIs
my $iri = IRI->new( %$args );
$args = { value => $iri->as_string };
}
return $args;
};
=item C<< as_string >>
Returns the IRI value.
=cut
sub as_string {
my $self = shift;
return $self->abs;
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
L
L
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/Quad.pm 000644 000765 000024 00000000225 14636707547 020230 x ustar 00greg staff 000000 000000 30 mtime=1719373671.970277383
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/Quad.pm 000644 000765 000024 00000003627 14636707547 016270 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::Quad - RDF Quads
=head1 VERSION
This document describes Attean::Quad version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $quad = Attean::Quad->new( $s, $p, $o, $g );
=head1 DESCRIPTION
The Attean::Quad class represents an RDF quad.
It conforms to the L role.
=head1 ROLES
This class consumes L.
=head1 METHODS
=over 4
=item C<< subject >>
=item C<< predicate >>
=item C<< object >>
=item C<< graph >>
=back
=cut
package Attean::QuadPattern 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Attean::RDF;
use Attean::API::Binding;
has 'subject' => (is => 'ro', required => 1);
has 'predicate' => (is => 'ro', required => 1);
has 'object' => (is => 'ro', required => 1);
has 'graph' => (is => 'ro', required => 1);
with 'Attean::API::QuadPattern';
}
package Attean::Quad 0.034 {
use Moo;
use Attean::API::Binding;
has 'subject' => (is => 'ro', does => 'Attean::API::BlankOrIRI', required => 1);
has 'predicate' => (is => 'ro', does => 'Attean::API::IRI', required => 1);
has 'object' => (is => 'ro', does => 'Attean::API::Term', required => 1);
has 'graph' => (is => 'ro', does => 'Attean::API::BlankOrIRI', required => 1);
with 'Attean::API::Quad';
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
if (scalar(@_) == 4) {
my %args;
@args{ $class->variables } = @_;
return $class->$orig(%args);
}
return $class->$orig(@_);
};
}
1;
__END__
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/Plan.pm 000644 000765 000024 00000000224 14636707547 020227 x ustar 00greg staff 000000 000000 29 mtime=1719373671.95193081
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/Plan.pm 000644 000765 000024 00000256723 14636707547 016277 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
use utf8;
=head1 NAME
Attean::Plan - Representation of SPARQL query plan operators
=head1 VERSION
This document describes Attean::Plan version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
=head1 DESCRIPTION
This is a utility package that defines all the Attean query plan classes
in the Attean::Plan namespace:
=over 4
=cut
use Attean::API::Query;
=item * L
Evaluates a quad pattern against the model.
=cut
package Attean::Plan::Quad 0.034 {
use Moo;
use Scalar::Util qw(blessed reftype);
use Types::Standard qw(ConsumerOf ArrayRef);
use AtteanX::Functions::CompositeLists;
use AtteanX::Functions::CompositeMaps;
use namespace::clean;
has 'subject' => (is => 'ro', required => 1);
has 'predicate' => (is => 'ro', required => 1);
has 'object' => (is => 'ro', required => 1);
has 'graph' => (is => 'ro', required => 1);
with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::NullaryQueryTree';
with 'Attean::API::QuadPattern';
around 'BUILDARGS' => sub {
my $orig = shift;
my $class = shift;
my $args = $orig->( $class, @_ );
if (exists $args->{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
my %vars;
foreach my $pos (qw(subject predicate object graph)) {
my $term = $args->{$pos};
if (blessed($term) and $term->does('Attean::API::Variable')) {
$vars{$term->value} = $term;
}
}
my @vars = keys %vars;
$args->{in_scope_variables} = [@vars];
return $args;
};
sub plan_as_string {
my $self = shift;
my @nodes = $self->values;
my @strings;
foreach my $t (@nodes) {
if (ref($t) eq 'ARRAY') {
my @tstrings = map { $_->ntriples_string } @$t;
if (scalar(@tstrings) == 1) {
push(@strings, @tstrings);
} else {
push(@strings, '[' . join(', ', @tstrings) . ']');
}
} elsif ($t->does('Attean::API::TermOrVariable')) {
push(@strings, $t->ntriples_string);
} else {
use Data::Dumper;
die "Unrecognized node in quad pattern: " . Dumper($t);
}
}
return sprintf('Quad { %s }', join(', ', @strings));
}
sub substitute_impl {
my $self = shift;
my $model = shift;
my $b = shift;
my @values = $self->values;
foreach my $i (0 .. $#values) {
my $value = $values[$i];
if (reftype($value) eq 'ARRAY') {
my @values;
foreach my $value (@{ $value }) {
my $name = $value->value;
if (my $node = $b->value($name)) {
push(@values, $node);
} else {
push(@values, $value);
}
$values[$i] = \@values;
}
} elsif ($value->does('Attean::API::Variable')) {
my $name = $value->value;
if (my $node = $b->value($name)) {
$values[$i] = $node;
}
}
}
return sub {
return $model->get_bindings( @values );
}
}
sub impl {
my $self = shift;
my $model = shift;
my @values = $self->values;
return sub {
return $model->get_bindings( @values );
}
}
}
=item * L
Evaluates a join (natural-, anti-, or left-) using a nested loop.
=cut
package Attean::Plan::NestedLoopJoin 0.034 {
use Moo;
use List::MoreUtils qw(all);
use namespace::clean;
with 'Attean::API::BindingSubstitutionPlan';
with 'Attean::API::Plan::Join';
sub plan_as_string {
my $self = shift;
if ($self->left) {
return 'NestedLoop Left Join';
} elsif ($self->anti) {
return 'NestedLoop Anti Join';
} else {
return 'NestedLoop Join';
}
}
sub impl {
my $self = shift;
my $model = shift;
my @children = map { $_->impl($model) } @{ $self->children };
return $self->_impl($model, @children);
}
sub substitute_impl {
my $self = shift;
my $model = shift;
my $b = shift;
unless (all { $_->does('Attean::API::BindingSubstitutionPlan') } @{ $self->children }) {
die "Plan children do not all consume BindingSubstitutionPlan role:\n" . $self->as_string;
}
my @children = map { $_->substitute_impl($model, $b) } @{ $self->children };
return $self->_impl($model, @children);
}
sub _impl {
my $self = shift;
my $model = shift;
my @children = @_;
my $left = $self->left;
my $anti = $self->anti;
my $iter_variables = $self->in_scope_variables;
return sub {
my ($lhs, $rhs) = map { $_->() } @children;
my @right = $rhs->elements;
my @results;
while (my $l = $lhs->next) {
my $seen = 0;
foreach my $r (@right) {
my @shared = $l->shared_domain($r);
if ($anti and scalar(@shared) == 0) {
# in a MINUS, two results that have disjoint domains are considered not to be joinable
next;
}
if (my $j = $l->join($r)) {
$seen++;
if ($left) {
# TODO: filter with expression
push(@results, $j);
} elsif ($anti) {
} else {
push(@results, $j);
}
}
}
if ($left and not($seen)) {
push(@results, $l);
} elsif ($anti and not($seen)) {
push(@results, $l);
}
}
return Attean::ListIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
values => \@results,
);
}
}
}
=item * L
Evaluates a join (natural-, anti-, or left-) using a hash join.
=cut
package Attean::Plan::HashJoin 0.034 {
use Moo;
use List::MoreUtils qw(all);
use namespace::clean;
sub BUILD {
my $self = shift;
if ($self->anti) {
die "Cannot use a HashJoin for anti-joins";
}
}
with 'Attean::API::BindingSubstitutionPlan';
with 'Attean::API::Plan::Join';
sub plan_as_string {
my $self = shift;
my $name;
if ($self->left) {
$name = "Hash Left Join";
} else {
$name = "Hash Join";
}
return sprintf('%s { %s }', $name, join(', ', @{$self->join_variables}));
}
sub impl {
my $self = shift;
my $model = shift;
my @children = map { $_->impl($model) } @{ $self->children };
return $self->_impl($model, @children);
}
sub substitute_impl {
my $self = shift;
my $model = shift;
my $b = shift;
unless (all { $_->does('Attean::API::BindingSubstitutionPlan') } @{ $self->children }) {
die "Plan children do not all consume BindingSubstitutionPlan role:\n" . $self->as_string;
}
my @children = map { $_->substitute_impl($model, $b) } @{ $self->children };
return $self->_impl($model, @children);
}
sub _impl {
my $self = shift;
my $model = shift;
my @children = @_;
my $left = $self->left;
my $iter_variables = $self->in_scope_variables;
return sub {
my %hash;
my @vars = @{ $self->join_variables };
my $rhs = $children[1]->();
while (my $r = $rhs->next()) {
my $has_unbound_right_join_var = 0;
my @values;
foreach my $var (@vars) {
my $value = $r->value($var);
unless (defined($value)) {
$has_unbound_right_join_var++;
}
push(@values, $value);
}
if ($has_unbound_right_join_var) {
# this is a RHS row that doesn't have a term bound to one of the join variables.
# this will make it impossible to compute the proper hash key to access the row bucket,
# so we add this row to the null bucket (hash key '') which we try to join all LHS rows
# against.
push(@{ $hash{''} }, $r);
} else {
my $key = join(',', map { ref($_) ? $_->as_string : '' } @values);
push(@{ $hash{$key} }, $r);
}
}
my @results;
my $lhs = $children[0]->();
while (my $l = $lhs->next()) {
my $seen = 0;
my @values;
my $has_unbound_left_join_var = 0;
foreach my $var (@vars) {
my $value = $l->value($var);
unless (defined($value)) {
$has_unbound_left_join_var++;
}
push(@values, $value);
}
my @buckets;
if (my $b = $hash{''}) {
push(@buckets, $b);
}
if ($has_unbound_left_join_var) {
my $pattern = join(',', map { ref($_) ? quotemeta($_->as_string) : '.*' } @values);
foreach my $key (keys %hash) {
if ($key =~ /^${pattern}$/) {
push(@buckets, $hash{$key});
}
}
} else {
my $key = join(',', map { ref($_) ? $_->as_string : '' } @values);
if (my $rows = $hash{$key}) {
push(@buckets, $rows);
}
}
foreach my $rows (@buckets) {
foreach my $r (@$rows) {
if (my $j = $l->join($r)) {
$seen++;
if ($left) {
# TODO: filter with expression
push(@results, $j);
} else {
push(@results, $j);
}
}
}
}
if ($left and not($seen)) {
push(@results, $l);
}
}
return Attean::ListIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
values => \@results
);
}
}
}
=item * L
=cut
package Attean::Plan::Construct 0.034 {
use Moo;
use List::MoreUtils qw(all);
use Types::Standard qw(Str ArrayRef ConsumerOf InstanceOf);
use namespace::clean;
has 'triples' => (is => 'ro', 'isa' => ArrayRef[ConsumerOf['Attean::API::TripleOrQuadPattern']], required => 1);
with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::UnaryQueryTree';
sub plan_as_string {
my $self = shift;
my $triples = $self->triples;
return sprintf('Construct { %s }', join(' . ', map { $_->as_string } @$triples));
}
sub BUILDARGS {
# TODO: this code is repeated in several plan classes; figure out a way to share it.
my $class = shift;
my %args = @_;
my %vars = map { $_ => 1 } map { @{ $_->in_scope_variables } } @{ $args{ children } };
my @vars = keys %vars;
if (exists $args{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
$args{in_scope_variables} = \@vars;
return $class->SUPER::BUILDARGS(%args);
}
sub impl {
my $self = shift;
my $model = shift;
my @children = map { $_->impl($model) } @{ $self->children };
return $self->_impl($model, @children);
}
sub substitute_impl {
my $self = shift;
my $model = shift;
my $b = shift;
unless (all { $_->does('Attean::API::BindingSubstitutionPlan') } @{ $self->children }) {
die "Plan children do not all consume BindingSubstitutionPlan role:\n" . $self->as_string;
}
warn "TODO: fix substitute_impl to substitute construct triples";
my @children = map { $_->substitute_impl($model, $b) } @{ $self->children };
return $self->_impl($model, @children);
}
# replace blank nodes in all the triple patterns with fresh ones
sub refresh_triples {
my $self = shift;
my @t;
my %mapping;
foreach my $t (@_) {
foreach my $term ($t->values) {
if ($term->does('Attean::API::Blank')) {
$mapping{$term->as_string} = Attean::Blank->new();
}
}
}
my $mapper = Attean::TermMap->rewrite_map(\%mapping);
foreach my $t (@_) {
push(@t, $t->apply_map($mapper));
}
return @t;
}
sub _impl {
my $self = shift;
my $model = shift;
my $child = shift;
my @triples = @{ $self->triples };
return sub {
my $iter = $child->();
my @buffer;
my %seen;
return Attean::CodeIterator->new(
item_type => 'Attean::API::Triple',
generator => sub {
if (scalar(@buffer)) {
return shift(@buffer);
}
while (my $row = $iter->next) {
foreach my $tp ($self->refresh_triples(@triples)) {
my $tp = $tp->apply_bindings($row);
my $t = eval { $tp->as_triple };
if ($t) {
push(@buffer, $t);
}
}
if (scalar(@buffer)) {
my $t = shift(@buffer);
return $t;
}
}
}
)->grep(sub {
return not $seen{$_->as_string}++;
});
}
}
}
=item * L
=cut
package Attean::Plan::Describe 0.034 {
use Moo;
use Attean::RDF;
use List::MoreUtils qw(all);
use Types::Standard qw(Str ArrayRef ConsumerOf InstanceOf);
use namespace::clean;
has 'graph' => (is => 'ro');
has 'terms' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::TermOrVariable']]);
with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::UnaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
sub plan_as_string {
my $self = shift;
my $terms = $self->terms;
return sprintf('Describe { %s }', join(' . ', map { $_->as_string } @$terms));
}
sub impl {
my $self = shift;
my $model = shift;
my @children = map { $_->impl($model) } @{ $self->children };
return $self->_impl($model, @children);
}
sub substitute_impl {
my $self = shift;
my $model = shift;
my $b = shift;
unless (all { $_->does('Attean::API::BindingSubstitutionPlan') } @{ $self->children }) {
die "Plan children do not all consume BindingSubstitutionPlan role:\n" . $self->as_string;
}
warn "TODO: fix substitute_impl to substitute describe terms";
my @children = map { $_->substitute_impl($model, $b) } @{ $self->children };
return $self->_impl($model, @children);
}
sub _impl {
my $self = shift;
my $model = shift;
my $child = shift;
my $graph = $self->graph;
my @terms = @{ $self->terms };
# TODO: Split @terms into ground terms and variables.
# Only call get_quads once for ground terms.
# For variable terms, call get_quads for each variable-result combination.
return sub {
my $iter = $child->();
my @buffer;
my %seen;
return Attean::CodeIterator->new(
item_type => 'Attean::API::Triple',
generator => sub {
if (scalar(@buffer)) {
return shift(@buffer);
}
while (my $row = $iter->next) {
foreach my $term (@terms) {
my $value = $term->apply_binding($row);
if ($value->does('Attean::API::Term')) {
my $iter = $model->get_quads( $value, variable('predicate'), variable('object'), $graph );
push(@buffer, $iter->elements);
}
if (scalar(@buffer)) {
return shift(@buffer);
}
}
}
}
)->grep(sub {
return not $seen{$_->as_string}++;
});
}
}
}
=item * L
Filters results from a sub-plan based on the effective boolean value of a
named variable binding.
=cut
package Attean::Plan::EBVFilter 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Types::Standard qw(Str ConsumerOf);
use namespace::clean;
with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::UnaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
has 'variable' => (is => 'ro', isa => Str, required => 1);
sub plan_as_string {
my $self = shift;
return sprintf('EBVFilter { ?%s }', $self->variable);
}
sub tree_attributes { return qw(expression) };
sub substitute_impl {
my $self = shift;
my $model = shift;
my $bind = shift;
my ($impl) = map { $_->substitute_impl($model, $bind) } @{ $self->children };
my $var = $self->variable;
return sub {
my $iter = $impl->();
return $iter->grep(sub {
my $r = shift;
my $term = $r->value($var);
return 0 unless (blessed($term) and $term->does('Attean::API::Term'));
return $term->ebv;
});
};
}
sub impl {
my $self = shift;
my $model = shift;
my ($impl) = map { $_->impl($model) } @{ $self->children };
my $var = $self->variable;
return sub {
my $iter = $impl->();
return $iter->grep(sub {
my $r = shift;
my $term = $r->value($var);
return 0 unless (blessed($term) and $term->does('Attean::API::Term'));
return $term->ebv;
});
};
}
}
=item * L
Evaluates a set of sub-plans, returning the merged union of results, preserving
ordering.
=cut
package Attean::Plan::Merge 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Types::Standard qw(Str ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::BinaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
has 'variables' => (is => 'ro', isa => ArrayRef[Str], required => 1);
sub plan_as_string { return 'Merge' }
sub impl {
my $self = shift;
my $model = shift;
my @children = map { $_->impl($model) } @{ $self->children };
return sub {
die "Unimplemented";
};
}
}
=item * L
Evaluates a set of sub-plans, returning the union of results.
=cut
package Attean::Plan::Union 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use namespace::clean;
with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::BinaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
sub plan_as_string { return 'Union' }
sub impl {
my $self = shift;
my $model = shift;
my @children = map { $_->impl($model) } @{ $self->children };
return $self->_impl($model, @children);
}
sub substitute_impl {
my $self = shift;
my $model = shift;
my $b = shift;
unless (all { $_->does('Attean::API::BindingSubstitutionPlan') } @{ $self->children }) {
die "Plan children do not all consume BindingSubstitutionPlan role:\n" . $self->as_string;
}
my @children = map { $_->substitute_impl($model, $b) } @{ $self->children };
return $self->_impl($model, @children);
}
sub _impl {
my $self = shift;
my $model = shift;
my @children = @_;
my $iter_variables = $self->in_scope_variables;
return sub {
if (my $current = shift(@children)) {
my $iter = $current->();
return Attean::CodeIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
generator => sub {
while (blessed($iter)) {
my $row = $iter->next();
if ($row) {
return $row;
} else {
$current = shift(@children);
if ($current) {
$iter = $current->();
} else {
undef $iter;
}
}
}
},
);
} else {
return Attean::ListIterator->new( item_type => 'Attean::API::Result', variables => [], values => [], );
}
};
}
}
=item * L
Evaluates a sub-plan, and extends each result by evaluating a set of
expressions, binding the produced values to new variables.
=cut
package Attean::Plan::Extend 0.034 {
use Moo;
use Encode;
use UUID::Tiny ':std';
use URI::Escape;
use Data::Dumper;
use I18N::LangTags;
use POSIX qw(ceil floor);
use Digest::SHA;
use Digest::MD5 qw(md5_hex);
use Scalar::Util qw(blessed looks_like_number);
use List::MoreUtils qw(uniq all);
use Types::Standard qw(ConsumerOf ArrayRef InstanceOf HashRef);
use namespace::clean;
with 'MooX::Log::Any';
with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::UnaryQueryTree';
has 'expressions' => (is => 'ro', isa => HashRef[ConsumerOf['Attean::API::Expression']], required => 1);
has 'active_graphs' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::IRI']], required => 1);
sub plan_as_string {
my $self = shift;
my @strings = map { sprintf('?%s ↠%s', $_, $self->expressions->{$_}->as_string) } keys %{ $self->expressions };
return sprintf('Extend { %s }', join(', ', @strings));
}
sub tree_attributes { return qw(variable expression) };
sub BUILDARGS {
my $class = shift;
my %args = @_;
my $exprs = $args{ expressions };
my @vars = map { @{ $_->in_scope_variables } } @{ $args{ children } };
my @evars = (@vars, keys %$exprs);
if (exists $args{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
$args{in_scope_variables} = [@evars];
return $class->SUPER::BUILDARGS(%args);
}
sub evaluate_expression {
my $self = shift;
my $model = shift;
my $expr = shift;
my $r = shift;
Carp::confess unless ($expr->can('operator'));
my $op = $expr->operator;
state $true = Attean::Literal->true;
state $false = Attean::Literal->false;
state $type_roles = { qw(URI IRI IRI IRI BLANK Blank LITERAL Literal NUMERIC NumericLiteral TRIPLE Triple) };
state $type_classes = { qw(URI Attean::IRI IRI Attean::IRI STR Attean::Literal) };
if ($expr->isa('Attean::CastExpression')) {
my $datatype = $expr->datatype->value;
my ($child) = @{ $expr->children };
my $term = $self->evaluate_expression($model, $child, $r);
if ($datatype =~ m<^http://www.w3.org/2001/XMLSchema#string$>) {
my $value = $term->value;
if ($term->does('Attean::API::IRI')) {
return Attean::Literal->new(value => $term->value);
} elsif ($term->datatype->value eq 'http://www.w3.org/2001/XMLSchema#boolean') {
my $v = ($value eq 'true' or $value eq '1') ? 'true' : 'false';
return Attean::Literal->new(value => $v);
} elsif ($term->does('Attean::API::NumericLiteral')) {
my $v = $term->numeric_value();
if ($v == int($v)) {
return Attean::Literal->new(value => int($v));
}
}
return Attean::Literal->new(value => $value);
}
die "TypeError $op" unless (blessed($term) and $term->does('Attean::API::Literal'));
if ($datatype =~ m<^http://www.w3.org/2001/XMLSchema#(integer|float|double|decimal)>) {
my $value = $term->value;
my $num;
if ($datatype eq 'http://www.w3.org/2001/XMLSchema#integer') {
if ($term->datatype->value eq 'http://www.w3.org/2001/XMLSchema#boolean') {
$value = ($value eq 'true' or $value eq '1') ? '1' : '0';
} elsif ($term->does('Attean::API::NumericLiteral')) {
my $v = $term->numeric_value();
$v =~ s/[.].*$//;
$value = int($v);
} elsif ($value =~ /^[-+]\d+$/) {
my ($v) = "$value";
$v =~ s/[.].*$//;
$value = int($v);
}
$num = $value;
} elsif ($datatype eq 'http://www.w3.org/2001/XMLSchema#decimal') {
if ($term->datatype->value eq 'http://www.w3.org/2001/XMLSchema#boolean') {
$value = ($value eq 'true') ? '1' : '0';
} elsif ($term->does('Attean::API::NumericLiteral')) {
$value = $term->numeric_value;
} elsif (looks_like_number($value)) {
if ($value =~ /[eE]/) { # double
die "cannot cast to xsd:decimal as precision would be lost";
}
$value = +$value;
}
$num = "$value";
$num =~ s/[.]0+$/.0/;
$num =~ s/[.](\d+)0*$/.$1/;
} elsif ($datatype =~ m<^http://www.w3.org/2001/XMLSchema#(float|double)$>) {
my $typename = $1;
if ($term->datatype->value eq 'http://www.w3.org/2001/XMLSchema#boolean') {
$value = ($value eq 'true') ? '1.0' : '0.0';
} elsif ($term->does('Attean::API::NumericLiteral')) {
# no-op
} elsif (looks_like_number($value)) {
$value = +$value;
} else {
die "cannot cast unrecognized value '$value' to xsd:$typename";
}
$num = sprintf("%e", $value);
}
my $c = Attean::Literal->new(value => $num, datatype => $expr->datatype);
if (my $term = $c->canonicalized_term_strict()) {
return $term;
} else {
die "Term value is not a valid lexical form for $datatype";
}
} elsif ($datatype =~ m<^http://www.w3.org/2001/XMLSchema#boolean$>) {
if ($term->does('Attean::API::NumericLiteral')) {
my $value = $term->numeric_value;
return ($value == 0) ? Attean::Literal->false : Attean::Literal->true;
} else {
my $value = $term->value;
if ($value =~ m/^(true|false|0|1)$/) {
return ($value eq 'true' or $value eq '1') ? Attean::Literal->true : Attean::Literal->false;
} else {
die "Bad lexical form for xsd:boolean: '$value'";
}
}
} elsif ($datatype =~ m<^http://www.w3.org/2001/XMLSchema#dateTime$>) {
my $value = $term->value;
my $c = Attean::Literal->new(value => $value, datatype => $expr->datatype);
if ($c->does('Attean::API::DateTimeLiteral') and $c->datetime) {
return $c;
} else {
die "Bad lexical form for xsd:dateTime: '$value'";
}
}
$self->log->warn("Cast expression unimplemented for $datatype: " . Dumper($expr));
} elsif ($expr->isa('Attean::ValueExpression')) {
my $node = $expr->value;
if ($node->does('Attean::API::Variable')) {
my $value = $r->value($node->value);
unless (blessed($value)) {
die "Variable " . $node->as_string . " is unbound in expression " . $expr->as_string;
}
return $value;
} else {
return $node;
}
} elsif ($expr->isa('Attean::UnaryExpression')) {
my ($child) = @{ $expr->children };
my $term = $self->evaluate_expression($model, $child, $r);
if ($op eq '!') {
return ($term->ebv) ? $false : $true;
} elsif ($op eq '-' or $op eq '+') {
die "TypeError $op" unless (blessed($term) and $term->does('Attean::API::NumericLiteral'));
my $v = $term->numeric_value;
return Attean::Literal->new( value => eval "$op$v", datatype => $term->datatype );
}
die "Unimplemented UnaryExpression evaluation: " . $expr->operator;
} elsif ($expr->isa('Attean::BinaryExpression')) {
my $op = $expr->operator;
if ($op eq '&&') {
foreach my $child (@{ $expr->children }) {
my $term = $self->evaluate_expression($model, $child, $r);
unless ($term->ebv) {
return $false;
}
}
return $true;
} elsif ($op eq '||') {
foreach my $child (@{ $expr->children }) {
my $term = $self->evaluate_expression($model, $child, $r);
if (blessed($term) and $term->ebv) {
return $true;
}
}
return $false;
} elsif ($op eq '=') {
my ($lhs, $rhs) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $eq = $lhs->equals($rhs);
return $eq ? $true : $false; # TODO: this may not be using value-space comparision for numerics...
} elsif ($op eq '!=') {
my ($lhs, $rhs) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
return not($lhs->equals($rhs)) ? $true : $false; # TODO: this may not be using value-space comparision for numerics...
} elsif ($op =~ m#[<>]=?#) {
my ($lhs, $rhs) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $cmp = $lhs->compare($rhs);
if ($cmp < 0) {
return ($op =~ /^<=?/) ? $true : $false;
} elsif ($cmp > 0) {
return ($op =~ /^>=?/) ? $true : $false;
} else {
return ($op =~ /=/) ? $true : $false;
}
} elsif ($op =~ m<^[-+*/]$>) {
my ($lhs, $rhs) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
die "TypeError $op" unless all { blessed($_) and $_->does('Attean::API::NumericLiteral') } ($lhs, $rhs);
my ($lv, $rv) = map { $_->numeric_value } ($lhs, $rhs);
my $type = $lhs->binary_promotion_type($rhs, $op);
if ($op eq '+') {
return Attean::Literal->new(value => ($lv + $rv), datatype => $type);
} elsif ($op eq '-') {
return Attean::Literal->new(value => ($lv - $rv), datatype => $type);
} elsif ($op eq '*') {
return Attean::Literal->new(value => ($lv * $rv), datatype => $type);
} elsif ($op eq '/') {
return Attean::Literal->new(value => ($lv / $rv), datatype => $type);
}
}
$self->log->warn("Binary operator $op expression evaluation unimplemented: " . Dumper($expr));
die "Expression evaluation unimplemented: " . $expr->as_string;
} elsif ($expr->isa('Attean::FunctionExpression')) {
my $func = $expr->operator;
if ($func eq 'IF') {
my ($check, @children) = @{ $expr->children };
my ($term) = $self->evaluate_expression($model, $check, $r);
$self->log->warn($@) if ($@);
my $expr = $children[ (blessed($term) and $term->ebv) ? 0 : 1 ];
my $value = $self->evaluate_expression($model, $expr, $r);
# warn '############# ' . $value->as_string;
return $value;
} elsif ($func eq 'COALESCE') {
# warn "COALESCE: . " . $r->as_string . "\n";
foreach my $child (@{ $expr->children }) {
# warn '- ' . $child->as_string . "\n";
my $term = eval { $self->evaluate_expression($model, $child, $r) };
# warn $@ if $@;
if (blessed($term)) {
# warn ' returning ' . $term->as_string . "\n";
return $term;
}
}
# warn " no value\n";
return;
} elsif ($func eq 'BOUND') {
my ($child) = @{ $expr->children };
my ($term) = eval { $self->evaluate_expression($model, $child, $r) };
return blessed($term) ? $true : $false;
}
if ($func eq 'INVOKE') {
my @children = @{ $expr->children };
my $furi = $self->evaluate_expression($model, shift(@children), $r)->value;
if (my $func = Attean->get_global_function($furi)) {
my @operands = map { $self->evaluate_expression($model, $_, $r) } @children;
my $rr = eval { $func->($model, $self->active_graphs, @operands) };
if ($@) {
warn "INVOKE error: $@" if $@;
return;
}
return $rr;
} elsif (my $fform = Attean->get_global_functional_form($furi)) {
my @operands = map { eval { $self->evaluate_expression($model, $_, $r) } || undef } @children;
my $rr = eval { $fform->($model, $self->active_graphs, @operands) };
if ($@) {
warn "INVOKE error: $@" if $@;
return $r;
}
return $rr;
} else {
die "No extension registered for <$furi>";
}
} else {
my @terms = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
if ($func =~ /^IS([UI]RI|BLANK|LITERAL|NUMERIC|TRIPLE)$/) {
my $role = "Attean::API::$type_roles->{$1}";
my $t = shift(@terms);
my $ok = (blessed($t) and $t->does($role));
return $ok ? $true : $false;
} elsif ($func eq 'REGEX') {
my ($string, $pattern, $flags) = @terms;
# my ($string, $pattern, $flags) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
# TODO: ensure that $string is a literal
($string, $pattern, $flags) = map { blessed($_) ? $_->value : '' } ($string, $pattern, $flags);
my $re;
if ($flags =~ /i/) {
$re = qr/$pattern/i;
} else {
$re = qr/$pattern/;
}
return ($string =~ $re) ? $true : $false;
} elsif ($func =~ /^(NOT)?IN$/) {
my $ok = ($func eq 'IN') ? $true : $false;
my $notok = ($func eq 'IN') ? $false : $true;
# my @children = @{ $expr->children };
my ($term, @children) = @terms;
# my ($term) = $self->evaluate_expression($model, shift(@children), $r);
# foreach my $child (@{ $expr->children }) {
foreach my $value (@children) {
# my $value = $self->evaluate_expression($model, $child, $r);
if ($term->equals($value)) {
return $ok;
}
}
return $notok;
} elsif ($func eq 'NOW') {
my $dt = DateTime->now;
my $value = DateTime::Format::W3CDTF->new->format_datetime( $dt );
return Attean::Literal->new(value => $value, datatype => 'http://www.w3.org/2001/XMLSchema#dateTime');
} elsif ($func eq 'STR') {
my ($term) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
return Attean::Literal->new(value => $term->value);
} elsif ($func =~ /^[UI]RI$/) { # IRI URI
my ($term) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
return Attean::IRI->new(value => $term->value, base => $expr->base);
} elsif ($func eq 'ABS') {
my ($string) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $value = abs($string->numeric_value);
return Attean::Literal->new(value => $value, datatype => $string->datatype);
} elsif ($func eq 'ROUND') {
my ($string) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $value = $string->numeric_value;
my $mult = 1;
if ($value < 0) {
$mult = -1;
$value = -$value;
}
my $round = $mult * POSIX::floor($value + 0.50000000000008);
return Attean::Literal->new(value => $round, datatype => $string->datatype);
} elsif ($func eq 'CEIL') {
my ($string) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $value = ceil($string->numeric_value);
return Attean::Literal->new(value => $value, datatype => $string->datatype);
} elsif ($func eq 'FLOOR') {
my ($string) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $value = floor($string->numeric_value);
return Attean::Literal->new(value => $value, datatype => $string->datatype);
} elsif ($func eq 'CONCAT') {
my @strings = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
if (scalar(@strings) == 0) {
return Attean::Literal->new(value => '');
}
# die "CONCAT called with terms that are not argument compatible" unless ($strings[0]->argument_compatible(@strings));
my %args;
if (my $l = $strings[0]->language) {
$args{language} = $l;
} else {
my $dt = $strings[0]->datatype;
if ($dt->value eq '') {
$args{datatype} = 'http://www.w3.org/2001/XMLSchema#string';
}
}
foreach my $s (@strings) {
die unless ($s->does('Attean::API::Literal'));
die if ($s->datatype and not($s->datatype->value =~ m));
if (my $l2 = $s->language) {
if (my $l1 = $args{language}) {
if ($l1 ne $l2) {
delete $args{language};
}
}
} else {
delete $args{language};
}
}
my $c = Attean::Literal->new(value => join('', map { $_->value } @strings), %args);
return $c;
} elsif ($func eq 'DATATYPE') {
my ($string) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
die unless ($string->does('Attean::API::Literal'));
return $string->datatype;
} elsif ($func eq 'LANG') {
my ($string) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
die unless ($string->does('Attean::API::Literal'));
my $value = $string->language // '';
return Attean::Literal->new(value => $value);
} elsif ($func eq 'LANGMATCHES') {
my ($term, $pat) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $lang = $term->value;
my $match = $pat->value;
if ($match eq '*') {
# """A language-range of "*" matches any non-empty language-tag string."""
return $lang ? $true : $false;
} else {
return (I18N::LangTags::is_dialect_of( $lang, $match )) ? $true : $false;
}
} elsif ($func eq 'ENCODE_FOR_URI') {
my ($string) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
return Attean::Literal->new(value => uri_escape_utf8($string->value));
} elsif ($func =~ /^[LU]CASE$/) {
my $term = shift(@terms);
my $value = ($func eq 'LCASE') ? lc($term->value) : uc($term->value);
return Attean::Literal->new(value => $value, $term->construct_args);
} elsif ($func eq 'STRLANG') {
my ($term, $lang) = @terms;
die unless ($term->does('Attean::API::Literal'));
die unless ($term->datatype->value =~ m);
die if ($term->language);
return Attean::Literal->new(value => $term->value, language => $lang->value);
} elsif ($func eq 'STRDT') {
my ($term, $dt) = @terms;
die unless ($term->does('Attean::API::Literal'));
die unless ($term->datatype->value =~ m);
die if ($term->language);
# my ($term, $dt) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
return Attean::Literal->new(value => $term->value, datatype => $dt->value);
} elsif ($func eq 'REPLACE') {
my ($term, $pat, $rep) = @terms;
die unless ($term->does('Attean::API::Literal'));
die unless ($term->language or $term->datatype->value =~ m);
# my ($term, $pat, $rep) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $value = $term->value;
my $pattern = $pat->value;
my $replace = $rep->value;
die 'REPLACE() called with unsafe ?{} match pattern' if (index($pattern, '(?{') != -1 or index($pattern, '(??{') != -1);
die 'REPLACE() called with unsafe ?{} replace pattern' if (index($replace, '(?{') != -1 or index($replace, '(??{') != -1);
$replace =~ s/\\/\\\\/g;
$replace =~ s/\$(\d+)/\$$1/g;
$replace =~ s/"/\\"/g;
$replace = qq["$replace"];
no warnings 'uninitialized';
$value =~ s/$pattern/"$replace"/eeg;
# warn "==> " . Dumper($value);
return Attean::Literal->new(value => $value, $term->construct_args);
} elsif ($func eq 'SUBSTR') {
my ($term, @args) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $value = $term->value;
my @nums;
foreach my $i (0 .. $#args) {
my $argnum = $i + 2;
my $arg = $args[ $i ];
push(@nums, $arg->numeric_value);
}
$nums[0]--;
my $substring = (scalar(@nums) > 1) ? substr($value, $nums[0], $nums[1]) : substr($value, $nums[0]);
return Attean::Literal->new(value => $substring, $term->construct_args);
} elsif ($func eq 'CONTAINS') {
my ($term, $pattern) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
if ($term->has_language and $pattern->has_language) {
if ($term->literal_value_language ne $pattern->literal_value_language) {
die "CONTAINS called with literals of different languages";
}
}
my ($string, $pat) = map { $_->value } ($term, $pattern);
my $pos = index($string, $pat);
return ($pos >= 0) ? $true : $false;
} elsif ($func eq 'STRSTARTS') {
my (@terms) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my ($string, $pat) = map { $_->value } @terms;
return (substr($string, 0, length($pat)) eq $pat) ? $true : $false;
} elsif ($func eq 'STRENDS') {
my (@terms) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my ($string, $pat) = map { $_->value } @terms;
return (substr($string, length($string) - length($pat)) eq $pat) ? $true : $false;
} elsif ($func eq 'STRAFTER') {
my ($term, $pat) = @terms;
die "STRAFTER called without a literal" unless ($term->does('Attean::API::Literal'));
die "STRAFTER called without a plain literal" unless ($term->language or $term->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string');
die "$func arguments are not term compatible: " . join(', ', map { $_->as_string } @terms) unless ($term->argument_compatible($pat));
# TODO: check that the terms are argument compatible
my $value = $term->value;
my $match = $pat->value;
my $i = index($value, $match, 0);
if ($i < 0) {
return Attean::Literal->new(value => '');
} else {
return Attean::Literal->new(value => substr($value, $i+length($match)), $term->construct_args);
}
} elsif ($func eq 'STRBEFORE') {
my ($term, $pat) = @terms;
die "STRBEFORE called without a literal" unless ($term->does('Attean::API::Literal'));
die "STRBEFORE called without a plain literal" unless ($term->language or $term->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string');
die "$func arguments are not term compatible: " . join(', ', map { $_->as_string } @terms) unless ($term->argument_compatible($pat));
# TODO: check that the terms are argument compatible
my $value = $term->value;
my $match = $pat->value;
my $i = index($value, $match, 0);
if ($i < 0) {
return Attean::Literal->new(value => '');
} else {
return Attean::Literal->new(value => substr($value, 0, $i), $term->construct_args);
}
} elsif ($func eq 'STRLEN') {
my ($string) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
return Attean::Literal->new(value => length($string->value), datatype => 'http://www.w3.org/2001/XMLSchema#integer');
} elsif ($func eq 'MD5') {
my ($string) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $bytes = encode('UTF-8', $string->value, Encode::FB_CROAK);
return Attean::Literal->new(value => md5_hex($bytes));
} elsif ($func =~ /^SHA(\d+)$/) {
my $sha = Digest::SHA->new($1);
my ($string) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $bytes = encode('UTF-8', $string->value, Encode::FB_CROAK);
$sha->add($bytes);
return Attean::Literal->new(value => $sha->hexdigest);
} elsif ($func eq 'RAND') {
return Attean::Literal->new(value => rand(), datatype => 'http://www.w3.org/2001/XMLSchema#double');
} elsif ($func =~ /^(YEAR|MONTH|DAY|HOUR|MINUTE)S?$/) {
my $method = lc($1);
my ($term) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $dt = $term->datetime;
return Attean::Literal->new(value => $dt->$method(), datatype => 'http://www.w3.org/2001/XMLSchema#integer');
} elsif ($func eq 'SECONDS') {
my ($term) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $dt = $term->datetime;
return Attean::Literal->new(value => $dt->second, datatype => 'http://www.w3.org/2001/XMLSchema#decimal');
} elsif ($func eq 'TIMEZONE') {
my ($term) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $dt = $term->datetime;
my $tz = $dt->time_zone;
die "TIMEZONE called with a dateTime without a timezone" if ($tz->is_floating);
my $offset = $tz->offset_for_datetime( $dt );
my $minus = '';
if ($offset < 0) {
$minus = '-';
$offset = -$offset;
}
my $duration = "${minus}PT";
if ($offset >= 60*60) {
my $h = int($offset / (60*60));
$duration .= "${h}H" if ($h > 0);
$offset = $offset % (60*60);
}
if ($offset >= 60) {
my $m = int($offset / 60);
$duration .= "${m}M" if ($m > 0);
$offset = $offset % 60;
}
my $s = int($offset);
$duration .= "${s}S" if ($s > 0 or $duration eq 'PT');
return Attean::Literal->new(value => $duration, datatype => 'http://www.w3.org/2001/XMLSchema#dayTimeDuration');
} elsif ($func eq 'TZ') {
my ($term) = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $dt = $term->datetime;
my $tz = $dt->time_zone;
return Attean::Literal->new(value =>'') if ($tz->is_floating);
return Attean::Literal->new('Z') if ($tz->is_utc);
my $offset = $tz->offset_for_datetime( $dt );
my $hours = 0;
my $minutes = 0;
my $minus = '+';
if ($offset < 0) {
$minus = '-';
$offset = -$offset;
}
if ($offset >= 60*60) {
$hours = int($offset / (60*60));
$offset = $offset % (60*60);
}
if ($offset >= 60) {
$minutes = int($offset / 60);
$offset = $offset % 60;
}
my $seconds = int($offset);
return Attean::Literal->new(value => sprintf('%s%02d:%02d', $minus, $hours, $minutes));
} elsif ($func eq 'UUID') {
my $uuid = 'urn:uuid:' . uc(uuid_to_string(create_uuid()));
return Attean::IRI->new(value => $uuid);
} elsif ($func eq 'STRUUID') {
return Attean::Literal->new(value => uc(uuid_to_string(create_uuid())));
} elsif ($func eq 'BNODE') {
if (scalar(@{ $expr->children })) {
my $string = $self->evaluate_expression($model, $expr->children->[0], $r);
my $value = $string->value;
my $b = (exists $r->eval_stash->{'sparql:bnode'}{$value})
? $r->eval_stash->{'sparql:bnode'}{$value}
: Attean::Blank->new();
$r->eval_stash->{'sparql:bnode'}{$value} = $b;
return $b;
} else {
return Attean::Blank->new();
}
} elsif ($func eq 'SAMETERM') {
my @operands = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my ($a, $b) = @operands;
die "TypeError: SAMETERM" unless (blessed($operands[0]) and blessed($operands[1]));
if ($a->does('Attean::API::Binding')) {
my $ok = ($a->sameTerms($b));
return $ok ? $true : $false;
} else {
my $ok = ($a->value eq $b->value);
return $ok ? $true : $false;
}
} elsif ($func =~ /^(SUBJECT|PREDICATE|OBJECT)$/) {
my @operands = map { $self->evaluate_expression($model, $_, $r) } @{ $expr->children };
my $pos = lc($func);
my $term = $operands[0]->$pos();
return $term;
} else {
warn "Expression evaluation unimplemented: " . $expr->as_string;
$self->log->warn("Expression evaluation unimplemented: " . $expr->as_string);
die "Expression evaluation unimplemented: " . $expr->as_string;
}
}
} elsif ($expr->isa('Attean::ExistsPlanExpression')) {
my $plan = $expr->plan;
my $impl = $plan->substitute_impl($model, $r);
my $iter = $impl->();
my $found = 0;
if (my $row = $iter->next) {
# warn "EXISTS found row: " . $row->as_string;
$found++;
}
return $found ? Attean::Literal->true : Attean::Literal->false;
} else {
$self->log->warn("Expression evaluation unimplemented: " . $expr->as_string);
die "Expression evaluation unimplemented: " . $expr->as_string;
}
}
sub substitute_impl {
my $self = shift;
my $model = shift;
my $bind = shift;
my %exprs = %{ $self->expressions };
my ($impl) = map { $_->substitute_impl($model, $bind) } @{ $self->children };
# TODO: substitute variables in the expression
return $self->_impl($model, $impl, %exprs);
}
sub impl {
my $self = shift;
my $model = shift;
my %exprs = %{ $self->expressions };
my ($impl) = map { $_->impl($model) } @{ $self->children };
return $self->_impl($model, $impl, %exprs);
}
sub _impl {
my $self = shift;
my $model = shift;
my $impl = shift;
my %exprs = @_;
my $iter_variables = $self->in_scope_variables;
return sub {
my $iter = $impl->();
return Attean::CodeIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
generator => sub {
ROW: while (my $r = $iter->next) {
# warn 'Extend Row -------------------------------> ' . $r->as_string . "\n";
my %row = map { $_ => $r->value($_) } $r->variables;
foreach my $var (keys %exprs) {
my $expr = $exprs{$var};
# warn "-> $var => " . $expr->as_string . "\n";
my $term = eval { $self->evaluate_expression($model, $expr, $r) };
# if ($@) {
# warn "EXTEND expression evaluation error: $@";
# warn '- expression: ' . $expr->as_string;
# }
if (blessed($term)) {
# warn "===> " . $term->as_string . "\n";
if ($row{ $var } and $term->as_string ne $row{ $var }->as_string) {
next ROW;
}
if ($term->does('Attean::API::Binding')) {
# patterns need to be made ground to be bound as values (e.g. TriplePattern -> Triple)
$term = $term->ground($r);
}
$row{ $var } = $term;
}
}
return Attean::Result->new( bindings => \%row, eval_stash => $r->eval_stash );
}
return;
}
);
};
}
}
=item * L
Evaluates a sub-plan, and returns distinct results by checking a persistent
hash of already-seen results.
=cut
package Attean::Plan::HashDistinct 0.034 {
use Moo;
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
sub plan_as_string { return 'HashDistinct' }
sub impl {
my $self = shift;
my $model = shift;
my ($impl) = map { $_->impl($model) } @{ $self->children };
my %seen;
return sub {
my $iter = $impl->();
return $iter->grep(sub { return not($seen{ shift->as_string }++); });
};
}
}
=item * L
Evaluates an already-ordered sub-plan, and returns distinct results by
filtering out sequential duplicates.
=cut
package Attean::Plan::Unique 0.034 {
use Moo;
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
sub plan_as_string { return 'Unique' }
sub impl {
my $self = shift;
my $model = shift;
my ($impl) = map { $_->impl($model) } @{ $self->children };
return sub {
my $iter = $impl->();
my $last = '';
return $iter->grep(sub {
my $r = shift;
my $s = $r->as_string;
my $ok = $s ne $last;
$last = $s;
return $ok;
});
};
}
}
=item * L
Evaluates a sub-plan, and returns the results after optionally skipping some
number of results ("offset") and limiting the total number of returned results
("limit").
=cut
package Attean::Plan::Slice 0.034 {
use Moo;
use Types::Standard qw(Int);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
has 'limit' => (is => 'ro', isa => Int, default => -1);
has 'offset' => (is => 'ro', isa => Int, default => 0);
sub plan_as_string {
my $self = shift;
my @str;
push(@str, "Limit=" . $self->limit) if ($self->limit >= 0);
push(@str, "Offset=" . $self->offset) if ($self->offset > 0);
return sprintf('Slice { %s }', join(' ', @str));
}
sub impl {
my $self = shift;
my $model = shift;
my ($impl) = map { $_->impl($model) } @{ $self->children };
my $offset = $self->offset;
my $limit = $self->limit;
return sub {
my $iter = $impl->();
$iter = $iter->offset($offset) if ($offset > 0);
$iter = $iter->limit($limit) if ($limit >= 0);
return $iter;
};
}
}
=item * L
Evaluates a sub-plan and returns projected results by only keeping a fixed-set
of variable bindings in each result.
=cut
package Attean::Plan::Project 0.034 {
use Moo;
with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::UnaryQueryTree';
use Types::Standard qw(ArrayRef ConsumerOf);
has 'variables' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Variable']], required => 1);
sub BUILDARGS {
my $class = shift;
my %args = @_;
my @vars = map { $_->value } @{ $args{variables} };
if (exists $args{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
$args{in_scope_variables} = \@vars;
return $class->SUPER::BUILDARGS(%args);
}
# sub BUILD {
# my $self = shift;
# my @vars = map { $_->value } @{ $self->variables };
# unless (scalar(@vars)) {
# Carp::confess "No vars in project?";
# }
# }
sub plan_as_string {
my $self = shift;
return sprintf('Project { %s }', join(' ', map { '?' . $_->value } @{ $self->variables }));
}
sub tree_attributes { return qw(variables) };
sub substitute_impl {
my $self = shift;
my $model = shift;
my $bind = shift;
my ($impl) = map { $_->substitute_impl($model, $bind) } @{ $self->children };
my @vars = map { $_->value } @{ $self->variables };
my $iter_variables = $self->in_scope_variables;
# TODO: substitute variables in the projection where appropriate
return sub {
my $iter = $impl->();
return $iter->map(sub {
my $r = shift;
my $b = { map { my $t = $r->value($_); $t ? ($_ => $t) : () } @vars };
return Attean::Result->new( bindings => $b );
}, $iter->item_type, variables => $iter_variables);
};
}
sub impl {
my $self = shift;
my $model = shift;
my ($impl) = map { $_->impl($model) } @{ $self->children };
my @vars = map { $_->value } @{ $self->variables };
my $iter_variables = $self->in_scope_variables;
return sub {
my $iter = $impl->();
return $iter->map(sub {
my $r = shift;
my $b = { map { my $t = $r->value($_); $t ? ($_ => $t) : () } @vars };
return Attean::Result->new( bindings => $b );
}, $iter->item_type, variables => $iter_variables);
};
}
}
=item * L
Evaluates a sub-plan and returns the results after fully materializing and
sorting is applied.
=cut
package Attean::Plan::OrderBy 0.034 {
use Moo;
use Types::Standard qw(HashRef ArrayRef InstanceOf Bool Str);
use Scalar::Util qw(blessed);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
has 'variables' => (is => 'ro', isa => ArrayRef[Str], required => 1);
has 'ascending' => (is => 'ro', isa => HashRef[Bool], required => 1);
sub plan_as_string {
my $self = shift;
my @vars = @{ $self->variables };
my $ascending = $self->ascending;
my @strings = map { sprintf('%s(?%s)', ($ascending->{$_} ? 'ASC' : 'DESC'), $_) } @vars;
return sprintf('Order { %s }', join(', ', @strings));
}
sub sort_rows {
my $self = shift;
my $vars = shift;
my $ascending = shift;
my $rows = shift;
local($Attean::API::Binding::ALLOW_IRI_COMPARISON) = 1;
my @sorted = map { $_->[0] } sort {
my ($ar, $avalues) = @$a;
my ($br, $bvalues) = @$b;
my $c = 0;
foreach my $i (0 .. $#{ $vars }) {
my $ascending = $ascending->{ $vars->[$i] };
my ($av, $bv) = map { $_->[$i] } ($avalues, $bvalues);
# Mirrors code in Attean::SimpleQueryEvaluator->evaluate
if (blessed($av) and $av->does('Attean::API::Binding') and (not(defined($bv)) or not($bv->does('Attean::API::Binding')))) {
$c = 1;
} elsif (blessed($bv) and $bv->does('Attean::API::Binding') and (not(defined($av)) or not($av->does('Attean::API::Binding')))) {
$c = -1;
} else {
$c = eval { $av ? $av->compare($bv) : 1 };
if ($@) {
$c = 1;
}
}
$c *= -1 unless ($ascending);
last unless ($c == 0);
}
$c
} map {
my $r = $_;
[$r, [map { $r->value($_) } @$vars]]
} @$rows;
return @sorted;
}
sub impl {
my $self = shift;
my $model = shift;
my $vars = $self->variables;
my $ascending = $self->ascending;
my ($impl) = map { $_->impl($model) } @{ $self->children };
my $iter_variables = $self->in_scope_variables;
return sub {
my $iter = $impl->();
my @rows = $iter->elements;
my @sorted = $self->sort_rows($vars, $ascending, \@rows);
return Attean::ListIterator->new(
values => \@sorted,
variables => $iter_variables,
item_type => $iter->item_type
);
}
}
}
=item * L
Evaluates a SPARQL query against a remote endpoint.
=cut
package Attean::Plan::Service 0.034 {
use Moo;
use Types::Standard qw(ConsumerOf Bool Str InstanceOf);
use Encode qw(encode);
use Scalar::Util qw(blessed);
use URI::Escape;
use Attean::SPARQLClient;
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
has 'endpoint' => (is => 'ro', isa => ConsumerOf['Attean::API::TermOrVariable'], required => 1);
has 'silent' => (is => 'ro', isa => Bool, default => 0);
has 'sparql' => (is => 'ro', isa => Str, required => 1);
has 'user_agent' => (is => 'rw', isa => InstanceOf['LWP::UserAgent']);
has 'request_signer' => (is => 'rw');
sub plan_as_string {
my $self = shift;
my $sparql = $self->sparql;
$sparql =~ s/\s+/ /g;
return sprintf('Service <%s> %s', $self->endpoint->as_string, $sparql);
}
sub tree_attributes { return qw(endpoint) };
sub impl {
my $self = shift;
my $model = shift;
my $endpoint = $self->endpoint->value;
my $sparql = $self->sparql;
my $silent = $self->silent;
my %args = (
endpoint => $endpoint,
silent => $silent,
request_signer => $self->request_signer,
);
$args{user_agent} = $self->user_agent if ($self->user_agent);
my $client = Attean::SPARQLClient->new(%args);
return sub {
return $client->query($sparql);
};
}
}
=item * L
Returns a constant set of results.
=cut
package Attean::Plan::Table 0.034 {
use Moo;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
has variables => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Variable']]);
has rows => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Result']]);
sub tree_attributes { return qw(variables rows) };
sub plan_as_string {
my $self = shift;
my $level = shift;
my $indent = ' ' x ($level + 1);
my $vars = join(', ', map { "?$_" } @{ $self->in_scope_variables });
my $s = "Table (" . $vars . ")";
foreach my $row (@{ $self->rows }) {
$s .= "\n-${indent} " . $row->as_string;
}
return $s;
}
sub BUILDARGS {
my $class = shift;
my %args = @_;
my @vars = map { $_->value } @{ $args{variables} };
if (exists $args{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
$args{in_scope_variables} = \@vars;
return $class->SUPER::BUILDARGS(%args);
}
sub impl {
my $self = shift;
my $model = shift;
my $rows = $self->rows;
my $iter_variables = $self->in_scope_variables;
return sub {
return Attean::ListIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
values => $rows
);
};
}
}
=item * L
Returns a constant set of results.
Be aware that if the iterator being wrapped is not repeatable (consuming the
L role), then this plan may only be evaluated
once.
A size estimate may be given if it is available. If the iterator is an
L, the size of that iterator will be used.
=cut
package Attean::Plan::Iterator 0.034 {
use Moo;
use Types::Standard qw(ArrayRef ConsumerOf Int);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
has iterator => (is => 'ro', isa => ConsumerOf['Attean::API::ResultIterator']);
has size_estimate => (is => 'lazy', isa => Int, predicate => 1);
sub _build_size_estimate {
my $self = shift;
my $iter = $self->iterator;
if ($iter->isa('Attean::ListIterator')) {
return $iter->size;
}
}
sub tree_attributes { return qw(iterator) };
sub plan_as_string {
my $self = shift;
my $level = shift;
my $indent = ' ' x ($level + 1);
my $string = 'Iterator (';
$string .= join(', ', map { "?$_" } @{ $self->in_scope_variables });
if ($self->has_size_estimate) {
$string .= ' with ' . $self->size_estimate . ' elements';
}
$string .= ')';
return $string;
}
sub BUILDARGS {
my $class = shift;
my %args = @_;
my $vars = $args{iterator}->variables;
if (exists $args{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
$args{in_scope_variables} = $vars;
return $class->SUPER::BUILDARGS(%args);
}
sub impl {
my $self = shift;
my $model = shift;
my $iter = $self->iterator;
return sub {
if ($iter->does('Attean::API::RepeatableIterator')) {
$iter->reset;
}
return $iter;
};
}
}
=item * L
=cut
package Attean::Plan::ALPPath 0.034 {
use Moo;
use Attean::TreeRewriter;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
has 'subject' => (is => 'ro', required => 1);
has 'object' => (is => 'ro', required => 1);
has 'graph' => (is => 'ro', required => 1);
has 'step_begin' => (is => 'ro', required => 1);
has 'step_end' => (is => 'ro', required => 1);
has 'skip' => (is => 'ro', required => 1, default => 0);
# has 'children' => (is => 'ro', isa => ConsumerOf['Attean::API::BindingSubstitutionPlan'], required => 1);
with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::NullaryQueryTree';
sub tree_attributes { return qw(subject object graph) };
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
sub plan_as_string {
my $self = shift;
my @strings;
push(@strings, sprintf('%s ↠%s', map { $_->ntriples_string } ($self->subject, $self->step_begin)));
push(@strings, sprintf('%s ↠%s', map { $_->ntriples_string } ($self->object, $self->step_end)));
return sprintf('ALPPath %s', join(', ', @strings));
}
sub BUILDARGS {
my $class = shift;
my %args = @_;
my @vars = map { $_->value } grep { $_->does('Attean::API::Variable') } (@args{qw(subject object)});
if (exists $args{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
$args{in_scope_variables} = \@vars;
return $class->SUPER::BUILDARGS(%args);
}
sub alp {
my $model = shift;
my $graph = shift;
my $skip = shift;
my $x = shift;
my $path = shift;
my $v = shift;
my $start = shift;
my $end = shift;
my $bind = shift;
if (exists $v->{$x->as_string}) {
return;
}
my $binding = Attean::Result->new( bindings => { $start => $x } )->join($bind);
unless ($binding) {
return;
}
if ($skip) {
$skip--;
} else {
$v->{$x->as_string} = $x;
}
my $impl = $path->substitute_impl($model, $binding);
my $iter = $impl->();
while (my $row = $iter->next()) {
my $n = $row->value($end);
alp($model, $graph, $skip, $n, $path, $v, $start, $end, $bind);
}
}
sub substitute_impl {
my $self = shift;
my $model = shift;
my $bind = shift;
my $path = $self->children->[0];
my $subject = $self->subject;
my $object = $self->object;
my $graph = $self->graph;
my $start = $self->step_begin->value;
my $end = $self->step_end->value;
my $skip = $self->skip;
my $iter_variables = $self->in_scope_variables;
for ($subject, $object) {
if ($_->does('Attean::API::Variable')) {
my $name = $_->value;
if (my $node = $bind->value($name)) {
$_ = $node;
}
}
}
my $s_var = $subject->does('Attean::API::Variable');
my $o_var = $object->does('Attean::API::Variable');
if ($s_var and $o_var) {
return sub {
my $nodes = $model->graph_nodes($graph);
my @rows;
while (my $n = $nodes->next) {
my %seen;
alp($model, $graph, $skip, $n, $path, \%seen, $start, $end, $bind);
foreach my $term (values %seen) {
my $b = Attean::Result->new( bindings => {
$subject->value => $n,
$object->value => $term,
} );
push(@rows, $b);
}
}
return Attean::ListIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
values => \@rows,
);
};
} elsif ($o_var) {
return sub {
my %seen;
alp($model, $graph, $skip, $subject, $path, \%seen, $start, $end, $bind);
my @rows = map { Attean::Result->new( bindings => { $object->value => $_ } ) } (values %seen);
return Attean::ListIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
values => \@rows,
);
};
} elsif ($s_var) {
die "ALP for FB should never occur in a plan (should be inversed during planning)";
} else {
return sub {
my %seen;
alp($model, $graph, $skip, $subject, $path, \%seen, $start, $end, $bind);
if (exists $seen{ $object->as_string }) {
return Attean::ListIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
values => [Attean::Result->new()]
);
} else {
return Attean::ListIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
values => []
);
}
};
}
}
}
package Attean::Plan::ZeroOrOnePath 0.034 {
use Moo;
use Attean::TreeRewriter;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
has 'subject' => (is => 'ro', required => 1);
has 'object' => (is => 'ro', required => 1);
has 'graph' => (is => 'ro', required => 1);
with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::NullaryQueryTree';
sub BUILDARGS {
my $class = shift;
my %args = @_;
my @vars = map { $_->value } grep { $_->does('Attean::API::Variable') } (@args{qw(subject object)});
if (exists $args{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
$args{in_scope_variables} = \@vars;
return $class->SUPER::BUILDARGS(%args);
}
sub tree_attributes { return qw(subject object) };
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
sub plan_as_string { return 'ZeroOrOnePath' }
sub substitute_impl {
my $self = shift;
my $model = shift;
my $bind = shift;
my ($impl) = map { $_->substitute_impl($model, $bind) } @{ $self->children };
my $iter_variables = $self->in_scope_variables;
my $subject = $self->subject;
my $object = $self->object;
my $graph = $self->graph;
for ($subject, $object) {
if ($_->does('Attean::API::Variable')) {
my $name = $_->value;
if (my $node = $bind->value($name)) {
$_ = $node;
}
}
}
my $s_var = $subject->does('Attean::API::Variable');
my $o_var = $object->does('Attean::API::Variable');
return sub {
my @extra;
if ($s_var and $o_var) {
my $nodes = $model->graph_nodes($graph);
while (my $n = $nodes->next) {
push(@extra, Attean::Result->new( bindings => { map { $_->value => $n } ($subject, $object) } ));
}
} elsif ($s_var) {
push(@extra, Attean::Result->new( bindings => { $subject->value => $object } ));
} elsif ($o_var) {
push(@extra, Attean::Result->new( bindings => { $object->value => $subject } ));
} else {
if (0 == $subject->compare($object)) {
push(@extra, Attean::Result->new( bindings => {} ));
}
}
my $iter = $impl->();
my %seen;
return Attean::CodeIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
generator => sub {
while (scalar(@extra)) {
my $r = shift(@extra);
unless ($seen{$r->as_string}++) {
return $r;
}
}
while (my $r = $iter->next()) {
return unless ($r);
if ($seen{$r->as_string}++) {
next;
}
return $r;
}
}
);
};
}
}
=item * L
Returns an iterator containing a single boolean term indicating whether any
results were produced by evaluating the sub-plan.
=cut
package Attean::Plan::Exists 0.034 {
use Moo;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
has variables => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Variable']]);
has rows => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Result']]);
sub tree_attributes { return qw(variables rows) };
sub plan_as_string { return 'Exists' }
sub impl {
my $self = shift;
my $model = shift;
my ($impl) = map { $_->impl($model) } @{ $self->children };
return sub {
my $iter = $impl->();
my $result = $iter->next;
# if ($result) {
# warn "EXISTS: " . $result->as_string;
# }
my $term = $result ? Attean::Literal->true : Attean::Literal->false;
return Attean::ListIterator->new(values => [$term], item_type => 'Attean::API::Term');
}
}
}
=item * L
=cut
package Attean::Plan::Aggregate 0.034 {
use Moo;
use Encode;
use UUID::Tiny ':std';
use URI::Escape;
use I18N::LangTags;
use POSIX qw(ceil floor);
use Digest::SHA;
use Digest::MD5 qw(md5_hex);
use Scalar::Util qw(blessed);
use List::MoreUtils qw(uniq);
use Types::Standard qw(ConsumerOf InstanceOf HashRef ArrayRef);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
has 'aggregates' => (is => 'ro', isa => HashRef[ConsumerOf['Attean::API::Expression']], required => 1);
has 'groups' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Expression']], required => 1);
has 'active_graphs' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::IRI']], required => 1);
sub plan_as_string {
my $self = shift;
my @astrings = map { sprintf('?%s ↠%s', $_, $self->aggregates->{$_}->as_string) } keys %{ $self->aggregates };
my @gstrings = map { sprintf('%s', $_->as_string) } @{ $self->groups };
return sprintf('Aggregate { %s } Groups { %s }', join(', ', @astrings), join(', ', @gstrings));
}
sub tree_attributes { return qw(aggregates groups) };
sub BUILD {
# Ensure that the CT extensions are registered
AtteanX::Functions::CompositeLists->register();
AtteanX::Functions::CompositeMaps->register();
}
sub BUILDARGS {
my $class = shift;
my %args = @_;
my $aggs = $args{ aggregates };
my @vars = map { $_->value } grep { $_->does('Attean::API::Variable') } map { $_->value } @{ $args{groups} // [] };
my @evars = (@vars, keys %$aggs);
if (exists $args{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
$args{in_scope_variables} = [@evars];
return $class->SUPER::BUILDARGS(%args);
}
sub evaluate_aggregate {
my $self = shift;
my $model = shift;
my $expr = shift;
my $rows = shift;
my $op = $expr->operator;
my ($e) = @{ $expr->children };
# my @children = map { Attean::Plan::Extend->evaluate_expression($model, $_, $r) } @{ $expr->children };
# warn "$op — " . join(' ', map { $_->as_string } @children);
if ($op eq 'COUNT') {
my $count = 0;
foreach my $r (@$rows) {
if ($e) {
my $term = Attean::Plan::Extend->evaluate_expression($model, $e, $r);
if ($term) {
$count++;
}
} else {
# This is the special-case branch for COUNT(*)
$count++;
}
}
return Attean::Literal->new(value => $count, datatype => 'http://www.w3.org/2001/XMLSchema#integer');
} elsif ($op eq 'SUM') {
my @cmp;
my @terms;
foreach my $r (@$rows) {
my $term = Attean::Plan::Extend->evaluate_expression($model, $e, $r);
if ($term->does('Attean::API::NumericLiteral')) {
push(@terms, $term);
}
}
my $lhs = shift(@terms);
while (my $rhs = shift(@terms)) {
my $type = $lhs->binary_promotion_type($rhs, '+');
my ($lv, $rv) = map { $_->numeric_value } ($lhs, $rhs);
$lhs = Attean::Literal->new(value => ($lv + $rv), datatype => $type);
}
return $lhs;
} elsif ($op eq 'AVG') {
my @cmp;
my $count = 0;
my $all_ints = 1;
my @terms;
if (scalar(@$rows) == 0) {
return Attean::Literal->integer(0);
}
foreach my $r (@$rows) {
my $term = Attean::Plan::Extend->evaluate_expression($model, $e, $r);
die unless ($term->does('Attean::API::NumericLiteral'));
push(@terms, $term);
$count++;
}
my $lhs = shift(@terms);
while (my $rhs = shift(@terms)) {
my $type = $lhs->binary_promotion_type($rhs, '+');
my ($lv, $rv) = map { $_->numeric_value } ($lhs, $rhs);
$lhs = Attean::Literal->new(value => ($lv + $rv), datatype => $type);
}
my $rhs = Attean::Literal->new(value => $count, datatype => 'http://www.w3.org/2001/XMLSchema#integer');
my ($lv, $rv) = map { $_->numeric_value } ($lhs, $rhs);
my $type = $lhs->binary_promotion_type($rhs, '/');
return Attean::Literal->new(value => ($lv / $rv), datatype => $type);
} elsif ($op eq 'SAMPLE') {
foreach my $r (@$rows) {
my $term = Attean::Plan::Extend->evaluate_expression($model, $e, $r);
return $term if (blessed($term));
}
} elsif ($op =~ /^(MIN|MAX)$/) {
my @cmp;
foreach my $r (@$rows) {
my $term = Attean::Plan::Extend->evaluate_expression($model, $e, $r);
push(@cmp, $term);
}
@cmp = sort { $a->compare($b) } @cmp;
return ($op eq 'MIN') ? shift(@cmp) : pop(@cmp);
} elsif ($op eq 'GROUP_CONCAT') {
my $sep = $expr->scalar_vars->{seperator} // ' ';
my @values;
my $all_lang = 1;
my $all_str = 1;
my $lang;
foreach my $r (@$rows) {
my $term = Attean::Plan::Extend->evaluate_expression($model, $e, $r);
die "GROUP_CONCAT called with a non-literal argument" unless ($term->does('Attean::API::Literal'));
if ($term->language) {
$all_str = 0;
if (defined($lang) and $lang ne $term->language) {
$all_lang = 0;
} else {
$lang = $term->language;
}
} else {
$all_lang = 0;
$all_str = 0;
}
push(@values, $term->value);
}
my %strtype;
if ($all_lang and $lang) {
$strtype{language} = $lang;
} elsif ($all_str) {
$strtype{datatype} = 'http://www.w3.org/2001/XMLSchema#string'
}
my $string = join($sep, @values);
return Attean::Literal->new(value => $string, %strtype);
} elsif ($op eq 'FOLD') {
my @arg_exprs = @{ $expr->children };
my $order = $expr->order || [];
my @cmps = @$order;
my @exprs = map { $_->[1] } @cmps;
my @dirs = map { $_->[0] eq 'ASC' } @cmps;
my $l = eval {
if (scalar(@$order)) {
# sort $rows by the order condition
my $fold_cmp = sub {
my ($ar, $avalues) = @$a;
my ($br, $bvalues) = @$b;
my $c = 0;
foreach my $i (0 .. $#cmps) {
my ($av, $bv) = map { $_->[$i] } ($avalues, $bvalues);
# Mirrors code in Attean::Plan::OrderBy->sort_rows
if (not(blessed($av))) {
$c = -1;
} elsif (not(blessed($av))) {
$c = 1;
} elsif (blessed($av) and $av->does('Attean::API::Binding') and (not(defined($bv)) or not($bv->does('Attean::API::Binding')))) {
$c = 1;
} elsif (blessed($bv) and $bv->does('Attean::API::Binding') and (not(defined($av)) or not($av->does('Attean::API::Binding')))) {
$c = -1;
} else {
$c = eval { $av ? $av->compare($bv) : 1 };
if ($@) {
$c = 1;
}
}
$c *= -1 if ($dirs[$i] == 0);
last unless ($c == 0);
}
$c
};
my @sorted = map { $_->[0] } sort { $fold_cmp->() }
map { my $r = $_; [$r, [map { eval { Attean::Plan::Extend->evaluate_expression( $model, $_, $r ) } } @exprs]] }
@$rows;
$rows = \@sorted;
}
my %seen;
if (scalar(@arg_exprs) > 1) {
# map
my @values;
foreach my $r (@$rows) {
my ($key, $value) = map { eval { Attean::Plan::Extend->evaluate_expression($model, $_, $r) } || undef } @arg_exprs;
if (defined($key)) {
push(@values, $key, $value);
}
}
my $func = Attean->get_global_functional_form($AtteanX::Functions::CompositeMaps::MAP_TYPE_IRI);
$func->(undef, undef, @values);
} else {
my @values;
foreach my $r (@$rows) {
my $term = eval { Attean::Plan::Extend->evaluate_expression($model, $e, $r) };
if ($expr->distinct and blessed($term)) {
next if ($seen{ $term->as_string }++);
}
push(@values, $term);
}
my $func = Attean->get_global_functional_form($AtteanX::Functions::CompositeLists::LIST_TYPE_IRI);
$func->(undef, undef, @values);
}
};
warn $@ if $@;
return $l;
} elsif ($op eq 'CUSTOM') {
my $iri = $expr->custom_iri;
my $data = Attean->get_global_aggregate($iri);
unless ($data) {
die "No extension aggregate registered for <$iri>";
}
my $start = $data->{'start'};
my $process = $data->{'process'};
my $finalize = $data->{'finalize'};
my $thunk = $start->($model, $self->active_graphs);
foreach my $r (@$rows) {
my $t = Attean::Plan::Extend->evaluate_expression($model, $e, $r);
$process->($thunk, $t);
}
return $finalize->($thunk);
} else {
warn "Unexpected aggregate expression: $op";
}
die "$op not implemented";
}
sub impl {
my $self = shift;
my $model = shift;
my %aggs = %{ $self->aggregates };
my @groups = @{ $self->groups };
my $iter_variables = $self->in_scope_variables;
my $group_template_generator = sub {
my $r = shift;
my %components;
foreach my $g (@groups) {
if ($g->isa('Attean::ValueExpression')) {
my $value = $g->value;
if ($value->isa('Attean::Variable')) {
my $var = $value->value;
my $value = eval { Attean::Plan::Extend->evaluate_expression($model, $g, $r) };
if (blessed($value)) {
$components{$var} = $value;
}
}
}
}
return %components;
};
my $group_key_generator = sub {
my $r = shift;
my @components;
foreach my $g (@groups) {
my $value = eval { Attean::Plan::Extend->evaluate_expression($model, $g, $r) };
my $key = blessed($value) ? $value->as_string : '';
push(@components, $key);
}
my $group = join('|', @components);
return $group;
};
my $rank;
while (my($var, $agg) = each(%aggs)) {
if ($agg->operator eq 'RANK') {
$rank = $var;
}
}
my ($impl) = map { $_->impl($model) } @{ $self->children };
my %row_groups;
my %group_templates;
return sub {
my $iter = $impl->();
while (my $r = $iter->next) {
my $group_key = $group_key_generator->($r);
push(@{ $row_groups{ $group_key } }, $r);
unless (exists $group_templates{ $group_key }) {
$group_templates{ $group_key } = { $group_template_generator->($r) };
}
}
my @group_keys = keys %row_groups;
# SPARQL evaluation of aggregates over an empty input sequence should
# result in an empty result
my @results;
if (scalar(@group_keys) == 0 and scalar(@groups) == 0) {
push(@group_keys, '');
$row_groups{''} = [];
$group_templates{''} = {};
}
foreach my $group (@group_keys) {
my %row = %{ $group_templates{ $group } };
my $rows = $row_groups{$group};
if (defined $rank) {
my $agg = $aggs{$rank};
my $ascending = $agg->scalar_vars->{ascending} // {};
my $vars = [map { $_->value->value } @{ $agg->children }];
# TODO: support ordering by complex expressions in $vars, not just ValueExpressions with variables
my @sorted = Attean::Plan::OrderBy->sort_rows($vars, $ascending, $rows);
my $ord = 0;
foreach my $row (@sorted) {
my %b = %{ $row->bindings };
$b{ $rank } = Attean::Literal->integer($ord++);
my $r = Attean::Result->new( bindings => \%b );
push(@results, $r);
}
} else {
foreach my $var (keys %aggs) {
my $expr = $aggs{$var};
my $value = eval { $self->evaluate_aggregate($model, $expr, $rows) };
if ($value) {
$row{$var} = $value;
}
}
my $result = Attean::Result->new( bindings => \%row );
push(@results, $result);
}
}
return Attean::ListIterator->new(
values => \@results,
variables => $iter_variables,
item_type => 'Attean::API::Result'
);
};
}
}
package Attean::Plan::Sequence 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Types::Standard qw(ConsumerOf ArrayRef);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::QueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
sub plan_as_string { return 'Sequence'; }
sub impl {
my $self = shift;
my $model = shift;
my @children = map { $_->impl($model) } @{ $self->children };
return sub {
foreach my $child (@children) {
my $iter = $child->();
$iter->elements;
}
return Attean::ListIterator->new(values => [Attean::Literal->true], item_type => 'Attean::API::Term');
};
}
}
package Attean::Plan::Clear 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Types::Standard qw(ConsumerOf ArrayRef);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::NullaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
has 'graphs' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Term']]);
sub plan_as_string {
my $self = shift;
my $level = shift;
my $indent = ' ' x (1+$level);
my $s = sprintf("Clear { %d graphs }", scalar(@{ $self->graphs }));
foreach my $g (@{ $self->graphs }) {
my $name = $g->as_sparql;
chomp($name);
$s .= "\n-${indent} $name";
}
return $s;
}
sub impl {
my $self = shift;
my $model = shift;
my $graphs = $self->graphs;
return sub {
foreach my $g (@$graphs) {
$model->clear_graph($g);
}
return Attean::ListIterator->new(values => [Attean::Literal->true], item_type => 'Attean::API::Term');
};
}
}
package Attean::Plan::Drop 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Types::Standard qw(ConsumerOf ArrayRef);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::NullaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
has 'graphs' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Term']]);
sub plan_as_string {
my $self = shift;
my $level = shift;
my $indent = ' ' x (1+$level);
my $s = sprintf("Drop { %d graphs }", scalar(@{ $self->graphs }));
foreach my $g (@{ $self->graphs }) {
$s .= "\n-${indent} " . $g->as_sparql;
}
return $s;
}
sub impl {
my $self = shift;
my $model = shift;
my $graphs = $self->graphs;
return sub {
foreach my $g (@$graphs) {
$model->drop_graph($g);
}
return Attean::ListIterator->new(values => [Attean::Literal->true], item_type => 'Attean::API::Term');
};
}
}
package Attean::Plan::TripleTemplateToModelQuadMethod 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Types::Standard qw(ConsumerOf Str ArrayRef HashRef);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::UnaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
has 'order' => (is => 'ro', isa => ArrayRef[Str], required => 1);
has 'patterns' => (is => 'ro', isa => HashRef[ArrayRef[ConsumerOf['Attean::API::TripleOrQuadPattern']]], required => 1);
has 'graph' => (is => 'ro', isa => ConsumerOf['Attean::API::Term']);
sub plan_as_string {
my $self = shift;
my $level = shift;
my $indent = ' ' x (1+$level);
my $s = sprintf("Template-to-Model { Default graph: %s }", $self->graph->as_string);
foreach my $method (@{ $self->order }) {
my $pattern = $self->patterns->{ $method };
$s .= "\n-${indent} Method: ${method}";
foreach my $p (@$pattern) {
$s .= "\n-${indent} " . $p->as_string;
}
}
return $s;
}
sub impl {
my $self = shift;
my $model = shift;
my $child = $self->children->[0]->impl($model);
my $graph = $self->graph;
my @order = @{ $self->order };
my $method = shift(@order);
my $pattern = $self->patterns->{ $method };
return sub {
my $iter = $child->();
my @results;
while (my $t = $iter->next) {
if (scalar(@order)) {
push(@results, $t);
}
foreach my $p (@$pattern) {
my $q = $p->apply_bindings($t);
my $quad = $q->does('Attean::API::QuadPattern') ? $q : $q->as_quad_pattern($graph);
if ($quad->is_ground) {
# warn "# $method: " . $quad->as_string . "\n";
$model->$method($quad->as_quad);
} else {
# warn "not ground: " . $quad->as_string;
}
}
}
foreach my $method (@order) {
my $pattern = $self->patterns->{ $method };
foreach my $t (@results) {
foreach my $p (@$pattern) {
my $q = $p->apply_bindings($t);
my $quad = $q->does('Attean::API::QuadPattern') ? $q : $q->as_quad_pattern($graph);
if ($quad->is_ground) {
# warn "# $method: " . $quad->as_string . "\n";
$model->$method($quad->as_quad);
} else {
# warn "not ground: " . $quad->as_string;
}
}
}
}
return Attean::ListIterator->new(values => [Attean::Literal->integer($model->size)], item_type => 'Attean::API::Term');
};
}
}
package Attean::Plan::Load 0.034 {
use Moo;
use Encode;
use LWP::UserAgent;
use Scalar::Util qw(blessed);
use Types::Standard qw(Bool Str);
use namespace::clean;
with 'Attean::API::Plan', 'Attean::API::NullaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
has 'silent' => (is => 'ro', isa => Bool, default => 0);
has 'url' => (is => 'ro', isa => Str);
sub plan_as_string {
my $self = shift;
return sprintf("Load { %s }", $self->url);
}
sub impl {
my $self = shift;
my $url = $self->url;
my $ua = LWP::UserAgent->new();
my $silent = $self->silent;
my $accept = Attean->acceptable_parsers( handles => 'Attean::API::Triple' );
$ua->default_headers->push_header( 'Accept' => $accept );
return sub {
my $resp = $ua->get( $url );
if ($resp->is_success) {
my $ct = $resp->header('Content-Type');
if (my $pclass = Attean->get_parser( media_type => $ct )) {
my $p = $pclass->new();
my $str = $resp->decoded_content;
my $bytes = encode('UTF-8', $str, Encode::FB_CROAK);
my $iter = $p->parse_iter_from_bytes( $bytes );
return $iter;
}
}
if ($silent) {
return Attean::ListIterator->new(values => [], item_type => 'Attean::API::Triple');
} else {
die "Failed to load url: " . $resp->status_line;
}
};
}
}
=item * L
=cut
package Attean::Plan::Unfold 0.032 {
use Moo;
use Encode;
use UUID::Tiny ':std';
use URI::Escape;
use Data::Dumper;
use I18N::LangTags;
use POSIX qw(ceil floor);
use Digest::SHA;
use Digest::MD5 qw(md5_hex);
use Scalar::Util qw(blessed looks_like_number);
use List::MoreUtils qw(uniq all);
use Types::Standard qw(ConsumerOf ArrayRef InstanceOf HashRef);
use namespace::clean;
with 'MooX::Log::Any';
with 'Attean::API::BindingSubstitutionPlan', 'Attean::API::UnaryQueryTree';
has 'variables' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Variable']], required => 1);
has 'expression' => (is => 'ro', isa => ConsumerOf['Attean::API::Expression'], required => 1);
has 'active_graphs' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::IRI']], required => 1);
sub plan_as_string {
my $self = shift;
my @vars = map { $_->as_string } @{ $self->variables };
my $vars = '(' . join(', ', @vars) . ')';
return sprintf('Unfold { %s ↠%s }', $vars, $self->expression->as_string);
}
sub tree_attributes { return qw(variable expression) };
sub BUILDARGS {
my $class = shift;
my %args = @_;
my $exprs = $args{ expressions };
my @vars = map { @{ $_->in_scope_variables } } @{ $args{ children } };
my @evars = (@vars, keys %$exprs);
if (exists $args{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
$args{in_scope_variables} = [@evars];
return $class->SUPER::BUILDARGS(%args);
}
sub substitute_impl {
my $self = shift;
my $model = shift;
my $bind = shift;
my $expr = $self->expression;
my $vars = $self->variables;
my ($impl) = map { $_->substitute_impl($model, $bind) } @{ $self->children };
# TODO: substitute variables in the expression
return $self->_impl($model, $impl, $expr, @$vars);
}
sub impl {
my $self = shift;
my $model = shift;
my $expr = $self->expression;
my $vars = $self->variables;
my ($impl) = map { $_->impl($model) } @{ $self->children };
return $self->_impl($model, $impl, $expr, @$vars);
}
sub _impl {
my $self = shift;
my $model = shift;
my $impl = shift;
Carp::confess unless (defined($impl));
my $expr = shift;
my @vars = @_;
my $iter_variables = $self->in_scope_variables;
my $var = $vars[0];
my $index_var = $vars[1];
return sub {
my $iter = $impl->();
my @buffer;
return Attean::CodeIterator->new(
item_type => 'Attean::API::Result',
variables => $iter_variables,
generator => sub {
if (scalar(@buffer)) {
return shift(@buffer);
}
ROW: while (my $r = $iter->next) {
my %base = map { $_ => $r->value($_) } $r->variables;
my $cdt = eval { Attean::Plan::Extend->evaluate_expression($model, $expr, $r) };
warn "UNFOLD expression evaluation error: $@" if ($@);
unless ($cdt) {
return $r;
}
if ($cdt->does('Attean::API::Literal') and $cdt->datatype->value eq $AtteanX::Functions::CompositeLists::LIST_TYPE_IRI) {
my @values = AtteanX::Functions::CompositeLists::lex_to_list($cdt);
foreach my $index (0 .. $#values) {
my %row = %base;
my $term = $values[$index];
if (blessed($term)) {
if ($row{ $var } and $term->as_string ne $row{ $var }->as_string) {
next ROW;
}
if ($term->does('Attean::API::Binding')) {
# patterns need to be made ground to be bound as values (e.g. TriplePattern -> Triple)
$term = $term->ground($r);
}
$row{ $var->value } = $term;
if (defined $index_var) {
$row{ $index_var->value } = Attean::Literal->integer(1+$index);
}
}
push(@buffer, Attean::Result->new( bindings => \%row, eval_stash => $r->eval_stash ));
}
} elsif ($cdt->does('Attean::API::Literal') and $cdt->datatype->value eq $AtteanX::Functions::CompositeMaps::MAP_TYPE_IRI) {
my @values = AtteanX::Functions::CompositeMaps::lex_to_maplist($cdt);
while (my @pair = splice(@values, 0, 2)) {
my ($index, $term) = @pair;
my %row = %base;
if (blessed($term)) {
if ($row{ $var } and $term->as_string ne $row{ $var }->as_string) {
next ROW;
}
if ($term->does('Attean::API::Binding')) {
# patterns need to be made ground to be bound as values (e.g. TriplePattern -> Triple)
$term = $term->ground($r);
}
if (defined $index_var) {
# 2-var
$row{ $index_var->value } = $term;
$row{ $var->value } = $index;
} else {
# 1-var
$row{ $var->value } = $term;
}
}
push(@buffer, Attean::Result->new( bindings => \%row, eval_stash => $r->eval_stash ));
}
}
return shift(@buffer);
}
return;
}
);
};
}
}
# Create(iri)
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/IDPQueryPlanner.pm 000644 000765 000024 00000000225 14636707547 022320 x ustar 00greg staff 000000 000000 30 mtime=1719373671.858723731
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/IDPQueryPlanner.pm 000644 000765 000024 00000002373 14636707547 020355 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::IDPQueryPlanner - Iterative dynamic programming query planner
=head1 VERSION
This document describes Attean::IDPQueryPlanner version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $planner = Attean::IDPQueryPlanner->new();
my $default_graphs = [ Attean::IRI->new('http://example.org/') ];
my $plan = $planner->plan_for_algebra( $algebra, $model, $default_graphs );
my $iter = $plan->evaluate($model);
my $iter = $e->evaluate( $model );
=head1 DESCRIPTION
The Attean::IDPQueryPlanner class implements a query planner using the
iterative dynamic programming approach.
=head1 ATTRIBUTES
=over 4
=back
=head1 METHODS
=over 4
=cut
package Attean::IDPQueryPlanner 0.034 {
use Moo;
use namespace::clean;
extends 'Attean::QueryPlanner';
with 'Attean::API::IDPJoinPlanner';
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/QuadModel.pm 000644 000765 000024 00000000223 14636707547 021207 x ustar 00greg staff 000000 000000 28 mtime=1719373671.9864777
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/QuadModel.pm 000644 000765 000024 00000007061 14636707547 017245 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::QuadModel - RDF model backed by a quad-store
=head1 VERSION
This document describes Attean::QuadModel version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $model = Attean::QuadModel->new( store => $store );
=head1 DESCRIPTION
The Attean::QuadModel class represents a model that is backed by a single
L object.
It conforms to the L role.
The Attean::QuadModel constructor requires one named argument:
=over 4
=item store
A L object representing the backing
quad-store.
=back
=head1 METHODS
=over 4
=cut
package Attean::QuadModel 0.034 {
use Moo;
use Scalar::Util qw(reftype);
use namespace::clean;
has 'store' => (
is => 'ro',
does => 'Attean::API::QuadStore',
required => 1,
handles => [qw(size count_quads count_quads_estimate get_graphs holds)],
);
=item C<< get_quads ( $subject, $predicate, $object, $graph ) >>
Returns an L for quads in the model that match the
supplied C<< $subject >>, C<< $predicate >>, C<< $object >>, and C<< $graph >>.
Any of these terms may be undefined or a L object, in
which case that term will be considered as a wildcard for the purposes of
matching.
The returned iterator conforms to both L and
L.
=cut
sub get_quads {
my $self = shift;
my @nodes = @_[0..3];
foreach my $i (0..3) {
my $t = $nodes[$i];
if (not(ref($t)) or reftype($t) ne 'ARRAY') {
$nodes[$i] = [$t];
}
}
my @iters;
foreach my $s (@{ $nodes[0] }) {
foreach my $p (@{ $nodes[1] }) {
foreach my $o (@{ $nodes[2] }) {
foreach my $g (@{ $nodes[3] }) {
push(@iters, $self->store->get_quads($s, $p, $o, $g));
}
}
}
}
if (scalar(@iters) == 0) {
return Attean::ListIterator->new(values => [], item_type => 'Attean::API::Quad');
} elsif (scalar(@iters) == 1) {
return shift(@iters);
} else {
return Attean::IteratorSequence->new( iterators => \@iters, item_type => $iters[0]->item_type );
}
}
=item C<< plans_for_algebra( $algebra, $model, $active_graphs, $default_graphs ) >>
Delegates to the underlying store if the store consumes Attean::API::CostPlanner.
=cut
sub plans_for_algebra {
my $self = shift;
if ($self->store->does('Attean::API::CostPlanner')) {
return $self->store->plans_for_algebra(@_);
}
return;
}
=item C<< cost_for_plan( $plan ) >>
Delegates to the underlying store if the store consumes Attean::API::CostPlanner.
=cut
sub cost_for_plan {
my $self = shift;
if ($self->store->does('Attean::API::CostPlanner')) {
return $self->store->cost_for_plan(@_);
}
return;
}
with 'Attean::API::Model';
with 'Attean::API::CostPlanner';
}
package Attean::MutableQuadModel 0.034 {
use Moo;
extends 'Attean::QuadModel';
has 'store' => (
is => 'ro',
does => 'Attean::API::MutableQuadStore',
required => 1,
handles => [qw(size count_quads count_quads_estimate add_quad remove_quad get_graphs create_graph drop_graph clear_graph add_iter)],
);
with 'Attean::API::MutableModel';
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/SimpleQueryEvaluator.pm 000644 000765 000024 00000000223 14636707550 023470 x ustar 00greg staff 000000 000000 28 mtime=1719373672.0573448
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/SimpleQueryEvaluator.pm 000644 000765 000024 00000144630 14636707550 021532 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::SimpleQueryEvaluator - Simple query evaluator
=head1 VERSION
This document describes Attean::SimpleQueryEvaluator version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $algebra = Attean->get_parser('SPARQL')->parse('SELECT * WHERE { ... }');
my $active_graph = Attean::IRI->new('http://example.org/');
my $e = Attean::SimpleQueryEvaluator->new( model => $model );
my $iter = $e->evaluate( $algebra, $active_graph );
=head1 DESCRIPTION
The Attean::SimpleQueryEvaluator class implements a simple query evaluator that,
given an L and a L
object, evaluates the query represented by the algebra using data from the
model, and returns a query result.
=head1 ATTRIBUTES
=over 4
=cut
use Attean::Algebra;
use Attean::Expression;
package Attean::SimpleQueryEvaluator 0.034 {
use Moo;
use Encode qw(encode);
use Attean::RDF;
use AtteanX::Functions::CompositeLists;
use AtteanX::Functions::CompositeMaps;
use LWP::UserAgent;
use Scalar::Util qw(blessed);
use List::Util qw(all any reduce);
use Types::Standard qw(ConsumerOf InstanceOf Bool Object);
use URI::Escape;
use Attean::SPARQLClient;
use namespace::clean;
=item C<< model >>
The L object used for query evaluation.
=cut
has 'model' => (is => 'ro', isa => ConsumerOf['Attean::API::Model'], required => 1);
=item C<< default_graph >>
The L object representing the default graph in the C<< model >>.
The default graph will be excluded from enumeration of graph names for query
features such as C<< GRAPH ?g {} >>.
=cut
has 'default_graph' => (is => 'ro', isa => ConsumerOf['Attean::API::IRI'], required => 1);
has 'user_agent' => (is => 'rw', isa => InstanceOf['LWP::UserAgent'], default => sub { my $ua = LWP::UserAgent->new(); $ua->agent("Attean/$Attean::VERSION " . $ua->_agent); $ua });
=item C<< request_signer >>
If set, used to modify HTTP::Request objects used in evaluating SERVICE calls
before the request is made. This may be used to, for example, add cryptographic
signature headers to the request. The modification is performed by calling
C<< $request_signer->sign( $request ) >>.
=cut
has 'request_signer' => (is => 'rw', isa => Object);
has 'ground_blanks' => (is => 'rw', isa => Bool, default => 0);
sub BUILD {
# Ensure that the CT extensions are registered
AtteanX::Functions::CompositeLists->register();
AtteanX::Functions::CompositeMaps->register();
}
=back
=head1 METHODS
=over 4
=item C<< evaluate( $algebra, $active_graph ) >>
Returns an L object with results produced by evaluating
the query C<< $algebra >> against the evaluator's C<< model >>, using the
supplied C<< $active_graph >>.
=cut
sub evaluate {
my $self = shift;
my $algebra = shift;
my $active_graph = shift || Carp::confess "No active-graph passed to Attean::SimpleQueryEvaluator->evaluate";
Carp::confess "No algebra passed for evaluation" unless ($algebra);
my $expr_eval = Attean::SimpleQueryEvaluator::ExpressionEvaluator->new( evaluator => $self );
my @children = @{ $algebra->children };
my ($child) = $children[0];
if ($algebra->isa('Attean::Algebra::Query') or $algebra->isa('Attean::Algebra::Update')) {
return $self->evaluate($algebra->child, $active_graph, @_);
} elsif ($algebra->isa('Attean::Algebra::BGP')) {
my @triples = @{ $algebra->triples };
if (scalar(@triples) == 0) {
my $b = Attean::Result->new( bindings => {} );
return Attean::ListIterator->new(variables => [], values => [$b], item_type => 'Attean::API::Result');
} else {
my @iters;
my @new_vars;
my %blanks;
foreach my $t (@triples) {
push(@iters, $self->evaluate_pattern($t, $active_graph, \@new_vars, \%blanks));
}
while (scalar(@iters) > 1) {
my ($lhs, $rhs) = splice(@iters, 0, 2);
unshift(@iters, $lhs->join($rhs));
}
return shift(@iters)->map(sub { shift->project_complement(@new_vars) });
}
} elsif ($algebra->isa('Attean::Algebra::Distinct') or $algebra->isa('Attean::Algebra::Reduced')) {
my %seen;
my $iter = $self->evaluate( $child, $active_graph );
return $iter->grep(sub {
my $r = shift;
my $str = $r->as_string;
my $ok = not($seen{ $str }) ? 1 : 0;
$seen{ $str }++;
return $ok;
});
} elsif ($algebra->isa('Attean::Algebra::Extend')) {
my $child = $algebra;
my @extends;
my %extends;
while ($child->isa('Attean::Algebra::Extend')) {
my $expr = $child->expression;
my $var = $child->variable->value;
$extends{ $var } = $expr;
unshift(@extends, $var);
($child) = @{ $child->children };
}
return $self->evaluate( $child, $active_graph )->map(sub {
my $r = shift;
my %extension;
my %row_cache;
foreach my $var (@extends) {
my $expr = $extends{ $var };
my $val = $expr_eval->evaluate_expression( $expr, $r, $active_graph, \%row_cache );
if (blessed($val) and $val->does('Attean::API::Binding')) {
# patterns need to be made ground to be bound as values (e.g. TriplePattern -> Triple)
$val = $val->ground($r);
}
# warn "Extend error: $@" if ($@);
$r = Attean::Result->new( bindings => { $var => $val } )->join($r) if ($val);
}
return $r;
});
} elsif ($algebra->isa('Attean::Algebra::Unfold')) {
my $expr = $algebra->expression;
my @vars = map { $_->value } @{ $algebra->variables };
my ($first, $second);
$first = $vars[0];
if (scalar(@vars) == 2) {
$second = $vars[1];
}
my ($child) = @{ $algebra->children };
my $iter = $self->evaluate( $child, $active_graph );
my @results;
while (my $r = $iter->next) {
my %extension;
my %row_cache;
my $val = $expr_eval->evaluate_expression( $expr, $r, $active_graph, \%row_cache );
my $dt = $val->datatype;
if ($dt->value eq $AtteanX::Functions::CompositeLists::LIST_TYPE_IRI) {
my @nodes = AtteanX::Functions::CompositeLists::lex_to_list($val);
foreach my $i (0 .. $#nodes) {
my $val = $nodes[$i];
my %bindings;
if (defined($val)) {
if ($val->does('Attean::API::Binding')) {
# patterns need to be made ground to be bound as values (e.g. TriplePattern -> Triple)
$val = $val->ground($r);
}
# warn "Unfold error: $@" if ($@);
$bindings{$first} = $val if ($val);
}
if (defined($second)) {
$bindings{$second} = Attean::Literal->integer(1+$i);
}
my $new = Attean::Result->new( bindings => \%bindings )->join($r);
push(@results, $new);
}
} elsif ($dt->value eq $AtteanX::Functions::CompositeMaps::MAP_TYPE_IRI) {
my @nodes = AtteanX::Functions::CompositeMaps::lex_to_map($val);
# The namespace map here is used to correctly parse maps with boolean keys like `{true:1}`
my $p = AtteanX::Parser::Turtle->new();
while (my ($key_string, $val) = splice(@nodes, 0, 2)) {
my $key = $p->parse_node($key_string); # TODO: this mistakes "true:4" as an attempted prefixname, not a key-value pair
my %bindings;
if (defined($first)) {
if ($key->does('Attean::API::Binding')) {
# patterns need to be made ground to be bound as values (e.g. TriplePattern -> Triple)
$key = $key->ground($r);
}
# warn "Unfold error: $@" if ($@);
$bindings{$first} = $key if ($key);
}
if (defined($second)) {
if (blessed($val) and $val->does('Attean::API::Binding')) {
# patterns need to be made ground to be bound as values (e.g. TriplePattern -> Triple)
$val = $val->ground($r);
}
# warn "Unfold error: $@" if ($@);
$bindings{$second} = $val if ($val);
}
my $new = Attean::Result->new( bindings => \%bindings )->join($r);
push(@results, $new);
}
}
}
my %vars = map { $_ => 1 } $iter->variables;
$vars{$first}++;
$vars{$second}++ if defined($second);
return Attean::ListIterator->new(variables => [keys %vars], values => \@results, item_type => 'Attean::API::Result');
} elsif ($algebra->isa('Attean::Algebra::Filter')) {
# TODO: Merge adjacent filter evaluation so that they can share a row_cache hash (as is done for Extend above)
my $expr = $algebra->expression;
my $iter = $self->evaluate( $child, $active_graph );
return $iter->grep(sub {
my $t = $expr_eval->evaluate_expression( $expr, shift, $active_graph, {} );
# if ($@) { warn "Filter evaluation: $@\n" };
return ($t ? $t->ebv : 0);
});
} elsif ($algebra->isa('Attean::Algebra::OrderBy')) {
local($Attean::API::Binding::ALLOW_IRI_COMPARISON) = 1;
my $iter = $self->evaluate( $child, $active_graph );
my @rows = $iter->elements;
my @cmps = @{ $algebra->comparators };
my @exprs = map { $_->expression } @cmps;
my @dirs = map { $_->ascending } @cmps;
my @sorted = map { $_->[0] } sort {
my ($ar, $avalues) = @$a;
my ($br, $bvalues) = @$b;
my $c = 0;
foreach my $i (0 .. $#cmps) {
my ($av, $bv) = map { $_->[$i] } ($avalues, $bvalues);
# Mirrors code in Attean::Plan::OrderBy->sort_rows
if (blessed($av) and $av->does('Attean::API::Binding') and (not(defined($bv)) or not($bv->does('Attean::API::Binding')))) {
$c = 1;
} elsif (blessed($bv) and $bv->does('Attean::API::Binding') and (not(defined($av)) or not($av->does('Attean::API::Binding')))) {
$c = -1;
} else {
$c = eval { $av ? $av->order($bv) : 1 };
if ($@) {
$c = 1;
}
}
$c *= -1 if ($dirs[$i] == 0);
last unless ($c == 0);
}
$c
} map { my $r = $_; [$r, [map { $expr_eval->evaluate_expression( $_, $r, $active_graph, {} ) } @exprs]] } @rows;
return Attean::ListIterator->new( values => \@sorted, item_type => $iter->item_type, variables => $iter->variables);
} elsif ($algebra->isa('Attean::Algebra::Service')) {
my $endpoint = $algebra->endpoint->value;
my ($pattern) = @{ $algebra->children };
my $sparql = Attean::Algebra::Project->new( variables => [ map { variable($_) } $pattern->in_scope_variables ], children => [ $pattern ] )->as_sparql;
my $silent = $algebra->silent;
my $client = Attean::SPARQLClient->new(
endpoint => $endpoint,
silent => $silent,
user_agent => $self->user_agent,
request_signer => $self->request_signer,
);
return $client->query($sparql);
} elsif ($algebra->isa('Attean::Algebra::Graph')) {
my $graph = $algebra->graph;
return $self->evaluate($child, $graph) if ($graph->does('Attean::API::Term'));
my @iters;
my $graphs = $self->model->get_graphs();
my %vars;
while (my $g = $graphs->next) {
next if ($g->value eq $self->default_graph->value);
my $gr = Attean::Result->new( bindings => { $graph->value => $g } );
my $iter = $self->evaluate($child, $g)->map(sub { if (my $result = shift->join($gr)) { return $result } else { return } });
foreach my $v (@{ $iter->variables }) {
$vars{$v}++;
}
push(@iters, $iter);
}
return Attean::IteratorSequence->new( variables => [keys %vars], iterators => \@iters, item_type => 'Attean::API::Result' );
} elsif ($algebra->isa('Attean::Algebra::Group')) {
my @groupby = @{ $algebra->groupby };
my $iter = $self->evaluate($child, $active_graph);
my %groups;
while (my $r = $iter->next) {
my %vars;
my %row_cache;
my @group_terms = map { $expr_eval->evaluate_expression( $_, $r, $active_graph, \%row_cache ) } @groupby;
my $key = join(' ', map { blessed($_) ? $_->as_string : '' } @group_terms);
my %group_bindings;
foreach my $i (0 .. $#group_terms) {
my $v = $groupby[$i];
if (blessed($v) and $v->isa('Attean::ValueExpression') and $v->value->does('Attean::API::Variable') and $group_terms[$i]) {
$group_bindings{$v->value->value} = $group_terms[$i];
}
}
$groups{$key} = [Attean::Result->new( bindings => \%group_bindings ), []] unless (exists($groups{$key}));
push(@{ $groups{$key}[1] }, $r);
}
my @keys = keys %groups;
$groups{''} = [Attean::Result->new( bindings => {} ), []] if (scalar(@keys) == 0);
my $aggs = $algebra->aggregates;
my @results;
my %vars;
foreach my $key (keys %groups) {
my %row_cache;
my ($binding, $rows) = @{ $groups{$key} };
my $count = scalar(@$rows);
my %bindings;
foreach my $i (0 .. $#{ $aggs }) {
my $name = $aggs->[$i]->variable->value;
my $term = $expr_eval->evaluate_expression( $aggs->[$i], $rows, $active_graph, {} );
# warn "AGGREGATE error: $@" if ($@);
$vars{$name}++;
$bindings{ $name } = $term if ($term);
}
push(@results, Attean::Result->new( bindings => \%bindings )->join($binding));
}
return Attean::ListIterator->new(variables => [keys %vars], values => \@results, item_type => 'Attean::API::Result');
} elsif ($algebra->isa('Attean::Algebra::Join')) {
my ($lhs, $rhs) = map { $self->evaluate($_, $active_graph) } @children;
return $lhs->join($rhs);
} elsif ($algebra->isa('Attean::Algebra::LeftJoin')) {
my $expr = $algebra->expression;
my ($lhs_iter, $rhs_iter) = map { $self->evaluate($_, $active_graph) } @children;
my @rhs = $rhs_iter->elements;
my @results;
my %vars = map { $_ => 1 } (@{ $lhs_iter->variables }, @{ $rhs_iter->variables });
while (my $lhs = $lhs_iter->next) {
my $joined = 0;
foreach my $rhs (@rhs) {
if (my $j = $lhs->join($rhs)) {
if ($expr_eval->evaluate_expression( $expr, $j, $active_graph, {} )->ebv) {
$joined++;
push(@results, $j);
}
}
}
push(@results, $lhs) unless ($joined);
}
return Attean::ListIterator->new( variables => [keys %vars], values => \@results, item_type => 'Attean::API::Result');
} elsif ($algebra->isa('Attean::Algebra::Minus')) {
my ($lhsi, $rhs) = map { $self->evaluate($_, $active_graph) } @children;
my @rhs = $rhs->elements;
my @results;
while (my $lhs = $lhsi->next) {
my @compatible;
my @disjoint;
RHS: foreach my $rhs (@rhs) {
if (my $j = $lhs->join($rhs)) {
push(@compatible, 1);
} else {
push(@compatible, 0);
}
my $intersects = 0;
my %lhs_dom = map { $_ => 1 } $lhs->variables;
foreach my $rvar ($rhs->variables) {
if (exists $lhs_dom{$rvar}) {
$intersects = 1;
}
}
push(@disjoint, not($intersects));
}
my $count = scalar(@rhs);
my $keep = 1;
foreach my $i (0 .. $#rhs) {
$keep = 0 unless ($compatible[$i] == 0 or $disjoint[$i] == 1);
}
push(@results, $lhs) if ($keep);
}
return Attean::ListIterator->new( variables => $lhsi->variables, values => \@results, item_type => 'Attean::API::Result');
} elsif ($algebra->isa('Attean::Algebra::Path')) {
my $s = $algebra->subject;
my $path = $algebra->path;
my $o = $algebra->object;
my @children = @{ $path->children };
my ($child) = $children[0];
return $self->model->get_bindings( $s, $path->predicate, $o, $active_graph ) if ($path->isa('Attean::Algebra::PredicatePath'));
if ($path->isa('Attean::Algebra::InversePath')) {
my $path = Attean::Algebra::Path->new( subject => $o, path => $child, object => $s );
return $self->evaluate( $path, $active_graph );
} elsif ($path->isa('Attean::Algebra::AlternativePath')) {
my @children = @{ $path->children };
my @algebras = map { Attean::Algebra::Path->new( subject => $s, path => $_, object => $o ) } @children;
my @iters = map { $self->evaluate($_, $active_graph) } @algebras;
return Attean::IteratorSequence->new( iterators => \@iters, item_type => $iters[0]->item_type, variables => [$algebra->in_scope_variables] );
} elsif ($path->isa('Attean::Algebra::NegatedPropertySet')) {
my $preds = $path->predicates;
my %preds = map { $_->value => 1 } @$preds;
my $filter = $self->model->get_quads($s, undef, $o, $active_graph)->grep(sub {
my $q = shift;
my $p = $q->predicate;
return not exists $preds{ $p->value };
});
my %vars;
$vars{subject} = $s->value if ($s->does('Attean::API::Variable'));
$vars{object} = $o->value if ($o->does('Attean::API::Variable'));
return $filter->map(sub {
my $q = shift;
return unless $q;
my %bindings = map { $vars{$_} => $q->$_() } (keys %vars);
return Attean::Result->new( bindings => \%bindings );
}, 'Attean::API::Result', variables => [values %vars]);
} elsif ($path->isa('Attean::Algebra::SequencePath')) {
if (scalar(@children) == 1) {
my $path = Attean::Algebra::Path->new( subject => $s, path => $children[0], object => $o );
return $self->evaluate($path, $active_graph);
} else {
my @paths;
my $first = shift(@children);
my $join = Attean::Variable->new();
my @new_vars = ($join->value);
push(@paths, Attean::Algebra::Path->new( subject => $s, path => $first, object => $join ));
foreach my $i (0 .. $#children) {
my $newjoin = Attean::Variable->new();
my $obj = ($i == $#children) ? $o : $newjoin;
push(@new_vars, $newjoin->value);
push(@paths, Attean::Algebra::Path->new( subject => $join, path => $children[$i], object => $obj ));
$join = $newjoin;
}
while (scalar(@paths) > 1) {
my ($l, $r) = splice(@paths, 0, 2);
unshift(@paths, Attean::Algebra::Join->new( children => [$l, $r] ));
}
return $self->evaluate(shift(@paths), $active_graph)->map(sub { shift->project_complement(@new_vars) });
}
} elsif ($path->isa('Attean::Algebra::ZeroOrMorePath') or $path->isa('Attean::Algebra::OneOrMorePath')) {
if ($s->does('Attean::API::TermOrTriple') and $o->does('Attean::API::Variable')) {
my $v = {};
if ($path->isa('Attean::Algebra::ZeroOrMorePath')) {
$self->_ALP($active_graph, $s, $child, $v);
} else {
my $iter = $self->_eval($active_graph, $s, $child);
while (my $n = $iter->next) {
$self->_ALP($active_graph, $n, $child, $v);
}
}
my @results = map { Attean::Result->new( bindings => { $o->value => $_ } ) } (values %$v);
return Attean::ListIterator->new(variables => [$o->value], values => \@results, item_type => 'Attean::API::Result');
} elsif ($s->does('Attean::API::Variable') and $o->does('Attean::API::Variable')) {
my $nodes = $self->model->graph_nodes( $active_graph );
my @results;
while (my $t = $nodes->next) {
my $tr = Attean::Result->new( bindings => { $s->value => $t } );
my $p = Attean::Algebra::Path->new( subject => $t, path => $path, object => $o );
my $iter = $self->evaluate($p, $active_graph);
while (my $r = $iter->next) {
push(@results, $r->join($tr));
}
}
my %vars = map { $_ => 1 } ($s->value, $o->value);
return Attean::ListIterator->new(variables => [keys %vars], values => \@results, item_type => 'Attean::API::Result');
} elsif ($s->does('Attean::API::Variable') and $o->does('Attean::API::TermOrTriple')) {
my $pp = Attean::Algebra::InversePath->new( children => [$child] );
my $p = Attean::Algebra::Path->new( subject => $o, path => $pp, object => $s );
return $self->evaluate($p, $active_graph);
} else { # Term ZeroOrMorePath(path) Term
my $v = {};
$self->_ALP($active_graph, $s, $child, $v);
my @results;
foreach my $v (values %$v) {
return Attean::ListIterator->new(variables => [], values => [Attean::Result->new()], item_type => 'Attean::API::Result')
if ($v->equals($o));
}
return Attean::ListIterator->new(variables => [], values => [], item_type => 'Attean::API::Result');
}
} elsif ($path->isa('Attean::Algebra::ZeroOrOnePath')) {
my $path = Attean::Algebra::Path->new( subject => $s, path => $child, object => $o );
my @iters;
my %seen;
push(@iters, $self->evaluate( $path, $active_graph )->grep(sub { return not($seen{shift->as_string}++); }));
push(@iters, $self->_zeroLengthPath($s, $o, $active_graph));
my %vars;
foreach my $iter (@iters) {
$vars{$_}++ for (@{ $iter->variables });
}
return Attean::IteratorSequence->new( iterators => \@iters, item_type => 'Attean::API::Result', variables => [keys %vars] );
}
die "Unimplemented path type: $path";
} elsif ($algebra->isa('Attean::Algebra::Project')) {
my $iter = $self->evaluate( $child, $active_graph );
my @vars = map { $_->value } @{ $algebra->variables };
return $iter->map(sub {
my $r = shift;
my $b = { map { my $t = $r->value($_); $t ? ($_ => $t) : () } @vars };
return Attean::Result->new( bindings => $b );
}, undef, variables => \@vars); #->debug('Project result');
} elsif ($algebra->isa('Attean::Algebra::Slice')) {
my $iter = $self->evaluate( $child, $active_graph );
$iter = $iter->offset($algebra->offset) if ($algebra->offset > 0);
$iter = $iter->limit($algebra->limit) if ($algebra->limit >= 0);
return $iter;
} elsif ($algebra->isa('Attean::Algebra::Union')) {
my ($lhs, $rhs) = map { $self->evaluate($_, $active_graph) } @children;
return Attean::IteratorSequence->new(
iterators => [$lhs, $rhs],
item_type => 'Attean::API::Result',
variables => [$algebra->in_scope_variables]
);
} elsif ($algebra->isa('Attean::Algebra::Ask')) {
my $iter = $self->evaluate($child, $active_graph);
my $result = $iter->next;
return Attean::ListIterator->new(values => [$result ? Attean::Literal->true : Attean::Literal->false], item_type => 'Attean::API::Term');
} elsif ($algebra->isa('Attean::Algebra::Construct')) {
my $iter = $self->evaluate($child, $active_graph);
my $patterns = $algebra->triples;
use Data::Dumper;
my %seen;
return Attean::CodeIterator->new(
generator => sub {
my $r = $iter->next;
return unless ($r);
my %mapping = map { my $t = $r->value($_); $t ? ("?$_" => $t) : (); } ($r->variables);
my $mapper = Attean::TermMap->rewrite_map(\%mapping);
my @triples;
PATTERN: foreach my $p (@$patterns) {
my @terms = map {
($_->does('Attean::API::TriplePattern'))
? $_->as_triple
: $_
} $p->apply_map($mapper)->values;
unless (all { $_->does('Attean::API::TermOrTriple') } @terms) {
next PATTERN;
}
push(@triples, Attean::Triple->new(@terms));
}
return @triples;
},
item_type => 'Attean::API::Triple'
)->grep(sub { return not($seen{shift->as_string}++); });
} elsif ($algebra->isa('Attean::Algebra::Table')) {
my $vars = [map { $_->value } @{ $algebra->variables }];
return Attean::ListIterator->new(variables => $vars, values => $algebra->rows, item_type => 'Attean::API::Result');
}
die "Unimplemented simple algebra evaluation for: $algebra";
}
=item C<< evaluate_pattern( $pattern, $active_graph, \@new_vars, \%blanks ) >>
Returns an L object with results produced by evaluating
the triple- or quad-pattern C<< $pattern >> against the evaluator's
C<< model >>, using the supplied C<< $active_graph >>.
If the C<< ground_blanks >> option is false, replaces blank nodes in the
pattern with fresh variables before evaluation, and populates C<< %blanks >>
with pairs ($variable_name => $variable_node). Each new variable is also
appended to C<< @new_vars >> as it is created.
=cut
sub evaluate_pattern {
my $self = shift;
my $t = shift;
my $active_graph = shift || Carp::confess "No active-graph passed to Attean::SimpleQueryEvaluator->evaluate";
my $new_vars = shift;
my $blanks = shift;
my $q = $t->as_quad_pattern($active_graph);
my @values;
foreach my $v ($q->values) {
if (not($self->ground_blanks) and $v->does('Attean::API::Blank')) {
unless (exists $blanks->{$v->value}) {
$blanks->{$v->value} = Attean::Variable->new();
push(@$new_vars, $blanks->{$v->value}->value);
}
push(@values, $blanks->{$v->value});
} else {
push(@values, $v);
}
}
return $self->model->get_bindings( @values );
}
sub _ALP {
my $self = shift;
my $graph = shift;
my $term = shift;
my $path = shift;
my $v = shift;
return if (exists $v->{ $term->as_string });
$v->{ $term->as_string } = $term;
my $iter = $self->_eval($graph, $term, $path);
while (my $n = $iter->next) {
$self->_ALP($graph, $n, $path, $v);
}
}
sub _eval {
my $self = shift;
my $graph = shift;
my $term = shift;
my $path = shift;
my $pp = Attean::Algebra::Path->new( subject => $term, path => $path, object => variable('o') );
my $iter = $self->evaluate($pp, $graph);
my $terms = $iter->map(sub { shift->value('o') }, 'Attean::API::Term');
my %seen;
return $terms->grep(sub { not $seen{ shift->as_string }++ });
}
sub _zeroLengthPath {
my $self = shift;
my $s = shift;
my $o = shift;
my $graph = shift;
my $s_term = ($s->does('Attean::API::TermOrTriple'));
my $o_term = ($o->does('Attean::API::TermOrTriple'));
if ($s_term and $o_term) {
my @r;
push(@r, Attean::Result->new()) if ($s->equals($o));
return Attean::ListIterator->new(variables => [], values => \@r, item_type => 'Attean::API::Result');
} elsif ($s_term) {
my $name = $o->value;
my $r = Attean::Result->new( bindings => { $name => $s } );
return Attean::ListIterator->new(variables => [$name], values => [$r], item_type => 'Attean::API::Result');
} elsif ($o_term) {
my $name = $s->value;
my $r = Attean::Result->new( bindings => { $name => $o } );
return Attean::ListIterator->new(variables => [$name], values => [$r], item_type => 'Attean::API::Result');
} else {
my @vars = map { $_->value } ($s, $o);
my $nodes = $self->model->graph_nodes( $graph );
return $nodes->map(
sub {
my $term = shift;
Attean::Result->new( bindings => { map { $_ => $term } @vars } );
},
'Attean::API::Result',
variables => \@vars
);
}
}
}
package Attean::SimpleQueryEvaluator::ExpressionEvaluator 0.034 {
use Moo;
use Attean::RDF;
use Scalar::Util qw(blessed);
use Types::Standard qw(InstanceOf);
use URI::Escape qw(uri_escape_utf8);
use Encode qw(encode);
use POSIX qw(ceil floor);
use Digest;
use UUID::Tiny ':std';
use List::MoreUtils qw(zip);
use DateTime::Format::W3CDTF;
use I18N::LangTags;
use namespace::clean;
has 'evaluator' => (is => 'ro', isa => InstanceOf['Attean::SimpleQueryEvaluator']);
sub evaluate_expression {
my $self = shift;
my $expr = shift;
my $row = shift;
my $active_graph = shift;
my $row_cache = shift || {};
my $impl = $self->impl($expr, $active_graph);
my $result = eval { $impl->($row, row_cache => $row_cache) };
return $result;
}
sub impl {
my $self = shift;
my $expr = shift;
my $active_graph = shift;
my $op = $expr->operator;
my $true = Attean::Literal->true;
my $false = Attean::Literal->false;
if ($expr->isa('Attean::ExistsExpression')) {
my $pattern = $expr->pattern;
return sub {
my $r = shift;
my $table = Attean::Algebra::Table->new( variables => [map { variable($_) } $r->variables], rows => [$r] );
my $join = Attean::Algebra::Join->new( children => [$table, $pattern] );
# TODO: substitute variables at top-level of EXISTS pattern
my $iter = $self->evaluator->evaluate($join, $active_graph);
return ($iter->next) ? $true : $false;
};
} elsif ($expr->isa('Attean::ValueExpression')) {
my $node = $expr->value;
if ($node->does('Attean::API::Variable')) {
return sub { return shift->value($node->value); };
} else {
return sub { return $node };
}
} elsif ($expr->isa('Attean::UnaryExpression')) {
my ($child) = @{ $expr->children };
my $impl = $self->impl($child, $active_graph);
if ($op eq '!') {
return sub {
my $term = $impl->(@_);
return ($term->ebv) ? $false : $true;
}
} elsif ($op eq '-' or $op eq '+') {
return sub {
my $term = $impl->(@_);
die "TypeError $op" unless (blessed($term) and $term->does('Attean::API::NumericLiteral'));
my $v = $term->numeric_value;
return Attean::Literal->new( value => eval "$op$v", datatype => $term->datatype );
};
}
die "Unimplemented UnaryExpression evaluation: " . $expr->operator;
} elsif ($expr->isa('Attean::BinaryExpression')) {
my ($lhs, $rhs) = @{ $expr->children };
my ($lhsi, $rhsi) = map { $self->impl($_, $active_graph) } ($lhs, $rhs);
if ($op eq '&&') {
return sub {
my ($r, %args) = @_;
my $lbv = eval { $lhsi->($r, %args) };
my $rbv = eval { $rhsi->($r, %args) };
die "TypeError $op" unless ($lbv or $rbv);
return $false if (not($lbv) and not($rbv->ebv));
return $false if (not($rbv) and not($lbv->ebv));
die "TypeError $op" unless ($lbv and $rbv);
return ($lbv->ebv && $rbv->ebv) ? $true : $false;
}
} elsif ($op eq '||') {
return sub {
my ($r, %args) = @_;
my $lbv = eval { $lhsi->($r, %args) };
return $true if ($lbv and $lbv->ebv);
my $rbv = eval { $rhsi->($r, %args) };
die "TypeError $op" unless ($rbv);
return $true if ($rbv->ebv);
return $false if ($lbv);
die "TypeError $op";
}
} elsif ($op =~ m#^(?:[-+*/])$#) { # numeric operators: - + * /
return sub {
my ($r, %args) = @_;
($lhs, $rhs) = map { $_->($r, %args) } ($lhsi, $rhsi);
for ($lhs, $rhs) { die "TypeError $op" unless (blessed($_) and $_->does('Attean::API::NumericLiteral')); }
my $lv = $lhs->numeric_value;
my $rv = $rhs->numeric_value;
return Attean::Literal->new( value => eval "$lv $op $rv", datatype => $lhs->binary_promotion_type($rhs, $op) );
};
} elsif ($op =~ /^!?=$/) {
return sub {
my ($r, %args) = @_;
($lhs, $rhs) = map { $_->($r, %args) } ($lhsi, $rhsi);
for ($lhs, $rhs) { die "TypeError $op" unless (blessed($_) and $_->does('Attean::API::TermOrTriple')); }
my $ok;
if ($lhs->does('Attean::API::Binding')) {
$ok = $lhs->equals($rhs);
} else {
$ok = $lhs->equals($rhs);
}
$ok = not($ok) if ($op eq '!=');
return $ok ? $true : $false;
}
} elsif ($op =~ /^[<>]=?$/) {
return sub {
my ($r, %args) = @_;
($lhs, $rhs) = map { $_->($r, %args) } ($lhsi, $rhsi);
for ($lhs, $rhs) {
die "TypeError $op" unless $_->does('Attean::API::TermOrTriple');
die "TypeError $op" if ($_->does('Attean::API::IRI')); # comparison of IRIs is only defined for `ORDER BY`, not for general expressions
}
my $c = ($lhs->compare($rhs));
return $true if (($c < 0 and ($op =~ /<=?/)) or ($c > 0 and ($op =~ />=?/)) or ($c == 0 and ($op =~ /=/)));
return $false;
}
}
die "Unexpected operator evaluation: $op";
} elsif ($expr->isa('Attean::FunctionExpression')) {
my $func = $expr->operator;
my @children = map { $self->impl($_, $active_graph) } @{ $expr->children };
my %type_roles = qw(URI IRI IRI IRI BLANK Blank LITERAL Literal NUMERIC NumericLiteral TRIPLE Triple);
my %type_classes = qw(URI Attean::IRI IRI Attean::IRI STR Attean::Literal);
return sub {
my ($r, %args) = @_;
my $row_cache = $args{row_cache} || {};
if ($func eq 'IF') {
my $term = $children[0]->( $r, %args );
my $ebv = $term->ebv;
return $ebv ? $children[1]->( $r, %args ) : $children[2]->( $r, %args );
} elsif ($func eq 'IN' or $func eq 'NOTIN') {
($true, $false) = ($false, $true) if ($func eq 'NOTIN');
my $child = shift(@children);
my $term = $child->( $r, %args );
foreach my $c (@children) {
if (my $value = eval { $c->( $r, %args ) }) {
return $true if ($term->equals($value));
}
}
return $false;
} elsif ($func eq 'COALESCE') {
foreach my $c (@children) {
my $t = eval { $c->( $r, %args ) };
next if ($@);
return $t if $t;
}
return;
}
if ($func eq 'INVOKE') {
my $furi = shift(@children)->( $r, %args )->value;
if (my $f = Attean->get_global_function($furi)) {
my @operands = map { $_->( $r, %args ) } @children;
return $f->($self->evaluator->model, $active_graph, @operands);
} elsif (my $fform = Attean->get_global_functional_form($furi)) {
my @operands = map { eval { $_->( $r, %args ) } || undef } @children;
return $fform->($self->evaluator->model, $active_graph, @operands);
} else {
die "No extension registered for <$furi>";
}
} else {
my @operands = map { $_->( $r, %args ) } @children;
if ($func =~ /^(STR)$/) {
return $type_classes{$1}->new($operands[0]->value);
} elsif ($func =~ /^(SUBJECT|PREDICATE|OBJECT)$/) {
my $pos = lc($func);
my $term = $operands[0]->$pos();
return $term;
} elsif ($func =~ /^([UI]RI)$/) {
my $operand = $operands[0];
if ($operand->does('Attean::API::Literal')) {
if ($operand->datatype->value ne 'http://www.w3.org/2001/XMLSchema#string') {
die "TypeError: ${func} called with a datatyped-literal other than xsd:string";
}
}
my @base = $expr->has_base ? (base => $expr->base) : ();
return $type_classes{$1}->new(value => $operands[0]->value, @base);
} elsif ($func eq 'BNODE') {
if (scalar(@operands)) {
my $name = $operands[0]->value;
if (my $b = $row_cache->{bnodes}{$name}) {
return $b;
} else {
my $b = Attean::Blank->new();
$row_cache->{bnodes}{$name} = $b;
return $b;
}
}
return Attean::Blank->new();
} elsif ($func eq 'LANG') {
die "TypeError: LANG" unless ($operands[0]->does('Attean::API::Literal'));
return Attean::Literal->new($operands[0]->language // '');
} elsif ($func eq 'LANGMATCHES') {
my ($lang, $match) = map { $_->value } @operands;
if ($match eq '*') {
# """A language-range of "*" matches any non-empty language-tag string."""
return ($lang ? $true : $false);
} else {
return (I18N::LangTags::is_dialect_of( $lang, $match )) ? $true : $false;
}
} elsif ($func eq 'DATATYPE') {
return $operands[0]->datatype;
} elsif ($func eq 'BOUND') {
return $operands[0] ? $true : $false;
} elsif ($func eq 'RAND') {
return Attean::Literal->new( value => rand(), datatype => 'http://www.w3.org/2001/XMLSchema#double' );
} elsif ($func eq 'ABS') {
return Attean::Literal->new( value => abs($operands[0]->value), $operands[0]->construct_args );
} elsif ($func =~ /^(?:CEIL|FLOOR)$/) {
my $v = $operands[0]->value;
return Attean::Literal->new( value => (($func eq 'CEIL') ? ceil($v) : floor($v)), $operands[0]->construct_args );
} elsif ($func eq 'ROUND') {
return Attean::Literal->new( value => sprintf('%.0f', (0.000000000000001 + $operands[0]->numeric_value)), $operands[0]->construct_args );
} elsif ($func eq 'CONCAT') {
my $all_lang = 1;
my $all_str = 1;
my $lang;
if (scalar(@operands) == 0) {
return Attean::Literal->new(value => '');
}
foreach my $n (@operands) {
die "CONCAT called with a non-literal argument" unless ($n->does('Attean::API::Literal'));
if ($n->datatype->value ne 'http://www.w3.org/2001/XMLSchema#string') {
die "CONCAT called with a datatyped-literal other than xsd:string";
} elsif ($n->language) {
$all_str = 0;
if (defined($lang) and $lang ne $n->language) {
$all_lang = 0;
} else {
$lang = $n->language;
}
} else {
$all_lang = 0;
$all_str = 0;
}
}
my %strtype;
if ($all_lang and $lang) {
$strtype{language} = $lang;
} elsif ($all_str) {
$strtype{datatype} = 'http://www.w3.org/2001/XMLSchema#string'
}
return Attean::Literal->new( value => join('', map { $_->value } @operands), %strtype );
} elsif ($func eq 'SUBSTR') {
my $str = shift(@operands);
my @args = map { $_->numeric_value } @operands;
my $v = scalar(@args == 1) ? substr($str->value, $args[0]-1) : substr($str->value, $args[0]-1, $args[1]);
return Attean::Literal->new( value => $v, $str->construct_args );
} elsif ($func eq 'STRLEN') {
return Attean::Literal->integer(length($operands[0]->value));
} elsif ($func eq 'REPLACE') {
my ($node, $pat, $rep) = @operands;
die "TypeError: REPLACE called without a literal arg1 term" unless (blessed($node) and $node->does('Attean::API::Literal'));
die "TypeError: REPLACE called without a literal arg2 term" unless (blessed($pat) and $pat->does('Attean::API::Literal'));
die "TypeError: REPLACE called without a literal arg3 term" unless (blessed($rep) and $rep->does('Attean::API::Literal'));
die "TypeError: REPLACE called with a datatyped (non-xsd:string) literal" if ($node->datatype and $node->datatype->value ne 'http://www.w3.org/2001/XMLSchema#string');
my ($value, $pattern, $replace) = map { $_->value } @operands;
die "EvaluationError: REPLACE called with unsafe ?{} match pattern" if (index($pattern, '(?{') != -1 or index($pattern, '(??{') != -1);
die "EvaluationError: REPLACE called with unsafe ?{} replace pattern" if (index($replace, '(?{') != -1 or index($replace, '(??{') != -1);
$replace =~ s/\\/\\\\/g;
$replace =~ s/\$(\d+)/\$$1/g;
$replace =~ s/"/\\"/g;
$replace = qq["$replace"];
no warnings 'uninitialized';
$value =~ s/$pattern/"$replace"/eeg;
return Attean::Literal->new(value => $value, $node->construct_args);
} elsif ($func =~ /^[UL]CASE$/) {
return Attean::Literal->new( value => ($func eq 'UCASE' ? uc($operands[0]->value) : lc($operands[0]->value) ), $operands[0]->construct_args );
} elsif ($func eq 'ENCODE_FOR_URI') {
return Attean::Literal->new( uri_escape_utf8($operands[0]->value) );
} elsif ($func eq 'CONTAINS') {
my ($node, $pat) = @operands;
my ($lit, $plit) = map { $_->value } @operands;
die "TypeError: CONTAINS" if ($node->language and $pat->language and $node->language ne $pat->language);
return (index($lit, $plit) >= 0) ? $true : $false;
} elsif ($func eq 'STRSTARTS' or $func eq 'STRENDS') {
my ($lit, $plit) = map { $_->value } @operands;
if ($func eq 'STRENDS') {
my $pos = length($lit) - length($plit);
return (rindex($lit, $plit) == $pos) ? $true : $false;
} else {
return (index($lit, $plit) == 0) ? $true : $false;
}
} elsif ($func eq 'STRBEFORE' or $func eq 'STRAFTER') {
my ($node, $substr) = @operands;
die "$func called without a literal arg1 term" unless (blessed($node) and $node->does('Attean::API::Literal'));
die "$func called without a literal arg2 term" unless (blessed($substr) and $substr->does('Attean::API::Literal'));
die "$func called with a datatyped (non-xsd:string) literal" if ($node->datatype and $node->datatype->value ne 'http://www.w3.org/2001/XMLSchema#string');
my $lhs_simple = (not($node->language) and ($node->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string'));
my $rhs_simple = (not($substr->language) and ($substr->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string'));
if ($lhs_simple and $rhs_simple) {
# ok
} elsif ($node->language and $substr->language and $node->language eq $substr->language) {
# ok
} elsif ($node->language and $rhs_simple) {
# ok
} else {
die "$func called with literals that are not argument compatible";
}
my $value = $node->value;
my $match = $substr->value;
my $i = index($value, $match, 0);
if ($i < 0) {
return Attean::Literal->new('');
} else {
if ($func eq 'STRBEFORE') {
return Attean::Literal->new(value => substr($value, 0, $i), $node->construct_args);
} else {
return Attean::Literal->new(value => substr($value, $i+length($match)), $node->construct_args);
}
}
} elsif ($func =~ /^(?:YEAR|MONTH|DAY|HOURS|MINUTES)$/) {
my $method = lc($func =~ s/^(HOUR|MINUTE)S$/$1/r);
my $dt = $operands[0]->datetime;
return Attean::Literal->integer($dt->$method());
} elsif ($func eq 'SECONDS') {
my $dt = $operands[0]->datetime;
return Attean::Literal->decimal($dt->second());
} elsif ($func eq 'TZ' or $func eq 'TIMEZONE') {
my $dt = $operands[0]->datetime;
my $tz = $dt->time_zone;
if ($tz->is_floating) {
return Attean::Literal->new('') if ($func eq 'TZ');
die "TIMEZONE called with a dateTime without a timezone";
}
return Attean::Literal->new('Z') if ($func eq 'TZ' and $tz->is_utc);
if ($tz) {
my $offset = $tz->offset_for_datetime( $dt );
my $hours = 0;
my $minutes = 0;
my $minus = ($func eq 'TZ') ? '+' : '';
if ($offset < 0) {
$minus = '-';
$offset = -$offset;
}
my $duration = "${minus}PT";
if ($offset >= 60*60) {
my $h = int($offset / (60*60));
$duration .= "${h}H" if ($h > 0);
$hours = int($offset / (60*60));
$offset = $offset % (60*60);
}
if ($offset >= 60) {
my $m = int($offset / 60);
$duration .= "${m}M" if ($m > 0);
$minutes = int($offset / 60);
$offset = $offset % 60;
}
my $seconds = int($offset);
my $s = int($offset);
$duration .= "${s}S" if ($s > 0 or $duration eq 'PT');
return ($func eq 'TZ')
? Attean::Literal->new(sprintf('%s%02d:%02d', $minus, $hours, $minutes))
: Attean::Literal->new( value => $duration, datatype => "http://www.w3.org/2001/XMLSchema#dayTimeDuration");
} else {
return Attean::Literal->new('') if ($func eq 'TZ');
die "TIMEZONE called without a valid dateTime";
}
} elsif ($func eq 'NOW') {
my $value = DateTime::Format::W3CDTF->new->format_datetime( DateTime->now );
return Attean::Literal->new( value => $value, datatype => 'http://www.w3.org/2001/XMLSchema#dateTime' );
} elsif ($func =~ /^(?:STR)?UUID$/) {
return Attean::Literal->new(uc(uuid_to_string(create_uuid()))) if ($func eq 'STRUUID');
return Attean::IRI->new('urn:uuid:' . uc(uuid_to_string(create_uuid())));
} elsif ($func =~ /^(MD5|SHA1|SHA256|SHA384|SHA512)$/) {
my $hash = $func =~ s/SHA/SHA-/r;
my $digest = eval { Digest->new($hash)->add(encode('UTF-8', $operands[0]->value, Encode::FB_CROAK))->hexdigest };
return Attean::Literal->new($digest);
} elsif ($func eq 'STRLANG') {
my ($str, $lang) = @operands;
my @values = map { $_->value } @operands;
die "TypeError: STRLANG must be called with two plain literals" unless (blessed($str) and $str->does('Attean::API::Literal') and blessed($lang) and $lang->does('Attean::API::Literal'));
die "TypeError: STRLANG not called with a simple literal" unless ($str->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string' and not($str->language));
return Attean::Literal->new( value => $values[0], language => $values[1] );
} elsif ($func eq 'STRDT') {
die "TypeError: STRDT" unless ($operands[0]->does('Attean::API::Literal') and not($operands[0]->language));
if (my $dt = $operands[0]->datatype) {
die "TypeError: STRDT" unless ($dt->value eq 'http://www.w3.org/2001/XMLSchema#string');
}
die "TypeError: STRDT" unless ($operands[1]->does('Attean::API::IRI'));
my @values = map { $_->value } @operands;
return Attean::Literal->new( value => $values[0], datatype => $values[1] );
} elsif ($func eq 'SAMETERM') {
my ($a, $b) = @operands;
die "TypeError: SAMETERM" unless (blessed($operands[0]) and blessed($operands[1]));
my $cmp = eval { $a->compare($b) };
if (not($@) and $cmp) {
return $false;
}
if ($a->does('Attean::API::Binding')) {
my $ok = ($a->sameTerms($b));
return $ok ? $true : $false;
} else {
my $ok = ($a->value eq $b->value);
return $ok ? $true : $false;
}
} elsif ($func =~ /^IS([UI]RI|BLANK|LITERAL|NUMERIC|TRIPLE)$/) {
return $operands[0]->does("Attean::API::$type_roles{$1}") ? $true : $false;
} elsif ($func eq 'REGEX') {
my ($value, $pattern) = map { $_->value } @operands;
return ($value =~ /$pattern/) ? $true : $false;
}
die "Unimplemented FunctionExpression evaluation: " . $expr->operator;
}
};
} elsif ($expr->isa('Attean::AggregateExpression')) {
my $agg = $expr->operator;
my ($child) = @{ $expr->children };
if ($agg eq 'COUNT') {
if ($child) {
my $impl = $self->impl($child, $active_graph);
return sub {
my ($rows, %args) = @_;
my @terms = grep { blessed($_) } map { $impl->($_, %args) } @{ $rows };
if ($expr->distinct) {
my %seen;
@terms = grep { not($seen{$_->as_string}++) } @terms;
}
return Attean::Literal->integer(scalar(@terms));
};
} else {
return sub {
my ($rows, %args) = @_;
return Attean::Literal->integer(scalar(@$rows));
};
}
} elsif ($agg =~ /^(?:SAMPLE|MIN|MAX|SUM|AVG|GROUP_CONCAT|FOLD)$/) {
my $impl = $self->impl($child, $active_graph);
if ($agg eq 'SAMPLE') {
return sub {
my ($rows, %args) = @_;
return $impl->( shift(@$rows), %args )
};
} elsif ($agg eq 'MIN' or $agg eq 'MAX') {
my $expect = ($agg eq 'MIN') ? 1 : -1;
return sub {
my ($rows, %args) = @_;
my $extrema;
foreach my $r (@$rows) {
my $t = $impl->( $r, %args );
return if (not($t) and $agg eq 'MIN'); # unbound is always minimal
next if (not($t)); # unbound need not be considered for MAX
$extrema = $t if (not($extrema) or $extrema->compare($t) == $expect);
}
return $extrema;
};
} elsif ($agg eq 'SUM' or $agg eq 'AVG') {
return sub {
my ($rows, %args) = @_;
my $count = 0;
my $sum = Attean::Literal->integer(0);
my %seen;
foreach my $r (@$rows) {
my $term = $impl->( $r, %args );
if ($expr->distinct) {
next if ($seen{ $term->as_string }++);
}
if ($term->does('Attean::API::NumericLiteral')) {
$count++;
$sum = Attean::Literal->new( value => ($sum->numeric_value + $term->numeric_value), datatype => $sum->binary_promotion_type($term, '+') );
} else {
die "TypeError: AVG";
}
}
if ($agg eq 'AVG') {
$sum = not($count) ? undef : Attean::Literal->new( value => ($sum->numeric_value / $count), datatype => $sum->binary_promotion_type(Attean::Literal->integer($count), '/') );
}
return $sum;
};
} elsif ($agg eq 'GROUP_CONCAT') {
my $sep = $expr->scalar_vars->{ 'seperator' } // ' ';
return sub {
my ($rows, %args) = @_;
my %seen;
my @strings;
foreach my $r (@$rows) {
my $term = eval { $impl->( $r, %args ) };
if ($expr->distinct) {
next if ($seen{ blessed($term) ? $term->as_string : '' }++);
}
push(@strings, $term->value // '');
}
return Attean::Literal->new(join($sep, sort @strings));
};
} elsif ($agg eq 'FOLD') {
return sub {
my ($rows, %args) = @_;
my @children = @{ $expr->children };
my @impls = map { $self->impl($_, $active_graph) } @children;
my $order = $expr->order || [];
if (scalar(@$order)) {
# sort $rows by the order condition
my @cmps = @$order;
my @exprs = map { $_->[1] } @cmps;
my @dirs = map { $_->[0] eq 'ASC' } @cmps;
my @sorted = map { $_->[0] } sort {
my ($ar, $avalues) = @$a;
my ($br, $bvalues) = @$b;
my $c = 0;
foreach my $i (0 .. $#cmps) {
my ($av, $bv) = map { $_->[$i] } ($avalues, $bvalues);
# Mirrors code in Attean::Plan::OrderBy->sort_rows
if (not(blessed($av))) {
$c = -1;
} elsif (not(blessed($av))) {
$c = 1;
} elsif (blessed($av) and $av->does('Attean::API::Binding') and (not(defined($bv)) or not($bv->does('Attean::API::Binding')))) {
$c = 1;
} elsif (blessed($bv) and $bv->does('Attean::API::Binding') and (not(defined($av)) or not($av->does('Attean::API::Binding')))) {
$c = -1;
} else {
$c = eval { $av ? $av->compare($bv) : 1 };
if ($@) {
$c = 1;
}
}
$c *= -1 if ($dirs[$i] == 0);
last unless ($c == 0);
}
$c
} map { my $r = $_; [$r, [map { $self->evaluate_expression( $_, $r, $active_graph, {} ) } @exprs]] } @$rows;
$rows = \@sorted;
}
if (scalar(@impls) > 1) {
my @values;
foreach my $r (@$rows) {
my ($key, $value) = map { eval { $_->( $r, %args ) } || undef } @impls;
if (defined($key)) {
push(@values, $key, $value);
}
}
my $func = Attean->get_global_functional_form($AtteanX::Functions::CompositeMaps::MAP_TYPE_IRI);
my $m = $func->(undef, undef, @values);
return $m;
} else {
my %seen;
my @values;
foreach my $r (@$rows) {
my $term = eval { $impl->( $r, %args ) };
if ($expr->distinct) {
next if ($seen{ blessed($term) ? $term->as_string : '' }++);
}
push(@values, $term);
}
my $func = Attean->get_global_functional_form($AtteanX::Functions::CompositeLists::LIST_TYPE_IRI);
my $l = $func->(undef, undef, @values);
return $l;
}
};
} else {
warn "Unexpected aggregate expression: $agg";
}
} elsif ($agg eq 'CUSTOM') {
my $iri = $expr->custom_iri;
my $data = Attean->get_global_aggregate($iri);
unless ($data) {
die "No extension aggregate registered for <$iri>";
}
my $start = $data->{'start'};
my $process = $data->{'process'};
my $finalize = $data->{'finalize'};
my $impl = $self->impl($child, $active_graph);
return sub {
my ($rows, %args) = @_;
my $thunk = $start->($self->evaluator->model, $active_graph);
foreach my $r (@$rows) {
my $t = $impl->( $r, %args );
$process->($thunk, $t);
}
return $finalize->($thunk);
};
}
die "Unimplemented AggregateExpression evaluation: " . $expr->operator;
} elsif ($expr->isa('Attean::CastExpression')) {
my ($child) = @{ $expr->children };
my $impl = $self->impl( $child, $active_graph );
my $type = $expr->datatype;
return sub {
my ($r, %args) = @_;
my $term = $impl->($r, %args);
# TODO: reformat syntax for xsd:double
my $cast = Attean::Literal->new( value => $term->value, datatype => $type );
return $cast->canonicalized_term_strict() if ($cast->does('Attean::API::CanonicalizingLiteral'));
return $cast;
}
} else {
Carp::confess "No impl for expression " . $expr->as_string;
}
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/Literal.pm 000644 000765 000024 00000000225 14636707547 020732 x ustar 00greg staff 000000 000000 30 mtime=1719373671.930458325
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/Literal.pm 000644 000765 000024 00000007611 14636707547 016767 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::Literal - RDF Literals
=head1 VERSION
This document describes Attean::Literal version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $langterm = Attean::Literal->new(value => 'foo', language => 'en-US');
$langterm->ntriples_string; # "foo"@en-US
my $typeterm = Attean::Literal->new(value => '123', datatype => 'http://www.w3.org/2001/XMLSchema#integer');
$langterm->ntriples_string; # "123"^^
=head1 DESCRIPTION
The Attean::Literal class represents RDF literals.
It conforms to the L role.
=head1 ATTRIBUTES
The following attributes exist:
=over 4
=item C<< value >>
=item C<< language >>
=item C<< datatype >>
=back
=head1 METHODS
=over 4
=item C<< has_language >>
Returns true if the literal has a language tag, false otherwise.
=cut
package Attean::Literal 0.034 {
use Moo;
use Types::Standard qw(Str Maybe InstanceOf);
use Attean::API::Term;
use IRI;
use Sub::Install;
use Sub::Util qw(set_subname);
use Scalar::Util qw(blessed);
use namespace::clean;
my $XSD_STRING = IRI->new(value => 'http://www.w3.org/2001/XMLSchema#string');
has 'value' => (is => 'ro', isa => Str, required => 1);
has 'language' => (is => 'ro', isa => Maybe[Str], predicate => 'has_language');
has 'datatype' => (
is => 'ro',
isa => InstanceOf['Attean::IRI'],
required => 1,
coerce => sub {
my $dt = shift;
if (blessed($dt) and $dt->isa('Attean::IRI')) {
return $dt;
} else {
return blessed($dt) ? Attean::IRI->new($dt->as_string) : Attean::IRI->new($dt)
}
},
default => sub { $XSD_STRING }
);
has 'ntriples_string' => (is => 'ro', isa => Str, lazy => 1, builder => '_ntriples_string');
with 'Attean::API::Literal';
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
return $class->$orig(@_) if (scalar(@_) == 1 and ref($_[0]) eq "HASH");
if (scalar(@_) == 1) {
my $dt = IRI->new('http://www.w3.org/2001/XMLSchema#string');
return $class->$orig(value => shift, datatype => $dt);
}
return $class->$orig(@_);
};
around 'datatype' => sub {
my $orig = shift;
my $self = shift;
if ($self->has_language) {
return Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString');
} else {
return $self->$orig(@_);
}
};
sub _ntriples_string {
my $self = shift;
my $value = $self->value;
$value =~ s/\\/\\\\/g;
$value =~ s/\n/\\n/g;
$value =~ s/\r/\\r/g;
$value =~ s/"/\\"/g;
if ($self->has_language) {
return sprintf('"%s"@%s', $value, $self->language);
} else {
my $dt = $self->datatype->as_string;
if ($dt eq 'http://www.w3.org/2001/XMLSchema#string') {
return sprintf('"%s"', $value);
} else {
return sprintf('"%s"^^<%s>', $value, $dt);
}
}
}
=item C<< true >>
The xsd:true term.
=cut
sub true {
state $v = Attean::Literal->new( value => 'true', datatype => 'http://www.w3.org/2001/XMLSchema#boolean' );
return $v;
}
=item C<< false >>
The xsd:false term.
=cut
sub false {
state $v = Attean::Literal->new( value => 'false', datatype => 'http://www.w3.org/2001/XMLSchema#boolean' );
return $v;
}
{
for my $method (qw(integer decimal float double)) {
my $code = sub {
my $class = shift;
return $class->new( value => shift, datatype => "http://www.w3.org/2001/XMLSchema#$method" );
};
Sub::Install::install_sub({
code => set_subname("${method}", $code),
as => "${method}"
});
}
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/Algebra.pm 000644 000765 000024 00000000225 14636707546 020672 x ustar 00greg staff 000000 000000 30 mtime=1719373670.993679425
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/Algebra.pm 000644 000765 000024 00000133271 14636707546 016731 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
use utf8;
=head1 NAME
Attean::Algebra - Representation of SPARQL algebra operators
=head1 VERSION
This document describes Attean::Algebra version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
=head1 DESCRIPTION
This is a utility package that defines all the Attean query algebra classes
in the Attean::Algebra namespace:
=over 4
=cut
use Attean::API::Query;
package Attean::Algebra::Query 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(Bool ArrayRef HashRef ConsumerOf);
use Moo;
use namespace::clean;
has 'dataset' => (is => 'ro', isa => HashRef[ArrayRef[ConsumerOf['Attean::API::Term']]], default => sub { +{} });
has 'subquery' => (is => 'ro', isa => Bool, default => 0);
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
sub algebra_as_string {
my $self = shift;
my $name = $self->subquery ? 'SubQuery' : 'Query';
my %dataset = %{ $self->dataset };
my @default = @{ $dataset{ default } || [] };
my @named = @{ $dataset{ named } || [] };
my $has_dataset = (scalar(@default) + scalar(@named));
my $s = $name;
if ($has_dataset) {
my @parts;
if (scalar(@default)) {
push(@parts, 'Default graph(s): ' . join(', ', map { chomp; $_ } map { $_->as_sparql } @default));
}
if (scalar(@named)) {
push(@parts, 'Named graph(s): ' . join(', ', map { chomp; $_ } map { $_->as_sparql } @named));
}
$s .= ' { ' . join('; ', @parts) . ' }';
}
return $s;
}
sub sparql_tokens {
my $self = shift;
my $child = $self->child;
my $l = AtteanX::SPARQL::Token->lbrace;
my $r = AtteanX::SPARQL::Token->rbrace;
my $from = AtteanX::SPARQL::Token->keyword('FROM');
my $named = AtteanX::SPARQL::Token->keyword('NAMED');
my %dataset = %{ $self->dataset };
my @default = @{ $dataset{ default } || [] };
my @named = @{ $dataset{ named } || [] };
my $has_dataset = (scalar(@default) + scalar(@named));
if ($child->does('Attean::API::SPARQLQuerySerializable')) {
if ($self->subquery) {
my @tokens;
push(@tokens, $l);
push(@tokens, $child->sparql_tokens->elements);
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
} else {
my %args;
if ($has_dataset) {
$args{dataset} = $self->dataset;
}
return $child->query_tokens(%args);
}
} else {
my $sel = AtteanX::SPARQL::Token->keyword('SELECT');
my $star = AtteanX::SPARQL::Token->star;
my $where = AtteanX::SPARQL::Token->keyword('WHERE');
my @tokens;
if ($self->subquery) {
push(@tokens, $l);
}
push(@tokens, $sel, $star);
if ($has_dataset) {
foreach my $i (sort { $a->as_string cmp $b->as_string } @default) {
push(@tokens, $from);
push(@tokens, $i->sparql_tokens->elements);
}
foreach my $i (sort { $a->as_string cmp $b->as_string } @named) {
push(@tokens, $from);
push(@tokens, $named);
push(@tokens, $i->sparql_tokens->elements);
}
}
push(@tokens, $where);
push(@tokens, $l);
push(@tokens, $child->sparql_tokens->elements);
push(@tokens, $r);
if ($self->subquery) {
push(@tokens, $r);
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
}
package Attean::Algebra::Update 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(Bool);
use Moo;
use namespace::clean;
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
sub algebra_as_string { return 'Update' }
sub sparql_tokens {
my $self = shift;
my $child = $self->child;
return $child->sparql_tokens;
}
}
=item * L
=cut
package Attean::Algebra::Sequence 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo;
use namespace::clean;
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::QueryTree';
sub arity {
my $self = shift;
return scalar(@{ $self->children });
}
sub algebra_as_string { return 'Sequence' }
sub sparql_tokens {
my $self = shift;
my $semi = AtteanX::SPARQL::Token->semicolon;
my @tokens;
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $semi);
}
pop(@tokens); # remove last SEMICOLON token
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Join 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo;
use namespace::clean;
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::QueryTree';
sub algebra_as_string { return 'Join' }
sub sparql_tokens {
my $self = shift;
my $l = AtteanX::SPARQL::Token->lbrace;
my $r = AtteanX::SPARQL::Token->rbrace;
my @tokens;
push(@tokens, $l);
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_subtokens->elements);
}
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::LeftJoin 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::BinaryQueryTree';
has 'expression' => (is => 'ro', isa => ConsumerOf['Attean::API::Expression'], required => 1, default => sub { Attean::ValueExpression->new( value => Attean::Literal->true ) });
sub algebra_as_string {
my $self = shift;
return sprintf('LeftJoin { %s }', $self->expression->as_string);
}
sub tree_attributes { return qw(expression) };
sub sparql_tokens {
my $self = shift;
my $opt = AtteanX::SPARQL::Token->keyword('OPTIONAL');
my $l = AtteanX::SPARQL::Token->lbrace;
my $r = AtteanX::SPARQL::Token->rbrace;
my ($lhs, $rhs) = @{ $self->children };
my @tokens;
push(@tokens, $l);
push(@tokens, $lhs->sparql_subtokens->elements);
push(@tokens, $r, $opt, $l);
push(@tokens, $rhs->sparql_subtokens->elements);
my $expr = $self->expression;
my $is_true = 0;
if ($expr->isa('Attean::ValueExpression')) {
my $value = $expr->value;
if ($value->equals(Attean::Literal->true)) {
$is_true = 1;
}
}
unless ($is_true) {
my $f = AtteanX::SPARQL::Token->keyword('FILTER');
my $lparen = AtteanX::SPARQL::Token->lparen;
my $rparen = AtteanX::SPARQL::Token->rparen;
push(@tokens, $f);
push(@tokens, $lparen);
push(@tokens, $expr->sparql_tokens->elements);
push(@tokens, $rparen);
}
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Filter 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
has 'expression' => (is => 'ro', isa => ConsumerOf['Attean::API::Expression'], required => 1);
sub algebra_as_string {
my $self = shift;
return sprintf('Filter { %s }', $self->expression->as_string);
}
sub tree_attributes { return qw(expression) };
sub sparql_tokens {
my $self = shift;
my $f = AtteanX::SPARQL::Token->keyword('FILTER');
my $l = AtteanX::SPARQL::Token->lparen;
my $r = AtteanX::SPARQL::Token->rparen;
my ($child) = @{ $self->children };
my $expr = $self->expression;
my @tokens;
push(@tokens, $child->sparql_tokens->elements);
push(@tokens, $f);
push(@tokens, $l);
push(@tokens, $expr->sparql_tokens->elements);
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Union 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::BinaryQueryTree';
sub algebra_as_string { return 'Union' }
sub sparql_tokens {
my $self = shift;
my $union = AtteanX::SPARQL::Token->keyword('UNION');
my $l = AtteanX::SPARQL::Token->lbrace;
my $r = AtteanX::SPARQL::Token->rbrace;
my ($lhs, $rhs) = @{ $self->children };
my @tokens;
push(@tokens, $l);
push(@tokens, $lhs->sparql_subtokens->elements);
push(@tokens, $r, $union, $l);
push(@tokens, $rhs->sparql_subtokens->elements);
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Graph 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
has 'graph' => (is => 'ro', isa => ConsumerOf['Attean::API::TermOrVariable'], required => 1);
sub in_scope_variables {
my $self = shift;
my $graph = $self->graph;
my ($child) = @{ $self->children };
my @vars = $child->in_scope_variables;
if ($graph->does('Attean::API::Variable')) {
return Set::Scalar->new(@vars, $graph->value)->elements;
} else {
return @vars;
}
}
sub algebra_as_string {
my $self = shift;
return sprintf('Graph %s', $self->graph->as_string);
}
sub tree_attributes { return qw(graph) };
sub sparql_tokens {
my $self = shift;
my $graph = AtteanX::SPARQL::Token->keyword('GRAPH');
my $l = AtteanX::SPARQL::Token->lbrace;
my $r = AtteanX::SPARQL::Token->rbrace;
my ($child) = @{ $self->children };
my @tokens;
push(@tokens, $graph);
push(@tokens, $self->graph->sparql_tokens->elements);
push(@tokens, $l);
push(@tokens, $child->sparql_subtokens->elements);
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Extend 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
sub in_scope_variables {
my $self = shift;
my ($child) = @{ $self->children };
my @vars = $child->in_scope_variables;
return Set::Scalar->new(@vars, $self->variable->value)->elements;
}
with 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
has 'variable' => (is => 'ro', isa => ConsumerOf['Attean::API::Variable'], required => 1);
has 'expression' => (is => 'ro', isa => ConsumerOf['Attean::API::Expression'], required => 1);
sub algebra_as_string {
my $self = shift;
return sprintf('Extend { %s ↠%s }', $self->variable->as_string, $self->expression->as_string);
}
sub tree_attributes { return qw(variable expression) };
sub sparql_tokens {
my $self = shift;
my $bind = AtteanX::SPARQL::Token->keyword('BIND');
my $as = AtteanX::SPARQL::Token->keyword('AS');
my $l = AtteanX::SPARQL::Token->lparen;
my $r = AtteanX::SPARQL::Token->rparen;
my ($child) = @{ $self->children };
my $var = $self->variable;
my $expr = $self->expression;
my @tokens;
push(@tokens, $child->sparql_tokens->elements);
push(@tokens, $bind);
push(@tokens, $l);
push(@tokens, $expr->sparql_tokens->elements);
push(@tokens, $as);
push(@tokens, $var->sparql_tokens->elements);
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Unfold 0.031 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
sub in_scope_variables {
my $self = shift;
my ($child) = @{ $self->children };
my @vars = $child->in_scope_variables;
return Set::Scalar->new(@vars, map { $_->value } @{ $self->variables })->elements;
}
with 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
has 'variables' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Variable']], required => 1);
has 'expression' => (is => 'ro', isa => ConsumerOf['Attean::API::Expression'], required => 1);
sub algebra_as_string {
my $self = shift;
my @vars = map { $_->as_string } @{ $self->variables };
my $vars = '(' . join(', ', @vars) . ')';
return sprintf('Unfold { %s ↠%s }', $vars, $self->expression->as_string);
}
sub tree_attributes { return qw(variables expression) };
sub sparql_tokens {
my $self = shift;
my $explode = AtteanX::SPARQL::Token->keyword('UNFOLD');
my $as = AtteanX::SPARQL::Token->keyword('AS');
my $l = AtteanX::SPARQL::Token->lparen;
my $r = AtteanX::SPARQL::Token->rparen;
my ($child) = @{ $self->children };
my @vars = @{ $self->variables };
my $expr = $self->expression;
my @tokens;
push(@tokens, $child->sparql_tokens->elements);
push(@tokens, $explode);
push(@tokens, $l);
push(@tokens, $expr->sparql_tokens->elements);
push(@tokens, $as);
foreach my $i (0 .. $#vars) {
my $var = $vars[$i];
if ($i > 0) {
push(@tokens, AtteanX::SPARQL::Token->comma);
}
push(@tokens, $var->sparql_tokens->elements);
}
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Minus 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::BinaryQueryTree';
sub in_scope_variables {
my $self = shift;
my ($child) = @{ $self->children };
return $child->in_scope_variables;
}
sub algebra_as_string { return 'Minus' }
sub sparql_tokens {
my $self = shift;
my $minus = AtteanX::SPARQL::Token->keyword('MINUS');
my $l = AtteanX::SPARQL::Token->lbrace;
my $r = AtteanX::SPARQL::Token->rbrace;
my ($lhs, $rhs) = @{ $self->children };
my @tokens;
push(@tokens, $l);
push(@tokens, $lhs->sparql_subtokens->elements);
push(@tokens, $r, $minus, $l);
push(@tokens, $rhs->sparql_subtokens->elements);
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Distinct 0.034 {
use Moo;
use namespace::clean;
with 'Attean::API::SPARQLQuerySerializable';
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
sub algebra_as_string { return 'Distinct' }
}
=item * L
=cut
package Attean::Algebra::Reduced 0.034 {
use Moo;
use namespace::clean;
with 'Attean::API::SPARQLQuerySerializable';
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
sub algebra_as_string { return 'Reduced' }
}
=item * L
=cut
package Attean::Algebra::Slice 0.034 {
use Moo;
use Types::Standard qw(Int);
use namespace::clean;
with 'Attean::API::SPARQLQuerySerializable';
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
has 'limit' => (is => 'ro', isa => Int, default => -1);
has 'offset' => (is => 'ro', isa => Int, default => 0);
sub algebra_as_string {
my $self = shift;
my @str = ('Slice');
push(@str, "Limit=" . $self->limit) if ($self->limit >= 0);
push(@str, "Offset=" . $self->offset) if ($self->offset > 0);
return join(' ', @str);
}
}
=item * L
=cut
package Attean::Algebra::Project 0.034 {
use Types::Standard qw(ArrayRef ConsumerOf);
use Moo;
use namespace::clean;
with 'Attean::API::SPARQLQuerySerializable';
with 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
has 'variables' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Variable']], required => 1);
sub in_scope_variables {
my $self = shift;
my ($child) = @{ $self->children };
my $set = Set::Scalar->new( $child->in_scope_variables );
my $proj = Set::Scalar->new( map { $_->value } @{ $self->variables } );
return $set->intersection($proj)->elements;
}
sub algebra_as_string {
my $self = shift;
return sprintf('Project { %s }', join(' ', map { '?' . $_->value } @{ $self->variables }));
}
sub tree_attributes { return qw(variables) };
}
=item * L
=cut
package Attean::Algebra::Comparator 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(Bool ConsumerOf);
use namespace::clean;
has 'ascending' => (is => 'ro', isa => Bool, default => 1);
has 'expression' => (is => 'ro', isa => ConsumerOf['Attean::API::Expression'], required => 1);
sub tree_attributes { return qw(expression) };
sub as_string {
my $self = shift;
if ($self->ascending) {
return 'ASC(' . $self->expression->as_string . ')';
} else {
return 'DESC(' . $self->expression->as_string . ')';
}
}
sub sparql_tokens {
my $self = shift;
my $asc = AtteanX::SPARQL::Token->keyword('ASC');
my $desc = AtteanX::SPARQL::Token->keyword('DESC');
my $l = AtteanX::SPARQL::Token->lparen;
my $r = AtteanX::SPARQL::Token->rparen;
my @tokens;
if ($self->ascending) {
push(@tokens, $self->expression->sparql_tokens->elements);
} else {
push(@tokens, $desc, $l);
push(@tokens, $self->expression->sparql_tokens->elements);
push(@tokens, $r);
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::OrderBy 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ArrayRef InstanceOf);
use namespace::clean;
with 'Attean::API::SPARQLQuerySerializable';
with 'Attean::API::UnionScopeVariables', 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
has 'comparators' => (is => 'ro', isa => ArrayRef[InstanceOf['Attean::Algebra::Comparator']], required => 1);
sub tree_attributes { return qw(comparators) };
sub algebra_as_string {
my $self = shift;
return sprintf('Order { %s }', join(', ', map { $_->as_string } @{ $self->comparators }));
}
}
=item * L
=cut
package Attean::Algebra::BGP 0.034 {
use Moo;
use Attean::RDF;
use Set::Scalar;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::NullaryQueryTree', 'Attean::API::CanonicalizingBindingSet';
has 'triples' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::TriplePattern']], default => sub { [] });
sub in_scope_variables {
my $self = shift;
my $set = Set::Scalar->new();
foreach my $t (@{ $self->triples }) {
my @vars = $t->referenced_variables();
$set->insert(@vars);
}
return $set->elements;
}
sub sparql_tokens {
my $self = shift;
my @tokens;
my $dot = AtteanX::SPARQL::Token->dot;
foreach my $t (@{ $self->triples }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $dot);
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
sub algebra_as_string {
my $self = shift;
return 'BGP { ' . join(', ', map { $_->as_string } @{ $self->triples }) . ' }';
}
sub elements {
my $self = shift;
return @{ $self->triples };
}
sub canonicalize {
my $self = shift;
my ($algebra, $mapping) = $self->canonical_bgp_with_mapping();
my @proj = sort map { sprintf("(?v%03d AS $_)", $mapping->{$_}{id}) } grep { $mapping->{$_}{type} eq 'variable' } (keys %$mapping);
foreach my $var (keys %$mapping) {
$algebra = Attean::Algebra::Extend->new(
children => [$algebra],
variable => variable($var),
expression => Attean::ValueExpression->new( value => variable($mapping->{$var}{id}) ),
);
}
}
sub canonical_bgp_with_mapping {
my $self = shift;
my ($triples, $mapping) = $self->canonical_set_with_mapping();
my $algebra = Attean::Algebra::BGP->new( triples => $triples );
return ($algebra, $mapping);
}
sub tree_attributes { return qw(triples) };
}
=item * L
=cut
package Attean::Algebra::Service 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo;
use Types::Standard qw(ConsumerOf Bool);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree', 'Attean::API::UnionScopeVariables';
has 'endpoint' => (is => 'ro', isa => ConsumerOf['Attean::API::TermOrVariable'], required => 1);
has 'silent' => (is => 'ro', isa => Bool, default => 0);
sub algebra_as_string {
my $self = shift;
my $endpoint = $self->endpoint->as_sparql;
chomp($endpoint);
return sprintf('Service %s', $endpoint);
}
sub tree_attributes { return qw(endpoint) };
sub sparql_tokens {
my $self = shift;
my $service = AtteanX::SPARQL::Token->keyword('SERVICE');
my $l = AtteanX::SPARQL::Token->lbrace;
my $r = AtteanX::SPARQL::Token->rbrace;
my ($child) = @{ $self->children };
my @tokens;
push(@tokens, $service);
if ($self->silent) {
push(@tokens, AtteanX::SPARQL::Token->keyword('SILENT'));
}
push(@tokens, $self->endpoint->sparql_tokens->elements);
push(@tokens, $l);
push(@tokens, $child->sparql_subtokens->elements);
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Path 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::NullaryQueryTree';
has 'subject' => (is => 'ro', isa => ConsumerOf['Attean::API::TermOrVariableOrTriplePattern'], required => 1);
has 'path' => (is => 'ro', isa => ConsumerOf['Attean::API::PropertyPath'], required => 1);
has 'object' => (is => 'ro', isa => ConsumerOf['Attean::API::TermOrVariableOrTriplePattern'], required => 1);
sub in_scope_variables {
my $self = shift;
my @vars = map { $_->value } grep { $_->does('Attean::API::Variable') } ($self->subject, $self->object);
return Set::Scalar->new(@vars)->elements;
}
sub tree_attributes { return qw(subject path object) };
sub algebra_as_string {
my $self = shift;
return 'Path { ' . join(', ', map { $_->as_string } map { $self->$_() } qw(subject path object)) . ' }';
}
sub sparql_tokens {
my $self = shift;
my @tokens;
foreach my $t ($self->subject, $self->path, $self->object) {
push(@tokens, $t->sparql_tokens->elements);
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Group 0.034 {
use utf8;
use Moo;
use Attean::API::Query;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::SPARQLQuerySerializable';
with 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
has 'groupby' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Expression']]);
has 'aggregates' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::AggregateExpression']]);
sub BUILD {
my $self = shift;
foreach my $a (@{ $self->aggregates }) {
my $op = $a->operator;
if ($op eq 'RANK') {
if (scalar(@{ $self->aggregates }) > 1) {
die "Cannot use both aggregates and RANKing in grouping operator";
}
}
}
}
sub in_scope_variables {
my $self = shift;
my $aggs = $self->aggregates // [];
my $groups = $self->groupby // [];
my %vars;
foreach my $a (@$aggs) {
$vars{ $a->variable->value }++;
}
foreach my $e (@$groups) {
if ($e->isa('Attean::ValueExpression')) {
my $value = $e->value;
if ($value->does('Attean::API::Variable')) {
$vars{ $value->value }++;
}
}
}
return keys %vars;
}
sub algebra_as_string {
my $self = shift;
my @aggs;
my $aggs = $self->aggregates // [];
my $groups = $self->groupby // [];
foreach my $a (@$aggs) {
my $v = $a->variable->as_string;
my $op = $a->operator;
my $d = $a->distinct ? "DISTINCT " : '';
my ($e) = ((map { $_->as_string } @{ $a->children }), '');
my $s = "$v ↠${op}($d$e)";
push(@aggs, $s);
}
return sprintf('Group { %s } aggregate { %s }', join(', ', map { $_->as_string() } @$groups), join(', ', @aggs));
}
sub tree_attributes { return qw(groupby aggregates) };
}
=item * L
=cut
package Attean::Algebra::NegatedPropertySet 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::PropertyPath';
has 'predicates' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::IRI']], required => 0, default => sub { [] });
has 'reversed' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::IRI']], required => 0, default => sub { [] });
sub as_string {
my $self = shift;
my @forward = map { $_->ntriples_string } @{ $self->predicates };
my @rev = map { '^' . $_->ntriples_string } @{ $self->reversed };
return sprintf("!(%s)", join('|', @forward, @rev));
}
sub algebra_as_string { return 'NPS' }
sub tree_attributes { return qw(predicates reversed) };
sub as_sparql {
my $self = shift;
my @forward = map { $_->as_sparql } @{ $self->predicates };
my @rev = map { '^' . $_->as_sparql } @{ $self->reversed };
return sprintf("!(%s)", join('|', @forward, @rev));
}
sub sparql_tokens {
my $self = shift;
my $bang = AtteanX::SPARQL::Token->op_bang;
my $or = AtteanX::SPARQL::Token->path_or;
my $hat = AtteanX::SPARQL::Token->path_hat;
my $l = AtteanX::SPARQL::Token->lparen;
my $r = AtteanX::SPARQL::Token->rparen;
my @tokens;
push(@tokens, $bang, $l);
foreach my $t (@{ $self->predicates }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $or);
}
foreach my $t (@{ $self->reversed }) {
push(@tokens, $hat);
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $or);
}
pop(@tokens); # remove last OR token
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::PredicatePath 0.034 {
use Moo;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::PropertyPath';
has 'predicate' => (is => 'ro', isa => ConsumerOf['Attean::API::IRI'], required => 1);
sub as_string {
my $self = shift;
return $self->predicate->ntriples_string;
}
sub algebra_as_string {
my $self = shift;
return 'Property Path ' . $self->as_string;
}
sub tree_attributes { return qw(predicate) };
sub as_sparql {
my $self = shift;
return $self->predicate->as_sparql;
}
sub sparql_tokens {
my $self = shift;
return $self->predicate->sparql_tokens;
}
}
=item * L
=cut
package Attean::Algebra::InversePath 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::UnaryPropertyPath';
sub prefix_name { return "^" }
sub as_sparql {
my $self = shift;
my ($path) = @{ $self->children };
return '^' . $self->path->as_sparql;
}
sub sparql_tokens {
my $self = shift;
my $hat = AtteanX::SPARQL::Token->path_hat;
my $l = AtteanX::SPARQL::Token->lparen;
my $r = AtteanX::SPARQL::Token->rparen;
my @tokens;
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_tokens->elements);
}
if (scalar(@tokens) > 1) {
unshift(@tokens, $hat, $l);
push(@tokens, $r);
} else {
unshift(@tokens, $hat);
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::SequencePath 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use namespace::clean;
with 'Attean::API::NaryPropertyPath';
sub separator { return "/" }
sub as_sparql {
my $self = shift;
my @paths = @{ $self->children };
return '(' . join('/', map { $_->as_sparql } @paths) . ')';
}
sub sparql_tokens {
my $self = shift;
my $slash = AtteanX::SPARQL::Token->slash;
my @tokens;
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $slash);
}
pop(@tokens); # remove last SLASH token
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::AlternativePath 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use namespace::clean;
with 'Attean::API::NaryPropertyPath';
sub separator { return "|" }
sub as_sparql {
my $self = shift;
my @paths = @{ $self->children };
return '(' . join('|', map { $_->as_sparql } @paths) . ')';
}
sub sparql_tokens {
my $self = shift;
my $or = AtteanX::SPARQL::Token->path_or;
my @tokens;
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $or);
}
pop(@tokens); # remove last OR token
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::ZeroOrMorePath 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::UnaryPropertyPath';
sub postfix_name { return "*" }
sub as_sparql {
my $self = shift;
my ($path) = @{ $self->children };
return $self->path->as_sparql . '*';
}
sub sparql_tokens {
my $self = shift;
my $star = AtteanX::SPARQL::Token->star;
my $l = AtteanX::SPARQL::Token->lparen;
my $r = AtteanX::SPARQL::Token->rparen;
my @tokens;
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_tokens->elements);
}
if (scalar(@tokens) > 1) {
unshift(@tokens, $l);
push(@tokens, $r);
}
push(@tokens, $star);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::OneOrMorePath 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::UnaryPropertyPath';
sub postfix_name { return "+" }
sub as_sparql {
my $self = shift;
my ($path) = @{ $self->children };
return $self->path->as_sparql . '+';
}
sub sparql_tokens {
my $self = shift;
my $plus = AtteanX::SPARQL::Token->op_plus;
my $l = AtteanX::SPARQL::Token->lparen;
my $r = AtteanX::SPARQL::Token->rparen;
my @tokens;
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_tokens->elements);
}
if (scalar(@tokens) > 1) {
unshift(@tokens, $l);
push(@tokens, $r);
}
push(@tokens, $plus);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::ZeroOrOnePath 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::UnaryPropertyPath';
sub postfix_name { return "?" }
sub as_sparql {
my $self = shift;
my ($path) = @{ $self->children };
return $self->path->as_sparql . '?';
}
sub sparql_tokens {
my $self = shift;
my $q = AtteanX::SPARQL::Token->question;
my $l = AtteanX::SPARQL::Token->lparen;
my $r = AtteanX::SPARQL::Token->rparen;
my @tokens;
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_tokens->elements);
}
if (scalar(@tokens) > 1) {
unshift(@tokens, $l);
push(@tokens, $r);
}
push(@tokens, $q);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Table 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::NullaryQueryTree';
has variables => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Variable']]);
has rows => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Result']]);
sub in_scope_variables {
my $self = shift;
return map { $_->value } @{ $self->variables };
}
sub tree_attributes { return qw(variables rows) };
sub algebra_as_string { return 'Table' }
sub sparql_tokens {
my $self = shift;
my $values = AtteanX::SPARQL::Token->keyword('VALUES');
my $lparen = AtteanX::SPARQL::Token->lparen;
my $rparen = AtteanX::SPARQL::Token->rparen;
my $lbrace = AtteanX::SPARQL::Token->lbrace;
my $rbrace = AtteanX::SPARQL::Token->rbrace;
my @tokens;
push(@tokens, $values);
push(@tokens, $lparen);
foreach my $var (@{ $self->variables }) {
push(@tokens, $var->sparql_tokens->elements);
}
push(@tokens, $rparen);
push(@tokens, $lbrace);
foreach my $row (@{ $self->rows }) {
push(@tokens, $lparen);
foreach my $val ($row->values) {
# TODO: verify correct serialization of UNDEF
push(@tokens, $val->sparql_tokens->elements);
}
push(@tokens, $rparen);
}
push(@tokens, $rbrace);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Ask 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use namespace::clean;
with 'Attean::API::SPARQLQuerySerializable';
with 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
sub in_scope_variables { return; }
sub algebra_as_string { return 'Ask' }
}
=item * L
=cut
package Attean::Algebra::Construct 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::SPARQLQuerySerializable';
with 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
has 'triples' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::TriplePattern']]);
sub in_scope_variables { return qw(subject predicate object); }
sub tree_attributes { return; }
sub algebra_as_string {
my $self = shift;
my $triples = $self->triples;
return sprintf('Construct { %s }', join(' . ', map { $_->as_string } @$triples));
}
}
=item * L
=cut
package Attean::Algebra::Describe 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::SPARQLQuerySerializable';
with 'Attean::API::Algebra', 'Attean::API::UnaryQueryTree';
has 'terms' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::TermOrVariable']]);
sub in_scope_variables { return qw(subject predicate object); }
sub tree_attributes { return; }
sub algebra_as_string { return 'Describe' }
}
=item * L
=cut
package Attean::Algebra::Load 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(Bool ConsumerOf);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::NullaryQueryTree';
has 'silent' => (is => 'ro', isa => Bool, default => 0);
has 'url' => (is => 'ro', isa => ConsumerOf['Attean::API::IRI'], required => 1);
has 'graph' => (is => 'ro', isa => ConsumerOf['Attean::API::Term'], predicate => 'has_graph');
sub in_scope_variables { return; }
sub tree_attributes { return; }
sub algebra_as_string {
my $self = shift;
return 'Load ' . $self->url->as_string;
}
sub sparql_tokens {
my $self = shift;
my @tokens;
push(@tokens, AtteanX::SPARQL::Token->keyword('LOAD'));
if ($self->silent) {
push(@tokens, AtteanX::SPARQL::Token->keyword('SILENT'));
}
push(@tokens, $self->url->sparql_tokens->elements);
if ($self->has_graph) {
push(@tokens, AtteanX::SPARQL::Token->keyword('INTO'));
push(@tokens, AtteanX::SPARQL::Token->keyword('GRAPH'));
push(@tokens, $self->graph->sparql_tokens->elements);
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Clear 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(Enum Bool ConsumerOf);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::NullaryQueryTree';
has 'drop' => (is => 'ro', isa => Bool, default => 0);
has 'silent' => (is => 'ro', isa => Bool, default => 0);
has 'target' => (is => 'ro', isa => Enum[qw(GRAPH DEFAULT NAMED ALL)], required => 1);
has 'graph' => (is => 'ro', isa => ConsumerOf['Attean::API::Term']);
sub BUILD {
my $self = shift;
if ($self->target eq 'GRAPH') {
unless (blessed($self->graph)) {
die "Attean::Algebra::Clear operations with a GRAPH target must include a graph IRI";
}
}
}
sub in_scope_variables { return; }
sub tree_attributes { return; }
sub algebra_as_string {
my $self = shift;
return $self->drop ? 'Drop' : 'Clear';
}
sub sparql_tokens {
my $self = shift;
my @tokens;
push(@tokens, AtteanX::SPARQL::Token->keyword($self->drop ? 'DROP' : 'CLEAR'));
if ($self->silent) {
push(@tokens, AtteanX::SPARQL::Token->keyword('SILENT'));
}
if ($self->target =~ /^(DEFAULT|NAMED|ALL)$/) {
push(@tokens, AtteanX::SPARQL::Token->keyword($self->target));
} else {
push(@tokens, AtteanX::SPARQL::Token->keyword('GRAPH'));
push(@tokens, $self->graph->sparql_tokens->elements);
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Create 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(Bool ConsumerOf);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::NullaryQueryTree';
has 'silent' => (is => 'ro', isa => Bool, default => 0);
has 'graph' => (is => 'ro', isa => ConsumerOf['Attean::API::Term'], required => 1);
sub in_scope_variables { return; }
sub tree_attributes { return; }
sub algebra_as_string { return 'Create' }
sub sparql_tokens {
my $self = shift;
my @tokens;
push(@tokens, AtteanX::SPARQL::Token->keyword('CREATE'));
if ($self->silent) {
push(@tokens, AtteanX::SPARQL::Token->keyword('SILENT'));
}
push(@tokens, AtteanX::SPARQL::Token->keyword('GRAPH'));
push(@tokens, $self->graph->sparql_tokens->elements);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Add 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(Enum Bool ConsumerOf);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::NullaryQueryTree';
has 'silent' => (is => 'ro', isa => Bool, default => 0);
has 'drop_source' => (is => 'ro', isa => Bool, default => 0);
has 'drop_destination' => (is => 'ro', isa => Bool, default => 0);
has 'source' => (is => 'ro', isa => ConsumerOf['Attean::API::Term'], predicate => 'has_source');
has 'destination' => (is => 'ro', isa => ConsumerOf['Attean::API::Term'], predicate => 'has_destination');
sub in_scope_variables { return; }
sub tree_attributes { return; }
sub algebra_as_string {
my $self = shift;
return ($self->drop_source and $self->drop_destination) ? 'Move' : ($self->drop_destination) ? 'Copy' : 'Add';
}
sub sparql_tokens {
my $self = shift;
my @tokens;
my $op = ($self->drop_source and $self->drop_destination) ? 'MOVE' : ($self->drop_destination) ? 'COPY' : 'ADD';
push(@tokens, AtteanX::SPARQL::Token->keyword($op));
if ($self->silent) {
push(@tokens, AtteanX::SPARQL::Token->keyword('SILENT'));
}
if ($self->has_source) {
push(@tokens, AtteanX::SPARQL::Token->keyword('GRAPH'));
push(@tokens, $self->source->sparql_tokens->elements);
} else {
push(@tokens, AtteanX::SPARQL::Token->keyword('DEFAULT'));
}
push(@tokens, AtteanX::SPARQL::Token->keyword('TO'));
if ($self->has_destination) {
push(@tokens, AtteanX::SPARQL::Token->keyword('GRAPH'));
push(@tokens, $self->destination->sparql_tokens->elements);
} else {
push(@tokens, AtteanX::SPARQL::Token->keyword('DEFAULT'));
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
=item * L
=cut
package Attean::Algebra::Modify 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use List::MoreUtils qw(all any);
use Types::Standard qw(HashRef ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::Algebra', 'Attean::API::QueryTree';
has 'dataset' => (is => 'ro', isa => HashRef, default => sub { +{} });
has 'insert' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::TripleOrQuadPattern']], default => sub { [] });
has 'delete' => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::TripleOrQuadPattern']], default => sub { [] });
sub in_scope_variables { return; }
sub tree_attributes { return; }
sub _op_type {
my $self = shift;
my $i = scalar(@{ $self->insert });
my $d = scalar(@{ $self->delete });
my $w = scalar(@{ $self->children });
my $ig = all { $_->is_ground } @{ $self->insert };
my $dg = all { $_->is_ground } @{ $self->delete };
if ($i and not $d) {
# INSERT
return ($ig and not $w) ? 'ID' : 'I';
} elsif ($d and not $i) {
# DELETE
return ($dg and not $w) ? 'DD' : 'D';
} else {
# INSERT + DELETE
return 'U'
}
}
around 'blank_nodes' => sub {
my $orig = shift;
my $self = shift;
my @blanks = $orig->($self, @_);
my %seen = map { $_->value => 1 } @blanks;
foreach my $data ($self->insert, $self->delete) {
my @triples = @{ $data };
my @b = grep { $_->does('Attean::API::Blank') } map { $_->values } @triples;
push(@blanks, grep { not $seen{$_->value}++ } @b);
}
return @blanks;
};
sub algebra_as_string {
my $self = shift;
my $level = shift;
my $indent = ' ' x ($level + 1);
state $S = {
'ID' => 'Insert Data',
'I' => 'Insert',
'DD' => 'Delete Data',
'D' => 'Delete',
'U' => 'Update',
};
my $op = $self->_op_type();
my $s = $S->{ $op };
my @data;
my $ic = scalar(@{ $self->insert });
my $dc = scalar(@{ $self->delete });
if ($ic) {
my $name = $dc ? 'Insert Data' : 'Data';
push(@data, [$name, $self->insert]);
}
if ($dc) {
my $name = $ic ? 'Delete Data' : 'Data';
push(@data, [$name, $self->delete]);
}
foreach my $data (@data) {
my ($name, $quads) = @$data;
$s .= "\n-${indent} $name";
foreach my $q (@$quads) {
$s .= "\n-${indent} " . $q->as_string;
}
}
return $s;
}
sub sparql_tokens {
my $self = shift;
my $op = $self->_op_type();
my $l = AtteanX::SPARQL::Token->lbrace;
my $r = AtteanX::SPARQL::Token->rbrace;
my $dot = AtteanX::SPARQL::Token->dot;
my $data = AtteanX::SPARQL::Token->keyword('DATA');
my $insert = AtteanX::SPARQL::Token->keyword('INSERT');
my $delete = AtteanX::SPARQL::Token->keyword('DELETE');
my $where = AtteanX::SPARQL::Token->keyword('WHERE');
my $using = AtteanX::SPARQL::Token->keyword('USING');
my $named = AtteanX::SPARQL::Token->keyword('NAMED');
# TODO: Support 'DELETE WHERE' shortcut syntax
# TODO: Support WITH
my @dataset;
my $dataset = $self->dataset;
my @default = @{ $dataset->{default} || [] };
my @named = values %{ $dataset->{named} || {} };
if (scalar(@default) or scalar(@named)) {
foreach my $g (sort { $a->as_string cmp $b->as_string } @default) {
push(@dataset, $using, $g->sparql_tokens->elements);
}
foreach my $g (sort { $a->as_string cmp $b->as_string } @named) {
push(@dataset, $using, $named, $g->sparql_tokens->elements);
}
}
my @tokens;
if ($op eq 'ID' or $op eq 'DD') {
my $statements = ($op eq 'ID') ? $self->insert : $self->delete;
my $kw = ($op eq 'ID') ? $insert : $delete;
push(@tokens, $kw);
push(@tokens, $data);
push(@tokens, $l);
foreach my $t (@{ $statements }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $dot);
}
push(@tokens, $r);
} elsif ($op eq 'I' or $op eq 'D') {
my $statements = ($op eq 'I') ? $self->insert : $self->delete;
my $kw = ($op eq 'I') ? $insert : $delete;
push(@tokens, $kw);
push(@tokens, $l);
foreach my $t (@{ $statements }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $dot);
}
push(@tokens, $r);
push(@tokens, @dataset);
push(@tokens, $where);
push(@tokens, $l);
foreach my $c (@{ $self->children }) {
push(@tokens, $c->sparql_tokens->elements);
}
push(@tokens, $r);
} else {
foreach my $x ([$delete, $self->delete], [$insert, $self->insert]) {
my ($kw, $statements) = @$x;
push(@tokens, $kw);
push(@tokens, $l);
foreach my $t (@{ $statements }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $dot);
}
push(@tokens, $r);
}
push(@tokens, @dataset);
push(@tokens, $where);
push(@tokens, $l);
foreach my $c (@{ $self->children }) {
push(@tokens, $c->sparql_tokens->elements);
}
push(@tokens, $r);
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/TermMap.pm 000644 000765 000024 00000000224 14636707550 020674 x ustar 00greg staff 000000 000000 29 mtime=1719373672.08920138
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/TermMap.pm 000644 000765 000024 00000010212 14636707550 016721 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::TermMap - Mapping terms to new terms
=head1 VERSION
This document describes Attean::TermMap version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $m = Attean::TermMap->short_blank_map;
my $new_blank = $m->map( Attean::Blank->new('abcdefg') );
say $new_blank->ntriples_string; # _:a
=head1 DESCRIPTION
The Attean::TermMap class represents a one-way mapping process from and to
L objects. This mapping may rename the blank identifiers,
skolemize nodes, or map the nodes in some other, custom way.
It conforms to the L role.
=head1 ATTRIBUTES
=over 4
=item C<< mapper >>
A CODE reference that will map L objects to (possibly different)
term objects.
=back
=head1 CLASS METHODS
=over 4
=cut
package Attean::TermMap 0.034 {
use Moo;
use Types::Standard qw(CodeRef);
use Attean::API::Binding;
use UUID::Tiny ':std';
use namespace::clean;
with 'Attean::Mapper';
has 'mapper' => (is => 'ro', isa => CodeRef, default => sub { shift }, required => 1);
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
if (scalar(@_) == 1) {
return $class->$orig(mapper => shift);
}
return $class->$orig(@_);
};
=item C<< canonicalization_map >>
Returns a new L that canonicalizes recognized typed
L values.
=cut
sub canonicalization_map {
my $class = shift;
my %map;
return $class->new(mapper => sub {
my $term = shift;
return $term unless ($term->does('Attean::API::Literal'));
if ($term->does('Attean::API::CanonicalizingLiteral')) {
my $c = eval { $term->canonicalized_term };
return ($@) ? undef : $c;
}
return $term;
});
}
=item C<< uuid_blank_map >>
Returns a new L that renames blank nodes with UUID values.
=cut
sub uuid_blank_map {
my $class = shift;
my %map;
return $class->new(mapper => sub {
my $term = shift;
return $term unless ($term->does('Attean::API::Blank'));
my $id = $term->value;
return $map{$id} if (defined($map{$id}));
my $uuid = unpack('H*', create_uuid());
my $new = Attean::Blank->new( 'b' . $uuid );
$map{$id} = $new;
return $new;
});
}
=item C<< short_blank_map >>
Returns a new L that renames blank nodes with short
alphabetic names (e.g. _:a, _:b).
=cut
sub short_blank_map {
my $class = shift;
my %map;
my $next = 'a';
return $class->new(mapper => sub {
my $term = shift;
return $term unless ($term->does('Attean::API::Blank'));
my $id = $term->value;
if (defined(my $t = $map{$id})) {
return $t;
} else {
my $new = Attean::Blank->new( $next++ );
$map{$id} = $new;
return $new;
}
});
}
=item C<< rewrite_map( \%map ) >>
Given C<< %map >> whose keys are term C<< as_string >> serializations, and
objects are L objects, returns a new term map object that
maps terms matching entries in C<< %map >>, and all other terms to themselves.
=cut
sub rewrite_map {
my $class = shift;
my $map = shift;
return $class->new(mapper => sub {
my $term = shift;
return $map->{ $term->as_string } if (exists $map->{ $term->as_string });
return $term;
});
}
=back
=head1 METHODS
=over 4
=item C<< map( $term ) >>
Returns the term that is mapped to by the supplied C<< $term >>.
=cut
sub map {
my $self = shift;
my $term = shift;
return $self->mapper->( $term );
}
=item C<< binding_mapper >>
Returns a mapping function reference that maps L
objects by mapping their constituent mapped L objects.
=cut
sub binding_mapper {
my $self = shift;
return sub {
my $binding = shift;
return $binding->apply_map($self);
}
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/AggregateExpression.pod 000644 000765 000024 00000000225 14636707546 023451 x ustar 00greg staff 000000 000000 30 mtime=1719373670.966935272
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/AggregateExpression.pod 000644 000765 000024 00000002702 14636707546 021502 0 ustar 00greg staff 000000 000000 =head1 NAME
Attean::AggregateExpression - Representation of aggregate expression trees
=head1 VERSION
This document describes Attean::AggregateExpression version 0.034
=head1 DESCRIPTION
The Attean::AggregateExpression class represents an expression tree where the
root node is an aggregate operation (e.g. SUM(?a) or COALESCE(?a/?b, ?c, 0)).
=head1 ROLES
This role consumes the L role.
=head1 ATTRIBUTES
The following attributes exist:
=over 4
=item C<< operator >>
The name of the aggregate operator, from the allowable set: COUNT, SUM, MIN,
MAX, AVG, GROUP_CONCAT, SAMPLE.
=item C<< scalar_vars >>
A HASH reference of scalar variables. The only scalar variable defined for
SPARQL 1.1 is C<'seperator'>, a string separator used with the GROUP_CONCAT
aggregate.
=item C<< distinct >>
A boolean indicating whether the aggregate should operate over distinct term sets, or full multisets.
=item C<< variable >>
A L object which will be bound to the produced aggregate value in results.
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/BindingEqualityTest.pm 000644 000765 000024 00000000225 14636707547 023266 x ustar 00greg staff 000000 000000 30 mtime=1719373671.779591337
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/BindingEqualityTest.pm 000644 000765 000024 00000024416 14636707547 021325 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::BindingEqualityTest - Test for equality of binding sets with bnode isomorphism
=head1 VERSION
This document describes Attean::BindingEqualityTest version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $test = Attean::BindingEqualityTest->new();
if ($test->equals($iter_a, $iter_b)) {
say "Iterators contain equivalent bindings";
}
=head1 DESCRIPTION
...
=head1 METHODS
=over 4
=cut
package Attean::BindingEqualityTest::_Iter {
sub new {
my $class = shift;
my @iters = @_;
my @values = $class->_materialize([], @iters);
return bless(\@values, $class);
}
sub _materialize {
my $class = shift;
my $v = shift;
my @iters = @_;
if (scalar(@iters)) {
my $i = shift(@iters);
my @values;
while (my $vv = $i->next) {
my $prefix = [@$v, @$vv];
push(@values, $class->_materialize($prefix, @iters));
}
return @values;
} else {
return $v;
}
}
sub next {
my $self = shift;
return shift(@$self);
}
}
package Attean::BindingEqualityTest 0.034 {
use v5.14;
use warnings;
use Moo;
use Types::Standard qw(CodeRef ConsumerOf Str);
use Data::Dumper;
use Algorithm::Combinatorics qw(permutations);
use Scalar::Util qw(blessed);
use List::Util qw(shuffle);
use Attean::RDF;
use Digest::MD5 qw(md5_hex);
use namespace::clean;
with 'MooX::Log::Any';
has error => (is => 'rw', isa => Str, init_arg => undef);
sub _coerce {
my $o = shift;
if ($o->does('Attean::API::Model')) {
return $o->get_quads;
} elsif ($o->does('Attean::API::Iterator')) {
return $o;
}
return;
}
=item C<< equals ( $graph1, $graph2 ) >>
Returns true if the invocant and $graph represent two equal RDF graphs (e.g.
there exists a bijection between the RDF statements of the invocant and $graph).
=cut
sub equals {
my $self = shift;
$self->error('');
return $self->_check_equality(@_) ? 1 : 0;
}
sub _check_equality {
my $self = shift;
my ($a, $b) = map { _coerce($_) } @_;
my @graphs = ($a, $b);
my ($ba, $nba) = $self->split_blank_statements($a);
my ($bb, $nbb) = $self->split_blank_statements($b);
if (scalar(@$nba) != scalar(@$nbb)) {
my $nbac = scalar(@$nba);
my $nbbc = scalar(@$nbb);
# warn "====================================================\n";
# warn "BindingEqualityTest count of non-blank statements didn't match:\n";
# warn "-------- a\n";
# foreach my $t (@$nba) {
# warn $t->as_string . "\n";
# }
# warn "-------- b\n";
# foreach my $t (@$nbb) {
# warn $t->as_string . "\n";
# }
$self->error("count of non-blank statements didn't match ($nbac != $nbbc)");
return 0;
}
my $bac = scalar(@$ba);
my $bbc = scalar(@$bb);
if ($bac != $bbc) {
$self->error("count of blank statements didn't match ($bac != $bbc)");
return 0;
}
my $mapper = Attean::TermMap->canonicalization_map;
for ($nba, $nbb) {
@$_ = sort map { $_->apply_map($mapper)->as_string } @$_;
}
foreach my $i (0 .. $#{ $nba }) {
unless ($nba->[$i] eq $nbb->[$i]) {
# warn "====================================================\n";
# warn "BindingEqualityTest non-blank statements didn't match:\n";
# warn "-------- a\n";
# foreach my $t (@$nba) {
# warn $t . "\n";
# }
# warn "-------- b\n";
# foreach my $t (@$nbb) {
# warn $t . "\n";
# }
$self->error("non-blank triples don't match:\n" . Dumper($nba->[$i], $nbb->[$i]));
return 0;
}
}
return _find_mapping($self, $ba, $bb, 1);
}
=item C<< is_subgraph_of ( $graph1, $graph2 ) >>
Returns true if the invocant is a subgraph of $graph. (i.e. there exists an
injection of RDF statements from the invocant to $graph.)
=cut
sub is_subgraph_of {
my $self = shift;
$self->error('');
return $self->_check_subgraph(@_) ? 1 : 0;
}
=item C<< injection_map ( $graph1, $graph2 ) >>
If the invocant is a subgraph of $graph, returns a mapping of blank node
identifiers from the invocant graph to $graph as a hashref. Otherwise
returns false. The solution is not always unique; where there exist multiple
solutions, the solution returned is arbitrary.
=cut
sub injection_map {
my $self = shift;
$self->error('');
my $map = $self->_check_subgraph(@_);
return $map if $map;
return;
}
sub _check_subgraph {
my $self = shift;
my ($a, $b) = map { _coerce($_) } @_;
my @graphs = ($a, $b);
my ($ba, $nba) = $self->split_blank_statements($a);
my ($bb, $nbb) = $self->split_blank_statements($b);
if (scalar(@$nba) > scalar(@$nbb)) {
$self->error("invocant had too many blank node statements to be a subgraph of argument");
return 0;
} elsif (scalar(@$ba) > scalar(@$bb)) {
$self->error("invocant had too many non-blank node statements to be a subgraph of argument");
return 0;
}
my %NBB = map { $_->as_string => 1 } @$nbb;
foreach my $st (@$nba) {
unless ($NBB{ $st->as_string }) {
return 0;
}
}
return _find_mapping($self, $ba, $bb);
}
sub _statement_blank_irisets {
my $self = shift;
my @st = @_;
my %blank_ids_b_iris;
foreach my $st (@st) {
my @iris = map { $_->value } grep { $_->does('Attean::API::IRI') } $st->values;
unless (scalar(@iris)) {
push(@iris, '_');
}
foreach my $n (grep { $_->does('Attean::API::Blank') } $st->values) {
foreach my $i (@iris) {
$blank_ids_b_iris{$n->value}{$i}++;
}
}
}
my %iri_blanks;
foreach my $bid (sort keys %blank_ids_b_iris) {
my $d = Digest::MD5->new();
foreach my $iri (sort keys %{ $blank_ids_b_iris{$bid} }) {
$d->add($iri);
}
$iri_blanks{$d->hexdigest}{$bid}++;
}
return \%iri_blanks;
}
sub _find_mapping {
my $self = shift;
my $ba = shift;
my $bb = shift;
my $equal = shift || 0;
# warn "########### _find_mapping:\n";
# warn "============ A\n";
# foreach my $t (@$ba) {
# warn $t->as_string . "\n";
# }
# warn "============ B\n";
# foreach my $t (@$bb) {
# warn $t->as_string . "\n";
# }
if (scalar(@$ba) == 0) {
return {};
}
my %blank_ids_a;
foreach my $st (@$ba) {
foreach my $n ($st->blanks) {
$blank_ids_a{ $n->value }++;
}
}
my %blank_ids_b;
foreach my $st (@$bb) {
foreach my $n ($st->blanks) {
$blank_ids_b{ $n->value }++;
}
}
my (@ka, @kb);
my $kbp;
# if ($equal) {
# # if we're testing for equality, and not just finding an injection mapping,
# # we can avoid unnecessary work by restricting mappings to those where each
# # permutation only maps blank nodes to other blank nodes that appear in
# # similar bindings (in this case they appear with all the same IRIs)
# my $ba_iri_blanks = $self->_statement_blank_irisets(@$ba);
#
# my $bb_iri_blanks = $self->_statement_blank_irisets(@$bb);
#
# my $ba_keys = join('|', sort keys %$ba_iri_blanks);
# my $bb_keys = join('|', sort keys %$bb_iri_blanks);
# unless ($ba_keys eq $bb_keys) {
# $self->error("didn't find blank node mapping\n");
# return 0;
# }
#
# my @iters;
# foreach my $k (sort keys %$ba_iri_blanks) {
# unless (scalar(@{[keys %{ $ba_iri_blanks->{$k} }]}) == scalar(@{[keys %{ $bb_iri_blanks->{$k} }]})) {
# $self->error("didn't find blank node mapping\n");
# return 0;
# }
# push(@ka, keys %{ $ba_iri_blanks->{$k} });
# push(@kb, keys %{ $bb_iri_blanks->{$k} });
# my $i = permutations([keys %{ $bb_iri_blanks->{$k} }]);
# push(@iters, $i);
# }
#
# if (scalar(@iters) == 1) {
# $kbp = shift(@iters);
# } else {
# $kbp = Attean::BindingEqualityTest::_Iter->new(@iters);
# }
# } else {
@ka = keys %blank_ids_a;
@kb = keys %blank_ids_b;
$kbp = permutations( [shuffle @kb] );
# }
my $canon_map = Attean::TermMap->canonicalization_map;
my %bb_master;
foreach my $bb_item (@$bb) {
my $k = $bb_item->apply_map($canon_map)->as_string;
$bb_master{ $k }++;
}
# my %bb_master = map { $_->apply_map($canon_map)->as_string => 1 } @$bb;
my $count = 0;
MAPPING: while (my $mapping = $kbp->next) {
my %mapping_str;
@mapping_str{ @ka } = @$mapping;
my %mapping = map {
Attean::Blank->new($_)->as_string => Attean::Blank->new($mapping_str{$_})
} (keys %mapping_str);
my $mapper = Attean::TermMap->rewrite_map(\%mapping);
$self->log->trace("trying mapping: " . Dumper($mapping));
my %bb = %bb_master;
foreach my $st (@$ba) {
my $mapped_st = $st->apply_map($mapper)->as_string;
# warn ">>>>>>>\n";
# warn "-> " . $st->as_string . "\n";
# warn "-> " . $mapped_st . "\n";
$self->log->trace("checking for '$mapped_st' in " . Dumper(\%bb));
if ($bb{ $mapped_st }) {
$self->log->trace("Found mapping for binding: " . Dumper($mapped_st));
if (--$bb{ $mapped_st } == 0) {
delete $bb{ $mapped_st };
}
} else {
$self->log->trace("No mapping found for binding: " . Dumper($mapped_st));
# warn "No mapping found for binding: " . Dumper($mapped_st);
# warn Dumper(\%bb);
next MAPPING;
}
}
$self->error("found mapping: " . Dumper(\%mapping_str));
return \%mapping_str;
}
# warn "didn't find blank node mapping:\n";
# warn "============ A\n";
# foreach my $t (@$ba) {
# warn $t->as_string . "\n";
# }
# warn "============ B\n";
# foreach my $t (@$bb) {
# warn $t->as_string . "\n";
# }
$self->error("didn't find blank node mapping\n");
return 0;
}
=item C<< split_blank_statements( $iter ) >>
Returns two array refs containing bindings from C<< $iter >>, with bindings
containing blank nodes and bindings without any blank nodes, respectively.
=cut
sub split_blank_statements {
my $self = shift;
my $iter = shift;
my (@blanks, @nonblanks);
while (my $st = $iter->next) {
unless ($st->does('Attean::API::Binding')) {
die "Unexpected non-binding value found in BindingEqualityTest: " . $st->as_string;
}
if ($st->has_blanks) {
push(@blanks, $st);
} else {
push(@nonblanks, $st);
}
}
return (\@blanks, \@nonblanks);
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/QueryPlanner.pm 000644 000765 000024 00000000225 14636707550 021755 x ustar 00greg staff 000000 000000 30 mtime=1719373672.006796415
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/QueryPlanner.pm 000644 000765 000024 00000114220 14636707550 020005 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::QueryPlanner - Query planner
=head1 VERSION
This document describes Attean::QueryPlanner version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $planner = Attean::QueryPlanner->new();
my $default_graphs = [ Attean::IRI->new('http://example.org/') ];
my $plan = $planner->plan_for_algebra( $algebra, $model, $default_graphs );
my $iter = $plan->evaluate($model);
while (my $result = $iter->next()) {
say $result->as_string;
}
=head1 DESCRIPTION
The Attean::QueryPlanner class is a base class implementing common behavior for
query planners. Subclasses will need to consume or compose the
L role.
Trivial sub-classes may consume L, while more
complex planners may choose to implement complex join planning (e.g.
L).
=head1 ATTRIBUTES
=over 4
=cut
use Attean::Algebra;
use Attean::Plan;
use Attean::Expression;
package Attean::QueryPlanner 0.034 {
use Moo;
use Encode qw(encode);
use Attean::RDF;
use Scalar::Util qw(blessed reftype);
use List::Util qw(reduce);
use List::MoreUtils qw(all any);
use Types::Standard qw(Int ConsumerOf InstanceOf);
use URI::Escape;
use Algorithm::Combinatorics qw(subsets);
use List::Util qw(min);
use Math::Cartesian::Product;
use namespace::clean;
with 'Attean::API::QueryPlanner', 'MooX::Log::Any';
has 'counter' => (is => 'rw', isa => Int, default => 0);
has 'table_threshold' => (is => 'rw', isa => Int, default => 10);
=back
=head1 METHODS
=over 4
=item C<< new_temporary( $type ) >>
Returns a new unique (in the context of the query planner) ID string that may
be used for things like fresh (temporary) variables. The C<< $type >> string is
used in the generated name to aid in identifying different uses for the names.
=cut
sub new_temporary {
my $self = shift;
my $type = shift;
my $c = $self->counter;
$self->counter($c+1);
return sprintf('.%s-%d', $type, $c);
}
=item C<< plan_for_algebra( $algebra, $model, \@active_graphs, \@default_graphs ) >>
Returns the first plan returned from C<< plans_for_algebra >>.
=cut
sub plan_for_algebra {
my $self = shift;
my @plans = $self->plans_for_algebra(@_);
return shift(@plans);
}
=item C<< plans_for_algebra( $algebra, $model, \@active_graphs, \@default_graphs ) >>
Returns L objects representing alternate query plans for
evaluating the query C<< $algebra >> against the C<< $model >>, using
the supplied C<< $active_graph >>.
=cut
sub plans_for_algebra {
my $self = shift;
my $algebra = shift;
my $model = shift;
my $active_graphs = shift;
my $default_graphs = shift;
my %args = @_;
if ($model->does('Attean::API::CostPlanner')) {
my @plans = $model->plans_for_algebra($algebra, $self, $active_graphs, $default_graphs, %args);
if (@plans) {
return @plans; # trust that the model knows better than us what plans are best
} else {
$self->log->info("*** Model did not provide plans: $model");
}
}
Carp::confess "No algebra passed for evaluation" unless ($algebra);
# TODO: propagate interesting orders
my $interesting = [];
my @children = @{ $algebra->children };
my ($child) = $children[0];
if ($algebra->isa('Attean::Algebra::Query') or $algebra->isa('Attean::Algebra::Update')) {
return $self->plans_for_algebra($algebra->child, $model, $active_graphs, $default_graphs, %args);
} elsif ($algebra->isa('Attean::Algebra::BGP')) {
my $triples = $algebra->triples;
my @triples = @$triples;
my %blanks;
foreach my $i (0 .. $#triples) {
my $t = $triples[$i];
my @nodes = $t->values;
my $changed = 0;
foreach (@nodes) {
if ($_->does('Attean::API::Blank')) {
$changed++;
my $id = $_->value;
unless (exists $blanks{$id}) {
$blanks{$id} = Attean::Variable->new(value => $self->new_temporary('blank'));
}
$_ = $blanks{$id};
}
}
if ($changed) {
my $new = Attean::TriplePattern->new(@nodes);
$triples[$i] = $new;
}
}
my $bgp = Attean::Algebra::BGP->new( triples => \@triples );
my @plans = $self->bgp_join_plans($bgp, $model, $active_graphs, $default_graphs, $interesting, map {
[$self->access_plans($model, $active_graphs, $_)]
} @triples);
return @plans;
} elsif ($algebra->isa('Attean::Algebra::Join')) {
return $self->group_join_plans($model, $active_graphs, $default_graphs, $interesting, map {
[$self->plans_for_algebra($_, $model, $active_graphs, $default_graphs, %args)]
} @children);
} elsif ($algebra->isa('Attean::Algebra::Distinct') or $algebra->isa('Attean::Algebra::Reduced')) {
my @plans = $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args);
my @dist;
foreach my $p (@plans) {
if ($p->distinct) {
push(@dist, $p);
} else {
my @vars = @{ $p->in_scope_variables };
my $cmps = $p->ordered;
if ($self->_comparators_are_stable_and_cover_vars($cmps, @vars)) {
# the plan has a stable ordering which covers all the variables, so we can just uniq the iterator
push(@dist, Attean::Plan::Unique->new(children => [$p], distinct => 1, ordered => $p->ordered));
} else {
# TODO: if the plan isn't distinct, but is ordered, we can use a batched implementation
push(@dist, Attean::Plan::HashDistinct->new(children => [$p], distinct => 1, ordered => $p->ordered));
}
}
}
return @dist;
} elsif ($algebra->isa('Attean::Algebra::Filter')) {
# TODO: simple range relation filters can be handled differently if that filter operates on a variable that is part of the ordering
my $expr = $algebra->expression;
my $w = Attean::TreeRewriter->new(types => ['Attean::API::DirectedAcyclicGraph']);
$w->register_pre_handler(sub {
my ($t, $parent, $thunk) = @_;
if ($t->isa('Attean::ExistsExpression')) {
my $pattern = $t->pattern;
my $plan = $self->plan_for_algebra($pattern, $model, $active_graphs, $default_graphs, @_);
unless ($plan->does('Attean::API::BindingSubstitutionPlan')) {
die 'Exists plan does not consume Attean::API::BindingSubstitutionPlan: ' . $plan->as_string;
}
my $new = Attean::ExistsPlanExpression->new(
plan => $plan,
);
return (1, 0, $new);
}
return (0, 1, $t);
});
my ($changed, $rewritten) = $w->rewrite($expr, {});
if ($changed) {
$expr = $rewritten;
}
my $var = $self->new_temporary('filter');
my %exprs = ($var => $expr);
my @plans;
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args)) {
my $distinct = $plan->distinct;
my $ordered = $plan->ordered;
if ($expr->isa('Attean::ValueExpression') and $expr->value->does('Attean::API::Variable')) {
my $filtered = Attean::Plan::EBVFilter->new(children => [$plan], variable => $expr->value->value, distinct => $distinct, ordered => $ordered);
push(@plans, $filtered);
} else {
my @vars = ($var);
my @inscope = ($var, @{ $plan->in_scope_variables });
my @pvars = map { Attean::Variable->new($_) } @{ $plan->in_scope_variables };
my $extend = Attean::Plan::Extend->new(children => [$plan], expressions => \%exprs, distinct => 0, ordered => $ordered, active_graphs => $active_graphs);
my $filtered = Attean::Plan::EBVFilter->new(children => [$extend], variable => $var, distinct => 0, ordered => $ordered);
my $proj = $self->new_projection($filtered, $distinct, @{ $plan->in_scope_variables });
push(@plans, $proj);
}
}
return @plans;
} elsif ($algebra->isa('Attean::Algebra::OrderBy')) {
# TODO: no-op if already ordered
my @cmps = @{ $algebra->comparators };
my ($exprs, $ascending, $svars) = $self->_order_by($algebra);
my @plans;
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, interesting_order => $algebra->comparators, %args)) {
my $distinct = $plan->distinct;
if (scalar(@cmps) == 1 and $cmps[0]->expression->isa('Attean::ValueExpression') and $cmps[0]->expression->value->does('Attean::API::Variable')) {
# TODO: extend this to handle more than one comparator, so long as they are *all* just variables (and not complex expressions)
# If we're sorting by just a variable name, don't bother creating new variables for the sort expressions, use the underlying variable directy
my @vars = @{ $plan->in_scope_variables };
my @pvars = map { Attean::Variable->new($_) } @{ $plan->in_scope_variables };
my $var = $cmps[0]->expression->value->value;
my $ascending = { $var => $cmps[0]->ascending };
my $ordered = Attean::Plan::OrderBy->new(children => [$plan], variables => [$var], ascending => $ascending, distinct => $distinct, ordered => \@cmps);
push(@plans, $ordered);
} else {
my @vars = (@{ $plan->in_scope_variables }, keys %$exprs);
my @pvars = map { Attean::Variable->new($_) } @{ $plan->in_scope_variables };
my $extend = Attean::Plan::Extend->new(children => [$plan], expressions => $exprs, distinct => 0, ordered => $plan->ordered, active_graphs => $active_graphs);
my $ordered = Attean::Plan::OrderBy->new(children => [$extend], variables => $svars, ascending => $ascending, distinct => 0, ordered => \@cmps);
my $proj = $self->new_projection($ordered, $distinct, @{ $plan->in_scope_variables });
push(@plans, $proj);
}
}
return @plans;
} elsif ($algebra->isa('Attean::Algebra::LeftJoin')) {
my $l = [$self->plans_for_algebra($children[0], $model, $active_graphs, $default_graphs, %args)];
my $r = [$self->plans_for_algebra($children[1], $model, $active_graphs, $default_graphs, %args)];
return $self->join_plans($model, $active_graphs, $default_graphs, $l, $r, 'left', $algebra->expression);
} elsif ($algebra->isa('Attean::Algebra::Minus')) {
my $l = [$self->plans_for_algebra($children[0], $model, $active_graphs, $default_graphs, %args)];
my $r = [$self->plans_for_algebra($children[1], $model, $active_graphs, $default_graphs, %args)];
return $self->join_plans($model, $active_graphs, $default_graphs, $l, $r, 'minus');
} elsif ($algebra->isa('Attean::Algebra::Project')) {
my $vars = $algebra->variables;
my @vars = map { $_->value } @{ $vars };
my $vars_key = join(' ', sort @vars);
my $distinct = 0;
my @plans = map {
($vars_key eq join(' ', sort @{ $_->in_scope_variables }))
? $_ # no-op if plan is already properly-projected
: $self->new_projection($_, $distinct, @vars)
} $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args);
return @plans;
} elsif ($algebra->isa('Attean::Algebra::Graph')) {
my $graph = $algebra->graph;
if ($graph->does('Attean::API::Term')) {
if (my $available = $args{available_graphs}) {
# the list of available graphs has been restricted, and this
# graph is not available so return an empty table plan.
unless (any { $_->equals($graph) } @$available) {
my $plan = Attean::Plan::Table->new( variables => [], rows => [], distinct => 0, ordered => [] );
return $plan;
}
}
return $self->plans_for_algebra($child, $model, [$graph], $default_graphs, %args);
} else {
my $gvar = $graph->value;
my $graphs = $model->get_graphs;
my @plans;
my %vars = map { $_ => 1 } $child->in_scope_variables;
$vars{ $gvar }++;
my @vars = keys %vars;
my %available;
if (my $available = $args{available_graphs}) {
foreach my $a (@$available) {
$available{ $a->value }++;
}
$graphs = $graphs->grep(sub { $available{ $_->value } });
}
my @branches;
my %ignore = map { $_->value => 1 } @$default_graphs;
while (my $graph = $graphs->next) {
next if $ignore{ $graph->value };
my %exprs = ($gvar => Attean::ValueExpression->new(value => $graph));
# TODO: rewrite $child pattern here to replace any occurrences of the variable $gvar to $graph
my @plans = map {
Attean::Plan::Extend->new(children => [$_], expressions => \%exprs, distinct => 0, ordered => $_->ordered, active_graphs => $active_graphs);
} $self->plans_for_algebra($child, $model, [$graph], $default_graphs, %args);
push(@branches, \@plans);
}
if (scalar(@branches) == 1) {
@plans = @{ shift(@branches) };
} else {
cartesian { push(@plans, Attean::Plan::Union->new(children => [@_], distinct => 0, ordered => [])) } @branches;
}
return @plans;
}
} elsif ($algebra->isa('Attean::Algebra::Table')) {
my $rows = $algebra->rows;
my $vars = $algebra->variables;
my @vars = map { $_->value } @{ $vars };
if (scalar(@$rows) < $self->table_threshold) {
return Attean::Plan::Table->new( variables => $vars, rows => $rows, distinct => 0, ordered => [] );
} else {
my $iter = Attean::ListIterator->new(
item_type => 'Attean::API::Result',
variables => \@vars,
values => $rows
);
return Attean::Plan::Iterator->new( iterator => $iter, distinct => 0, ordered => [] );
}
} elsif ($algebra->isa('Attean::Algebra::Service')) {
my $endpoint = $algebra->endpoint;
my $silent = $algebra->silent;
my $sparql = sprintf('SELECT * WHERE { %s }', $child->as_sparql);
my @vars = $child->in_scope_variables;
my $plan = Attean::Plan::Service->new(
request_signer => $self->request_signer,
endpoint => $endpoint,
silent => $silent,
sparql => $sparql,
distinct => 0,
in_scope_variables => \@vars,
ordered => []
);
return $plan;
} elsif ($algebra->isa('Attean::Algebra::Slice')) {
my $limit = $algebra->limit;
my $offset = $algebra->offset;
my @plans;
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args)) {
my $vars = $plan->in_scope_variables;
push(@plans, Attean::Plan::Slice->new(children => [$plan], limit => $limit, offset => $offset, distinct => $plan->distinct, ordered => $plan->ordered));
}
return @plans;
} elsif ($algebra->isa('Attean::Algebra::Union')) {
# TODO: if both branches are similarly ordered, we can use Attean::Plan::Merge to keep the resulting plan ordered
my @vars = keys %{ { map { map { $_ => 1 } $_->in_scope_variables } @children } };
my @plansets = map { [$self->plans_for_algebra($_, $model, $active_graphs, $default_graphs, %args)] } @children;
my @plans;
cartesian {
push(@plans, Attean::Plan::Union->new(children => \@_, distinct => 0, ordered => []))
} @plansets;
return @plans;
} elsif ($algebra->isa('Attean::Algebra::Extend')) {
my $var = $algebra->variable->value;
my $expr = $algebra->expression;
my %exprs = ($var => $expr);
my @vars = $algebra->in_scope_variables;
my @plans;
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args)) {
my $extend = Attean::Plan::Extend->new(children => [$plan], expressions => \%exprs, distinct => 0, ordered => $plan->ordered, active_graphs => $active_graphs);
push(@plans, $extend);
}
return @plans;
} elsif ($algebra->isa('Attean::Algebra::Group')) {
my $aggs = $algebra->aggregates;
my $groups = $algebra->groupby;
my %exprs;
foreach my $expr (@$aggs) {
my $var = $expr->variable->value;
$exprs{$var} = $expr;
}
my @plans;
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args)) {
my $extend = Attean::Plan::Aggregate->new(children => [$plan], aggregates => \%exprs, groups => $groups, distinct => 0, ordered => [], active_graphs => $active_graphs);
push(@plans, $extend);
}
return @plans;
} elsif ($algebra->isa('Attean::Algebra::Ask')) {
my @plans;
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args)) {
return Attean::Plan::Exists->new(children => [$plan], distinct => 1, ordered => []);
}
return @plans;
} elsif ($algebra->isa('Attean::Algebra::Path')) {
my $s = $algebra->subject;
my $path = $algebra->path;
my $o = $algebra->object;
my @algebra = $self->simplify_path($s, $path, $o);
my @join;
if (scalar(@algebra)) {
my @triples;
while (my $pa = shift(@algebra)) {
if ($pa->isa('Attean::TriplePattern')) {
push(@triples, $pa);
} else {
if (scalar(@triples)) {
push(@join, Attean::Algebra::BGP->new( triples => [@triples] ));
@triples = ();
}
push(@join, $pa);
}
}
if (scalar(@triples)) {
push(@join, Attean::Algebra::BGP->new( triples => [@triples] ));
}
my @vars = $algebra->in_scope_variables;
my @joins = $self->group_join_plans($model, $active_graphs, $default_graphs, $interesting, map {
[$self->plans_for_algebra($_, $model, $active_graphs, $default_graphs, %args)]
} @join);
my @plans;
foreach my $j (@joins) {
push(@plans, Attean::Plan::Project->new(children => [$j], variables => [map { Attean::Variable->new($_) } @vars], distinct => 0, ordered => []));
}
return @plans;
} elsif ($path->isa('Attean::Algebra::ZeroOrMorePath') or $path->isa('Attean::Algebra::OneOrMorePath')) {
my $skip = $path->isa('Attean::Algebra::OneOrMorePath') ? 1 : 0;
my $begin = Attean::Variable->new(value => $self->new_temporary('pp'));
my $end = Attean::Variable->new(value => $self->new_temporary('pp'));
my $s_var = $s->does('Attean::API::Variable');
my $o_var = $o->does('Attean::API::Variable');
my $child = $path->children->[0];
my $a;
if ($s_var and not($o_var)) {
my $inv = Attean::Algebra::InversePath->new( children => [$child] );
$a = Attean::Algebra::Path->new( subject => $end, path => $inv, object => $begin );
} else {
$a = Attean::Algebra::Path->new( subject => $begin, path => $child, object => $end );
}
my @cplans = $self->plans_for_algebra($a, $model, $active_graphs, $default_graphs, %args);
my @plans;
foreach my $cplan (@cplans) {
my $plan = Attean::Plan::ALPPath->new(
subject => $s,
children => [$cplan],
object => $o,
graph => $active_graphs,
skip => $skip,
step_begin => $begin,
step_end => $end,
distinct => 0,
ordered => []
);
push(@plans, $plan);
}
return @plans;
} elsif ($path->isa('Attean::Algebra::ZeroOrOnePath')) {
my $a = Attean::Algebra::Path->new( subject => $s, path => $path->children->[0], object => $o );
my @children = $self->plans_for_algebra($a, $model, $active_graphs, $default_graphs, %args);
my @plans;
foreach my $plan (@children) {
push(@plans, Attean::Plan::ZeroOrOnePath->new(
subject => $s,
children => [$plan],
object => $o,
graph => $active_graphs,
distinct => 0,
ordered => []
));
}
return @plans;
} else {
die "Cannot simplify property path $path: " . $algebra->as_string;
}
} elsif ($algebra->isa('Attean::Algebra::Construct')) {
my @children = $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args);
my @plans;
foreach my $plan (@children) {
push(@plans, Attean::Plan::Construct->new(triples => $algebra->triples, children => [$plan], distinct => 0, ordered => []));
}
return @plans;
} elsif ($algebra->isa('Attean::Algebra::Describe')) {
my @children = $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args);
my @plans;
foreach my $plan (@children) {
push(@plans, Attean::Plan::Describe->new(terms => $algebra->terms, graph => $active_graphs, children => [$plan], distinct => 0, ordered => []));
}
return @plans;
} elsif ($algebra->isa('Attean::Algebra::Clear')) {
my $plan_class = $algebra->drop ? 'Attean::Plan::Drop' : 'Attean::Plan::Clear';
my $target = $algebra->target;
if ($target eq 'GRAPH') {
return Attean::Plan::Clear->new(graphs => [$algebra->graph]);
} else {
my %default = map { $_->value => 1 } @$active_graphs;
my $graphs = $model->get_graphs;
my @graphs;
while (my $graph = $graphs->next) {
if ($target eq 'ALL') {
push(@graphs, $graph);
} else {
if ($target eq 'DEFAULT' and $default{ $graph->value }) {
push(@graphs, $graph);
} elsif ($target eq 'NAMED' and not $default{ $graph->value }) {
push(@graphs, $graph);
}
}
}
return $plan_class->new(graphs => \@graphs);
}
} elsif ($algebra->isa('Attean::Algebra::Add')) {
my $triple = triplepattern(variable('s'), variable('p'), variable('o'));
my $child;
my $default_source = 0;
if (my $from = $algebra->source) {
($child) = $self->access_plans( $model, $active_graphs, $triple->as_quad_pattern($from) );
} else {
$default_source++;
my $bgp = Attean::Algebra::BGP->new( triples => [$triple] );
($child) = $self->plans_for_algebra($bgp, $model, $active_graphs, $default_graphs, %args);
}
my $dest;
my $default_dest = 0;
if (my $g = $algebra->destination) {
$dest = $triple->as_quad_pattern($g);
} else {
$default_dest++;
$dest = $triple->as_quad_pattern($default_graphs->[0]);
}
my @plans;
my $run_update = 1;
if ($default_dest and $default_source) {
$run_update = 0;
} elsif ($default_dest or $default_source) {
#
} elsif ($algebra->source->equals($algebra->destination)) {
$run_update = 0;
}
if ($run_update) {
if ($algebra->drop_destination) {
my @graphs = $algebra->has_destination ? $algebra->destination : @$default_graphs;
unshift(@plans, Attean::Plan::Clear->new(graphs => [@graphs]));
}
push(@plans, Attean::Plan::TripleTemplateToModelQuadMethod->new(
graph => $default_graphs->[0],
order => ['add_quad'],
patterns => {'add_quad' => [$dest]},
children => [$child],
));
if ($algebra->drop_source) {
my @graphs = $algebra->has_source ? $algebra->source : @$default_graphs;
push(@plans, Attean::Plan::Clear->new(graphs => [@graphs]));
}
}
my $plan = (scalar(@plans) == 1) ? shift(@plans) : Attean::Plan::Sequence->new( children => \@plans );
return $plan;
} elsif ($algebra->isa('Attean::Algebra::Modify')) {
unless ($child) {
# This is an INSERT/DELETE DATA algebra with ground data and no pattern
$child = Attean::Algebra::BGP->new( triples => [] );
}
my $dataset = $algebra->dataset;
my @default = @{ $dataset->{default} || [] };
my @named = values %{ $dataset->{named} || {} };
my @active_graphs = @$active_graphs;
my @default_graphs = @$default_graphs;
if (scalar(@default) or scalar(@named)) {
# change the available named graphs
# change the active graph(s)
@active_graphs = @default;
@default_graphs = @default;
$args{ available_graphs } = [@named];
} else {
# no custom dataset
}
my @children = $self->plans_for_algebra($child, $model, \@active_graphs, \@default_graphs, %args);
my $i = $algebra->insert;
my $d = $algebra->delete;
my %patterns;
my @order;
if (scalar(@$d)) {
push(@order, 'remove_quad');
$patterns{ 'remove_quad' } = $d;
}
if (scalar(@$i)) {
push(@order, 'add_quad');
$patterns{ 'add_quad' } = $i;
}
return map {
Attean::Plan::TripleTemplateToModelQuadMethod->new(
graph => $default_graphs->[0],
order => \@order,
patterns => \%patterns,
children => [$_],
)
} @children;
} elsif ($algebra->isa('Attean::Algebra::Load')) {
my $pattern = triplepattern(variable('subject'), variable('predicate'), variable('object'));
my $load = Attean::Plan::Load->new( url => $algebra->url->value, silent => $algebra->silent );
my $graph = $algebra->has_graph ? $algebra->graph : $default_graphs->[0];
my $plan = Attean::Plan::TripleTemplateToModelQuadMethod->new(
graph => $graph,
order => ['add_quad'],
patterns => {'add_quad' => [$pattern]},
children => [$load],
);
return $plan;
} elsif ($algebra->isa('Attean::Algebra::Create')) {
return Attean::Plan::Sequence->new( children => [] );
} elsif ($algebra->isa('Attean::Algebra::Sequence')) {
my @plans;
foreach my $child (@{ $algebra->children }) {
my ($plan) = $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args);
push(@plans, $plan);
}
return Attean::Plan::Sequence->new( children => \@plans );
} elsif ($algebra->isa('Attean::Algebra::Unfold')) {
my @plans = $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args);
my @unfold;
foreach my $p (@plans) {
push(@unfold, Attean::Plan::Unfold->new( children => [$p], expression => $algebra->expression, variables => $algebra->variables, active_graphs => $active_graphs ));
}
return @unfold;
}
die "Unimplemented algebra evaluation for: " . $algebra->as_string;
}
# sub plans_for_unbounded_path {
# my $self = shift;
# my $algebra = shift;
# my $model = shift;
# my $active_graphs = shift;
# my $default_graphs = shift;
# my %args = @_;
#
# my $s = $algebra->subject;
# my $path = $algebra->path;
# my $o = $algebra->object;
#
# return Attean::Plan::ALPPath->new(distinct => 0, ordered => []);
# }
sub _package {
my $self = shift;
my @args = @_;
my @bgptriples = map { @{ $_->triples } } grep { $_->isa('Attean::Algebra::BGP') } @args;
my @triples = grep { $_->isa('Attean::TriplePattern') } @args;
my @rest = grep { not $_->isa('Attean::Algebra::BGP') and not $_->isa('Attean::TriplePattern') } @args;
if (scalar(@rest) == 0) {
return Attean::Algebra::BGP->new( triples => [@bgptriples, @triples] );
} else {
my $p = Attean::Algebra::BGP->new( triples => [@bgptriples, @triples] );
while (scalar(@rest) > 0) {
$p = Attean::Algebra::Join->new( children => [$p, shift(@rest)] );
}
return $p;
}
}
=item C<< simplify_path( $subject, $path, $object ) >>
Return a simplified L object corresponding to the given
property path.
=cut
sub simplify_path {
my $self = shift;
my $s = shift;
my $path = shift;
my $o = shift;
if ($path->isa('Attean::Algebra::SequencePath')) {
my $jvar = Attean::Variable->new(value => $self->new_temporary('pp'));
my ($lhs, $rhs) = @{ $path->children };
my @paths;
push(@paths, $self->simplify_path($s, $lhs, $jvar));
push(@paths, $self->simplify_path($jvar, $rhs, $o));
return $self->_package(@paths);
} elsif ($path->isa('Attean::Algebra::InversePath')) {
my ($ipath) = @{ $path->children };
return $self->simplify_path($o, $ipath, $s);
} elsif ($path->isa('Attean::Algebra::PredicatePath')) {
my $pred = $path->predicate;
return Attean::TriplePattern->new($s, $pred, $o);
} elsif ($path->isa('Attean::Algebra::AlternativePath')) {
my ($l, $r) = @{ $path->children };
my $la = $self->_package($self->simplify_path($s, $l, $o));
my $ra = $self->_package($self->simplify_path($s, $r, $o));
return Attean::Algebra::Union->new( children => [$la, $ra] );
} elsif ($path->isa('Attean::Algebra::NegatedPropertySet')) {
my @branches;
my @preds = @{ $path->predicates };
if (scalar(@preds)) {
my $pvar = Attean::Variable->new(value => $self->new_temporary('nps'));
my $pvar_e = Attean::ValueExpression->new( value => $pvar );
my $t = Attean::TriplePattern->new($s, $pvar, $o);
my @vals = map { Attean::ValueExpression->new( value => $_ ) } @preds;
my $expr = Attean::FunctionExpression->new( children => [$pvar_e, @vals], operator => 'notin' );
my $bgp = Attean::Algebra::BGP->new( triples => [$t] );
my $f_fwd = Attean::Algebra::Filter->new( children => [$bgp], expression => $expr );
push(@branches, $f_fwd);
}
my @rev = @{ $path->reversed };
if (scalar(@rev)) {
my $pvar = Attean::Variable->new(value => $self->new_temporary('nps_rev'));
my $pvar_e = Attean::ValueExpression->new( value => $pvar );
my $t = Attean::TriplePattern->new($o, $pvar, $s);
my @vals = map { Attean::ValueExpression->new( value => $_ ) } @rev;
my $expr = Attean::FunctionExpression->new( children => [$pvar_e, @vals], operator => 'notin' );
my $bgp = Attean::Algebra::BGP->new( triples => [$t] );
my $f_rev = Attean::Algebra::Filter->new( children => [$bgp], expression => $expr );
push(@branches, $f_rev);
}
if (scalar(@branches) == 1) {
return shift(@branches);
} else {
return Attean::Algebra::Union->new( children => \@branches );
}
} else {
return;
}
}
=item C<< new_projection( $plan, $distinct, @variable_names ) >>
Return a new L<< Attean::Plan::Project >> plan over C<< $plan >>, projecting
the named variables. C<< $disctinct >> should be true if the caller can
guarantee that the resulting plan will produce distinct results, false otherwise.
This method takes care of computing plan metadata such as the resulting ordering.
=cut
sub new_projection {
my $self = shift;
my $plan = shift;
my $distinct = shift;
my @vars = @_;
my $order = $plan->ordered;
my @pvars = map { Attean::Variable->new($_) } @vars;
my %pvars = map { $_ => 1 } @vars;
my @porder;
CMP: foreach my $cmp (@{ $order }) {
my @cmpvars = $self->_comparator_referenced_variables($cmp);
foreach my $v (@cmpvars) {
unless ($pvars{ $v }) {
# projection is dropping a variable used in this comparator
# so we lose any remaining ordering that the sub-plan had.
last CMP;
}
}
# all the variables used by this comparator are available after
# projection, so the resulting plan will continue to be ordered
# by this comparator
push(@porder, $cmp);
}
return Attean::Plan::Project->new(children => [$plan], variables => \@pvars, distinct => $distinct, ordered => \@porder);
}
=item C<< bgp_join_plans( $bgp, $model, \@active_graphs, \@default_graphs, \@interesting_order, \@plansA, \@plansB, ... ) >>
Returns a list of alternative plans for the join of a set of triples.
The arguments C<@plansA>, C<@plansB>, etc. represent alternative plans for each
triple participating in the join.
=cut
sub bgp_join_plans {
my $self = shift;
my $bgp = shift;
my $model = shift;
my $active = shift;
my $default = shift;
my $interesting = shift;
my @triples = @_;
if (scalar(@triples)) {
my @plans = $self->joins_for_plan_alternatives($model, $active, $default, $interesting, @triples);
my @triples = @{ $bgp->triples };
# If the BGP does not contain any blanks, then the results are
# guaranteed to be distinct. Otherwise, we have to assume they're
# not distinct.
my $distinct = 1;
LOOP: foreach my $t (@triples) {
foreach my $b ($t->values_consuming_role('Attean::API::Blank')) {
$distinct = 0;
last LOOP;
}
foreach my $b ($t->values_consuming_role('Attean::API::Variable')) {
if ($b->value =~ /^[.]/) {
# variable names starting with a dot represent placeholders introduced during query planning (with C)
# they are not projectable, and so may cause an otherwise distinct result to become non-distinct
$distinct = 0;
last LOOP;
}
}
}
# Set the distinct flag on each of the top-level join plans that
# represents the entire BGP. (Sub-plans won't ever be marked as
# distinct, but that shouldn't matter to the rest of the planning
# process.)
if ($distinct) {
foreach my $p (@plans) {
$p->distinct(1);
}
}
return @plans;
} else {
# The empty BGP is a special case -- it results in a single join-identity result
my $r = Attean::Result->new( bindings => {} );
my $plan = Attean::Plan::Table->new( rows => [$r], variables => [], distinct => 1, ordered => [] );
return $plan;
}
}
=item C<< group_join_plans( $model, \@active_graphs, \@default_graphs, \@interesting_order, \@plansA, \@plansB, ... ) >>
Returns a list of alternative plans for the join of a set of sub-plans.
The arguments C<@plansA>, C<@plansB>, etc. represent alternative plans for each
sub-plan participating in the join.
=cut
sub group_join_plans {
my $self = shift;
return $self->joins_for_plan_alternatives(@_);
}
=item C<< joins_for_plan_alternatives( $model, \@active_graphs, \@default_graphs, $interesting, \@plan_A, \@plan_B, ... ) >>
Returns a list of alternative plans that may all be used to produce results
matching the join of C<< plan_A >>, C< plan_B >>, etc. Each plan array here
(e.g. C<< @plan_A >>) should contain equivalent plans.
=cut
sub joins_for_plan_alternatives {
my $self = shift;
my $model = shift;
my $active_graphs = shift;
my $default_graphs = shift;
my $interesting = shift;
my @args = @_; # each $args[$i] here is an array reference containing alternate plans for element $i
die "This query planner does not seem to consume a Attean::API::JoinPlanner role (which is necessary for query planning)";
}
=item C<< access_plans( $model, $active_graphs, $pattern ) >>
Returns a list of alternative L objects that may be used to
produce results matching the L $pattern in
the context of C<< $active_graphs >>.
=cut
# $pattern is a Attean::API::TripleOrQuadPattern object
# Return a Attean::API::Plan object that represents the evaluation of $pattern.
# e.g. different plans might represent different ways of producing the matches (table scan, index match, etc.)
sub access_plans {
my $self = shift;
my $model = shift;
my $active_graphs = shift;
my $pattern = shift;
my @vars = map { $_->value } $pattern->values_consuming_role('Attean::API::Variable');
my %vars;
my $dup = 0;
foreach my $v (@vars) {
$dup++ if ($vars{$v}++);
}
my $distinct = 0; # TODO: is this pattern distinct? does it have blank nodes?
my @nodes = $pattern->values;
unless ($nodes[3]) {
$nodes[3] = $active_graphs;
}
my $plan = Attean::Plan::Quad->new(
subject => $nodes[0],
predicate => $nodes[1],
object => $nodes[2],
graph => $nodes[3],
values => \@nodes,
distinct => $distinct,
ordered => [],
);
return $plan;
}
=item C<< join_plans( $model, \@active_graphs, \@default_graphs, \@plan_left, \@plan_right, $type [, $expr] ) >>
Returns a list of alternative plans for the join of one plan from C<< @plan_left >>
and one plan from C<< @plan_right >>. The join C<< $type >> must be one of
C<< 'inner' >>, C<< 'left' >>, or C<< 'minus' >>, indicating the join algorithm
to be used. If C<< $type >> is C<< 'left' >>, then the optional C<< $expr >>
may be used to supply a filter expression that should be used by the SPARQL
left-join algorithm.
=cut
# $lhs and $rhs are both Attean::API::Plan objects
# Return a Attean::API::Plan object that represents the evaluation of $lhs ⋈ $rhs.
# The $left and $minus flags indicate the type of the join to be performed (⟕ and ▷, respectively).
# e.g. different plans might represent different join algorithms (nested loop join, hash join, etc.) or different orderings ($lhs ⋈ $rhs or $rhs ⋈ $lhs)
sub join_plans {
my $self = shift;
my $model = shift;
my $active_graphs = shift;
my $default_graphs = shift;
my $lplans = shift;
my $rplans = shift;
my $type = shift;
my $left = ($type eq 'left');
my $minus = ($type eq 'minus');
my $expr = shift;
my @plans;
Carp::confess unless (reftype($lplans) eq 'ARRAY');
foreach my $lhs (@{ $lplans }) {
foreach my $rhs (@{ $rplans }) {
my @vars = (@{ $lhs->in_scope_variables }, @{ $rhs->in_scope_variables });
my %vars;
my %join_vars;
foreach my $v (@vars) {
if ($vars{$v}++) {
$join_vars{$v}++;
}
}
my @join_vars = keys %join_vars;
if ($left) {
if (scalar(@join_vars) > 0) {
push(@plans, Attean::Plan::HashJoin->new(children => [$lhs, $rhs], left => 1, expression => $expr, join_variables => \@join_vars, distinct => 0, ordered => []));
}
push(@plans, Attean::Plan::NestedLoopJoin->new(children => [$lhs, $rhs], left => 1, expression => $expr, join_variables => \@join_vars, distinct => 0, ordered => $lhs->ordered));
} elsif ($minus) {
# we can't use a hash join for MINUS queries, because of the definition of MINUS having a special case for compatible results that have disjoint domains
push(@plans, Attean::Plan::NestedLoopJoin->new(children => [$lhs, $rhs], anti => 1, join_variables => \@join_vars, distinct => 0, ordered => $lhs->ordered));
} else {
if (scalar(@join_vars) > 0) {
# if there's shared variables (hopefully), we can also use a hash join
push(@plans, Attean::Plan::HashJoin->new(children => [$lhs, $rhs], join_variables => \@join_vars, distinct => 0, ordered => []));
push(@plans, Attean::Plan::HashJoin->new(children => [$rhs, $lhs], join_variables => \@join_vars, distinct => 0, ordered => []));
# } else {
# warn "No join vars for $lhs ⋈ $rhs";
}
# nested loop joins work in all cases
push(@plans, Attean::Plan::NestedLoopJoin->new(children => [$lhs, $rhs], join_variables => \@join_vars, distinct => 0, ordered => $lhs->ordered));
push(@plans, Attean::Plan::NestedLoopJoin->new(children => [$rhs, $lhs], join_variables => \@join_vars, distinct => 0, ordered => $rhs->ordered));
}
}
}
return @plans;
}
sub _comparator_referenced_variables {
my $self = shift;
my %vars;
while (my $c = shift) {
my $expr = $c->expression;
foreach my $v ($expr->in_scope_variables) {
$vars{$v}++;
}
}
return keys %vars;
}
sub _comparators_are_stable_and_cover_vars {
my $self = shift;
my $cmps = shift;
my @vars = @_;
my %unseen = map { $_ => 1 } @vars;
foreach my $c (@$cmps) {
return 0 unless ($c->expression->is_stable);
foreach my $v ($self->_comparator_referenced_variables($c)) {
delete $unseen{$v};
}
}
my @keys = keys %unseen;
return (scalar(@keys) == 0);
}
sub _order_by {
my $self = shift;
my $algebra = shift;
my ($exprs, $ascending, $svars);
my @cmps = @{ $algebra->comparators };
my %ascending;
my %exprs;
my @svars;
foreach my $i (0 .. $#cmps) {
my $var = $self->new_temporary('order');
my $cmp = $cmps[$i];
push(@svars, $var);
$ascending{$var} = $cmp->ascending;
$exprs{$var} = $cmp->expression;
}
return (\%exprs, \%ascending, \@svars);
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/SPARQLClient.pm 000644 000765 000024 00000000224 14636707550 021470 x ustar 00greg staff 000000 000000 29 mtime=1719373672.07360228
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/SPARQLClient.pm 000644 000765 000024 00000007117 14636707550 017527 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::SPARQLClient - RDF blank nodes
=head1 VERSION
This document describes Attean::SPARQLClient version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $client = Attean::SPARQLClient->new(endpoint => 'http://example.org/sparql');
my $results = $client->query('SELECT * WHERE { ?s ?p ?o }');
while (my $r = $results->next) {
say $r->as_string;
}
=head1 DESCRIPTION
The Attean::SPARQLClient class provides an API to execute SPARQL queries
against a remote SPARQL Protocol endpoint.
=head1 ATTRIBUTES
The following attributes exist:
=over 4
=item C<< endpoint >>
A URL of the remote service implementing the SPARQL 1.1 Protocol. This value
is a L, but can be coerced from a string.
=item C<< silent >>
=item << user_agent >>
=item C<< request_signer >>
=back
=head1 METHODS
=over 4
=cut
package Attean::SPARQLClient 0.034 {
use Moo;
use Types::Standard qw(ConsumerOf Bool Str InstanceOf);
use Encode qw(encode);
use Scalar::Util qw(blessed);
use URI::Escape;
use Attean::RDF qw(iri);
use namespace::clean;
has 'endpoint' => (is => 'ro', isa => ConsumerOf['Attean::API::IRI'], coerce => sub { iri(shift) }, required => 1);
has 'silent' => (is => 'ro', isa => Bool, default => 0);
has 'user_agent' => (is => 'rw', isa => InstanceOf['LWP::UserAgent'], default => sub { my $ua = LWP::UserAgent->new(); $ua->agent("Attean/$Attean::VERSION " . $ua->_agent); $ua });
has 'request_signer' => (is => 'rw');
=item C<< query_request( $sparql ) >>
Returns an HTTP::Request object for the given SPARQL query string.
=cut
sub query_request {
my $self = shift;
my $sparql = shift;
my $endpoint = $self->endpoint->value;
my $uri = URI->new($endpoint);
my %params = $uri->query_form;
$params{'query'} = $sparql;
$uri->query_form(%params);
my $url = $uri->as_string;
my $req = HTTP::Request->new('GET', $url);
if (my $signer = $self->request_signer) {
$signer->sign($req);
}
return $req;
}
=item C<< query( $sparql ) >>
Executes the given SPARQL query string at the remote endpoint. If execution is
successful, returns an Attean::API::Iterator object with the results. If
execution fails but the client C<< silent >> flag is true, returns an empty
iterator. Otherwise raises an error via C<< die >>.
=cut
sub query {
my $self = shift;
my $sparql = shift;
my $req = $self->query_request($sparql);
my $silent = $self->silent;
my $ua = $self->user_agent;
my $response = $ua->request($req);
if (blessed($response) and $response->is_success) {
my $type = $response->header('Content-Type');
my $pclass = Attean->get_parser(media_type => $type) or die "No parser for media type: $type";
my $parser = $pclass->new();
my $xml = $response->decoded_content;
my $bytes = encode('UTF-8', $xml, Encode::FB_CROAK);
return $parser->parse_iter_from_bytes($bytes);
} elsif ($silent) {
my $b = Attean::Result->new( bindings => {} );
return Attean::ListIterator->new(variables => [], values => [$b], item_type => 'Attean::API::Result');
} else {
die "SPARQL Protocol error: " . $response->status_line;
}
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
L
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/Result.pm 000644 000765 000024 00000000225 14636707550 020606 x ustar 00greg staff 000000 000000 30 mtime=1719373672.038392468
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/Result.pm 000644 000765 000024 00000004617 14636707550 016646 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::Result - SPARQL Result
=head1 VERSION
This document describes Attean::Result version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $result = Attean::Result->new(bindings => { name => $literal, homepage => $iri } );
my @vars = $result->variables; # ('name', 'homepage')
my $term = $result->value('name'); # $term == $literal
=head1 DESCRIPTION
The Attean::Result class represents a SPARQL result (a set of bindings from
variable names to Ls).
It conforms to the L role.
=head1 METHODS
=over 4
=cut
package Attean::Result 0.034 {
use Moo;
use Types::Standard qw(HashRef ConsumerOf);
use Attean::API::Binding;
use namespace::clean;
with 'Attean::API::Result';
=item C<< bindings >>
Returns the HASH reference containing the variable bindings for this result.
=cut
has 'bindings' => (is => 'ro', isa => HashRef[ConsumerOf['Attean::API::TermOrTriple']], default => sub { +{} });
# sub BUILD {
# my $self = shift;
# my $args = shift;
# use Data::Dumper;
# my $b = $args->{bindings};
# my $keys = [keys %$b];
# if (scalar(@$keys) == 2) {
# Carp::cluck 'NEW RESULT CONSTRUCTED with variables ' . Dumper($keys);
# }
# }
=item C<< value( $name ) >>
Returns the term object bound to the C<< $name >>d variable, or undef if the
name does not map to a term.
=cut
sub value {
my $self = shift;
my $k = shift;
return $self->bindings->{$k};
}
=item C<< variables >>
Returns a list of the variable names that are bound to terms in this result
object.
=cut
sub variables {
my $self = shift;
return keys %{ $self->bindings };
}
=item C<< as_string >>
Returns a string serialization of the variable bindings contained in the result.
=cut
sub as_string {
my $self = shift;
my @vars = $self->variables;
my @strs = map { join('=', $_, $self->value($_)->ntriples_string) } sort $self->variables;
return '{' . join(', ', @strs) . '}';
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/API.pm 000644 000765 000024 00000000225 14636707547 017747 x ustar 00greg staff 000000 000000 30 mtime=1719373671.762572982
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/API.pm 000644 000765 000024 00000015647 14636707547 016014 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::API - Utility package for loading all Attean role packages.
=head1 VERSION
This document describes Attean::API version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
=head1 DESCRIPTION
This is a utility package that will load all the Attean-related Moo roles
in the Attean::API namespace.
=head1 METHODS
=over 4
=cut
package Attean::API::ResultOrTerm 0.034 {
use Moo::Role;
}
package Attean::API::BlankOrIRI 0.034 {
use Moo::Role;
with 'Attean::API::Term', 'Attean::API::BlankOrIRIOrTriple';
}
package Attean::API::BlankOrIRIOrTriple 0.034 {
use Moo::Role;
}
package Attean::API::TermOrTriple 0.034 {
use Moo::Role;
}
package Attean::API::TermOrVariable 0.034 {
use Scalar::Util qw(blessed);
use Sub::Install;
use Sub::Util qw(set_subname);
use Moo::Role;
with 'Attean::API::SPARQLSerializable';
sub equals {
my ($a, $b) = @_;
return ($a->as_string eq $b->as_string);
}
sub is_bound {
my $self = shift;
return (! $self->does('Attean::API::Variable'));
}
sub apply_binding {
my $self = shift;
my $class = ref($self);
my $bind = shift;
if ($self->does('Attean::API::Variable')) {
my $name = $self->value;
my $replace = $bind->value($name);
if (defined($replace) and blessed($replace)) {
return $replace;
} else {
return $self;
}
} else {
return $self;
}
}
BEGIN {
my %types = (
variable => 'Variable',
blank => 'Blank',
literal => 'Literal',
resource => 'IRI',
iri => 'IRI',
);
while (my ($name, $role) = each(%types)) {
my $method = "is_$name";
my $code = sub { return shift->does("Attean::API::$role") };
Sub::Install::install_sub({
code => set_subname($method, $code),
as => $method
});
}
}
}
package Attean::API::TermOrVariableOrTriplePattern 0.034 {
use Scalar::Util qw(blessed);
use Sub::Install;
use Sub::Util qw(set_subname);
use Moo::Role;
with 'Attean::API::SPARQLSerializable';
sub is_bound {
my $self = shift;
return (! $self->does('Attean::API::Variable'));
}
sub apply_binding {
my $self = shift;
my $class = ref($self);
my $bind = shift;
if ($self->does('Attean::API::Variable')) {
my $name = $self->value;
my $replace = $bind->value($name);
if (defined($replace) and blessed($replace)) {
return $replace;
} else {
return $self;
}
} else {
return $self;
}
}
BEGIN {
my %types = (
variable => 'Variable',
blank => 'Blank',
literal => 'Literal',
resource => 'IRI',
iri => 'IRI',
pattern => 'TriplePattern'
);
while (my ($name, $role) = each(%types)) {
my $method = "is_$name";
my $code = sub { return shift->does("Attean::API::$role") };
Sub::Install::install_sub({
code => set_subname($method, $code),
as => $method
});
}
}
}
package Attean::Mapper 0.034 {
use Moo::Role;
requires 'map'; # my $that = $object->map($this)
}
package Attean::API::Variable 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Moo::Role;
with 'Attean::API::TermOrVariable';
=item C<< as_string >>
Returns a string representation of the variable.'
=cut
sub as_string {
my $self = shift;
return '?' . $self->value;
}
sub sparql_tokens {
my $self = shift;
my $t = AtteanX::SPARQL::Token->fast_constructor( VAR, -1, -1, -1, -1, [$self->value] );
return Attean::ListIterator->new( values => [$t], item_type => 'AtteanX::SPARQL::Token' );
}
}
package Attean::API::CanonicalizingBindingSet 0.034 {
use Attean::RDF;
use Moo::Role;
use namespace::clean;
with 'MooX::Log::Any';
requires 'elements';
sub canonical_set {
my $self = shift;
my ($set) = $self->canonical_set_with_mapping;
return $set;
}
sub canonical_set_with_mapping {
my $self = shift;
my @t = $self->elements;
my @tuples = map { [ $_->tuples_string, $_, {} ] } @t;
my $replacements = 0;
foreach my $p (@tuples) {
my ($str, $t) = @$p;
foreach my $pos ($t->variables) {
my $term = $t->value($pos);
my $tstr = $term->ntriples_string;
if ($term->does('Attean::API::Blank') or $term->does('Attean::API::Variable')) {
$str =~ s/\Q$tstr\E/~/;
$str .= "#$tstr";
$p->[2]{$pos} = $tstr;
$replacements++;
$p->[0] = $str;
}
}
}
@tuples = sort { $a->[0] cmp $b->[0] } @tuples;
my $counter = 1;
my %mapping;
foreach my $i (0 .. $#tuples) {
my $p = $tuples[$i];
my ($str, $t) = @$p;
my $item_class = ref($t);
my ($next, $last) = ('')x2;
$last = $tuples[$i-1][0] if ($i > 0);
$next = $tuples[$i+1][0] if ($i < $#tuples);
next if ($str eq $last or $str eq $next);
foreach my $pos (reverse $t->variables) {
if (defined(my $tstr = $p->[2]{$pos})) {
$tstr =~ /^([?]|_:)([^#]+)$/;
my $prefix = $1;
my $name = $2;
my $key = "$prefix$name";
delete $p->[2]{$pos};
my $id = (exists($mapping{$key})) ? $mapping{$key}{id} : sprintf("v%03d", $counter++);
my $type = ($prefix eq '?' ? 'variable' : 'blank');
$mapping{ $key } = { id => $id, prefix => $prefix, type => $type };
my %t = $p->[1]->mapping;
$t{ $pos } = ($type eq 'blank') ? Attean::Blank->new($id) : Attean::Variable->new($id);
my $t = $item_class->new( %t );
$p->[1] = $t;
$p->[0] = $t->tuples_string;
}
}
}
foreach my $p (@tuples) {
my ($str, $t) = @$p;
my $item_class = ref($t);
foreach my $pos (reverse $t->variables) {
if (defined(my $tstr = $p->[2]{$pos})) {
$tstr =~ /^([?]|_:)([^#]+)$/;
my $prefix = $1;
my $name = $2;
my $key = "$prefix$name";
delete $p->[2]{$pos};
unless (exists($mapping{$key})) {
$self->error("Cannot canonicalize binding set");
return;
}
my $id = $mapping{$key}{id};
my $type = ($prefix eq '?' ? 'variable' : 'blank');
$mapping{ $key } = { id => $id, prefix => $prefix, type => $type };
my %t = $p->[1]->mapping;
$t{ $pos } = ($type eq 'blank') ? Attean::Blank->new($id) : Attean::Variable->new($id);
my $t = $item_class->new( %t );
$p->[1] = $t;
$p->[0] = $t->tuples_string;
}
}
}
@tuples = sort { $a->[0] cmp $b->[0] } @tuples;
my $elements = [ map { $_->[1] } @tuples ];
return ($elements, \%mapping);
}
}
package Attean::API 0.034 {
use Attean::API::Term;
use Attean::API::Store;
use Attean::API::Model;
use Attean::API::Iterator;
use Attean::API::Parser;
use Attean::API::Serializer;
use Attean::API::Query;
use Attean::API::Expression;
use Attean::API::Plan;
use Attean::API::QueryPlanner;
use Attean::Variable;
use Attean::Blank;
use Attean::IRI;
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/Variable.pm 000644 000765 000024 00000000225 14636707550 021055 x ustar 00greg staff 000000 000000 30 mtime=1719373672.152792005
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/Variable.pm 000644 000765 000024 00000003275 14636707550 017114 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::Variable - Pattern matching variables
=head1 VERSION
This document describes Attean::Variable version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $term = Attean::Variable->new('name');
$term->ntriples_string; # ?name
=head1 DESCRIPTION
The Attean::Variable class represents variables for use in pattern matching.
It conforms to the L role.
=head1 ATTRIBUTES
=over 4
=item C<< value >>
=item C<< ntriples_string >>
=back
=cut
package Attean::Variable 0.034 {
use Moo;
use Types::Standard qw(Str);
use UUID::Tiny ':std';
use namespace::clean;
has 'value' => (is => 'ro', isa => Str, required => 1);
has 'ntriples_string' => (is => 'ro', isa => Str, lazy => 1, builder => '_ntriples_string');
with 'Attean::API::Variable';
with 'Attean::API::TermOrVariable';
with 'Attean::API::TermOrVariableOrTriplePattern';
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
if (scalar(@_) == 0) {
my $uuid = unpack('H*', create_uuid());
return $class->$orig(value => 'v' . $uuid);
} elsif (scalar(@_) == 1) {
return $class->$orig(value => shift);
}
return $class->$orig(@_);
};
sub _ntriples_string {
my $self = shift;
return '?' . $self->value;
}
}
1;
__END__
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/TripleModel.pm 000644 000765 000024 00000000225 14636707550 021550 x ustar 00greg staff 000000 000000 30 mtime=1719373672.137284108
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/TripleModel.pm 000644 000765 000024 00000022547 14636707550 017612 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::TripleModel - RDF model backed by a set of triple-stores
=head1 VERSION
This document describes Attean::TripleModel version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $model = Attean::TripleModel->new( stores => {
'http://example.org/graph1' => $store1,
'http://example.org/graph2' => $store2,
} );
=head1 DESCRIPTION
The Attean::TripleModel class represents a model that is backed by a set of
L objects, identified by an IRI
string. It conforms to the L role.
The Attean::TripleModel constructor requires one named argument:
=over 4
=item stores
A hash mapping graph IRI values to L
objects representing the backing triple-store for that graph.
=back
=head1 METHODS
=over 4
=cut
package Attean::TripleModel 0.034 {
use Moo;
use Types::Standard qw(ArrayRef ConsumerOf HashRef);
use Scalar::Util qw(reftype blessed);
use namespace::clean;
with 'MooX::Log::Any';
with 'Attean::API::Model';
with 'Attean::API::CostPlanner';
has 'stores' => (
is => 'ro',
isa => HashRef[ConsumerOf['Attean::API::TripleStore']],
required => 1,
default => sub { +{} },
);
=item C<< size >>
=cut
sub size {
my $self = shift;
my $count = 0;
foreach my $store (values %{ $self->stores }) {
$count += $store->size;
}
return $count;
}
=item C<< count_quads >>
=cut
sub count_quads {
my $self = shift;
# TODO: don't materialize results here just to count them
my $iter = $self->get_quads( @_ );
my $count = 0;
while (my $r = $iter->next) {
$count++;
}
return $count;
}
=item C<< count_quads_estimate >>
=cut
sub count_quads_estimate {
my $self = shift;
my ($s, $p, $o, $g) = @_;
if (blessed($g) and $g->does('Attean::API::IRI')) {
if (my $store = $self->stores->{ $g->value }) {
return $store->count_quads_estimate(@_);
} else {
return 0;
}
} else {
return $self->count_quads(@_);
}
}
=item C<< holds >>
=cut
sub holds {
my $self = shift;
return ($self->count_quads_estimate(@_) > 0)
}
=item C<< get_graphs >>
=cut
sub get_graphs {
my $self = shift;
my @graphs = map { Attean::IRI->new($_) } keys %{ $self->stores };
return Attean::ListIterator->new( values => \@graphs, item_type => 'Attean::API::Term' );
}
=item C<< get_quads ( $subject, $predicate, $object, $graph ) >>
Returns an L for quads in the model that match the
supplied C<< $subject >>, C<< $predicate >>, C<< $object >>, and C<< $graph >>.
Any of these terms may be undefined or a L object, in
which case that term will be considered as a wildcard for the purposes of
matching.
The returned iterator conforms to both L and
L.
=cut
sub get_quads {
my $self = shift;
my @nodes = @_[0..3];
foreach my $i (0..3) {
my $t = $nodes[$i] // Attean::Variable->new();
if (not(ref($t)) or reftype($t) ne 'ARRAY') {
$nodes[$i] = [$t];
}
}
my @iters;
foreach my $s (@{ $nodes[0] }) {
foreach my $p (@{ $nodes[1] }) {
foreach my $o (@{ $nodes[2] }) {
foreach my $g (@{ $nodes[3] }) {
my $iter = $self->_get_quads($s, $p, $o, $g);
push(@iters, $iter);
}
}
}
}
if (scalar(@iters) <= 1) {
return shift(@iters);
} else {
return Attean::IteratorSequence->new( iterators => \@iters, item_type => $iters[0]->item_type );
}
}
sub _get_quads {
my $self = shift;
my $s = shift;
my $p = shift;
my $o = shift;
my $g = shift;
if (blessed($g) and $g->does('Attean::API::IRI')) {
if (my $store = $self->stores->{ $g->value }) {
my $iter = $store->get_triples($s, $p, $o);
return $iter->as_quads($g);
}
} elsif (blessed($g) and $g->does('Attean::API::Variable')) {
my @iters;
while (my ($g, $store) = each %{ $self->stores }) {
my $iter = $store->get_triples($s, $p, $o);
my $graph = Attean::IRI->new($g);
my $quads = $iter->map(sub { $_->as_quad($graph) }, 'Attean::API::Quad');
push(@iters, $quads);
}
my $iter = Attean::IteratorSequence->new( iterators => \@iters, item_type => $iters[0]->item_type );
return $iter;
} else {
my $name = (blessed($g) and $g->can('as_string')) ? $g->as_string : "$g";
$self->log->warn("TripleModel cannot produce quads for non-IRI graph: $name");
}
return Attean::ListIterator->new( values => [], item_type => 'Attean::API::Quad' );
}
=item C<< plans_for_algebra( $algebra, $planner, $active_graphs, $default_graphs ) >>
Delegates to an underlying store if the active graph is bound to the store,
and the store consumes Attean::API::CostPlanner.
=cut
sub plans_for_algebra {
my $self = shift;
my $algebra = shift;
my $planner = shift;
my $active_graphs = shift;
my $default_graphs = shift;
my @plans;
if (scalar(@$active_graphs) == 1) {
my $graph = $active_graphs->[0];
if (my $store = $self->stores->{ $graph->value }) {
if ($store->does('Attean::API::CostPlanner')) {
push(@plans, $store->plans_for_algebra($algebra, $planner, $active_graphs, $default_graphs));
}
}
}
return @plans;
}
=item C<< cost_for_plan( $plan ) >>
Attempts to delegate to all the underlying stores if that store consumes Attean::API::CostPlanner.
=cut
sub cost_for_plan {
my $self = shift;
my $plan = shift;
foreach my $store (values %{ $self->stores }) {
if ($store->does('Attean::API::CostPlanner')) {
if (defined(my $cost = $store->cost_for_plan($plan, @_))) {
return $cost;
}
}
}
return;
}
}
package Attean::AddativeTripleModelRole 0.034 {
use Scalar::Util qw(blessed);
use Types::Standard qw(CodeRef);
use Moo::Role;
with 'Attean::API::Model';
has 'store_constructor' => (is => 'ro', isa => CodeRef, required => 1);
=item C<< add_store( $graph => $store ) >>
Add the L C<< $store >> object to the model using the
IRI string value C<< $graph >> as the graph name.
=cut
sub add_store {
my $self = shift;
my $graph = shift;
my $iri = blessed($graph) ? $graph->value : $graph;
my $store = shift;
die if exists $self->stores->{ $iri };
$self->stores->{ $iri } = $store;
}
=item C<< create_graph( $graph ) >>
Create a new L and add it to the model using the
L C<< $graph >> as the graph name.
The store is constructed by using this object's C<< store_constructor >>
attribute:
my $store = $self->store_constructor->($graph);
=cut
sub create_graph {
my $self = shift;
my $graph = shift;
my $iri = $graph->value;
return if exists $self->stores->{ $iri };
my $store = $self->store_constructor->($graph);
$self->stores->{ $iri } = $store;
};
=item C<< drop_graph( $graph ) >>
Removes the store associated with the given C<< $graph >>.
=cut
sub drop_graph {
my $self = shift;
my $g = shift;
if ($g->does('Attean::API::IRI')) {
delete $self->stores->{ $g->value };
}
}
}
package Attean::MutableTripleModel 0.034 {
use Moo;
use Types::Standard qw(ArrayRef ConsumerOf HashRef);
use Scalar::Util qw(reftype);
use namespace::clean;
extends 'Attean::TripleModel';
with 'Attean::API::MutableModel';
has 'stores' => (
is => 'ro',
isa => HashRef[ConsumerOf['Attean::API::MutableTripleStore']],
required => 1,
default => sub { +{} },
);
=item C<< add_quad ( $quad ) >>
Adds the specified C<$quad> to the underlying model.
=cut
sub add_quad {
my $self = shift;
my $q = shift;
my $g = $q->graph;
die "Cannot add a quad whose graph is not an IRI" unless ($g->does('Attean::API::IRI'));
my $v = $g->value;
if (my $store = $self->stores->{ $v }) {
$store->add_triple( $q->as_triple );
} else {
Carp::confess "No such graph: $v";
}
}
=item C<< remove_quad ( $quad ) >>
Removes the specified C<< $quad >> from the underlying store.
=cut
sub remove_quad {
my $self = shift;
my $q = shift;
my $g = $q->graph;
if ($g->does('Attean::API::IRI')) {
my $v = $g->value;
if (my $store = $self->stores->{ $v }) {
$store->remove_triple( $q->as_triple );
}
}
}
sub create_graph { die; }
=item C<< drop_graph( $graph ) >>
Removes the store associated with the given C<< $graph >>.
=cut
sub drop_graph {
my $self = shift;
my $g = shift;
if ($g->does('Attean::API::IRI')) {
delete $self->stores->{ $g->value };
}
}
=item C<< clear_graph( $graph ) >>
Removes all quads with the given C<< $graph >>.
=cut
sub clear_graph {
my $self = shift;
my $g = shift;
$self->drop_graph($g);
$self->create_graph($g);
}
}
package Attean::AddativeTripleModel 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Types::Standard qw(CodeRef);
use namespace::clean;
extends 'Attean::TripleModel';
with 'Attean::AddativeTripleModelRole';
}
package Attean::AddativeMutableTripleModel 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Types::Standard qw(CodeRef);
use namespace::clean;
extends 'Attean::MutableTripleModel';
with 'Attean::AddativeTripleModelRole';
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/IteratorSequence.pm 000644 000765 000024 00000000224 14636707547 022617 x ustar 00greg staff 000000 000000 29 mtime=1719373671.89898414
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/IteratorSequence.pm 000644 000765 000024 00000004515 14636707547 020655 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::IteratorSequence - Iterator implementation backed by zero or more sub-iterators
=head1 VERSION
This document describes Attean::IteratorSequence version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $iter = Attean::IteratorSequence->new(iterators => [$iter1, $iter2]);
=head1 DESCRIPTION
The Attean::IteratorSequence class represents a typed iterator that is backed
by zero or more sub-iterators. When iterated over, it will return all the
elements of all of its sub-iterators, in order, before returning undef.
It conforms to the L role.
The Attean::IteratorSequence constructor requires two named arguments:
=over 4
=item iterators
An array reference containing zero or more L objects.
=item item_type
A string representing the type of the items that will be returned from the
iterator.
=back
=head1 METHODS
=over 4
=cut
package Attean::IteratorSequence 0.034 {
use Moo;
use Types::Standard qw(ArrayRef ConsumerOf);
use namespace::clean;
with 'Attean::API::Iterator';
has iterators => (is => 'ro', isa => ArrayRef[ConsumerOf['Attean::API::Iterator']], default => sub { [] });
=item C<< next >>
Returns the iterator's next item, or undef upon reaching the end of iteration.
=cut
sub next {
my $self = shift;
my $list = $self->iterators;
while (1) {
return unless (scalar(@$list));
my $iter = $list->[0];
my $item = $iter->next;
unless (defined($item)) {
shift(@$list);
next;
}
return $item;
}
}
=item C<< push( $iterator ) >>
Adds the new C<< $iterator >> to the end of the array of sub-iterators.
After this call, C<< $iterator >> will be owned by the IteratorSequence,
so making any method calls on C<< $iterator >> after this point may produce
unexpected results.
=cut
sub push {
my $self = shift;
my $iter = shift;
push(@{ $self->iterators }, $iter);
return;
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/RDF.pm 000644 000765 000024 00000000225 14636707550 017743 x ustar 00greg staff 000000 000000 30 mtime=1719373672.022275896
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/RDF.pm 000644 000765 000024 00000006103 14636707550 015773 0 ustar 00greg staff 000000 000000 =head1 NAME
Attean::RDF - Utility package for exporting shorthand functions for constructing RDF objects
=head1 VERSION
This document describes Attean::RDF version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean::RDF;
my $s = blank('b');
my $p = iri('http://xmlns.com/foaf/0.1/name');
my $o = langliteral("Eve", "en");
my $triple = triple($s, $p, $o);
say $triple->as_string; # _:b "Eve"@en .
=head1 DESCRIPTION
This is a utility package for exporting shorthand functions for constructing
RDF objects such as IRIs, Literals, Blanks, Triples, etc.
=head1 FUNCTIONS
All of the functions defined in this package may be exported (and are exported
by default).
=over 4
=cut
package Attean::RDF 0.034 {
use v5.14;
use warnings;
require Exporter::Tiny;
our @ISA = qw(Exporter::Tiny);
our @EXPORT = qw(iri blank literal dtliteral langliteral variable triple quad triplepattern quadpattern bgp);
require Attean;
use List::MoreUtils qw(zip);
use namespace::clean;
=item C<< variable( $value ) >>
C<< Attean::Variable->new($value) >>
=cut
sub variable {
return Attean::Variable->new(@_);
}
=item C<< iri( $value ) >>
C<< Attean::IRI->new($value) >>
=cut
sub iri {
return Attean::IRI->new(@_);
}
=item C<< blank( $value ) >>
C<< Attean::Blank->new($value) >>
=cut
sub blank {
return Attean::Blank->new(@_);
}
=item C<< literal( $value ) >>
C<< Attean::Literal->new($value) >>
=cut
sub literal {
return Attean::Literal->new(@_);
}
=item C<< dtliteral( $value, $dt ) >>
C<< Attean::Literal->new( value => $value, datatype => $dt ) >>
=cut
sub dtliteral {
my @k = qw(value datatype);
return Attean::Literal->new(zip @k, @_);
}
=item C<< langliteral( $value, $lang ) >>
C<< Attean::Literal->new( value => $value, language => $lang ) >>
=cut
sub langliteral {
my @k = qw(value language);
return Attean::Literal->new(zip @k, @_);
}
=item C<< triple( @terms ) >>
C<< Attean::Triple->new( @terms ) >>
=cut
sub triple {
return Attean::Triple->new(@_);
}
=item C<< triplepattern( @terms ) >>
C<< Attean::TriplePattern->new( @terms ) >>
=cut
sub triplepattern {
return Attean::TriplePattern->new(@_);
}
=item C<< quad( @terms ) >>
C<< Attean::Quad->new( @terms ) >>
=cut
sub quad {
return Attean::Quad->new(@_);
}
=item C<< quadpattern( @terms ) >>
C<< Attean::QuadPattern->new( @terms ) >>
=cut
sub quadpattern {
return Attean::QuadPattern->new(@_);
}
=item C<< bgp( @triplepatterns ) >>
C<< Attean::Algebra::BGP->new( triples => \@triplepatterns ) >>
=cut
sub bgp {
return Attean::Algebra::BGP->new(triples => \@_);
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
L
L
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/API/ 000755 000765 000024 00000000000 14636711137 015427 5 ustar 00greg staff 000000 000000 Attean-0.034/lib/Attean/PaxHeader/ListIterator.pm 000644 000765 000024 00000000225 14636707547 021763 x ustar 00greg staff 000000 000000 30 mtime=1719373671.914498953
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/ListIterator.pm 000644 000765 000024 00000005235 14636707547 020020 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::ListIterator - Iterator implementation backed by a list/array of values
=head1 VERSION
This document describes Attean::ListIterator version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my @values = map { Attean::Literal->new($_) } (1,2,3);
my $iter = Attean::ListIterator->new(
values => \@values,
item_type => 'Attean::API::Term',
);
say $iter->next->value; # 1
say $iter->next->value; # 2
say $iter->next->value; # 3
=head1 DESCRIPTION
The Attean::ListIterator class represents a typed iterator.
It conforms to the L role.
The Attean::ListIterator constructor requires two named arguments:
=over 4
=item values
An array reference containing the items to iterate over.
=item item_type
A string representing the type of the items that will be returned from the
iterator.
=back
=head1 METHODS
=over 4
=cut
package Attean::ListIterator 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Type::Tiny::Role;
use Types::Standard qw(ArrayRef Int);
use namespace::clean;
has values => (is => 'ro', isa => ArrayRef, required => 1);
has current => (is => 'rw', isa => Int, init_arg => undef, default => 0);
sub BUILD {
my $self = shift;
my $role = $self->item_type;
foreach my $item (@{ $self->values }) {
if (Role::Tiny->is_role($role)) {
die "ListIterator item <$item> is not a $role" unless (blessed($item) and $item->does($role));
}
}
}
=item C<< reset >>
Resets the iterator's internal state so that iteration begins again at the
beginning of the values array.
=cut
sub reset {
my $self = shift;
$self->current(0);
}
=item C<< next >>
Returns the iterator's next item, or undef upon reaching the end of iteration.
=cut
sub next {
my $self = shift;
my $list = $self->values;
my $index = $self->current;
my $item = $list->[$index];
return unless defined($item);
$self->current(1+$index);
return $item;
}
=item C<< size >>
Returns the number of elements still remaining in the iterator until it is
fully consumed or until C<< reset >> is called.
=cut
sub size {
my $self = shift;
return scalar(@{ $self->values }) - $self->current;
}
with 'Attean::API::RepeatableIterator';
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/Triple.pm 000644 000765 000024 00000000225 14636707550 020567 x ustar 00greg staff 000000 000000 30 mtime=1719373672.121386816
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/Triple.pm 000644 000765 000024 00000004174 14636707550 016625 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::Triple - RDF Triples
=head1 VERSION
This document describes Attean::Triple version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $triple = Attean::Triple->new( $s, $p, $o );
=head1 DESCRIPTION
The Attean::Triple class represents an RDF triple.
It conforms to the L role.
=head1 ROLES
This role consumes L.
=head1 METHODS
=over 4
=item C<< subject >>
=item C<< predicate >>
=item C<< object >>
=back
=cut
package Attean::TriplePattern 0.034 {
use Moo;
use Scalar::Util qw(blessed);
use Attean::RDF;
use Attean::API::Binding;
has 'subject' => (is => 'ro', required => 1);
has 'predicate' => (is => 'ro', required => 1);
has 'object' => (is => 'ro', required => 1);
with 'Attean::API::TriplePattern';
sub as_quadpattern {
my $self = shift;
my $graph = shift;
# TODO: deprecate this in favor of as_quad_pattern() provided by Attean::API::TriplePattern
return $self->as_quad_pattern($graph);
}
sub ntriples_string {
my $self = shift;
return join(' ', '<<', (map { $self->$_()->ntriples_string() } qw(subject predicate object)), '>>');
}
}
package Attean::Triple 0.034 {
use Moo;
use Attean::API::Binding;
has 'subject' => (is => 'ro', does => 'Attean::API::BlankOrIRI', required => 1);
has 'predicate' => (is => 'ro', does => 'Attean::API::IRI', required => 1);
has 'object' => (is => 'ro', does => 'Attean::API::Term', required => 1);
with 'Attean::API::Triple';
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
if (scalar(@_) == 3) {
my %args;
@args{ $class->variables } = @_;
return $class->$orig(%args);
}
return $class->$orig(@_);
};
}
1;
__END__
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/TreeRewriter.pm 000644 000765 000024 00000000225 14636707550 021753 x ustar 00greg staff 000000 000000 30 mtime=1719373672.105185292
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/TreeRewriter.pm 000644 000765 000024 00000012261 14636707550 020005 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::TreeRewriter - Walk and rewrite subtrees
=head1 VERSION
This document describes Attean::TreeRewriter version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $w = Attean::TreeRewriter->new();
my ($rewritten, $tree) = $w->rewrite($tree, $thunk);
if ($rewritten) {
...
}
=head1 DESCRIPTION
The Attean::TreeRewriter class walks the nodes of query trees and rewrites
sub-trees based on handlers that have been registered prior to rewriting.
=head1 ROLES
None.
=head1 METHODS
=over 4
=cut
package Attean::TreeRewriter 0.034 {
use Moo;
use Types::Standard qw(CodeRef ArrayRef Str);
use Scalar::Util qw(blessed refaddr);
use namespace::clean;
with 'MooX::Log::Any';
has types => (is => 'rw', isa => ArrayRef[Str], default => sub { ['Attean::API::DirectedAcyclicGraph'] });
has pre_handlers => (is => 'rw', isa => ArrayRef[CodeRef], default => sub { [] });
=item C<< register_pre_handler( \&code ) >>
Register a handler that will be called for each sub-tree during tree rewriting.
The function will be called as C<< &code( $tree, $parent_node, $thunk ) >> where
C<< $thunk >> is an opaque value passed to C<< rewrite >>.
The function must return a list C<< ($handled, $descend, $rewritten) >>.
C<< $handled >> is a boolean indicating whether the handler function rewrote
the sub-tree, which is returned as C<< $rewritten >>. The C<< $descend >>
boolean value indicates whether the the tree rewriting should continue downwards
in the tree.
=cut
sub register_pre_handler {
my $self = shift;
my $code = shift;
push(@{ $self->pre_handlers }, $code);
}
sub _fire_pre_handlers {
my $self = shift;
my ($t, $parent, $thunk) = @_;
my $main_descend = 0;
foreach my $cb (@{ $self->pre_handlers }) {
my ($handled, $descend, $rewritten) = $cb->($t, $parent, $thunk);
unless (defined($descend)) {
$descend = 1;
}
if ($handled) {
return ($descend, $rewritten);
} elsif ($descend) {
$main_descend = 1;
}
}
return ($main_descend, undef);
}
=item C<< rewrite( $tree, $thunk, \%seen, $parent ) >>
Rewrites the given C<< $tree >> using the registered handler functions.
C<< $thunk >> is passed through to each handler function.
C<< %seen >> is currently unused.
C<< $parent >> is passed through to the handler functions as the value of the
pseudo-parent tree node for C<< $tree >>.
Returns a list C<< ($handled, $tree) >> with C<< $handled >> indicating whether
rewriting was performed, with the corresponding rewritten C<< $tree >>.
=cut
sub rewrite {
my $self = shift;
my $tree = shift;
my $thunk = shift;
my $seen = shift || {};
my $parent = shift;
my $ok = 0;
# if ($seen->{ refaddr($tree) }++) {
# return (0, $tree);
# }
foreach my $type (@{ $self->types }) {
if (blessed($tree) and $tree->does($type)) {
$ok++;
}
}
unless ($ok) {
$self->log->debug(ref($tree) . ' does not conform to any rewrite roles');
return (0, $tree);
}
my ($descend, $rewritten) = $self->_fire_pre_handlers($tree, $parent, $thunk);
if ($rewritten) {
if (refaddr($rewritten) == refaddr($tree)) {
return (0, $tree);
}
if ($descend) {
(undef, my $rewritten2) = $self->rewrite($rewritten, $thunk, $seen, $parent);
my $changed = (refaddr($rewritten) != refaddr($rewritten2));
return ($changed, $rewritten2);
} else {
return (1, $rewritten);
}
}
if ($descend) {
my @children;
my %attributes;
my $changed = 0;
if ($tree->does('Attean::API::DirectedAcyclicGraph')) {
my @c = @{ $tree->children };
foreach my $i (0 .. $#c) {
my $p = $c[$i];
my ($childchanged, $child) = $self->rewrite($p, $thunk, $seen, $tree);
push(@children, $childchanged ? $child : $p);
if ($childchanged) {
$self->log->debug("Child $p changed for parent $tree");
$changed = 1;
}
}
}
if ($tree->can('tree_attributes')) {
foreach my $attr ($tree->tree_attributes) {
my $p = $tree->$attr();
if (ref($p) eq 'ARRAY') {
my @patterns;
foreach my $pp (@$p) {
# warn "- $attr: $pp\n";
my ($childchanged, $child) = $self->rewrite($pp, $thunk, $seen, $tree);
if ($childchanged) {
$changed = 1;
}
push(@patterns, $child);
}
$attributes{$attr} = \@patterns;
} else {
# warn "- $attr: $p\n";
my ($childchanged, $child) = $self->rewrite($p, $thunk, $seen, $tree);
$attributes{$attr} = $child;
if ($childchanged) {
$changed = 1;
}
}
}
}
if ($changed) {
my $class = ref($tree);
$rewritten = $class->new( %attributes, children => \@children );
# (undef, $rewritten) = $self->rewrite($rewritten, $thunk, $seen, $parent);
return (1, $rewritten);
}
}
return (0, $tree);
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/Blank.pm 000644 000765 000024 00000000225 14636707547 020365 x ustar 00greg staff 000000 000000 30 mtime=1719373671.795937645
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/Blank.pm 000644 000765 000024 00000003172 14636707547 016420 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::Blank - RDF blank nodes
=head1 VERSION
This document describes Attean::Blank version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $term = Attean::Blank->new('b1');
$term->ntriples_string; # _:b1
=head1 DESCRIPTION
The Attean::Blank class represents RDF blank nodes.
It conforms to the L role.
=head1 ROLES
This role consumes L, which provides the following methods:
=over 4
=item C<< value >>
=back
=cut
package Attean::Blank 0.034 {
use Moo;
use Types::Standard qw(Str);
use UUID::Tiny ':std';
use namespace::clean;
has 'value' => (is => 'ro', isa => Str, required => 1);
has 'ntriples_string' => (is => 'ro', isa => Str, lazy => 1, builder => '_ntriples_string');
with 'Attean::API::Blank';
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
if (scalar(@_) == 0) {
my $uuid = unpack('H*', create_uuid());
return $class->$orig(value => 'b' . $uuid);
} elsif (scalar(@_) == 1) {
my $value = shift // '';
return $class->$orig(value => $value);
}
return $class->$orig(@_);
};
sub _ntriples_string {
my $self = shift;
return '_:' . $self->value;
}
}
1;
__END__
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/Expression.pm 000644 000765 000024 00000000225 14636707547 021475 x ustar 00greg staff 000000 000000 30 mtime=1719373671.837015926
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/Expression.pm 000644 000765 000024 00000030455 14636707547 017534 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::Expression - SPARQL Expressions
=head1 VERSION
This document describes Attean::Expression version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $binding = Attean::Result->new();
my $value = Attean::ValueExpression->new( value => Attean::Literal->integer(2) );
my $plus = Attean::BinaryExpression->new( children => [$value, $value], operator => '+' );
my $result = $plus->evaluate($binding);
say $result->numeric_value; # 4
=head1 DESCRIPTION
This is a utility package that defines all the Attean SPARQL expression classes
consisting of logical, numeric, and function operators, constant terms, and
variables. Expressions may be evaluated in the context of a
L object, and either return a L object
or throw a type error exception.
The expression classes are:
=over 4
=cut
use Attean::API::Expression;
=item * L
=cut
package Attean::ValueExpression 0.034 {
use Moo;
use Types::Standard qw(ConsumerOf);
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use namespace::clean;
with 'Attean::API::SPARQLSerializable';
with 'Attean::API::Expression';
has 'value' => (is => 'ro', isa => ConsumerOf['Attean::API::TermOrVariableOrTriplePattern']);
sub arity { return 0 }
sub BUILDARGS {
my $class = shift;
return $class->SUPER::BUILDARGS(@_, operator => '_value');
}
sub tree_attributes { return qw(operator) }
sub is_stable {
return 1;
}
sub as_string {
my $self = shift;
my $str = $self->value->ntriples_string;
if ($str =~ m[^"(true|false)"\^\^$]) {
return $1;
} elsif ($str =~ m[^"(\d+)"\^\^$]) {
return $1
}
return $str;
}
sub in_scope_variables {
my $self = shift;
if ($self->value->does('Attean::API::Variable')) {
return $self->value->value;
}
return;
}
sub sparql_tokens {
my $self = shift;
return $self->value->sparql_tokens;
}
sub unaggregated_variables {
my $self = shift;
if ($self->value->does('Attean::API::Variable')) {
return $self->value;
}
return;
}
}
=item * L
=cut
package Attean::UnaryExpression 0.034 {
use Moo;
use Types::Standard qw(Enum);
use namespace::clean;
with 'Attean::API::UnaryExpression', 'Attean::API::Expression', 'Attean::API::UnaryQueryTree';
my %map = ('NOT' => '!');
around 'BUILDARGS' => sub {
my $orig = shift;
my $class = shift;
my $args = $class->$orig(@_);
my $op = $args->{operator};
$args->{operator} = $map{uc($op)} if (exists $map{uc($op)});
return $args;
};
sub BUILD {
my $self = shift;
state $type = Enum[qw(+ - !)];
$type->assert_valid($self->operator);
}
sub tree_attributes { return qw(operator) }
sub is_stable {
my $self = shift;
foreach my $c (@{ $self->children }) {
return 0 unless ($c->is_stable);
}
return 1;
}
}
=item * L
=cut
package Attean::BinaryExpression 0.034 {
use Moo;
use Types::Standard qw(Enum);
use namespace::clean;
with 'Attean::API::BinaryExpression';
sub BUILD {
my $self = shift;
state $type = Enum[qw(+ - * / < <= > >= != = && ||)];
$type->assert_valid($self->operator);
}
sub tree_attributes { return qw(operator) }
sub is_stable {
my $self = shift;
foreach my $c (@{ $self->children }) {
return 0 unless ($c->is_stable);
}
return 1;
}
}
=item * L
=cut
package Attean::FunctionExpression 0.034 {
use Moo;
use Types::Standard qw(Enum ConsumerOf HashRef);
use Types::Common::String qw(UpperCaseStr);
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use namespace::clean;
has 'operator' => (is => 'ro', isa => UpperCaseStr, coerce => UpperCaseStr->coercion, required => 1);
has 'base' => (is => 'rw', isa => ConsumerOf['Attean::IRI'], predicate => 'has_base');
with 'Attean::API::NaryExpression';
with 'Attean::API::SPARQLSerializable';
around 'BUILDARGS' => sub {
my $orig = shift;
my $class = shift;
my $args = $class->$orig(@_);
if ($args->{operator} eq 'ISURI') {
$args->{operator} = 'ISIRI';
}
$args->{operator} = UpperCaseStr->coercion->($args->{operator});
return $args;
};
sub BUILD {
my $self = shift;
state $type = Enum[qw(INVOKE IN NOTIN STR LANG LANGMATCHES DATATYPE BOUND IRI URI BNODE RAND ABS CEIL FLOOR ROUND CONCAT SUBSTR STRLEN REPLACE UCASE LCASE ENCODE_FOR_URI CONTAINS STRSTARTS STRENDS STRBEFORE STRAFTER YEAR MONTH DAY HOURS MINUTES SECONDS TIMEZONE TZ NOW UUID STRUUID MD5 SHA1 SHA256 SHA384 SHA512 COALESCE IF STRLANG STRDT SAMETERM ISIRI ISBLANK ISLITERAL ISNUMERIC REGEX TRIPLE ISTRIPLE SUBJECT PREDICATE OBJECT)];
$type->assert_valid($self->operator);
}
sub tree_attributes { return qw(operator) }
sub is_stable {
my $self = shift;
return 0 if ($self->operator =~ m/^(?:RAND|BNODE|UUID|STRUUID|NOW)$/);
foreach my $c (@{ $self->children }) {
return 0 unless ($c->is_stable);
}
return 1;
}
sub sparql_tokens {
my $self = shift;
my $func = AtteanX::SPARQL::Token->keyword($self->operator);
my $lparen = AtteanX::SPARQL::Token->lparen;
my $rparen = AtteanX::SPARQL::Token->rparen;
my $comma = AtteanX::SPARQL::Token->comma;
my @tokens;
push(@tokens, $func, $lparen);
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $comma);
}
if (scalar(@tokens) > 2) {
pop(@tokens); # remove the last comma
}
push(@tokens, $rparen);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
package Attean::AggregateExpression 0.034 {
use Moo;
use Types::Standard qw(Bool Enum Str HashRef ConsumerOf Maybe ArrayRef);
use Types::Common::String qw(UpperCaseStr);
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use namespace::clean;
around 'BUILDARGS' => sub {
my $orig = shift;
my $class = shift;
my $args = $class->$orig(@_);
$args->{operator} = UpperCaseStr->coercion->($args->{operator});
return $args;
};
sub BUILD {
my ($self, $args) = @_;
state $type = Enum[qw(COUNT SUM MIN MAX AVG GROUP_CONCAT SAMPLE RANK CUSTOM FOLD)];
$type->assert_valid(shift->operator);
}
has 'custom_iri' => (is => 'ro', isa => Maybe[Str]);
has 'operator' => (is => 'ro', isa => UpperCaseStr, coerce => UpperCaseStr->coercion, required => 1);
has 'scalar_vars' => (is => 'ro', isa => HashRef, default => sub { +{} });
has 'distinct' => (is => 'ro', isa => Bool, default => 0);
has 'variable' => (is => 'ro', isa => ConsumerOf['Attean::API::Variable'], required => 1);
has 'order' => (is => 'ro', isa => ArrayRef, required => 1, default => sub { [] });
with 'Attean::API::AggregateExpression';
with 'Attean::API::SPARQLSerializable';
sub tree_attributes { return qw(operator scalar_vars variable) }
sub is_stable {
my $self = shift;
foreach my $expr (@{ $self->groups }, values %{ $self->aggregates }) {
return 0 unless ($expr->is_stable);
}
return 1;
}
sub sparql_tokens {
my $self = shift;
my $distinct = AtteanX::SPARQL::Token->keyword('DISTINCT');
my $func = AtteanX::SPARQL::Token->keyword($self->operator);
my $lparen = AtteanX::SPARQL::Token->lparen;
my $rparen = AtteanX::SPARQL::Token->rparen;
my $comma = AtteanX::SPARQL::Token->comma;
my @tokens;
push(@tokens, $func);
push(@tokens, $lparen);
if ($self->distinct) {
push(@tokens, $distinct);
}
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $comma);
}
if (scalar(@tokens) > 2) {
pop(@tokens); # remove the last comma
}
my $vars = $self->scalar_vars;
my @keys = keys %$vars;
if (scalar(@keys)) {
die "TODO: Implement SPARQL serialization for aggregate scalar vars";
}
push(@tokens, $rparen);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
package Attean::CastExpression 0.034 {
use Moo;
use Types::Standard qw(Enum ConsumerOf);
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use namespace::clean;
with 'Attean::API::SPARQLSerializable';
with 'Attean::API::UnaryExpression', 'Attean::API::Expression', 'Attean::API::UnaryQueryTree';
has 'datatype' => (is => 'ro', isa => ConsumerOf['Attean::API::IRI']);
sub BUILDARGS {
my $class = shift;
return $class->SUPER::BUILDARGS(@_, operator => '_cast');
}
sub BUILD {
my $self = shift;
state $type = Enum[map { "http://www.w3.org/2001/XMLSchema#$_" } qw(integer decimal float double string boolean dateTime)];
$type->assert_valid($self->datatype->value);
}
sub tree_attributes { return qw(operator datatype) }
sub is_stable {
my $self = shift;
foreach my $c (@{ $self->children }) {
return 0 unless ($c->is_stable);
}
return 1;
}
sub sparql_tokens {
my $self = shift;
my $dt = AtteanX::SPARQL::Token->fast_constructor( IRI, -1, -1, -1, -1, [$self->datatype->value] ),
my $lparen = AtteanX::SPARQL::Token->lparen;
my $rparen = AtteanX::SPARQL::Token->rparen;
my $comma = AtteanX::SPARQL::Token->comma;
my @tokens;
push(@tokens, $dt, $lparen);
foreach my $t (@{ $self->children }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, $comma);
}
if (scalar(@tokens) > 2) {
pop(@tokens); # remove the last comma
}
push(@tokens, $rparen);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
package Attean::ExistsExpression 0.034 {
use Moo;
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::SPARQLSerializable';
with 'Attean::API::Expression';
sub arity { return 0 }
sub BUILDARGS {
my $class = shift;
return $class->SUPER::BUILDARGS(@_, operator => '_exists');
}
has 'pattern' => (is => 'ro', isa => ConsumerOf['Attean::API::Algebra']);
sub as_string {
my $self = shift;
my $sparql = $self->pattern->as_sparql;
$sparql =~ s/\s+/ /g;
return "EXISTS { $sparql }";
}
sub tree_attributes { return qw(operator pattern) }
sub is_stable {
my $self = shift;
# TODO: need deep analysis of exists pattern to tell if this is stable
# (there might be an unstable filter expression deep inside the pattern)
return 0;
}
sub sparql_tokens {
my $self = shift;
my $exists = AtteanX::SPARQL::Token->keyword('EXISTS');
my $lbrace = AtteanX::SPARQL::Token->lbrace;
my $rbrace = AtteanX::SPARQL::Token->rbrace;
my $child = $self->pattern;
my @tokens;
push(@tokens, $exists, $lbrace);
push(@tokens, $child->sparql_tokens->elements);
push(@tokens, $rbrace);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
sub unaggregated_variables {
my $self = shift;
return map { Attean::Variable->new($_) } $self->pattern->in_scope_variables;
}
}
package Attean::ExistsPlanExpression 0.034 {
use Moo;
use Types::Standard qw(ConsumerOf);
use namespace::clean;
with 'Attean::API::Expression';
sub arity { return 0 }
sub BUILDARGS {
my $class = shift;
return $class->SUPER::BUILDARGS(@_, operator => '_existsplan');
}
has 'plan' => (is => 'ro', isa => ConsumerOf['Attean::API::BindingSubstitutionPlan']);
sub as_string {
my $self = shift;
# TODO: implement as_string for EXISTS patterns
return "Attean::ExistsPlanExpression { ... }";
}
sub as_sparql {
my $self = shift;
my %args = @_;
my $level = $args{level} // 0;
my $sp = $args{indent} // ' ';
my $indent = $sp x $level;
# TODO: implement as_string for EXISTS patterns
return "EXISTS { " . $self->pattern->as_sparql( level => $level+1, indent => $sp ) . " }";
}
sub tree_attributes { return qw(operator plan) }
sub is_stable {
my $self = shift;
# TODO: need deep analysis of exists pattern to tell if this is stable
# (there might be an unstable filter expression deep inside the pattern)
return 0;
}
sub unaggregated_variables {
my $self = shift;
die "unaggregated_variables cannot be called on Attean::ExistsPlanExpression";
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/PaxHeader/CodeIterator.pm 000644 000765 000024 00000000225 14636707604 021714 x ustar 00greg staff 000000 000000 30 mtime=1719373700.032817461
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/CodeIterator.pm 000644 000765 000024 00000004525 14636707604 017752 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::CodeIterator - Iterator implementation backed by a generator function
=head1 VERSION
This document describes Attean::CodeIterator version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
my $iter = Attean::CodeIterator->new(
generator => sub {
state $value = 0;
Attean::Literal->new(++$value)
},
item_type => 'Attean::API::Term',
);
say $iter->next->value; # 1
say $iter->next->value; # 2
say $iter->next->value; # 3
=head1 DESCRIPTION
The Attean::CodeIterator class represents a typed iterator.
It conforms to the L role.
The Attean::CodeIterator constructor requires two named arguments:
=over 4
=item generator
A code reference that when called will return either the iterator's next item,
or undef upon reaching the end of iteration.
=item item_type
A L object representing the type of the items
that will be returned from the iterator.
=back
=head1 METHODS
=over 4
=cut
package Attean::CodeIterator 0.034 {
use Moo;
use Type::Tiny::Role;
use Scalar::Util qw(blessed);
use Types::Standard qw(CodeRef ArrayRef);
use Role::Tiny ();
use namespace::clean;
with 'Attean::API::Iterator';
has generator => (is => 'ro', isa => CodeRef, required => 1);
has _buffer => (is => 'ro', isa => ArrayRef, init_arg => undef, default => sub { [] });
=item C<< next >>
Returns the iterator's next item, or undef upon reaching the end of iteration.
=cut
sub next {
my $self = shift;
my $buffer = $self->_buffer;
if (scalar(@$buffer)) {
return shift(@$buffer);
}
my @items = $self->generator->();
my $item = shift(@items);
return unless defined($item);
if (scalar(@items)) {
push(@$buffer, @items);
}
my $role = $self->item_type;
if (Role::Tiny->is_role($role)) {
unless (blessed($item) and $item->does($role)) {
die "CodeIterator item is not a $role: $item";
}
}
return $item;
}
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/API/PaxHeader/Plan.pm 000644 000765 000024 00000000225 14636707547 020641 x ustar 00greg staff 000000 000000 30 mtime=1719373671.346311642
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/API/Plan.pm 000644 000765 000024 00000015510 14636707547 016673 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
use utf8;
=head1 NAME
Attean::API::Plan - Query plan
=head1 VERSION
This document describes Attean::API::Plan version 0.034
=head1 DESCRIPTION
The Attean::API::Plan role defines a common API for all query plans.
=head1 ATTRIBUTES
=over 4
=item C<< cost >>
=item C<< distinct >>
=item C<< item_type >>
=item C<< in_scope_variables >>
=item C<< ordered >>
=back
=head1 REQUIRED METHODS
The following methods are required by the L role:
=over 4
=item C<< impl( $model ) >>
Returns a code reference that when called (without arguments), returns an
L object.
=back
=head1 METHODS
=over 4
=item C<< has_cost >>
=cut
use Type::Tiny::Role;
package Attean::API::Plan 0.034 {
use Scalar::Util qw(blessed);
use Types::Standard qw(ArrayRef CodeRef Str Object InstanceOf Bool Num Int);
use Moo::Role;
has 'cost' => (is => 'rw', isa => Int, predicate => 'has_cost');
has 'distinct' => (is => 'rw', isa => Bool, required => 1, default => 0);
has 'item_type' => (is => 'ro', isa => Str, required => 1, default => 'Attean::API::Result');
has 'in_scope_variables' => (is => 'ro', isa => ArrayRef[Str], required => 1);
has 'ordered' => (is => 'ro', isa => ArrayRef, required => 1, default => sub { [] });
requires 'impl';
requires 'plan_as_string';
=item C<< as_string >>
Returns a tree-structured string representation of this plan, including children.
=cut
sub as_string {
my $self = shift;
my $string = '';
$self->walk( prefix => sub {
my $a = shift;
my $level = shift;
my $parent = shift;
my $indent = ' ' x $level;
my @flags;
push(@flags, 'distinct') if ($a->distinct);
if (scalar(@{ $a->ordered })) {
my @orders;
foreach my $c (@{ $a->ordered }) {
my $dir = $c->ascending ? "↑" : "↓";
my $s = $dir . $c->expression->as_string;
push(@orders, $s);
}
push(@flags, "order: " . join('; ', @orders));
}
if (defined(my $cost = $a->cost)) {
push(@flags, "cost: $cost");
}
$string .= "-$indent " . $a->plan_as_string($level);
if (scalar(@flags)) {
$string .= ' (' . join(' ', @flags) . ")";
}
$string .= "\n";
});
return $string;
}
=item C<< evaluate( $model ) >>
Evaluates this plan and returns the resulting iterator.
=cut
sub evaluate {
my $self = shift;
my $impl = $self->impl(@_);
return $impl->();
}
=item C<< in_scope_variables_union( @plans ) >>
Returns the set union of C<< in_scope_variables >> of the given plan objects.
=cut
sub in_scope_variables_union {
my @plans = grep { blessed($_) } @_;
my %vars = map { $_ => 1 } map { @{ $_->in_scope_variables } } @plans;
return keys %vars;
}
=item C<< subplans_of_type_are_variable_connected( $type ) >>
Returns true if the subpatterns of the given C<< $type >> are all connected
through their C<< in_scope_variables >>, false otherwise (implying a cartesian
product if the connecting plans perform some form of join.
=cut
sub subplans_of_type_are_variable_connected {
my $self = shift;
my @types = @_;
my @c = $self->subpatterns_of_type(@types);
return $self->_plans_are_variable_connected(@c);
}
=item C<< children_are_variable_connected( $type ) >>
Returns true if the children of this plan are all connected
through their C<< in_scope_variables >>, false otherwise (implying a cartesian
product if this plan performs some form of join.
=cut
sub children_are_variable_connected {
my $self = shift;
my @c = @{ $self->children };
return $self->_plans_are_variable_connected(@c);
}
sub _plans_are_variable_connected {
# TODO: In the worst case, this is going to run in O(n^2) in the number
# of children. Better indexing of the children by variables can speed
# this up.
my $self = shift;
my @c = @_;
# warn "===========================\n";
# foreach my $c (@c) {
# warn $c->as_string;
# }
return 1 unless (scalar(@c));
my %vars_by_child;
foreach my $i (0 .. $#c) {
my $c = $c[$i];
foreach my $var (@{ $c->in_scope_variables }) {
$vars_by_child{$i}{$var}++;
}
}
#
my @remaining = keys %vars_by_child;
return 1 unless (scalar(@remaining));
my $current = shift(@remaining);
# warn 'Starting with ' . $c[$current]->as_string;
my %seen_vars = %{ $vars_by_child{$current} };
LOOP: while (scalar(@remaining)) {
foreach my $i (0 .. $#remaining) {
my $candidate = $remaining[$i];
my @candidate_vars = keys %{ $vars_by_child{$candidate} };
foreach my $var (@candidate_vars) {
if (exists $seen_vars{ $var }) {
foreach my $var (@candidate_vars) {
$seen_vars{$var}++;
}
# warn "connected with $var: " . $c[$candidate]->as_string;
splice(@remaining, $i, 1);
next LOOP;
}
}
}
# warn 'Not fully connected';
return 0;
}
# warn 'Fully connected';
return 1;
}
}
package Attean::API::BindingSubstitutionPlan 0.034 {
use Moo::Role;
with 'Attean::API::Plan';
requires 'substitute_impl'; # $code = $plan->impl($model, $binding);
sub impl {
my $self = shift;
my $model = shift;
my $b = Attean::Result->new();
return $self->substitute_impl($model, $b);
}
}
package Attean::API::UnionScopeVariablesPlan 0.034 {
use Moo::Role;
with 'Attean::API::Plan';
around 'BUILDARGS' => sub {
my $orig = shift;
my $class = shift;
my %args = @_;
my @vars = Attean::API::Plan->in_scope_variables_union( @{ $args{children} } );
if (exists $args{in_scope_variables}) {
Carp::confess "in_scope_variables is computed automatically, and must not be specified in the $class constructor";
}
$args{in_scope_variables} = [@vars];
return $orig->( $class, %args );
};
}
package Attean::API::Plan::Join 0.034 {
use Types::Standard qw(CodeRef);
use Types::Standard qw(ArrayRef Str ConsumerOf Bool);
use Moo::Role;
with 'Attean::API::Plan', 'Attean::API::BinaryQueryTree';
with 'Attean::API::UnionScopeVariablesPlan';
has 'join_variables' => (is => 'ro', isa => ArrayRef[Str], required => 1);
has 'anti' => (is => 'ro', isa => Bool, default => 0); # is this an anti-join
has 'left' => (is => 'ro', isa => Bool, default => 0); # is this a left, outer-join
# if this is a left, outer-join, this is the filter expression that acts as part of the join operation (see the SPARQL semantics for LeftJoin for more details)
has 'expression' => (is => 'ro', isa => ConsumerOf['Attean::API::Expression'], required => 0, default => sub { Attean::ValueExpression->new( value => Attean::Literal->true ) });
}
1;
__END__
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/API/PaxHeader/PushParser.pod 000644 000765 000024 00000000225 14636707547 022211 x ustar 00greg staff 000000 000000 30 mtime=1719373671.378743648
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/API/PushParser.pod 000644 000765 000024 00000004176 14636707547 020251 0 ustar 00greg staff 000000 000000 =head1 NAME
Attean::API::PushParser - Role for parsers that natively call a callback function for each parsed item
=head1 VERSION
This document describes Attean::API::PushParser version 0.034
=head1 DESCRIPTION
The Attean::API::PushParser role defines parsers that can efficiently call a
callback function for each object constructed from the parsed data. This role
adds methods that builds on this functionality to allow parsing data using
different approaches.
=head1 ROLES
This role consumes the L role.
=head1 REQUIRED METHODS
Classes consuming this role must provide the following methods:
=over 4
=item C<< parse_cb_from_io( $fh ) >>
Calls the C<< $parser->handler >> function once for each object that result
from parsing the data read from the L object C<< $fh >>.
=item C<< parse_cb_from_bytes( $data ) >>
Calls the C<< $parser->handler >> function once for each object that result
from parsing the data read from the UTF-8 encoded byte string C<< $data >>.
=back
=head1 METHODS
This role provides default implementations of the following methods:
=over 4
=item C<< parse_iter_from_io( $fh ) >>
Returns an L that result from parsing the data read from
the L object C<< $fh >>.
=item C<< parse_iter_from_bytes( $data ) >>
Returns an L that result from parsing the data read from
the UTF-8 encoded byte string C<< $data >>.
=item C<< parse_list_from_io( $fh ) >>
Returns a list of all objects that result from parsing the data read from the
L object C<< $fh >>.
=item C<< parse_list_from_bytes( $data ) >>
Returns a list of all objects that result from parsing the data read from the
UTF-8 encoded byte string C<< $data >>.
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/API/PaxHeader/Store.pm 000644 000765 000024 00000000225 14636707547 021043 x ustar 00greg staff 000000 000000 30 mtime=1719373671.579067623
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/API/Store.pm 000644 000765 000024 00000011370 14636707547 017075 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::API::Store - Triple/quad store role
=head1 VERSION
This document describes Attean::Store version 0.034
=head1 DESCRIPTION
The Attean::Store role is an empty role that more specialized roles conform to:
=over 4
=item * L
=item * L
=item * L
=item * L
=item * L
=item * L
=item * L
=item * L
=back
=cut
package Attean::API::Store 0.034 {
use Moo::Role;
}
package Attean::API::TripleStore 0.034 {
use Scalar::Util qw(blessed);
use Moo::Role;
with 'Attean::API::Store';
requires 'get_triples';
before 'get_triples' => sub {
if (scalar(@_) == 2 and blessed($_[1]) and not($_[1]->does('Attean::API::TermOrVariable'))) {
my $type = ref($_[0]);
die "get_triples called with a single $type argument, but expecting a list of terms/variables";
}
};
sub count_triples {
my $self = shift;
my $iter = $self->get_triples(@_);
my $count = 0;
while (my $r = $iter->next) {
$count++;
}
return $count;
}
sub count_triples_estimate {
my $self = shift;
return $self->count_triples(@_);
}
sub size {
my $self = shift;
return $self->count_triples();
}
sub holds {
my $self = shift;
return ($self->count_triples_estimate(@_) > 0)
}
}
package Attean::API::MutableTripleStore 0.034 {
use Moo::Role;
with 'Attean::API::TripleStore';
requires 'add_triple';
requires 'remove_triple';
before 'add_triple' => sub {
my $self = shift;
my $quad = shift;
unless ($quad->is_ground) {
die "Cannot add a non-ground triple (with variables) to a model";
}
};
}
package Attean::API::ETagCacheableTripleStore 0.034 {
use Moo::Role;
with 'Attean::API::TripleStore';
requires 'etag_value_for_triples';
}
package Attean::API::TimeCacheableTripleStore 0.034 {
use Moo::Role;
with 'Attean::API::TripleStore';
requires 'mtime_for_triples';
}
package Attean::API::QuadStore 0.034 {
use Scalar::Util qw(blessed);
use Moo::Role;
with 'Attean::API::Store';
requires 'get_quads';
before 'get_quads' => sub {
if (scalar(@_) == 2 and blessed($_[1]) and not($_[1]->does('Attean::API::TermOrVariable'))) {
my $type = ref($_[0]);
die "get_quads called with a single $type argument, but expecting a list of terms/variables";
}
};
sub count_quads {
my $self = shift;
my $iter = $self->get_quads(@_);
my $count = 0;
while (my $r = $iter->next) {
$count++;
}
return $count;
}
sub count_quads_estimate {
my $self = shift;
return $self->count_quads(@_);
}
sub holds {
my $self = shift;
return ($self->count_quads_estimate(@_) > 0)
}
sub get_graphs {
my $self = shift;
my $iter = $self->get_quads(@_);
my %graphs;
while (my $r = $iter->next) {
my $g = $r->graph;
$graphs{ $g->as_string }++;
}
return Attean::ListIterator->new( values => [map { Attean::IRI->new($_) } keys %graphs], item_type => 'Attean::API::Term' );
}
sub size {
my $self = shift;
return $self->count_quads();
}
}
package Attean::API::MutableQuadStore 0.034 {
use Role::Tiny ();
use Moo::Role;
use Type::Tiny::Role;
with 'Attean::API::QuadStore';
requires 'add_quad';
requires 'remove_quad';
requires 'create_graph';
requires 'drop_graph';
requires 'clear_graph';
before 'add_quad' => sub {
my $self = shift;
my $quad = shift;
unless ($quad->is_ground) {
die "Cannot add a non-ground quad (with variables) to a store";
}
};
sub add_iter {
my $self = shift;
my $iter = shift;
my $type = $iter->item_type;
use Data::Dumper;
die "Iterator type $type isn't quads" unless (Role::Tiny::does_role($type, 'Attean::API::Quad'));
while (my $q = $iter->next) {
$self->add_quad($q);
}
}
}
package Attean::API::ETagCacheableQuadStore 0.034 {
use Moo::Role;
with 'Attean::API::QuadStore';
requires 'etag_value_for_quads';
}
package Attean::API::TimeCacheableQuadStore 0.034 {
use Moo::Role;
with 'Attean::API::QuadStore';
requires 'mtime_for_quads';
}
package Attean::API::BulkUpdatableStore 0.034 {
use Moo::Role;
requires 'begin_bulk_updates';
requires 'end_bulk_updates';
}
package Attean::API::RDFStarStore 0.034 {
use Moo::Role;
with 'Attean::API::Store';
}
1;
__END__
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/API/PaxHeader/Result.pod 000644 000765 000024 00000000225 14636707547 021373 x ustar 00greg staff 000000 000000 30 mtime=1719373671.513123378
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/API/Result.pod 000644 000765 000024 00000002131 14636707547 017420 0 ustar 00greg staff 000000 000000 =head1 NAME
Attean::API::Result - Role representing a set of variable bindings
=head1 VERSION
This document describes Attean::API::Result version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
=head1 DESCRIPTION
This is a Moo role representing quad patterns.
=head1 ROLES
This role consumes L.
=head1 METHODS
=over 4
=item C<< join( $result ) >>
Returns the combined variable binding set if the referent and C<< $result >>
are compatible (as defined by the SPARQL semantics), or C<< undef >> otherwise.
=item C<< apply_map( $mapper ) >>
Returns a new variable binding set object with all terms mapped through the
given L object C<< $mapper >>.
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/API/PaxHeader/PullParser.pod 000644 000765 000024 00000000225 14636707547 022206 x ustar 00greg staff 000000 000000 30 mtime=1719373671.362647909
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/API/PullParser.pod 000644 000765 000024 00000004115 14636707547 020237 0 ustar 00greg staff 000000 000000 =head1 NAME
Attean::API::PullParser - Role for parsers that natively return an iterator
=head1 VERSION
This document describes Attean::API::PullParser version 0.034
=head1 DESCRIPTION
The Attean::API::PullParser role defines parsers that can efficiently construct
and return an iterator of the parsed data. This role adds methods that
builds on this functionality to allow parsing data using different approaches.
=head1 ROLES
This role consumes the L role.
=head1 REQUIRED METHODS
Classes consuming this role must provide the following methods:
=over 4
=item C<< parse_iter_from_io( $fh ) >>
Returns an L that result from parsing the data read from
the L object C<< $fh >>.
=item C<< parse_iter_from_bytes( $data ) >>
Returns an L that result from parsing the data read from
the UTF-8 encoded byte string C<< $data >>.
=back
=head1 METHODS
This role provides default implementations of the following methods:
=over 4
=item C<< parse_cb_from_io( $fh ) >>
Calls the C<< $parser->handler >> function once for each object that result
from parsing the data read from the L object C<< $fh >>.
=item C<< parse_cb_from_bytes( $data ) >>
Calls the C<< $parser->handler >> function once for each object that result
from parsing the data read from the UTF-8 encoded byte string C<< $data >>.
=item C<< parse_list_from_io( $fh ) >>
Returns a list of all objects that result from parsing the data read from the
L object C<< $fh >>.
=item C<< parse_list_from_bytes( $data ) >>
Returns a list of all objects that result from parsing the data read from the
UTF-8 encoded byte string C<< $data >>.
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/API/PaxHeader/ResultParser.pod 000644 000765 000024 00000000225 14636707547 022550 x ustar 00greg staff 000000 000000 30 mtime=1719373671.528855178
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/API/ResultParser.pod 000644 000765 000024 00000001716 14636707547 020605 0 ustar 00greg staff 000000 000000 =head1 NAME
Attean::API::ResultParser - Role for parsers of L objects
=head1 VERSION
This document describes Attean::API::ResultParser version 0.034
=head1 DESCRIPTION
The Attean::API::ResultParser role defines parsers of L objects.
=head1 ROLES
This role consumes the L role.
=head1 METHODS
This role provides default implementations of the following methods:
=over 4
=item C<< handled_type >>
Returns a L object for objects which consume the
L role.
=back
=head1 BUGS
Please report any bugs or feature requests to through the GitHub web interface
at L.
=head1 SEE ALSO
=head1 AUTHOR
Gregory Todd Williams C<< >>
=head1 COPYRIGHT
Copyright (c) 2014--2022 Gregory Todd Williams.
This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
Attean-0.034/lib/Attean/API/PaxHeader/Query.pm 000644 000765 000024 00000000225 14636707547 021054 x ustar 00greg staff 000000 000000 30 mtime=1719373671.464103659
64 LIBARCHIVE.xattr.com.apple.TextEncoding=VVRGLTg7MTM0MjE3OTg0
55 SCHILY.xattr.com.apple.TextEncoding=UTF-8;134217984
Attean-0.034/lib/Attean/API/Query.pm 000644 000765 000024 00000042407 14636707547 017113 0 ustar 00greg staff 000000 000000 use v5.14;
use warnings;
=head1 NAME
Attean::API::Query - Utility package defining query-related roles
=head1 VERSION
This document describes Attean::API::Query version 0.034
=head1 SYNOPSIS
use v5.14;
use Attean;
=head1 DESCRIPTION
This is a utility package for defining query-related roles:
=over 4
=item * L
=cut
package Attean::API::DirectedAcyclicGraph 0.034 {
use Scalar::Util qw(refaddr);
use Types::Standard qw(ArrayRef ConsumerOf);
use Moo::Role;
# =item C<< children >>
#
# An ARRAY reference of L objects.
#
# =back
#
# =cut
has 'children' => (
is => 'ro',
isa => ArrayRef[ConsumerOf['Attean::API::DirectedAcyclicGraph']],
default => sub { [] },
);
# =item C<< is_leaf >>
#
# Returns true if the referent has zero C<< children >>, false otherwise.
#
# =cut
sub is_leaf {
my $self = shift;
return not(scalar(@{ $self->children }));
}
# =item C<< walk( prefix => \&pre_cb, postfix => \&pre_cb ) >>
#
# Walks the graph rooted at the referent, calling C<< &pre_cb >> (if supplied)
# before descending, and C<< &post_cb >> (if supplied) after descending. The
# callback functions are passed the current graph walk node as the single
# argument.
#
# =cut
sub walk {
my $self = shift;
my %args = @_;
my $level = $args{ level } // 0;
my $parent = $args{ parent };
if (my $cb = $args{ prefix }) {
$cb->( $self, $level, $parent );
}
foreach my $c (@{ $self->children }) {
$c->walk( %args, level => (1+$level), parent => $self );
}
if (my $cb = $args{ postfix }) {
$cb->( $self, $level, $parent );
}
}
# =item C<< has_only_subtree_types( @classes ) >>
#
# Returns true if the invocant and all of its sub-trees are instances of only
# the listed classes, false otherwise.
#
# =cut
sub has_only_subtree_types {
my $self = shift;
my @types = @_;
my %types = map { $_ => 1 } @types;
return 0 unless (exists $types{ ref($self) });
my %classes;
$self->walk( prefix => sub {
my $plan = shift;
$classes{ref($plan)}++;
});
foreach my $type (@types) {
delete $classes{$type};
}
my @keys = keys %classes;
return (scalar(@keys) == 0) ? 1 : 0;
}
# =item C<< cover( prefix => \&pre_cb, postfix => \&pre_cb ) >>
#
# Similar to C<< walk >>, walks the graph rooted at the referent, calling
# C<< &pre_cb >> (if supplied) before descending, and C<< &post_cb >> (if
# supplied) after descending. However, unlike C<< walk >>, each node in the graph
# is visited only once.
#
# =cut
sub cover {
my $self = shift;
return $self->_cover({}, @_);
}
sub _cover {
my $self = shift;
my $seen = shift;
my %cb = @_;
return if ($seen->{refaddr($self)}++);
if (my $cb = $cb{ prefix }) {
$cb->( $self );
}
foreach my $c (@{ $self->children }) {
$c->_cover( $seen, %cb );
}
if (my $cb = $cb{ postfix }) {
$cb->( $self );
}
}
sub subpatterns_of_type {
my $self = shift;
my @types = @_;
my @p;
$self->walk( prefix => sub {
my $a = shift;
foreach my $t (@types) {
push(@p, $a) if ($a->isa($t) or $a->does($t));
}
});
return @p;
}
}
package Attean::API::SPARQLSerializable 0.034 {
use AtteanX::SPARQL::Constants;
use AtteanX::SPARQL::Token;
use Encode qw(decode_utf8);
use Attean::API::Iterator;
use Attean::API::Serializer;
use AtteanX::Serializer::SPARQL;
use Moo::Role;
requires 'sparql_tokens';
sub as_sparql {
my $self = shift;
my $s = AtteanX::Serializer::SPARQL->new();
my $i = $self->sparql_tokens;
my $bytes = $s->serialize_iter_to_bytes($i);
return decode_utf8($bytes);
}
sub sparql_subtokens {
my $self = shift;
if ($self->does('Attean::API::SPARQLQuerySerializable')) {
my $l = AtteanX::SPARQL::Token->lbrace;
my $r = AtteanX::SPARQL::Token->rbrace;
my @tokens;
push(@tokens, $l);
push(@tokens, $self->sparql_tokens->elements);
push(@tokens, $r);
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
} else {
return $self->sparql_tokens;
}
}
sub dataset_tokens {
my $self = shift;
my $dataset = shift;
my @default = @{ $dataset->{ default } || [] };
my @named = @{ $dataset->{ named } || [] };
my $has_dataset = (scalar(@default) + scalar(@named));
my @tokens;
if ($has_dataset) {
my $from = AtteanX::SPARQL::Token->keyword('FROM');
my $named = AtteanX::SPARQL::Token->keyword('NAMED');
foreach my $i (sort { $a->as_string cmp $b->as_string } @default) {
push(@tokens, $from);
push(@tokens, $i->sparql_tokens->elements);
}
foreach my $i (sort { $a->as_string cmp $b->as_string } @named) {
push(@tokens, $from);
push(@tokens, $named);
push(@tokens, $i->sparql_tokens->elements);
}
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
sub query_tokens {
my $self = shift;
my %args = @_;
my $dataset = $args{dataset} || {};
my $as = AtteanX::SPARQL::Token->keyword('AS');
my $lparen = AtteanX::SPARQL::Token->lparen;
my $rparen = AtteanX::SPARQL::Token->rparen;
my $algebra = $self;
my %modifiers;
my $form = 'SELECT';
if ($algebra->isa('Attean::Algebra::Ask')) {
$form = 'ASK';
($algebra) = @{ $algebra->children };
} elsif ($algebra->isa('Attean::Algebra::Describe')) {
$form = 'DESCRIBE';
$modifiers{describe} = $algebra->terms;
($algebra) = @{ $algebra->children };
} elsif ($algebra->isa('Attean::Algebra::Construct')) {
$form = 'CONSTRUCT';
$modifiers{construct} = $algebra->triples;
($algebra) = @{ $algebra->children };
}
unless ($form eq 'CONSTRUCT' or $form eq 'DESCRIBE') {
while ($algebra->isa('Attean::Algebra::Extend') or $algebra->isa('Attean::Algebra::Group') or $algebra->isa('Attean::Algebra::OrderBy') or $algebra->isa('Attean::Algebra::Distinct') or $algebra->isa('Attean::Algebra::Reduced') or $algebra->isa('Attean::Algebra::Slice') or $algebra->isa('Attean::Algebra::Project')) {
# TODO: Handle HAVING
# TODO: Error if Slice appears before distinct/reduced
if ($algebra->isa('Attean::Algebra::Distinct')) {
$modifiers{ distinct } = 1;
} elsif ($algebra->isa('Attean::Algebra::Reduced')) {
$modifiers{ reduced } = 1;
} elsif ($algebra->isa('Attean::Algebra::Slice')) {
if ($algebra->limit >= 0) {
$modifiers{ limit } = $algebra->limit;
}
if ($algebra->offset > 0) {
$modifiers{ offset } = $algebra->offset;
}
} elsif ($algebra->isa('Attean::Algebra::OrderBy')) {
$modifiers{order} = $algebra->comparators;
} elsif ($algebra->isa('Attean::Algebra::Extend')) {
my $v = $algebra->variable;
my $name = $v->value;
my $expr = $algebra->expression;
my @tokens;
push(@tokens, $lparen);
push(@tokens, $expr->sparql_tokens->elements);
push(@tokens, $as);
push(@tokens, $v->sparql_tokens->elements);
push(@tokens, $rparen);
$modifiers{project_expression_tokens}{$name} = \@tokens;
} elsif ($algebra->isa('Attean::Algebra::Project')) {
my $vars = $algebra->variables;
my ($child) = @{ $algebra->children };
my @vars = sort(map { $_->value } @$vars);
my @subvars = sort($child->in_scope_variables);
if (scalar(@vars) == scalar(@subvars) and join('.', @vars) eq join('.', @subvars)) {
# this is a SELECT * query
} else {
foreach my $v (@$vars) {
my $name = $v->value;
unless ($modifiers{project_variables}{$name}++) {
push(@{ $modifiers{project_variables_order} }, $name);
}
}
}
} elsif ($algebra->isa('Attean::Algebra::Group')) {
my $aggs = $algebra->aggregates;
my $groups = $algebra->groupby;
foreach my $agg (@$aggs) {
my $v = $agg->variable;
my $name = $v->value;
my @tokens;
push(@tokens, $lparen);
push(@tokens, $agg->sparql_tokens->elements);
push(@tokens, $as);
push(@tokens, $v->sparql_tokens->elements);
push(@tokens, $rparen);
unless ($modifiers{project_variables}{$name}++) {
push(@{ $modifiers{project_variables_order} }, $name);
}
$modifiers{project_expression_tokens}{$name} = \@tokens;
}
foreach my $group (@$groups) {
push(@{ $modifiers{groups} }, $group->sparql_tokens->elements);
}
} else {
die "Unexpected pattern type encountered in query_tokens: " . ref($algebra);
}
($algebra) = @{ $algebra->children };
}
}
my @tokens;
my $where = AtteanX::SPARQL::Token->keyword('WHERE');
my $lbrace = AtteanX::SPARQL::Token->lbrace;
my $rbrace = AtteanX::SPARQL::Token->rbrace;
if ($form eq 'SELECT') {
push(@tokens, AtteanX::SPARQL::Token->keyword('SELECT'));
if ($modifiers{distinct}) {
push(@tokens, AtteanX::SPARQL::Token->keyword('DISTINCT'));
} elsif ($modifiers{reduced}) {
push(@tokens, AtteanX::SPARQL::Token->keyword('REDUCED'));
}
if (my $p = $modifiers{project_variables_order}) {
foreach my $name (@$p) {
if (my $etokens = $modifiers{project_expression_tokens}{$name}) {
push(@tokens, @$etokens);
} else {
my $v = Attean::Variable->new( value => $name );
push(@tokens, $v->sparql_tokens->elements);
}
}
} else {
push(@tokens, AtteanX::SPARQL::Token->star);
}
push(@tokens, $self->dataset_tokens($dataset)->elements);
push(@tokens, $where);
if ($algebra->isa('Attean::Algebra::Join')) {
# don't emit extraneous braces at the top-level
push(@tokens, $algebra->sparql_tokens->elements);
} else {
push(@tokens, $lbrace);
push(@tokens, $algebra->sparql_tokens->elements);
push(@tokens, $rbrace);
}
if (my $groups = $modifiers{groups}) {
push(@tokens, AtteanX::SPARQL::Token->keyword('GROUP'));
push(@tokens, AtteanX::SPARQL::Token->keyword('BY'));
push(@tokens, @$groups);
}
if (my $expr = $modifiers{having}) {
push(@tokens, AtteanX::SPARQL::Token->keyword('HAVING'));
push(@tokens, $expr->sparql_tokens->elements);
}
if (my $comps = $modifiers{order}) {
push(@tokens, AtteanX::SPARQL::Token->keyword('ORDER'));
push(@tokens, AtteanX::SPARQL::Token->keyword('BY'));
foreach my $c (@$comps) {
push(@tokens, $c->sparql_tokens->elements);
}
}
if (exists $modifiers{limit}) {
push(@tokens, AtteanX::SPARQL::Token->keyword('LIMIT'));
push(@tokens, AtteanX::SPARQL::Token->integer($modifiers{limit}));
}
if (exists $modifiers{offset}) {
push(@tokens, AtteanX::SPARQL::Token->keyword('OFFSET'));
push(@tokens, AtteanX::SPARQL::Token->integer($modifiers{offset}));
}
} elsif ($form eq 'DESCRIBE') {
push(@tokens, AtteanX::SPARQL::Token->keyword('DESCRIBE'));
foreach my $t (@{ $modifiers{describe} }) {
push(@tokens, $t->sparql_tokens->elements);
}
push(@tokens, $self->dataset_tokens($dataset)->elements);
push(@tokens, $where);
push(@tokens, $lbrace);
push(@tokens, $algebra->sparql_tokens->elements);
push(@tokens, $rbrace);
} elsif ($form eq 'CONSTRUCT') {
push(@tokens, AtteanX::SPARQL::Token->keyword('CONSTRUCT'));
push(@tokens, $lbrace);
foreach my $t (@{ $modifiers{construct} }) {
push(@tokens, $t->sparql_tokens->elements);
push(@tokens, AtteanX::SPARQL::Token->dot);
}
push(@tokens, $rbrace);
push(@tokens, $self->dataset_tokens($dataset)->elements);
push(@tokens, $where);
push(@tokens, $lbrace);
push(@tokens, $algebra->sparql_tokens->elements);
push(@tokens, $rbrace);
} elsif ($form eq 'ASK') {
push(@tokens, AtteanX::SPARQL::Token->keyword('ASK'));
push(@tokens, $self->dataset_tokens($dataset)->elements);
push(@tokens, $lbrace);
push(@tokens, $algebra->sparql_tokens->elements);
push(@tokens, $rbrace);
} else {
die "Unexpected query for '$form' in query_tokens";
}
return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
}
}
package Attean::API::SPARQLQuerySerializable 0.034 {
use Moo::Role;
use namespace::clean;
with 'Attean::API::SPARQLSerializable';
sub sparql_tokens {
my $self = shift;
return $self->query_tokens;
}
}
=item * L
=cut
package Attean::API::Algebra 0.034 {
use Moo::Role;
use Types::Standard qw(ArrayRef ConsumerOf);
with 'Attean::API::SPARQLSerializable';
has 'hints' => (is => 'rw', isa => ArrayRef[ArrayRef[ConsumerOf['Attean::API::Term']]], default => sub { [] });
requires 'as_sparql';
requires 'in_scope_variables'; # variables that will be in-scope after this operation is evaluated
sub unary {
my $self = shift;
return unless (scalar(@{ $self->children }) == 1);
return $self->children->[0];
}
sub algebra_as_string {
my $self = shift;
return "$self";
}
sub as_string {
my $self = shift;
my $string = '';
$self->walk( prefix => sub {
my $a = shift;
my $level = shift;
my $parent = shift;
my $indent = ' ' x $level;
$string .= "-$indent " . $a->algebra_as_string($level) . "\n";
});
return $string;
}
sub blank_nodes {
my $self = shift;
my %blanks;
$self->walk( prefix => sub {
my $a = shift;
if ($a->isa('Attean::Algebra::BGP')) {
my @triples = @{ $a->triples };
my @nodes = grep { $_->does('Attean::API::Blank') } map { $_->values } @triples;
foreach my $b (@nodes) {
$blanks{ $b->value } = $b;
}
} elsif ($a->isa('Attean::Algebra::Path')) {
my @nodes = grep { $_->does('Attean::API::Blank') } ($a->subject, $a->object);
foreach my $b (@nodes) {
$blanks{ $b->value } = $b;
}
}
});
return values %blanks;
}
sub BUILD {}
if ($ENV{ATTEAN_TYPECHECK}) {
around 'BUILD' => sub {
my $orig = shift;
my $self = shift;
$self->$orig(@_);
my $name = ref($self);
$name =~ s/^.*://;
if ($self->can('arity')) {
my $arity = $self->arity;
my $children = $self->children;
my $size = scalar(@$children);
unless ($size == $arity) {
Carp::confess "${name} algebra construction with bad number of children (expected $arity, but got $size)";
}
}
}
}
}
=item * L
=cut
package Attean::API::QueryTree 0.034 {
use Moo::Role;
with 'Attean::API::DirectedAcyclicGraph';
}
=item * L
=cut
package Attean::API::NullaryQueryTree 0.034 {
use Moo::Role;
sub arity { return 0 }
with 'Attean::API::QueryTree';
}
=item * L
=cut
package Attean::API::UnaryQueryTree 0.034 {
use Moo::Role;
sub arity { return 1 }
with 'Attean::API::QueryTree';
sub child {
my $self = shift;
return $self->children->[0];
}
}
=item * L
=cut
package Attean::API::BinaryQueryTree 0.034 {
use Moo::Role;
sub arity { return 2 }
with 'Attean::API::QueryTree';
}
=item * L
=cut
package Attean::API::PropertyPath 0.034 {
use Moo::Role;
with 'Attean::API::QueryTree';
requires 'as_string';
requires 'as_sparql';
}
=item * L