Re: tightening up on use of oid 0

Поиск
Список
Период
Сортировка
Искать

Re: tightening up on use of oid 0

От:
"Iain" <iain@mst.co.jp>
Дата:
Hi,

Interesting, thanks for your feedback.

regards
Iain
----- Original Message ----- 
From: "Oliver Jowett" 
To: "Iain" 
Cc: 
Sent: Thursday, October 14, 2004 11:40 AM
Subject: Re: [JDBC] tightening up on use of oid 0


> Iain wrote:
>> Hi Oliver,
>>
>> Just out of interest, is the case you marked,
>>
>>>   setObject(i, (Integer)null);     // (*)
>>
>> equivalent to
>>
>>    Integer someInteger = null;
>>    setObject(i, someInteger);
>>
>> ?
>
> Yes.
>
>> I would ask the question then, is there any situation where there is no 
>> alternative to the insufficiantly typed calls you listed?
>
> I think there is always an alternative.
>
> For standard types you can use setNull or setObject with a type code:
>
>   setNull(i, Types.INTEGER);
>   setObject(i, null, Types.INTEGER);
>
> For extension types (classed as Types.OTHER) you can use the singleton 
> NULL objects I introduced in my patch:
>
>   setObject(i, PGline.NULL);
>   setObject(i, PGline.NULL, Types.OTHER);
>
> -O
>
> ---------------------------(end of broadcast)---------------------------
> TIP 9: the planner will ignore your desire to choose an index scan if your
>      joining column's datatypes do not match 

Re: tightening up on use of oid 0

От:
"Iain" <iain@mst.co.jp>
Дата:
Hi,

For my part, I've never used any of those calls.

FWIW, I always use reference objects such as "Integer" as opposed to "int" 
so I've never done it that way. All calls to setObject or set 
would always use a variable which is typed (and may be null) my assumption 
is that I avoid all such ambiguities that way.

regards
iain

> Kris Jurka wrote:

> General question for the list: how much code is out there that performs 
> one of these (equivalent) calls?
>
>   PreparedStatement.setObject(i, null);
>   PreparedStatement.setObject(i, null, Types.OTHER);
>   PreparedStatement.setNull(i, Types.OTHER);
>

Re: tightening up on use of oid 0

От:
"Iain" <iain@mst.co.jp>
Дата:
Hi Oliver,

Just out of interest, is the case you marked,

>   setObject(i, (Integer)null);     // (*)

equivalent to

    Integer someInteger = null;
    setObject(i, someInteger);

?

From what I remember of my code I'd be surprised if I was doing either as 
this case would use setInt instead of setObject. I don't think I use 
setObject anywhere.

I would ask the question then, is there any situation where there is no 
alternative to the insufficiantly typed calls you listed? From my limited 
view of the situation, my feeling is that there isn't, so I would say that 
such calls should produce errors rather than some kind of default behavour.

Cheers
Iain

Re: tightening up on use of oid 0

От:
Tom Lane <tgl@sss.pgh.pa.us>
Дата:
Oliver Jowett  writes:
> - "? IS NULL" breaks if the parameter is an untyped NULL (this was the 
> original issue -- in an off-list email --that made me look at this area)

Hmm.  The system doesn't complain if you do "select 'z' IS NULL".  It
knows that it doesn't have a hard idea about the datatype of 'z', but it
also knows that it doesn't matter much.  The reason that you are seeing
a failure is that exec_parse_message() explicitly fails if any parameter
datatypes remain UNKNOWN after parsing.  I made it do that because I
expected that client code would be unhappy to get UNKNOWN back as a
"resolved" parameter datatype.  Would you rather get that result or
a failure?

> - Functions taking 'anyarray' or 'anyelement' don't like untyped NULLs, 
> even if they are STRICT (ERROR:  could not determine anyarray/anyelement 
> type because input has type "unknown")

This you're just stuck with.  There has to be some way to determine the
actual datatype imputed to the function result, and if you supply an
untyped parameter then there isn't.  It hasn't got anything to do with
whether the parameter is NULL or not.

			regards, tom lane

Re: PGobject overhaul (was Re: tightening up on use of oid

От:
Markus Schaber <schabios@logi-track.com>
Дата:
Hi, Oliver,

On Thu, 28 Oct 2004 15:18:13 +1300
Oliver Jowett  wrote:

> - Implementations of PGobject should provide a ctor taking a single 
> String; this is called by the driver to construct non-null objects.

Is it possible to use a static factory function instead?

This would make it possible to produce different subclasses depending on
the String, which would be useful e. G. for PostGIS, als all the
geometry classes share the same postgres type "geometry".

> When we come to do binary-format types, I'd expect we would have a 
> subinterface (PGbinaryobject?) that adds whatever methods are needed for 
> binary parameter formatting. Objects that implement PGbinaryobject 
> become candidates for binary transfer.

What do you think about the factory / handler object approach that AFAIR
was discussed here some days ago?

So the driver gets registered one PGfactory for every postgres type, and
this factory then has methods to transform objects to text and binary
representation and vice-versa.

This would allow us to read and write instances of third-party defined
classes that don't implement any postgres specific interface.

We still could have a default factory implementation that gets used
whenever any legacy application uses PGObject subclasses.

Markus

-- 
markus schaber | dipl. informatiker
logi-track ag | rennweg 14-16 | ch 8001 zürich
phone +41-43-888 62 52 | fax +41-43-888 62 53
mailto:schabios@logi-track.com | www.logi-track.com

tightening up on use of oid 0

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
I am currently cleaning up a few places where OID 0 could get used as a 
parameter type (causing the backend to try to infer a type). For 
example, this occurs when setObject() is passed an object that it can't 
classify (not any of Integer, Boolean, PGobject, etc), or when calling 
setObject() with a non-PGobject and Types.OTHER.

I think this is a bad idea: we do not have any mechanism in place to 
check that the type actually matches the expected type. It also seems 
quite fragile to rely on this behaviour when everything else in JDBC is 
fairly strongly typed.

Another place I am less sure about is setNull(x, Types.OTHER); this 
passes the NULL with type OID 0. Without this, there is no other way to 
set a nonstandard-typed parameter to null. I think this is still 
problematic. Consider the case where you have two functions:

   foo(line)
   foo(box)

Executing "SELECT foo(?)" via PreparedStatement will work fine if you 
pass a non-null PGline or PGbox argument to setObject, but if you try to 
setNull() then you will get ambiguity between the two functions at 
execution time.

I can't see a way to fix this without a postgresql extension of some 
sort. Options I can think of are:

1) PGStatement.setNull(int parameterIndex,String databaseType): sets 
parameter to null with the given type.

2) PGStatement.setTypeHint(int parameterIndex,String databaseType): sets
parameter's type, only, to the given type; this is then used by later 
calls to setNull().

3) PGConnection.addDataType(String type, Class klass, int sqlType): 
register a new java.sql.Types value along with the type handler.

(3) seems like the least intrusive approach, but it requires that 
extension types somehow decide on a unique Types value. I'm not sure if 
this is practical.

-O

Re: tightening up on use of oid 0

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Kris Jurka wrote:
> 
> On Fri, 8 Oct 2004, Oliver Jowett wrote:
> 
>>Executing "SELECT foo(?)" via PreparedStatement will work fine if you 
>>pass a non-null PGline or PGbox argument to setObject, but if you try to 
>>setNull() then you will get ambiguity between the two functions at 
>>execution time.
>>
>>I can't see a way to fix this without a postgresql extension of some 
>>sort. Options I can think of are:
> 
> 
> Can't the existing PGobject interface handle this.  You can create a 
> PGobject with the correct datatype and a null value and that should work.

OK, so getValue() and toString() return null?

This would still mean that setNull() would not work for Types.OTHER, though.

-O

Re: tightening up on use of oid 0

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Oliver Jowett wrote:
> I am currently cleaning up a few places where OID 0 could get used as a 
> parameter type (causing the backend to try to infer a type).

Here is a patch to do this, including the PGobject changes to handle SQL 
NULLs.

The PGobject / geometric type changes are a bit ugly in places, mostly 
because those types are mutable (unnecessarily, IMO).

-O
Index: org/postgresql/errors.properties
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/errors.properties,v
retrieving revision 1.38
diff -u -c -r1.38 errors.properties
*** org/postgresql/errors.properties	9 Oct 2004 06:02:58 -0000	1.38
--- org/postgresql/errors.properties	9 Oct 2004 10:13:41 -0000
***************
*** 73,78 ****
--- 73,80 ----
  postgresql.prep.type:Unknown Types value.
  postgresql.prep.typenotfound:Unknown type {0}.
  postgresql.prep.zeroinstring:Zero bytes may not occur in string parameters.
+ postgresql.prep.untypedsetnull:setNull(i,Types.OTHER) and setObject(i,null,Types.OTHER) are not supported. Instead, use setObject(i,object,Types.OTHER) where 'object' is an appropriate PGobject subclass instance representing a NULL.
+ postgresql.prep.untypedsetobject:setObject(i,null) is not supported. Instead, use setNull(i,type) or setObject(i,null,type).
  postgresql.res.badbigdec:Bad BigDecimal {0}
  postgresql.res.badbyte:Bad Byte {0}
  postgresql.res.baddate:Bad Date Format {0}
Index: org/postgresql/geometric/PGbox.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/geometric/PGbox.java,v
retrieving revision 1.8
diff -u -c -r1.8 PGbox.java
*** org/postgresql/geometric/PGbox.java	29 Jun 2004 06:43:26 -0000	1.8
--- org/postgresql/geometric/PGbox.java	9 Oct 2004 10:13:41 -0000
***************
*** 23,31 ****
  public class PGbox extends PGobject implements Serializable, Cloneable
  {
  	/**
! 	 * These are the two points.
  	 */
! 	public PGpoint point[] = new PGpoint[2];
  
  	/**
  	 * @param x1 first x coordinate
--- 23,31 ----
  public class PGbox extends PGobject implements Serializable, Cloneable
  {
  	/**
! 	 * These are the two points, or null for a SQL NULL.
  	 */
! 	public PGpoint point[];
  
  	/**
  	 * @param x1 first x coordinate
***************
*** 35,43 ****
  	 */
  	public PGbox(double x1, double y1, double x2, double y2)
  	{
! 		this();
! 		this.point[0] = new PGpoint(x1, y1);
! 		this.point[1] = new PGpoint(x2, y2);
  	}
  
  	/**
--- 35,41 ----
  	 */
  	public PGbox(double x1, double y1, double x2, double y2)
  	{
! 		this(new PGpoint(x1, y1), new PGpoint(x2, y2));
  	}
  
  	/**
***************
*** 47,54 ****
  	public PGbox(PGpoint p1, PGpoint p2)
  	{
  		this();
! 		this.point[0] = p1;
! 		this.point[1] = p2;
  	}
  
  	/**
--- 45,51 ----
  	public PGbox(PGpoint p1, PGpoint p2)
  	{
  		this();
! 		this.point = new PGpoint[] { p1, p2 };
  	}
  
  	/**
***************
*** 82,89 ****
  		if (t.getSize() != 2)
  			throw new PSQLException("postgresql.geo.box", PSQLState.DATA_TYPE_MISMATCH, value);
  
! 		point[0] = new PGpoint(t.getToken(0));
! 		point[1] = new PGpoint(t.getToken(1));
  	}
  
  	/**
--- 79,88 ----
  		if (t.getSize() != 2)
  			throw new PSQLException("postgresql.geo.box", PSQLState.DATA_TYPE_MISMATCH, value);
  
! 		point = new PGpoint[] {
! 			new PGpoint(t.getToken(0)),
! 			new PGpoint(t.getToken(1))
! 		};
  	}
  
  	/**
***************
*** 96,101 ****
--- 95,106 ----
  		{
  			PGbox p = (PGbox)obj;
  
+ 			if (point == p.point)
+ 				return true;
+ 
+ 			if (point == null || p.point == null)
+ 				return false;
+ 
  			// Same points.
  			if (p.point[0].equals(point[0]) && p.point[1].equals(point[1]))
  				return true;
***************
*** 126,136 ****
--- 131,148 ----
  		// its X and Y components; we end up with an exclusive-OR of the two X and
  		// two Y components, which is equal whenever equals() would return true
  		// since xor is commutative.
+ 
+ 		if (point == null)
+ 			return 0;
+ 
  		return point[0].hashCode() ^ point[1].hashCode();
  	}
  
  	public Object clone()
  	{
+ 		if (point == null)
+ 			return new PGbox();
+ 
  		return new PGbox((PGpoint)point[0].clone(), (PGpoint)point[1].clone());
  	}
  
***************
*** 139,144 ****
--- 151,161 ----
  	 */
  	public String getValue()
  	{
+ 		if (point == null)
+ 			return null;
+ 
  		return point[0].toString() + "," + point[1].toString();
  	}
+ 
+ 	public final static PGbox NULL = new PGbox();
  }
Index: org/postgresql/geometric/PGcircle.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/geometric/PGcircle.java,v
retrieving revision 1.10
diff -u -c -r1.10 PGcircle.java
*** org/postgresql/geometric/PGcircle.java	29 Jun 2004 06:43:26 -0000	1.10
--- org/postgresql/geometric/PGcircle.java	9 Oct 2004 10:13:41 -0000
***************
*** 24,30 ****
  public class PGcircle extends PGobject implements Serializable, Cloneable
  {
  	/**
! 	 * This is the center point
  	 */
  	public PGpoint center;
  
--- 24,30 ----
  public class PGcircle extends PGobject implements Serializable, Cloneable
  {
  	/**
! 	 * This is the center point, or null for a NULL value
  	 */
  	public PGpoint center;
  
***************
*** 102,107 ****
--- 102,114 ----
  		if (obj instanceof PGcircle)
  		{
  			PGcircle p = (PGcircle)obj;
+ 
+ 			if (center == null && p.center == null)
+ 				return true;
+ 
+ 			if (center == null || p.center == null)
+ 				return false;
+ 
  			return p.center.equals(center) && p.radius == radius;
  		}
  		return false;
***************
*** 109,120 ****
--- 116,133 ----
  
  	public int hashCode()
  	{
+ 		if (center == null)
+ 			return 0;
+ 
  		long v = Double.doubleToLongBits(radius);
  		return (int) (center.hashCode() ^ v ^ (v>>>32));
  	}
  
  	public Object clone()
  	{
+ 		if (center == null)
+ 			return new PGcircle();
+ 
  		return new PGcircle((PGpoint)center.clone(), radius);
  	}
  
***************
*** 123,128 ****
--- 136,146 ----
  	 */
  	public String getValue()
  	{
+ 		if (center == null)
+ 			return null;
+ 
  		return "<" + center + "," + radius + ">";
  	}
+ 
+ 	public final static PGcircle NULL = new PGcircle();
  }
Index: org/postgresql/geometric/PGline.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/geometric/PGline.java,v
retrieving revision 1.8
diff -u -c -r1.8 PGline.java
*** org/postgresql/geometric/PGline.java	29 Jun 2004 06:43:26 -0000	1.8
--- org/postgresql/geometric/PGline.java	9 Oct 2004 10:13:41 -0000
***************
*** 26,34 ****
  public class PGline extends PGobject implements Serializable, Cloneable
  {
  	/**
! 	 * These are the two points.
  	 */
! 	public PGpoint point[] = new PGpoint[2];
  
  	/**
  	 * @param x1 coordinate for first point
--- 26,34 ----
  public class PGline extends PGobject implements Serializable, Cloneable
  {
  	/**
! 	 * These are the two points, or null for a SQL NULL.
  	 */
! 	public PGpoint point[];
  
  	/**
  	 * @param x1 coordinate for first point
***************
*** 48,55 ****
  	public PGline(PGpoint p1, PGpoint p2)
  	{
  		this();
! 		this.point[0] = p1;
! 		this.point[1] = p2;
  	}
  
  	/**
--- 48,54 ----
  	public PGline(PGpoint p1, PGpoint p2)
  	{
  		this();
! 		this.point = new PGpoint[] { p1, p2 };
  	}
  
  	/**
***************
*** 80,87 ****
  		if (t.getSize() != 2)
  			throw new PSQLException("postgresql.geo.line", PSQLState.DATA_TYPE_MISMATCH, s);
  
! 		point[0] = new PGpoint(t.getToken(0));
! 		point[1] = new PGpoint(t.getToken(1));
  	}
  
  	/**
--- 79,88 ----
  		if (t.getSize() != 2)
  			throw new PSQLException("postgresql.geo.line", PSQLState.DATA_TYPE_MISMATCH, s);
  
! 		point = new PGpoint[] { 
! 			new PGpoint(t.getToken(0)),
! 			new PGpoint(t.getToken(1))
! 		};
  	}
  
  	/**
***************
*** 93,98 ****
--- 94,106 ----
  		if (obj instanceof PGline)
  		{
  			PGline p = (PGline)obj;
+ 
+ 			if (point == p.point)
+ 				return true;
+ 
+ 			if (point == null || p.point == null)
+ 				return false;
+ 
  			return (p.point[0].equals(point[0]) && p.point[1].equals(point[1])) ||
  				   (p.point[0].equals(point[1]) && p.point[1].equals(point[0]));
  		}
***************
*** 100,110 ****
--- 108,124 ----
  	}
  
  	public int hashCode() {
+ 		if (point == null)
+ 			return 0;
+ 
  		return point[0].hashCode() ^ point[1].hashCode();
  	}
  
  	public Object clone()
  	{
+ 		if (point == null)
+ 			return new PGline();
+ 
  		return new PGline((PGpoint)point[0].clone(), (PGpoint)point[1].clone());
  	}
  
***************
*** 113,118 ****
--- 127,137 ----
  	 */
  	public String getValue()
  	{
+ 		if (point == null)
+ 			return null;
+ 
  		return "[" + point[0] + "," + point[1] + "]";
  	}
+ 
+ 	public static final PGline NULL = new PGline();
  }
Index: org/postgresql/geometric/PGlseg.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/geometric/PGlseg.java,v
retrieving revision 1.8
diff -u -c -r1.8 PGlseg.java
*** org/postgresql/geometric/PGlseg.java	29 Jun 2004 06:43:26 -0000	1.8
--- org/postgresql/geometric/PGlseg.java	9 Oct 2004 10:13:41 -0000
***************
*** 23,31 ****
  public class PGlseg extends PGobject implements Serializable, Cloneable
  {
  	/**
! 	 * These are the two points.
  	 */
! 	public PGpoint point[] = new PGpoint[2];
  
  	/**
  	 * @param x1 coordinate for first point
--- 23,31 ----
  public class PGlseg extends PGobject implements Serializable, Cloneable
  {
  	/**
! 	 * These are the two points, or null for a SQL NULL.
  	 */
! 	public PGpoint point[];
  
  	/**
  	 * @param x1 coordinate for first point
***************
*** 45,52 ****
  	public PGlseg(PGpoint p1, PGpoint p2)
  	{
  		this();
! 		this.point[0] = p1;
! 		this.point[1] = p2;
  	}
  
  	/**
--- 45,51 ----
  	public PGlseg(PGpoint p1, PGpoint p2)
  	{
  		this();
! 		this.point = new PGpoint[] { p1, p2 };
  	}
  
  	/**
***************
*** 77,84 ****
  		if (t.getSize() != 2)
  			throw new PSQLException("postgresql.geo.lseg", PSQLState.DATA_TYPE_MISMATCH);
  
! 		point[0] = new PGpoint(t.getToken(0));
! 		point[1] = new PGpoint(t.getToken(1));
  	}
  
  	/**
--- 76,85 ----
  		if (t.getSize() != 2)
  			throw new PSQLException("postgresql.geo.lseg", PSQLState.DATA_TYPE_MISMATCH);
  
! 		point = new PGpoint[] { 
! 			new PGpoint(t.getToken(0)),
! 			new PGpoint(t.getToken(1))
! 		};
  	}
  
  	/**
***************
*** 90,95 ****
--- 91,103 ----
  		if (obj instanceof PGlseg)
  		{
  			PGlseg p = (PGlseg)obj;
+ 
+ 			if (point == p.point)
+ 				return true;
+ 
+ 			if (point == null || p.point == null)
+ 				return false;
+ 
  			return (p.point[0].equals(point[0]) && p.point[1].equals(point[1])) ||
  				   (p.point[0].equals(point[1]) && p.point[1].equals(point[0]));
  		}
***************
*** 98,108 ****
--- 106,122 ----
  
  	public int hashCode()
  	{
+ 		if (point == null)
+ 			return 0;
+ 
  		return point[0].hashCode() ^ point[1].hashCode();
  	}
  
  	public Object clone()
  	{
+ 		if (point == null)
+ 			return new PGlseg();
+ 
  		return new PGlseg((PGpoint)point[0].clone(), (PGpoint)point[1].clone());
  	}
  
***************
*** 111,116 ****
--- 125,135 ----
  	 */
  	public String getValue()
  	{
+ 		if (point == null)
+ 			return null;
+ 
  		return "[" + point[0] + "," + point[1] + "]";
  	}
+ 
+ 	public final static PGlseg NULL = new PGlseg();
  }
Index: org/postgresql/geometric/PGpath.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/geometric/PGpath.java,v
retrieving revision 1.9
diff -u -c -r1.9 PGpath.java
*** org/postgresql/geometric/PGpath.java	29 Jun 2004 06:43:26 -0000	1.9
--- org/postgresql/geometric/PGpath.java	9 Oct 2004 10:13:41 -0000
***************
*** 28,34 ****
  	public boolean open;
  
  	/**
! 	 * The points defining this path
  	 */
  	public PGpoint points[];
  
--- 28,34 ----
  	public boolean open;
  
  	/**
! 	 * The points defining this path, or null for a SQL NULL.
  	 */
  	public PGpoint points[];
  
***************
*** 98,103 ****
--- 98,109 ----
  		{
  			PGpath p = (PGpath)obj;
  
+ 			if (points == null && p.points == null)
+ 				return true;
+ 
+ 			if (points == null || p.points == null)
+ 				return false;
+ 
  			if (p.points.length != points.length)
  				return false;
  
***************
*** 115,120 ****
--- 121,130 ----
  
  	public int hashCode() {
  		// XXX not very good..
+ 
+ 		if (points == null)
+ 			return 0;
+ 
  		int hash = 0;
  		for (int i = 0; i < points.length && i < 5; ++i) {
  			hash = hash ^ points[i].hashCode();
***************
*** 124,129 ****
--- 134,142 ----
  
  	public Object clone()
  	{
+ 		if (points == null)
+ 			return new PGpath();
+ 
  		PGpoint ary[] = new PGpoint[points.length];
  		for (int i = 0;i < points.length;i++)
  			ary[i] = (PGpoint)points[i].clone();
***************
*** 135,140 ****
--- 148,156 ----
  	 */
  	public String getValue()
  	{
+ 		if (points == null)
+ 			return null;
+ 
  		StringBuffer b = new StringBuffer(open ? "[" : "(");
  
  		for (int p = 0;p < points.length;p++)
***************
*** 167,170 ****
--- 183,188 ----
  	{
  		open = true;
  	}
+ 
+ 	public final static PGpath NULL = new PGpath();
  }
Index: org/postgresql/geometric/PGpoint.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/geometric/PGpoint.java,v
retrieving revision 1.9
diff -u -c -r1.9 PGpoint.java
*** org/postgresql/geometric/PGpoint.java	29 Jun 2004 06:43:26 -0000	1.9
--- org/postgresql/geometric/PGpoint.java	9 Oct 2004 10:13:41 -0000
***************
*** 94,99 ****
--- 94,106 ----
  		if (obj instanceof PGpoint)
  		{
  			PGpoint p = (PGpoint)obj;
+ 
+ 			if (this == NULL && p == NULL)
+ 				return true;
+ 
+ 			if (this == NULL || p == NULL)
+ 				return false;
+ 
  			return x == p.x && y == p.y;
  		}
  		return false;
***************
*** 101,106 ****
--- 108,116 ----
  
  	public int hashCode()
  	{
+ 		if (this == NULL)
+ 			return 0;
+ 
  		long v1 = Double.doubleToLongBits(x);
  		long v2 = Double.doubleToLongBits(y);
  		return (int) (v1 ^ v2 ^ (v1>>>32) ^ (v2>>>32));
***************
*** 108,113 ****
--- 118,126 ----
  
  	public Object clone()
  	{
+ 		if (this == NULL)
+ 			return NULL;
+ 
  		return new PGpoint(x, y);
  	}
  
***************
*** 116,121 ****
--- 129,137 ----
  	 */
  	public String getValue()
  	{
+ 		if (this == NULL)
+ 			return null;
+ 
  		return "(" + x + "," + y + ")";
  	}
  
***************
*** 184,187 ****
--- 200,207 ----
  		setLocation(p.x, p.y);
  	}
  
+ 	// Ugly hack -- this unique object, by object identity, is a SQL NULL.
+ 	// Cloning NULL gives you NULL. You can mutate NULL, but it doesn't
+ 	// affect its NULL-ness.
+ 	public final static PGpoint NULL = new PGpoint();
  }
Index: org/postgresql/geometric/PGpolygon.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/geometric/PGpolygon.java,v
retrieving revision 1.8
diff -u -c -r1.8 PGpolygon.java
*** org/postgresql/geometric/PGpolygon.java	29 Jun 2004 06:43:26 -0000	1.8
--- org/postgresql/geometric/PGpolygon.java	9 Oct 2004 10:13:41 -0000
***************
*** 20,26 ****
  public class PGpolygon extends PGobject implements Serializable, Cloneable
  {
  	/**
! 	 * The points defining the polygon
  	 */
  	public PGpoint points[];
  
--- 20,26 ----
  public class PGpolygon extends PGobject implements Serializable, Cloneable
  {
  	/**
! 	 * The points defining the polygon, or null if this is a SQL NULL.
  	 */
  	public PGpoint points[];
  
***************
*** 76,81 ****
--- 76,87 ----
  		{
  			PGpolygon p = (PGpolygon)obj;
  
+ 			if (points == p.points)
+ 				return true;
+ 
+ 			if (points == null || p.points == null)
+ 				return false;
+ 
  			if (p.points.length != points.length)
  				return false;
  
***************
*** 89,94 ****
--- 95,103 ----
  	}
  
  	public int hashCode() {
+ 		if (points == null)
+ 			return 0;
+ 
  		// XXX not very good..
  		int hash = 0;
  		for (int i = 0; i < points.length && i < 5; ++i) {
***************
*** 99,104 ****
--- 108,116 ----
  
  	public Object clone()
  	{
+ 		if (points == null)
+ 			return new PGpolygon();
+ 
  		PGpoint ary[] = new PGpoint[points.length];
  		for (int i = 0;i < points.length;i++)
  			ary[i] = (PGpoint)points[i].clone();
***************
*** 110,115 ****
--- 122,130 ----
  	 */
  	public String getValue()
  	{
+ 		if (points == null)
+ 			return null;
+ 
  		StringBuffer b = new StringBuffer();
  		b.append("(");
  		for (int p = 0;p < points.length;p++)
***************
*** 121,124 ****
--- 136,141 ----
  		b.append(")");
  		return b.toString();
  	}
+ 
+ 	public final static PGpolygon NULL = new PGpolygon();
  }
Index: org/postgresql/jdbc2/AbstractJdbc2ResultSet.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/jdbc2/AbstractJdbc2ResultSet.java,v
retrieving revision 1.48
diff -u -c -r1.48 AbstractJdbc2ResultSet.java
*** org/postgresql/jdbc2/AbstractJdbc2ResultSet.java	30 Sep 2004 18:16:52 -0000	1.48
--- org/postgresql/jdbc2/AbstractJdbc2ResultSet.java	9 Oct 2004 10:13:42 -0000
***************
*** 31,36 ****
--- 31,37 ----
  import org.postgresql.Driver;
  import org.postgresql.core.*;
  import org.postgresql.largeobject.*;
+ import org.postgresql.util.PGobject;
  import org.postgresql.util.PGbytea;
  import org.postgresql.util.PGtokenizer;
  import org.postgresql.util.PSQLException;
***************
*** 766,775 ****
  			{
  				String key = (String) keys.nextElement();
  				Object o = updateValues.get(key);
! 				if (o instanceof NullObject)
! 					insertStatement.setNull(i,java.sql.Types.NULL);
! 				else
! 					insertStatement.setObject(i, o);
  			}
  
  			insertStatement.executeUpdate();
--- 767,773 ----
  			{
  				String key = (String) keys.nextElement();
  				Object o = updateValues.get(key);
! 				insertStatement.setObject(i, o);
  			}
  
  			insertStatement.executeUpdate();
***************
*** 1084,1090 ****
  	public synchronized void updateNull(int columnIndex)
  	throws SQLException
  	{
! 		updateValue(columnIndex, new NullObject());
  	}
  
  
--- 1082,1089 ----
  	public synchronized void updateNull(int columnIndex)
  	throws SQLException
  	{
! 		String columnTypeName = connection.getPGType(fields[columnIndex - 1].getOID());
! 		updateValue(columnIndex, new NullObject(columnTypeName));
  	}
  
  
***************
*** 1245,1254 ****
  				for (; iterator.hasNext(); i++)
  				{
  					Object o = iterator.next();
! 					if (o instanceof NullObject)
! 						updateStatement.setNull(i+1,java.sql.Types.NULL);
! 					else
! 						updateStatement.setObject( i + 1, o );
  
  				}
  				for ( int j = 0; j < numKeys; j++, i++)
--- 1244,1250 ----
  				for (; iterator.hasNext(); i++)
  				{
  					Object o = iterator.next();
! 					updateStatement.setObject( i + 1, o );
  
  				}
  				for ( int j = 0; j < numKeys; j++, i++)
***************
*** 2613,2620 ****
  		}
  	};
  
! 	class NullObject {
! 	};
  
  }
  
--- 2609,2628 ----
  		}
  	};
  
! 	//
! 	// We need to specify the type of NULL when updating a column to NULL, so
! 	// NullObject is a simple extension of PGobject that always returns null
! 	// values but retains column type info.
! 	//
  
+ 	class NullObject extends PGobject {
+ 		NullObject(String type) {
+ 			setType(type);
+ 		}
+ 
+ 		public String getValue() {
+ 			return null;
+ 		}
+ 	};
  }
  
Index: org/postgresql/jdbc2/AbstractJdbc2Statement.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java,v
retrieving revision 1.32
diff -u -c -r1.32 AbstractJdbc2Statement.java
*** org/postgresql/jdbc2/AbstractJdbc2Statement.java	9 Oct 2004 06:02:58 -0000	1.32
--- org/postgresql/jdbc2/AbstractJdbc2Statement.java	9 Oct 2004 10:13:42 -0000
***************
*** 816,824 ****
  				oid = Oid.BYTEA;
  				break;
  			case Types.OTHER:
  			default:
! 				oid = 0;
! 				break;
  		}
  
  		preparedParameters.setNull(adjustParamIndex(parameterIndex), oid);
--- 816,826 ----
  				oid = Oid.BYTEA;
  				break;
  			case Types.OTHER:
+ 				// We cannot determine an appropriate OID in this case.
+ 				throw new PSQLException("postgresql.prep.untypednull", PSQLState.INVALID_PARAMETER_TYPE);
  			default:
! 				// Bad Types value.
! 				throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE);
  		}
  
  		preparedParameters.setNull(adjustParamIndex(parameterIndex), oid);
***************
*** 1372,1377 ****
--- 1374,1389 ----
  			return new BigDecimal(x.toString()).toString();
  	}
  
+ 	// Helper method for setting parameters to PGobject subclasses.
+ 	private void setPGobject(int parameterIndex, PGobject x) throws SQLException {
+ 		String typename = x.getType();
+ 		int oid = connection.getPGType(typename);
+ 		if (oid == Oid.INVALID)
+ 			throw new PSQLException("postgresql.prep.typenotfound", PSQLState.INVALID_PARAMETER_TYPE, typename);
+ 
+ 		setString(parameterIndex, x.getValue(), oid);
+ 	}
+ 
  	/*
  	 * Set the value of a parameter using an object; use the java.lang
  	 * equivalent objects for integral values.
***************
*** 1478,1487 ****
  				setObject(parameterIndex, x);
  				break;
  			case Types.OTHER:
! 				if (x instanceof PGobject)
! 					setString(parameterIndex, ((PGobject)x).getValue(), connection.getPGType( ((PGobject)x).getType() ));
! 				else
  					throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE);
  				break;
  			default:
  				throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE);
--- 1490,1501 ----
  				setObject(parameterIndex, x);
  				break;
  			case Types.OTHER:
! 				if (x instanceof PGobject) {
! 					setPGobject(parameterIndex, (PGobject)x);
! 				} else {
! 					// Nope. Go away!
  					throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE);
+ 				}
  				break;
  			default:
  				throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE);
***************
*** 1499,1509 ****
  	public void setObject(int parameterIndex, Object x) throws SQLException
  	{
  		checkClosed();
! 		if (x == null)
! 		{
! 			setNull(parameterIndex, Types.OTHER);
! 			return;
  		}
  		if (x instanceof String)
  			setString(parameterIndex, (String)x);
  		else if (x instanceof BigDecimal)
--- 1513,1523 ----
  	public void setObject(int parameterIndex, Object x) throws SQLException
  	{
  		checkClosed();
! 		if (x == null) {
! 			// We cannot determine an appropriate OID in this case.
! 			throw new PSQLException("postgresql.prep.untypedsetobject", PSQLState.INVALID_PARAMETER_TYPE);
  		}
+ 
  		if (x instanceof String)
  			setString(parameterIndex, (String)x);
  		else if (x instanceof BigDecimal)
***************
*** 1529,1538 ****
  		else if (x instanceof Boolean)
  			setBoolean(parameterIndex, ((Boolean)x).booleanValue());
  		else if (x instanceof PGobject)
! 			setString(parameterIndex, ((PGobject)x).getValue(), connection.getPGType(((PGobject)x).getType()));
! 		else
! 			// Try to store as a string in database
! 			setString(parameterIndex, x.toString(), 0);
  	}
  
  	/*
--- 1543,1553 ----
  		else if (x instanceof Boolean)
  			setBoolean(parameterIndex, ((Boolean)x).booleanValue());
  		else if (x instanceof PGobject)
! 			setPGobject(parameterIndex, (PGobject)x);
! 		else {
! 			// Nope. Go away!
! 			throw new PSQLException("postgresql.prep.type", PSQLState.INVALID_PARAMETER_TYPE);
! 		}
  	}
  
  	/*
Index: org/postgresql/test/jdbc2/GeometricTest.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/test/jdbc2/GeometricTest.java,v
retrieving revision 1.1
diff -u -c -r1.1 GeometricTest.java
*** org/postgresql/test/jdbc2/GeometricTest.java	29 Jun 2004 06:43:28 -0000	1.1
--- org/postgresql/test/jdbc2/GeometricTest.java	9 Oct 2004 10:13:42 -0000
***************
*** 44,50 ****
  		Statement stmt = con.createStatement();
  		ResultSet rs = stmt.executeQuery("SELECT " + column + " FROM testgeometric");
  		assertTrue(rs.next());
! 		assertEquals(obj, rs.getObject(1));
  		rs.close();
  
  		stmt.executeUpdate("DELETE FROM testgeometric");
--- 44,56 ----
  		Statement stmt = con.createStatement();
  		ResultSet rs = stmt.executeQuery("SELECT " + column + " FROM testgeometric");
  		assertTrue(rs.next());
! 		Object check = rs.getObject(1);
! 		if (obj.getValue() == null) {
! 			assertNull(check);
! 			assertTrue(rs.wasNull());
! 		} else {
! 			assertEquals(obj, check);
! 		}
  		rs.close();
  
  		stmt.executeUpdate("DELETE FROM testgeometric");
***************
*** 57,68 ****
--- 63,76 ----
  		checkReadWrite(new PGbox(1.0, -2.0, 3.0, 4.0),  "boxval");
  		checkReadWrite(new PGbox(1.0, 2.0, -3.0, 4.0),  "boxval");
  		checkReadWrite(new PGbox(1.0, 2.0, 3.0, -4.0),  "boxval");
+ 		checkReadWrite(PGbox.NULL,  "boxval");
  	}
  
  	public void testPGcircle() throws Exception {
  		checkReadWrite(new PGcircle(1.0, 2.0, 3.0),    "circleval");
  		checkReadWrite(new PGcircle(-1.0, 2.0, 3.0),    "circleval");
  		checkReadWrite(new PGcircle(1.0, -2.0, 3.0),    "circleval");
+ 		checkReadWrite(PGcircle.NULL, "circleval");
  	}
  
  	public void testPGlseg() throws Exception {
***************
*** 71,76 ****
--- 79,85 ----
  		checkReadWrite(new PGlseg(1.0, -2.0, 3.0, 4.0), "lsegval");
  		checkReadWrite(new PGlseg(1.0, 2.0, -3.0, 4.0), "lsegval");
  		checkReadWrite(new PGlseg(1.0, 2.0, 3.0, -4.0), "lsegval");
+ 		checkReadWrite(PGlseg.NULL, "lsegval");
  	}
  
  	public void testPGpath() throws Exception {
***************
*** 85,90 ****
--- 94,100 ----
  
  		checkReadWrite(new PGpath(points, true),  "pathval");
  		checkReadWrite(new PGpath(points, false), "pathval");
+ 		checkReadWrite(PGpath.NULL, "pathval");
  	}
  
  	public void testPGpolygon() throws Exception {
***************
*** 98,106 ****
--- 108,118 ----
  		};
  
  		checkReadWrite(new PGpolygon(points), "polygonval");
+ 		checkReadWrite(PGpolygon.NULL, "polygonval");
  	}
  
  	public void testPGpoint() throws Exception {
  		checkReadWrite(new PGpoint(1.0, 2.0), "pointval");
+ 		checkReadWrite(PGpoint.NULL, "pointval");
  	}
  } 
Index: org/postgresql/util/PGInterval.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/util/PGInterval.java,v
retrieving revision 1.2
diff -u -c -r1.2 PGInterval.java
*** org/postgresql/util/PGInterval.java	20 Sep 2004 08:36:51 -0000	1.2
--- org/postgresql/util/PGInterval.java	9 Oct 2004 10:13:42 -0000
***************
*** 4,23 ****
  
  public class PGInterval extends PGobject implements Serializable, Cloneable
  {
!   public PGInterval()
!   {
!     setType("interval");
!   }
!   public PGInterval(String value )
!   {
!     this.value = value;
!   }
  
!   /*
!    * This must be overidden to allow the object to be cloned
!    */
!   public Object clone()
!   {
!     return new PGInterval( value );
!   }
  }
--- 4,26 ----
  
  public class PGInterval extends PGobject implements Serializable, Cloneable
  {
! 	public PGInterval()
! 	{
! 		setType("interval");
! 	}
  
! 	public PGInterval(String value)
! 	{
! 		this.value = value;
! 	}
! 
! 	/*
! 	 * This must be overidden to allow the object to be cloned
! 	 */
! 	public Object clone()
! 	{
! 		return new PGInterval( value );
! 	}
! 
! 	public final static PGInterval NULL = new PGInterval();
  }
Index: org/postgresql/util/PGmoney.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/util/PGmoney.java,v
retrieving revision 1.7
diff -u -c -r1.7 PGmoney.java
*** org/postgresql/util/PGmoney.java	29 Nov 2003 19:52:11 -0000	1.7
--- org/postgresql/util/PGmoney.java	9 Oct 2004 10:13:42 -0000
***************
*** 81,86 ****
--- 81,93 ----
  		if (obj instanceof PGmoney)
  		{
  			PGmoney p = (PGmoney)obj;
+ 
+ 			if (this == p)
+ 				return true;
+ 
+ 			if (this == NULL || p == NULL)
+ 				return false;
+ 
  			return val == p.val;
  		}
  		return false;
***************
*** 91,101 ****
--- 98,114 ----
  	 */
  	public Object clone()
  	{
+ 		if (this == NULL)
+ 			return NULL;
+ 
  		return new PGmoney(val);
  	}
  
  	public String getValue()
  	{
+ 		if (this == NULL)
+ 			return null;
+ 
  		if (val < 0)
  		{
  			return "-$" + ( -val);
***************
*** 105,108 ****
--- 118,127 ----
  			return "$" + val;
  		}
  	}
+ 
+ 
+ 	// Ugly hack -- this unique object, by object identity, is a SQL NULL.
+ 	// Cloning NULL gives you NULL. You can mutate NULL, but it doesn't
+ 	// affect its NULL-ness.
+ 	public final static PGmoney NULL = new PGmoney();
  }
Index: org/postgresql/util/PGobject.java
===================================================================
RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/util/PGobject.java,v
retrieving revision 1.6
diff -u -c -r1.6 PGobject.java
*** org/postgresql/util/PGobject.java	7 Jun 2004 21:52:46 -0000	1.6
--- org/postgresql/util/PGobject.java	9 Oct 2004 10:13:42 -0000
***************
*** 62,68 ****
  
  	/**
  	 * This must be overidden, to return the value of the object, in the
! 	 * form required by org.postgresql.
  	 * @return the value of this object
  	 */
  	public String getValue()
--- 62,70 ----
  
  	/**
  	 * This must be overidden, to return the value of the object, in the
! 	 * form required by org.postgresql. If this returns null, the object
! 	 * represents an appropriately-typed SQL NULL.
! 	 *
  	 * @return the value of this object
  	 */
  	public String getValue()
***************
*** 99,104 ****
  	 */
  	public String toString()
  	{
! 		return getValue();
  	}
  }
--- 101,109 ----
  	 */
  	public String toString()
  	{
! 		String value = getValue();
! 		if (value == null)
! 			return "NULL";
! 		return value;
  	}
  }

Re: tightening up on use of oid 0

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Kris Jurka wrote:
> 
> On Sat, 9 Oct 2004, Oliver Jowett wrote:
> 
> 
>>Oliver Jowett wrote:
>>
>>>I am currently cleaning up a few places where OID 0 could get used as a 
>>>parameter type (causing the backend to try to infer a type).
>>
>>Here is a patch to do this, including the PGobject changes to handle SQL 
>>NULLs.
>>
> 
> 
> What I was suggesting before was a means to allow users to specify a pg
> type for the Types.OTHER case, but not to require it.  I don't see the
> danger in allowing OID 0 in this case.

Ah, I thought you were OK with disallowing setNull(Types.OTHER) 
entirely, I must have misunderstood what you said earlier.

I described one problem with allowing it in an earlier email:

>> Consider the case where you have two functions:
>> 
>>   foo(line)
>>   foo(box)
>> 
>> Executing "SELECT foo(?)" via PreparedStatement will work fine if you pass a non-null PGline or PGbox argument to setObject, but if you try to setNull() then you will get ambiguity between the two functions at execution time. 

That's quite unpredictable behaviour.

> I know you are after complete 
> strong typing, but I don't see the benefit while I do see the drawback.

What is the drawback? The only case that will change is the case that is 
currently ambiguous. And there is a fairly simple mechanism for 
disambiguating it via PGobject.

-O

Re: tightening up on use of oid 0

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Kris Jurka wrote:

> I was testing this out a little and this doesn't produce the error I 
> expected:
> 
> CREATE FUNCTION g(int) RETURNS int AS 'SELECT 1;' LANGUAGE sql;
> CREATE FUNCTION g(float) RETURNS int AS 'SELECT 2;' LANGUAGE sql;
> SELECT g(NULL);

I think implicit casting is interfering here somehow.

With types that don't have implicit casts, you get ambiguity:

>> test=> CREATE FUNCTION h(line) RETURNS int AS 'SELECT 1;' LANGUAGE sql;
>> CREATE FUNCTION
>> test=> CREATE FUNCTION h(point) RETURNS int AS 'SELECT 2;' LANGUAGE sql;
>> CREATE FUNCTION
>> test=> SELECT h(NULL);
>> ERROR:  function h("unknown") is not unique
>> HINT:  Could not choose a best candidate function. You may need to add explicit type casts.

-O

Re: tightening up on use of oid 0

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Kris Jurka wrote:

> Your approach has the benefit of being 
> fail-fast as adding a new function to the database suddently can't 
> produce an ambiguity for Java code.

Right. The driver cannot guarantee that setNull(i,Types.OTHER) will 
always be able to infer the right type. It seems safer to me to 
completely disallow it than have it sometimes succeed and sometimes fail 
depending on the exact state of the database. It may break older apps, 
but new apps would be more robust.

General question for the list: how much code is out there that performs 
one of these (equivalent) calls?

   PreparedStatement.setObject(i, null);
   PreparedStatement.setObject(i, null, Types.OTHER);
   PreparedStatement.setNull(i, Types.OTHER);

> I don't think it's a common 
> situation to have overloaded functions that get called with non-typed 
> nulls, so I wanted to allow it to work as usual for non-ambiguous 
> cases.

After some experimentation, it's not just overloaded functions that are 
affected.

- "? IS NULL" breaks if the parameter is an untyped NULL (this was the 
original issue -- in an off-list email --that made me look at this area)

- Functions taking 'anyarray' or 'anyelement' don't like untyped NULLs, 
even if they are STRICT (ERROR:  could not determine anyarray/anyelement 
type because input has type "unknown")

I'd like to catch those errors earlier than query execution if possible. 
If nothing else, it'll encourage application developers to provide 
correct type info to the driver in all cases..

-O

Re: tightening up on use of oid 0

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Iain wrote:
> Hi,
> 
> For my part, I've never used any of those calls.
> 
> FWIW, I always use reference objects such as "Integer" as opposed to 
> "int" so I've never done it that way. All calls to setObject or 
> set would always use a variable which is typed (and may be 
> null) my assumption is that I avoid all such ambiguities that way.

Just to clarify.. these calls are typed:

   setInt(i, 42);
   setObject(i, new Integer(42));
   setObject(i, new Integer(42), Types.INTEGER);
   setObject(i, null, Types.INTEGER);
   setNull(i, Types.INTEGER);
   setObject(i, new PGline(...), Types.OTHER);

These calls are not (sufficiently) typed:

   setObject(i, null);
   setObject(i, (Integer)null);     // (*)
   setObject(i, null, Types.OTHER);
   setNull(i, Types.OTHER);

Types.OTHER on its own is not specific enough to identify a particular 
backend type, and Java nulls have no inherent type ('instanceof' will 
always return false).

 From your description it sounds like you may use the case marked (*) ?

-O

Re: tightening up on use of oid 0

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Kris Jurka wrote:
> 
> On Wed, 13 Oct 2004, Oliver Jowett wrote:
> 
> 
>>I'd like to catch those errors earlier than query execution if possible. 
>>If nothing else, it'll encourage application developers to provide 
>>correct type info to the driver in all cases..
> 
> 
> I don't see the real benefit of catching this one statement earlier.  It's 
> still a runtime failure.

My point was that it is useful to generate an error on *all* uses of 
untyped nulls, so that the developer sees the error in all cases 
regardless of the database state or statement context.

Consider, for example, framework code that calls user-specified or 
user-provided functions. If it uses untyped nulls, and the driver uses 
Oid.UNKNOWN, you will only see the problem when the framework tries to 
pass a NULL to an overloaded function. If the driver rejects the call 
with an error in all cases where a NULL is used, then the framework 
developer sees the error as soon as they exercise the NULL path, 
regardless of whether their test environment has overloaded functions or 
not.

====

The spec has this to say about the setObject case:

>> If setObject is called without a type parameter, the Java Object is
>> implicitly mapped using the default mapping for that object type.

There is no default mapping (in appendix B table B-4) for Java nulls.

Of course the spec then goes on to say:

>> If a Java null is passed to any of the setter methods that take a Java
>> object, the parameter will be set to JDBC NULL.

I don't know which takes precedence. The way that setNull() is organized 
implies that JDBC requires NULLs to be typed, but there's nothing 
explicit about it.

-O

Re: tightening up on use of oid 0

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Iain wrote:
> Hi Oliver,
> 
> Just out of interest, is the case you marked,
> 
>>   setObject(i, (Integer)null);     // (*)
> 
> equivalent to
> 
>    Integer someInteger = null;
>    setObject(i, someInteger);
> 
> ?

Yes.

> I would ask the question then, is there any situation where there is no 
> alternative to the insufficiantly typed calls you listed?

I think there is always an alternative.

For standard types you can use setNull or setObject with a type code:

   setNull(i, Types.INTEGER);
   setObject(i, null, Types.INTEGER);

For extension types (classed as Types.OTHER) you can use the singleton 
NULL objects I introduced in my patch:

   setObject(i, PGline.NULL);
   setObject(i, PGline.NULL, Types.OTHER);

-O

Re: tightening up on use of oid 0

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Kris Jurka wrote:

> I was looking at the assorted changes to the PGobject extensions and I'm 
> unclear on exactly how NULL is handled.  Consider PGmoney has tests for 
> NULL in equals, clone, and getValue, but PGbox does not.  Is this simply 
> an oversight or is there something more profound going on here.

I ended up with two approaches for this.

For those types where there was already a field I could hijack to 
represent NULL -- e.g. PGbox's points array -- I used that to represent 
null values. The singleton NULL is just a normal object that happens to 
have a null value. You can have several different objects that all 
represent null if you like, and you can mutate an object representing a 
null value just like any other object of the type. This is consistent 
with the way other instances of the type operate, but it's slightly 
dangerous as it's possible to modify the NULL singleton so it no longer 
has a null value (pity we don't have 'const'..)

For the other types that didn't have a suitable field, I'd have needed 
to add a field to every instance of the type to indicate whether the 
object was a null or not. Instead, I used the identity of the NULL 
singleton to decide when an object is null. In that case, there is only 
ever one object that represents a null, and the actual value it holds is 
irrelevant -- getValue() etc. check the identity of 'this' before 
looking at the actual value.

It's hardly ideal but it kept the changes to a minimum. If you don't 
mind a more invasive set of changes, I can probably come up with 
something better.

-O

PGobject overhaul (was Re: tightening up on use of oid 0)

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Kris Jurka wrote:
> 
> On Thu, 14 Oct 2004, Oliver Jowett wrote:
>   [... PGobject and NULL ...]
>>It's hardly ideal but it kept the changes to a minimum. If you don't 
>>mind a more invasive set of changes, I can probably come up with 
>>something better.
> 
> Yes, let's think about this a little more.  I unfortunately don't have any 
> brilliant ideas, perhaps just adding a boolean everywhere is simplest.

I've applied the non-PGobject bits of this patch.

For PGobject it turned into a bit of a general overhaul. Currently I have:

PGobject layer:

- PGobject becomes an interface
- Implementations of PGobject should provide a ctor taking a single 
String; this is called by the driver to construct non-null objects.
- PGobject.setType() and PGobject.setValue() go away entirely
- A new helper class, PGunknown, provides an immutable type+value 
implementation of PGobject (i.e. 'new PGunknown("mytype","myvalue")'). 
This gives the functionality currently available via PGobject.setType() 
/ setValue, and is used by the driver when it receives an unhandled type 
in a resultset.

PGobject subclasses:

Mutability:
- Generally, classes become immutable where it's easy to do (PGmoney, 
PGinterval, PGpoint, PGline, PGlseg, PGcircle, PGbox)
- PGpoint.translate() returns a new PGpoint rather than modifying the 
existing point (I'm not sure why this method even exists really..)
- Constant-sized PGpoint[] arrays in PGline, PGlseg, PGbox become 
separate fields; this makes immutability much easier.
- PGpolygon and PGpath remain mutable as it's hard to make them 
immutable without incurring lots of array copies.

NULL-handling:
- No-arg ctors construct NULL objects.
- PGmoney and PGpoint get boolean isNull fields, the other types reuse 
an existing reference field.
- equals(), hashCode(), getValue() take account of NULL-ness.
- Add per-class static NULL singleton fields for convenience; for 
mutable types, I'm not sure whether to just omit the singletons 
(slightly less convenient/readable), or to use PGunknown to get an 
immutable NULL (works, but 'instanceof' on NULL becomes misleading).

Much of the mutability changes here are just personal preference, 
they're not necessary to support NULLs but I thought I'd clean things up 
while I was in the area.

When we come to do binary-format types, I'd expect we would have a 
subinterface (PGbinaryobject?) that adds whatever methods are needed for 
binary parameter formatting. Objects that implement PGbinaryobject 
become candidates for binary transfer.

Any thoughts on this general approach?

-O

Re: PGobject overhaul (was Re: tightening up on use of oid

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Oliver Jowett wrote:

> For PGobject it turned into a bit of a general overhaul. Currently I have: [...]

I've put my current changes up at: 
http://visible.randomly.org/pgjdbc/pgjdbc-pgobject-changes.txt

-O

Re: PGobject overhaul (was Re: tightening up on use of oid

От:
Oliver Jowett <oliver@opencloud.com>
Дата:
Kris Jurka wrote:
> 
> On Thu, 28 Oct 2004, Oliver Jowett wrote:
> 
> 
>>For PGobject it turned into a bit of a general overhaul. Currently I have:
> 
> 
> These changes are way too drastic for something as minor as preventing a 
> user from accidentally mutating a NULL PGobject.

That wasn't really my motivation. It was a general cleanup of PGobject 
and subclasses. The current implementations leave something to be desired.

> These API changes suck 
> for both developers and users.  There's no way to make a PGobject 
> implementation compile against both 7.4 and 8.0 drivers.  Altering the 
> PGline API means user code can't compile against both 7.4 and 8.0 drivers.

The feedback I got earlier was that changing the PGobject API wasn't a 
big deal and that external users of the API (which seems to boil down to 
PostGIS & co only) would just track the changes. Is this not true?

> If we were providing exciting new features, then maybe, but for now we've
> got to find a way to make this work without huge API changes or we should
> abandon the whole idea and go back to your original patch.  What
> immediately comes to mind is making the PGobject interface an abstract
> class with all abstract methods so that a developer can implement a type
> that can work with both driver versions.

That's possible. I'd almost prefer doing a new interface and having 
PGobject be a completely-abstract implementation of it. Having the 
driver-required bit as an interface makes it much easier to integrate 
into whatever representation of the data is convenient to the application.

This might be moot if we look at a mapping mechanism that is external to 
the data objects themselves, as suggested by Markus.

Either way, I can't really justify spending much more time on this: we 
don't use extension types in our code at all, and there is still an 
(admittedly ugly) way to set NULL values for extension types in the 
existing driver.

-O

Re: tightening up on use of oid 0

От:
Kris Jurka <books@ejurka.com>
Дата:


On Fri, 8 Oct 2004, Oliver Jowett wrote:
> 
> Executing "SELECT foo(?)" via PreparedStatement will work fine if you 
> pass a non-null PGline or PGbox argument to setObject, but if you try to 
> setNull() then you will get ambiguity between the two functions at 
> execution time.
> 
> I can't see a way to fix this without a postgresql extension of some 
> sort. Options I can think of are:

Can't the existing PGobject interface handle this.  You can create a 
PGobject with the correct datatype and a null value and that should work.

Kris Jurka

Re: tightening up on use of oid 0

От:
Kris Jurka <books@ejurka.com>
Дата:


On Sat, 9 Oct 2004, Oliver Jowett wrote:

> > Can't the existing PGobject interface handle this.  You can create a 
> > PGobject with the correct datatype and a null value and that should work.
> 
> OK, so getValue() and toString() return null?

Right.

> This would still mean that setNull() would not work for Types.OTHER, though.
> 

I don't think this is a very big deal.  The only place this looks 
important is for an overloaded function call.  The previous driver didn't 
have any typing at all for NULLs, and I can only recall one complaint 
about.  The other API additions you suggested didn't really appeal to me 
and we have an "out" with PGobject, so I think we're OK here.

Kris Jurka

Re: tightening up on use of oid 0

От:
Kris Jurka <books@ejurka.com>
Дата:


On Sat, 9 Oct 2004, Oliver Jowett wrote:

> Oliver Jowett wrote:
> > I am currently cleaning up a few places where OID 0 could get used as a 
> > parameter type (causing the backend to try to infer a type).
> 
> Here is a patch to do this, including the PGobject changes to handle SQL 
> NULLs.
> 

What I was suggesting before was a means to allow users to specify a pg
type for the Types.OTHER case, but not to require it.  I don't see the 
danger in allowing OID 0 in this case.  I know you are after complete 
strong typing, but I don't see the benefit while I do see the drawback.
Comments?

Kris Jurka

Re: tightening up on use of oid 0

От:
Kris Jurka <books@ejurka.com>
Дата:


On Wed, 13 Oct 2004, Oliver Jowett wrote:

> >> Consider the case where you have two functions:
> >> 
> >>   foo(line)
> >>   foo(box)
> >> 
> >> Executing "SELECT foo(?)" via PreparedStatement will work fine if you
> >> pass a non-null PGline or PGbox argument to setObject, but if you try
> >> to setNull() then you will get ambiguity between the two functions at
> >> execution time.
> 

I was expecting to see this "ERROR:  function f("unknown") is not unique"
in all ambiguous situations.  Your approach has the benefit of being 
fail-fast as adding a new function to the database suddently can't 
produce an ambiguity for Java code.  I don't think it's a common 
situation to have overloaded functions that get called with non-typed 
nulls, so I wanted to allow it to work as usual for non-ambiguous 
cases.

I was testing this out a little and this doesn't produce the error I 
expected:

CREATE FUNCTION g(int) RETURNS int AS 'SELECT 1;' LANGUAGE sql;
CREATE FUNCTION g(float) RETURNS int AS 'SELECT 2;' LANGUAGE sql;
SELECT g(NULL);

Instead it returns 2 indicating the float version was called.  I don't 
know if this is a bug and/or oddity of the type system, but if it's the 
expected behavior then I definitely agree with you.

> What is the drawback? The only case that will change is the case that is 
> currently ambiguous. And there is a fairly simple mechanism for 
> disambiguating it via PGobject.

The case that will change is that all the non-ambiguous cases can 
no longer be called with untyped nulls:
 - non ambiguous functions
 - INSERT/UPDATE statements that use nulls

Kris Jurka

Re: tightening up on use of oid 0

От:
Kris Jurka <books@ejurka.com>
Дата:


On Wed, 13 Oct 2004, Oliver Jowett wrote:

> I'd like to catch those errors earlier than query execution if possible. 
> If nothing else, it'll encourage application developers to provide 
> correct type info to the driver in all cases..

I don't see the real benefit of catching this one statement earlier.  It's 
still a runtime failure.

Kris Jurka

Re: tightening up on use of oid 0

От:
Kris Jurka <books@ejurka.com>
Дата:


On Thu, 14 Oct 2004, Oliver Jowett wrote:

> Kris Jurka wrote:
> 
> > I was looking at the assorted changes to the PGobject extensions and I'm 
> > unclear on exactly how NULL is handled.  Consider PGmoney has tests for 
> > NULL in equals, clone, and getValue, but PGbox does not.  Is this simply 
> > an oversight or is there something more profound going on here.
> 
> I ended up with two approaches for this.

I don't like the lack of consistency here, "new PGbox()" is NULL, but "new 
PGmoney()" is zero instead.  I also don't like the ability to mutate away 
NULLness.  This means another application can break mine by modifying the 
shared PGbox.NULL object.

> It's hardly ideal but it kept the changes to a minimum. If you don't 
> mind a more invasive set of changes, I can probably come up with 
> something better.

Yes, let's think about this a little more.  I unfortunately don't have any 
brilliant ideas, perhaps just adding a boolean everywhere is simplest.

Kris Jurka

Here's a merged version of the patch, if it helps:

http://www.ejurka.com/pgsql/patches/

Re: PGobject overhaul (was Re: tightening up on use of oid

От:
Kris Jurka <books@ejurka.com>
Дата:


On Thu, 28 Oct 2004, Oliver Jowett wrote:

> For PGobject it turned into a bit of a general overhaul. Currently I have:

These changes are way too drastic for something as minor as preventing a 
user from accidentally mutating a NULL PGobject.  These API changes suck 
for both developers and users.  There's no way to make a PGobject 
implementation compile against both 7.4 and 8.0 drivers.  Altering the 
PGline API means user code can't compile against both 7.4 and 8.0 drivers.

If we were providing exciting new features, then maybe, but for now we've
got to find a way to make this work without huge API changes or we should
abandon the whole idea and go back to your original patch.  What
immediately comes to mind is making the PGobject interface an abstract
class with all abstract methods so that a developer can implement a type
that can work with both driver versions.


Kris Jurka
FAQ