1616# under the License.
1717
1818import json
19+ import re
1920from abc import ABC , abstractmethod
2021from typing import Any , Dict , Optional , Tuple , Type , Union
2122
@@ -111,6 +112,29 @@ def render(self) -> str:
111112 def _render_internal (self ) -> str :
112113 pass
113114
115+ @staticmethod
116+ def _format_index (index : IndexType ) -> str :
117+ return index ._index ._name if hasattr (index , "_index" ) else str (index )
118+
119+ @staticmethod
120+ def _format_id (id : FieldType , allow_patterns : bool = False ) -> str :
121+ s = str (id ) # in case it is an InstrumentedField
122+ if allow_patterns and "*" in s :
123+ return s # patterns cannot be escaped
124+ if re .fullmatch (r"[a-zA-Z_@][a-zA-Z0-9_\.]*" , s ):
125+ return s
126+ # this identifier needs to be escaped
127+ s .replace ("`" , "``" )
128+ return f"`{ s } `"
129+
130+ @staticmethod
131+ def _format_expr (expr : ExpressionType ) -> str :
132+ return (
133+ json .dumps (expr )
134+ if not isinstance (expr , (str , InstrumentedExpression ))
135+ else str (expr )
136+ )
137+
114138 def _is_forked (self ) -> bool :
115139 if self .__class__ .__name__ == "Fork" :
116140 return True
@@ -427,7 +451,7 @@ def sample(self, probability: float) -> "Sample":
427451 """
428452 return Sample (self , probability )
429453
430- def sort (self , * columns : FieldType ) -> "Sort" :
454+ def sort (self , * columns : ExpressionType ) -> "Sort" :
431455 """The ``SORT`` processing command sorts a table on one or more columns.
432456
433457 :param columns: The columns to sort on.
@@ -570,15 +594,12 @@ def metadata(self, *fields: FieldType) -> "From":
570594 return self
571595
572596 def _render_internal (self ) -> str :
573- indices = [
574- index if isinstance (index , str ) else index ._index ._name
575- for index in self ._indices
576- ]
597+ indices = [self ._format_index (index ) for index in self ._indices ]
577598 s = f'{ self .__class__ .__name__ .upper ()} { ", " .join (indices )} '
578599 if self ._metadata_fields :
579600 s = (
580601 s
581- + f' METADATA { ", " .join ([str (field ) for field in self ._metadata_fields ])} '
602+ + f' METADATA { ", " .join ([self . _format_id (field ) for field in self ._metadata_fields ])} '
582603 )
583604 return s
584605
@@ -594,7 +615,11 @@ class Row(ESQLBase):
594615 def __init__ (self , ** params : ExpressionType ):
595616 super ().__init__ ()
596617 self ._params = {
597- k : json .dumps (v ) if not isinstance (v , InstrumentedExpression ) else v
618+ self ._format_id (k ): (
619+ json .dumps (v )
620+ if not isinstance (v , InstrumentedExpression )
621+ else self ._format_expr (v )
622+ )
598623 for k , v in params .items ()
599624 }
600625
@@ -615,7 +640,7 @@ def __init__(self, item: str):
615640 self ._item = item
616641
617642 def _render_internal (self ) -> str :
618- return f"SHOW { self ._item } "
643+ return f"SHOW { self ._format_id ( self . _item ) } "
619644
620645
621646class Branch (ESQLBase ):
@@ -667,11 +692,11 @@ def as_(self, type_name: str, pvalue_name: str) -> "ChangePoint":
667692 return self
668693
669694 def _render_internal (self ) -> str :
670- key = "" if not self ._key else f" ON { self ._key } "
695+ key = "" if not self ._key else f" ON { self ._format_id ( self . _key ) } "
671696 names = (
672697 ""
673698 if not self ._type_name and not self ._pvalue_name
674- else f' AS { self ._type_name or "type" } , { self ._pvalue_name or "pvalue" } '
699+ else f' AS { self ._format_id ( self . _type_name or "type" ) } , { self ._format_id ( self . _pvalue_name or "pvalue" ) } '
675700 )
676701 return f"CHANGE_POINT { self ._value } { key } { names } "
677702
@@ -709,12 +734,13 @@ def with_(self, inference_id: str) -> "Completion":
709734 def _render_internal (self ) -> str :
710735 if self ._inference_id is None :
711736 raise ValueError ("The completion command requires an inference ID" )
737+ with_ = {"inference_id" : self ._inference_id }
712738 if self ._named_prompt :
713739 column = list (self ._named_prompt .keys ())[0 ]
714740 prompt = list (self ._named_prompt .values ())[0 ]
715- return f"COMPLETION { column } = { prompt } WITH { self . _inference_id } "
741+ return f"COMPLETION { self . _format_id ( column ) } = { self . _format_id ( prompt ) } WITH { json . dumps ( with_ ) } "
716742 else :
717- return f"COMPLETION { self ._prompt [0 ]} WITH { self . _inference_id } "
743+ return f"COMPLETION { self ._format_id ( self . _prompt [0 ]) } WITH { json . dumps ( with_ ) } "
718744
719745
720746class Dissect (ESQLBase ):
@@ -742,9 +768,13 @@ def append_separator(self, separator: str) -> "Dissect":
742768
743769 def _render_internal (self ) -> str :
744770 sep = (
745- "" if self ._separator is None else f' APPEND_SEPARATOR="{ self ._separator } "'
771+ ""
772+ if self ._separator is None
773+ else f" APPEND_SEPARATOR={ json .dumps (self ._separator )} "
774+ )
775+ return (
776+ f"DISSECT { self ._format_id (self ._input )} { json .dumps (self ._pattern )} { sep } "
746777 )
747- return f"DISSECT { self ._input } { json .dumps (self ._pattern )} { sep } "
748778
749779
750780class Drop (ESQLBase ):
@@ -760,7 +790,7 @@ def __init__(self, parent: ESQLBase, *columns: FieldType):
760790 self ._columns = columns
761791
762792 def _render_internal (self ) -> str :
763- return f'DROP { ", " .join ([str (col ) for col in self ._columns ])} '
793+ return f'DROP { ", " .join ([self . _format_id (col , allow_patterns = True ) for col in self ._columns ])} '
764794
765795
766796class Enrich (ESQLBase ):
@@ -814,12 +844,18 @@ def with_(self, *fields: FieldType, **named_fields: FieldType) -> "Enrich":
814844 return self
815845
816846 def _render_internal (self ) -> str :
817- on = "" if self ._match_field is None else f" ON { self ._match_field } "
847+ on = (
848+ ""
849+ if self ._match_field is None
850+ else f" ON { self ._format_id (self ._match_field )} "
851+ )
818852 with_ = ""
819853 if self ._named_fields :
820- with_ = f' WITH { ", " .join ([f"{ name } = { field } " for name , field in self ._named_fields .items ()])} '
854+ with_ = f' WITH { ", " .join ([f"{ self . _format_id ( name ) } = { self . _format_id ( field ) } " for name , field in self ._named_fields .items ()])} '
821855 elif self ._fields is not None :
822- with_ = f' WITH { ", " .join ([str (field ) for field in self ._fields ])} '
856+ with_ = (
857+ f' WITH { ", " .join ([self ._format_id (field ) for field in self ._fields ])} '
858+ )
823859 return f"ENRICH { self ._policy } { on } { with_ } "
824860
825861
@@ -832,7 +868,10 @@ class Eval(ESQLBase):
832868 """
833869
834870 def __init__ (
835- self , parent : ESQLBase , * columns : FieldType , ** named_columns : FieldType
871+ self ,
872+ parent : ESQLBase ,
873+ * columns : ExpressionType ,
874+ ** named_columns : ExpressionType ,
836875 ):
837876 if columns and named_columns :
838877 raise ValueError (
@@ -844,10 +883,13 @@ def __init__(
844883 def _render_internal (self ) -> str :
845884 if isinstance (self ._columns , dict ):
846885 cols = ", " .join (
847- [f"{ name } = { value } " for name , value in self ._columns .items ()]
886+ [
887+ f"{ self ._format_id (name )} = { self ._format_expr (value )} "
888+ for name , value in self ._columns .items ()
889+ ]
848890 )
849891 else :
850- cols = ", " .join ([f"{ col } " for col in self ._columns ])
892+ cols = ", " .join ([f"{ self . _format_expr ( col ) } " for col in self ._columns ])
851893 return f"EVAL { cols } "
852894
853895
@@ -900,7 +942,7 @@ def __init__(self, parent: ESQLBase, input: FieldType, pattern: str):
900942 self ._pattern = pattern
901943
902944 def _render_internal (self ) -> str :
903- return f"GROK { self ._input } { json .dumps (self ._pattern )} "
945+ return f"GROK { self ._format_id ( self . _input ) } { json .dumps (self ._pattern )} "
904946
905947
906948class Keep (ESQLBase ):
@@ -916,7 +958,7 @@ def __init__(self, parent: ESQLBase, *columns: FieldType):
916958 self ._columns = columns
917959
918960 def _render_internal (self ) -> str :
919- return f'KEEP { ", " .join ([f"{ col } " for col in self ._columns ])} '
961+ return f'KEEP { ", " .join ([f"{ self . _format_id ( col , allow_patterns = True ) } " for col in self ._columns ])} '
920962
921963
922964class Limit (ESQLBase ):
@@ -932,7 +974,7 @@ def __init__(self, parent: ESQLBase, max_number_of_rows: int):
932974 self ._max_number_of_rows = max_number_of_rows
933975
934976 def _render_internal (self ) -> str :
935- return f"LIMIT { self ._max_number_of_rows } "
977+ return f"LIMIT { json . dumps ( self ._max_number_of_rows ) } "
936978
937979
938980class LookupJoin (ESQLBase ):
@@ -967,7 +1009,9 @@ def _render_internal(self) -> str:
9671009 if isinstance (self ._lookup_index , str )
9681010 else self ._lookup_index ._index ._name
9691011 )
970- return f"LOOKUP JOIN { index } ON { self ._field } "
1012+ return (
1013+ f"LOOKUP JOIN { self ._format_index (index )} ON { self ._format_id (self ._field )} "
1014+ )
9711015
9721016
9731017class MvExpand (ESQLBase ):
@@ -983,7 +1027,7 @@ def __init__(self, parent: ESQLBase, column: FieldType):
9831027 self ._column = column
9841028
9851029 def _render_internal (self ) -> str :
986- return f"MV_EXPAND { self ._column } "
1030+ return f"MV_EXPAND { self ._format_id ( self . _column ) } "
9871031
9881032
9891033class Rename (ESQLBase ):
@@ -999,7 +1043,7 @@ def __init__(self, parent: ESQLBase, **columns: FieldType):
9991043 self ._columns = columns
10001044
10011045 def _render_internal (self ) -> str :
1002- return f'RENAME { ", " .join ([f"{ old_name } AS { new_name } " for old_name , new_name in self ._columns .items ()])} '
1046+ return f'RENAME { ", " .join ([f"{ self . _format_id ( old_name ) } AS { self . _format_id ( new_name ) } " for old_name , new_name in self ._columns .items ()])} '
10031047
10041048
10051049class Sample (ESQLBase ):
@@ -1015,7 +1059,7 @@ def __init__(self, parent: ESQLBase, probability: float):
10151059 self ._probability = probability
10161060
10171061 def _render_internal (self ) -> str :
1018- return f"SAMPLE { self ._probability } "
1062+ return f"SAMPLE { json . dumps ( self ._probability ) } "
10191063
10201064
10211065class Sort (ESQLBase ):
@@ -1026,12 +1070,16 @@ class Sort(ESQLBase):
10261070 in a single expression.
10271071 """
10281072
1029- def __init__ (self , parent : ESQLBase , * columns : FieldType ):
1073+ def __init__ (self , parent : ESQLBase , * columns : ExpressionType ):
10301074 super ().__init__ (parent )
10311075 self ._columns = columns
10321076
10331077 def _render_internal (self ) -> str :
1034- return f'SORT { ", " .join ([f"{ col } " for col in self ._columns ])} '
1078+ sorts = [
1079+ " " .join ([self ._format_id (term ) for term in str (col ).split (" " )])
1080+ for col in self ._columns
1081+ ]
1082+ return f'SORT { ", " .join ([f"{ sort } " for sort in sorts ])} '
10351083
10361084
10371085class Stats (ESQLBase ):
@@ -1062,14 +1110,17 @@ def by(self, *grouping_expressions: ExpressionType) -> "Stats":
10621110
10631111 def _render_internal (self ) -> str :
10641112 if isinstance (self ._expressions , dict ):
1065- exprs = [f"{ key } = { value } " for key , value in self ._expressions .items ()]
1113+ exprs = [
1114+ f"{ self ._format_id (key )} = { self ._format_expr (value )} "
1115+ for key , value in self ._expressions .items ()
1116+ ]
10661117 else :
1067- exprs = [f"{ expr } " for expr in self ._expressions ]
1118+ exprs = [f"{ self . _format_expr ( expr ) } " for expr in self ._expressions ]
10681119 expression_separator = ",\n "
10691120 by = (
10701121 ""
10711122 if self ._grouping_expressions is None
1072- else f'\n BY { ", " .join ([f"{ expr } " for expr in self ._grouping_expressions ])} '
1123+ else f'\n BY { ", " .join ([f"{ self . _format_expr ( expr ) } " for expr in self ._grouping_expressions ])} '
10731124 )
10741125 return f'STATS { expression_separator .join ([f"{ expr } " for expr in exprs ])} { by } '
10751126
@@ -1087,7 +1138,7 @@ def __init__(self, parent: ESQLBase, *expressions: ExpressionType):
10871138 self ._expressions = expressions
10881139
10891140 def _render_internal (self ) -> str :
1090- return f'WHERE { " AND " .join ([f"{ expr } " for expr in self ._expressions ])} '
1141+ return f'WHERE { " AND " .join ([f"{ self . _format_expr ( expr ) } " for expr in self ._expressions ])} '
10911142
10921143
10931144def and_ (* expressions : InstrumentedExpression ) -> "InstrumentedExpression" :
0 commit comments