Skip to content

Commit 5e4cf31

Browse files
committed
Change processing order of parameters and escape sequences
Previously, the driver processed ODBC escape sequences before replacing ODBC parameter markers (?) with ClickHouse positional parameters. This caused incorrect parameter ordering for functions whose argument order differs between the ODBC standard and ClickHouse (for example, LOCATE → position). Example (old behavior): {FN LOCATE(?, ?)} → position(?, ?) → position({odbc_parameter_1:String}, {odbc_parameter_2:String}) At this point, the original parameter order is lost, making it impossible to reorder arguments correctly. For LOCATE, the correct mapping requires the second parameter to come first. New behavior: {FN LOCATE(?, ?)} → {FN LOCATE({odbc_parameter_1:String}, {odbc_parameter_2:String})} → position({odbc_parameter_2:String}, {odbc_parameter_1:String}) This change fixes the issue by replacing ODBC parameter markers before processing ODBC escape sequences. The parser and lexer were also extended to support ClickHouse positional parameter syntax.
1 parent a3c9363 commit 5e4cf31

File tree

7 files changed

+40
-25
lines changed

7 files changed

+40
-25
lines changed

driver/escaping/escape_sequences.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,23 @@ string processDateTime(const StringView seq, Lexer & lex) {
460460
}
461461
}
462462

463+
string processClickHouseQueryParameter(Token name, const StringView seq, Lexer & lex) {
464+
bool emit_space = lex.GetEmitSpaces();
465+
lex.SetEmitSpaces(true);
466+
467+
std::string result = "{" + name.literal.to_string();
468+
while (lex.Peek().type != Token::RCURLY) {
469+
const Token tok = lex.Consume();
470+
if (tok.type == Token::EOS) {
471+
return seq.to_string();
472+
}
473+
result += tok.literal.to_string();
474+
}
475+
result += '}';
476+
lex.SetEmitSpaces(emit_space);
477+
return result;
478+
}
479+
463480
string processEscapeSequencesImpl(const StringView seq, Lexer & lex) {
464481
string result;
465482

@@ -490,7 +507,17 @@ string processEscapeSequencesImpl(const StringView seq, Lexer & lex) {
490507

491508
// Unimplemented
492509
case Token::T:
510+
return seq.to_string();
511+
493512
default:
513+
// Positional query parameters, just like ODBC escape sequences, also wrapped in
514+
// curly braces. However then always have format {name : type}, which does not
515+
// happen with ODBC escape sequences.
516+
if (lex.Peek().type == Token::COLON) {
517+
result += processClickHouseQueryParameter(tok, seq, lex);
518+
break;
519+
}
520+
494521
return seq.to_string();
495522
}
496523
};

driver/escaping/lexer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ Token Lexer::NextToken() {
147147
return MakeToken(Token::RCURLY, 1);
148148
case ',':
149149
return MakeToken(Token::COMMA, 1);
150+
case ':':
151+
return MakeToken(Token::COLON, 1);
150152
case '?':
151153
return MakeToken(Token::PARAM, 1);
152154

driver/escaping/lexer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ struct Token {
3838
RPARENT, // )
3939
LCURLY, // {
4040
RCURLY, // }
41+
COLON, // :
4142
};
4243

4344
#undef DECLARE
@@ -79,6 +80,8 @@ class Lexer {
7980
/// Enable or disable emitting of space tokens.
8081
void SetEmitSpaces(bool value);
8182

83+
bool GetEmitSpaces() { return emit_space_; }
84+
8285
private:
8386
/// Makes token of length len againts current position.
8487
Token MakeToken(const Token::Type type, size_t len);

driver/statement.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ void Statement::prepareQuery(const std::string & q) {
3434

3535
is_prepared = false;
3636
query = q;
37-
processEscapeSequences();
3837
extractParametersinfo();
3938
is_prepared = true;
4039
}
@@ -94,6 +93,9 @@ Statement::HttpRequestData Statement::prepareHttpRequest()
9493
}
9594

9695
ret.query = buildFinalQuery(param_bindings);
96+
if (getAttrAs<SQLULEN>(SQL_ATTR_NOSCAN, SQL_NOSCAN_OFF) != SQL_NOSCAN_ON) {
97+
ret.query = replaceEscapeSequences(ret.query);
98+
}
9799
return ret;
98100
}
99101

@@ -201,11 +203,6 @@ void Statement::requestNextPackOfResultSets(std::unique_ptr<ResultMutator> && mu
201203
++next_param_set_idx;
202204
}
203205

204-
void Statement::processEscapeSequences() {
205-
if (getAttrAs<SQLULEN>(SQL_ATTR_NOSCAN, SQL_NOSCAN_OFF) != SQL_NOSCAN_ON)
206-
query = replaceEscapeSequences(query);
207-
}
208-
209206
void Statement::extractParametersinfo() {
210207
auto & apd_desc = getEffectiveDescriptor(SQL_ATTR_APP_PARAM_DESC);
211208
auto & ipd_desc = getEffectiveDescriptor(SQL_ATTR_IMP_PARAM_DESC);

driver/statement.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ class Statement
100100
private:
101101
void requestNextPackOfResultSets(std::unique_ptr<ResultMutator> && mutator);
102102

103-
void processEscapeSequences();
104103
void extractParametersinfo();
105104
std::string buildFinalQuery(const std::vector<ParamBindingInfo>& param_bindings);
106105
std::string getParamFinalName(std::size_t param_idx);

driver/test/escape_sequences_ut.cpp

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -297,22 +297,8 @@ TEST(EscapeSequencesCase, DateTime) {
297297
ASSERT_EQ(replaceEscapeSequences("SELECT {ts '2017.01.01 10:01:01'}"), "SELECT toDateTime('2017.01.01 10:01:01')");
298298
}
299299

300-
TEST(EscapeSequencesCase, LOCATE) {
301-
ASSERT_EQ(replaceEscapeSequences(
302-
"SELECT {fn LOCATE('needle', `haystack`, 42)}"),
303-
"SELECT locate('needle',`haystack`,accurateCast(42,'UInt64'))");
304-
ASSERT_EQ(replaceEscapeSequences(
305-
"SELECT {fn LOCATE(?, `haystack`, 42)}"),
306-
"SELECT locate(?,`haystack`,accurateCast(42,'UInt64'))");
307-
ASSERT_EQ(replaceEscapeSequences(
308-
"SELECT {fn LOCATE('needle', `haystack`, ?)}"),
309-
"SELECT locate('needle',`haystack`,accurateCast(?,'UInt64'))");
310-
ASSERT_EQ(replaceEscapeSequences(
311-
"SELECT {fn LOCATE('needle', `haystack`)}"),
312-
"SELECT locate('needle',`haystack`,accurateCast(1,'UInt64'))");
313-
ASSERT_EQ(replaceEscapeSequences(
314-
"SELECT {fn LOCATE(?, `haystack`)}"),
315-
"SELECT locate(?,`haystack`,accurateCast(1,'UInt64'))");
300+
TEST(EscapeSequencesCase, ClickHouseParametersInEscapeSequences) {
301+
ASSERT_EQ(replaceEscapeSequences("{fn BIT_LENGTH({odbc_positional_1:Nullable(String)})}"), "(length({odbc_positional_1:Nullable(String)}) * 8)");
316302
}
317303

318304
TEST(EscapeSequencesCase, LCASE) {
@@ -327,3 +313,4 @@ TEST(EscapeSequencesCase, LTRIM) {
327313
TEST(EscapeSequencesCase, BIT_LENGTH) {
328314
ASSERT_EQ(replaceEscapeSequences("{fn BIT_LENGTH('Hello World!')}"), "(length('Hello World!') * 8)");
329315
}
316+

driver/test/statement_parameter_binding_ut.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,8 @@ TEST_F(StatementBindingTest, FunctionLocate) {
279279

280280
auto [query, params] = execute();
281281
ASSERT_EQ(query,
282-
"select locate({odbc_positional_1:LowCardinality(String)},"
283-
"{odbc_positional_2:LowCardinality(String)},"
282+
"select position({odbc_positional_2:LowCardinality(String)},"
283+
"{odbc_positional_1:LowCardinality(String)},"
284284
"accurateCast({odbc_positional_3:Nullable(Int32)},'UInt64'))");
285285
ASSERT_EQ(params.size(), 3);
286286
ASSERT_EQ(params["param_odbc_positional_1"], "needle");

0 commit comments

Comments
 (0)