Repository: mathesar-foundation/mathesar

Issue:
We need a way to specify Preproc function for base_grouping_column
## Description
In partial summarization, when the `grouping_expressions` also include the `base_grouping_column`, "guessing" does not work.
* We need a place to specify `preproc` functions for the `base_grouping_column`. We should either allow additionally specifying it within `grouping_expressions` or extend `base_grouping_column` to include `preproc`.
* If we do not allow it within `grouping_expressions`, the "guessed" response should not include it within `grouping_expressions`. Currently, it is also included.

cc: @dmos62  @mathemancer 


Discussion:
The implementation intent was for a complete specification to include `base_grouping_column` in the `grouping_expressions` list, and to consider a specification as partial when (and only when) any of the input aliases (distinct from initial columns) are missing from the specification. Adding `base_grouping_column` to `grouping_expressions` should not override anything.

Btw, the definition of input aliases might not be clear. We consider each transform in a query to have its own input aliases. The input aliases of the first transform are the query's initial columns. Some transforms introduce new aliases (e.g. summarization's aggregation) or remove aliases (`select` transform). Current implementation only works off of initial columns. The improvements I'm currently working on will let the backend make smarter recommendations where input aliases are not equivalent to initial columns.

Resolving patch:
diff --git a/db/transforms/operations/finish_specifying.py b/db/transforms/operations/finish_specifying.py
--- a/db/transforms/operations/finish_specifying.py
+++ b/db/transforms/operations/finish_specifying.py
@@ -26,17 +26,16 @@ def finish_specifying_summarize_transform(
         _split_missing_input_aliases_into_group_and_agg_lists(
             db_query, ix_of_summarize_transform, engine, metadata
         )
-    there_is_something_to_add = \
-        aliases_to_be_added_to_group_by or aliases_to_be_added_to_group_by
-    if there_is_something_to_add:
-        summarize_transform = \
-            summarize_transform.get_new_with_aliases_added_to_agg_on(
-                aliases_to_be_added_to_agg_on
-            )
+    if aliases_to_be_added_to_group_by:
         summarize_transform = \
             summarize_transform.get_new_with_aliases_added_to_group_by(
                 aliases_to_be_added_to_group_by
             )
+    if aliases_to_be_added_to_agg_on:
+        summarize_transform = \
+            summarize_transform.get_new_with_aliases_added_to_agg_on(
+                aliases_to_be_added_to_agg_on
+            )
     return summarize_transform
 
 
@@ -180,7 +179,10 @@ def _is_initial_column_unique_constrained(initial_column, engine, metadata):
 
 
 def _is_sa_column_unique_constrained(sa_column):
-    return sa_column.primary_key or sa_column.unique
+    return bool(
+        sa_column.primary_key
+        or sa_column.unique
+    )
 
 
 def _get_oid_of_initial_column(initial_column):


Test patch:
diff --git a/db/tests/transforms/test_finish_specifying_summarization.py b/db/tests/transforms/test_finish_specifying_summarization.py
--- a/db/tests/transforms/test_finish_specifying_summarization.py
+++ b/db/tests/transforms/test_finish_specifying_summarization.py
@@ -163,6 +163,18 @@ def initial_columns(academics_ids):
 )
 
 
+# a partial summarization transform whose base grouping column is in the grouping_expressions list
+empty_summarize_base_col_in_grouping = Summarize(
+    dict(
+        base_grouping_column=gen_alias.academics.id,
+        grouping_expressions=[
+            _gen_grouping_expr(gen_alias.academics.id),
+        ],
+        aggregation_expressions=[]
+    )
+)
+
+
 # the summarization transform that's the result of fully specifying `empty_summarize`.
 full_summarize = Summarize(
     dict(
@@ -199,6 +211,12 @@ def initial_columns(academics_ids):
 @pytest.mark.parametrize(
     'input_summarize_transform, expected_summarize_transform, transforms_before, transforms_after',
     [
+        [
+            empty_summarize_base_col_in_grouping,
+            full_summarize,
+            [],
+            [],
+        ],
         [
             empty_summarize,
             full_summarize,
diff --git a/mathesar/tests/api/query/test_query_run_partial_transforms.py b/mathesar/tests/api/query/test_query_run_partial_transforms.py
--- a/mathesar/tests/api/query/test_query_run_partial_transforms.py
+++ b/mathesar/tests/api/query/test_query_run_partial_transforms.py
@@ -1,16 +1,79 @@
+import pytest
+
 from db.transforms.base import Summarize, Limit
 from db.transforms.operations.serialize import serialize_transformation
 
 
+fully_speced_summarize = \
+    Summarize(
+        dict(
+            aggregation_expressions=[
+                dict(
+                    function='aggregate_to_array',
+                    input_alias='col2',
+                    output_alias='col2_agged'
+                )
+            ],
+            base_grouping_column='col1',
+            grouping_expressions=[
+                dict(
+                    input_alias='col1',
+                    output_alias='col1_grouped',
+                )
+            ]
+        )
+    )
+
+
+@pytest.mark.parametrize(
+    'input_summarize, expected_summarize', [
+        [
+            Summarize(
+                dict(
+                    base_grouping_column='col1',
+                    aggregation_expressions=[
+                        dict(
+                            function='aggregate_to_array',
+                            input_alias='col2',
+                            output_alias='col2_agged'
+                        )
+                    ],
+                ),
+            ),
+            fully_speced_summarize,
+        ],
+        [
+            Summarize(
+                dict(
+                    base_grouping_column='col1',
+                    grouping_expressions=[
+                        dict(
+                            input_alias='col1',
+                            output_alias='col1_grouped',
+                        )
+                    ]
+                )
+            ),
+            fully_speced_summarize,
+        ],
+        [
+            Summarize(
+                dict(
+                    base_grouping_column='col1',
+                )
+            ),
+            fully_speced_summarize,
+        ],
+    ]
+)
 def test_partial_summarize_transform(
-    create_patents_table, client,
+    create_patents_table, client, input_summarize, expected_summarize,
 ):
     base_table = create_patents_table(table_name='patent_query_run_minimal_table')
-    col1_alias = 'col1'
     initial_columns = [
         {
             'id': base_table.get_column_by_name('Center').id,
-            'alias': col1_alias,
+            'alias': 'col1',
             'display_name': 'Column 1',
         },
         {
@@ -20,34 +83,9 @@ def test_partial_summarize_transform(
         },
     ]
     input_summarize_transform_json = \
-        serialize_transformation(
-            Summarize(
-                dict(
-                    base_grouping_column=col1_alias,
-                )
-            )
-        )
+        serialize_transformation(input_summarize)
     expected_summarize_transform_json = \
-        serialize_transformation(
-            Summarize(
-                {
-                    'aggregation_expressions': [
-                        {
-                            'function': 'aggregate_to_array',
-                            'input_alias': 'col2',
-                            'output_alias': 'col2_agged'
-                        }
-                    ],
-                    'base_grouping_column': 'col1',
-                    'grouping_expressions': [
-                        {
-                            'input_alias': 'col1',
-                            'output_alias': 'col1_grouped'
-                        }
-                    ]
-                }
-            )
-        )
+        serialize_transformation(expected_summarize)
     limit_transform_json = serialize_transformation(Limit(5))
     input_transformations = [
         limit_transform_json,