Repository: mathesar-foundation/mathesar

Issue:
Transformation for hiding columns
We want a DE transformation that selects a subset of columns.


Discussion:

Resolving patch:
diff --git a/db/transforms/base.py b/db/transforms/base.py
--- a/db/transforms/base.py
+++ b/db/transforms/base.py
@@ -392,6 +392,50 @@ def _add_aliases_to_summarization_expr_field(
     return summarization
 
 
+class HideColumns(Transform):
+    """
+    Selects every column in the transform, except for the columns specified in the spec.
+
+    We're implementing this transform in terms of selecting columns (since Postgres doesn't really
+    support "selecting everything except").
+    """
+    type = "hide"
+
+    def apply_to_relation(self, relation):
+        input_aliases = [
+            col.name
+            for col
+            in relation.c
+        ]
+        columns_to_select = self.get_columns_to_select(input_aliases)
+        select_transform = SelectSubsetOfColumns(columns_to_select)
+        relation = select_transform.apply_to_relation(relation)
+        return relation
+
+    def get_unique_constraint_mappings(self, input_aliases):
+        columns_to_select = self.get_columns_to_select(input_aliases)
+        return [
+            UniqueConstraintMapping(
+                column_to_select,
+                column_to_select,
+            )
+            for column_to_select
+            in columns_to_select
+        ]
+
+    def get_columns_to_select(self, input_aliases):
+        return [
+            column
+            for column
+            in input_aliases
+            if column not in self._columns_to_hide
+        ]
+
+    @property
+    def _columns_to_hide(self):
+        return self.spec
+
+
 class SelectSubsetOfColumns(Transform):
     type = "select"
 


Test patch:
diff --git a/db/tests/records/operations/test_select.py b/db/tests/records/operations/test_select.py
--- a/db/tests/records/operations/test_select.py
+++ b/db/tests/records/operations/test_select.py
@@ -1,13 +1,9 @@
-import pytest
-
 from decimal import Decimal
 from collections import Counter
 
 from sqlalchemy import Column, VARCHAR
 
 from db.records.operations.select import get_records, get_column_cast_records
-from db.transforms.operations.apply import apply_transformations
-from db.transforms import base as transforms_base
 from db.tables.operations.create import create_mathesar_table
 from db.types.base import PostgresType
 
@@ -108,75 +104,3 @@ def test_get_records_duplicate_only(roster_table_obj):
     all_counter = {k: v for k, v in all_counter.items() if v > 1}
     got_counter = Counter(tuple(r[c] for c in duplicate_only) for r in dupe_record_list)
     assert all_counter == got_counter
-
-
-# TODO might want to move this to transforms test namespace
-@pytest.mark.parametrize(
-    "transformations,expected_records",
-    [
-        [
-            [
-                transforms_base.Filter(
-                    spec=dict(
-                        contains=[
-                            dict(column_name=["Student Name"]),
-                            dict(literal=["son"]),
-                        ]
-                    ),
-                ),
-                transforms_base.Order(
-                    spec=[{"field": "Teacher Email", "direction": "asc"}],
-                ),
-                transforms_base.Limit(
-                    spec=5,
-                ),
-                transforms_base.SelectSubsetOfColumns(
-                    spec=["id"],
-                ),
-            ],
-            [
-                (978,),
-                (194,),
-                (99,),
-                (155,),
-                (192,),
-            ]
-        ],
-        [
-            [
-                transforms_base.Limit(
-                    spec=50,
-                ),
-                transforms_base.Filter(
-                    spec=dict(
-                        contains=[
-                            dict(column_name=["Student Name"]),
-                            dict(literal=["son"]),
-                        ]
-                    ),
-                ),
-                transforms_base.Order(
-                    spec=[{"field": "Teacher Email", "direction": "asc"}],
-                ),
-                transforms_base.Limit(
-                    spec=5,
-                ),
-                transforms_base.SelectSubsetOfColumns(
-                    spec=["id"],
-                ),
-            ],
-            [
-                (31,),
-                (16,),
-                (18,),
-                (24,),
-                (33,),
-            ]
-        ],
-    ]
-)
-def test_transformations(roster_table_obj, transformations, expected_records):
-    roster, engine = roster_table_obj
-    relation = apply_transformations(roster, transformations)
-    records = get_records(relation, engine)
-    assert records == expected_records
diff --git a/db/tests/transforms/test_basic.py b/db/tests/transforms/test_basic.py
new file mode 100644
--- /dev/null
+++ b/db/tests/transforms/test_basic.py
@@ -0,0 +1,92 @@
+import pytest
+
+from db.transforms.operations.apply import apply_transformations
+from db.transforms import base as transforms_base
+from db.records.operations.select import get_records
+
+
+@pytest.mark.parametrize(
+    "transformations,expected_records",
+    [
+        [
+            [
+                transforms_base.Filter(
+                    spec=dict(
+                        contains=[
+                            dict(column_name=["Student Name"]),
+                            dict(literal=["son"]),
+                        ]
+                    ),
+                ),
+                transforms_base.Order(
+                    spec=[{"field": "Teacher Email", "direction": "asc"}],
+                ),
+                transforms_base.Limit(
+                    spec=5,
+                ),
+                transforms_base.SelectSubsetOfColumns(
+                    spec=["id"],
+                ),
+            ],
+            [
+                (978,),
+                (194,),
+                (99,),
+                (155,),
+                (192,),
+            ]
+        ],
+        [
+            [
+                transforms_base.Limit(
+                    spec=50,
+                ),
+                transforms_base.Filter(
+                    spec=dict(
+                        contains=[
+                            dict(column_name=["Student Name"]),
+                            dict(literal=["son"]),
+                        ]
+                    ),
+                ),
+                transforms_base.Order(
+                    spec=[{"field": "Teacher Email", "direction": "asc"}],
+                ),
+                transforms_base.Limit(
+                    spec=5,
+                ),
+                transforms_base.SelectSubsetOfColumns(
+                    spec=["id"],
+                ),
+            ],
+            [
+                (31,),
+                (16,),
+                (18,),
+                (24,),
+                (33,),
+            ]
+        ],
+        [
+            [
+                transforms_base.Limit(
+                    spec=1,
+                ),
+                transforms_base.SelectSubsetOfColumns(
+                    spec=["id", "Grade"],
+                ),
+                transforms_base.HideColumns(
+                    spec=["Grade"],
+                ),
+            ],
+            [
+                (1,),
+            ]
+        ],
+    ]
+)
+def test_transformations(roster_table_obj, transformations, expected_records):
+    roster, engine = roster_table_obj
+    relation = apply_transformations(roster, transformations)
+    records = get_records(relation, engine)
+    assert records == expected_records