mirror of
https://github.com/postgres/postgres.git
synced 2025-04-25 21:42:33 +03:00
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
This commit is contained in:
parent
4f5cdadf03
commit
65edb54186
@ -4,7 +4,7 @@
|
|||||||
# Makefile for Java JDBC interface
|
# Makefile for Java JDBC interface
|
||||||
#
|
#
|
||||||
# IDENTIFICATION
|
# 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
|
# These are really test classes not true examples
|
||||||
TESTS= example/metadata.class \
|
TESTS= example/metadata.class \
|
||||||
example/threadsafe.class
|
example/threadsafe.class \
|
||||||
|
example/Unicode.class
|
||||||
|
|
||||||
# Non functional/obsolete examples
|
# Non functional/obsolete examples
|
||||||
# example/datestyle.class \
|
# example/datestyle.class \
|
||||||
@ -266,6 +267,7 @@ tests: $(TESTS)
|
|||||||
@echo The following tests have been built:
|
@echo The following tests have been built:
|
||||||
@echo " example.metadata Tests various metadata methods"
|
@echo " example.metadata Tests various metadata methods"
|
||||||
@echo " example.threadsafe Tests the driver's thread safety"
|
@echo " example.threadsafe Tests the driver's thread safety"
|
||||||
|
@echo " example.Unicode Tests unicode charset support"
|
||||||
@echo ------------------------------------------------------------
|
@echo ------------------------------------------------------------
|
||||||
@echo
|
@echo
|
||||||
|
|
||||||
@ -276,6 +278,7 @@ example/psql.class: example/psql.java
|
|||||||
example/ImageViewer.class: example/ImageViewer.java
|
example/ImageViewer.class: example/ImageViewer.java
|
||||||
example/threadsafe.class: example/threadsafe.java
|
example/threadsafe.class: example/threadsafe.java
|
||||||
example/metadata.class: example/metadata.java
|
example/metadata.class: example/metadata.java
|
||||||
|
example/Unicode.class: example/Unicode.java
|
||||||
|
|
||||||
#######################################################################
|
#######################################################################
|
||||||
#
|
#
|
||||||
|
240
src/interfaces/jdbc/example/Unicode.java
Normal file
240
src/interfaces/jdbc/example/Unicode.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ import org.postgresql.largeobject.*;
|
|||||||
import org.postgresql.util.*;
|
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
|
* This abstract class is used by org.postgresql.Driver to open either the JDBC1 or
|
||||||
* JDBC2 versions of the Connection class.
|
* JDBC2 versions of the Connection class.
|
||||||
@ -30,6 +30,14 @@ public abstract class Connection
|
|||||||
private String PG_PASSWORD;
|
private String PG_PASSWORD;
|
||||||
private String PG_DATABASE;
|
private String PG_DATABASE;
|
||||||
private boolean PG_STATUS;
|
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_OK = true;
|
||||||
public boolean CONNECTION_BAD = false;
|
public boolean CONNECTION_BAD = false;
|
||||||
@ -111,6 +119,8 @@ public abstract class Connection
|
|||||||
PG_PORT = port;
|
PG_PORT = port;
|
||||||
PG_HOST = new String(host);
|
PG_HOST = new String(host);
|
||||||
PG_STATUS = CONNECTION_BAD;
|
PG_STATUS = CONNECTION_BAD;
|
||||||
|
|
||||||
|
encoding = info.getProperty("charSet"); // could be null
|
||||||
|
|
||||||
// Now make the initial connection
|
// Now make the initial connection
|
||||||
try
|
try
|
||||||
@ -154,7 +164,8 @@ public abstract class Connection
|
|||||||
// The most common one to be thrown here is:
|
// The most common one to be thrown here is:
|
||||||
// "User authentication failed"
|
// "User authentication failed"
|
||||||
//
|
//
|
||||||
throw new SQLException(pg_stream.ReceiveString(4096));
|
throw new SQLException(pg_stream.ReceiveString
|
||||||
|
(4096, getEncoding()));
|
||||||
|
|
||||||
case 'R':
|
case 'R':
|
||||||
// Get the type of request
|
// Get the type of request
|
||||||
@ -224,7 +235,8 @@ public abstract class Connection
|
|||||||
break;
|
break;
|
||||||
case 'E':
|
case 'E':
|
||||||
case 'N':
|
case 'N':
|
||||||
throw new SQLException(pg_stream.ReceiveString(4096));
|
throw new SQLException(pg_stream.ReceiveString
|
||||||
|
(4096, getEncoding()));
|
||||||
default:
|
default:
|
||||||
throw new PSQLException("postgresql.con.setup");
|
throw new PSQLException("postgresql.con.setup");
|
||||||
}
|
}
|
||||||
@ -313,7 +325,7 @@ public abstract class Connection
|
|||||||
|
|
||||||
Field[] fields = null;
|
Field[] fields = null;
|
||||||
Vector tuples = new Vector();
|
Vector tuples = new Vector();
|
||||||
byte[] buf = new byte[sql.length()];
|
byte[] buf = null;
|
||||||
int fqp = 0;
|
int fqp = 0;
|
||||||
boolean hfr = false;
|
boolean hfr = false;
|
||||||
String recv_status = null, msg;
|
String recv_status = null, msg;
|
||||||
@ -325,6 +337,18 @@ public abstract class Connection
|
|||||||
// larger than 8K. Peter June 6 2000
|
// larger than 8K. Peter June 6 2000
|
||||||
//if (sql.length() > 8192)
|
//if (sql.length() > 8192)
|
||||||
//throw new PSQLException("postgresql.con.toolong",sql);
|
//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
|
try
|
||||||
{
|
{
|
||||||
pg_stream.SendChar('Q');
|
pg_stream.SendChar('Q');
|
||||||
@ -513,6 +537,15 @@ public abstract class Connection
|
|||||||
{
|
{
|
||||||
return PG_USER;
|
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.
|
* This returns the Fastpath API for the current connection.
|
||||||
|
@ -90,7 +90,15 @@ public class Driver implements java.sql.Driver
|
|||||||
* <p>The java.util.Properties argument can be used to pass arbitrary
|
* <p>The java.util.Properties argument can be used to pass arbitrary
|
||||||
* string tag/value pairs as connection arguments. Normally, at least
|
* string tag/value pairs as connection arguments. Normally, at least
|
||||||
* "user" and "password" properties should be included in the
|
* "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:
|
* Our protocol takes the forms:
|
||||||
* <PRE>
|
* <PRE>
|
||||||
|
@ -235,17 +235,22 @@ public class PG_Stream
|
|||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String ReceiveString(int maxsize) throws SQLException {
|
||||||
|
return ReceiveString(maxsize, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receives a null-terminated string from the backend. Maximum of
|
* Receives a null-terminated string from the backend. Maximum of
|
||||||
* maxsiz bytes - if we don't see a null, then we assume something
|
* maxsiz bytes - if we don't see a null, then we assume something
|
||||||
* has gone wrong.
|
* 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
|
* @return string from back end
|
||||||
* @exception SQLException if an I/O error occurs
|
* @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];
|
byte[] rst = new byte[maxsiz];
|
||||||
int s = 0;
|
int s = 0;
|
||||||
@ -267,7 +272,16 @@ public class PG_Stream
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new PSQLException("postgresql.stream.ioerror",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;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +163,16 @@ public class ResultSet extends org.postgresql.ResultSet implements java.sql.Resu
|
|||||||
wasNullFlag = (this_row[columnIndex - 1] == null);
|
wasNullFlag = (this_row[columnIndex - 1] == null);
|
||||||
if(wasNullFlag)
|
if(wasNullFlag)
|
||||||
return null;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -164,7 +164,16 @@ public class ResultSet extends org.postgresql.ResultSet implements java.sql.Resu
|
|||||||
wasNullFlag = (this_row[columnIndex - 1] == null);
|
wasNullFlag = (this_row[columnIndex - 1] == null);
|
||||||
if(wasNullFlag)
|
if(wasNullFlag)
|
||||||
return null;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user