Skip to content

Commit 0f785ff

Browse files
sklarsapuzpuzpuz
andauthored
fix(client): modify config parsing behavior to match other clients (#28)
Co-authored-by: Andrey Pechkurov <[email protected]>
1 parent 8a27774 commit 0f785ff

File tree

5 files changed

+93
-79
lines changed

5 files changed

+93
-79
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Features:
1111
* Optimized for batch writes.
1212
* Supports TLS encryption and ILP authentication.
1313
* Automatic write retries and connection reuse for ILP over HTTP.
14-
* Tested against QuestDB 7.3.11 and newer versions.
14+
* Tested against QuestDB 7.3.10 and newer versions.
1515

1616
New in v3:
1717
* Supports ILP over HTTP using the same client semantics

conf_parse.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func confFromStr(conf string) (*lineSenderConfig, error) {
6060
}
6161

6262
for k, v := range data.KeyValuePairs {
63-
switch strings.ToLower(k) {
63+
switch k {
6464
case "addr":
6565
senderConf.address = v
6666
case "username":
@@ -86,6 +86,10 @@ func confFromStr(conf string) (*lineSenderConfig, error) {
8686
default:
8787
panic("add a case for " + k)
8888
}
89+
case "token_x":
90+
case "token_y":
91+
// Some clients require public key.
92+
// But since Go sender doesn't need it, we ignore the values.
8993
case "auto_flush":
9094
if v == "off" {
9195
senderConf.autoFlushRows = 0
@@ -166,9 +170,8 @@ func parseConfigStr(conf string) (configData, error) {
166170
KeyValuePairs: map[string]string{},
167171
}
168172

169-
nextRune rune
170-
isEscaping bool
171-
hasTrailingSemicolon bool
173+
nextRune rune
174+
isEscaping bool
172175
)
173176

174177
schemaStr, conf, found := strings.Cut(conf, "::")
@@ -182,10 +185,8 @@ func parseConfigStr(conf string) (configData, error) {
182185
return result, NewInvalidConfigStrError("'addr' key not found")
183186
}
184187

185-
if strings.HasSuffix(conf, ";") {
186-
hasTrailingSemicolon = true
187-
} else {
188-
conf = conf + ";" // add trailing semicolon if it doesn't exist
188+
if !strings.HasSuffix(conf, ";") {
189+
return result, NewInvalidConfigStrError("trailing semicolon ';' required")
189190
}
190191

191192
keyValueStr := []rune(conf)
@@ -198,7 +199,7 @@ func parseConfigStr(conf string) (configData, error) {
198199
switch rune {
199200
case ';':
200201
if isKey {
201-
if nextRune == 0 && !hasTrailingSemicolon {
202+
if nextRune == 0 {
202203
return result, NewInvalidConfigStrError("unexpected end of string")
203204
}
204205
return result, NewInvalidConfigStrError("invalid key character ';'")

conf_test.go

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func TestParserHappyCases(t *testing.T) {
5454
testCases := []parseConfigTestCase{
5555
{
5656
name: "http and ipv4 address",
57-
config: fmt.Sprintf("http::addr=%s", addr),
57+
config: fmt.Sprintf("http::addr=%s;", addr),
5858
expected: qdb.ConfigData{
5959
Schema: "http",
6060
KeyValuePairs: map[string]string{
@@ -74,7 +74,7 @@ func TestParserHappyCases(t *testing.T) {
7474
},
7575
{
7676
name: "tcp and address",
77-
config: fmt.Sprintf("tcp::addr=%s", addr),
77+
config: fmt.Sprintf("tcp::addr=%s;", addr),
7878
expected: qdb.ConfigData{
7979
Schema: "tcp",
8080
KeyValuePairs: map[string]string{
@@ -84,7 +84,7 @@ func TestParserHappyCases(t *testing.T) {
8484
},
8585
{
8686
name: "http and username/password",
87-
config: fmt.Sprintf("http::addr=%s;username=%s;password=%s", addr, user, pass),
87+
config: fmt.Sprintf("http::addr=%s;username=%s;password=%s;", addr, user, pass),
8888
expected: qdb.ConfigData{
8989
Schema: "http",
9090
KeyValuePairs: map[string]string{
@@ -107,7 +107,7 @@ func TestParserHappyCases(t *testing.T) {
107107
},
108108
{
109109
name: "tcp with key and user",
110-
config: fmt.Sprintf("tcp::addr=%s;token=%s;username=%s", addr, token, user),
110+
config: fmt.Sprintf("tcp::addr=%s;token=%s;username=%s;", addr, token, user),
111111
expected: qdb.ConfigData{
112112
Schema: "tcp",
113113
KeyValuePairs: map[string]string{
@@ -119,7 +119,7 @@ func TestParserHappyCases(t *testing.T) {
119119
},
120120
{
121121
name: "https with min_throughput",
122-
config: fmt.Sprintf("https::addr=%s;min_throughput=%d", addr, min_throughput),
122+
config: fmt.Sprintf("https::addr=%s;min_throughput=%d;", addr, min_throughput),
123123
expected: qdb.ConfigData{
124124
Schema: "https",
125125
KeyValuePairs: map[string]string{
@@ -130,7 +130,7 @@ func TestParserHappyCases(t *testing.T) {
130130
},
131131
{
132132
name: "https with min_throughput, init_buf_size and tls_verify=unsafe_off",
133-
config: fmt.Sprintf("https::addr=%s;min_throughput=%d;init_buf_size=%d;tls_verify=unsafe_off", addr, min_throughput, 1024),
133+
config: fmt.Sprintf("https::addr=%s;min_throughput=%d;init_buf_size=%d;tls_verify=unsafe_off;", addr, min_throughput, 1024),
134134
expected: qdb.ConfigData{
135135
Schema: "https",
136136
KeyValuePairs: map[string]string{
@@ -143,7 +143,7 @@ func TestParserHappyCases(t *testing.T) {
143143
},
144144
{
145145
name: "tcps with tls_verify=unsafe_off",
146-
config: fmt.Sprintf("tcps::addr=%s;tls_verify=unsafe_off", addr),
146+
config: fmt.Sprintf("tcps::addr=%s;tls_verify=unsafe_off;", addr),
147147
expected: qdb.ConfigData{
148148
Schema: "tcps",
149149
KeyValuePairs: map[string]string{
@@ -154,7 +154,7 @@ func TestParserHappyCases(t *testing.T) {
154154
},
155155
{
156156
name: "http with min_throughput, request_timeout, and retry_timeout",
157-
config: fmt.Sprintf("http::addr=%s;min_throughput=%d;request_timeout=%d;retry_timeout=%d",
157+
config: fmt.Sprintf("http::addr=%s;min_throughput=%d;request_timeout=%d;retry_timeout=%d;",
158158
addr, min_throughput, request_timeout.Milliseconds(), retry_timeout.Milliseconds()),
159159
expected: qdb.ConfigData{
160160
Schema: "http",
@@ -168,7 +168,7 @@ func TestParserHappyCases(t *testing.T) {
168168
},
169169
{
170170
name: "tcp with tls_verify=on",
171-
config: fmt.Sprintf("tcp::addr=%s;tls_verify=on", addr),
171+
config: fmt.Sprintf("tcp::addr=%s;tls_verify=on;", addr),
172172
expected: qdb.ConfigData{
173173
Schema: "tcp",
174174
KeyValuePairs: map[string]string{
@@ -177,18 +177,6 @@ func TestParserHappyCases(t *testing.T) {
177177
},
178178
},
179179
},
180-
{
181-
name: "password with an escaped semicolon",
182-
config: fmt.Sprintf("http::addr=%s;username=%s;password=pass;;word", addr, user),
183-
expected: qdb.ConfigData{
184-
Schema: "http",
185-
KeyValuePairs: map[string]string{
186-
"addr": addr,
187-
"username": user,
188-
"password": "pass;word",
189-
},
190-
},
191-
},
192180
{
193181
name: "password with an escaped semicolon (ending with a ';')",
194182
config: fmt.Sprintf("http::addr=%s;username=%s;password=pass;;word;", addr, user),
@@ -215,7 +203,7 @@ func TestParserHappyCases(t *testing.T) {
215203
},
216204
{
217205
name: "equal sign in password",
218-
config: fmt.Sprintf("http::addr=%s;username=%s;password=pass=word", addr, user),
206+
config: fmt.Sprintf("http::addr=%s;username=%s;password=pass=word;", addr, user),
219207
expected: qdb.ConfigData{
220208
Schema: "http",
221209
KeyValuePairs: map[string]string{
@@ -253,15 +241,10 @@ func TestParserPathologicalCases(t *testing.T) {
253241
config: "http::",
254242
expectedErrMsgContains: "'addr' key not found",
255243
},
256-
{
257-
name: "unescaped semicolon in password leads to unexpected end of string",
258-
config: "http::addr=localhost:9000;username=test;password=pass;word",
259-
expectedErrMsgContains: "unexpected end of string",
260-
},
261244
{
262245
name: "unescaped semicolon in password leads to invalid key character",
263246
config: "http::addr=localhost:9000;username=test;password=pass;word;",
264-
expectedErrMsgContains: "invalid key character ';'",
247+
expectedErrMsgContains: "unexpected end of",
265248
},
266249
}
267250

@@ -299,7 +282,17 @@ func TestHappyCasesFromConf(t *testing.T) {
299282
testCases := []configTestCase{
300283
{
301284
name: "user and token",
302-
config: fmt.Sprintf("tcp::addr=%s;username=%s;token=%s",
285+
config: fmt.Sprintf("tcp::addr=%s;username=%s;token=%s;",
286+
addr, user, token),
287+
expectedOpts: []qdb.LineSenderOption{
288+
qdb.WithTcp(),
289+
qdb.WithAddress(addr),
290+
qdb.WithAuth(user, token),
291+
},
292+
},
293+
{
294+
name: "token_x and token_y (ignored)",
295+
config: fmt.Sprintf("tcp::addr=%s;username=%s;token=%s;token_x=xyz;token_y=xyz;",
303296
addr, user, token),
304297
expectedOpts: []qdb.LineSenderOption{
305298
qdb.WithTcp(),
@@ -309,7 +302,7 @@ func TestHappyCasesFromConf(t *testing.T) {
309302
},
310303
{
311304
name: "init_buf_size and max_buf_size",
312-
config: fmt.Sprintf("tcp::addr=%s;init_buf_size=%d;max_buf_size=%d",
305+
config: fmt.Sprintf("tcp::addr=%s;init_buf_size=%d;max_buf_size=%d;",
313306
addr, initBufSize, maxBufSize),
314307
expectedOpts: []qdb.LineSenderOption{
315308
qdb.WithTcp(),
@@ -320,7 +313,7 @@ func TestHappyCasesFromConf(t *testing.T) {
320313
},
321314
{
322315
name: "with tls",
323-
config: fmt.Sprintf("tcp::addr=%s;tls_verify=on",
316+
config: fmt.Sprintf("tcp::addr=%s;tls_verify=on;",
324317
addr),
325318
expectedOpts: []qdb.LineSenderOption{
326319
qdb.WithTcp(),
@@ -330,7 +323,7 @@ func TestHappyCasesFromConf(t *testing.T) {
330323
},
331324
{
332325
name: "with tls and unsafe_off",
333-
config: fmt.Sprintf("tcp::addr=%s;tls_verify=unsafe_off",
326+
config: fmt.Sprintf("tcp::addr=%s;tls_verify=unsafe_off;",
334327
addr),
335328
expectedOpts: []qdb.LineSenderOption{
336329
qdb.WithTcp(),
@@ -340,7 +333,7 @@ func TestHappyCasesFromConf(t *testing.T) {
340333
},
341334
{
342335
name: "request_timeout and retry_timeout milli conversion",
343-
config: fmt.Sprintf("http::addr=%s;request_timeout=%d;retry_timeout=%d",
336+
config: fmt.Sprintf("http::addr=%s;request_timeout=%d;retry_timeout=%d;",
344337
addr, requestTimeout.Milliseconds(), retryTimeout.Milliseconds()),
345338
expectedOpts: []qdb.LineSenderOption{
346339
qdb.WithHttp(),
@@ -351,7 +344,7 @@ func TestHappyCasesFromConf(t *testing.T) {
351344
},
352345
{
353346
name: "password before username",
354-
config: fmt.Sprintf("http::addr=%s;password=%s;username=%s",
347+
config: fmt.Sprintf("http::addr=%s;password=%s;username=%s;",
355348
addr, pass, user),
356349
expectedOpts: []qdb.LineSenderOption{
357350
qdb.WithHttp(),
@@ -361,7 +354,7 @@ func TestHappyCasesFromConf(t *testing.T) {
361354
},
362355
{
363356
name: "min_throughput",
364-
config: fmt.Sprintf("http::addr=%s;min_throughput=%d",
357+
config: fmt.Sprintf("http::addr=%s;min_throughput=%d;",
365358
addr, minThroughput),
366359
expectedOpts: []qdb.LineSenderOption{
367360
qdb.WithHttp(),
@@ -371,7 +364,7 @@ func TestHappyCasesFromConf(t *testing.T) {
371364
},
372365
{
373366
name: "bearer token",
374-
config: fmt.Sprintf("http::addr=%s;token=%s",
367+
config: fmt.Sprintf("http::addr=%s;token=%s;",
375368
addr, token),
376369
expectedOpts: []qdb.LineSenderOption{
377370
qdb.WithHttp(),
@@ -381,7 +374,7 @@ func TestHappyCasesFromConf(t *testing.T) {
381374
},
382375
{
383376
name: "auto flush",
384-
config: fmt.Sprintf("http::addr=%s;auto_flush_rows=100;auto_flush_interval=1000",
377+
config: fmt.Sprintf("http::addr=%s;auto_flush_rows=100;auto_flush_interval=1000;",
385378
addr),
386379
expectedOpts: []qdb.LineSenderOption{
387380
qdb.WithHttp(),
@@ -416,44 +409,54 @@ func TestPathologicalCasesFromConf(t *testing.T) {
416409
},
417410
{
418411
name: "invalid schema",
419-
config: "foobar::addr=localhost:1111",
412+
config: "foobar::addr=localhost:1111;",
420413
expectedErrMsgContains: "invalid schema",
421414
},
422415
{
423416
name: "invalid tls_verify 1",
424-
config: "tcp::addr=localhost:1111;tls_verify=invalid",
417+
config: "tcp::addr=localhost:1111;tls_verify=invalid;",
425418
expectedErrMsgContains: "invalid tls_verify",
426419
},
427420
{
428421
name: "invalid tls_verify 2",
429-
config: "http::addr=localhost:1111;tls_verify=invalid",
422+
config: "http::addr=localhost:1111;tls_verify=invalid;",
430423
expectedErrMsgContains: "invalid tls_verify",
431424
},
432425
{
433426
name: "unsupported option",
434-
config: "tcp::addr=localhost:1111;unsupported_option=invalid",
427+
config: "tcp::addr=localhost:1111;unsupported_option=invalid;",
435428
expectedErrMsgContains: "unsupported option",
436429
},
437430
{
438431
name: "invalid auto_flush",
439-
config: "http::addr=localhost:1111;auto_flush=invalid",
432+
config: "http::addr=localhost:1111;auto_flush=invalid;",
440433
expectedErrMsgContains: "invalid auto_flush",
441434
},
442435
{
443436
name: "invalid auto_flush_rows",
444-
config: "http::addr=localhost:1111;auto_flush_rows=invalid",
437+
config: "http::addr=localhost:1111;auto_flush_rows=invalid;",
445438
expectedErrMsgContains: "invalid auto_flush_rows",
446439
},
447440
{
448441
name: "invalid auto_flush_interval",
449-
config: "http::addr=localhost:1111;auto_flush_interval=invalid",
442+
config: "http::addr=localhost:1111;auto_flush_interval=invalid;",
450443
expectedErrMsgContains: "invalid auto_flush_interval",
451444
},
452445
{
453446
name: "unsupported option",
454-
config: "http::addr=localhost:1111;unsupported_option=invalid",
447+
config: "http::addr=localhost:1111;unsupported_option=invalid;",
448+
expectedErrMsgContains: "unsupported option",
449+
},
450+
{
451+
name: "case-sensitive values",
452+
config: "http::aDdr=localhost:9000;",
455453
expectedErrMsgContains: "unsupported option",
456454
},
455+
{
456+
name: "trailing semicolon required",
457+
config: "http::addr=localhost:9000",
458+
expectedErrMsgContains: "trailing semicolon",
459+
},
457460
}
458461

459462
for _, tc := range testCases {

0 commit comments

Comments
 (0)