Replies: 2 comments
-
Thanks for writing this down 👍 I will try to have a look at it. |
Beta Was this translation helpful? Give feedback.
-
So I had some more time to think about this. Generally speaking I see the use case for this and I agree that we can provide something to solve this issue at least partially. It's up to discussion of everything of this solution should be part of diesel itself, or if some parts should better live in a distinct crate. Now let me come to the details. It's definitively good to have some code around here, so that we could talk already about specific things.
That's a good choice in my opinion.
(I would likely choose the explicit constructor method)
That's reasonable and that's something that should be provided by diesel itself. The question is if we provide a trait StatementType {
type Type: StatementTypeChoice;
}
struct Read;
impl StatementTypeChoice for Read{ };
struct Write;
impl StatementTypeChoice for Write {};
struct Mixed;
impl StatementTypeChoice for Mixed{} That would allow a more fine grained classification of existing types. For example marking
That's the point I do dislike most with the current proposed implementation. I would like to see if there is an alternative way here without duplicating. Not sure about possible options here, although…
I would be interested about knowing something more about why it did not work at all. Maybe there are some tweaks to the |
Beta Was this translation helpful? Give feedback.
-
Motivation
It can be useful when implementing a business application to differentiate between actions that are read-only and actions that are read+write. This gives us more type-safety when working with databases, which is IMO what Diesel is all about. It transforms a fair amount of potential security bugs and smelly design into compile-time errors.
This suggestion aims to experiment with a design that makes it a compile-error when a read-only connection tries to launch an insert / update / delete SQL statement.
Basic implementation here.
Example
For example with the suggested design, this compiles:
Design
My very first design is based off the idea to have an analog to the
RunQueryDsl
trait but for read-only calls, that prevents one to run a statement that is not read-only in case we're given such a connection. In no way do I claim this design is perfect, but it's worth because it allows me to demonstrate what I mean. I first tried implementing it as a separate crate, but ultimately decided to draft this as a fork of Diesel for easier experimentation. After looking at howdiesel-async
integrates with the rest of the query builder, I actually do believe it's feasible.To my knowledge, there is no such thing as "read-only queries" in SQL. The way I would typically prevent an SQL connection from inserting something (at runtime) is by connecting with a user that has been granted certain permissions, but of course it's not Diesel's responsibility to know about any of this. This means in my opinion that this feature can only realistically be implemented as an extension to
query_dsl
and possiblyquery_builder
. So here's what I have on my branch :ReadOnly<C>
is a wrapper for a connection that makes it read-only (so, it blocks certain statements from compiling)ReadOnlyExt
but to be fair it can be replaced with afn read_only(self) -> ReadOnly<Self>
inside theConnection
trait. This is an artifact of me trying to implement this as a separate crate.ReadOnlyStatement
that is implemented for every statement that can be executed on a read-only connectionRunReadOnlyQueryDsl
, which is basically a copy ofRunQueryDsl
but with functions suffixed with_ro
to prevent function names clashes.Pros
Connection
implementorCons
unsafe
_ro
(see example, where I usefirst_ro
instead ofro
). This is a bigger usability concern that I would like to know how to address.ReadOnly<C>
does not implementConnection
. This is to me a big redflag, as aReadOnly<Conn>
should probably still be aConnection
. See alternative ideas for why it isn't the case in my draft.Alternative ideas
ReadOnly
as not only a wrapper for a Connection, but also a wrapper for aBackend
implementor (thinkimpl Connection for ReadOnly<C: Conn> { type Backend = ReadOnly<C::Backend>; [...] }
). Turns out the internals of Diesel makes this impractical because ofAstPass
andAstPassInternals
being generic overDB: Backend
. I could never get it to compile. Maybe someone has relevant comments ?Beta Was this translation helpful? Give feedback.
All reactions