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

Provide a better "raw sql" escape hatch for full queries that we don't support in the query builder #650

Closed
sgrif opened this issue Feb 9, 2017 · 1 comment

Comments

@sgrif
Copy link
Member

sgrif commented Feb 9, 2017

Today we have some form of an escape hatch in the form of the sql function. However, it's meant for use with small fragments, not full queries. It's limited because it requires specifying the full SQL type of the output, and cannot handle bind parameters. I'd like to introduce a new function that is a more ergonomic API for raw SQL as an escape hatch for queries we don't support in the query builder yet.

Ultimately I've resisted introducing this, as we want to support all possible safe queries in the query builder eventually, but I think we need a proper escape hatch for us to go 1.0.

I'd like this API to be fully untyped, and named to make it clear that no verification of the query is occurring. For deserialization, it should not go through Queryable, as I do not think enforcing column order is ergonomic when writing raw SQL (and Queryable needs a SQL type to work). Instead I'd like to introduce an UntypedQueryable, which takes a row directly, and is able to fetch fields by name. I'm fine with the API requiring specifying the SQL type on this call if type inference can't pick it up. e.g. let updated_at: NaiveDateTime = row.get::<Timestamp>("updated_at")?. I suspect for most types which only implement FromSql for a single DB type type inference will handle it, but we should explore our options here and see what feels best.

I'm less clear what we should do with bind parameters. I do know that I don't want to go the route that the postgres crate went where we take &[&UntypedToSql], as constructing that value is painful, it forces dynamic dispatch (which then usually also forces boxing), and requires &[] for queries with no bind parameters. We could potentially still pass tuples here as long as type inference does what we want. I also think conn.raw_query("SELECT * FROM users WHERE id = $1").bind(id) could work (as an aside, I think we will likely need to pass the connection in before the bind parameters for this to work, which will lead to a semi-weird api of conn.raw_query().bind().execute()). I'm fine with also sometimes requiring giving a SQL type for the bind here as long as type inference is able to handle the most common cases.

@andy128k
Copy link

andy128k commented May 3, 2017

I'm new to Rust and Diesel. but what I love is type safety and all great things LoadDsl does. That's why I'd prefer "better sql" instead of new untyped API.
Nice ORMs like ActiveRecord and Django's ORM allows to put bits of SQL but keep the rest of code working with typed models.

I'm playing with Diesel and see a lot of limitations (like inability to make multiple joins or multiple belongs_to) and that's ok. Even with raw SQL Diesel is still great.

I created couple workarounds to solve issues with raw sql queries.
First is obvious -- no binds so I just use PQescapeString.
Second one is unpredictable order of columns in queries with *, so this ugliness was born https://gist.github.com/andy128k/b3cae82d13e38f51c4c5bf56f5d6540a

@sgrif sgrif added this to the 1.0 milestone Jun 5, 2017
sgrif added a commit that referenced this issue Sep 29, 2017
`sql` has always been a bit clunky to use if you wanted to write a whole
query with it. You have to specify the SQL type of the query, which also
means you have to list every column out explicitly to ensure they're in
the expected order.

This adds a replacement API that more geared towards writing complete
queries. There are two notable differences. The first is that the SQL
type of each field is specified during deserialization, not when the
query is constructed. The types are never checked anyway (other than to
make sure the deserialization is valid), so it makes sense to defer this
to a place where we can derive it.

The second difference is that columns are deserialized by name, not by
index. This has all of the gotchas that are associated with doing that
with other libraries (name conflicts between tables, etc)

I had originally planned on having `NamedRow::get` optionally also take
a table name. However, PG gives you the OID not the name, so we'd need
to do another query to get the table name. SQLite only gives you the
table/column name if compiled with a flag to enable it, which the system
sqlite3 on mac does not.

For those two reasons, I've opted to use the column name alone. This
means that the derived `QueryableByName` is unlikely to be usable with
joins, but that's a price we'll have to pay for now.

I'd like to expand the number of cases that our derive impl can handle
(e.g. allow providing a sql type in addition to a column name), but this
is a good bare minimum place to start.

Fixes #650.
sgrif added a commit that referenced this issue Sep 29, 2017
`sql` has always been a bit clunky to use if you wanted to write a whole
query with it. You have to specify the SQL type of the query, which also
means you have to list every column out explicitly to ensure they're in
the expected order.

This adds a replacement API that more geared towards writing complete
queries. There are two notable differences. The first is that the SQL
type of each field is specified during deserialization, not when the
query is constructed. The types are never checked anyway (other than to
make sure the deserialization is valid), so it makes sense to defer this
to a place where we can derive it.

The second difference is that columns are deserialized by name, not by
index. This has all of the gotchas that are associated with doing that
with other libraries (name conflicts between tables, etc)

I had originally planned on having `NamedRow::get` optionally also take
a table name. However, PG gives you the OID not the name, so we'd need
to do another query to get the table name. SQLite only gives you the
table/column name if compiled with a flag to enable it, which the system
sqlite3 on mac does not.

For those two reasons, I've opted to use the column name alone. This
means that the derived `QueryableByName` is unlikely to be usable with
joins, but that's a price we'll have to pay for now.

I'd like to expand the number of cases that our derive impl can handle
(e.g. allow providing a sql type in addition to a column name), but this
is a good bare minimum place to start.

Fixes #650.
sgrif added a commit that referenced this issue Sep 30, 2017
`sql` has always been a bit clunky to use if you wanted to write a whole
query with it. You have to specify the SQL type of the query, which also
means you have to list every column out explicitly to ensure they're in
the expected order.

This adds a replacement API that more geared towards writing complete
queries. There are two notable differences. The first is that the SQL
type of each field is specified during deserialization, not when the
query is constructed. The types are never checked anyway (other than to
make sure the deserialization is valid), so it makes sense to defer this
to a place where we can derive it.

The second difference is that columns are deserialized by name, not by
index. This has all of the gotchas that are associated with doing that
with other libraries (name conflicts between tables, etc)

I had originally planned on having `NamedRow::get` optionally also take
a table name. However, PG gives you the OID not the name, so we'd need
to do another query to get the table name. SQLite only gives you the
table/column name if compiled with a flag to enable it, which the system
sqlite3 on mac does not.

For those two reasons, I've opted to use the column name alone. This
means that the derived `QueryableByName` is unlikely to be usable with
joins, but that's a price we'll have to pay for now.

I'd like to expand the number of cases that our derive impl can handle
(e.g. allow providing a sql type in addition to a column name), but this
is a good bare minimum place to start.

Fixes #650.
sgrif added a commit that referenced this issue Sep 30, 2017
`sql` has always been a bit clunky to use if you wanted to write a whole
query with it. You have to specify the SQL type of the query, which also
means you have to list every column out explicitly to ensure they're in
the expected order.

This adds a replacement API that more geared towards writing complete
queries. There are two notable differences. The first is that the SQL
type of each field is specified during deserialization, not when the
query is constructed. The types are never checked anyway (other than to
make sure the deserialization is valid), so it makes sense to defer this
to a place where we can derive it.

The second difference is that columns are deserialized by name, not by
index. This has all of the gotchas that are associated with doing that
with other libraries (name conflicts between tables, etc)

I had originally planned on having `NamedRow::get` optionally also take
a table name. However, PG gives you the OID not the name, so we'd need
to do another query to get the table name. SQLite only gives you the
table/column name if compiled with a flag to enable it, which the system
sqlite3 on mac does not.

For those two reasons, I've opted to use the column name alone. This
means that the derived `QueryableByName` is unlikely to be usable with
joins, but that's a price we'll have to pay for now.

I'd like to expand the number of cases that our derive impl can handle
(e.g. allow providing a sql type in addition to a column name), but this
is a good bare minimum place to start.

Fixes #650.
sgrif added a commit that referenced this issue Oct 16, 2017
`sql` has always been a bit clunky to use if you wanted to write a whole
query with it. You have to specify the SQL type of the query, which also
means you have to list every column out explicitly to ensure they're in
the expected order.

This adds a replacement API that more geared towards writing complete
queries. There are two notable differences. The first is that the SQL
type of each field is specified during deserialization, not when the
query is constructed. The types are never checked anyway (other than to
make sure the deserialization is valid), so it makes sense to defer this
to a place where we can derive it.

The second difference is that columns are deserialized by name, not by
index. This has all of the gotchas that are associated with doing that
with other libraries (name conflicts between tables, etc)

I had originally planned on having `NamedRow::get` optionally also take
a table name. However, PG gives you the OID not the name, so we'd need
to do another query to get the table name. SQLite only gives you the
table/column name if compiled with a flag to enable it, which the system
sqlite3 on mac does not.

For those two reasons, I've opted to use the column name alone. This
means that the derived `QueryableByName` is unlikely to be usable with
joins, but that's a price we'll have to pay for now.

I'd like to expand the number of cases that our derive impl can handle
(e.g. allow providing a sql type in addition to a column name), but this
is a good bare minimum place to start.

Fixes #650.
sgrif added a commit that referenced this issue Oct 16, 2017
`sql` has always been a bit clunky to use if you wanted to write a whole
query with it. You have to specify the SQL type of the query, which also
means you have to list every column out explicitly to ensure they're in
the expected order.

This adds a replacement API that more geared towards writing complete
queries. There are two notable differences. The first is that the SQL
type of each field is specified during deserialization, not when the
query is constructed. The types are never checked anyway (other than to
make sure the deserialization is valid), so it makes sense to defer this
to a place where we can derive it.

The second difference is that columns are deserialized by name, not by
index. This has all of the gotchas that are associated with doing that
with other libraries (name conflicts between tables, etc)

I had originally planned on having `NamedRow::get` optionally also take
a table name. However, PG gives you the OID not the name, so we'd need
to do another query to get the table name. SQLite only gives you the
table/column name if compiled with a flag to enable it, which the system
sqlite3 on mac does not.

For those two reasons, I've opted to use the column name alone. This
means that the derived `QueryableByName` is unlikely to be usable with
joins, but that's a price we'll have to pay for now.

I'd like to expand the number of cases that our derive impl can handle
(e.g. allow providing a sql type in addition to a column name), but this
is a good bare minimum place to start.

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

No branches or pull requests

3 participants