Repository: great-expectations/great_expectations

Issue:
Use cleaner solution for non-truncating division in python 2
Prefer `from __future__ import division` to `1.*x/y`


Discussion:
Done inline during improve_partitions push.

Resolving patch:
diff --git a/great_expectations/execution_engine/sqlalchemy_execution_engine.py b/great_expectations/execution_engine/sqlalchemy_execution_engine.py
--- a/great_expectations/execution_engine/sqlalchemy_execution_engine.py
+++ b/great_expectations/execution_engine/sqlalchemy_execution_engine.py
@@ -1,6 +1,7 @@
 import copy
 import datetime
 import logging
+import traceback
 from pathlib import Path
 from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
 from urllib.parse import urlparse
@@ -10,6 +11,7 @@
 from great_expectations.core.util import convert_to_json_serializable
 from great_expectations.exceptions import (
     DatasourceKeyPairAuthBadPassphraseError,
+    ExecutionEngineError,
     GreatExpectationsError,
     InvalidConfigError,
 )
@@ -33,6 +35,7 @@
     from sqlalchemy.engine import reflection
     from sqlalchemy.engine.default import DefaultDialect
     from sqlalchemy.engine.url import URL
+    from sqlalchemy.exc import OperationalError
     from sqlalchemy.sql import Select
     from sqlalchemy.sql.elements import TextClause, quoted_name
 except ImportError:
@@ -41,6 +44,7 @@
     Select = None
     TextClause = None
     quoted_name = None
+    OperationalError = None
 
 
 try:
@@ -558,9 +562,9 @@ def resolve_metric_bundle(
         self,
         metric_fn_bundle: Iterable[Tuple[MetricConfiguration, Any, dict, dict]],
     ) -> dict:
-        """For every metrics in a set of Metrics to resolve, obtains necessary metric keyword arguments and builds a
-        bundles the metrics into one large query dictionary so that they are all executed simultaneously. Will fail if
-        bundling the metrics together is not possible.
+        """For every metric in a set of Metrics to resolve, obtains necessary metric keyword arguments and builds
+        bundles of the metrics into one large query dictionary so that they are all executed simultaneously. Will fail
+        if bundling the metrics together is not possible.
 
             Args:
                 metric_fn_bundle (Iterable[Tuple[MetricConfiguration, Callable, dict]): \
@@ -601,12 +605,19 @@ def resolve_metric_bundle(
                 query["domain_kwargs"], domain_type="identity"
             )
             assert len(query["select"]) == len(query["ids"])
-            res = self.engine.execute(
-                sa.select(query["select"]).select_from(selectable)
-            ).fetchall()
-            logger.debug(
-                f"SqlAlchemyExecutionEngine computed {len(res[0])} metrics on domain_id {IDDict(compute_domain_kwargs).to_id()}"
-            )
+            try:
+                res = self.engine.execute(
+                    sa.select(query["select"]).select_from(selectable)
+                ).fetchall()
+                logger.debug(
+                    f"SqlAlchemyExecutionEngine computed {len(res[0])} metrics on domain_id {IDDict(compute_domain_kwargs).to_id()}"
+                )
+            except OperationalError as oe:
+                exception_message: str = "An SQL execution Exception occurred.  "
+                exception_traceback: str = traceback.format_exc()
+                exception_message += f'{type(oe).__name__}: "{str(oe)}".  Traceback: "{exception_traceback}".'
+                logger.error(exception_message)
+                raise ExecutionEngineError(message=exception_message)
             assert (
                 len(res) == 1
             ), "all bundle-computed metrics must be single-value statistics"
diff --git a/great_expectations/expectations/metrics/column_aggregate_metric.py b/great_expectations/expectations/metrics/column_aggregate_metric.py
--- a/great_expectations/expectations/metrics/column_aggregate_metric.py
+++ b/great_expectations/expectations/metrics/column_aggregate_metric.py
@@ -2,6 +2,7 @@
 from functools import wraps
 from typing import Any, Callable, Dict, Tuple, Type
 
+import great_expectations.exceptions as ge_exceptions
 from great_expectations.execution_engine import ExecutionEngine, PandasExecutionEngine
 from great_expectations.execution_engine.execution_engine import (
     MetricDomainTypes,
@@ -27,7 +28,7 @@ def column_aggregate_value(
     engine: Type[ExecutionEngine],
     metric_fn_type="value",
     domain_type="column",
-    **kwargs
+    **kwargs,
 ):
     """Return the column aggregate metric decorator for the specified engine.
 
@@ -62,14 +63,22 @@ def inner_func(
                 df, _, accessor_domain_kwargs = execution_engine.get_compute_domain(
                     domain_kwargs=metric_domain_kwargs, domain_type=domain_type
                 )
-                if filter_column_isnull:
-                    df = df[df[accessor_domain_kwargs["column"]].notnull()]
-                return metric_fn(
-                    cls,
-                    column=df[accessor_domain_kwargs["column"]],
-                    **metric_value_kwargs,
-                    _metrics=metrics,
-                )
+
+                column_name = accessor_domain_kwargs["column"]
+                try:
+                    if filter_column_isnull:
+                        df = df[df[column_name].notnull()]
+
+                    return metric_fn(
+                        cls,
+                        column=df[column_name],
+                        **metric_value_kwargs,
+                        _metrics=metrics,
+                    )
+                except KeyError:
+                    raise ge_exceptions.ExecutionEngineError(
+                        message=f'Error: The column "{column_name}" in BatchData does not exist.'
+                    )
 
             return inner_func
 
@@ -126,9 +135,11 @@ def inner_func(
                 ) = execution_engine.get_compute_domain(
                     compute_domain_kwargs, domain_type=domain_type
                 )
+
                 column_name = accessor_domain_kwargs["column"]
                 sqlalchemy_engine = execution_engine.engine
                 dialect = sqlalchemy_engine.dialect
+
                 metric_aggregate = metric_fn(
                     cls,
                     column=sa.column(column_name),
@@ -181,10 +192,17 @@ def inner_func(
                 ) = execution_engine.get_compute_domain(
                     domain_kwargs=compute_domain_kwargs, domain_type=domain_type
                 )
+
                 column_name = accessor_domain_kwargs["column"]
+                if column_name not in data.columns:
+                    raise ge_exceptions.ExecutionEngineError(
+                        message=f'Error: The column "{column_name}" in BatchData does not exist.'
+                    )
+
+                column = data[column_name]
                 metric_aggregate = metric_fn(
                     cls,
-                    column=data[column_name],
+                    column=column,
                     **metric_value_kwargs,
                     _table=data,
                     _column_name=column_name,
diff --git a/great_expectations/expectations/metrics/map_metric.py b/great_expectations/expectations/metrics/map_metric.py
--- a/great_expectations/expectations/metrics/map_metric.py
+++ b/great_expectations/expectations/metrics/map_metric.py
@@ -1,16 +1,15 @@
+import logging
+import traceback
 import uuid
 from functools import wraps
-from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
+from typing import Any, Callable, Dict, List, Optional, Type, Union
 
 import numpy as np
 
 import great_expectations.exceptions as ge_exceptions
 from great_expectations.core import ExpectationConfiguration
 from great_expectations.core.util import convert_to_json_serializable
-from great_expectations.exceptions.metric_exceptions import (
-    MetricError,
-    MetricProviderError,
-)
+from great_expectations.exceptions.metric_exceptions import MetricProviderError
 from great_expectations.execution_engine import ExecutionEngine, PandasExecutionEngine
 from great_expectations.execution_engine.execution_engine import (
     MetricDomainTypes,
@@ -22,6 +21,7 @@
     SparkDFExecutionEngine,
 )
 from great_expectations.execution_engine.sqlalchemy_execution_engine import (
+    OperationalError,
     SqlAlchemyExecutionEngine,
     sa,
 )
@@ -35,6 +35,8 @@
 )
 from great_expectations.validator.validation_graph import MetricConfiguration
 
+logger = logging.getLogger(__name__)
+
 
 def column_function_partial(
     engine: Type[ExecutionEngine], partial_fn_type: str = None, **kwargs
@@ -89,15 +91,23 @@ def inner_func(
                 ) = execution_engine.get_compute_domain(
                     domain_kwargs=metric_domain_kwargs, domain_type=domain_type
                 )
-                if filter_column_isnull:
-                    df = df[df[accessor_domain_kwargs["column"]].notnull()]
-                values = metric_fn(
-                    cls,
-                    df[accessor_domain_kwargs["column"]],
-                    **metric_value_kwargs,
-                    _metrics=metrics,
-                )
-                return values, compute_domain_kwargs, accessor_domain_kwargs
+
+                column_name = accessor_domain_kwargs["column"]
+                try:
+                    if filter_column_isnull:
+                        df = df[df[column_name].notnull()]
+
+                    values = metric_fn(
+                        cls,
+                        df[column_name],
+                        **metric_value_kwargs,
+                        _metrics=metrics,
+                    )
+                    return values, compute_domain_kwargs, accessor_domain_kwargs
+                except KeyError:
+                    raise ge_exceptions.ExecutionEngineError(
+                        message=f'Error: The column "{column_name}" in BatchData does not exist.'
+                    )
 
             return inner_func
 
@@ -208,10 +218,17 @@ def inner_func(
                 ) = execution_engine.get_compute_domain(
                     domain_kwargs=compute_domain_kwargs, domain_type=domain_type
                 )
+
                 column_name = accessor_domain_kwargs["column"]
+                if column_name not in data.columns:
+                    raise ge_exceptions.ExecutionEngineError(
+                        message=f'Error: The column "{column_name}" in BatchData does not exist.'
+                    )
+
+                column = data[column_name]
                 column_function = metric_fn(
                     cls,
-                    column=data[column_name],
+                    column=column,
                     **metric_value_kwargs,
                     _metrics=metrics,
                     _compute_domain_kwargs=compute_domain_kwargs,
@@ -285,14 +302,15 @@ def inner_func(
                 ) = execution_engine.get_compute_domain(
                     domain_kwargs=metric_domain_kwargs, domain_type=domain_type
                 )
-                # TODO: <Alex>Is this the correct place for and type of Exception to raise if domain data is not attributable?</Alex>
+
+                column_name = accessor_domain_kwargs["column"]
                 try:
                     if filter_column_isnull:
-                        df = df[df[accessor_domain_kwargs["column"]].notnull()]
+                        df = df[df[column_name].notnull()]
 
                     meets_expectation_series = metric_fn(
                         cls,
-                        df[accessor_domain_kwargs["column"]],
+                        df[column_name],
                         **metric_value_kwargs,
                         _metrics=metrics,
                     )
@@ -303,7 +321,7 @@ def inner_func(
                     )
                 except KeyError:
                     raise ge_exceptions.ExecutionEngineError(
-                        message=f'Error: The column "{accessor_domain_kwargs["column"]}" in BatchData does not exist.'
+                        message=f'Error: The column "{column_name}" in BatchData does not exist.'
                     )
 
             return inner_func
@@ -349,6 +367,7 @@ def inner_func(
                 ) = execution_engine.get_compute_domain(
                     metric_domain_kwargs, domain_type=domain_type
                 )
+
                 column_name = accessor_domain_kwargs["column"]
                 dialect = execution_engine.dialect_module
                 sqlalchemy_engine = execution_engine.engine
@@ -411,6 +430,7 @@ def inner_func(
                 filter_column_isnull = kwargs.get(
                     "filter_column_isnull", getattr(cls, "filter_column_isnull", True)
                 )
+
                 (
                     data,
                     compute_domain_kwargs,
@@ -418,7 +438,13 @@ def inner_func(
                 ) = execution_engine.get_compute_domain(
                     domain_kwargs=metric_domain_kwargs, domain_type=domain_type
                 )
+
                 column_name = accessor_domain_kwargs["column"]
+                if column_name not in data.columns:
+                    raise ge_exceptions.ExecutionEngineError(
+                        message=f'Error: The column "{column_name}" in BatchData does not exist.'
+                    )
+
                 column = data[column_name]
                 expected_condition = metric_fn(
                     cls,
@@ -484,6 +510,7 @@ def _pandas_column_map_condition_values(
     df, _, _ = execution_engine.get_compute_domain(
         domain_kwargs=compute_domain_kwargs, domain_type="identity"
     )
+
     ###
     # NOTE: 20201111 - JPC - in the map_series / map_condition_series world (pandas), we
     # currently handle filter_column_isnull differently than other map_fn / map_condition
@@ -492,14 +519,21 @@ def _pandas_column_map_condition_values(
     filter_column_isnull = kwargs.get(
         "filter_column_isnull", getattr(cls, "filter_column_isnull", False)
     )
-    if filter_column_isnull:
-        df = df[df[accessor_domain_kwargs["column"]].notnull()]
 
-    if "column" in accessor_domain_kwargs:
-        domain_values = df[accessor_domain_kwargs["column"]]
-    else:
-        raise ValueError(
-            "_pandas_column_map_condition_values requires a column in accessor_domain_kwargs"
+    column_name = accessor_domain_kwargs["column"]
+    try:
+        if filter_column_isnull:
+            df = df[df[column_name].notnull()]
+
+        if "column" in accessor_domain_kwargs:
+            domain_values = df[column_name]
+        else:
+            raise ValueError(
+                "_pandas_column_map_condition_values requires a column in accessor_domain_kwargs"
+            )
+    except KeyError:
+        raise ge_exceptions.ExecutionEngineError(
+            message=f'Error: The column "{column_name}" in BatchData does not exist.'
         )
 
     result_format = metric_value_kwargs["result_format"]
@@ -541,6 +575,7 @@ def _pandas_column_map_series_and_domain_values(
     df, _, _ = execution_engine.get_compute_domain(
         domain_kwargs=compute_domain_kwargs, domain_type="identity"
     )
+
     ###
     # NOTE: 20201111 - JPC - in the map_series / map_condition_series world (pandas), we
     # currently handle filter_column_isnull differently than other map_fn / map_condition
@@ -549,14 +584,21 @@ def _pandas_column_map_series_and_domain_values(
     filter_column_isnull = kwargs.get(
         "filter_column_isnull", getattr(cls, "filter_column_isnull", False)
     )
-    if filter_column_isnull:
-        df = df[df[accessor_domain_kwargs["column"]].notnull()]
 
-    if "column" in accessor_domain_kwargs:
-        domain_values = df[accessor_domain_kwargs["column"]]
-    else:
-        raise ValueError(
-            "_pandas_column_map_series_and_domain_values requires a column in accessor_domain_kwargs"
+    column_name = accessor_domain_kwargs["column"]
+    try:
+        if filter_column_isnull:
+            df = df[df[column_name].notnull()]
+
+        if "column" in accessor_domain_kwargs:
+            domain_values = df[column_name]
+        else:
+            raise ValueError(
+                "_pandas_column_map_series_and_domain_values requires a column in accessor_domain_kwargs"
+            )
+    except KeyError:
+        raise ge_exceptions.ExecutionEngineError(
+            message=f'Error: The column "{column_name}" in BatchData does not exist.'
         )
 
     result_format = metric_value_kwargs["result_format"]
@@ -597,6 +639,7 @@ def _pandas_map_condition_index(
     df, _, _ = execution_engine.get_compute_domain(
         domain_kwargs=compute_domain_kwargs, domain_type="identity"
     )
+
     ###
     # NOTE: 20201111 - JPC - in the map_series / map_condition_series world (pandas), we
     # currently handle filter_column_isnull differently than other map_fn / map_condition
@@ -605,16 +648,22 @@ def _pandas_map_condition_index(
     filter_column_isnull = kwargs.get(
         "filter_column_isnull", getattr(cls, "filter_column_isnull", False)
     )
-    if filter_column_isnull:
-        df = df[df[accessor_domain_kwargs["column"]].notnull()]
-    data = df[accessor_domain_kwargs["column"]]
+
+    column_name = accessor_domain_kwargs["column"]
+    try:
+        if filter_column_isnull:
+            df = df[df[column_name].notnull()]
+    except KeyError:
+        raise ge_exceptions.ExecutionEngineError(
+            message=f'Error: The column "{column_name}" in BatchData does not exist.'
+        )
 
     result_format = metric_value_kwargs["result_format"]
     if result_format["result_format"] == "COMPLETE":
-        return list(df[boolean_mapped_unexpected_values == True].index)
+        return list(df[boolean_mapped_unexpected_values].index)
     else:
         return list(
-            df[boolean_mapped_unexpected_values == True].index[
+            df[boolean_mapped_unexpected_values].index[
                 : result_format["partial_unexpected_count"]
             ]
         )
@@ -638,6 +687,7 @@ def _pandas_column_map_condition_value_counts(
     df, _, _ = execution_engine.get_compute_domain(
         domain_kwargs=compute_domain_kwargs, domain_type="identity"
     )
+
     ###
     # NOTE: 20201111 - JPC - in the map_series / map_condition_series world (pandas), we
     # currently handle filter_column_isnull differently than other map_fn / map_condition
@@ -646,26 +696,30 @@ def _pandas_column_map_condition_value_counts(
     filter_column_isnull = kwargs.get(
         "filter_column_isnull", getattr(cls, "filter_column_isnull", False)
     )
-    if filter_column_isnull:
-        df = df[df[accessor_domain_kwargs["column"]].notnull()]
-    data = df[accessor_domain_kwargs["column"]]
-    if "column" in accessor_domain_kwargs:
-        domain_values = df[accessor_domain_kwargs["column"]]
-    else:
-        raise ValueError(
-            "_pandas_column_map_condition_value_counts requires a column in accessor_domain_kwargs"
+
+    column_name = accessor_domain_kwargs["column"]
+    try:
+        if filter_column_isnull:
+            df = df[df[column_name].notnull()]
+        if "column" in accessor_domain_kwargs:
+            domain_values = df[column_name]
+        else:
+            raise ValueError(
+                "_pandas_column_map_condition_value_counts requires a column in accessor_domain_kwargs"
+            )
+    except KeyError:
+        raise ge_exceptions.ExecutionEngineError(
+            message=f'Error: The column "{column_name}" in BatchData does not exist.'
         )
 
     result_format = metric_value_kwargs["result_format"]
     value_counts = None
     try:
-        value_counts = domain_values[
-            boolean_mapped_unexpected_values == True
-        ].value_counts()
+        value_counts = domain_values[boolean_mapped_unexpected_values].value_counts()
     except ValueError:
         try:
             value_counts = (
-                domain_values[boolean_mapped_unexpected_values == True]
+                domain_values[boolean_mapped_unexpected_values]
                 .apply(tuple)
                 .value_counts()
             )
@@ -673,7 +727,7 @@ def _pandas_column_map_condition_value_counts(
             pass
 
     if not value_counts:
-        raise MetricError("Unable to compute value counts")
+        raise ge_exceptions.MetricError("Unable to compute value counts")
 
     if result_format["result_format"] == "COMPLETE":
         return value_counts
@@ -699,6 +753,7 @@ def _pandas_map_condition_rows(
     df, _, _ = execution_engine.get_compute_domain(
         domain_kwargs=compute_domain_kwargs, domain_type="identity"
     )
+
     ###
     # NOTE: 20201111 - JPC - in the map_series / map_condition_series world (pandas), we
     # currently handle filter_column_isnull differently than other map_fn / map_condition
@@ -707,15 +762,21 @@ def _pandas_map_condition_rows(
     filter_column_isnull = kwargs.get(
         "filter_column_isnull", getattr(cls, "filter_column_isnull", False)
     )
-    if filter_column_isnull:
-        df = df[df[accessor_domain_kwargs["column"]].notnull()]
-    data = df[accessor_domain_kwargs["column"]]
+
+    column_name = accessor_domain_kwargs["column"]
+    try:
+        if filter_column_isnull:
+            df = df[df[column_name].notnull()]
+    except KeyError:
+        raise ge_exceptions.ExecutionEngineError(
+            message=f'Error: The column "{column_name}" in BatchData does not exist.'
+        )
 
     result_format = metric_value_kwargs["result_format"]
     if result_format["result_format"] == "COMPLETE":
-        return df[boolean_mapped_unexpected_values == True]
+        return df[boolean_mapped_unexpected_values]
     else:
-        return df[boolean_mapped_unexpected_values == True][
+        return df[boolean_mapped_unexpected_values][
             result_format["partial_unexpected_count"]
         ]
 
@@ -766,49 +827,58 @@ def _sqlalchemy_map_condition_unexpected_count_value(
         # mssql expects all temporary table names to have a prefix '#'
         temp_table_name = f"#{temp_table_name}"
 
-    with execution_engine.engine.begin():
-        metadata: sa.MetaData = sa.MetaData(execution_engine.engine)
-        temp_table_obj: sa.Table = sa.Table(
-            temp_table_name,
-            metadata,
-            sa.Column("condition", sa.Integer, primary_key=False, nullable=False),
-        )
-        temp_table_obj.create(execution_engine.engine, checkfirst=True)
+    try:
+        with execution_engine.engine.begin():
+            metadata: sa.MetaData = sa.MetaData(execution_engine.engine)
+            temp_table_obj: sa.Table = sa.Table(
+                temp_table_name,
+                metadata,
+                sa.Column("condition", sa.Integer, primary_key=False, nullable=False),
+            )
+            temp_table_obj.create(execution_engine.engine, checkfirst=True)
+
+            count_case_statement: List[sa.sql.elements.Label] = [
+                sa.case(
+                    [
+                        (
+                            unexpected_condition,
+                            1,
+                        )
+                    ],
+                    else_=0,
+                ).label("condition")
+            ]
+            inner_case_query: sa.sql.dml.Insert = temp_table_obj.insert().from_select(
+                count_case_statement,
+                sa.select(count_case_statement).select_from(selectable),
+            )
+            execution_engine.engine.execute(inner_case_query)
 
-        count_case_statement: List[sa.sql.elements.Label] = [
-            sa.case(
+        unexpected_count_query: sa.Select = (
+            sa.select(
                 [
-                    (
-                        unexpected_condition,
-                        1,
-                    )
-                ],
-                else_=0,
-            ).label("condition")
-        ]
-        inner_case_query: sa.sql.dml.Insert = temp_table_obj.insert().from_select(
-            count_case_statement,
-            sa.select(count_case_statement).select_from(selectable),
-        )
-        execution_engine.engine.execute(inner_case_query)
-
-    unexpected_count_query: sa.Select = (
-        sa.select(
-            [
-                sa.func.sum(sa.column("condition")).label("unexpected_count"),
-            ]
+                    sa.func.sum(sa.column("condition")).label("unexpected_count"),
+                ]
+            )
+            .select_from(temp_table_obj)
+            .alias("UnexpectedCountSubquery")
         )
-        .select_from(temp_table_obj)
-        .alias("UnexpectedCountSubquery")
-    )
 
-    unexpected_count = execution_engine.engine.execute(
-        sa.select(
-            [
-                unexpected_count_query.c.unexpected_count,
-            ]
+        unexpected_count = execution_engine.engine.execute(
+            sa.select(
+                [
+                    unexpected_count_query.c.unexpected_count,
+                ]
+            )
+        ).scalar()
+    except OperationalError as oe:
+        exception_message: str = "An SQL execution Exception occurred.  "
+        exception_traceback: str = traceback.format_exc()
+        exception_message += (
+            f'{type(oe).__name__}: "{str(oe)}".  Traceback: "{exception_traceback}".'
         )
-    ).scalar()
+        logger.error(exception_message)
+        raise ge_exceptions.ExecutionEngineError(message=exception_message)
 
     return convert_to_json_serializable(unexpected_count)
 
@@ -847,10 +917,19 @@ def _sqlalchemy_column_map_condition_values(
     )
     if result_format["result_format"] != "COMPLETE":
         query = query.limit(result_format["partial_unexpected_count"])
-    return [
-        val.unexpected_values
-        for val in execution_engine.engine.execute(query).fetchall()
-    ]
+    try:
+        return [
+            val.unexpected_values
+            for val in execution_engine.engine.execute(query).fetchall()
+        ]
+    except OperationalError as oe:
+        exception_message: str = "An SQL execution Exception occurred.  "
+        exception_traceback: str = traceback.format_exc()
+        exception_message += (
+            f'{type(oe).__name__}: "{str(oe)}".  Traceback: "{exception_traceback}".'
+        )
+        logger.error(exception_message)
+        raise ge_exceptions.ExecutionEngineError(message=exception_message)
 
 
 def _sqlalchemy_column_map_condition_value_counts(
@@ -876,12 +955,21 @@ def _sqlalchemy_column_map_condition_value_counts(
             "_sqlalchemy_column_map_condition_value_counts requires a column in accessor_domain_kwargs"
         )
     column = sa.column(accessor_domain_kwargs["column"])
-    return execution_engine.engine.execute(
-        sa.select([column, sa.func.count(column)])
-        .select_from(selectable)
-        .where(unexpected_condition)
-        .group_by(column)
-    ).fetchall()
+    try:
+        return execution_engine.engine.execute(
+            sa.select([column, sa.func.count(column)])
+            .select_from(selectable)
+            .where(unexpected_condition)
+            .group_by(column)
+        ).fetchall()
+    except OperationalError as oe:
+        exception_message: str = "An SQL execution Exception occurred.  "
+        exception_traceback: str = traceback.format_exc()
+        exception_message += (
+            f'{type(oe).__name__}: "{str(oe)}".  Traceback: "{exception_traceback}".'
+        )
+        logger.error(exception_message)
+        raise ge_exceptions.ExecutionEngineError(message=exception_message)
 
 
 def _sqlalchemy_map_condition_rows(
@@ -909,7 +997,16 @@ def _sqlalchemy_map_condition_rows(
     )
     if result_format["result_format"] != "COMPLETE":
         query = query.limit(result_format["partial_unexpected_count"])
-    return execution_engine.engine.execute(query).fetchall()
+    try:
+        return execution_engine.engine.execute(query).fetchall()
+    except OperationalError as oe:
+        exception_message: str = "An SQL execution Exception occurred.  "
+        exception_traceback: str = traceback.format_exc()
+        exception_message += (
+            f'{type(oe).__name__}: "{str(oe)}".  Traceback: "{exception_traceback}".'
+        )
+        logger.error(exception_message)
+        raise ge_exceptions.ExecutionEngineError(message=exception_message)
 
 
 def _spark_map_condition_unexpected_count_aggregate_fn(


Test patch:
diff --git a/tests/expectations/metrics/test_core.py b/tests/expectations/metrics/test_core.py
--- a/tests/expectations/metrics/test_core.py
+++ b/tests/expectations/metrics/test_core.py
@@ -2,7 +2,9 @@
 
 import numpy as np
 import pandas as pd
+import pytest
 
+import great_expectations.exceptions as ge_exceptions
 from great_expectations.core.batch import Batch
 from great_expectations.execution_engine import (
     PandasExecutionEngine,
@@ -66,19 +68,6 @@ def test_basic_metric():
     assert results == {desired_metric.id: 3}
 
 
-def test_column_max():
-    df = pd.DataFrame({"a": [1, 2, 3, 3, None]})
-    batch = Batch(data=df)
-    engine = PandasExecutionEngine(batch_data_dict={batch.id: batch.data})
-    desired_metric = MetricConfiguration(
-        metric_name="column.max",
-        metric_domain_kwargs={"column": "a"},
-        metric_value_kwargs=dict(),
-    )
-    results = engine.resolve_metrics(metrics_to_resolve=(desired_metric,))
-    assert results == {desired_metric.id: 3}
-
-
 def test_mean_metric_pd():
     engine = _build_pandas_engine(pd.DataFrame({"a": [1, 2, 3, None]}))
     desired_metric = MetricConfiguration(
@@ -101,7 +90,41 @@ def test_stdev_metric_pd():
     assert results == {desired_metric.id: 1}
 
 
-def test_max_metric_sa(sa):
+def test_max_metric_pd_column_exists():
+    df = pd.DataFrame({"a": [1, 2, 3, 3, None]})
+    batch = Batch(data=df)
+    engine = PandasExecutionEngine(batch_data_dict={batch.id: batch.data})
+
+    desired_metric = MetricConfiguration(
+        metric_name="column.max",
+        metric_domain_kwargs={"column": "a"},
+        metric_value_kwargs=dict(),
+    )
+    results = engine.resolve_metrics(metrics_to_resolve=(desired_metric,))
+    assert results == {desired_metric.id: 3}
+
+
+def test_max_metric_pd_column_does_not_exist():
+    df = pd.DataFrame({"a": [1, 2, 3, 3, None]})
+    batch = Batch(data=df)
+    engine = PandasExecutionEngine(batch_data_dict={batch.id: batch.data})
+
+    desired_metric = MetricConfiguration(
+        metric_name="column.max",
+        metric_domain_kwargs={"column": "non_existent_column"},
+        metric_value_kwargs=dict(),
+    )
+
+    with pytest.raises(ge_exceptions.ExecutionEngineError) as eee:
+        # noinspection PyUnusedLocal
+        results = engine.resolve_metrics(metrics_to_resolve=(desired_metric,))
+    assert (
+        str(eee.value)
+        == 'Error: The column "non_existent_column" in BatchData does not exist.'
+    )
+
+
+def test_max_metric_sa_column_exists(sa):
     engine = _build_sa_engine(pd.DataFrame({"a": [1, 2, 1, None]}), sa)
 
     partial_metric = MetricConfiguration(
@@ -124,7 +147,32 @@ def test_max_metric_sa(sa):
     assert results == {desired_metric.id: 2}
 
 
-def test_max_metric_spark(spark_session):
+def test_max_metric_sa_column_does_not_exist(sa):
+    engine = _build_sa_engine(pd.DataFrame({"a": [1, 2, 1, None]}), sa)
+
+    partial_metric = MetricConfiguration(
+        metric_name="column.max.aggregate_fn",
+        metric_domain_kwargs={"column": "non_existent_column"},
+        metric_value_kwargs=dict(),
+    )
+
+    metrics = engine.resolve_metrics(metrics_to_resolve=(partial_metric,))
+    desired_metric = MetricConfiguration(
+        metric_name="column.max",
+        metric_domain_kwargs={"column": "non_existent_column"},
+        metric_value_kwargs=dict(),
+        metric_dependencies={"metric_partial_fn": partial_metric},
+    )
+
+    with pytest.raises(ge_exceptions.ExecutionEngineError) as eee:
+        # noinspection PyUnusedLocal
+        results = engine.resolve_metrics(
+            metrics_to_resolve=(desired_metric,), metrics=metrics
+        )
+    assert "An SQL execution Exception occurred.  OperationalError" in str(eee.value)
+
+
+def test_max_metric_spark_column_exists(spark_session):
     engine = _build_spark_engine(pd.DataFrame({"a": [1, 2, 1]}), spark_session)
     partial_metric = MetricConfiguration(
         metric_name="column.max.aggregate_fn",
@@ -146,6 +194,24 @@ def test_max_metric_spark(spark_session):
     assert results == {desired_metric.id: 2}
 
 
+def test_max_metric_spark_column_does_not_exist(spark_session):
+    engine = _build_spark_engine(pd.DataFrame({"a": [1, 2, 1]}), spark_session)
+
+    partial_metric = MetricConfiguration(
+        metric_name="column.max.aggregate_fn",
+        metric_domain_kwargs={"column": "non_existent_column"},
+        metric_value_kwargs=dict(),
+    )
+
+    with pytest.raises(ge_exceptions.ExecutionEngineError) as eee:
+        # noinspection PyUnusedLocal
+        metrics = engine.resolve_metrics(metrics_to_resolve=(partial_metric,))
+    assert (
+        str(eee.value)
+        == 'Error: The column "non_existent_column" in BatchData does not exist.'
+    )
+
+
 def test_map_value_set_sa(sa):
     engine = _build_sa_engine(pd.DataFrame({"a": [1, 2, 3, 3, None]}), sa)
     desired_metric = MetricConfiguration(
@@ -282,7 +348,7 @@ def test_map_column_value_lengths_between_pd():
     assert ser_expected_lengths.equals(result_series)
 
 
-def test_map_unique_pd():
+def test_map_unique_pd_column_exists():
     engine = _build_pandas_engine(pd.DataFrame({"a": [1, 2, 3, 3, None]}))
     desired_metric = MetricConfiguration(
         metric_name="column_values.unique.condition",
@@ -294,17 +360,31 @@ def test_map_unique_pd():
     assert list(results[desired_metric.id][0]) == [False, False, True, True]
 
 
-def test_map_unique_spark(spark_session):
-    engine = _build_spark_engine(
+def test_map_unique_pd_column_does_not_exist():
+    engine = _build_pandas_engine(pd.DataFrame({"a": [1, 2, 3, 3, None]}))
+
+    desired_metric = MetricConfiguration(
+        metric_name="column_values.unique.condition",
+        metric_domain_kwargs={"column": "non_existent_column"},
+        metric_value_kwargs=dict(),
+    )
+
+    with pytest.raises(ge_exceptions.ExecutionEngineError) as eee:
+        # noinspection PyUnusedLocal
+        results = engine.resolve_metrics(metrics_to_resolve=(desired_metric,))
+    assert (
+        str(eee.value)
+        == 'Error: The column "non_existent_column" in BatchData does not exist.'
+    )
+
+
+def test_map_unique_sa_column_exists(sa):
+    engine = _build_sa_engine(
         pd.DataFrame(
-            {
-                "a": [1, 2, 3, 3, 4, None],
-                "b": [None, "foo", "bar", "baz", "qux", "fish"],
-            }
+            {"a": [1, 2, 3, 3, None], "b": ["foo", "bar", "baz", "qux", "fish"]}
         ),
-        spark_session,
+        sa,
     )
-
     condition_metric = MetricConfiguration(
         metric_name="column_values.unique.condition",
         metric_domain_kwargs={"column": "a"},
@@ -312,15 +392,28 @@ def test_map_unique_spark(spark_session):
     )
     metrics = engine.resolve_metrics(metrics_to_resolve=(condition_metric,))
 
-    # unique is a *window* function so does not use the aggregate_fn version of unexpected count
+    # This is no longer a MAP_CONDITION because mssql does not support it. Instead, it is a WINDOW_CONDITION
+    #
+    # aggregate_fn = MetricConfiguration(
+    #     metric_name="column_values.unique.unexpected_count.aggregate_fn",
+    #     metric_domain_kwargs={"column": "a"},
+    #     metric_value_kwargs=dict(),
+    #     metric_dependencies={"unexpected_condition": condition_metric},
+    # )
+    # aggregate_fn_metrics = engine.resolve_metrics(
+    #     metrics_to_resolve=(aggregate_fn,), metrics=metrics
+    # )
+
     desired_metric = MetricConfiguration(
         metric_name="column_values.unique.unexpected_count",
         metric_domain_kwargs={"column": "a"},
         metric_value_kwargs=dict(),
+        # metric_dependencies={"metric_partial_fn": aggregate_fn},
         metric_dependencies={"unexpected_condition": condition_metric},
     )
     results = engine.resolve_metrics(
-        metrics_to_resolve=(desired_metric,), metrics=metrics
+        metrics_to_resolve=(desired_metric,),
+        metrics=metrics,  # metrics=aggregate_fn_metrics
     )
     assert results[desired_metric.id] == 2
 
@@ -361,10 +454,10 @@ def test_map_unique_spark(spark_session):
     results = engine.resolve_metrics(
         metrics_to_resolve=(desired_metric,), metrics=metrics
     )
-    assert results[desired_metric.id] == [(3, "bar"), (3, "baz")]
+    assert results[desired_metric.id] == [(3, "baz"), (3, "qux")]
 
 
-def test_map_unique_sa(sa):
+def test_map_unique_sa_column_does_not_exist(sa):
     engine = _build_sa_engine(
         pd.DataFrame(
             {"a": [1, 2, 3, 3, None], "b": ["foo", "bar", "baz", "qux", "fish"]}
@@ -373,7 +466,7 @@ def test_map_unique_sa(sa):
     )
     condition_metric = MetricConfiguration(
         metric_name="column_values.unique.condition",
-        metric_domain_kwargs={"column": "a"},
+        metric_domain_kwargs={"column": "non_existent_column"},
         metric_value_kwargs=dict(),
     )
     metrics = engine.resolve_metrics(metrics_to_resolve=(condition_metric,))
@@ -392,14 +485,47 @@ def test_map_unique_sa(sa):
 
     desired_metric = MetricConfiguration(
         metric_name="column_values.unique.unexpected_count",
-        metric_domain_kwargs={"column": "a"},
+        metric_domain_kwargs={"column": "non_existent_column"},
         metric_value_kwargs=dict(),
         # metric_dependencies={"metric_partial_fn": aggregate_fn},
         metric_dependencies={"unexpected_condition": condition_metric},
     )
+    with pytest.raises(ge_exceptions.ExecutionEngineError) as eee:
+        # noinspection PyUnusedLocal
+        results = engine.resolve_metrics(
+            metrics_to_resolve=(desired_metric,),
+            metrics=metrics,  # metrics=aggregate_fn_metrics
+        )
+    assert "An SQL execution Exception occurred.  OperationalError" in str(eee.value)
+
+
+def test_map_unique_spark_column_exists(spark_session):
+    engine = _build_spark_engine(
+        pd.DataFrame(
+            {
+                "a": [1, 2, 3, 3, 4, None],
+                "b": [None, "foo", "bar", "baz", "qux", "fish"],
+            }
+        ),
+        spark_session,
+    )
+
+    condition_metric = MetricConfiguration(
+        metric_name="column_values.unique.condition",
+        metric_domain_kwargs={"column": "a"},
+        metric_value_kwargs=dict(),
+    )
+    metrics = engine.resolve_metrics(metrics_to_resolve=(condition_metric,))
+
+    # unique is a *window* function so does not use the aggregate_fn version of unexpected count
+    desired_metric = MetricConfiguration(
+        metric_name="column_values.unique.unexpected_count",
+        metric_domain_kwargs={"column": "a"},
+        metric_value_kwargs=dict(),
+        metric_dependencies={"unexpected_condition": condition_metric},
+    )
     results = engine.resolve_metrics(
-        metrics_to_resolve=(desired_metric,),
-        metrics=metrics,  # metrics=aggregate_fn_metrics
+        metrics_to_resolve=(desired_metric,), metrics=metrics
     )
     assert results[desired_metric.id] == 2
 
@@ -440,7 +566,33 @@ def test_map_unique_sa(sa):
     results = engine.resolve_metrics(
         metrics_to_resolve=(desired_metric,), metrics=metrics
     )
-    assert results[desired_metric.id] == [(3, "baz"), (3, "qux")]
+    assert results[desired_metric.id] == [(3, "bar"), (3, "baz")]
+
+
+def test_map_unique_spark_column_does_not_exist(spark_session):
+    engine = _build_spark_engine(
+        pd.DataFrame(
+            {
+                "a": [1, 2, 3, 3, 4, None],
+                "b": [None, "foo", "bar", "baz", "qux", "fish"],
+            }
+        ),
+        spark_session,
+    )
+
+    condition_metric = MetricConfiguration(
+        metric_name="column_values.unique.condition",
+        metric_domain_kwargs={"column": "non_existent_column"},
+        metric_value_kwargs=dict(),
+    )
+
+    with pytest.raises(ge_exceptions.ExecutionEngineError) as eee:
+        # noinspection PyUnusedLocal
+        metrics = engine.resolve_metrics(metrics_to_resolve=(condition_metric,))
+    assert (
+        str(eee.value)
+        == 'Error: The column "non_existent_column" in BatchData does not exist.'
+    )
 
 
 def test_z_score_under_threshold_pd():
diff --git a/tests/validator/test_validator.py b/tests/validator/test_validator.py
--- a/tests/validator/test_validator.py
+++ b/tests/validator/test_validator.py
@@ -1,17 +1,15 @@
 import pandas as pd
 import pytest
 
-import great_expectations.expectations.metrics
+import great_expectations.exceptions as ge_exceptions
 from great_expectations.core import IDDict
 from great_expectations.core.batch import Batch, BatchRequest, PartitionRequest
 from great_expectations.core.expectation_configuration import ExpectationConfiguration
 from great_expectations.core.expectation_validation_result import (
     ExpectationValidationResult,
 )
-from great_expectations.exceptions import InvalidDataContextKeyError
 from great_expectations.exceptions.metric_exceptions import MetricProviderError
 from great_expectations.execution_engine import PandasExecutionEngine
-from great_expectations.expectations.core import ExpectColumnMaxToBeBetween
 from great_expectations.expectations.core.expect_column_value_z_scores_to_be_less_than import (
     ExpectColumnValueZScoresToBeLessThan,
 )
@@ -247,13 +245,15 @@ def test_graph_validate_with_bad_config(basic_datasource):
         expectation_type="expect_column_max_to_be_between",
         kwargs={"column": "not_in_table", "min_value": 1, "max_value": 29},
     )
-    try:
+    with pytest.raises(ge_exceptions.ExecutionEngineError) as eee:
+        # noinspection PyUnusedLocal
         result = Validator(
             execution_engine=PandasExecutionEngine(), batches=[batch]
         ).graph_validate(configurations=[expectation_configuration])
-    except KeyError as e:
-        result = e
-    assert isinstance(result, KeyError)
+    assert (
+        str(eee.value)
+        == 'Error: The column "not_in_table" in BatchData does not exist.'
+    )
 
 
 # Tests that runtime configuration actually works during graph validation
@@ -361,7 +361,7 @@ def test_validator_default_expectation_args__sql(
 
     print(my_validator.get_default_expectation_arguments())
 
-    with pytest.raises(InvalidDataContextKeyError):
+    with pytest.raises(ge_exceptions.InvalidDataContextKeyError):
         # expectation_suite_name is a number not str
         my_validator = context.get_validator(
             datasource_name="my_sqlite_db",