Repository: Azure/pykusto

Issue:
Column names that are language keywords don't parse well
if a column name is, for example, 'title', it should be parsed as "['title']", otherwise, Kusto throws an exception

Discussion:

Patch:
diff --git a/pykusto/expressions.py b/pykusto/expressions.py
index 6a7617a..967780d 100644
--- a/pykusto/expressions.py
+++ b/pykusto/expressions.py
@@ -2,6 +2,7 @@ from datetime import datetime, timedelta
 from typing import Any, List, Tuple, Mapping, Optional
 from typing import Union
 
+from pykusto.keywords import KUSTO_KEYWORDS
 from pykusto.kql_converters import KQL
 from pykusto.type_utils import plain_expression, aggregation_expression, PythonTypes, kql_converter, KustoType, typed_column, TypeRegistrar, get_base_types
 
@@ -786,8 +787,8 @@ class BaseColumn(BaseExpression):
         assert cls is not BaseColumn, "BaseColumn is abstract"
         return object.__new__(cls)
 
-    def __init__(self, name: str) -> None:
-        super().__init__(KQL(f"['{name}']" if '.' in name else name))
+    def __init__(self, name: str, quote: bool = False) -> None:
+        super().__init__(KQL(f"['{name}']" if quote or '.' in name or name in KUSTO_KEYWORDS else name))
         self._name = name
 
     def get_name(self) -> str:
@@ -872,6 +873,13 @@ class ColumnGenerator:
     def __getitem__(self, name: str) -> AnyTypeColumn:
         return AnyTypeColumn(name)
 
+    # noinspection PyMethodMayBeStatic
+    def of(self, name: str) -> AnyTypeColumn:
+        """
+        Workaround in case automatic column name quoting fails
+        """
+        return AnyTypeColumn(name, quote=True)
+
 
 # Recommended usage: from pykusto.expressions import column_generator as col
 # TODO: Is there a way to enforce this to be a singleton?
diff --git a/pykusto/keywords.py b/pykusto/keywords.py
new file mode 100644
index 0000000..e44c743
--- /dev/null
+++ b/pykusto/keywords.py
@@ -0,0 +1,149 @@
+# noinspection SpellCheckingInspection
+KUSTO_KEYWORDS = frozenset([
+    'abs',
+    'accumulate',
+    'acos',
+    'ago',
+    'and',
+    'anomalychart',
+    'anomalycolumns',
+    'areachart',
+    'array_concat',
+    'array_iif',
+    'array_index_of',
+    'array_length',
+    'array_rotate_left',
+    'array_rotate_right',
+    'array_shift_left',
+    'array_shift_right',
+    'array_slice',
+    'array_split',
+    'avg',
+    'avgif',
+    'axes',
+    'bag_keys',
+    'barchart',
+    'bin',
+    'bin',
+    'bin',
+    'bin_at',
+    'bin_auto',
+    'card',
+    'ceiling',
+    'columnchart',
+    'contains',
+    'cos',
+    'countif',
+    'dcount',
+    'dcount_hll',
+    'dcountif',
+    'dynamic',
+    'endofday',
+    'endofmonth',
+    'endofweek',
+    'endofyear',
+    'endswith',
+    'exp',
+    'floor',
+    'format_datetime',
+    'format_timespan',
+    'getmonth',
+    'gettype',
+    'getyear',
+    'hash',
+    'hourofday',
+    'iff',
+    'iif',
+    'in',
+    'isempty',
+    'isfinite',
+    'isinf',
+    'isnan',
+    'isnotempty',
+    'isnotnull',
+    'isnull',
+    'kind',
+    'ladderchart',
+    'legend',
+    'linechart',
+    'log',
+    'loggamma',
+    'make_bag',
+    'make_datetime',
+    'make_list',
+    'make_set',
+    'matches',
+    'max',
+    'maxif',
+    'min',
+    'minif',
+    'none',
+    'not',
+    'now',
+    'or',
+    'pack',
+    'pack_array',
+    'panels',
+    'parse_json',
+    'percentiles',
+    'piechart',
+    'pivotchart',
+    'pow',
+    'regex',
+    'round',
+    'scatterchart',
+    'series',
+    'set_difference',
+    'set_has_element',
+    'set_intersect',
+    'set_union',
+    'sign',
+    'split',
+    'sqrt',
+    'stacked',
+    'stacked100',
+    'stackedareachart',
+    'startofday',
+    'startofmonth',
+    'startofweek',
+    'startofyear',
+    'startswith',
+    'stdev',
+    'stdevif',
+    'stdevp',
+    'strcat_array',
+    'strcat_delim',
+    'strcmp',
+    'string_size',
+    'strlen',
+    'strrep',
+    'substring',
+    'sum',
+    'sumif',
+    'table',
+    'timechart',
+    'timepivot',
+    'title',
+    'tobool',
+    'todatetime',
+    'tohex',
+    'toint',
+    'tolong',
+    'tolower',
+    'tostring',
+    'toupper',
+    'unstacked',
+    'variance',
+    'varianceif',
+    'variancep',
+    'xaxis',
+    'xcolumn',
+    'xtitle',
+    'yaxis',
+    'ycolumns',
+    'ymax',
+    'ymin',
+    'ysplit',
+    'ysplit',
+    'ytitle',
+])


Test patch:
diff --git a/test/test_expressions.py b/test/test_expressions.py
index bd17f79..be7f396 100644
--- a/test/test_expressions.py
+++ b/test/test_expressions.py
@@ -349,3 +349,13 @@ class TestExpressions(TestBase):
         self.assertIsInstance(field2, AnyTypeColumn)
         self.assertEqual('foo', field1.get_name())
         self.assertEqual('foo.bar', field2.get_name())
+
+    def test_column_name_quoting(self):
+        self.assertEqual(
+            ' | where [\'title\'] has "test"',
+            Query().where(t.title.has("test")).render()
+        )
+        self.assertEqual(
+            ' | where [\'stringField\'] has "test"',
+            Query().where(col.of('stringField').has("test")).render()
+        )