Skip to content
New issue

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

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

Already on GitHub? # to your account

Compile time checked queries #161

Open
melkir opened this issue Aug 6, 2022 · 5 comments
Open

Compile time checked queries #161

melkir opened this issue Aug 6, 2022 · 5 comments

Comments

@melkir
Copy link

melkir commented Aug 6, 2022

Would it be possible to support something similar to sqlx?

I think it could be really nice to have this feature when it comes to have a great developer experience 🙂

https://github.com/launchbadge/sqlx#sqlx-is-not-an-orm

@tailhook
Copy link
Contributor

tailhook commented Aug 8, 2022

Sure. This is in our to do list. We don't have a timeline yet, though.

@imbolc
Copy link

imbolc commented Feb 6, 2023

Just to mention it's the only thing preventing me from switching from Postgres :)

@xlash123
Copy link

xlash123 commented May 16, 2024

Adding my +1 for support of this feature. Compile-time guarantees are my biggest reason for using Rust, and I would love to see EdgeDB take full advantage of that. As a newbie to EdgeDB, this would also be extremely helpful for getting me getting used to the query language.

@MarioIshac
Copy link

Also adding +1, this feature would provide a great end-to-end experience with the static typing of Rust and EdgeDB.

@ifiokjr
Copy link
Contributor

ifiokjr commented Aug 26, 2024

@melkir @imbolc @tailhook @MarioIshac I've just published a tool I've been using internally this week.

https://github.com/ifiokjr/edgedb_codegen

It's still very early and I'm eager for feedback.

The types are currently generated from a running instance of the edgedb instance using the environment variable EDGEDB_INSTANCE. Once this is in place it will automatically verify the queries written.

Inline Queries

use edgedb_codegen::edgedb_query;
use edgedb_errors::Error;
use edgedb_tokio::create_client;

// Creates a module called `simple` with a function called `query` and structs
// for the `Input` and `Output`.
edgedb_query!(
	simple,
	"select {hello := \"world\", custom := <str>$custom }"
);

#[tokio::main]
async fn main() -> Result<(), Error> {
	let client = create_client().await?;
	let input = simple::Input::builder().custom("custom").build();

	// For queries the following code can be used.
	let output = simple::query(&client, &input).await?;

	Ok(())
}

The macro above generates the following code:

pub mod simple {
	use ::edgedb_codegen::exports as e;
	#[doc = r" Execute the desired query."]
	#[cfg(feature = "query")]
	pub async fn query(
		client: &e::edgedb_tokio::Client,
		props: &Input,
	) -> core::result::Result<Output, e::edgedb_errors::Error> {
		client.query_required_single(QUERY, props).await
	}
	#[doc = r" Compose the query as part of a larger transaction."]
	#[cfg(feature = "query")]
	pub async fn transaction(
		conn: &mut e::edgedb_tokio::Transaction,
		props: &Input,
	) -> core::result::Result<Output, e::edgedb_errors::Error> {
		conn.query_required_single(QUERY, props).await
	}
	#[derive(Clone, Debug, e :: typed_builder :: TypedBuilder)]
	#[cfg_attr(feature = "serde", derive(e::serde::Serialize, e::serde::Deserialize))]
	#[cfg_attr(feature = "query", derive(e::edgedb_derive::Queryable))]
	pub struct Input {
		#[builder(setter(into))]
		pub custom: String,
	}
	impl e::edgedb_protocol::query_arg::QueryArgs for Input {
		fn encode(
			&self,
			encoder: &mut e::edgedb_protocol::query_arg::Encoder,
		) -> core::result::Result<(), e::edgedb_errors::Error> {
			let map = e::edgedb_protocol::named_args! { "custom" => self . custom . clone () , };
			map.encode(encoder)
		}
	}
	#[derive(Clone, Debug, e :: typed_builder :: TypedBuilder)]
	#[cfg_attr(feature = "serde", derive(e::serde::Serialize, e::serde::Deserialize))]
	#[cfg_attr(feature = "query", derive(e::edgedb_derive::Queryable))]
	pub struct Output {
		#[builder(setter(into))]
		pub hello: String,
		#[builder(setter(into))]
		pub custom: String,
	}
	#[doc = r" The original query string provided to the macro. Can be reused in your codebase."]
	pub const QUERY: &str = "select { hello := \"world\", custom := <str>$custom }";
}

Query Files

Define a query file in the queries directory of your crate called select_user.edgeql.

# queries/select_user.edgeql

select User {
  name,
  bio,
  slug,
} filter .slug = <str>$slug;

Then use the edgedb_query macro to import the query.

use edgedb_codegen::edgedb_query;
use edgedb_errors::Error;
use edgedb_tokio::create_client;

// Creates a module called `select_user` with public functions `transaction` and
// `query` as well as structs for the `Input` and `Output`.
edgedb_query!(select_user);

#[tokio::main]
async fn main() -> Result<(), Error> {
	let client = create_client().await?;

	// Generated code can be run inside a transaction.
	let result = client
		.transaction(|mut txn| {
			async move {
				let input = select_user::Input::builder().slug("test").build();
				let output = select_user::transaction(&mut txn, &input).await?;
				Ok(output)
			}
		})
		.await?;

	Ok(())
}

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants