Add support for raw queries without going through query builder (#234) - #243
Add support for raw queries without going through query builder (#234)#243ozgurcancal wants to merge 4 commits into
Conversation
| /// Question marks are escaped using odd/even strategy: | ||
| /// - `?` (odd count) → `??` | ||
| /// - `??` (even count) → `??` (unchanged) | ||
| /// - `???` (odd count) → `????` |
There was a problem hiding this comment.
I wonder, if this is a raw query without support to binds on ?, why it is needed? For example, this is how the CLI client behaves:
SELECT
'??',
'?',
'????'
┌─'??'─┬─'?'─┬─'????'─┐
1. │ ?? │ ? │ ???? │
└──────┴─────┴────────┘
i.e. no additional translation for ? symbols. Or is it because of some HTTP interface quirks?
There was a problem hiding this comment.
Thanks for your comment. My tests show that Literal ? in SQL is escaped as ?? for ClickHouse HTTP interface whereas CLI client sends SQL directly so it is an HTTP interface quirk as you sad
There was a problem hiding this comment.
I think that might be a URL encoding oddity, then. It is worth checking that it is performed correctly.
For example, using POST:
curl -XPOST "http://localhost:8123" --data-binary "select '?', '??', '???' FORMAT JSONEachRow"
{"'?'":"?","'??'":"??","'???'":"???"}
GET with proper URI encoding:
curl "http://localhost:8123?query=SELECT%20'%3F'%2C'%3F%3F'%2C'%3F%3F%3F'%20FORMAT%20JSONEachRow"
{"'?'":"?","'??'":"??","'???'":"???"}
Where the query is encoded as:
encodeURIComponent("SELECT '?','??','???' FORMAT JSONEachRow")
"SELECT%20'%3F'%2C'%3F%3F'%2C'%3F%3F%3F'%20FORMAT%20JSONEachRow"
All in all, that could be related to #230 and this bit in particular:
https://github.com/ClickHouse/clickhouse-rs/blob/main/src/query.rs#L158-L169
There was a problem hiding this comment.
Thanks I think my last commit would fix the issue, I also compared the output with the CLI client.
| /// // query.bind(value) ❌ | ||
| /// // query.param("key", value) ❌ | ||
| /// ``` | ||
| pub fn query_raw(&self, query: &str) -> query_raw::QueryRaw { |
There was a problem hiding this comment.
I am curious if we can just add a builder method to the main Query that disables query builder usage, as well as some bind checks.
There was a problem hiding this comment.
Thanks for the comment, I just wanted to implement a solution with zero change on the existing codebase to make further changes and tests easier. I could adjust builder mechanism using a bool flag too such as;
pub struct Query {
client: Client,
sql: SqlBuilder,
raw: bool
}
There was a problem hiding this comment.
would have to compare the two choices to really decide, ideally you have Query/QueryRaw & implement Query on top of QueryRaw, but very likely that runs into some hurdle
| #[track_caller] | ||
| pub fn param(self, _name: &str, _value: impl Serialize) -> Self { | ||
| panic!("cannot set parameters on raw query - use regular query() for parameter binding"); | ||
| } |
There was a problem hiding this comment.
if this is a separate struct/impl, why do we even need these methods that always panic?
There was a problem hiding this comment.
Thanks for the detailed review. I thought the developer should be notified about the misuse of the functionality to help prevent future bugs.
slvrtrn
left a comment
There was a problem hiding this comment.
Thanks for the contribution. I like that there is a lot of tests and docs. I have a few questions about the implementation details, though, see the comments.
805acf0 to
0c070fe
Compare
| async fn query_raw_preserves_exact_sql() { | ||
| let client = prepare_database!(); | ||
|
|
||
| // Test that raw query preserves the exact SQL including whitespace and formatting |
There was a problem hiding this comment.
It can check, for instance, system.query_log to ensure it, but currently it doesn't ensure this statement
There was a problem hiding this comment.
Thanks for the comment, it now compares with the system logs
| /// [`Identifier`]: crate::sql::Identifier | ||
| #[track_caller] | ||
| pub fn bind(mut self, value: impl Bind) -> Self { | ||
| if self.raw { |
There was a problem hiding this comment.
In general, I'm fine with this implementation.
However, it's slightly strange to check in runtime properties that can easily be checked at compile time:
struct Query<M = DefaultMode> { .. }
impl Query<DefaultMode> {
pub(crate) fn new(..) -> Self { .. }
pub fn bind(..) { .. } // only in the default mode
}
impl Query<RawMode> {
pub(crate) fn new(..) -> Self { .. }
}
and, obviously, a trait (sealed) to turn bind_fields on/off
anyway up to @slvrtrn
There was a problem hiding this comment.
We could also try to explore an option of using QueryRaw as a building block for Query, as @serprex suggested. But perhaps current impl is good enough to start with, so let's not change it.
| query::Query::new(self, query) | ||
| } | ||
|
|
||
| /// Starts a new SELECT/DDL query that will be used as-is without any processing. |
There was a problem hiding this comment.
I think we should provide more documentation on this difference to help newcomers choose the right constructor
|
@ozgurcancal, let's resolve the conflicts, add a bit more docs as @loyd requested and then we can merge. |
- Update documentation and tests
|
|
||
| - [usage.rs](usage.rs) - creating tables, executing other DDLs, inserting the data, and selecting it back. Optional cargo features: `inserter`. | ||
| - [usage.rs](usage.rs) - creating tables, executing other DDLs, inserting the data, and selecting it back. Additionally, it covers `WATCH` queries. Optional cargo features: `inserter`, `watch`. | ||
| - [query_raw.rs](query_raw.rs) - raw queries without parameter binding, with question mark escaping. FORMAT is the RowBinary by default |
There was a problem hiding this comment.
WATCH is removed as of #245, since it is deprecated feature, and its implementation was incompatible with CH 25+.
with question mark escaping
is it still true?
| /// [`Identifier`]: crate::sql::Identifier | ||
| #[track_caller] | ||
| pub fn bind(mut self, value: impl Bind) -> Self { | ||
| if self.raw { |
There was a problem hiding this comment.
We could also try to explore an option of using QueryRaw as a building block for Query, as @serprex suggested. But perhaps current impl is good enough to start with, so let's not change it.
|
|
||
| // Test question marks in SQL comments - should work without binding | ||
| let result = client | ||
| .query_raw("SELECT 1 /* What? How?? Why??? */ WHERE 1=1") |
There was a problem hiding this comment.
I like this test, very nice 👍
There was a problem hiding this comment.
#243 (comment)
It would be great, but I think the new builder will share a lot with the current one, so we’ll probably need a common base trait for both, and that means a bunch of changes.
|
CH Cloud test failures can be ignored, cause it's secrets pull issue for now, need to re-do the setup to support PRs from external contributors. |
I have addressed the suggested fixes. Thanks for the support, next time my PR will be much cleaner :) |
|
I think we shouldn't merge this PR until #157 (comment) is discussed |
Summary
Implements raw SQL query support as requested in #234.