Hi Manish,

I have to be honest and say that I haven't taken the time to fully understand the "guts" of this patch--namely, the part where you rewrite the group-by tree in GroupByNode.addAggregateColumns(). But I did do some review of the overall patch and I have the following comments. If you can address these I think the resultant patch will be a lot easier to read (and will also be smaller :) so that I or anyone else reviewing can focus more on the "guts" of what this patch is really doing...

------------------------------------------------------------------

0) Patch doesn't apply cleanly with latest codeline--there appear to be conflicts with the JUnit test changes and also one conflict in sqlgrammar.jj. I manually resolved these conflicts in my local codeline so that I could run some tests/view the code, but I think it'd be appropriate to post an updated patch (sync'd with the latest codeline) to ease the review process for others.

1) TernarnyOpNode.isEquivalent(): I think we need to check "receiver" equivalence as well, in addition to left and right operands. For example, I did the following and I received an error as expected:

ij> create table s1 (vc varchar(20), vc2 varchar(10));
0 rows inserted/updated/deleted

ij> insert into s1 values ('allo', 'bye'), ('inigo', 'montoya'), 
('six','fingers');
3 rows inserted/updated/deleted

ij> select substr(vc, 3, 4) from s1 group by vc2;
ERROR 42Y30: The SELECT list of a grouped query contains at least one invalid expression. If a SELECT list has a GROUP BY, the list may only contain valid grouping expressions and valid aggregate expressions.

I then I did the following, expecting to see the same error--but this actually works:

ij> select substr(vc, 3, 4) from s1 group by substr(vc2, 3, 4);
1
----
igo
lo
x

3 rows selected

I think this is because TernaryOperatorNode.isEquivalent() doesn't compare "receiver" and thus thinks that vc and vc2 are the same, hence allowing the group by. Is this supposed to work or is it supposed to throw an error? My understanding is that it should fail, but I could of course be wrong...

2) Aren't we losing information with this change?

In VerifyAggregateExpressionsVisitor.java:

-                               throw
StandardException.newException(SQLState.LANG_INVALID_COL_REF_GROUPED_SELECT_LIST, cr.getSQLColumnName());
+                               throw
StandardException.newException(SQLState.LANG_INVALID_GROUPED_SELECT_LIST);

Before your changes we see the column reference name:

ij> select substr(vc, 3, 4) from s1 group by vc2;
ERROR 42Y36: Column reference 'VC' is invalid. For a SELECT list with a GROUP BY, the list may only contain grouping columns and valid aggregate expressions.

But after the patch is applied, we lose the name of the invalid column 
reference:

ij> select substr(vc, 3, 4) from s1 group by vc2;
ERROR 42Y30: The SELECT list of a grouped query contains at least one invalid expression. If a SELECT list has a GROUP BY, the list may only contain valid grouping expressions and valid aggregate expressions.

Generally it seems like a good idea to provide the user with as much information as possible for error conditions like this. Is it possible to preserve the result column name in cases like this (i.e. cases where we do actually know what the invalid column reference is)?

3) In UnaryOperatorNode.java the check for equivalence of the operand has three parts to it:

+       return (operator.equals(other.operator)
+                       || (operand == other.operand)
+                       || ((operand != null) && operand.isEquivalent(other)));

But for other classes--such as BinaryOperatorNode.java--the check is only a single call to "isEquivalent" for each operand:

+               return methodName.equals(other.methodName)
+                      && leftOperand.isEquivalent(other.leftOperand)
+                      && rightOperand.isEquivalent(other.rightOperand);

Can you explain why the operand for UnaryOperatorNode has three checks (.equals(), "==", and .isEquivalent()) while the operands for BinaryOperatorNode only have a single check (.isEquivalent())? Some comments to describe the difference might be useful in these classes.

3) I think there are several places in this patch where you have comments explaining "what" by not explaining "why"--and I think the latter would be helpful. For example:

In VerifyAggregateExpressionsVisitor.java:

Why do we disallow expressions involving native java computation in the following code?

+    } else if (node instanceof JavaToSQLValueNode)
+    {
+      // disallow any expression which involves native java computation.
+      throw StandardException.newException( (groupByList == null) ?
+          SQLState.LANG_INVALID_NON_GROUPED_SELECT_LIST :
+          SQLState.LANG_INVALID_GROUPED_SELECT_LIST);

Why do we not visit children under subqueries in the following code?

        /**
-        * Don't visit children under an aggregate
+        * Don't visit children under an aggregate, subquery or any node which
+        * is equivalent to any of the group by expressions.

In SelectNode.java:

Why do we need to add a call to preprocess in the following code?

+    if (groupByList != null)
+    {
+      groupByList.preprocess(numTables, fromList, whereSubquerys, 
wherePredicates);
+               }
+               

For this one you can probably just use the comments that you already included in your patch description, namely: "This is needed because expressions can get rewritten in the select list but not in the grouping list causing problems when the result set is generated." It'd be great if this explanation was included in the actual code.

In GroupByNode.java:

Why do we always say that the source is not in sorted order if we have an expression instead of a ColumnReference?

-        crs[index] = gc.getColumnReference();
+        if (gc.getColumnExpression() instanceof ColumnReference)
+        {
+          crs[index] = (ColumnReference)gc.getColumnExpression();
+        }
+        else
+        {
+          isInSortedOrder = false;
+          break;
+        }

Also, in the comment you posted to DERBY-883 with the patch you explained how the rewrite is different now:

o the rewrite logic is a bit different now. Earlier, we would change
each unaggregate ColumnReference in the select list and point it to the
GroupByColumn. Now we replace each group by expression that we find
in the projection list with a virtual column node that effectively points
to a result column in the result set doing the group by. In addition
the original routine which did the rewrite is now split up into two separate
and smaller routines: addUnAggColumns and addAggregateColumns.

It would be great if this explanation could be added to the relevant parts in the code and/or if the comments for that section could be updated to match the new behavior. This will make it easier for people to understand what is going on when they read the code several years from now.

In GroupByList.java:

Why don't we need to add a ResultColumn/ColumnReference pair to SelectNode's RCL when the grouping column is an expression (as opposed to a ColumnReference), as in the following code?

@@ -185,7 +174,8 @@
    /* If no match found in the SELECT list, then add a matching
     * ResultColumn/ColumnReference pair to the SelectNode's RCL.
     */
-       if (! matchFound)
+       if (! matchFound &&
+           groupingCol.getColumnExpression() instanceof ColumnReference)
        {
                ResultColumn newRC;

These are just some examples; I think there are others too. If you can look again at the patch and try to add comments explaining *why* we do or do not need to do things like this, that would be a huge help to those reading the code now and in the future. These don't have to be extensive--even just a line or two can be very helpful.

4) In GroupByColumn and GroupByList you have deleted a bunch of methods that apparently are unused. While this is a useful thing to do, I don't know if we want to do that as part of the DERBY-883 patch--it makes the diff bigger and doesn't add to the patch functionally. Instead you could file a new Jira entry and attach a separate patch that removes the unnecessary methods to that Jira issue, to be reviewed/committed separately. Or you could create a follow-up patch for this issue that just separates out the changes for removing these methods.

5) Extraneous diff in QueryTreeNode.java? Are any of these changes actually required or are they just there for debugging? If they are an intentional part of the patch then a) comments explaining these changes would be helpful, and b) it might be good to actually delete the commented out lines altogether, instead of leaving them in to clutter the code.

6) New methods could use javadoc explaining in more detail what they do:

GroupByNode.addUnAggColumns()
GroupByNode.addAggregateColumns()

7) There are a couple of places where you have commented lines out instead of deleting them. If there's a reason you left these in, then comments explaining that would be great; otherwise I think it's cleaner to just delete the lines altogether. For example:

In GroupByColumn.java:

@@ -48,7 +48,8 @@
         */
        public void init(Object colRef)
        {
-               this.colRef = (ColumnReference) colRef;
+               //this.colRef = (ColumnReference) colRef;
+               this.columnExpression = (ValueNode)colRef;
        }

        /**

8) I think the diff for master/subquery.out has some stuff from a failed merge in it:

Index: java/testing/org/apache/derbyTesting/functionTests/master/subquery.out
===================================================================
--- java/testing/org/apache/derbyTesting/functionTests/master/subquery.out
(revision 422486)
+++ java/testing/org/apache/derbyTesting/functionTests/master/subquery.out
(working copy)
@@ -1016,8 +1016,14 @@
                                        Number of rows visited=9
                                        Scan type=heap
                                        start position:
+<<<<<<< .mine
 null                                   stop position:
+null
+                                       scan qualifiers:
+=======
+null                                   stop position:
 null                                   scan qualifiers:
+>>>>>>> .r422486
 None
                                        next qualifiers:
 None

**** Nit-picking: Changes that you don't *have* to do but that might help in cleaning up the patch.

A. Unnecessary white-space changes?

In SelectNode.java:

-                       VerifyAggregateExpressionsVisitor visitor =
-                               new 
VerifyAggregateExpressionsVisitor(groupByList);
+                       VerifyAggregateExpressionsVisitor visitor =
+                               new 
VerifyAggregateExpressionsVisitor(groupByList);
                        resultColumns.accept(visitor);
-               }
-
+        }
+
+
+               

In GroupByNode.java:

-                                                                               
C_NodeTypes.RESULT_COLUMN_LIST,
-                                                                               
getContextManager()),
-                                       ((FromTable) 
childResult).getTableNumber());
+                                                       
C_NodeTypes.RESULT_COLUMN_LIST,
+                                                       getContextManager()),
+                               ((FromTable) childResult).getTableNumber());

...

-               ** For each aggregate
-               */
+                ** For each aggregate
+                */

...

-
+                       
                        /*
-                       ** AGG RESULT: Set the aggregate result to null in the
-                       ** bottom project restrict.
-                       */
+                        ** AGG RESULT: Set the aggregate result to null in the
+                        ** bottom project restrict.
+                        */

... etc.

There are quite a few white-space changes in this patch that make it hard to figure out what has actually changed.

B. Lines longer than 80 chars:

In SelectNode.java:

@@ -965,6 +956,11 @@
                                                                   
wherePredicates);
                }

+               if (groupByList != null)
+               {
+                       groupByList.preprocess(numTables, fromList, 
whereSubquerys, wherePredicates);
+               }
+               

In VerifyAggregateExpressionsVisitor.java:

+               return ((node instanceof AggregateNode) ||
+                               (node instanceof SubqueryNode) ||
+                               (node instanceof ValueNode &&
+                                               groupByList != null
+                                               && 
groupByList.findGroupingColumn((ValueNode)node) != null));

I know, this whole 80 character thing is a bit picky--but that seems to be the rule adopted by most developers in the Derby code, so I'm just bringing it up...

------------------------------------------------------------------

I know that's a lot of feedback and very little of it has to do with the actual correctness of the code--so my apologies if this is a bit annoying. But I do think that some additional comments and an effort to reduce the extra diffs will make this a cleaner patch and will help developers in the long and short-term who might find themselves reading this code.

Functionally, this is a great patch--an excellent feature enhancement for Derby. Thank you so much for taking the time to figure out all of the details--and for writing such extensive tests, as well. I hope this feedback doesn't discourage you from continuing to work on this or other Derby changes: I'm just posting feedback based on my own opinions. If you disagree with anything I've written here, please feel free to speak up!

Thanks again for all of the work on this,
Army

Reply via email to