diff --git a/src/interfaces/jdbc/org/postgresql/util/Serialize.java b/src/interfaces/jdbc/org/postgresql/util/Serialize.java index 3af43e6eb840e8a93391eea6411ce02b0fbc2b38..a89fba5baf387523070ba1ba41fa3f70654a2d58 100644 --- a/src/interfaces/jdbc/org/postgresql/util/Serialize.java +++ b/src/interfaces/jdbc/org/postgresql/util/Serialize.java @@ -23,16 +23,16 @@ public class Serialize { // This is the connection that the instance refers to protected org.postgresql.Connection conn; - + // This is the table name protected String tableName; - + // This is the class name protected String className; - + // This is the Class for this serialzed object protected Class ourClass; - + /** * This creates an instance that can be used to serialize or deserialize * a Java object from a PostgreSQL table. @@ -41,16 +41,16 @@ public class Serialize { try { conn = c; - tableName = type.toLowerCase(); - className = toClassName(type); + tableName = toPostgreSQL(type); + className = type; ourClass = Class.forName(className); } catch(ClassNotFoundException cnfe) { throw new PSQLException("postgresql.serial.noclass",type); } - + // Second check, the type must be a table boolean status = false; - ResultSet rs = conn.ExecSQL("select typname from pg_type,pg_class where typname=relname and typname='"+type+"'"); + 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; @@ -59,10 +59,26 @@ public class Serialize // This should never occur, as org.postgresql has it's own internal checks if(!status) throw new PSQLException("postgresql.serial.table",type); - + // Finally cache the fields within the table } - + + /** + * Constructor when Object is passed in + */ + public Serialize(org.postgresql.Connection c,Object o) throws SQLException + { + this(c, o.getClass().getName()); + } + + /** + * Constructor when Class is passed in + */ + public Serialize(org.postgresql.Connection c, Class cls) throws SQLException + { + this(c, cls.getName()); + } + /** * This fetches an object from a table, given it's OID * @param oid The oid of the object @@ -73,14 +89,20 @@ public class Serialize { try { Object obj = ourClass.newInstance(); - + // NB: we use java.lang.reflect here to prevent confusion with // the org.postgresql.Field - java.lang.reflect.Field f[] = ourClass.getDeclaredFields(); + + // 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; 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<f.length;i++) { String n = f[i].getName(); if(n.equals("oid")) { @@ -95,13 +117,37 @@ public class Serialize sb.append(tableName); sb.append(" where oid="); sb.append(oid); - + DriverManager.println("store: "+sb.toString()); ResultSet rs = conn.ExecSQL(sb.toString()); if(rs!=null) { if(rs.next()) { for(int i=0;i<f.length;i++) { - f[i].set(obj,rs.getObject(i+1)); + if ( !Modifier.isFinal(f[i].getModifiers()) ) { + + if (f[i].getType().getName().equals("short")){ + f[i].setShort(obj, rs.getShort(i+1)); + } + else + if (f[i].getType().getName().equals("char")){ + f[i].setChar(obj, rs.getString(i+1).toCharArray()[0]); + } + else + if (f[i].getType().getName().equals("byte")){ + f[i].setByte(obj, rs.getByte(i+1)); + } + else + // booleans come out of pgsql as a t or an f + if (f[i].getType().getName().equals("boolean")){ + if ( rs.getString(i+1).equals("t")) + f[i].setBoolean(obj, true); + else + f[i].setBoolean(obj, false); + } + else{ + f[i].set(obj,rs.getObject(i+1)); + } + } } } rs.close(); @@ -114,7 +160,7 @@ public class Serialize throw new SQLException(ie.toString()); } } - + /** * This stores an object into a table, returning it's OID.<p> * @@ -138,24 +184,27 @@ public class Serialize try { // NB: we use java.lang.reflect here to prevent confusion with // the org.postgresql.Field - java.lang.reflect.Field f[] = ourClass.getDeclaredFields(); + + // 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; - + // Find out if we have an oid value for(int i=0;i<f.length;i++) { String n = f[i].getName(); if(n.equals("oid")) { hasOID=true; oidFIELD=i; - + // We are an update if oid != 0 update = f[i].getInt(o)>0; } } - - StringBuffer sb = new StringBuffer(update?"update "+tableName+" set":"insert into "+tableName+" values "); + + StringBuffer sb = new StringBuffer(update?"update "+tableName+" set":"insert into " + tableName); char sep=update?' ':'('; for(int i=0;i<f.length;i++) { String n = f[i].getName(); @@ -164,49 +213,108 @@ public class Serialize sep=','; if(update) { sb.append('='); - if(f[i].getType().getName().equals("java.lang.String")) { + // handle unset values + if (f[i].get(o) == null) + sb.append("null"); + else + if(f[i].getType().getName().equals("java.lang.String") || + f[i].getType().getName().equals("char")) { sb.append('\''); - sb.append(f[i].get(o).toString()); + // don't allow single qoutes or newlines in the string + sb.append(fixString(f[i].get(o).toString())); sb.append('\''); } else sb.append(f[i].get(o).toString()); } } - + if(!update) { sb.append(") values "); sep='('; for(int i=0;i<f.length;i++) { - String n = f[i].getName(); - if(f[i].getType().getName().equals("java.lang.String")) { + sb.append(sep); + sep=','; + // handle unset values + if (f[i].get(o) == null) + sb.append("null"); + else + if(f[i].getType().getName().equals("java.lang.String") || + f[i].getType().getName().equals("char")) { sb.append('\''); - sb.append(f[i].get(o).toString()); + // don't allow single quotes or newlines in the string + sb.append(fixString(f[i].get(o).toString())); sb.append('\''); } else sb.append(f[i].get(o).toString()); } sb.append(')'); } - + DriverManager.println("store: "+sb.toString()); - ResultSet rs = conn.ExecSQL(sb.toString()); - if(rs!=null) { - rs.close(); - } - + org.postgresql.ResultSet rs = (org.postgresql.ResultSet)conn.ExecSQL(sb.toString()); + // fetch the OID for returning int oid=0; if(hasOID) { - // set the oid in the object + // If an update use the existing oid in the object f[oidFIELD].setInt(o,oid); } + else { + String statStr = rs.getStatusString(); + oid = Integer.parseInt(statStr.substring(statStr.indexOf(" ") + 1, statStr.lastIndexOf(" "))); + } + + if(rs!=null) { + rs.close(); + } + return oid; - + } catch(IllegalAccessException iae) { throw new SQLException(iae.toString()); } } - + + /** + * + */ + private String fixString(String s) { + + int idx = -1; + + // handle null + if (s == null) + return ""; + + // if the string has single quotes in it escape them + if ((idx = s.indexOf("'")) > -1) { + StringBuffer buf = new StringBuffer(); + StringTokenizer tok = new StringTokenizer(s, "'"); + // handle quote as 1St charater + if (idx > 0) buf.append(tok.nextToken()); + + while(tok.hasMoreTokens()) + 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) { + StringBuffer buf = new StringBuffer(); + StringTokenizer tok = new StringTokenizer(s, "\n"); + if (idx > 0) buf.append(tok.nextToken()); + + while(tok.hasMoreTokens()) + buf.append("\\n").append(tok.nextToken()); + + s = buf.toString(); + } + + return s; + + } + /** * This method is not used by the driver, but it creates a table, given * a Serializable Java Object. It should be used before serializing any @@ -219,7 +327,7 @@ public class Serialize { create(con,o.getClass()); } - + /** * This method is not used by the driver, but it creates a table, given * a Serializable Java Object. It should be used before serializing any @@ -232,49 +340,50 @@ public class Serialize { if(c.isInterface()) throw new PSQLException("postgresql.serial.interface"); - + // 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)); +// DriverManager.println("found "+rs.getString(1)); // No entries returned, so the table doesn't exist - + StringBuffer sb = new StringBuffer("create table "); sb.append(tableName); char sep='('; - - java.lang.reflect.Field[] fields = c.getDeclaredFields(); + +// java.lang.reflect.Field[] fields = c.getDeclaredFields(); + java.lang.reflect.Field[] fields = c.getFields(); for(int i=0;i<fields.length;i++) { Class type = fields[i].getType(); - + // oid is a special field if(!fields[i].getName().equals("oid")) { sb.append(sep); sb.append(fields[i].getName()); sb.append(' '); sep=','; - + if(type.isArray()) { // array handling } else { // convert the java type to org.postgresql, recursing if a class // is found - String n = fields[i].getType().getName(); + String n = type.getName(); int j=0; for(;j<tp.length && !tp[j][0].equals(n);j++); if(j<tp.length) sb.append(tp[j][1]); else { - create(con,fields[i].getType()); + create(con, type); sb.append(toPostgreSQL(n)); } } } } sb.append(")"); - + // Now create the table DriverManager.println("Serialize.create:"+sb); con.ExecSQL(sb.toString()); @@ -283,22 +392,26 @@ public class Serialize DriverManager.println("Serialize.create: table "+tableName+" exists, skipping"); } } - + // This is used to translate between Java primitives and PostgreSQL types. private static final String tp[][] = { - {"boolean", "int1"}, +// {"boolean", "int1"}, + {"boolean", "bool"}, {"double", "float8"}, {"float", "float4"}, {"int", "int4"}, - {"long", "int4"}, +// {"long", "int4"}, + {"long", "int8"}, {"short", "int2"}, {"java.lang.String", "text"}, {"java.lang.Integer", "int4"}, {"java.lang.Float", "float4"}, {"java.lang.Double", "float8"}, - {"java.lang.Short", "int2"} + {"java.lang.Short", "int2"}, + {"char", "char"}, + {"byte", "int2"} }; - + /** * This converts a Java Class name to a org.postgresql table, by replacing . with * _<p> @@ -314,17 +427,25 @@ public class Serialize public static String toPostgreSQL(String name) throws SQLException { name = name.toLowerCase(); - + if(name.indexOf("_")>-1) throw new PSQLException("postgresql.serial.underscore"); - - if(name.length()>32) - throw new PSQLException("postgresql.serial.namelength",name,new Integer(name.length())); - + + // Postgres table names can only be 32 character long + // 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) { + name = name.substring(name.lastIndexOf(".") + 1); + + if(name.length()>32) + throw new PSQLException("postgresql.serial.namelength",name,new Integer(name.length())); + } + return name.replace('.','_'); } - - + + /** * This converts a org.postgresql table to a Java Class name, by replacing _ with * .<p> @@ -338,5 +459,5 @@ public class Serialize name = name.toLowerCase(); return name.replace('_','.'); } - + }