-
Notifications
You must be signed in to change notification settings - Fork 119
Add support for raw queries without going through query builder (#234) #243
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
base: main
Are you sure you want to change the base?
Conversation
src/lib.rs
Outdated
/// Question marks are escaped using odd/even strategy: | ||
/// - `?` (odd count) → `??` | ||
/// - `??` (even count) → `??` (unchanged) | ||
/// - `???` (odd count) → `????` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks I think my last commit would fix the issue, I also compared the output with the CLI client.
src/lib.rs
Outdated
/// // query.bind(value) ❌ | ||
/// // query.param("key", value) ❌ | ||
/// ``` | ||
pub fn query_raw(&self, query: &str) -> query_raw::QueryRaw { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
src/query_raw.rs
Outdated
#[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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this is a separate struct/impl, why do we even need these methods that always panic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed review. I thought the developer should be notified about the misuse of the functionality to help prevent future bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
tests/it/query_raw.rs
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the comment, it now compares with the system logs
@@ -53,6 +63,9 @@ impl Query { | |||
/// [`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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, it's probably duplicate of #243 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -313,6 +313,11 @@ impl Client { | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
examples/README.md
Outdated
@@ -8,7 +8,8 @@ If something is missing, or you found a mistake in one of these examples, please | |||
|
|||
### General usage | |||
|
|||
- [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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
@@ -53,6 +63,9 @@ impl Query { | |||
/// [`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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this test, very nice 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#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.