Thread: Abandoning PGobject

Abandoning PGobject

From
Markus Schaber
Date:
Hi,

There have been multiple discussions about new custom type
implementations on this list, and the necessary extensions that are
needed to allow binary transfer.

I just implemented the raw skeleton of my current idea how such thing
may work. It is based on what I remember from earlier.

The basic idea is that primitive types, Strings and byte[] are handled
by the driver directly, and all other built-in datatypes (e. G.
BigDecimal or Timestamp) as well as extension datatypes are handled by
this new model.

As it may make sense to call getInt on a BigDecimal, for reading all
methods are present, they are meant to be called 1:1 by the underlying
ResultSet. For writing, this is different, and everything can be handled
by a single setObject() method. I'm not really shure about which of the
given variants is the best one, so I included some of them.

It allows having different handlers for reading and writing, as well as
it allows a single handler to handle several sql and java types. It can
be made backwards compatible by including a TypeTextReader/
TypeTextWriter implementation that handles all PGobject subclasses.

This code is just a rough skeleton for my idea. I would like to get your
feedback what you think about it before I put too much effort into this.

Open issues are:

- What do you think about SUNs jdbc type map approach? I personally
think that, unfortunately, this is not flexible enough.

- How do we handle endianness? PostGIS canonical binary representation
includes endian information, but what about the other types?

- Handling of NULL values. I currently assume that the TypeDrivers are
not used for NULL values, because they can be handled in a type
independend manner by the driver itsself.



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
/* TypeBinaryWriter.java
 *
 * (C) 28.02.2005 Markus Schaber, Logi-Track ag, CH 8001 Zuerich
 *
 * $Id: $
 */
package org.postgresql.types;

import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.Calendar;


public interface TypeBinaryReader extends TypeDriver {
    String getString(byte[] data, int start, int length) throws SQLException;

    boolean getBoolean(byte[] data, int start, int length) throws SQLException;

    byte getByte(byte[] data, int start, int length) throws SQLException;

    short getShort(byte[] data, int start, int length) throws SQLException;

    int getInt(byte[] data, int start, int length) throws SQLException;

    long getLong(byte[] data, int start, int length) throws SQLException;

    float getFloat(byte[] data, int start, int length) throws SQLException;

    double getDouble(byte[] data, int start, int length) throws SQLException;

    BigDecimal getBigDecimal(byte[] data, int start, int length) throws SQLException;

    byte[] getBytes(byte[] data, int start, int length) throws SQLException;

    java.sql.Date getDate(byte[] data, int start, int length) throws SQLException;

    java.sql.Date getDate(byte[] data, int start, int length, Calendar cal) throws SQLException;

    java.sql.Time getTime(byte[] data, int start, int length) throws SQLException;

    java.sql.Time getTime(byte[] data, int start, int length, Calendar cal) throws SQLException;

    java.sql.Timestamp getTimestamp(byte[] data, int start, int length) throws SQLException;

    java.sql.Timestamp getTimestamp(byte[] data, int start, int length, Calendar cal) throws SQLException;

    java.io.InputStream getAsciiStream(byte[] data, int start, int length) throws SQLException;

    java.io.InputStream getUnicodeStream(byte[] data, int start, int length) throws SQLException;

    java.io.InputStream getBinaryStream(byte[] data, int start, int length) throws SQLException;

    Object getObject(byte[] data, int start, int length) throws SQLException;

    java.io.Reader getCharacterStream(byte[] data, int start, int length) throws SQLException;

    java.net.URL getURL(byte[] data, int start, int length) throws SQLException;
}
/*
 * TypeBinaryWriter.java
 *
 * (C) 28.02.2005 Markus Schaber, Logi-Track ag, CH 8001 Zuerich
 *
 * $Id: $
 */
package org.postgresql.types;

import java.io.IOException;
import java.io.OutputStream;
import java.sql.SQLException;

public interface TypeBinaryWriter extends TypeDriver {

    /**
     * Return the length of an encoded object in bytes.
     *
     * This method can calculate the length on actual data. You can call it with
     * any object the driver accepts via set* methods.
     *
     * @return the lenght in bytes, -1 for unknown.
     * @throws SQLException if the driver does not know to handle the object or
     *             something else goes wrong.
     */
    int objectBytesLength(Object data) throws SQLException;

    /** Render the object into the SQL binary representation */
    void renderObject(Object data, byte[] target, int startindex);

    /**
     * Render the object into the SQL binary representation
     */
    void renderObject(Object data, OutputStream outstream) throws SQLException, IOException;
}
/*
 * TypeDriver.java
 *
 * (C) 28.02.2005 Markus Schaber, Logi-Track ag, CH 8001 Zuerich
 *
 * $Id: $
 */
package org.postgresql.types;

import org.postgresql.PGConnection;

import java.sql.SQLException;

public interface TypeDriver {

    /**
     * Return all classes that are known to be supported at compile-time.
     */
    public Class[] getSupportedClasses();

    /** Return all sql types that are known to be supported. */
    public String[] getSupportedTypes();

    /** Test whether we support the SQL type. */
    public boolean supports(String type);

    /**
     * Test whether we support the following class.
     *
     * This must return true for all classes that are in getSupportedClasses. It
     * is also allowed to return true for other classes it knows to write, for
     * example third-party subclasses that are not known at compile time.
     */
    public boolean supports(Class klass);

    /** Which SQL type do we produce for this object
     * Note that this mapping is not 1:1, a single SQL type may correspond
     * to a cuple of java types.
     */
    public String objectType(Object data) throws SQLException;

    /** Do we support creation of binary representation?
     * @returns true on instances of TypeBinaryWriter
     */
    public boolean supportsBinaryWrite();

    /** Do we support creation of text representation?
     * @returns true on instances of TypeTextWriter
     */
    public boolean supportsTextWrite();

    /** Do we support parsing of binary representation?
     * @returns true on instances of TypeBinaryReader
     */
    public boolean supportsBinaryReading();

    /** Do we support parsing of text representation?
     * @returns true on instances of TypeTextReader
     */
    public boolean supportsTextReading();

    /**
     * Initialize the TypeDriver for a new connection.
     *
     * This method may do read-only queries on the database. It allows the
     * driver to make adoptions for different protocols, server versions or
     * installed data types. An example for this is PostGIS, where older
     * versions do not support binary representation on the server side.
     *
     * XXX: Should we only use java.sql.Connection here?
     *
     * @returns the TypeDriver instance, which may be the same as the input
     *          parameter if no adoption is made.
     */
    public TypeDriver forConnection(PGConnection conn) throws SQLException;
}
/*
 * TypeMap.java
 *
 * (C) 28.02.2005 Markus Schaber, Logi-Track ag, CH 8001 Zuerich
 *
 * $Id: $
 */
package org.postgresql.types;

import org.postgresql.PGConnection;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;

public class TypeMap {
    protected HashMap byType = new HashMap();
    protected HashMap byClass = new HashMap();
    protected ArrayList drivers = new ArrayList();

    /**
     * Search the driver for a given SQL type
     *
     * @returns the driver, null if none is found
     */
    public TypeDriver getDriverForType(String type) {
        return (TypeDriver) byType.get(type);
    }

    /**
     * Search the driver for a given java object
     *
     * @returns the driver, null if none is found
     */
    public TypeDriver getDriverForObject(Object obj) {
        Class klass = obj.getClass();
        return getDriverForClass(klass);
    }

    /**
     * Search the driver for a given java class
     *
     * @returns the driver, null if none is found
     */
    private TypeDriver getDriverForClass(Class klass) {
        TypeDriver d = (TypeDriver) byClass.get(klass);
        if (d == null) {
            for (int i = 0; i < drivers.size(); i++) {
                TypeDriver drv = (TypeDriver) drivers.get(i);
                if (drv.supports(klass)) {
                    d = drv;
                    break;
                }
            }
            if (d != null) { // cache the search result
                byClass.put(klass, d);
            }
        }
        return d;
    }

    /** Register a new driver */
    public void addDriver(TypeDriver drv) {
        Class[] classes = drv.getSupportedClasses();
        for (int i = 0; i < classes.length; i++) {
            byClass.put(classes[i], drv);
        }
        String[] types = drv.getSupportedTypes();
        for (int i = 0; i < types.length; i++) {
            byType.put(types[i], drv);
        }
        drivers.add(drv);
    }

    /**
     * Clones the TypeDriver for a new connection.
     *
     * This is e. G. used by the driver to get connection specific instances
     * from the default TypeDriver, and by Connection.setCatalog() to re-init
     * the typemap on a database change.
     *
     * This method may do read-only queries on the database, as it calls
     * TypeDriver.forConnection() for each known driver.
     *
     * XXX: Should we only use java.sql.Connection here?
     *
     * @returns the TypeDriver instance
     */
    public TypeMap forConnection(PGConnection conn) throws SQLException {
        TypeMap result = new TypeMap();
        for (int i = 0; i < drivers.size(); i++) {
            TypeDriver drv = (TypeDriver) drivers.get(i);
            TypeDriver newdrv = drv.forConnection(conn);
            result.addDriver(newdrv);
        }
        return result;
    }
}
/* TypeBinaryWriter.java
 *
 * (C) 28.02.2005 Markus Schaber, Logi-Track ag, CH 8001 Zuerich
 *
 * $Id: $
 */
package org.postgresql.types;

import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.Calendar;


public interface TypeTextReader extends TypeDriver {
    String getString(String data) throws SQLException;

    boolean getBoolean(String data) throws SQLException;

    byte getByte(String data) throws SQLException;

    short getShort(String data) throws SQLException;

    int getInt(String data) throws SQLException;

    long getLong(String data) throws SQLException;

    float getFloat(String data) throws SQLException;

    double getDouble(String data) throws SQLException;

    BigDecimal getBigDecimal(String data) throws SQLException;

    byte[] getBytes(String data) throws SQLException;

    java.sql.Date getDate(String data) throws SQLException;

    java.sql.Date getDate(String data, Calendar cal) throws SQLException;

    java.sql.Time getTime(String data) throws SQLException;

    java.sql.Time getTime(String data, Calendar cal) throws SQLException;

    java.sql.Timestamp getTimestamp(String data) throws SQLException;

    java.sql.Timestamp getTimestamp(String data, Calendar cal) throws SQLException;

    java.io.InputStream getAsciiStream(String data) throws SQLException;

    java.io.InputStream getUnicodeStream(String data) throws SQLException;

    java.io.InputStream getBinaryStream(String data) throws SQLException;

    Object getObject(String data) throws SQLException;

    java.io.Reader getCharacterStream(String data) throws SQLException;

    java.net.URL getURL(String data) throws SQLException;
}
/*
 * TypeBinaryWriter.java
 *
 * (C) 28.02.2005 Markus Schaber, Logi-Track ag, CH 8001 Zuerich
 *
 * $Id: $
 */
package org.postgresql.types;

import java.io.IOException;
import java.io.Writer;
import java.sql.SQLException;

public interface TypeTextWriter extends TypeDriver {

    /**
     * Return the length of an encoded object in characters.
     *
     * This method can calculate the length on actual data. You can call it with
     * any object the driver accepts via set* methods.
     *
     * @return the lenght in bytes, -1 for unknown.
     * @throws SQLException if the driver does not know to handle the object or
     *             something else goes wrong.
     */
    int objectCharLength(Object data) throws SQLException;

    /** Render the object into the SQL text representation */
    String renderObject(Object data);

    /** Render the object into the SQL text representation */
    void renderObject(Object data, StringBuffer sb);

    /** Render the object into the SQL text representation */
    void renderObject(Object data, char[] target, int startindex);

    /** Render the object into the SQL text representation
     * @throws IOException */
    void renderObject(Object data, Writer target) throws IOException;
}

Re: Abandoning PGobject

From
Oliver Jowett
Date:
Markus Schaber wrote:

> - How do we handle endianness? PostGIS canonical binary representation
> includes endian information, but what about the other types?

I have not looked at your code, but the simplest approach seems to be to
only support binary transfer on V3 connections. The standard V3 types
have binary representations that use fixed endianness irregardless of
the endianness of the server.

-O

Re: Abandoning PGobject

From
Markus Schaber
Date:
Hi, Oliver,

Oliver Jowett schrieb:

>> - How do we handle endianness? PostGIS canonical binary representation
>> includes endian information, but what about the other types?
>
> I have not looked at your code,

Well, currently, it is rather a skeleton than a code, and endianness
just does not exist :-)

> but the simplest approach seems to be to
> only support binary transfer on V3 connections. The standard V3 types
> have binary representations that use fixed endianness irregardless of
> the endianness of the server.

That sounds good. So type implementors will have four possibilities:

- use fixed endianness for canonical binary representation (as the
built-in types do, preferred solution)
- include an endianness flag in the data (as PostGIS does, following the
OpenGIS wkb spec)
- probe the endianness on connection initialization (forConnection() method)
- fall back to text only representation.


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