Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a JSON file query persister strategy to relay-compiler #3666

Closed

Conversation

vincentriemer
Copy link
Contributor

NOTE: This is essentially the first time I've developed in Rust so feel free to eviscerate it because...

I have no idea what I'm doing

Summary

Previously in the new rust relay-compiler your only option for persisting queries is to use the remote persister which makes POST requests to a given endpoint with the operation text to be persisted. This PR adds the option to instead persist your queries to a local JSON file in a very similar way to how the JS compiler originally did it.

If you had a basic remote persister config like this:

persistConfig: {
  url: "http://localhost:3000",
  params: {}
}

changing it to a local JSON file persister means updating the config to:

persistConfig: {
  filePath: "./queries.json"
}

When this is enabled and a new query is persisted the compiler will first attempt to read the local file if it exists as a hash map (creating a new one if there is no existing data), hashes the operation text, adds the operation text to the hashmap keyed by the hashed version of the operation, and then serializes & writes the resulting map to the configured path.

Test Plan

Leveraging my own simple repo I've been using for reproducing issues with the new relay compiler, installed the relay-compiler to a version including this PR's changes, and updated the relay.config.js file as such:

module.exports = {
  "src": "./src",
  "schema": "./schema.graphql",
  "artifactDirectory": "./src/__generated__",
  "language": "typescript",
  "persistConfig": {
-    "url": "http://localhost:3000",
-    "params": {}
+    filePath: "./queries.json"
  }
};

Then after running the relay compiler I verified that it created a queries.json file and it contained the expected persisted query json output:

{
  "00dc69b2cfc4c1da3528e9aaf65a05091740fc71": "query AppQuery {\n  viewer {\n    submission(name: \"t3_qvyl2j\") {\n      ...Post_submission\n      id\n    }\n  }\n}\n\nfragment Post_submission on Submission {\n  title\n  author\n  selftext {\n    html\n  }\n}\n"
}

@vincentriemer vincentriemer marked this pull request as ready for review December 1, 2021 21:27
@alunyov
Copy link
Contributor

alunyov commented Dec 2, 2021

Wow! This is great @vincentriemer! As for the first time rust contribution you very much know what you're doing!

I think we also need to test the case with --watch mode. I think we may be adding more queries to this file, essentially for every intermediate, but valid change in the watch mode these queries would get persisted to this file.

Maybe we also need to pass CLI flags to FilePersister - and error if --watch flag is provided (as a simple workaround)?

Copy link
Contributor

@alunyov alunyov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fill free to import this to fbsource, I think there also maybe some internal formatting issues that needs to be fixed.

But this looks great!

/// additional parameters to send.
pub params: FnvIndexMap<String, String>,
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this serde feature, the only problem with it: obscure error messages if both/mixed values are provided.

Comment on lines +19 to +35
pub struct RemotePersistConfig {
/// URL to send a POST request to to persist.
pub url: String,
/// The document will be in a POST parameter `text`. This map can contain
/// additional parameters to send.
pub params: FnvIndexMap<String, String>,
}

pub struct RemotePersister {
config: RemotePersistConfig,
}

impl RemotePersister {
pub fn new(config: RemotePersistConfig) -> Self {
Self { config }
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@josephsavona
Copy link
Contributor

I just quickly skimmed the code but what is the strategy for handling the fact that queries are persisted concurrently?

@alunyov
Copy link
Contributor

alunyov commented Dec 2, 2021

Also, alternative approach is to implement this persisting the way you did here: https://github.com/vincentriemer/new-relay-compiler-issues-repro/blob/main/mockQueryPersistServer.js

But just make this mockQueryPersistServer store the map in memory, and flush it to the file system (periodically, maybe)?

@vincentriemer
Copy link
Contributor Author

I just quickly skimmed the code but what is the strategy for handling the fact that queries are persisted concurrently?

I'm sorry I'm a JS dev what does "concurrently" mean \s

But just make this mockQueryPersistServer store the map in memory, and flush it to the file system (periodically, maybe)?

But more seriously: I think this is the right strategy and I'll update the PR to do this — make it so that it reads the existing file once at initialization time and keeps the persisted query map in memory, flushing back to the file only when a new query is added.

@facebook-github-bot
Copy link
Contributor

@vincentriemer has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@wyattjoh
Copy link
Contributor

It seems as though the hashing mechanism here is locked to SHA1:

fn hash_operation(&self, operation_text: String) -> String {
let mut hash = Sha1::new();
hash.input(&operation_text);
hex::encode(hash.result())
}

Our team has been looking to utilize the generated ID's to use with Apollo Server's Automatic Persisted Queries (or APQ), and they stipulate a required SHA256 hash.

As it seems y'all are already changing this from MD5 to SHA1, is it possible to either:

  1. Change this to SHA256 to support APQ directly
  2. Support changing the hashing algorithm directly using this new FilePersister config

@janicduplessis
Copy link
Contributor

Tested this and works well! Needed to make some changes to fix conflicts (janicduplessis@ae1f783).

This would help a lot migrating from the js compiler. There is actually a lot of complexity that goes into running an http server just to generate a queries json file, especially if you want to abstract away starting the server in background (start server in bg, wait for it to start before running compiler, close server once compiler done). Also making the server somewhat performant by batching writes.

@tbezman
Copy link
Contributor

tbezman commented Jan 27, 2022

@vincentriemer My team could definitely benefit from this. Are you going to continue work or should I drive this to completion?

@alunyov
Copy link
Contributor

alunyov commented Jan 27, 2022

@tbezman @vincentriemer yes, we will include these changes to one of the upcoming releases

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants