diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java b/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java index 8532fe777f..aabb492309 100644 --- a/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java +++ b/src/interfaces/jdbc/org/postgresql/jdbc2/PreparedStatement.java @@ -613,7 +613,8 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta else if (x instanceof PGobject) setString(parameterIndex, ((PGobject)x).getValue()); else - setLong(parameterIndex, connection.putObject(x)); + // Try to store java object in database + setSerialize(parameterIndex, connection.putObject(x), x.getClass().getName() ); } /** @@ -674,6 +675,29 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta inStrings[paramIndex - 1] = s; } + /** + * Set a parameter to a tablerow-type oid reference. + * + * @param parameterIndex the first parameter is 1... + * @param x the oid of the object from org.postgresql.util.Serialize.store + * @param classname the classname of the java object x + * @exception SQLException if a database access error occurs + */ + private void setSerialize(int parameterIndex, long x, String classname) throws SQLException + { + // converts . to _, toLowerCase, and ensures length<32 + String tablename = Serialize.toPostgreSQL( classname ); + DriverManager.println("setSerialize: setting " + x + "::" + tablename ); + + // OID reference to tablerow-type must be cast like: :: + // Note that postgres support for tablerow data types is incomplete/broken. + // This cannot be just a plain OID because then there would be ambiguity + // between when you want the oid itself and when you want the object + // an oid references. + set(parameterIndex, Long.toString(x) + "::" + tablename ); + } + + // ** JDBC 2 Extensions ** /** @@ -803,3 +827,4 @@ public class PreparedStatement extends Statement implements java.sql.PreparedSta } } + diff --git a/src/interfaces/jdbc/org/postgresql/util/Serialize.java b/src/interfaces/jdbc/org/postgresql/util/Serialize.java index a89fba5baf..2e80102ecb 100644 --- a/src/interfaces/jdbc/org/postgresql/util/Serialize.java +++ b/src/interfaces/jdbc/org/postgresql/util/Serialize.java @@ -8,16 +8,102 @@ import java.util.*; import java.sql.*; /** - * This class uses PostgreSQL's object oriented features to store Java Objects. + * This class uses PostgreSQL's object oriented features to store Java Objects.

* * It does this by mapping a Java Class name to a table in the database. Each * entry in this new table then represents a Serialized instance of this * class. As each entry has an OID (Object IDentifier), this OID can be - * included in another table. + * included in another table.

* - * This is too complex to show here, and will be documented in the main - * documents in more detail. + * Serialize depends on a feature of Postgres that allows + * a table to be used as a data type. However, Postgres support of + * this feature is incomplete. The basic ability to create and use + * a table as a field type in another table exists:
+ * CREATE TABLE myclass( var1 TEXT, var2 INTEGER );
+ * CREATE TABLE othertable( field1 TEXT, field2 myclass );
+ * INSERT INTO myclass VALUES ('Hello', 1);
+ * INSERT INTO othertable VALUES ('World', xxxx::myclass);
+ * where xxxx is the OID of a row in myclass
+ * This lets othertable reference a myclass instance but + * the support to actually make any use of the myclass data type + * is not there. For instance, you cannot compare the myclass field + * with ANY other data type, not even with other myclass values. + * Casting to and from the myclass type will also not work. + * From the limited testing done, only the literal xxxx::myclass + * syntax appears to work.

* + * Queries like:
+ * SELECT othertable.field2.var1 FROM othertable;
+ * will not work but were suggested in the original Postgres + * design documents.

+ * Because support is incomplete for table data types, tables + * such as othertable that hold java instances should also + * hold an oid field for the same java instance:
+ * CREATE othertable( field1 TEXT, field2 myclass, myclassOID oid);
+ * This oid-type field would be set with setInt() immediately after + * setting the myclass-type field with setObject(). The order of these + * set calls matters since the oid is not available until after + * setting the object when inserting a new object. With the oid, + * queries and comparisons etc. can be done to locate a myclass. + * Read below about how to include an int oid field in your java class + * that is updated by setObject() when it is inserted.

+ * + * The myclass table represents a java class. This table is created + * by Serialize.create(). Serialize.create() must be called before + * the first use of the myclass java class in PreparedStatement.setObject() + * calls. This is a one-time initialization step.

+ * + * There are a number of limitations placed on the java class to be + * used by Serialize: + *

+ * + * Suggested usage: + *
    + *
  1. Create your javaclass and include an int oid = 0; field. + *
  2. Run Serialize.create( conn, javaclass ) to create the table for javaclass (once). + *
  3. Create mytable in the database with fields like: jclassoid INTEGER, jclass JAVACLASS
    + *
  4. Use a jdbc2.PreparedStatement to insert, update, or select from mytable. + * Use setObject(2, jclass), followed by setInt(1, jclass.oid) to setup an insert. + *
  5. Use jclass.oid and jclassoid to do queries since the jclass field cannot be used + * for anything but fetching the javaclass instance with getObject("jclass"). + *
+ * Finally, Serialize is a work in progress and being a utility class, it is not supported. + * You are "on your own" if you use it. If you use it and make any enhancements, + * please consider joining the email lists pgsql-jdbc@postgresql.org and pgsql-patches@postgresql.org + * and contributing your additions. */ public class Serialize { @@ -41,10 +127,12 @@ public class Serialize { try { conn = c; + DriverManager.println("Serialize: initializing instance for type: " + type); tableName = toPostgreSQL(type); className = type; ourClass = Class.forName(className); } catch(ClassNotFoundException cnfe) { + DriverManager.println("Serialize: " + className + " java class not found"); throw new PSQLException("postgresql.serial.noclass",type); } @@ -52,14 +140,17 @@ public class Serialize boolean status = false; ResultSet rs = conn.ExecSQL("select typname from pg_type,pg_class where typname=relname and typname='" + tableName + "'"); if(rs!=null) { - if(rs.next()) - status=true; + if(rs.next()) { + status = true; + DriverManager.println("Serialize: " + tableName + " table found"); + } rs.close(); - } + } // This should never occur, as org.postgresql has it's own internal checks - if(!status) + if(!status) { + DriverManager.println("Serialize: " + tableName + " table not found"); throw new PSQLException("postgresql.serial.table",type); - + } // Finally cache the fields within the table } @@ -85,81 +176,72 @@ public class Serialize * @return Object relating to oid * @exception SQLException on error */ - public Object fetch(int oid) throws SQLException - { - try { - Object obj = ourClass.newInstance(); + public Object fetch(int oid) throws SQLException + { + try { + DriverManager.println("Serialize.fetch: " + "attempting to instantiate object of type: " + ourClass.getName() ); + Object obj = ourClass.newInstance(); + DriverManager.println("Serialize.fetch: " + "instantiated object of type: " + ourClass.getName() ); - // NB: we use java.lang.reflect here to prevent confusion with - // the org.postgresql.Field + // NB: we use java.lang.reflect here to prevent confusion with + // the org.postgresql.Field - // used getFields to get only public fields. We have no way to set values - // for other declarations. Maybe look for setFieldName() methods? - java.lang.reflect.Field f[] = ourClass.getFields(); + // used getFields to get only public fields. We have no way to set values + // for other declarations. Maybe look for setFieldName() methods? + java.lang.reflect.Field f[] = ourClass.getFields(); + boolean hasOID=false; + int oidFIELD=-1; - boolean hasOID=false; - int oidFIELD=-1; - StringBuffer sb = new StringBuffer("select"); - char sep=' '; + StringBuffer sb = new StringBuffer("select"); + char sep=' '; + // build a select for the fields. Look for the oid field to use in the where + for(int i=0;i @@ -179,114 +261,116 @@ public class Serialize * @return oid of stored object * @exception SQLException on error */ - public int store(Object o) throws SQLException - { - try { - // NB: we use java.lang.reflect here to prevent confusion with - // the org.postgresql.Field + public int store(Object o) throws SQLException + { + try { + // NB: we use java.lang.reflect here to prevent confusion with + // the org.postgresql.Field - // don't save private fields since we would not be able to fetch them - java.lang.reflect.Field f[] = ourClass.getFields(); + // don't save private fields since we would not be able to fetch them + java.lang.reflect.Field f[] = ourClass.getFields(); - boolean hasOID=false; - int oidFIELD=-1; - boolean update=false; + boolean hasOID=false; + int oidFIELD=-1; + boolean update=false; - // Find out if we have an oid value - for(int i=0;i 0; + } + } - // We are an update if oid != 0 - update = f[i].getInt(o)>0; + StringBuffer sb = new StringBuffer(update?"update "+tableName+" set":"insert into "+tableName+" "); + char sep=update?' ':'('; + for(int i=0;i -1) { StringBuffer buf = new StringBuffer(); StringTokenizer tok = new StringTokenizer(s, "'"); @@ -294,25 +378,24 @@ public class Serialize if (idx > 0) buf.append(tok.nextToken()); while(tok.hasMoreTokens()) - buf.append("\\'").append(tok.nextToken()); + buf.append("''").append(tok.nextToken()); s = buf.toString(); } - // if the string has newlines in it convert them to \n - if ((idx = s.indexOf("\n")) > -1) { + // if the string has backslashes in it escape them them as \\ + if ((idx = s.indexOf("\\")) > -1) { StringBuffer buf = new StringBuffer(); - StringTokenizer tok = new StringTokenizer(s, "\n"); + StringTokenizer tok = new StringTokenizer(s, "\\"); if (idx > 0) buf.append(tok.nextToken()); while(tok.hasMoreTokens()) - buf.append("\\n").append(tok.nextToken()); + buf.append("\\\\").append(tok.nextToken()); s = buf.toString(); } return s; - } /** @@ -336,62 +419,62 @@ public class Serialize * @param o Class to base table on * @exception SQLException on error */ - public static void create(org.postgresql.Connection con,Class c) throws SQLException - { - if(c.isInterface()) - throw new PSQLException("postgresql.serial.interface"); + public static void create(org.postgresql.Connection con,Class c) throws SQLException + { + if(c.isInterface()) throw new PSQLException("postgresql.serial.interface"); - // See if the table exists - String tableName = toPostgreSQL(c.getName()); + // See if the table exists + String tableName = toPostgreSQL(c.getName()); - ResultSet rs = con.ExecSQL("select relname from pg_class where relname = '"+tableName+"'"); - if(!rs.next()) { -// DriverManager.println("found "+rs.getString(1)); - // No entries returned, so the table doesn't exist + ResultSet rs = con.ExecSQL("select relname from pg_class where relname = '"+tableName+"'"); + if( rs.next() ) { + DriverManager.println("Serialize.create: table "+tableName+" exists, skipping"); + rs.close(); + return; + } - StringBuffer sb = new StringBuffer("create table "); - sb.append(tableName); - char sep='('; + // else table not found, so create it + DriverManager.println("Serialize.create: table " + tableName + " not found, creating" ); + // No entries returned, so the table doesn't exist -// java.lang.reflect.Field[] fields = c.getDeclaredFields(); - java.lang.reflect.Field[] fields = c.getFields(); - for(int i=0;i-1) throw new PSQLException("postgresql.serial.underscore"); - // Postgres table names can only be 32 character long + // Postgres table names can only be 32 character long. + // Reserve 1 char, so allow only up to 31 chars. // If the full class name with package is too long // then just use the class name. If the class name is // too long throw an exception. - if(name.length() > 32) { + // + if( name.length() > 31 ) { name = name.substring(name.lastIndexOf(".") + 1); - - if(name.length()>32) + if( name.length() >31 ) throw new PSQLException("postgresql.serial.namelength",name,new Integer(name.length())); } - return name.replace('.','_'); }