TomohitoNakayama wrote:
If possible , I want to work for DERBY-318 before DERBY-308 ....
Tomohito,
I hope you don't mind, but when I filed DERBY-318, I wasn't sure whether the
problem was with the DERBY-167 patch or with the server. So, in order to keep
you from spending your time trying to track down a problem that wasn't your
fault, I started looking at this issue from the server side of things--and I
think I've found the problem.
To make a long story short, the NPE in the server happens because of the
"toString()" method defined in DefaultNodeImpl.
Why? Well, I try to explain it below. Excuse the formality of it all, but I
had to do it this way in order to keep it clear in my own head ;)
There are several pieces that make up the puzzle. Let me start by saying what
they are, and then I'll try to tie them together.
---------------
-- The pieces.
---------------
1) For a column that is GENERATED BY DEFAULT, the 'defaultText' variable in the
corresponding DefaultNodeImpl is null. Since DefaultNodeImpl.toString() just
returns defaultText, the result of toString() on a DefaultNodeImpl that is
GENERATED BY DEFAULT is null.
2) The underlying DataTypeDescriptor for all default columns is
"org.apache.derby.iapi.types.UserType". In that class, the "isNull()" method is
simply written to return "(value == null)", while the "getString()" method is as
follows:
public String getString()
{
if (! isNull())
{
return value.toString();
}
else
{
return null;
}
}
3) For a column that is GENERATED BY DEFAULT, the 'value' variable in UserType
is a non-null instance of DefaultNodeImpl.
---------------
-- The problem.
---------------
In Network Server, we take the result set from the query "SELECT COLUMNDEFAULT
FROM SYS.SYSCOLUMNS" and we do two things. First, we call
rs.getString(<columnNum>), and then we call rs.wasNull().
[ ** rs.getString() ** ]
* The call to rs.getString(...) on the "COLUMNDEFAULT" column ultimately makes a
call to UserType.getString() (Piece #2).
* That method in turn calls UserType.isNull().
* UserType.isNull() looks at 'value', which is a non-null instance of
DefaultNodeImpl(Piece #3 above), and since it's NOT null, it returns "false"
(Piece #2).
* UserType.getString() looks at the result from UserType.isNull(), which is
"false". It then calls "value.toString()" (Piece #2).
* Since 'value' is an instance of DefaultNodeImpl (Piece #3), this ends up being
a call to DefaultNodeImpl.toString().
* DefaultNodeImpl.toString() just returns 'defaultText', which is null for
columns that are GENERATED BY DEFAULT (Piece #1).
* UserType.getString() returns the null value that it got from
DefaultNodeImpl.toString().
* End result: the server call to "rs.getString()" returns null.
[ ** rs.wasNull() ** ]
* The server call to rs.wasNull() on the "COLUMNDEFAULT" column ultimately makes
a call to UserType.isNull().
* UserType.isNull() looks at 'value', which is a non-null instance of
DefaultNodeImpl(Piece #3 above), and since it's NOT null, it returns "false"
(Piece #2).
* End result: the server call to "rs.wasNull()" returns FALSE.
THEREFORE, the server calls rs.getString() and gets NULL back, but then when it
calls rs.wasNull(), it's told that the value it got back was NOT null. So it
tries to use the value (which IS null) and ends up with a Null Pointer Exception.
---------------
Possible fix:
---------------
Just to verify that this is in the fact the problem, I changed the "toString()"
method in DefaultNodeImpl to the following:
public String toString()
{
if (isDefaultValueAutoinc())
return "GENERATED BY DEFAULT";
return defaultText;
}
What this means is that the server call to rs.getString() will now return
"GENERATED BY DEFAULT" instead of null. This in turn means that the result of
rs.wasNull() will be correct, and the result of the query "SELECT COLUMNDEFAULT
FROM SYS.SYSCOLUMNS" will be the string "GENERATED BY DEFAULT".
I then ran the repro described in DERBY-318, and it ran correctly, listing
"GENERATED BY DEFAULT" as the default value for the column in question.
That said, I think returning any non-null value in DefaultNodeImpl.toString()
will solve the problem The question then becomes what value should we return?
Is "GENERATED BY DEFAULT" an acceptable result? To me, that seems like it
should be okay--it says that the default value isn't known, but that it's
generated at insertion time, if needed. But that's just me--it's quite possible
other people out there have a different opinion...
---------------
Conclusion:
---------------
1) This is NOT a Network Server problem (which is what I wanted to find out, and
that's why I started looking at it).
2) This problem can be fixed with two lines :) But I leave it up to the others
on this list to decide if this is the _best_ fix, and to decide on what the
non-null string value should be...
I hope that helps, and I hope you don't feel cheated because I took the time to
look into this. As I said, I wanted to make sure you weren't chasing a problem
that had its origins in Network Server...
Army