OSDN Git Service

Attached are a patch to allow the charset encoding used by the JDBC
authorBruce Momjian <bruce@momjian.us>
Tue, 12 Sep 2000 04:58:50 +0000 (04:58 +0000)
committerBruce Momjian <bruce@momjian.us>
Tue, 12 Sep 2000 04:58:50 +0000 (04:58 +0000)
driver to be set, and a description of said patch.  Please refer to
the latter for more information.

William
--
William Webber                               william@peopleweb.net.au

src/interfaces/jdbc/Makefile
src/interfaces/jdbc/example/Unicode.java [new file with mode: 0644]
src/interfaces/jdbc/org/postgresql/Connection.java
src/interfaces/jdbc/org/postgresql/Driver.java
src/interfaces/jdbc/org/postgresql/PG_Stream.java
src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java
src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java

index efd71fc..445d606 100644 (file)
@@ -4,7 +4,7 @@
 #    Makefile for Java JDBC interface
 #
 # IDENTIFICATION
-#    $Id: Makefile,v 1.23 2000/06/06 11:05:56 peter Exp $
+#    $Id: Makefile,v 1.24 2000/09/12 04:58:46 momjian Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -226,7 +226,8 @@ EX2=        example/blobtest.class
 
 # These are really test classes not true examples
 TESTS= example/metadata.class \
-       example/threadsafe.class
+       example/threadsafe.class \
+       example/Unicode.class
 
 # Non functional/obsolete examples
 #      example/datestyle.class \
@@ -266,6 +267,7 @@ tests:      $(TESTS)
        @echo The following tests have been built:
        @echo "  example.metadata     Tests various metadata methods"
        @echo "  example.threadsafe   Tests the driver's thread safety"
+       @echo "  example.Unicode      Tests unicode charset support"
        @echo ------------------------------------------------------------
        @echo
 
@@ -276,6 +278,7 @@ example/psql.class:                 example/psql.java
 example/ImageViewer.class:             example/ImageViewer.java
 example/threadsafe.class:              example/threadsafe.java
 example/metadata.class:                        example/metadata.java
+example/Unicode.class:                  example/Unicode.java
 
 #######################################################################
 #
diff --git a/src/interfaces/jdbc/example/Unicode.java b/src/interfaces/jdbc/example/Unicode.java
new file mode 100644 (file)
index 0000000..64d7ce8
--- /dev/null
@@ -0,0 +1,240 @@
+package example;
+
+import java.io.*;
+import java.sql.*;
+import java.util.*;
+
+/**
+ *  Test inserting and extracting Unicode-encoded strings.
+ *
+ *  Synopsis:
+ *     example.Unicode <url> <user> <password>
+ *  where <url> must specify an existing database to which <user> and
+ *  <password> give access and which has UNICODE as its encoding.
+ *  (To create a database with UNICODE encoding, you need to compile
+ *  postgres with "--enable-multibyte" and run createdb with the
+ *  flag "-E UNICODE".)
+ *
+ *  This test only produces output on error.
+ *
+ *  @author William Webber <william@live.com.au>
+ */
+public class Unicode {
+    
+    /**
+     *  The url for the database to connect to.
+     */
+    private String url;
+
+    /**
+     *  The user to connect as.
+     */
+    private String user;
+
+    /**
+     *  The password to connect with.
+     */
+    private String password;
+
+    private static void usage() {
+        log("usage: example.Unicode <url> <user> <password>");
+    }
+
+    private static void log(String message) {
+        System.err.println(message);
+    }
+
+    private static void log(String message, Exception e) {
+        System.err.println(message);
+        e.printStackTrace();
+    }
+
+
+    public Unicode(String url, String user, String password) {
+        this.url = url;
+        this.user = user;
+        this.password = password;
+    }
+
+    /**
+     *  Establish and return a connection to the database.
+     */
+    private Connection getConnection() throws SQLException, 
+    ClassNotFoundException {
+        Class.forName("org.postgresql.Driver");
+        Properties info = new Properties();
+        info.put("user", user);
+        info.put("password", password);
+        info.put("charSet", "utf-8");
+        return DriverManager.getConnection(url, info);
+    }
+
+    /**
+     *  Get string representing a block of 256 consecutive unicode characters.
+     *  We exclude the null character, "'",  and "\".
+     */
+    private String getSqlSafeUnicodeBlock(int blockNum) {
+        if (blockNum < 0 || blockNum > 255)
+            throw new IllegalArgumentException("blockNum must be from 0 to "
+                                               + "255: " + blockNum);
+        StringBuffer sb = new StringBuffer(256);
+        int blockFirst = blockNum * 256;
+        int blockLast = blockFirst + 256;
+        for (int i = blockFirst; i < blockLast; i++) {
+            char c = (char) i;
+            if (c == '\0' || c == '\'' || c == '\\')
+                continue;
+            sb.append(c);
+        }
+        return sb.toString();
+    }
+
+    /**
+     *  Is the block a block of valid unicode values.
+     *  d800 to db7f is the "unassigned high surrogate" range.
+     *  db80 to dbff is the "private use" range.
+     *  These should not be used in actual Unicode strings;
+     *  at least, jdk1.2 will not convert them to utf-8.
+     */
+    private boolean isValidUnicodeBlock(int blockNum) {
+        if (blockNum >= 0xd8 && blockNum <= 0xdb)
+            return false;
+        else
+            return true;
+    }
+
+    /**
+     *  Report incorrect block retrieval.
+     */
+    private void reportRetrievalError(int blockNum, String block, 
+                                      String retrieved) {
+        String message = "Block " + blockNum + " returned incorrectly: ";
+        int i = 0;
+        for (i = 0; i < block.length(); i++) {
+            if (i >= retrieved.length()) { 
+                message += "too short";
+                break;
+            } else if (retrieved.charAt(i) != block.charAt(i)) {
+                message += 
+                    "first changed character at position " + i + ", sent as 0x"
+                   + Integer.toHexString((int) block.charAt(i)) 
+                   + ", retrieved as 0x" 
+                   + Integer.toHexString ((int) retrieved.charAt(i));
+                break;
+            }
+        }
+        if (i >= block.length())
+            message += "too long";
+        log(message);
+    }
+        
+    /**
+     *  Do the testing.
+     */
+    public void runTest() {
+        Connection connection = null; 
+        Statement statement = null; 
+        int blockNum = 0;
+        final int CREATE = 0;
+        final int INSERT = 1;
+        final int SELECT = 2;
+        final int LIKE = 3;
+        int mode = CREATE;
+        try {
+            connection = getConnection();
+            statement = connection.createStatement();
+            statement.executeUpdate("CREATE TABLE test_unicode " 
+                                    + "( blockNum INT PRIMARY KEY, "
+                                    + "block TEXT );");
+            mode = INSERT;
+            for (blockNum = 0; blockNum < 256; blockNum++) {
+                if (isValidUnicodeBlock(blockNum)) {
+                    String block = getSqlSafeUnicodeBlock(blockNum);
+                    statement.executeUpdate
+                        ("INSERT INTO test_unicode VALUES ( " + blockNum 
+                         + ", '" + block + "');");
+                }
+            }
+            mode = SELECT;
+            for (blockNum = 0; blockNum < 256; blockNum++) {
+                if (isValidUnicodeBlock(blockNum)) {
+                    String block = getSqlSafeUnicodeBlock(blockNum);
+                    ResultSet rs = statement.executeQuery
+                        ("SELECT block FROM test_unicode WHERE blockNum = "
+                         + blockNum + ";");
+                    if (!rs.next())
+                        log("Could not retrieve block " + blockNum);
+                    else {
+                        String retrieved = rs.getString(1);
+                        if (!retrieved.equals(block)) {
+                            reportRetrievalError(blockNum, block, retrieved);
+                        }
+                    }
+                }
+            }
+            mode = LIKE;
+            for (blockNum = 0; blockNum < 256; blockNum++) {
+                if (isValidUnicodeBlock(blockNum)) {
+                    String block = getSqlSafeUnicodeBlock(blockNum);
+                    String likeString = "%" + 
+                        block.substring(2, block.length() - 3) + "%" ;
+                    ResultSet rs = statement.executeQuery
+                        ("SELECT blockNum FROM test_unicode WHERE block LIKE '"
+                         + likeString + "';");
+                    if (!rs.next())
+                        log("Could get block " + blockNum + " using LIKE");
+                }
+            }
+        } catch (SQLException sqle) {
+            switch (mode) {
+            case CREATE:
+                log("Exception creating database", sqle);
+                break;
+            case INSERT:
+                log("Exception inserting block " + blockNum, sqle);
+                break;
+            case SELECT:
+                log("Exception selecting block " + blockNum, sqle);
+                break;
+            case LIKE: 
+                log("Exception doing LIKE on block " + blockNum, sqle);
+                break;
+            default:
+                log("Exception", sqle);
+                break;
+            }
+        } catch (ClassNotFoundException cnfe) {
+            log("Unable to load driver", cnfe);
+            return;
+        }
+        try {
+            if (statement != null)
+                statement.close();
+            if (connection != null)
+                connection.close();
+        } catch (SQLException sqle) {
+            log("Exception closing connections", sqle);
+        }
+        if (mode > CREATE) {
+            // If the backend gets what it regards as garbage on a connection,
+            // that connection may become unusable.  To be safe, we create
+            // a fresh connection to delete the table.
+            try {
+                connection = getConnection();
+                statement = connection.createStatement();
+                statement.executeUpdate("DROP TABLE test_unicode;");
+            } catch (Exception sqle) {
+                log("*** ERROR: unable to delete test table "
+                    + "test_unicode; must be deleted manually", sqle);
+            }
+        }
+    }
+
+    public static void main(String [] args) {
+        if (args.length != 3) {
+            usage();
+            System.exit(1);
+        }
+        new Unicode(args[0], args[1], args[2]).runTest();
+    }
+}
index 539faec..3383411 100644 (file)
@@ -10,7 +10,7 @@ import org.postgresql.largeobject.*;
 import org.postgresql.util.*;
 
 /**
- * $Id: Connection.java,v 1.4 2000/06/06 11:05:59 peter Exp $
+ * $Id: Connection.java,v 1.5 2000/09/12 04:58:47 momjian Exp $
  *
  * This abstract class is used by org.postgresql.Driver to open either the JDBC1 or
  * JDBC2 versions of the Connection class.
@@ -30,6 +30,14 @@ public abstract class Connection
   private String PG_PASSWORD;
   private String PG_DATABASE;
   private boolean PG_STATUS;
+
+  /**
+   *  The encoding to use for this connection.
+   *  If <b>null</b>, the encoding has not been specified by the
+   *  user, and the default encoding for the platform should be
+   *  used.
+   */
+  private String encoding;
   
   public boolean CONNECTION_OK = true;
   public boolean CONNECTION_BAD = false;
@@ -111,6 +119,8 @@ public abstract class Connection
     PG_PORT = port;
     PG_HOST = new String(host);
     PG_STATUS = CONNECTION_BAD;
+
+    encoding = info.getProperty("charSet");  // could be null
     
     // Now make the initial connection
     try
@@ -154,7 +164,8 @@ public abstract class Connection
                // The most common one to be thrown here is:
                // "User authentication failed"
                //
-               throw new SQLException(pg_stream.ReceiveString(4096));
+               throw new SQLException(pg_stream.ReceiveString
+                                       (4096, getEncoding()));
                
              case 'R':
                // Get the type of request
@@ -224,7 +235,8 @@ public abstract class Connection
           break;
        case 'E':
        case 'N':
-           throw new SQLException(pg_stream.ReceiveString(4096));
+           throw new SQLException(pg_stream.ReceiveString
+                                  (4096, getEncoding()));
         default:
           throw new PSQLException("postgresql.con.setup");
       }
@@ -313,7 +325,7 @@ public abstract class Connection
            
            Field[] fields = null;
            Vector tuples = new Vector();
-           byte[] buf = new byte[sql.length()];
+           byte[] buf = null;
            int fqp = 0;
            boolean hfr = false;
            String recv_status = null, msg;
@@ -325,6 +337,18 @@ public abstract class Connection
            // larger than 8K. Peter June 6 2000
            //if (sql.length() > 8192)
            //throw new PSQLException("postgresql.con.toolong",sql);
+
+        if (getEncoding() == null)
+            buf = sql.getBytes();
+        else {
+            try {
+                buf = sql.getBytes(getEncoding());
+            } catch (UnsupportedEncodingException unse) {
+                 throw new PSQLException("postgresql.con.encoding",
+                                        unse);
+            }
+        }
+
            try
                {
                    pg_stream.SendChar('Q');
@@ -513,6 +537,15 @@ public abstract class Connection
     {
        return PG_USER;
     }
+
+    /**
+     *  Get the character encoding to use for this connection.
+     *  @return the encoding to use, or <b>null</b> for the 
+     *  default encoding.
+     */
+    public String getEncoding() throws SQLException {
+        return encoding;
+    }
     
     /**
      * This returns the Fastpath API for the current connection.
index 779c58e..d5a5c8d 100644 (file)
@@ -90,7 +90,15 @@ public class Driver implements java.sql.Driver
    * <p>The java.util.Properties argument can be used to pass arbitrary
    * string tag/value pairs as connection arguments.  Normally, at least
    * "user" and "password" properties should be included in the 
-   * properties.
+   * properties.  In addition, the "charSet" property can be used to 
+   * set a character set encoding (e.g. "utf-8") other than the platform 
+   * default (typically Latin1).  This is necessary in particular if storing 
+   * multibyte characters in the database.  For a list of supported
+   * character encoding , see 
+   * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html
+   * Note that you will probably want to have set up the Postgres database
+   * itself to use the same encoding, with the "-E <encoding>" argument
+   * to createdb.
    *
    * Our protocol takes the forms:
    * <PRE>
index 35bd6df..22c41bd 100644 (file)
@@ -235,17 +235,22 @@ public class PG_Stream
       }
       return n;
   }
+
+    public String ReceiveString(int maxsize) throws SQLException {
+        return ReceiveString(maxsize, null);
+    }
   
   /**
    * Receives a null-terminated string from the backend.  Maximum of
    * maxsiz bytes - if we don't see a null, then we assume something
    * has gone wrong.
    *
-   * @param maxsiz maximum length of string
+   * @param encoding the charset encoding to use.
+   * @param maxsiz maximum length of string in bytes
    * @return string from back end
    * @exception SQLException if an I/O error occurs
    */
-  public String ReceiveString(int maxsiz) throws SQLException
+  public String ReceiveString(int maxsiz, String encoding) throws SQLException
   {
     byte[] rst = new byte[maxsiz];
     int s = 0;
@@ -267,7 +272,16 @@ public class PG_Stream
       } catch (IOException e) {
        throw new PSQLException("postgresql.stream.ioerror",e);
       }
-      String v = new String(rst, 0, s);
+      String v = null;
+      if (encoding == null)
+          v = new String(rst, 0, s);
+      else {
+          try {
+              v = new String(rst, 0, s, encoding);
+          } catch (UnsupportedEncodingException unse) {
+              throw new PSQLException("postgresql.stream.encoding", unse);
+          }
+      }
       return v;
   }
   
index 497e401..e65cf80 100644 (file)
@@ -163,7 +163,16 @@ public class ResultSet extends org.postgresql.ResultSet implements java.sql.Resu
     wasNullFlag = (this_row[columnIndex - 1] == null);
     if(wasNullFlag)
       return null;
-    return new String(this_row[columnIndex - 1]);
+    String encoding = connection.getEncoding();
+    if (encoding == null)
+        return new String(this_row[columnIndex - 1]);
+    else {
+        try {
+            return new String(this_row[columnIndex - 1], encoding);
+        } catch (UnsupportedEncodingException unse) {
+            throw new PSQLException("postgresql.res.encoding", unse);
+        }
+    }
   }
   
   /**
index 85cb3c1..e9f6c79 100644 (file)
@@ -164,7 +164,16 @@ public class ResultSet extends org.postgresql.ResultSet implements java.sql.Resu
     wasNullFlag = (this_row[columnIndex - 1] == null);
     if(wasNullFlag)
       return null;
-    return new String(this_row[columnIndex - 1]);
+    String encoding = connection.getEncoding();
+    if (encoding == null)
+        return new String(this_row[columnIndex - 1]);
+    else {
+        try {
+            return new String(this_row[columnIndex - 1], encoding);
+        } catch (UnsupportedEncodingException unse) {
+            throw new PSQLException("postgresql.res.encoding", unse);
+        }
+    }
   }
   
   /**