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/expectations/metrics/multicolumn_map_metrics/compound_columns_unique.py b/great_expectations/expectations/metrics/multicolumn_map_metrics/compound_columns_unique.py
--- a/great_expectations/expectations/metrics/multicolumn_map_metrics/compound_columns_unique.py
+++ b/great_expectations/expectations/metrics/multicolumn_map_metrics/compound_columns_unique.py
@@ -66,7 +66,9 @@ def _sqlalchemy_function(self, column_list, **kwargs):
         # Need all columns of the table for the purposes of reporting entire rows satisfying unexpected condition logic.
         table_columns = kwargs.get("_table_columns")
 
-        table = kwargs.get("_table")
+        table = kwargs.get(
+            "_table"
+        )  # Note that here, "table" is of the "sqlalchemy.sql.selectable.Subquery" type.
 
         # First, obtain the SQLAlchemy "FromClause" version of the original "table" for the purposes of gaining the
         # "FromClause.c" attribute, which is a namespace of all the columns contained within the "FROM" clause (these
@@ -74,8 +76,10 @@ def _sqlalchemy_function(self, column_list, **kwargs):
         table_columns_selector = [
             sa.column(column_name) for column_name in table_columns
         ]
-        original_table_query = (
-            sa.select(table_columns_selector).select_from(table).alias("original_table")
+        original_table_clause = (
+            sa.select(table_columns_selector)
+            .select_from(table)
+            .alias("original_table_clause")
         )
 
         # Second, "SELECT FROM" the original table, represented by the "FromClause" object, querying all columns of the
@@ -86,32 +90,35 @@ def _sqlalchemy_function(self, column_list, **kwargs):
         group_count_query = (
             sa.select(count_selector)
             .group_by(*column_list)
-            .select_from(original_table_query)
-            .alias("group_counts")
+            .select_from(original_table_clause)
+            .alias("group_counts_subquery")
         )
 
         # The above "group_count_query", if executed, will produce the result set containing the number of rows that
         # equals the number of distinct values of the group -- unique grouping (e.g., as in a multi-column primary key).
         # Hence, in order for the "_num_rows" column values to provide an entry for each row of the original table, the
-        # "SELECT FROM" of "group_count_query" must undergo an "INNER JOIN" operation with the "original_table_query"
+        # "SELECT FROM" of "group_count_query" must undergo an "INNER JOIN" operation with the "original_table_clause"
         # object, whereby all table columns in the two "FromClause" objects must match, respectively, as the conditions.
         conditions = sa.and_(
             *(
-                group_count_query.c[name] == original_table_query.c[name]
+                group_count_query.c[name] == original_table_clause.c[name]
                 for name in column_names
             )
         )
         # noinspection PyProtectedMember
         compound_columns_count_query = (
             sa.select(
-                [original_table_query, group_count_query.c._num_rows.label("_num_rows")]
+                [
+                    original_table_clause,
+                    group_count_query.c._num_rows.label("_num_rows"),
+                ]
             )
             .select_from(
-                original_table_query.join(
+                original_table_clause.join(
                     right=group_count_query, onclause=conditions, isouter=False
                 )
             )
-            .alias("records_with_grouped_column_counts")
+            .alias("records_with_grouped_column_counts_subquery")
         )
 
         # The returned SQLAlchemy "FromClause" "compound_columns_count_query" object realizes the "map" metric function.
diff --git a/great_expectations/expectations/metrics/util.py b/great_expectations/expectations/metrics/util.py
--- a/great_expectations/expectations/metrics/util.py
+++ b/great_expectations/expectations/metrics/util.py
@@ -24,7 +24,7 @@
     from sqlalchemy.engine import Engine, reflection
     from sqlalchemy.engine.interfaces import Dialect
     from sqlalchemy.exc import OperationalError
-    from sqlalchemy.sql import Insert, Select
+    from sqlalchemy.sql import Insert, Select, TableClause
     from sqlalchemy.sql.elements import (
         BinaryExpression,
         ColumnElement,
@@ -44,6 +44,7 @@
     BinaryExpression = None
     ColumnElement = None
     Label = None
+    TableClause = None
     TextClause = None
     literal = None
     custom_op = None
@@ -102,6 +103,7 @@
     import teradatasqlalchemy.types as teradatatypes
 except ImportError:
     teradatasqlalchemy = None
+    teradatatypes = None
 
 
 def get_dialect_regex_expression(column, regex, dialect, positive=True):
@@ -307,7 +309,7 @@ def get_sqlalchemy_column_metadata(
                 sqlalchemy_engine=engine,
             )
 
-        # Use fallback because for mssql reflection doesn't throw an error but returns an empty list
+        # Use fallback because mssql reflection mechanism does not throw an error but returns an empty list
         if len(columns) == 0:
             columns = column_reflection_fallback(
                 selectable=table_selectable,
@@ -329,26 +331,105 @@ def column_reflection_fallback(
     if dialect.name.lower() == "mssql":
         # Get column names and types from the database
         # Reference: https://dataedo.com/kb/query/sql-server/list-table-columns-in-database
-        columns_query: str = f"""
-SELECT
-    SCHEMA_NAME(tab.schema_id) AS schema_name,
-    tab.name AS table_name,
-    col.column_id AS column_id,
-    col.name AS column_name,
-    t.name AS column_data_type,
-    col.max_length AS column_max_length,
-    col.precision AS column_precision
-FROM sys.tables AS tab
-    INNER JOIN sys.columns AS col
-    ON tab.object_id = col.object_id
-    LEFT JOIN sys.types AS t
-    ON col.user_type_id = t.user_type_id
-WHERE tab.name = '{selectable}'
-ORDER BY schema_name,
-    table_name,
-    column_id
-"""
-        col_info_query: TextClause = sa.text(columns_query)
+        tables_table_clause: TableClause = sa.table(
+            "tables",
+            sa.column("object_id"),
+            sa.column("schema_id"),
+            sa.column("name"),
+            schema="sys",
+        ).alias("sys_tables_table_clause")
+        tables_table_query: Select = (
+            sa.select(
+                [
+                    tables_table_clause.c.object_id.label("object_id"),
+                    sa.func.schema_name(tables_table_clause.c.schema_id).label(
+                        "schema_name"
+                    ),
+                    tables_table_clause.c.name.label("table_name"),
+                ]
+            )
+            .select_from(tables_table_clause)
+            .alias("sys_tables_table_subquery")
+        )
+        columns_table_clause: TableClause = sa.table(
+            "columns",
+            sa.column("object_id"),
+            sa.column("user_type_id"),
+            sa.column("column_id"),
+            sa.column("name"),
+            sa.column("max_length"),
+            sa.column("precision"),
+            schema="sys",
+        ).alias("sys_columns_table_clause")
+        columns_table_query: Select = (
+            sa.select(
+                [
+                    columns_table_clause.c.object_id.label("object_id"),
+                    columns_table_clause.c.user_type_id.label("user_type_id"),
+                    columns_table_clause.c.column_id.label("column_id"),
+                    columns_table_clause.c.name.label("column_name"),
+                    columns_table_clause.c.max_length.label("column_max_length"),
+                    columns_table_clause.c.precision.label("column_precision"),
+                ]
+            )
+            .select_from(columns_table_clause)
+            .alias("sys_columns_table_subquery")
+        )
+        types_table_clause: TableClause = sa.table(
+            "types",
+            sa.column("user_type_id"),
+            sa.column("name"),
+            schema="sys",
+        ).alias("sys_types_table_clause")
+        types_table_query: Select = (
+            sa.select(
+                [
+                    types_table_clause.c.user_type_id.label("user_type_id"),
+                    types_table_clause.c.name.label("column_data_type"),
+                ]
+            )
+            .select_from(types_table_clause)
+            .alias("sys_types_table_subquery")
+        )
+        inner_join_conditions: BinaryExpression = sa.and_(
+            *(tables_table_query.c.object_id == columns_table_query.c.object_id,)
+        )
+        outer_join_conditions: BinaryExpression = sa.and_(
+            *(
+                columns_table_query.columns.user_type_id
+                == types_table_query.columns.user_type_id,
+            )
+        )
+        col_info_query: Select = (
+            sa.select(
+                [
+                    tables_table_query.c.schema_name,
+                    tables_table_query.c.table_name,
+                    columns_table_query.c.column_id,
+                    columns_table_query.c.column_name,
+                    types_table_query.c.column_data_type,
+                    columns_table_query.c.column_max_length,
+                    columns_table_query.c.column_precision,
+                ]
+            )
+            .select_from(
+                tables_table_query.join(
+                    right=columns_table_query,
+                    onclause=inner_join_conditions,
+                    isouter=False,
+                ).join(
+                    right=types_table_query,
+                    onclause=outer_join_conditions,
+                    isouter=True,
+                )
+            )
+            .where(tables_table_query.c.table_name == selectable.name)
+            .order_by(
+                tables_table_query.c.schema_name.asc(),
+                tables_table_query.c.table_name.asc(),
+                columns_table_query.c.column_id.asc(),
+            )
+        )
         col_info_tuples_list: List[tuple] = sqlalchemy_engine.execute(
             col_info_query
         ).fetchall()


Test patch: