From 21ff9ded5931bb381d7d4719fe618fe131c76a67 Mon Sep 17 00:00:00 2001 From: Selene Chew Date: Wed, 7 Apr 2021 11:52:29 -0400 Subject: [PATCH 1/5] WIP SimpleExecute --- Pipfile | 3 +- Pipfile.lock | 551 +++++++++++------- .../compiler/compiler_frontend.py | 18 +- .../query_planning_and_execution/__init__.py | 1 + .../make_query_plan.py | 2 +- .../sync_query_execution.py | 35 ++ .../query_planning_and_execution/typedefs.py | 251 ++++++++ .../test_make_query_plan.py | 2 +- setup.py | 4 +- 9 files changed, 636 insertions(+), 231 deletions(-) create mode 100644 graphql_compiler/query_planning_and_execution/__init__.py rename graphql_compiler/{schema_transformation => query_planning_and_execution}/make_query_plan.py (99%) create mode 100644 graphql_compiler/query_planning_and_execution/sync_query_execution.py create mode 100644 graphql_compiler/query_planning_and_execution/typedefs.py diff --git a/Pipfile b/Pipfile index 6d4960e61..ec6afa9ab 100755 --- a/Pipfile +++ b/Pipfile @@ -40,10 +40,11 @@ sphinx = ">=1.8,<2" [packages] # Make sure to keep in sync with setup.py requirements. ciso8601 = ">=2.1.3,<3" +dataclasses-json = ">=0.5.2,<0.6" funcy = ">=1.7.3,<2" graphql-core = ">=3.1.2,<3.2" # minor versions sometimes contain breaking changes six = ">=1.10.0" -sqlalchemy = ">=1.3.0,<2" +sqlalchemy = ">=1.3.0,<1.4" # The below is necessary to make a few pylint passes work properly, since pylint expects to be able # to run "import graphql_compiler" in the environment in which it runs. diff --git a/Pipfile.lock b/Pipfile.lock index c857fb500..e1da09387 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3cb98c3dd25e1ed97827a7776f87def23c3112f56b8e5a6cb06119e7559ed6b6" + "sha256": "cccd3a7f57d64a4d4e40bcc9881b91001c99832d42b31d9f5c0ea5a6aa9ec5a8" }, "pipfile-spec": 6, "requires": { @@ -23,6 +23,14 @@ "index": "pypi", "version": "==2.1.3" }, + "dataclasses-json": { + "hashes": [ + "sha256:56ec931959ede74b5dedf65cf20772e6a79764d20c404794cce0111c88c085ff", + "sha256:b746c48d9d8e884e2a0ffa59c6220a1b21f94d4f9f12c839da0a8a0efd36dc19" + ], + "index": "pypi", + "version": "==0.5.2" + }, "funcy": { "hashes": [ "sha256:65b746fed572b392d886810a98d56939c6e0d545abb750527a717c21ced21008", @@ -37,11 +45,33 @@ }, "graphql-core": { "hashes": [ - "sha256:b1826fbd1c6c290f7180d758ecf9c3859a46574cff324bf35a10167533c0e463", - "sha256:c056424cbdaa0ff67446e4379772f43746bad50a44ec23d643b9bdcd052f5b3a" + "sha256:95b0d6510bb5532dcac5927f5dfd674a580b33d6ba6417c3dc75086d9a210c83", + "sha256:db599b57b0f7dc13541922a83b30192be573b7143330ef3613813942ce4073a0" ], "index": "pypi", - "version": "==3.1.2" + "version": "==3.1.3" + }, + "marshmallow": { + "hashes": [ + "sha256:0dd42891a5ef288217ed6410917f3c6048f585f8692075a0052c24f9bfff9dfd", + "sha256:16e99cb7f630c0ef4d7d364ed0109ac194268dde123966076ab3dafb9ae3906b" + ], + "markers": "python_version >= '3.5'", + "version": "==3.11.1" + }, + "marshmallow-enum": { + "hashes": [ + "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58", + "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072" + ], + "version": "==1.5.1" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" }, "six": { "hashes": [ @@ -53,47 +83,65 @@ }, "sqlalchemy": { "hashes": [ - "sha256:04f995fcbf54e46cddeb4f75ce9dfc17075d6ae04ac23b2bacb44b3bc6f6bf11", - "sha256:0c6406a78a714a540d980a680b86654feadb81c8d0eecb59f3d6c554a4c69f19", - "sha256:0c72b90988be749e04eff0342dcc98c18a14461eb4b2ad59d611b57b31120f90", - "sha256:108580808803c7732f34798eb4a329d45b04c562ed83ee90f09f6a184a42b766", - "sha256:1418f5e71d6081aa1095a1d6b567a562d2761996710bdce9b6e6ba20a03d0864", - "sha256:17610d573e698bf395afbbff946544fbce7c5f4ee77b5bcb1f821b36345fae7a", - "sha256:216ba5b4299c95ed179b58f298bda885a476b16288ab7243e89f29f6aeced7e0", - "sha256:2ff132a379838b1abf83c065be54cef32b47c987aedd06b82fc76476c85225eb", - "sha256:314f5042c0b047438e19401d5f29757a511cfc2f0c40d28047ca0e4c95eabb5b", - "sha256:318b5b727e00662e5fc4b4cd2bf58a5116d7c1b4dd56ffaa7d68f43458a8d1ed", - "sha256:3ab5b44a07b8c562c6dcb7433c6a6c6e03266d19d64f87b3333eda34e3b9936b", - "sha256:426ece890153ccc52cc5151a1a0ed540a5a7825414139bb4c95a868d8da54a52", - "sha256:491fe48adc07d13e020a8b07ef82eefc227003a046809c121bea81d3dbf1832d", - "sha256:4a84c7c7658dd22a33dab2e2aa2d17c18cb004a42388246f2e87cb4085ef2811", - "sha256:54da615e5b92c339e339fe8536cce99fe823b6ed505d4ea344852aefa1c205fb", - "sha256:5a7f224cdb7233182cec2a45d4c633951268d6a9bcedac37abbf79dd07012aea", - "sha256:61628715931f4962e0cdb2a7c87ff39eea320d2aa96bd471a3c293d146f90394", - "sha256:62285607a5264d1f91590abd874d6a498e229d5840669bd7d9f654cfaa599bd0", - "sha256:62fb881ba51dbacba9af9b779211cf9acff3442d4f2993142015b22b3cd1f92a", - "sha256:68428818cf80c60dc04aa0f38da20ad39b28aba4d4d199f949e7d6e04444ea86", - "sha256:6aaa13ee40c4552d5f3a59f543f0db6e31712cc4009ec7385407be4627259d41", - "sha256:70121f0ae48b25ef3e56e477b88cd0b0af0e1f3a53b5554071aa6a93ef378a03", - "sha256:715b34578cc740b743361f7c3e5f584b04b0f1344f45afc4e87fbac4802eb0a0", - "sha256:758fc8c4d6c0336e617f9f6919f9daea3ab6bb9b07005eda9a1a682e24a6cacc", - "sha256:7d4b8de6bb0bc736161cb0bbd95366b11b3eb24dd6b814a143d8375e75af9990", - "sha256:81d8d099a49f83111cce55ec03cc87eef45eec0d90f9842b4fc674f860b857b0", - "sha256:888d5b4b5aeed0d3449de93ea80173653e939e916cc95fe8527079e50235c1d2", - "sha256:95bde07d19c146d608bccb9b16e144ec8f139bcfe7fd72331858698a71c9b4f5", - "sha256:9bf572e4f5aa23f88dd902f10bb103cb5979022a38eec684bfa6d61851173fec", - "sha256:bab5a1e15b9466a25c96cda19139f3beb3e669794373b9ce28c4cf158c6e841d", - "sha256:bd4b1af45fd322dcd1fb2a9195b4f93f570d1a5902a842e3e6051385fac88f9c", - "sha256:bde677047305fe76c7ee3e4492b545e0018918e44141cc154fe39e124e433991", - "sha256:c389d7cc2b821853fb018c85457da3e7941db64f4387720a329bc7ff06a27963", - "sha256:d055ff750fcab69ca4e57b656d9c6ad33682e9b8d564f2fbe667ab95c63591b0", - "sha256:d53f59744b01f1440a1b0973ed2c3a7de204135c593299ee997828aad5191693", - "sha256:f115150cc4361dd46153302a640c7fa1804ac207f9cc356228248e351a8b4676", - "sha256:f1e88b30da8163215eab643962ae9d9252e47b4ea53404f2c4f10f24e70ddc62", - "sha256:f8191fef303025879e6c3548ecd8a95aafc0728c764ab72ec51a0bdf0c91a341" + "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8", + "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d", + "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48", + "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab", + "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b", + "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443", + "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75", + "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109", + "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996", + "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894", + "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4", + "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60", + "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2", + "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba", + "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233", + "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658", + "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7", + "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e", + "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39", + "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6", + "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b", + "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8", + "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c", + "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f", + "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79", + "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519", + "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064", + "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375", + "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548", + "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7", + "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79", + "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b", + "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4", + "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9" ], "index": "pypi", - "version": "==1.3.22" + "version": "==1.3.24" + }, + "stringcase": { + "hashes": [ + "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008" + ], + "version": "==1.2.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", + "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", + "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" + ], + "version": "==3.7.4.3" + }, + "typing-inspect": { + "hashes": [ + "sha256:3b98390df4d999a28cf5b35d8b333425af5da2ece8a4ea9e98f71e7591347b4f", + "sha256:8f1b1dd25908dbfd81d3bebc218011531e7ab614ba6e5bf7826d887c834afab7", + "sha256:de08f50a22955ddec353876df7b2545994d6df08a2f45d54ac8c05e530372ca0" + ], + "version": "==0.6.0" } }, "develop": { @@ -184,58 +232,61 @@ }, "coverage": { "hashes": [ - "sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297", - "sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1", - "sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497", - "sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606", - "sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528", - "sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b", - "sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4", - "sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830", - "sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1", - "sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f", - "sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d", - "sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3", - "sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8", - "sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500", - "sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7", - "sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb", - "sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b", - "sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059", - "sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b", - "sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72", - "sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36", - "sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277", - "sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c", - "sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631", - "sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff", - "sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8", - "sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec", - "sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b", - "sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7", - "sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105", - "sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b", - "sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c", - "sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b", - "sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98", - "sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4", - "sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879", - "sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f", - "sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4", - "sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044", - "sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e", - "sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899", - "sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f", - "sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448", - "sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714", - "sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2", - "sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d", - "sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd", - "sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7", - "sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae" + "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", + "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", + "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", + "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", + "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", + "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", + "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", + "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", + "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", + "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", + "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", + "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", + "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", + "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", + "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", + "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", + "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", + "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", + "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", + "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", + "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", + "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", + "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", + "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", + "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", + "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", + "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", + "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", + "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", + "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", + "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", + "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", + "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", + "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", + "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", + "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", + "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3", + "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e", + "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066", + "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf", + "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b", + "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", + "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669", + "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", + "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", + "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", + "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb", + "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160", + "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", + "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079", + "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d", + "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==5.3.1" + "version": "==5.5" }, "docutils": { "hashes": [ @@ -253,19 +304,19 @@ }, "flake8": { "hashes": [ - "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839", - "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b" + "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff", + "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0" ], "index": "pypi", - "version": "==3.8.4" + "version": "==3.9.0" }, "flake8-bugbear": { "hashes": [ - "sha256:528020129fea2dea33a466b9d64ab650aa3e5f9ffc788b70ea4bc6cf18283538", - "sha256:f35b8135ece7a014bc0aee5b5d485334ac30a6da48494998cc1fabf7ec70d703" + "sha256:2346c81f889955b39e4a368eb7d508de723d9de05716c287dc860a4073dc57e7", + "sha256:4f305dca96be62bf732a218fe6f1825472a621d3452c5b994d8f89dae21dbafa" ], "index": "pypi", - "version": "==20.11.1" + "version": "==21.4.3" }, "flake8-print": { "hashes": [ @@ -276,19 +327,19 @@ }, "gitdb": { "hashes": [ - "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac", - "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9" + "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0", + "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005" ], "markers": "python_version >= '3.4'", - "version": "==4.0.5" + "version": "==4.0.7" }, "gitpython": { "hashes": [ - "sha256:42dbefd8d9e2576c496ed0059f3103dcef7125b9ce16f9d5f9c834aed44a1dac", - "sha256:867ec3dfb126aac0f8296b19fb63b8c4a399f32b4b6fafe84c4b10af5fa9f7b5" + "sha256:3283ae2fba31c913d857e12e5ba5f9a7772bbc064ae2bb09efafa71b0dd4939b", + "sha256:be27633e7509e58391f10207cd32b2a6cf5b908f92d9cd30da2e514e1137af61" ], "markers": "python_version >= '3.4'", - "version": "==3.1.12" + "version": "==3.1.14" }, "idna": { "hashes": [ @@ -316,11 +367,11 @@ }, "jinja2": { "hashes": [ - "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", - "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" + "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", + "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.11.2" + "version": "==2.11.3" }, "lazy-object-proxy": { "hashes": [ @@ -356,8 +407,12 @@ "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", + "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f", + "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39", "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014", + "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f", "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", @@ -366,24 +421,39 @@ "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85", + "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1", "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850", + "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0", "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb", "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1", + "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2", "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7", "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8", "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193", "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b", "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5", + "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c", + "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032", "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", + "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.1.1" @@ -397,31 +467,39 @@ }, "more-itertools": { "hashes": [ - "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330", - "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf" + "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", + "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713" ], "markers": "python_version >= '3.5'", - "version": "==8.6.0" + "version": "==8.7.0" }, "mypy": { "hashes": [ - "sha256:0a0d102247c16ce93c97066443d11e2d36e6cc2a32d8ccc1f705268970479324", - "sha256:0d34d6b122597d48a36d6c59e35341f410d4abfa771d96d04ae2c468dd201abc", - "sha256:2170492030f6faa537647d29945786d297e4862765f0b4ac5930ff62e300d802", - "sha256:2842d4fbd1b12ab422346376aad03ff5d0805b706102e475e962370f874a5122", - "sha256:2b21ba45ad9ef2e2eb88ce4aeadd0112d0f5026418324176fd494a6824b74975", - "sha256:72060bf64f290fb629bd4a67c707a66fd88ca26e413a91384b18db3876e57ed7", - "sha256:af4e9ff1834e565f1baa74ccf7ae2564ae38c8df2a85b057af1dbbc958eb6666", - "sha256:bd03b3cf666bff8d710d633d1c56ab7facbdc204d567715cb3b9f85c6e94f669", - "sha256:c614194e01c85bb2e551c421397e49afb2872c88b5830e3554f0519f9fb1c178", - "sha256:cf4e7bf7f1214826cf7333627cb2547c0db7e3078723227820d0a2490f117a01", - "sha256:da56dedcd7cd502ccd3c5dddc656cb36113dd793ad466e894574125945653cea", - "sha256:e86bdace26c5fe9cf8cb735e7cedfe7850ad92b327ac5d797c656717d2ca66de", - "sha256:e97e9c13d67fbe524be17e4d8025d51a7dca38f90de2e462243ab8ed8a9178d1", - "sha256:eea260feb1830a627fb526d22fbb426b750d9f5a47b624e8d5e7e004359b219c" + "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e", + "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064", + "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c", + "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4", + "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97", + "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df", + "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8", + "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a", + "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56", + "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7", + "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6", + "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5", + "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a", + "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521", + "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564", + "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49", + "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66", + "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a", + "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119", + "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506", + "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c", + "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb" ], "index": "pypi", - "version": "==0.790" + "version": "==0.812" }, "mypy-extensions": { "hashes": [ @@ -461,11 +539,11 @@ }, "packaging": { "hashes": [ - "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858", - "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093" + "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", + "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.8" + "version": "==20.9" }, "parameterized": { "hashes": [ @@ -552,11 +630,11 @@ }, "pycodestyle": { "hashes": [ - "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", - "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" + "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", + "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.6.0" + "version": "==2.7.0" }, "pydocstyle": { "hashes": [ @@ -568,19 +646,19 @@ }, "pyflakes": { "hashes": [ - "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", - "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" + "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", + "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.2.0" + "version": "==2.3.1" }, "pygments": { "hashes": [ - "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435", - "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337" + "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94", + "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8" ], "markers": "python_version >= '3.5'", - "version": "==2.7.4" + "version": "==2.8.1" }, "pylint": { "hashes": [ @@ -635,36 +713,53 @@ }, "pytest-cov": { "hashes": [ - "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191", - "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e" + "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7", + "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da" ], "index": "pypi", - "version": "==2.10.1" + "version": "==2.11.1" }, "pytz": { "hashes": [ - "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4", - "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5" + "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", + "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" ], - "version": "==2020.5" + "version": "==2021.1" }, "pyyaml": { "hashes": [ - "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", - "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", - "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", - "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e", - "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", - "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", - "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", - "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", - "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", - "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a", - "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", - "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", - "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" - ], - "version": "==5.3.1" + "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", + "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", + "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", + "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", + "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", + "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", + "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", + "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", + "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", + "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", + "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", + "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", + "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", + "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", + "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", + "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", + "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", + "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", + "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", + "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", + "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", + "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", + "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", + "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", + "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", + "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", + "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", + "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", + "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==5.4.1" }, "redis": { "hashes": [ @@ -683,49 +778,49 @@ }, "regex": { "hashes": [ - "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538", - "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4", - "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc", - "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa", - "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444", - "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1", - "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af", - "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8", - "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9", - "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88", - "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba", - "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364", - "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e", - "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7", - "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0", - "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31", - "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683", - "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee", - "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b", - "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884", - "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c", - "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e", - "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562", - "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85", - "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c", - "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6", - "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d", - "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b", - "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70", - "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b", - "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b", - "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f", - "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0", - "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5", - "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5", - "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f", - "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e", - "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512", - "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d", - "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917", - "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f" - ], - "version": "==2020.11.13" + "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5", + "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79", + "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31", + "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500", + "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11", + "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14", + "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3", + "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439", + "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c", + "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82", + "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711", + "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093", + "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a", + "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb", + "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8", + "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17", + "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000", + "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d", + "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480", + "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc", + "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0", + "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9", + "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765", + "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e", + "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a", + "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07", + "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f", + "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac", + "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7", + "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed", + "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968", + "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7", + "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2", + "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4", + "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87", + "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8", + "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10", + "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29", + "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605", + "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6", + "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042" + ], + "version": "==2021.4.4" }, "requests": { "hashes": [ @@ -745,11 +840,11 @@ }, "smmap": { "hashes": [ - "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4", - "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24" + "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182", + "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==3.0.4" + "markers": "python_version >= '3.5'", + "version": "==4.0.0" }, "snapshottest": { "hashes": [ @@ -761,10 +856,10 @@ }, "snowballstemmer": { "hashes": [ - "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", - "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" + "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2", + "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914" ], - "version": "==2.0.0" + "version": "==2.1.0" }, "sphinx": { "hashes": [ @@ -776,11 +871,11 @@ }, "sphinx-rtd-theme": { "hashes": [ - "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5", - "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113" + "sha256:32bd3b5d13dc8186d7a42fc816a23d32e83a4827d7d9882948e7b837c232da5a", + "sha256:4a05bdbe8b1446d77a01e20a23ebc6777c74f43237035e76be89699308987d6f" ], "index": "pypi", - "version": "==0.5.1" + "version": "==0.5.2" }, "sphinxcontrib-serializinghtml": { "hashes": [ @@ -874,23 +969,29 @@ }, "urllib3": { "hashes": [ - "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", - "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" + "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df", + "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.2" + "version": "==1.26.4" }, "wasmer": { "hashes": [ - "sha256:519d9f7a33f0dc28498edf8d074944df1cfda3f4d56a6b0a9dc606e99f230c07", - "sha256:6e37fd76931da954d12f7708e7fc790c479e17ea6dfe54bb4f1403d6fe919c62", - "sha256:92aee4b3994735f72e27c9deaca36d063781ddfd1068756bf63509288c1a2c14", - "sha256:c36c6c7a3582c0467ea76c5b86a5a25c2e1a713669ebf8fc6511b26f30828ed7", - "sha256:d5a58b318628bd48605a4810ee9ba030ec4423dee730d45f031bd082cf36ad8b", - "sha256:f864b1e910e6803aa8f3ed9c371b145f0b6fd191f5fbefea011ac7ed4258734d", - "sha256:fa1c8479781c91e6b814ef006f6e099b6550eba12601a8617475fb514fc09365" - ], - "version": "==0.4.1" + "sha256:04e5d51eed336be0e1bf1445aa032fc34466b77220c26732b8990df872f29e92", + "sha256:0d755a93184380f9785e1f8269b070f4a4a3285ae0f7f9571cfaf2ea215b102d", + "sha256:11e73026cb7e9a948b86324bd573cafe4d7be91f65ff45b1dc67485044348754", + "sha256:15b355fed4ebe9d912a7fe236391aac591cc64c7647e14e517c2a6f451a83914", + "sha256:1fada13fb536f44e12d7c98c557f5b7a55df94389d3cb90964193dcc8e16f0fa", + "sha256:427e9c5e5301a453a09b8b9ceb8c793d85411b8f45e54c9c0d801566dd8d214e", + "sha256:690c5ed46a98e91bc638f52a25e5011c5baa1ef0581f414018eb7aa0e7f844bc", + "sha256:6b41046db73facc064cb9649627d9cdd1b88ada74e745134e44e09ada6827056", + "sha256:8b13214b5dcc84d43f4c44e2309442497f19137561afada7f34babad89365355", + "sha256:a08be93aff695ce3be871994f726716b68bef36bd583bccad75778359cd273df", + "sha256:d77b169079183c131d398c57dfc36e5cc4b34ae2543878ac2056e858f040c099", + "sha256:dd6c5ab2c41e9fd31442483ac7adc81d886cc3242d604dc3c895df257aa7dbe2", + "sha256:e2ecd788140468d2d489fc92537b47e2baf4ffdec00fe41f055715d261e4988b" + ], + "version": "==1.0.0" }, "wcwidth": { "hashes": [ diff --git a/graphql_compiler/compiler/compiler_frontend.py b/graphql_compiler/compiler/compiler_frontend.py index 185ceece0..4e4aff06b 100644 --- a/graphql_compiler/compiler/compiler_frontend.py +++ b/graphql_compiler/compiler/compiler_frontend.py @@ -62,6 +62,7 @@ from dataclasses import dataclass from typing import Dict, List, Optional +from dataclasses_json import DataClassJsonMixin from graphql import ( DocumentNode, GraphQLInt, @@ -133,7 +134,18 @@ from .validation import validate_schema_and_query_ast -@dataclass(frozen=True) +@dataclass(init=True, repr=True, eq=True, frozen=True) +class ProviderMetadata(DataClassJsonMixin): + """Metadata about the provider.""" + + # Name of the type of provider (ex. PostgreSQL, Cypher, etc). + backend_type: str + + # Whether this backend requires MSSQL fold postprocessing for folded outputs. + requires_fold_postprocessing: bool + + +@dataclass(init=True, repr=True, eq=False, frozen=True) class OutputMetadata: """Metadata about a query's outputs.""" @@ -148,6 +160,9 @@ class OutputMetadata: # in which case the type must be GraphQLInt. folded: bool + # Provider metadata. + provider_metadata: Optional[ProviderMetadata] = None + def __eq__(self, other): """Check another OutputMetadata object for equality against this one.""" # Unfortunately, GraphQL types don't have an equality operator defined, @@ -156,6 +171,7 @@ def __eq__(self, other): is_same_type(self.type, other.type) and self.optional == other.optional and self.folded == other.folded + and self.provider_metadata == other.provider_metadata ) def __ne__(self, other): diff --git a/graphql_compiler/query_planning_and_execution/__init__.py b/graphql_compiler/query_planning_and_execution/__init__.py new file mode 100644 index 000000000..267b30955 --- /dev/null +++ b/graphql_compiler/query_planning_and_execution/__init__.py @@ -0,0 +1 @@ +# Copyright 2021-present Kensho Technologies, LLC. diff --git a/graphql_compiler/schema_transformation/make_query_plan.py b/graphql_compiler/query_planning_and_execution/make_query_plan.py similarity index 99% rename from graphql_compiler/schema_transformation/make_query_plan.py rename to graphql_compiler/query_planning_and_execution/make_query_plan.py index 6547b08f2..3cc53ecdc 100644 --- a/graphql_compiler/schema_transformation/make_query_plan.py +++ b/graphql_compiler/query_planning_and_execution/make_query_plan.py @@ -21,7 +21,7 @@ from ..ast_manipulation import get_only_query_definition from ..exceptions import GraphQLValidationError from ..schema import FilterDirective, OutputDirective -from .split_query import AstType, SubQueryNode +from ..schema_transformation.split_query import AstType, SubQueryNode @dataclass diff --git a/graphql_compiler/query_planning_and_execution/sync_query_execution.py b/graphql_compiler/query_planning_and_execution/sync_query_execution.py new file mode 100644 index 000000000..b9af2f666 --- /dev/null +++ b/graphql_compiler/query_planning_and_execution/sync_query_execution.py @@ -0,0 +1,35 @@ +# Copyright 2021-present Kensho Technologies, LLC. +from typing import Any, Callable, ContextManager, Dict, Iterable, Mapping + +from ..post_processing.sql_post_processing import post_process_mssql_folds +from ..query_formatting.common import deserialize_multiple_arguments +from .typedefs import QueryExecutionFunc, SimpleExecute + + +def _execute_simple_execute_node( + provider_client_makers: Dict[str, Callable[[], ContextManager[QueryExecutionFunc]]], + query_plan_node: SimpleExecute, +) -> Iterable[Mapping[str, Any]]: + """Execute a SimpleExecute.""" + provider_client_maker = provider_client_makers[query_plan_node.provider_id] + arguments = deserialize_multiple_arguments( + query_plan_node.arguments, query_plan_node.input_metadata + ) + + with provider_client_maker() as query_client: + result = query_client(query_plan_node.query, query_plan_node.input_metadata, arguments) + # Apply post processing for MSSQL folds if applicable. + requires_postprocessing = False + for _, output_metadata in query_plan_node.output_metadata.items(): + if ( + output_metadata.provider_metadata is not None + and output_metadata.provider_metadata.requires_fold_postprocessing + ): + requires_postprocessing = True + if requires_postprocessing: + list_dict_result = [dict(entry) for entry in result] + post_process_mssql_folds(list_dict_result, query_plan_node.output_metadata) + return list_dict_result + + # Otherwise, return result as is. + return result diff --git a/graphql_compiler/query_planning_and_execution/typedefs.py b/graphql_compiler/query_planning_and_execution/typedefs.py new file mode 100644 index 000000000..d46aec8b8 --- /dev/null +++ b/graphql_compiler/query_planning_and_execution/typedefs.py @@ -0,0 +1,251 @@ +# Copyright 2021-present Kensho Technologies, LLC. +from abc import ABCMeta +import copy +from dataclasses import dataclass, field +from typing import Any, Callable, Dict, Iterable, Mapping, Optional + +from dataclasses_json import DataClassJsonMixin, config +from graphql import ( + GraphQLList, + GraphQLNonNull, + GraphQLType, + ListTypeNode, + NamedTypeNode, + NonNullTypeNode, + TypeNode, + parse_type, + specified_scalar_types, +) + +from .. import GraphQLDate, GraphQLDateTime, GraphQLDecimal +from ..compiler.compiler_frontend import OutputMetadata, ProviderMetadata +from ..global_utils import is_same_type + + +QueryExecutionFunc = Callable[ + [str, Dict[str, GraphQLType], Dict[str, Any]], Iterable[Mapping[str, Any]] +] + +# Custom scalar types. +CUSTOM_SCALAR_TYPES = { + GraphQLDate.name: GraphQLDate, + GraphQLDateTime.name: GraphQLDateTime, + GraphQLDecimal.name: GraphQLDecimal, +} + +# Custom scalar types must not have name conflicts with builtin scalar types. +if set(CUSTOM_SCALAR_TYPES).intersection(specified_scalar_types): + raise AssertionError( + f"Custom scalar types must have different names than builtin scalar types. Received " + f"overlapping type(s) {set(CUSTOM_SCALAR_TYPES).intersection(specified_scalar_types)}. " + f"Custom scalar types: {set(CUSTOM_SCALAR_TYPES)}. Builtin scalar types: " + f"{set(specified_scalar_types)}." + ) + +# Custom scalar types combined with builtin scalar types represent all allowable scalar types. +ALL_SCALAR_TYPES = copy.copy(CUSTOM_SCALAR_TYPES) +ALL_SCALAR_TYPES.update(specified_scalar_types) + + +def _type_from_scalar_type_dictionary( + scalar_types: Dict[str, GraphQLType], type_node: TypeNode +) -> GraphQLType: + """Get the GraphQL type definition from an AST node. + + Given a scalar type dictionary and an AST node describing a type, return a GraphQLType + definition, which applies to that type. For example, if provided the parsed AST node for + `[Date]`, a GraphQLList instance will be returned, containing the type called + "Date" found in the scalar type dictionary. If a type called "Date" is not found in the scalar + type dictionary, then None will be returned. + + Note: this is very similar to GraphQL's type_from_ast. However, instead of requiring a GraphQL + schema this function requires a dictionary of the scalar types. This simplifies deserialization + and allows for custom scalar types without constructing an entire schema. + + Args: + scalar_types: dictionary mapping type name to GraphQLType + type_node: AST node describing a type + + Returns: + GraphQLType that applies to the type specified in type_node. + + Raises: + AssertionError: if an invalid type node is given. + """ + if isinstance(type_node, ListTypeNode): + inner_type = _type_from_scalar_type_dictionary(scalar_types, type_node.type) + if inner_type: + return GraphQLList(inner_type) + else: + raise AssertionError( + f"Invalid type node. ListTypeNode contained inner type {inner_type}." + ) + elif isinstance(type_node, NonNullTypeNode): + inner_type = _type_from_scalar_type_dictionary(scalar_types, type_node.type) + if inner_type: + return GraphQLNonNull(inner_type) + else: + raise AssertionError( + f"Invalid type node. NonNullTypeNone contained inner type {inner_type}." + ) + elif isinstance(type_node, NamedTypeNode): + return scalar_types[type_node.name.value] + + # Not reachable. All possible type nodes have been considered. + raise AssertionError(f"Unexpected type node: {type_node}.") + + +def _serialize_output_metadata_field( + output_metadata_dictionary: Optional[Dict[str, "OutputMetadata"]] +) -> Optional[Dict[str, Dict[str, Any]]]: + """Serialize OutputMetadata into a dictionary.""" + if not output_metadata_dictionary: + return None + dictionary_value = {} + for output_name, output_metadata in output_metadata_dictionary.items(): + dictionary_value[output_name] = { + "graphql_type": str(output_metadata["graphql_type"]), + "optional": output_metadata["optional"], + "folded": output_metadata["folded"], + "provider_metadata": output_metadata["provider_metadata"], + } + return dictionary_value + + +def _deserialize_output_metadata_field( + dict_value: Optional[Dict[str, Dict[str, Any]]] +) -> Optional[Dict[str, "OutputMetadata"]]: + """Deserialize the dictionary representation of OutputMetadata.""" + if not dict_value: + return None + output_metadata_dictionary = {} + for output_name, output_metadata in dict_value.items(): + output_metadata_dictionary[output_name] = OutputMetadata( + type=_type_from_scalar_type_dictionary( + ALL_SCALAR_TYPES, parse_type(output_metadata["graphql_type"]) + ), + optional=output_metadata["optional"], + folded=output_metadata["folded"], + provider_metadata=ProviderMetadata( + backend_type=output_metadata["provider_metadata"]["backend_type"], + requires_fold_postprocessing=output_metadata["provider_metadata"][ + "requires_fold_postprocessing" + ], + ), + ) + return output_metadata_dictionary + + +def _serialize_input_metadata_field( + input_metadata_dictionary: Optional[Dict[str, Any]] +) -> Optional[Dict[str, str]]: + """Serialize input metadata, converting GraphQLTypes to strings.""" + # It is possible to have an empty input metadata dictionary (i.e. no inputs for the query). + # Note that this is different than "None", which means no metadata was provided. + if input_metadata_dictionary == {}: + return {} + if not input_metadata_dictionary: + return None + dictionary_value = {} + for input_name, input_type in input_metadata_dictionary.items(): + dictionary_value[input_name] = str(input_type) + return dictionary_value + + +def _deserialize_input_metadata_field( + dict_value: Optional[Dict[str, str]] +) -> Optional[Dict[str, GraphQLType]]: + """Deserialize input metadata, converting strings to GraphQLTypes.""" + # It is possible to have an empty input metadata dictionary (i.e. no inputs for the query). + # Note that this is different than "None", which means no metadata was provided. + if dict_value == {}: + return {} + if not dict_value: + return None + input_metadata_dictionary = {} + for input_name, input_type in dict_value.items(): + input_metadata_dictionary[input_name] = _type_from_scalar_type_dictionary( + ALL_SCALAR_TYPES, parse_type(input_type) + ) + return input_metadata_dictionary + + +def _compare_input_metadata_field( + left_input_metadata: Optional[Dict[str, GraphQLType]], + right_input_metadata: Optional[Dict[str, GraphQLType]], +) -> bool: + """Check input_metadata SimpleExecute field equality, comparing GraphQLTypes appropriately.""" + if left_input_metadata is None: + # Since left_input_metadata is None, checking for equality requires determining whether or + # not right_metadata is also None. If right_metadata is None, left and right metadata + # are equal and True is returned. + return right_input_metadata is None + + # right_input_metadata is None, but left_input_metadata is not. + if right_input_metadata is None: + return False + + # Neither left_input_metadata nor right_input_metadata is None. + input_metadata_keys = left_input_metadata.keys() + + # Check if input_metadata keys match. + if input_metadata_keys != right_input_metadata.keys(): + return False + # Check if input_metadata values match for all keys. + for key in input_metadata_keys: + if not is_same_type(left_input_metadata[key], right_input_metadata[key]): + return False + + # All keys and values match so return True. + return True + + +# ############ +# Public API # +# ############ + + +@dataclass(init=True, repr=True, eq=True, frozen=True) +class QueryPlanNode(DataClassJsonMixin, metaclass=ABCMeta): + """Abstract query plan node. May or may not contain other nodes, depending on its type.""" + + # Unique ID of the plan node. + # Note: do not compare "uuid" values to determine whether query plan nodes are equal, since two + # plans with different identifiers might still be semantically equivalent and therefore equal. + uuid: str = field(compare=False) + + +@dataclass(init=True, repr=True, eq=False, frozen=True) +class SimpleExecute(QueryPlanNode): + """Just give the specified query and args to the provider, it'll execute it for you as-is.""" + + provider_id: str + query: str # in whatever query language the provider will accept (not necessarily GraphQL) + arguments: Dict[str, Any] + + # Input and output metadata of the query. + output_metadata: Dict[str, OutputMetadata] = field( + metadata=config( + encoder=_serialize_output_metadata_field, decoder=_deserialize_output_metadata_field + ) + ) + input_metadata: Dict[str, GraphQLType] = field( + metadata=config( + encoder=_serialize_input_metadata_field, decoder=_deserialize_input_metadata_field + ) + ) + + def __eq__(self, other: Any) -> bool: + """Check equality between an object and this SimpleExecute.""" + if not isinstance(other, SimpleExecute): + return False + + # Perform special check for input_metadata since GraphQLTypes don't have equality, and + # check all other fields in a straight forward manner. + return ( + self.provider_id == other.provider_id + and self.query == other.query + and self.arguments == other.arguments + and self.output_metadata == other.output_metadata + and _compare_input_metadata_field(self.input_metadata, other.input_metadata) + ) diff --git a/graphql_compiler/tests/schema_transformation_tests/test_make_query_plan.py b/graphql_compiler/tests/schema_transformation_tests/test_make_query_plan.py index eb825fc90..997995d5c 100644 --- a/graphql_compiler/tests/schema_transformation_tests/test_make_query_plan.py +++ b/graphql_compiler/tests/schema_transformation_tests/test_make_query_plan.py @@ -4,7 +4,7 @@ from graphql import parse, print_ast -from ...schema_transformation.make_query_plan import make_query_plan +from ...query_planning_and_execution.make_query_plan import make_query_plan from ...schema_transformation.split_query import split_query from .example_schema import basic_merged_schema diff --git a/setup.py b/setup.py index 26e84f88a..f5d372058 100755 --- a/setup.py +++ b/setup.py @@ -56,10 +56,10 @@ def find_long_description() -> str: packages=find_packages(exclude=["tests*"]), install_requires=[ # Make sure to keep in sync with Pipfile requirements. "ciso8601>=2.1.3,<3", - "funcy>=1.7.3,<2", + "dataclasses-json>=0.5.2,<0.6" "funcy>=1.7.3,<2", "graphql-core>=3.1.2,<3.2", "six>=1.10.0", - "sqlalchemy>=1.3.0,<2", + "sqlalchemy>=1.3.0,<1.4", ], extras_require={ ':python_version<"3.7"': ["dataclasses>=0.7,<1"], From 0b0442964c18f4415749f9b21abcd407a1b1f73a Mon Sep 17 00:00:00 2001 From: Selene Chew Date: Fri, 16 Apr 2021 12:27:18 -0400 Subject: [PATCH 2/5] generate_query_plan implementation --- .../compiler/compiler_frontend.py | 16 -- .../make_query_plan.py | 228 ++++++++++++++---- .../sync_query_execution.py | 9 +- .../query_planning_and_execution/typedefs.py | 84 +++++-- graphql_compiler/schema/schema_info.py | 102 ++++---- graphql_compiler/schema/typedefs.py | 2 +- .../test_sqlalchemy_schema_generation.py | 33 +++ 7 files changed, 336 insertions(+), 138 deletions(-) diff --git a/graphql_compiler/compiler/compiler_frontend.py b/graphql_compiler/compiler/compiler_frontend.py index 4e4aff06b..3d84b8e39 100644 --- a/graphql_compiler/compiler/compiler_frontend.py +++ b/graphql_compiler/compiler/compiler_frontend.py @@ -62,7 +62,6 @@ from dataclasses import dataclass from typing import Dict, List, Optional -from dataclasses_json import DataClassJsonMixin from graphql import ( DocumentNode, GraphQLInt, @@ -134,17 +133,6 @@ from .validation import validate_schema_and_query_ast -@dataclass(init=True, repr=True, eq=True, frozen=True) -class ProviderMetadata(DataClassJsonMixin): - """Metadata about the provider.""" - - # Name of the type of provider (ex. PostgreSQL, Cypher, etc). - backend_type: str - - # Whether this backend requires MSSQL fold postprocessing for folded outputs. - requires_fold_postprocessing: bool - - @dataclass(init=True, repr=True, eq=False, frozen=True) class OutputMetadata: """Metadata about a query's outputs.""" @@ -160,9 +148,6 @@ class OutputMetadata: # in which case the type must be GraphQLInt. folded: bool - # Provider metadata. - provider_metadata: Optional[ProviderMetadata] = None - def __eq__(self, other): """Check another OutputMetadata object for equality against this one.""" # Unfortunately, GraphQL types don't have an equality operator defined, @@ -171,7 +156,6 @@ def __eq__(self, other): is_same_type(self.type, other.type) and self.optional == other.optional and self.folded == other.folded - and self.provider_metadata == other.provider_metadata ) def __ne__(self, other): diff --git a/graphql_compiler/query_planning_and_execution/make_query_plan.py b/graphql_compiler/query_planning_and_execution/make_query_plan.py index 3cc53ecdc..d2e60be9e 100644 --- a/graphql_compiler/query_planning_and_execution/make_query_plan.py +++ b/graphql_compiler/query_planning_and_execution/make_query_plan.py @@ -1,7 +1,8 @@ # Copyright 2019-present Kensho Technologies, LLC. from copy import copy from dataclasses import dataclass -from typing import FrozenSet, List, Optional, Tuple, cast +from typing import FrozenSet, List, Optional, Tuple, Union, cast +from uuid import uuid4 from graphql import print_ast from graphql.language.ast import ( @@ -17,11 +18,25 @@ StringValueNode, ) from graphql.pyutils import FrozenList +from sqlalchemy.dialects.mssql.base import MSDialect from ..ast_manipulation import get_only_query_definition +from ..compiler.common import ( + CYPHER_LANGUAGE, + GREMLIN_LANGUAGE, + MATCH_LANGUAGE, + compile_graphql_to_cypher, + compile_graphql_to_gremlin, + compile_graphql_to_match, + compile_graphql_to_sql, +) +from ..compiler.sqlalchemy_extensions import print_sqlalchemy_query_string from ..exceptions import GraphQLValidationError +from ..global_utils import QueryStringWithParameters from ..schema import FilterDirective, OutputDirective +from ..schema.schema_info import CommonSchemaInfo, SQLAlchemySchemaInfo from ..schema_transformation.split_query import AstType, SubQueryNode +from .typedefs import ProviderMetadata, QueryPlan, SimpleExecute @dataclass @@ -66,44 +81,65 @@ class QueryPlanDescriptor: output_join_descriptors: List[OutputJoinDescriptor] -def make_query_plan( - root_sub_query_node: SubQueryNode, intermediate_output_names: FrozenSet[str] -) -> QueryPlanDescriptor: - """Return a QueryPlanDescriptor, whose query ASTs have @filters added. - - For each parent of parent and child SubQueryNodes, a new @filter directive will be added - in the child AST. It will be added on the field whose @output directive has the out_name - equal to the child's out name as specified in the QueryConnection. The newly added @filter - will be a 'in_collection' type filter, and the name of the local variable is guaranteed to - be the same as the out_name of the @output on the parent. - - ASTs contained in the input node and its children nodes will not be modified. +def _make_simple_execute_node( + query_and_parameters: QueryStringWithParameters, + schema_info: Union[CommonSchemaInfo, SQLAlchemySchemaInfo], + provider_id: str, + backend_type: Optional[str], +) -> SimpleExecute: + """Generate a SimpleExecuteNode. Args: - root_sub_query_node: representing the base of a query split into pieces - that we want to turn into a query plan. - intermediate_output_names: names of outputs to be removed at the end. + query_and_parameters: the query and parameters for which to create a SimpleExecute. + schema_info: schema information to use for query compilation. + provider_id: the identifier of the provider to be queried. + backend_type: the backend type. Note: this is inferred from the SQLAlchemySchemaInfo's + dialect for SQL backends. Returns: - QueryPlanDescriptor containing a tree of SubQueryPlans that wrap around each individual - query AST, the set of intermediate output names that are to be removed at the end, and - information on which outputs are to be connect to which in what manner. - """ - output_join_descriptors: List[OutputJoinDescriptor] = [] + SimpleExecute with all necessary information to execute the given query_and_parameters. - root_sub_query_plan = SubQueryPlan( - query_ast=root_sub_query_node.query_ast, - schema_id=root_sub_query_node.schema_id, - parent_query_plan=None, - child_query_plans=[], - ) - - _make_query_plan_recursive(root_sub_query_node, root_sub_query_plan, output_join_descriptors) + """ + if isinstance(schema_info, SQLAlchemySchemaInfo): + # Compiled SQL query. + compilation_result = compile_graphql_to_sql(schema_info, query_and_parameters.query_string) + query = print_sqlalchemy_query_string(compilation_result.query, schema_info.dialect) + + provider_metadata = ProviderMetadata( + backend_type=schema_info.dialect.name, + requires_fold_postprocessing=isinstance(schema_info.dialect, MSDialect), + ) + else: + if backend_type == CYPHER_LANGUAGE: + compilation_result = compile_graphql_to_cypher( + schema_info, query_and_parameters.query_string + ) + elif backend_type == GREMLIN_LANGUAGE: + compilation_result = compile_graphql_to_gremlin( + schema_info, query_and_parameters.query_string + ) + elif backend_type == MATCH_LANGUAGE: + compilation_result = compile_graphql_to_match( + schema_info, query_and_parameters.query_string + ) + # TODO: add InterpreterAdapter based backends + else: + raise AssertionError(f"Unknown non-SQL backend_type {backend_type}.") + # Extract the query and create provider_metadata (process is the same across Cypher, + # Gremlin, and Match backends) + query = compilation_result.query + provider_metadata = ProviderMetadata( + backend_type=backend_type, requires_fold_postprocessing=False + ) - return QueryPlanDescriptor( - root_sub_query_plan=root_sub_query_plan, - intermediate_output_names=intermediate_output_names, - output_join_descriptors=output_join_descriptors, + return SimpleExecute( + str(uuid4()), + provider_id, + provider_metadata, + query, + query_and_parameters.parameters, + compilation_result.output_metadata, + compilation_result.input_metadata, ) @@ -291,6 +327,118 @@ def _get_in_collection_filter_directive(input_filter_name: str) -> DirectiveNode ) +def _get_plan_and_depth_in_dfs_order(query_plan: SubQueryPlan) -> List[Tuple[SubQueryPlan, int]]: + """Return a list of topologically sorted (query plan, depth) tuples.""" + + def _get_plan_and_depth_in_dfs_order_helper( + query_plan: SubQueryPlan, depth: int + ) -> List[Tuple[SubQueryPlan, int]]: + plan_and_depth_in_dfs_order = [(query_plan, depth)] + for child_query_plan in query_plan.child_query_plans: + plan_and_depth_in_dfs_order.extend( + _get_plan_and_depth_in_dfs_order_helper(child_query_plan, depth + 1) + ) + return plan_and_depth_in_dfs_order + + return _get_plan_and_depth_in_dfs_order_helper(query_plan, 0) + + +###### +# Public API +###### + + +def generate_query_plan( + query_and_parameters: QueryStringWithParameters, + provider_id: str, + *, + desired_page_size: Optional[int] = 5000, +) -> QueryPlan: + """Generate a query plan for query_and_parameters targeting data source 'provider_id'. + + Note: this functionality is intended to replace make_query_plan below. TODO(selene) + + Args: + query_and_parameters: the query and parameters for which to create a QueryPlan. + provider_id: the identifier of the provider to be queried. + desired_page_size: target page size. None indicates that pagination should be disabled. + The benefit of pagination with this sync executor is that: + - You can receive results as they arrive (see execute_query_plan) + - The load on the database is more controlled, and timeouts are + less likely. + The desired_page_size is fulfilled on a best-effort basis; setting + a particular desired_page_size does not guarantee pages of precisely + that size. Setting desired_page_size to a number less than 1000 is + not recommended, since the server is unlikely to have sufficiently + granular statistics to accomplish that, and the client is more likely + to fetch many entirely empty pages. Note: this is not currently used, + but will be in the near future! TODO(selene): implement ParallelPaginated + + Returns: + QueryPlan for the given query_and_parameters against the data source specified + by the provider_id. + + """ + # TODO: look up schema_info and backend_type by provider_id + schema_info = None + backend_type = None + query_plan_node = _make_simple_execute_node( + query_and_parameters, schema_info, provider_id, backend_type + ) + + version = 1 + return QueryPlan( + version, + provider_id, + query_and_parameters.query_string, + query_and_parameters.parameters, + desired_page_size, + query_plan_node.output_metadata, + query_plan_node, + ) + + +def make_query_plan( + root_sub_query_node: SubQueryNode, intermediate_output_names: FrozenSet[str] +) -> QueryPlanDescriptor: + """Return a QueryPlanDescriptor, whose query ASTs have @filters added. + + For each parent of parent and child SubQueryNodes, a new @filter directive will be added + in the child AST. It will be added on the field whose @output directive has the out_name + equal to the child's out name as specified in the QueryConnection. The newly added @filter + will be a 'in_collection' type filter, and the name of the local variable is guaranteed to + be the same as the out_name of the @output on the parent. + + ASTs contained in the input node and its children nodes will not be modified. + + Args: + root_sub_query_node: representing the base of a query split into pieces + that we want to turn into a query plan. + intermediate_output_names: names of outputs to be removed at the end. + + Returns: + QueryPlanDescriptor containing a tree of SubQueryPlans that wrap around each individual + query AST, the set of intermediate output names that are to be removed at the end, and + information on which outputs are to be connect to which in what manner. + """ + output_join_descriptors: List[OutputJoinDescriptor] = [] + + root_sub_query_plan = SubQueryPlan( + query_ast=root_sub_query_node.query_ast, + schema_id=root_sub_query_node.schema_id, + parent_query_plan=None, + child_query_plans=[], + ) + + _make_query_plan_recursive(root_sub_query_node, root_sub_query_plan, output_join_descriptors) + + return QueryPlanDescriptor( + root_sub_query_plan=root_sub_query_plan, + intermediate_output_names=intermediate_output_names, + output_join_descriptors=output_join_descriptors, + ) + + def print_query_plan(query_plan_descriptor: QueryPlanDescriptor, indentation_depth: int = 4) -> str: """Return a string describing query plan.""" query_plan_strings = [""] @@ -311,17 +459,3 @@ def print_query_plan(query_plan_descriptor: QueryPlanDescriptor, indentation_dep query_plan_strings.append(str(query_plan_descriptor.intermediate_output_names) + "\n") return "".join(query_plan_strings) - - -def _get_plan_and_depth_in_dfs_order(query_plan: SubQueryPlan) -> List[Tuple[SubQueryPlan, int]]: - """Return a list of topologically sorted (query plan, depth) tuples.""" - - def _get_plan_and_depth_in_dfs_order_helper(query_plan, depth): - plan_and_depth_in_dfs_order = [(query_plan, depth)] - for child_query_plan in query_plan.child_query_plans: - plan_and_depth_in_dfs_order.extend( - _get_plan_and_depth_in_dfs_order_helper(child_query_plan, depth + 1) - ) - return plan_and_depth_in_dfs_order - - return _get_plan_and_depth_in_dfs_order_helper(query_plan, 0) diff --git a/graphql_compiler/query_planning_and_execution/sync_query_execution.py b/graphql_compiler/query_planning_and_execution/sync_query_execution.py index b9af2f666..13de793a7 100644 --- a/graphql_compiler/query_planning_and_execution/sync_query_execution.py +++ b/graphql_compiler/query_planning_and_execution/sync_query_execution.py @@ -15,17 +15,12 @@ def _execute_simple_execute_node( arguments = deserialize_multiple_arguments( query_plan_node.arguments, query_plan_node.input_metadata ) + requires_postprocessing = query_plan_node.provider_metadata.requires_fold_postprocessing with provider_client_maker() as query_client: result = query_client(query_plan_node.query, query_plan_node.input_metadata, arguments) + # Apply post processing for MSSQL folds if applicable. - requires_postprocessing = False - for _, output_metadata in query_plan_node.output_metadata.items(): - if ( - output_metadata.provider_metadata is not None - and output_metadata.provider_metadata.requires_fold_postprocessing - ): - requires_postprocessing = True if requires_postprocessing: list_dict_result = [dict(entry) for entry in result] post_process_mssql_folds(list_dict_result, query_plan_node.output_metadata) diff --git a/graphql_compiler/query_planning_and_execution/typedefs.py b/graphql_compiler/query_planning_and_execution/typedefs.py index d46aec8b8..30a964308 100644 --- a/graphql_compiler/query_planning_and_execution/typedefs.py +++ b/graphql_compiler/query_planning_and_execution/typedefs.py @@ -8,6 +8,7 @@ from graphql import ( GraphQLList, GraphQLNonNull, + GraphQLScalarType, GraphQLType, ListTypeNode, NamedTypeNode, @@ -18,12 +19,13 @@ ) from .. import GraphQLDate, GraphQLDateTime, GraphQLDecimal -from ..compiler.compiler_frontend import OutputMetadata, ProviderMetadata +from ..compiler.compiler_frontend import OutputMetadata from ..global_utils import is_same_type +from ..typedefs import QueryArgumentGraphQLType QueryExecutionFunc = Callable[ - [str, Dict[str, GraphQLType], Dict[str, Any]], Iterable[Mapping[str, Any]] + [str, Dict[str, QueryArgumentGraphQLType], Dict[str, Any]], Iterable[Mapping[str, Any]] ] # Custom scalar types. @@ -48,7 +50,7 @@ def _type_from_scalar_type_dictionary( - scalar_types: Dict[str, GraphQLType], type_node: TypeNode + scalar_types: Dict[str, GraphQLScalarType], type_node: TypeNode ) -> GraphQLType: """Get the GraphQL type definition from an AST node. @@ -63,7 +65,7 @@ def _type_from_scalar_type_dictionary( and allows for custom scalar types without constructing an entire schema. Args: - scalar_types: dictionary mapping type name to GraphQLType + scalar_types: dictionary mapping type name to GraphQLScalarType type_node: AST node describing a type Returns: @@ -96,7 +98,7 @@ def _type_from_scalar_type_dictionary( def _serialize_output_metadata_field( - output_metadata_dictionary: Optional[Dict[str, "OutputMetadata"]] + output_metadata_dictionary: Optional[Dict[str, OutputMetadata]] ) -> Optional[Dict[str, Dict[str, Any]]]: """Serialize OutputMetadata into a dictionary.""" if not output_metadata_dictionary: @@ -104,17 +106,16 @@ def _serialize_output_metadata_field( dictionary_value = {} for output_name, output_metadata in output_metadata_dictionary.items(): dictionary_value[output_name] = { - "graphql_type": str(output_metadata["graphql_type"]), - "optional": output_metadata["optional"], - "folded": output_metadata["folded"], - "provider_metadata": output_metadata["provider_metadata"], + "type": str(output_metadata.type), + "optional": output_metadata.optional, + "folded": output_metadata.folded, } return dictionary_value def _deserialize_output_metadata_field( dict_value: Optional[Dict[str, Dict[str, Any]]] -) -> Optional[Dict[str, "OutputMetadata"]]: +) -> Optional[Dict[str, OutputMetadata]]: """Deserialize the dictionary representation of OutputMetadata.""" if not dict_value: return None @@ -122,16 +123,10 @@ def _deserialize_output_metadata_field( for output_name, output_metadata in dict_value.items(): output_metadata_dictionary[output_name] = OutputMetadata( type=_type_from_scalar_type_dictionary( - ALL_SCALAR_TYPES, parse_type(output_metadata["graphql_type"]) + ALL_SCALAR_TYPES, parse_type(output_metadata["type"]) ), optional=output_metadata["optional"], folded=output_metadata["folded"], - provider_metadata=ProviderMetadata( - backend_type=output_metadata["provider_metadata"]["backend_type"], - requires_fold_postprocessing=output_metadata["provider_metadata"][ - "requires_fold_postprocessing" - ], - ), ) return output_metadata_dictionary @@ -171,8 +166,8 @@ def _deserialize_input_metadata_field( def _compare_input_metadata_field( - left_input_metadata: Optional[Dict[str, GraphQLType]], - right_input_metadata: Optional[Dict[str, GraphQLType]], + left_input_metadata: Optional[Dict[str, QueryArgumentGraphQLType]], + right_input_metadata: Optional[Dict[str, QueryArgumentGraphQLType]], ) -> bool: """Check input_metadata SimpleExecute field equality, comparing GraphQLTypes appropriately.""" if left_input_metadata is None: @@ -200,11 +195,29 @@ def _compare_input_metadata_field( return True +def _deserialize_independent_query_plan_field(dict_value: Dict[str, Any]) -> "IndependentQueryPlan": + """Deserialize the dict representation of IndependentQueryPlan.""" + # Note: there will be more types of IndependentQueryPlans that will require different + # deserialization shortly. + return SimpleExecute.from_dict(dict_value) + + # ############ # Public API # # ############ +@dataclass(init=True, repr=True, eq=True, frozen=True) +class ProviderMetadata(DataClassJsonMixin): + """Metadata about the provider.""" + + # Name of the type of provider (ex. PostgreSQL, Cypher, etc). + backend_type: str + + # Whether this backend requires MSSQL fold postprocessing for folded outputs. + requires_fold_postprocessing: bool + + @dataclass(init=True, repr=True, eq=True, frozen=True) class QueryPlanNode(DataClassJsonMixin, metaclass=ABCMeta): """Abstract query plan node. May or may not contain other nodes, depending on its type.""" @@ -220,6 +233,7 @@ class SimpleExecute(QueryPlanNode): """Just give the specified query and args to the provider, it'll execute it for you as-is.""" provider_id: str + provider_metadata: ProviderMetadata query: str # in whatever query language the provider will accept (not necessarily GraphQL) arguments: Dict[str, Any] @@ -229,7 +243,7 @@ class SimpleExecute(QueryPlanNode): encoder=_serialize_output_metadata_field, decoder=_deserialize_output_metadata_field ) ) - input_metadata: Dict[str, GraphQLType] = field( + input_metadata: Dict[str, QueryArgumentGraphQLType] = field( metadata=config( encoder=_serialize_input_metadata_field, decoder=_deserialize_input_metadata_field ) @@ -249,3 +263,33 @@ def __eq__(self, other: Any) -> bool: and self.output_metadata == other.output_metadata and _compare_input_metadata_field(self.input_metadata, other.input_metadata) ) + + +# More types of IndependentQueryPlans will be added in the future. +IndependentQueryPlan = SimpleExecute + + +@dataclass(init=True, repr=True, eq=True, frozen=True) +class QueryPlan(DataClassJsonMixin): + """A description of the execution of a GraphQL query, including pagination and joins.""" + + # Version number, so we can make breaking changes without requiring lock-step upgrades. + # Clients should report supported version ranges when requesting a plan, and the server + # should pick the highest version that is supported by both client and server. + version: int + + # Metadata on which provider produced the plan, and for what inputs. + provider_id: str + input_graphql_query: str + input_parameters: Dict[str, Any] + desired_page_size: Optional[int] + output_metadata: Dict[str, OutputMetadata] = field( + metadata=config( + encoder=_serialize_output_metadata_field, decoder=_deserialize_output_metadata_field + ) + ) + + # The actual query plan. + plan_root_node: IndependentQueryPlan = field( + metadata=config(decoder=_deserialize_independent_query_plan_field) + ) diff --git a/graphql_compiler/schema/schema_info.py b/graphql_compiler/schema/schema_info.py index da44351fa..1a2d8e312 100644 --- a/graphql_compiler/schema/schema_info.py +++ b/graphql_compiler/schema/schema_info.py @@ -162,12 +162,13 @@ class SQLSchemaInfo(BackendSpecificSchemaInfo): # Specifying the dialect for which we are compiling # e.g. sqlalchemy.dialects.mssql.dialect() dialect: Dialect + # dict mapping every GraphQL object type or interface type name in the schema to # a sqlalchemy table. # Column types that do not exist for this dialect are not allowed. # All tables are expected to have primary keys. - vertex_name_to_table: Dict[str, sqlalchemy.Table] + # dict mapping every GraphQL object type or interface type name in the schema to # dict mapping every vertex field name at that type to a JoinDescriptor. # The tables the join is to be performed on are not specified. @@ -226,56 +227,63 @@ def _create_sql_schema_info( ) -# Complete schema information sufficient to compile GraphQL queries to SQLAlchemy -# -# It describes the tables that correspond to each type (object type or interface type), -# and gives instructions on how to perform joins for each vertex field. The property fields on each -# type are implicitly mapped to columns with the same name on the corresponding table. -# -# NOTES: -# - RootSchemaQuery is a special type that does not need a corresponding table. -# - Builtin types like __Schema, __Type, etc. don't need corresponding tables. -# - Builtin fields like _x_count do not need corresponding columns. -SQLAlchemySchemaInfo = namedtuple( - "SQLAlchemySchemaInfo", - ( - # GraphQLSchema - "schema", - # optional dict of GraphQL interface or type -> GraphQL union. - # Used as a workaround for GraphQL's lack of support for - # inheritance across "types" (i.e. non-interfaces), as well as a - # workaround for Gremlin's total lack of inheritance-awareness. - # The key-value pairs in the dict specify that the "key" type - # is equivalent to the "value" type, i.e. that the GraphQL type or - # interface in the key is the most-derived common supertype - # of every GraphQL type in the "value" GraphQL union. - # Recursive expansion of type equivalence hints is not performed, - # and only type-level correctness of this argument is enforced. - # See README.md for more details on everything this parameter does. - # ***** - # Be very careful with this option, as bad input here will - # lead to incorrect output queries being generated. - # ***** - "type_equivalence_hints", - # sqlalchemy.engine.interfaces.Dialect, specifying the dialect we are compiling for - # (e.g. sqlalchemy.dialects.mssql.dialect()). - "dialect", - # dict mapping every graphql object type or interface type name in the schema to - # a sqlalchemy table. Column types that do not exist for this dialect are not allowed. - # All tables are expected to have primary keys. - "vertex_name_to_table", - # dict mapping every graphql object type or interface type name in the schema to: - # dict mapping every vertex field name at that type to a JoinDescriptor. The - # tables the join is to be performed on are not specified. They are inferred from - # the schema and the tables dictionary. - "join_descriptors", - ), -) +@dataclass +class SQLAlchemySchemaInfo: + """Complete schema information sufficient to compile GraphQL queries to SQLAlchemy. + + It describes the tables that correspond to each type (object type or interface type), + and gives instructions on how to perform joins for each vertex field. The property fields on + each type are implicitly mapped to columns with the same name on the corresponding table. + + Notes: + - RootSchemaQuery is a special type that does not need a corresponding table. + - Builtin types like __Schema, __Type, etc. don't need corresponding tables. + - Builtin fields like _x_count do not need corresponding columns. + + TODO: This class is essentially the same as SQLSchemaInfo. SQLSchemaInfo is part of an + incomplete refactor started in + https://github.com/kensho-technologies/graphql-compiler/pull/714 + SQLAlchemySchemaInfo is currently used to compile GraphQL to SQL while CommonSchemaInfo + is currently used to compile GraphQL to match, gremlin, and cypher. + """ + + schema: GraphQLSchema + + # Optional dict of GraphQL interface or type -> GraphQL union. + # Used as a workaround for GraphQL's lack of support for + # inheritance across "types" (i.e. non-interfaces), as well as a + # workaround for Gremlin's total lack of inheritance-awareness. + # The key-value pairs in the dict specify that the "key" type + # is equivalent to the "value" type, i.e. that the GraphQL type or + # interface in the key is the most-derived common supertype + # of every GraphQL type in the "value" GraphQL union. + # Recursive expansion of type equivalence hints is not performed, + # and only type-level correctness of this argument is enforced. + # See README.md for more details on everything this parameter does. + # ***** + # Be very careful with this option, as bad input here will + # lead to incorrect output queries being generated. + # ***** + type_equivalence_hints: Optional[TypeEquivalenceHintsType] + + # Specifying the SQL Dialect. + dialect: Dialect + + # Mapping every GraphQL object or interface type name in the schema to the corresponding + # SQLAlchemy table. Column types that do not exist for this dialect are not allowed. + # All tables are expected to have primary keys. + vertex_name_to_table: Dict[str, sqlalchemy.Table] + + # Mapping every GraphQL object or interface type name in the schema to: + # dict mapping every vertex field name at that type to a JoinDescriptor. The + # tables the join is to be performed on are not specified. They are inferred from + # the schema and the tables dictionary. + join_descriptors: Dict[str, Dict[str, JoinDescriptor]] def make_sqlalchemy_schema_info( schema: GraphQLSchema, - type_equivalence_hints: TypeEquivalenceHintsType, + type_equivalence_hints: Optional[TypeEquivalenceHintsType], dialect: Dialect, vertex_name_to_table: Dict[str, sqlalchemy.Table], join_descriptors: Dict[str, Dict[str, JoinDescriptor]], diff --git a/graphql_compiler/schema/typedefs.py b/graphql_compiler/schema/typedefs.py index d00ad7426..10304b190 100644 --- a/graphql_compiler/schema/typedefs.py +++ b/graphql_compiler/schema/typedefs.py @@ -19,7 +19,7 @@ # Dict of GraphQL type name -> (Dict of field name on that type -> the desired type of the field) ClassToFieldTypeOverridesType = Dict[str, Dict[str, GraphQLSchemaFieldType]] -# The type of the type equivalence hints object, which defines which GraphQL intefaces and object +# The type of the type equivalence hints object, which defines which GraphQL interfaces and object # types should be considered equivalent to which union types. This is our workaround for the lack # of interface-interface and object-object inheritance. TypeEquivalenceHintsType = Dict[Union[GraphQLInterfaceType, GraphQLObjectType], GraphQLUnionType] diff --git a/graphql_compiler/tests/schema_generation_tests/test_sqlalchemy_schema_generation.py b/graphql_compiler/tests/schema_generation_tests/test_sqlalchemy_schema_generation.py index d6af1cc7a..8883fc4d6 100644 --- a/graphql_compiler/tests/schema_generation_tests/test_sqlalchemy_schema_generation.py +++ b/graphql_compiler/tests/schema_generation_tests/test_sqlalchemy_schema_generation.py @@ -112,12 +112,26 @@ def test_table_vertex_representation_with_non_default_name(self) -> None: def test_represent_supported_fields(self) -> None: table1_graphql_object = self.schema_info.schema.get_type("Table1") + # mypy complained even with self.assertIsInstance(table1_graphql_object, GraphQLObjectType) + # so performing a manual check. + if not isinstance(table1_graphql_object, GraphQLObjectType): + raise AssertionError( + f"table1_graphql_object expected to be GraphQLObjectType, but was of type " + f"{type(table1_graphql_object)}" + ) self.assertEqual( table1_graphql_object.fields["column_with_supported_type"].type, GraphQLString ) def test_ignored_fields_not_supported(self) -> None: table1_graphql_object = self.schema_info.schema.get_type("Table1") + # mypy complained even with self.assertIsInstance(table1_graphql_object, GraphQLObjectType) + # so performing a manual check. + if not isinstance(table1_graphql_object, GraphQLObjectType): + raise AssertionError( + f"table1_graphql_object expected to be GraphQLObjectType, but was of type " + f"{type(table1_graphql_object)}" + ) self.assertTrue("column_with_non_supported_type" not in table1_graphql_object.fields) def test_warn_when_type_is_not_supported(self) -> None: @@ -140,11 +154,30 @@ def test_do_not_support_sql_tz_aware_datetime_types(self) -> None: def test_mssql_scalar_type_representation(self) -> None: table1_graphql_object = self.schema_info.schema.get_type("Table1") + # mypy complained even with self.assertIsInstance(table1_graphql_object, GraphQLObjectType) + # so performing a manual check. + if not isinstance(table1_graphql_object, GraphQLObjectType): + raise AssertionError( + f"table1_graphql_object expected to be GraphQLObjectType, but was of type " + f"{type(table1_graphql_object)}" + ) self.assertEqual(table1_graphql_object.fields["column_with_mssql_type"].type, GraphQLInt) def test_direct_sql_edge_representation(self) -> None: table1_graphql_object = self.schema_info.schema.get_type("Table1") arbitrarily_named_graphql_object = self.schema_info.schema.get_type("ArbitraryObjectName") + # mypy complained even with self.assertIsInstance(table1_graphql_object, GraphQLObjectType) + # so performing a manual check. + if not isinstance(table1_graphql_object, GraphQLObjectType): + raise AssertionError( + f"table1_graphql_object expected to be GraphQLObjectType, but was of type " + f"{type(table1_graphql_object)}" + ) + if not isinstance(arbitrarily_named_graphql_object, GraphQLObjectType): + raise AssertionError( + f"arbitrarily_named_graphql_object expected to be GraphQLObjectType, but was of " + f"type {type(arbitrarily_named_graphql_object)}" + ) self.assertEqual( table1_graphql_object.fields["out_test_edge"].type.of_type.name, "ArbitraryObjectName" ) From c31d8f1a8f022e6df3dc4ade7a1878cb0af3cfa3 Mon Sep 17 00:00:00 2001 From: Selene Chew Date: Fri, 16 Apr 2021 12:49:44 -0400 Subject: [PATCH 3/5] add execute_query_plan --- .../make_query_plan.py | 4 ++-- .../sync_query_execution.py | 22 ++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/graphql_compiler/query_planning_and_execution/make_query_plan.py b/graphql_compiler/query_planning_and_execution/make_query_plan.py index d2e60be9e..11bff1142 100644 --- a/graphql_compiler/query_planning_and_execution/make_query_plan.py +++ b/graphql_compiler/query_planning_and_execution/make_query_plan.py @@ -122,7 +122,7 @@ def _make_simple_execute_node( compilation_result = compile_graphql_to_match( schema_info, query_and_parameters.query_string ) - # TODO: add InterpreterAdapter based backends + # TODO(selene): add InterpreterAdapter based backends else: raise AssertionError(f"Unknown non-SQL backend_type {backend_type}.") # Extract the query and create provider_metadata (process is the same across Cypher, @@ -379,7 +379,7 @@ def generate_query_plan( by the provider_id. """ - # TODO: look up schema_info and backend_type by provider_id + # TODO(selene): look up schema_info and backend_type by provider_id schema_info = None backend_type = None query_plan_node = _make_simple_execute_node( diff --git a/graphql_compiler/query_planning_and_execution/sync_query_execution.py b/graphql_compiler/query_planning_and_execution/sync_query_execution.py index 13de793a7..e8453bf90 100644 --- a/graphql_compiler/query_planning_and_execution/sync_query_execution.py +++ b/graphql_compiler/query_planning_and_execution/sync_query_execution.py @@ -3,7 +3,7 @@ from ..post_processing.sql_post_processing import post_process_mssql_folds from ..query_formatting.common import deserialize_multiple_arguments -from .typedefs import QueryExecutionFunc, SimpleExecute +from .typedefs import QueryExecutionFunc, QueryPlan, SimpleExecute def _execute_simple_execute_node( @@ -28,3 +28,23 @@ def _execute_simple_execute_node( # Otherwise, return result as is. return result + + +###### +# Public API +###### + + +def execute_query_plan( + provider_client_makers: Dict[str, Callable[[], ContextManager[QueryExecutionFunc]]], + query_plan: QueryPlan, +) -> Iterable[Mapping[str, Any]]: + """Execute a QueryPlan.""" + # TODO(selene): figure out if we want some sort of class to contain provider_client_makers + if isinstance(query_plan.plan_root_node, SimpleExecute): + return _execute_simple_execute_node(provider_client_makers, query_plan.plan_root_node) + else: + raise NotImplementedError( + f"Received plan_root_node with unsupported type " + f"{type(query_plan.plan_root_node)}: {query_plan.plan_root_node}" + ) From ba8c663c688e11f117dead8bda0f06df07da043c Mon Sep 17 00:00:00 2001 From: Selene Chew Date: Mon, 26 Apr 2021 13:36:29 -0400 Subject: [PATCH 4/5] fix spacing --- graphql_compiler/query_planning/typedefs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graphql_compiler/query_planning/typedefs.py b/graphql_compiler/query_planning/typedefs.py index 9f2e4e62a..ce10bb6cb 100644 --- a/graphql_compiler/query_planning/typedefs.py +++ b/graphql_compiler/query_planning/typedefs.py @@ -209,6 +209,7 @@ def _deserialize_independent_query_plan_field(dict_value: Dict[str, Any]) -> "In # Public API # # ############ + @unique class BackendType(Enum): # N.B.: The values of the enums are the "human-friendly" display names. Since they are shown From 646db34d4205d3cfb27c08653694372dc55a0854 Mon Sep 17 00:00:00 2001 From: Selene Chew Date: Mon, 26 Apr 2021 18:09:00 -0400 Subject: [PATCH 5/5] use enums for backend type --- .../query_planning/make_query_plan.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/graphql_compiler/query_planning/make_query_plan.py b/graphql_compiler/query_planning/make_query_plan.py index 11bff1142..fea2be85f 100644 --- a/graphql_compiler/query_planning/make_query_plan.py +++ b/graphql_compiler/query_planning/make_query_plan.py @@ -18,13 +18,9 @@ StringValueNode, ) from graphql.pyutils import FrozenList -from sqlalchemy.dialects.mssql.base import MSDialect from ..ast_manipulation import get_only_query_definition from ..compiler.common import ( - CYPHER_LANGUAGE, - GREMLIN_LANGUAGE, - MATCH_LANGUAGE, compile_graphql_to_cypher, compile_graphql_to_gremlin, compile_graphql_to_match, @@ -36,7 +32,7 @@ from ..schema import FilterDirective, OutputDirective from ..schema.schema_info import CommonSchemaInfo, SQLAlchemySchemaInfo from ..schema_transformation.split_query import AstType, SubQueryNode -from .typedefs import ProviderMetadata, QueryPlan, SimpleExecute +from .typedefs import BackendType, ProviderMetadata, QueryPlan, SimpleExecute @dataclass @@ -85,7 +81,7 @@ def _make_simple_execute_node( query_and_parameters: QueryStringWithParameters, schema_info: Union[CommonSchemaInfo, SQLAlchemySchemaInfo], provider_id: str, - backend_type: Optional[str], + backend_type: BackendType, ) -> SimpleExecute: """Generate a SimpleExecuteNode. @@ -93,32 +89,44 @@ def _make_simple_execute_node( query_and_parameters: the query and parameters for which to create a SimpleExecute. schema_info: schema information to use for query compilation. provider_id: the identifier of the provider to be queried. - backend_type: the backend type. Note: this is inferred from the SQLAlchemySchemaInfo's - dialect for SQL backends. + backend_type: the backend type. Note: if schema_info is of type SQLAlchemySchemaInfo, the + backend_type must be a flavor of SQL. Returns: SimpleExecute with all necessary information to execute the given query_and_parameters. """ + # Ensure that if a SQL schema info object is passed, that the backend type is a SQL dialect. if isinstance(schema_info, SQLAlchemySchemaInfo): # Compiled SQL query. compilation_result = compile_graphql_to_sql(schema_info, query_and_parameters.query_string) query = print_sqlalchemy_query_string(compilation_result.query, schema_info.dialect) + # Determine if the SQL dialect requires post-processing. + if backend_type == BackendType.mssql: + requires_fold_postprocessing = True + elif backend_type == BackendType.postgresql: + requires_fold_postprocessing = False + else: + raise AssertionError( + f"Received SQLAlchemySchemaInfo, but a non-SQL backend type {backend_type}." + ) + provider_metadata = ProviderMetadata( - backend_type=schema_info.dialect.name, - requires_fold_postprocessing=isinstance(schema_info.dialect, MSDialect), + backend_type=backend_type, + requires_fold_postprocessing=requires_fold_postprocessing, ) + # All other backends use CommonSchemaInfo. else: - if backend_type == CYPHER_LANGUAGE: + if backend_type == BackendType.cypher: compilation_result = compile_graphql_to_cypher( schema_info, query_and_parameters.query_string ) - elif backend_type == GREMLIN_LANGUAGE: + elif backend_type == BackendType.gremlin: compilation_result = compile_graphql_to_gremlin( schema_info, query_and_parameters.query_string ) - elif backend_type == MATCH_LANGUAGE: + elif backend_type == BackendType.match: compilation_result = compile_graphql_to_match( schema_info, query_and_parameters.query_string )