@@ -72,6 +72,7 @@ type LineSender struct {
7272 lastMsgPos int
7373 lastErr error
7474 hasTable bool
75+ hasTags bool
7576 hasFields bool
7677}
7778
@@ -232,8 +233,8 @@ func (s *LineSender) Close() error {
232233// called before any Symbol or Column method.
233234//
234235// Table name cannot contain any of the following characters:
235- // '\n', '\r', '. ', '? ', ',' , ': ', '\', '/', '\0 ', ')', '(', '+', '*',
236- // '~', '% ', '-' .
236+ // '\n', '\r', '? ', ', ', ''' , '" ', '\', '/', ': ', ')', '(', '+', '*',
237+ // '%', ' ~', starting '. ', trailing '.', or a non-printable char .
237238func (s * LineSender ) Table (name string ) * LineSender {
238239 if s .lastErr != nil {
239240 return s
@@ -242,7 +243,7 @@ func (s *LineSender) Table(name string) *LineSender {
242243 s .lastErr = fmt .Errorf ("table name already provided: %w" , ErrInvalidMsg )
243244 return s
244245 }
245- s .lastErr = s .writeStrName (name )
246+ s .lastErr = s .writeTableName (name )
246247 if s .lastErr != nil {
247248 return s
248249 }
@@ -254,11 +255,8 @@ func (s *LineSender) Table(name string) *LineSender {
254255// before any Column method.
255256//
256257// Symbol name cannot contain any of the following characters:
257- // '\n', '\r', '.', '?', ',', ':', '\', '/', '\0', ')', '(', '+', '*',
258- // '~', '%', '-'.
259- //
260- // Symbol values cannot contain any of the following characters:
261- // '\n', '\r'.
258+ // '\n', '\r', '?', '.', ',', ''', '\"', '\\', '/', ':', ')', '(', '+',
259+ // '-', '*' '%%', '~', or a non-printable char.
262260func (s * LineSender ) Symbol (name , val string ) * LineSender {
263261 if s .lastErr != nil {
264262 return s
@@ -272,26 +270,30 @@ func (s *LineSender) Symbol(name, val string) *LineSender {
272270 return s
273271 }
274272 s .buf .WriteByte (',' )
275- s .lastErr = s .writeStrName (name )
273+ s .lastErr = s .writeColumnName (name )
276274 if s .lastErr != nil {
277275 return s
278276 }
279277 s .buf .WriteByte ('=' )
280278 s .lastErr = s .writeStrValue (val , false )
279+ if s .lastErr != nil {
280+ return s
281+ }
282+ s .hasTags = true
281283 return s
282284}
283285
284286// Int64Column adds a 64-bit integer (long) column value to the ILP
285287// message.
286288//
287289// Column name cannot contain any of the following characters:
288- // '\n', '\r', '. ', '? ', ',', ':' , '\', '/ ', '\0 ', ') ', '( ', '+ ', '* ',
289- // '~ ', '% ', '-' .
290+ // '\n', '\r', '? ', '. ', ',', ''' , '\" ', '\\ ', '/ ', ': ', ') ', '( ', '+ ',
291+ // '- ', '*' '%% ', '~', or a non-printable char .
290292func (s * LineSender ) Int64Column (name string , val int64 ) * LineSender {
291293 if ! s .prepareForField (name ) {
292294 return s
293295 }
294- s .lastErr = s .writeStrName (name )
296+ s .lastErr = s .writeColumnName (name )
295297 if s .lastErr != nil {
296298 return s
297299 }
@@ -306,13 +308,13 @@ func (s *LineSender) Int64Column(name string, val int64) *LineSender {
306308// message.
307309//
308310// Column name cannot contain any of the following characters:
309- // '\n', '\r', '. ', '? ', ',', ':' , '\', '/ ', '\0 ', ') ', '( ', '+ ', '* ',
310- // '~ ', '% ', '-' .
311+ // '\n', '\r', '? ', '. ', ',', ''' , '\" ', '\\ ', '/ ', ': ', ') ', '( ', '+ ',
312+ // '- ', '*' '%% ', '~', or a non-printable char .
311313func (s * LineSender ) Float64Column (name string , val float64 ) * LineSender {
312314 if ! s .prepareForField (name ) {
313315 return s
314316 }
315- s .lastErr = s .writeStrName (name )
317+ s .lastErr = s .writeColumnName (name )
316318 if s .lastErr != nil {
317319 return s
318320 }
@@ -325,16 +327,13 @@ func (s *LineSender) Float64Column(name string, val float64) *LineSender {
325327// StringColumn adds a string column value to the ILP message.
326328//
327329// Column name cannot contain any of the following characters:
328- // '\n', '\r', '.', '?', ',', ':', '\\', '/', '\0', ')', '(', '+', '*',
329- // '~', '%', '-'.
330- //
331- // Column values cannot contain any of the following characters:
332- // '\n', '\r'.
330+ // '\n', '\r', '?', '.', ',', ''', '\"', '\\', '/', ':', ')', '(', '+',
331+ // '-', '*' '%%', '~', or a non-printable char.
333332func (s * LineSender ) StringColumn (name , val string ) * LineSender {
334333 if ! s .prepareForField (name ) {
335334 return s
336335 }
337- s .lastErr = s .writeStrName (name )
336+ s .lastErr = s .writeColumnName (name )
338337 if s .lastErr != nil {
339338 return s
340339 }
@@ -352,13 +351,13 @@ func (s *LineSender) StringColumn(name, val string) *LineSender {
352351// BoolColumn adds a boolean column value to the ILP message.
353352//
354353// Column name cannot contain any of the following characters:
355- // '\n', '\r', '. ', '? ', ',', ':' , '\', '/ ', '\0 ', ') ', '( ', '+ ', '* ',
356- // '~ ', '% ', '-' .
354+ // '\n', '\r', '? ', '. ', ',', ''' , '\" ', '\\ ', '/ ', ': ', ') ', '( ', '+ ',
355+ // '- ', '*' '%% ', '~', or a non-printable char .
357356func (s * LineSender ) BoolColumn (name string , val bool ) * LineSender {
358357 if ! s .prepareForField (name ) {
359358 return s
360359 }
361- s .lastErr = s .writeStrName (name )
360+ s .lastErr = s .writeColumnName (name )
362361 if s .lastErr != nil {
363362 return s
364363 }
@@ -372,9 +371,9 @@ func (s *LineSender) BoolColumn(name string, val bool) *LineSender {
372371 return s
373372}
374373
375- func (s * LineSender ) writeStrName (str string ) error {
374+ func (s * LineSender ) writeTableName (str string ) error {
376375 if str == "" {
377- return fmt .Errorf ("table or column name cannot be empty: %w" , ErrInvalidMsg )
376+ return fmt .Errorf ("table name cannot be empty: %w" , ErrInvalidMsg )
378377 }
379378 // Since we're interested in ASCII chars, it's fine to iterate
380379 // through bytes instead of runes.
@@ -385,16 +384,14 @@ func (s *LineSender) writeStrName(str string) error {
385384 s .buf .WriteByte ('\\' )
386385 case '=' :
387386 s .buf .WriteByte ('\\' )
388- case '"' :
389- s .buf .WriteByte ('\\' )
390- case '\n' :
391- return fmt .Errorf ("table or column name contains a new line char: %s: %w" , str , ErrInvalidMsg )
392- case '\r' :
393- return fmt .Errorf ("table or column name contains a carriage return char: %s: %w" , str , ErrInvalidMsg )
387+ case '.' :
388+ if i == 0 || i == len (str )- 1 {
389+ return fmt .Errorf ("table name contains '.' char at the start or end: %s: %w" , str , ErrInvalidMsg )
390+ }
394391 default :
395- if illegalNameChar (b ) {
396- return fmt .Errorf ("table or column name contains one of illegal chars : " +
397- "'. ', '?', ',', ':' , '\\ ', '/', '\\ 0 ', ')', '(', '+', '*', '~ ', '%% ', '-' : %s: %w" ,
392+ if illegalTableNameChar (b ) {
393+ return fmt .Errorf ("table name contains an illegal char : " +
394+ "'\\ n ', '\\ r', ' ?', ',', ''' , '\" ', ' \\ ', '/', ': ', ')', '(', '+', '*' '%% ', '~ ', or a non-printable char : %s: %w" ,
398395 str , ErrInvalidMsg )
399396 }
400397 }
@@ -403,20 +400,26 @@ func (s *LineSender) writeStrName(str string) error {
403400 return nil
404401}
405402
406- func illegalNameChar (ch byte ) bool {
403+ func illegalTableNameChar (ch byte ) bool {
407404 switch ch {
408- case '.' :
405+ case '\n' :
406+ return true
407+ case '\r' :
409408 return true
410409 case '?' :
411410 return true
412411 case ',' :
413412 return true
414- case ':' :
413+ case '\'' :
414+ return true
415+ case '"' :
415416 return true
416417 case '\\' :
417418 return true
418419 case '/' :
419420 return true
421+ case ':' :
422+ return true
420423 case ')' :
421424 return true
422425 case '(' :
@@ -425,13 +428,134 @@ func illegalNameChar(ch byte) bool {
425428 return true
426429 case '*' :
427430 return true
431+ case '%' :
432+ return true
428433 case '~' :
429434 return true
430- case '%' :
435+ case '\u0000' :
436+ return true
437+ case '\u0001' :
438+ return true
439+ case '\u0002' :
440+ return true
441+ case '\u0003' :
442+ return true
443+ case '\u0004' :
444+ return true
445+ case '\u0005' :
446+ return true
447+ case '\u0006' :
448+ return true
449+ case '\u0007' :
450+ return true
451+ case '\u0008' :
452+ return true
453+ case '\u0009' :
454+ return true
455+ case '\u000b' :
456+ return true
457+ case '\u000c' :
458+ return true
459+ case '\u000e' :
460+ return true
461+ case '\u000f' :
462+ return true
463+ case '\u007f' :
464+ return true
465+ }
466+ return false
467+ }
468+
469+ func (s * LineSender ) writeColumnName (str string ) error {
470+ if str == "" {
471+ return fmt .Errorf ("column name cannot be empty: %w" , ErrInvalidMsg )
472+ }
473+ // Since we're interested in ASCII chars, it's fine to iterate
474+ // through bytes instead of runes.
475+ for i := 0 ; i < len (str ); i ++ {
476+ b := str [i ]
477+ switch b {
478+ case ' ' :
479+ s .buf .WriteByte ('\\' )
480+ case '=' :
481+ s .buf .WriteByte ('\\' )
482+ default :
483+ if illegalColumnNameChar (b ) {
484+ return fmt .Errorf ("column name contains an illegal char: " +
485+ "'\\ n', '\\ r', '?', '.', ',', ''', '\" ', '\\ ', '/', ':', ')', '(', '+', '-', '*' '%%', '~', or a non-printable char: %s: %w" ,
486+ str , ErrInvalidMsg )
487+ }
488+ }
489+ s .buf .WriteByte (b )
490+ }
491+ return nil
492+ }
493+
494+ func illegalColumnNameChar (ch byte ) bool {
495+ switch ch {
496+ case '\n' :
497+ return true
498+ case '\r' :
499+ return true
500+ case '?' :
501+ return true
502+ case '.' :
503+ return true
504+ case ',' :
505+ return true
506+ case '\'' :
507+ return true
508+ case '"' :
509+ return true
510+ case '\\' :
511+ return true
512+ case '/' :
513+ return true
514+ case ':' :
515+ return true
516+ case ')' :
517+ return true
518+ case '(' :
519+ return true
520+ case '+' :
431521 return true
432522 case '-' :
433523 return true
434- case '\x00' :
524+ case '*' :
525+ return true
526+ case '%' :
527+ return true
528+ case '~' :
529+ return true
530+ case '\u0000' :
531+ return true
532+ case '\u0001' :
533+ return true
534+ case '\u0002' :
535+ return true
536+ case '\u0003' :
537+ return true
538+ case '\u0004' :
539+ return true
540+ case '\u0005' :
541+ return true
542+ case '\u0006' :
543+ return true
544+ case '\u0007' :
545+ return true
546+ case '\u0008' :
547+ return true
548+ case '\u0009' :
549+ return true
550+ case '\u000b' :
551+ return true
552+ case '\u000c' :
553+ return true
554+ case '\u000e' :
555+ return true
556+ case '\u000f' :
557+ return true
558+ case '\u007f' :
435559 return true
436560 }
437561 return false
@@ -456,13 +580,15 @@ func (s *LineSender) writeStrValue(str string, quoted bool) error {
456580 s .buf .WriteByte ('\\' )
457581 }
458582 case '"' :
583+ if quoted {
584+ s .buf .WriteByte ('\\' )
585+ }
586+ case '\n' :
587+ s .buf .WriteByte ('\\' )
588+ case '\r' :
459589 s .buf .WriteByte ('\\' )
460590 case '\\' :
461591 s .buf .WriteByte ('\\' )
462- case '\n' :
463- return fmt .Errorf ("symbol or string column value contains a new line char: %s: %w" , str , ErrInvalidMsg )
464- case '\r' :
465- return fmt .Errorf ("symbol or string column value contains a carriage return char: %s: %w" , str , ErrInvalidMsg )
466592 }
467593 s .buf .WriteByte (b )
468594 }
@@ -512,6 +638,9 @@ func (s *LineSender) At(ctx context.Context, ts int64) error {
512638 if ! s .hasTable {
513639 return fmt .Errorf ("table name was not provided: %w" , ErrInvalidMsg )
514640 }
641+ if ! s .hasTags && ! s .hasFields {
642+ return fmt .Errorf ("no symbols or columns were provided: %w" , ErrInvalidMsg )
643+ }
515644
516645 if ts > - 1 {
517646 s .buf .WriteByte (' ' )
@@ -521,6 +650,7 @@ func (s *LineSender) At(ctx context.Context, ts int64) error {
521650
522651 s .lastMsgPos = s .buf .Len ()
523652 s .hasTable = false
653+ s .hasTags = false
524654 s .hasFields = false
525655
526656 if s .buf .Len () > s .bufCap {
0 commit comments