16
16
# under the License.
17
17
18
18
import json
19
+ import re
19
20
from abc import ABC , abstractmethod
20
21
from typing import Any , Dict , Optional , Tuple , Type , Union
21
22
@@ -111,6 +112,29 @@ def render(self) -> str:
111
112
def _render_internal (self ) -> str :
112
113
pass
113
114
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
+
114
138
def _is_forked (self ) -> bool :
115
139
if self .__class__ .__name__ == "Fork" :
116
140
return True
@@ -427,7 +451,7 @@ def sample(self, probability: float) -> "Sample":
427
451
"""
428
452
return Sample (self , probability )
429
453
430
- def sort (self , * columns : FieldType ) -> "Sort" :
454
+ def sort (self , * columns : ExpressionType ) -> "Sort" :
431
455
"""The ``SORT`` processing command sorts a table on one or more columns.
432
456
433
457
:param columns: The columns to sort on.
@@ -570,15 +594,12 @@ def metadata(self, *fields: FieldType) -> "From":
570
594
return self
571
595
572
596
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 ]
577
598
s = f'{ self .__class__ .__name__ .upper ()} { ", " .join (indices )} '
578
599
if self ._metadata_fields :
579
600
s = (
580
601
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 ])} '
582
603
)
583
604
return s
584
605
@@ -594,7 +615,11 @@ class Row(ESQLBase):
594
615
def __init__ (self , ** params : ExpressionType ):
595
616
super ().__init__ ()
596
617
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
+ )
598
623
for k , v in params .items ()
599
624
}
600
625
@@ -615,7 +640,7 @@ def __init__(self, item: str):
615
640
self ._item = item
616
641
617
642
def _render_internal (self ) -> str :
618
- return f"SHOW { self ._item } "
643
+ return f"SHOW { self ._format_id ( self . _item ) } "
619
644
620
645
621
646
class Branch (ESQLBase ):
@@ -667,11 +692,11 @@ def as_(self, type_name: str, pvalue_name: str) -> "ChangePoint":
667
692
return self
668
693
669
694
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 ) } "
671
696
names = (
672
697
""
673
698
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" ) } '
675
700
)
676
701
return f"CHANGE_POINT { self ._value } { key } { names } "
677
702
@@ -709,12 +734,13 @@ def with_(self, inference_id: str) -> "Completion":
709
734
def _render_internal (self ) -> str :
710
735
if self ._inference_id is None :
711
736
raise ValueError ("The completion command requires an inference ID" )
737
+ with_ = {"inference_id" : self ._inference_id }
712
738
if self ._named_prompt :
713
739
column = list (self ._named_prompt .keys ())[0 ]
714
740
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_ ) } "
716
742
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_ ) } "
718
744
719
745
720
746
class Dissect (ESQLBase ):
@@ -742,9 +768,13 @@ def append_separator(self, separator: str) -> "Dissect":
742
768
743
769
def _render_internal (self ) -> str :
744
770
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 } "
746
777
)
747
- return f"DISSECT { self ._input } { json .dumps (self ._pattern )} { sep } "
748
778
749
779
750
780
class Drop (ESQLBase ):
@@ -760,7 +790,7 @@ def __init__(self, parent: ESQLBase, *columns: FieldType):
760
790
self ._columns = columns
761
791
762
792
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 ])} '
764
794
765
795
766
796
class Enrich (ESQLBase ):
@@ -814,12 +844,18 @@ def with_(self, *fields: FieldType, **named_fields: FieldType) -> "Enrich":
814
844
return self
815
845
816
846
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
+ )
818
852
with_ = ""
819
853
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 ()])} '
821
855
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
+ )
823
859
return f"ENRICH { self ._policy } { on } { with_ } "
824
860
825
861
@@ -832,7 +868,10 @@ class Eval(ESQLBase):
832
868
"""
833
869
834
870
def __init__ (
835
- self , parent : ESQLBase , * columns : FieldType , ** named_columns : FieldType
871
+ self ,
872
+ parent : ESQLBase ,
873
+ * columns : ExpressionType ,
874
+ ** named_columns : ExpressionType ,
836
875
):
837
876
if columns and named_columns :
838
877
raise ValueError (
@@ -844,10 +883,13 @@ def __init__(
844
883
def _render_internal (self ) -> str :
845
884
if isinstance (self ._columns , dict ):
846
885
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
+ ]
848
890
)
849
891
else :
850
- cols = ", " .join ([f"{ col } " for col in self ._columns ])
892
+ cols = ", " .join ([f"{ self . _format_expr ( col ) } " for col in self ._columns ])
851
893
return f"EVAL { cols } "
852
894
853
895
@@ -900,7 +942,7 @@ def __init__(self, parent: ESQLBase, input: FieldType, pattern: str):
900
942
self ._pattern = pattern
901
943
902
944
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 )} "
904
946
905
947
906
948
class Keep (ESQLBase ):
@@ -916,7 +958,7 @@ def __init__(self, parent: ESQLBase, *columns: FieldType):
916
958
self ._columns = columns
917
959
918
960
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 ])} '
920
962
921
963
922
964
class Limit (ESQLBase ):
@@ -932,7 +974,7 @@ def __init__(self, parent: ESQLBase, max_number_of_rows: int):
932
974
self ._max_number_of_rows = max_number_of_rows
933
975
934
976
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 ) } "
936
978
937
979
938
980
class LookupJoin (ESQLBase ):
@@ -967,7 +1009,9 @@ def _render_internal(self) -> str:
967
1009
if isinstance (self ._lookup_index , str )
968
1010
else self ._lookup_index ._index ._name
969
1011
)
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
+ )
971
1015
972
1016
973
1017
class MvExpand (ESQLBase ):
@@ -983,7 +1027,7 @@ def __init__(self, parent: ESQLBase, column: FieldType):
983
1027
self ._column = column
984
1028
985
1029
def _render_internal (self ) -> str :
986
- return f"MV_EXPAND { self ._column } "
1030
+ return f"MV_EXPAND { self ._format_id ( self . _column ) } "
987
1031
988
1032
989
1033
class Rename (ESQLBase ):
@@ -999,7 +1043,7 @@ def __init__(self, parent: ESQLBase, **columns: FieldType):
999
1043
self ._columns = columns
1000
1044
1001
1045
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 ()])} '
1003
1047
1004
1048
1005
1049
class Sample (ESQLBase ):
@@ -1015,7 +1059,7 @@ def __init__(self, parent: ESQLBase, probability: float):
1015
1059
self ._probability = probability
1016
1060
1017
1061
def _render_internal (self ) -> str :
1018
- return f"SAMPLE { self ._probability } "
1062
+ return f"SAMPLE { json . dumps ( self ._probability ) } "
1019
1063
1020
1064
1021
1065
class Sort (ESQLBase ):
@@ -1026,12 +1070,16 @@ class Sort(ESQLBase):
1026
1070
in a single expression.
1027
1071
"""
1028
1072
1029
- def __init__ (self , parent : ESQLBase , * columns : FieldType ):
1073
+ def __init__ (self , parent : ESQLBase , * columns : ExpressionType ):
1030
1074
super ().__init__ (parent )
1031
1075
self ._columns = columns
1032
1076
1033
1077
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 ])} '
1035
1083
1036
1084
1037
1085
class Stats (ESQLBase ):
@@ -1062,14 +1110,17 @@ def by(self, *grouping_expressions: ExpressionType) -> "Stats":
1062
1110
1063
1111
def _render_internal (self ) -> str :
1064
1112
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
+ ]
1066
1117
else :
1067
- exprs = [f"{ expr } " for expr in self ._expressions ]
1118
+ exprs = [f"{ self . _format_expr ( expr ) } " for expr in self ._expressions ]
1068
1119
expression_separator = ",\n "
1069
1120
by = (
1070
1121
""
1071
1122
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 ])} '
1073
1124
)
1074
1125
return f'STATS { expression_separator .join ([f"{ expr } " for expr in exprs ])} { by } '
1075
1126
@@ -1087,7 +1138,7 @@ def __init__(self, parent: ESQLBase, *expressions: ExpressionType):
1087
1138
self ._expressions = expressions
1088
1139
1089
1140
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 ])} '
1091
1142
1092
1143
1093
1144
def and_ (* expressions : InstrumentedExpression ) -> "InstrumentedExpression" :
0 commit comments