Repository: pgjdbc/pgjdbc
Pull request #780 (2017-03-13 16:19:11)

Honour setLogStream. If the logStream is set
then use it to output logs
Also set the parent logger level to the log level
requested.

codecov-io: # [Codecov](https://codecov.io/gh/pgjdbc/pgjdbc/pull/780?src=pr&el=h1) Report
> Merging [#780](https://codecov.io/gh/pgjdbc/pgjdbc/pull/780?src=pr&el=desc) into [master](https://codecov.io/gh/pgjdbc/pgjdbc/commit/9c80adc24776ae02e0aff419e1cddf26a28eeff0?src=pr&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `0%`.



```diff
@@             Coverage Diff              @@
##             master     #780      +/-   ##
============================================
- Coverage     65.25%   65.23%   -0.02%     
  Complexity     3496     3496              
============================================
  Files           164      164              
  Lines         15125    15131       +6     
  Branches       2450     2452       +2     
============================================
+ Hits           9870     9871       +1     
- Misses         4078     4083       +5     
  Partials       1177     1177
```

------

[Continue to review full report at Codecov](https://codecov.io/gh/pgjdbc/pgjdbc/pull/780?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pgjdbc/pgjdbc/pull/780?src=pr&el=footer). Last update [9c80adc...d21ee3a](https://codecov.io/gh/pgjdbc/pgjdbc/compare/9c80adc24776ae02e0aff419e1cddf26a28eeff0...d21ee3aff5531e6f6d33a9d78f6b206a9441a7df?el=footer&src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).
vlsi: Could you please add a test for the feature?

Patch:
diff --git a/docs/documentation/head/callproc.md b/docs/documentation/head/callproc.md
index c2d91c59..bf5e14a5 100644
--- a/docs/documentation/head/callproc.md
+++ b/docs/documentation/head/callproc.md
@@ -22,7 +22,7 @@ This example shows how to call a PostgreSQL™ built in function, `upper`, which
 simply converts the supplied string argument to uppercase.
 
 ```java
-CallableStatement upperProc = conn.prepareCall("{ ? = call upper( ? ) }");
+CallableStatement upperProc = conn.prepareCall("{? = call upper( ? ) }");
 upperProc.registerOutParameter(1, Types.VARCHAR);
 upperProc.setString(2, "lowercase to uppercase");
 upperProc.execute();
@@ -95,7 +95,7 @@ stmt.close();
 conn.setAutoCommit(false);
 
 // Procedure call.
-CallableStatement proc = conn.prepareCall("{ ? = call refcursorfunc() }");
+CallableStatement proc = conn.prepareCall("{? = call refcursorfunc() }");
 proc.registerOutParameter(1, Types.OTHER);
 proc.execute();
 ResultSet results = (ResultSet) proc.getObject(1);
@@ -116,7 +116,7 @@ you are free to directly use cursor commands on it, such as `FETCH` and `MOVE`.
 
 ```java
 conn.setAutoCommit(false);
-CallableStatement proc = conn.prepareCall("{ ? = call refcursorfunc() }");
+CallableStatement proc = conn.prepareCall("{? = call refcursorfunc() }");
 proc.registerOutParameter(1, Types.OTHER);
 proc.execute();
 String cursorName = proc.getString(1);
diff --git a/pgjdbc/pom.xml b/pgjdbc/pom.xml
index 0d2ba622..f78fa4f6 100644
--- a/pgjdbc/pom.xml
+++ b/pgjdbc/pom.xml
@@ -266,6 +266,7 @@
           </dependencies>
           <configuration>
             <configLocation>src/main/checkstyle/checks.xml</configLocation>
+	    <suppressionsLocation>src/main/checkstyle/suppressions.xml</suppressionsLocation>
             <violationSeverity>error</violationSeverity>
             <failOnViolation>true</failOnViolation>
             <failsOnError>true</failsOnError>
diff --git a/pgjdbc/src/main/checkstyle/suppressions.xml b/pgjdbc/src/main/checkstyle/suppressions.xml
new file mode 100644
index 00000000..76e50708
--- /dev/null
+++ b/pgjdbc/src/main/checkstyle/suppressions.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE suppressions PUBLIC
+        "-//Puppy Crawl//DTD Suppressions 1.1//EN"
+        "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
+
+<suppressions>
+    <suppress files="WriterHandler\.java" checks="RegexpHeader"/>
+</suppressions>
diff --git a/pgjdbc/src/main/java/org/postgresql/Driver.java b/pgjdbc/src/main/java/org/postgresql/Driver.java
index d471f4d9..27c6b486 100644
--- a/pgjdbc/src/main/java/org/postgresql/Driver.java
+++ b/pgjdbc/src/main/java/org/postgresql/Driver.java
@@ -6,12 +6,14 @@
 package org.postgresql;
 
 import org.postgresql.jdbc.PgConnection;
+
 import org.postgresql.util.ExpressionProperties;
 import org.postgresql.util.GT;
 import org.postgresql.util.HostSpec;
 import org.postgresql.util.PSQLException;
 import org.postgresql.util.PSQLState;
 import org.postgresql.util.SharedTimer;
+import org.postgresql.util.WriterHandler;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -29,8 +31,11 @@ import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Properties;
 import java.util.Set;
+import java.util.logging.Formatter;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+import java.util.logging.StreamHandler;
 
 /**
  * The Java SQL framework allows for multiple database drivers. Each driver should supply a class
@@ -308,7 +313,7 @@ public class Driver implements java.sql.Driver {
       loggerHandlerFile = null;
     }
 
-    java.util.logging.Handler handler = new java.util.logging.ConsoleHandler();
+    java.util.logging.Handler handler = null;
     if (driverLogFile != null) {
       try {
         handler = new java.util.logging.FileHandler(driverLogFile);
@@ -318,7 +323,21 @@ public class Driver implements java.sql.Driver {
       }
     }
 
-    handler.setFormatter(new java.util.logging.SimpleFormatter());
+    Formatter formatter = new SimpleFormatter();
+
+    if ( handler == null ) {
+      if (DriverManager.getLogWriter() != null) {
+        handler = new WriterHandler(DriverManager.getLogWriter());
+      } else if ( DriverManager.getLogStream() != null) {
+        handler = new StreamHandler(DriverManager.getLogStream(), formatter);
+      } else {
+        handler = new StreamHandler(System.err, formatter);
+      }
+    } else {
+      handler.setFormatter(formatter);
+    }
+
+    handler.setLevel(PARENT_LOGGER.getLevel());
     PARENT_LOGGER.setUseParentHandlers(false);
     PARENT_LOGGER.addHandler(handler);
   }
diff --git a/pgjdbc/src/main/java/org/postgresql/util/WriterHandler.java b/pgjdbc/src/main/java/org/postgresql/util/WriterHandler.java
new file mode 100644
index 00000000..822f9536
--- /dev/null
+++ b/pgjdbc/src/main/java/org/postgresql/util/WriterHandler.java
@@ -0,0 +1,125 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+package org.postgresql.util;
+
+import java.io.Writer;
+
+import java.util.logging.ErrorManager;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+
+public class WriterHandler extends Handler {
+
+  private boolean doneHeader;
+
+  private Writer writer;
+
+  public WriterHandler(Writer inWriter) {
+    super();
+    setLevel(Level.INFO);
+    setFilter(null);
+    setFormatter(new SimpleFormatter());
+    writer = inWriter;
+  }
+
+  public WriterHandler(Writer inWriter, Formatter formatter) {
+    super();
+    setLevel(Level.INFO);
+    setFilter(null);
+    setFormatter(formatter);
+    writer = inWriter;
+
+  }
+
+  public synchronized void publish(final LogRecord record) {
+    if (!isLoggable(record)) {
+      return;
+    }
+    String msg;
+    try {
+      msg = getFormatter().format(record);
+    } catch (Exception ex) {
+      // We don't want to throw an exception here, but we
+      // report the exception to any registered ErrorManager.
+      reportError(null, ex, ErrorManager.FORMAT_FAILURE);
+      return;
+    }
+
+    try {
+      if (!doneHeader) {
+        writer.write(getFormatter().getHead(this));
+        doneHeader = true;
+      }
+      writer.write(msg);
+    } catch (Exception ex) {
+      // We don't want to throw an exception here, but we
+      // report the exception to any registered ErrorManager.
+      reportError(null, ex, ErrorManager.WRITE_FAILURE);
+    }
+  }
+
+  public boolean isLoggable(final LogRecord record) {
+    if (writer == null || record == null) {
+      return false;
+    }
+    return super.isLoggable(record);
+  }
+
+  public synchronized void flush() {
+    if (writer != null) {
+      try {
+        writer.flush();
+      } catch (Exception ex) {
+        // We don't want to throw an exception here, but we
+        // report the exception to any registered ErrorManager.
+        reportError(null, ex, ErrorManager.FLUSH_FAILURE);
+      }
+    }
+  }
+
+  private synchronized void flushAndClose() throws SecurityException {
+
+    if (writer != null) {
+      try {
+        if (!doneHeader) {
+          writer.write(getFormatter().getHead(this));
+          doneHeader = true;
+        }
+        writer.write(getFormatter().getTail(this));
+        writer.flush();
+        writer.close();
+      } catch (Exception ex) {
+        // We don't want to throw an exception here, but we
+        // report the exception to any registered ErrorManager.
+        reportError(null, ex, ErrorManager.CLOSE_FAILURE);
+      }
+      writer = null;
+
+    }
+  }
+
+  public synchronized void close() throws SecurityException {
+    flushAndClose();
+  }
+}


Test patch:
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DriverTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DriverTest.java
index 9d26beaf..07416d81 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DriverTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/DriverTest.java
@@ -13,15 +13,20 @@ import static org.junit.Assert.fail;
 
 import org.postgresql.Driver;
 import org.postgresql.test.TestUtil;
+import org.postgresql.util.NullOutputStream;
+import org.postgresql.util.WriterHandler;
 
 import org.junit.Test;
 
+import java.io.PrintWriter;
 import java.lang.reflect.Method;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Properties;
+import java.util.logging.Handler;
+import java.util.logging.Logger;
 
 /*
  * Tests the dynamically created class org.postgresql.Driver
@@ -180,4 +185,74 @@ public class DriverTest {
     }
     fail("Driver has not been found in DriverManager's list but it should be registered");
   }
+
+  @Test
+  public void testSetLogWriter() throws Exception {
+
+    // this is a dummy to make sure TestUtil is initialized
+    Connection con = DriverManager.getConnection(TestUtil.getURL(), TestUtil.getUser(), TestUtil.getPassword());
+    con.close();
+    String loggerLevel = System.getProperty("loggerLevel");
+    String loggerFile = System.getProperty("loggerFile");
+
+    try {
+
+      PrintWriter printWriter = new PrintWriter(new NullOutputStream(System.err));
+      DriverManager.setLogWriter(printWriter);
+      assertEquals(DriverManager.getLogWriter(), printWriter);
+      System.clearProperty("loggerFile");
+      System.clearProperty("loggerLevel");
+      Properties props = new Properties();
+      props.setProperty("user", TestUtil.getUser());
+      props.setProperty("password", TestUtil.getPassword());
+      props.setProperty("loggerLevel", "DEBUG");
+      con = DriverManager.getConnection(TestUtil.getURL(), props);
+
+      Logger logger = Logger.getLogger("org.postgresql");
+      Handler[] handlers = logger.getHandlers();
+      assertTrue(handlers[0] instanceof WriterHandler );
+      con.close();
+    } finally {
+      DriverManager.setLogWriter(null);
+      System.setProperty("loggerLevel", loggerLevel);
+      System.setProperty("loggerFile", loggerFile);
+
+    }
+
+  }
+
+  @Test
+  public void testSetLogStream() throws Exception {
+
+    // this is a dummy to make sure TestUtil is initialized
+    Connection con = DriverManager.getConnection(TestUtil.getURL(), TestUtil.getUser(), TestUtil.getPassword());
+    con.close();
+    String loggerLevel = System.getProperty("loggerLevel");
+    String loggerFile = System.getProperty("loggerFile");
+
+    try {
+
+      DriverManager.setLogStream(new NullOutputStream(System.err));
+      System.clearProperty("loggerFile");
+      System.clearProperty("loggerLevel");
+      Properties props = new Properties();
+      props.setProperty("user", TestUtil.getUser());
+      props.setProperty("password", TestUtil.getPassword());
+      props.setProperty("loggerLevel", "DEBUG");
+      con = DriverManager.getConnection(TestUtil.getURL(), props);
+
+      Logger logger = Logger.getLogger("org.postgresql");
+      Handler []handlers = logger.getHandlers();
+      assertTrue( handlers[0] instanceof WriterHandler );
+      con.close();
+    } finally {
+      DriverManager.setLogStream(null);
+      System.setProperty("loggerLevel", loggerLevel);
+      System.setProperty("loggerFile", loggerFile);
+
+
+    }
+
+  }
+
 }
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java
index e4f069b2..7d20eea4 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java
@@ -26,6 +26,7 @@ import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
 import java.net.URLEncoder;
 import java.sql.DriverPropertyInfo;
+import java.util.ArrayList;
 import java.util.Map;
 import java.util.Properties;
 import java.util.TreeMap;
@@ -213,4 +214,47 @@ public class PGPropertyTest {
     assertEquals("user", userName, PGProperty.USER.get(parsed));
     assertEquals("password", password, PGProperty.PASSWORD.get(parsed));
   }
+
+  @Test
+  public void testLowerCamelCase() {
+    // These are legacy properties excluded for backward compatibility.
+    ArrayList<String> excluded = new ArrayList<String>();
+    excluded.add("LOG_LEVEL"); // Remove with PR #722
+    excluded.add("PREPARED_STATEMENT_CACHE_SIZE_MIB"); // preparedStatementCacheSizeMi[B]
+    excluded.add("DATABASE_METADATA_CACHE_FIELDS_MIB"); // databaseMetadataCacheFieldsMi[B]
+    excluded.add("STRING_TYPE"); // string[t]ype
+    excluded.add("SSL_MODE"); // ssl[m]ode
+    excluded.add("SSL_FACTORY"); // ssl[f]actory
+    excluded.add("SSL_FACTORY_ARG"); // ssl[f]actory[a]rg
+    excluded.add("SSL_HOSTNAME_VERIFIER"); // ssl[h]ostname[v]erifier
+    excluded.add("SSL_CERT"); // ssl[c]ert
+    excluded.add("SSL_KEY"); // ssl[k]ey
+    excluded.add("SSL_ROOT_CERT"); // ssl[r]oot[c]ert
+    excluded.add("SSL_PASSWORD"); // ssl[p]assword
+    excluded.add("SSL_PASSWORD_CALLBACK"); // ssl[p]assword[c]allback
+    excluded.add("APPLICATION_NAME"); // [A]pplicationName
+    excluded.add("GSS_LIB"); // gss[l]ib
+    excluded.add("REWRITE_BATCHED_INSERTS"); // re[W]riteBatchedInserts
+
+    for (PGProperty property : PGProperty.values()) {
+      if (!property.name().startsWith("PG")) { // Ignore all properties that start with PG
+        String[] words = property.name().split("_");
+        if (words.length == 1) {
+          assertEquals(words[0].toLowerCase(), property.getName());
+        } else {
+          if (!excluded.contains(property.name())) {
+            String word = "";
+            for (int i = 0; i < words.length; i++) {
+              if (i == 0) {
+                word = words[i].toLowerCase();
+              } else {
+                word += words[i].substring(0, 1).toUpperCase() + words[i].substring(1).toLowerCase();
+              }
+            }
+            assertEquals(word, property.getName());
+          }
+        }
+      }
+    }
+  }
 }
diff --git a/pgjdbc/src/test/java/org/postgresql/util/NullOutputStream.java b/pgjdbc/src/test/java/org/postgresql/util/NullOutputStream.java
new file mode 100644
index 00000000..764dc1ac
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/util/NullOutputStream.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.util;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Created by davec on 3/14/17.
+ */
+public class NullOutputStream extends PrintStream {
+
+  public NullOutputStream(OutputStream out) {
+    super(out);
+  }
+
+  @Override
+  public void write(int b) {
+
+  }
+
+  @Override
+  public void write(byte[] buf, int off, int len) {
+
+  }
+}