aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Momjian <bruce@momjian.us>2000-09-12 04:58:50 +0000
committerBruce Momjian <bruce@momjian.us>2000-09-12 04:58:50 +0000
commit65edb541865032b5750cfe58cb8f7affbe1fc298 (patch)
tree6f7c1aae279e75e5be4dceae692c8d3b2c9386a4
parent4f5cdadf03ae97de4fbd9ef90e15a080291b6a13 (diff)
downloadpostgresql-65edb541865032b5750cfe58cb8f7affbe1fc298.tar.gz
postgresql-65edb541865032b5750cfe58cb8f7affbe1fc298.zip
Attached are a patch to allow the charset encoding used by the JDBC
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
-rw-r--r--src/interfaces/jdbc/Makefile7
-rw-r--r--src/interfaces/jdbc/example/Unicode.java240
-rw-r--r--src/interfaces/jdbc/org/postgresql/Connection.java41
-rw-r--r--src/interfaces/jdbc/org/postgresql/Driver.java10
-rw-r--r--src/interfaces/jdbc/org/postgresql/PG_Stream.java20
-rw-r--r--src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java11
-rw-r--r--src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java11
7 files changed, 328 insertions, 12 deletions
diff --git a/src/interfaces/jdbc/Makefile b/src/interfaces/jdbc/Makefile
index efd71fc2317..445d606ef8f 100644
--- a/src/interfaces/jdbc/Makefile
+++ b/src/interfaces/jdbc/Makefile
@@ -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
index 00000000000..64d7ce8c89e
--- /dev/null
+++ b/src/interfaces/jdbc/example/Unicode.java
@@ -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();
+ }
+}
diff --git a/src/interfaces/jdbc/org/postgresql/Connection.java b/src/interfaces/jdbc/org/postgresql/Connection.java
index 539faecd3e0..33834118bda 100644
--- a/src/interfaces/jdbc/org/postgresql/Connection.java
+++ b/src/interfaces/jdbc/org/postgresql/Connection.java
@@ -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.
diff --git a/src/interfaces/jdbc/org/postgresql/Driver.java b/src/interfaces/jdbc/org/postgresql/Driver.java
index 779c58eb008..d5a5c8d24fe 100644
--- a/src/interfaces/jdbc/org/postgresql/Driver.java
+++ b/src/interfaces/jdbc/org/postgresql/Driver.java
@@ -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>
diff --git a/src/interfaces/jdbc/org/postgresql/PG_Stream.java b/src/interfaces/jdbc/org/postgresql/PG_Stream.java
index 35bd6df364b..22c41bdb3a3 100644
--- a/src/interfaces/jdbc/org/postgresql/PG_Stream.java
+++ b/src/interfaces/jdbc/org/postgresql/PG_Stream.java
@@ -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;
}
diff --git a/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java b/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java
index 497e401bde8..e65cf80b55b 100644
--- a/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java
+++ b/src/interfaces/jdbc/org/postgresql/jdbc1/ResultSet.java
@@ -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);
+ }
+ }
}
/**
diff --git a/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java b/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java
index 85cb3c1f434..e9f6c79f41b 100644
--- a/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java
+++ b/src/interfaces/jdbc/org/postgresql/jdbc2/ResultSet.java
@@ -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);
+ }
+ }
}
/**