[en] Solution to Postgres JDBC driver ignoring charSet directive with SQL_ASCII database

New” Post­gres JDBC dri­vers check data­ba­se engi­ne ver­sion and if it’s not below 7.3 they will igno­re char­Set dec­la­ra­tion. It bre­aks enco­ding of 8‑bit cha­rac­ters if data­ba­se has SQL_ASCII enco­ding (which basi­cal­ly means DONT_CARE_ANYTHING_GOES). If your data­ba­se is set to SQL_ASCII and in reali­ty it sto­res e.g. Win­dows-1250 (CP1250) cha­rac­ters, JDBC dri­ver will assu­me incor­rect enco­ding and dia­cri­ti­cal cha­rac­ters will be broken.

Even tho­ugh I under­stand the reasons given by Post­gres deve­lo­pers for this (SQL_ASCII is NOT sup­po­sed to be used with any­thing other than 7‑bit cha­rac­ters and it is obso­le­te), the solu­tion they­’re sug­ge­sting (to dump the db, and convert it to UTF‑8) is not always possi­ble. For instan­ce, I would have a hard time tel­ling my custo­mer to do it, espe­cial­ly becau­se the­ir old softwa­re might break.

So I’ve pat­ched the JDBC dri­ver to accept char­Set direc­ti­ve in JDBC URLs and also added a method set­Char­set to PGSimpleDataSource.

Here you can down­lo­ad it: Post­gres JDBC dri­ver — char­Set patched

5 komentarz do “[en] Solution to Postgres JDBC driver ignoring charSet directive with SQL_ASCII database

  1. I down­lo­aded your pat­ched Post­gre­SQL JDBC dri­ver, and used it with my old SQL_ASCII data­ba­se. Eve­ry­thing wor­ked gre­at!! Thank you very much!! Any chan­ce you could post or send me the sour­ce code with your modi­fi­ca­tions? Thanks in advance.

  2. Hi Edu­ar­do,

    I’m glad it works for you. I’ve only pat­ched 3 files, here goes the diff:

    # This patch file was generated by NetBeans IDE
    # This patch can be applied using context Tools: Apply Diff Patch action on respective folder.
    # It uses platform neutral UTF-8 encoding.
    # Above lines and this line are ignored by the patching process.
    Index: pgjdbc/org/postgresql/core/ConnectionFactory.java
    --- pgjdbc/org/postgresql/core/ConnectionFactory.java Base (1.10)
    +++ pgjdbc/org/postgresql/core/ConnectionFactory.java Locally Modified (Based On 1.10)
    @@ -30,7 +30,7 @@
          * connection is returned.
          */
         private static final Object[][] versions = {
    -                { "3", new org.postgresql.core.v3.ConnectionFactoryImpl() },
    +//                { "3", new org.postgresql.core.v3.ConnectionFactoryImpl() },
                     { "2", new org.postgresql.core.v2.ConnectionFactoryImpl() },
                 };
     
    Index: pgjdbc/org/postgresql/core/v2/ConnectionFactoryImpl.java
    --- pgjdbc/org/postgresql/core/v2/ConnectionFactoryImpl.java Base (1.24)
    +++ pgjdbc/org/postgresql/core/v2/ConnectionFactoryImpl.java Locally Modified (Based On 1.24)
    @@ -395,29 +395,29 @@
     
             protoConnection.setServerVersion(dbVersion);
     
    -        if (dbVersion.compareTo("7.3") >= 0)
    -        {
    -            // set encoding to be unicode; set datestyle; ensure autocommit is on
    -            // (no-op on 7.4, but might be needed under 7.3)
    -            // The begin/commit is to avoid leaving a transaction open if we're talking to a
    -            // 7.3 server that defaults to autocommit = off.
    -
    -            if (logger.logDebug())
    -                logger.debug("Switching to UTF8 client_encoding");
    -
    -            String sql = "begin; set autocommit = on; set client_encoding = 'UTF8'; ";
    -            if (dbVersion.compareTo("9.0") >= 0) {
    -                sql += "SET extra_float_digits=3; ";
    -            } else if (dbVersion.compareTo("7.4") >= 0) {
    -                sql += "SET extra_float_digits=2; ";
    -            }
    -            sql += "commit";
    -
    -            SetupQueryRunner.run(protoConnection, sql, false);
    -            protoConnection.setEncoding(Encoding.getDatabaseEncoding("UTF8"));
    -        }
    -        else
    -        {
    +//        if (dbVersion.compareTo("7.3") >= 0)
    +//        {
    +//            // set encoding to be unicode; set datestyle; ensure autocommit is on
    +//            // (no-op on 7.4, but might be needed under 7.3)
    +//            // The begin/commit is to avoid leaving a transaction open if we're talking to a
    +//            // 7.3 server that defaults to autocommit = off.
    +//
    +//            if (logger.logDebug())
    +//                logger.debug("Switching to UTF8 client_encoding");
    +//
    +//            String sql = "begin; set autocommit = on; set client_encoding = 'UTF8'; ";
    +//            if (dbVersion.compareTo("9.0") >= 0) {
    +//                sql += "SET extra_float_digits=3; ";
    +//            } else if (dbVersion.compareTo("7.4") >= 0) {
    +//                sql += "SET extra_float_digits=2; ";
    +//            }
    +//            sql += "commit";
    +//
    +//            SetupQueryRunner.run(protoConnection, sql, false);
    +//            protoConnection.setEncoding(Encoding.getDatabaseEncoding("UTF8"));
    +//        }
    +//        else
    +//        {
                 String charSet = info.getProperty("charSet");
                 String dbEncoding = (results[1] == null ? null : protoConnection.getEncoding().decode(results[1]));
                 if (logger.logDebug())
    @@ -442,7 +442,7 @@
                     // XXX is this ever reached?
                     protoConnection.setEncoding(Encoding.defaultEncoding());
                 }
    -        }
    +//        }
     
             if (logger.logDebug())
                 logger.debug("Connection encoding (using JVM's nomenclature): " + protoConnection.getEncoding());
    Index: pgjdbc/org/postgresql/ds/common/BaseDataSource.java
    --- pgjdbc/org/postgresql/ds/common/BaseDataSource.java Base (1.25)
    +++ pgjdbc/org/postgresql/ds/common/BaseDataSource.java Locally Modified (Based On 1.25)
    @@ -48,6 +48,7 @@
         private String databaseName;
         private String user;
         private String password;
    +    private String charset;
         private int portNumber = 0;
         private int prepareThreshold = 5;
         private int unknownLength = Integer.MAX_VALUE;
    @@ -484,6 +485,9 @@
             if (compatible != null) {
                 sb.append("&compatible="+compatible);
             }
    +        if (charset != null) {
    +            sb.append("&charSet="+charset);
    +        }
             if (applicationName != null) {
                 sb.append("&ApplicationName=");
                 sb.append(applicationName);
    @@ -537,6 +541,10 @@
             {
                 ref.add(new StringRefAddr("compatible", compatible));
             }
    +        if (charset != null)
    +        {
    +            ref.add(new StringRefAddr("charset", charset));
    +        }
     
             ref.add(new StringRefAddr("logLevel", Integer.toString(logLevel)));
             ref.add(new StringRefAddr("protocolVersion", Integer.toString(protocolVersion)));
    @@ -566,6 +574,7 @@
             out.writeBoolean(binaryTransfer);
             out.writeObject(binaryTransferEnable);
             out.writeObject(binaryTransferDisable);
    +        out.writeObject(charset);
         }
     
         protected void readBaseObject(ObjectInputStream in) throws IOException, ClassNotFoundException
    @@ -589,6 +598,7 @@
             binaryTransfer = in.readBoolean();
             binaryTransferEnable = (String)in.readObject();
             binaryTransferDisable = (String)in.readObject();
    +        charset = (String)in.readObject();
         }
     
         public void initializeFrom(BaseDataSource source) throws IOException, ClassNotFoundException {
    @@ -601,4 +611,12 @@
             readBaseObject(ois);
         }
     
    +    public String getCharset() {
    +        return charset;
     }
    +
    +    public void setCharset(String charset) {
    +        this.charset = charset;
    +    }
    +
    +}
    
  3. This has been tre­men­do­usly help­ful, as recen­tly Post­gres (> 9.4) deci­ded to throw an error for non-utf‑8 cha­rac­ters. With our lega­cy db, this would not work with JDBC insi­sting on UTF‑8.

    Thanks aga­in for the patch.

  4. Hi,

    Looks like when ente­ring UTF‑8 strings thro­ugh this dri­ver using preparedStatement.setString method, it is mes­sing up the strings. Put­ting T?kia inste­ad of Tëkia, e.g.,

    Any tho­ughts?

    Thanks.

  5. Well, it’s been 6 years sin­ce I wor­ked on it, so I don’t remem­ber too much 🙁 I only did SELECT * queries with pat­ched dri­ver (my use case was to mir­ror the DB to some pla­ce and work from the­re), so I did­n’t even try pre­pa­red sta­te­ments. I’d guess the­re­’s a reason Post­gres devs aban­do­ned the option I resto­red with this patch. But I’m afra­id the­re won’t be an easy solu­tion for You unless You take JDBC dri­ve­r’s sour­ce code, patch it (diff is in one on my pre­vio­us com­ments) and debug it Your­self. It’s not that dif­fi­cult once You start with it 🙂

Możliwość komentowania została wyłączona.