Class Node

java.lang.Object
org.gjt.cuspy.JarX
org.postgresql.pljava.packaging.Node

public class Node extends JarX
Extends the JarX extraction tool to provide a resolve method that replaces prefixes pljava/foo/ in path names stored in the archive with the result of pg_config --foo.

As this represents a second extra .class file that has to be added to the installer jar anyway, it will also contain some methods intended to be useful for tasks related to installation and testing. The idea is not to go overboard, but supply a few methods largely modeled on the most basic ones of PostgreSQL's PostgreSQL::Test::Cluster Perl module (formerly named PostgresNode, from which the name of this class was taken). The methods can be invoked from jshell if its classpath includes the installer jar (and one of the PostgreSQL JDBC drivers).

An introduction with examples is available.

Unlike the many capabilities of PostgreSQL::Test::Cluster, this only deals in TCP sockets bound to localhost (StandardProtocolFamily.UNIX finally arrived in Java 16 but this class does not support it yet) and only a few of the most basic operations.

As in JarX itself, some liberties with coding style may be taken here to keep this one extra .class file from proliferating into a bunch of them.

As the testing-related methods here are intended for ad-hoc or scripted use in jshell, they are typically declared to throw any checked exception, without further specifics. There are many overloads of methods named q and qp (mnemonic of query and query-print), to make interactive use in jshell comfortable with just a few static imports.

  • Field Details

    • s_isWindows

      public static final boolean s_isWindows
      True if the platform is determined to be Windows.

      On Windows, forWindowsCRuntime should be applied to any ProcessBuilder before invoking it; the details of the transformation applied by asPgCtlInvocation change, and use_pg_ctl may prove useful, as pg_ctl on Windows is able to drop administrative privileges that would otherwise prevent postgres from starting.

    • s_urlForm

      public static final int s_urlForm
      The first form of PostgreSQL JDBC driver connection URL found to be recognized by an available driver, or URL_FORM_NONE.
    • URL_FORM_NONE

      public static final int URL_FORM_NONE
      Value of s_urlForm indicating no available JDBC driver was found to accept any of the supported connection URL forms.
      See Also:
    • URL_FORM_PGJDBC

      public static final int URL_FORM_PGJDBC
      Value of s_urlForm indicating an available JDBC driver reported accepting a connection URL in the PGJDBC form starting with "jdbc:postgresql:".
      See Also:
    • URL_FORM_PGJDBCNG

      public static final int URL_FORM_PGJDBCNG
      Value of s_urlForm indicating an available JDBC driver reported accepting a connection URL in the pgjdbc-ng form starting with "jdbc:pgsql:".
      See Also:
    • NOTHING_OR_PGJDBC_ZERO_COUNT

      public static final InvocationHandler NOTHING_OR_PGJDBC_ZERO_COUNT
      A state (see stateMachine) that expects nothing (if the driver is pgjdbc-ng) or a zero row count (if the driver is PGJDBC).

      For some utility statements (such as CREATE EXTENSION) with no result, the pgjdbc-ng driver will produce no result, while the PGJDBC driver produces a zero count, as it would for a DML statement that did not affect any rows. This state handles either case.

      When URL_FORM_PGJDBCNG == s_urlForm, this state consumes nothing and moves to the numerically next state. Otherwise (JDBC), it checks that the current object is a zero row count, consuming it and moving to the numerically next state if it is, returning false otherwise.

  • Method Details

    • main

      public static void main(String[] args) throws Exception
      Performs an ordinary installation, using pg_config or the corresponding system properties to learn where the files belong, and unpacking the files (not including this class or its ancestors) there.
      Throws:
      Exception
    • extract

      public void extract() throws Exception
      Extracts the jar contents, just as done in the normal case of running this class with java -jar.

      Only to be called on the singleton instance s_jarxHelper.

      For a version that doesn't really extract anything, but still primes the resolve method to know where things should be extracted, see dryExtract().

      Overrides:
      extract in class JarX
      Throws:
      Exception - if anything doesn't work, punt
    • prepareResolver

      public void prepareResolver(String v) throws Exception
      Prepares the resolver, ignoring the passed string (ordinarily a script or rules); this resolver's rules are hardcoded.
      Overrides:
      prepareResolver in class JarX
      Parameters:
      v - value of the _JarX_PathResolver main attribute
      Throws:
      Exception - this implementation throws no checked exceptions, but an overriding implementation may
    • resolve

      public String resolve(String storedPath, String platformPath) throws Exception
      Replaces a prefix pljava/key in a path to be extracted with the value of the pgconfig.key system property, or the result of invoking pg_config (or the exact executable named in the pgconfig system property, if present) with the option --key.
      Overrides:
      resolve in class JarX
      Parameters:
      storedPath - The path as stored in the archive, always /-separated
      platformPath - The path after only replacing / with the platform separator
      Returns:
      plat unchanged, or a corrected location for extracting the entry, or null to suppress extracting the entry
      Throws:
      Exception - this implementation may throw ScriptException, an overriding implementation may throw others
    • set_WARNING_localized

      public static void set_WARNING_localized(String s)
      Changes the severity string used to recognize when the backend is sending a WARNING.

      When the driver is PGJDBC, the classification done here of SQLWarning instances into actual warning messages or informative ones depends on a tag ("WARNING" in English) that the backend delivers in the local language. For the classification to happen correctly when a different language is selected, use this method to supply the string (for example, "PERINGATAN" in Indonesian) that the backend uses for warnings in that language.

    • toString

      public String toString()
      Identifying information for a "node" instance, or for the singleton extractor instance.
      Overrides:
      toString in class Object
    • get_new_node

      public static Node get_new_node(String name) throws Exception
      Returns a new Node that can be used to initialize and start a PostgreSQL instance.

      Establishes a VM shutdown hook that will stop the server (if started) and recursively remove the basedir before the VM exits.

      Throws:
      Exception
    • get_free_port

      public static int get_free_port() throws Exception
      Returns a TCP port on the loopback interface that is free at the moment this method is called.
      Throws:
      Exception
    • clean_node

      public void clean_node() throws Exception
      Recursively removes the basedir and its descendants.
      Throws:
      Exception
    • clean_node

      public void clean_node(boolean keepRoot) throws Exception
      Recursively removes the basedir (unless keepRoot) and its descendants.
      Parameters:
      keepRoot - if true, the descendants are removed, but not the basedir itself.
      Throws:
      Exception
    • data_dir

      public Path data_dir()
      Returns the directory name to be used as the PostgreSQL data directory for this node.
    • initialized_cluster

      public AutoCloseable initialized_cluster() throws Exception
      Like init() but returns an AutoCloseable that will recursively remove the files and directories under the basedir (but not the basedir itself) on the exit of a calling try-with-resources scope.
      Throws:
      Exception
    • initialized_cluster

      public AutoCloseable initialized_cluster(Map<String,String> suppliedOptions) throws Exception
      Like init() but returns an AutoCloseable that will recursively remove the files and directories under the basedir (but not the basedir itself) on the exit of a calling try-with-resources scope.
      Throws:
      Exception
    • initialized_cluster

      public AutoCloseable initialized_cluster(UnaryOperator<ProcessBuilder> tweaks) throws Exception
      Like init() but returns an AutoCloseable that will recursively remove the files and directories under the basedir (but not the basedir itself) on the exit of a calling try-with-resources scope.
      Throws:
      Exception
    • initialized_cluster

      public AutoCloseable initialized_cluster(Map<String,String> suppliedOptions, UnaryOperator<ProcessBuilder> tweaks) throws Exception
      Like init() but returns an AutoCloseable that will recursively remove the files and directories under the basedir (but not the basedir itself) on the exit of a calling try-with-resources scope.
      Throws:
      Exception
    • init

      public void init() throws Exception
      Invokes initdb for the node, passing default options appropriate for this setting.
      Throws:
      Exception
    • init

      public void init(Map<String,String> suppliedOptions) throws Exception
      Invokes initdb for the node, with suppliedOptions overriding or supplementing the ones that would be passed by default.
      Throws:
      Exception
    • init

      public void init(UnaryOperator<ProcessBuilder> tweaks) throws Exception
      Invokes initdb for the node, passing default options appropriate for this setting, and tweaks to be applied to the ProcessBuilder before it is started.
      Throws:
      Exception
    • init

      public void init(Map<String,String> suppliedOptions, UnaryOperator<ProcessBuilder> tweaks) throws Exception
      Invokes initdb for the node, with suppliedOptions overriding or supplementing the ones that would be passed by default, and tweaks to be applied to the ProcessBuilder before it is started.

      By default, postgres will be the name of the superuser, UTF-8 will be the encoding, auth-local will be peer and auth-host will be md5. The initialization will skip fsync for speed rather than safety (if something goes wrong, just clean_node() and start over).

      The initdb that will be run is the one in the bindir reported by pg_config (or set by -Dpgconfig.bindir).

      Parameters:
      suppliedOptions - a Map where each key is an option to initdb (for example, --encoding), and the value corresponds.
      tweaks - a lambda applicable to the ProcessBuilder to further configure it. On Windows, the tweaks will be applied ahead of transformation of the arguments by forWindowsCRuntime.
      Throws:
      Exception
    • started_server

      public AutoCloseable started_server() throws Exception
      Like start() but returns an AutoCloseable that will stop the server on the exit of a calling try-with-resources scope.
      Throws:
      Exception
    • started_server

      public AutoCloseable started_server(Map<String,String> suppliedOptions) throws Exception
      Like start() but returns an AutoCloseable that will stop the server on the exit of a calling try-with-resources scope.
      Throws:
      Exception
    • started_server

      public AutoCloseable started_server(UnaryOperator<ProcessBuilder> tweaks) throws Exception
      Like start() but returns an AutoCloseable that will stop the server on the exit of a calling try-with-resources scope.

      Supplied tweaks will be applied to the ProcessBuilder used to start the server; if pg_ctl is being used, they will also be applied when running pg_ctl stop to stop it.

      Throws:
      Exception
    • started_server

      public AutoCloseable started_server(Map<String,String> suppliedOptions, UnaryOperator<ProcessBuilder> tweaks) throws Exception
      Like start() but returns an AutoCloseable that will stop the server on the exit of a calling try-with-resources scope.

      Supplied tweaks will be applied to the ProcessBuilder used to start the server; if pg_ctl is being used, they will also be applied when running pg_ctl stop to stop it.

      Throws:
      Exception
    • start

      public void start() throws Exception
      Starts a PostgreSQL server for the node with default options appropriate for this setting.
      Throws:
      Exception
    • start

      public void start(Map<String,String> suppliedOptions) throws Exception
      Starts a PostgreSQL server for the node, with suppliedOptions overriding or supplementing the ones that would be passed by default.
      Throws:
      Exception
    • start

      public void start(UnaryOperator<ProcessBuilder> tweaks) throws Exception
      Starts a PostgreSQL server for the node, passing default options appropriate for this setting, and tweaks to be applied to the ProcessBuilder before it is started.
      Throws:
      Exception
    • start

      public void start(Map<String,String> suppliedOptions, UnaryOperator<ProcessBuilder> tweaks) throws Exception
      Starts a PostgreSQL server for the node, with suppliedOptions overriding or supplementing the ones that would be passed by default, and tweaks to be applied to the ProcessBuilder before it is started.

      By default, the server will listen only on the loopback interface and not on any Unix-domain socket, on the port selected when this Node was created, and for a maximum of 16 connections. Its cluster name will be the name given to this Node, and fsync will be off to favor speed over durability. The log line prefix will be shortened to just the node name and (when connected) the application_name.

      The server that will be run is the one in the bindir reported by pg_config (or set by -Dpgconfig.bindir).

      If the server is PostgreSQL 10 or later, it is definitely ready to accept connections when this method returns. If not, it is highly likely to be ready, but no test connection has been made to confirm it.

      Parameters:
      suppliedOptions - a Map where the key is a configuration variable name as seen in postgresql.conf or passed to the server with -c and the value corresponds.
      tweaks - a lambda applicable to the ProcessBuilder to further configure it. Under use_pg_ctl(true), the tweaks are applied after the arguments have been transformed by asPgCtlInvocation. On Windows, they are applied ahead of transformation of the arguments by forWindowsCRuntime.
      Throws:
      Exception
    • stop

      public void stop() throws Exception
      Stops the server instance associated with this Node.

      Has the effect of stop(tweaks) without any tweaks.

      Throws:
      Exception
    • stop

      public void stop(UnaryOperator<ProcessBuilder> tweaks) throws Exception
      Stops the server instance associated with this Node.

      No effect if it has not been started or has already been stopped, but a message to standard error is logged if the server had been started and the process is found to have exited unexpectedly.

      Parameters:
      tweaks - tweaks to apply to a ProcessBuilder; unused unless pg_ctl will be used to stop the server. When used, they are applied ahead of the transformation of the arguments by forWindowsCRuntime used on Windows.
      Throws:
      Exception
    • use_pg_ctl

      public void use_pg_ctl(boolean setting)
      Sets whether to use pg_ctl to start and stop the server (if true), or start postgres and stop it directly (if false, the default).

      On Windows, pg_ctl is able to drop administrator rights and start the server from an account that would otherwise trigger the server's refusal to start from a privileged account.

    • connect

      public Connection connect() throws Exception
      Returns a Connection to the server associated with this Node, using default properties appropriate for this setting.
      Throws:
      Exception
    • connect

      public Connection connect(Map<String,String> suppliedProperties) throws Exception
      Returns a Connection to the server associated with this Node, with suppliedProperties overriding or supplementing the ones that would be passed by default.
      Throws:
      Exception
    • connect

      public Connection connect(Properties p) throws Exception
      Returns a Connection to the server associated with this Node, with supplied properties p overriding or supplementing the ones that would be passed by default.

      By default, the connection is to the postgres database as the postgres user, using the password internally generated for this node, and with an application_name generated from a counter of connections for this node.

      Throws:
      Exception
    • setConfig

      public static Stream<Object> setConfig(Connection c, String settingName, String newValue, boolean isLocal) throws Exception
      Sets a configuration variable on the server.

      This deserves a convenience method because the most familiar PostgreSQL syntax for SET doesn't lend itself to parameterization.

      Returns:
      a result stream from executing the statement
      Throws:
      Exception
    • loadPLJava

      public static Stream<Object> loadPLJava(Connection c) throws Exception
      Loads PL/Java (with a LOAD command, not CREATE EXTENSION).

      This was standard procedure in PostgreSQL versions that pre-dated the extension support. It is largely obsolete with the advent of CREATE EXTENSION, but still has one distinct use case: this is what will work if you do not have administrative access to install PL/Java's files in the standard directories where CREATE EXTENSION expects them, but can only place them in some other location the server can read. Then you simply have to make sure that pljava.module_path is set correctly to locate the jar files, and give the correct shared-object path to LOAD (which this method does).

      It is also useful to see better diagnostics if something is going wrong, as PostgreSQL severely suppresses diagnostic messages during CREATE EXTENSION.

      Returns:
      a result stream from executing the statement
      Throws:
      Exception
    • installJar

      public static Stream<Object> installJar(Connection c, String uri, String jarName, boolean deploy) throws Exception
      Installs a jar.
      Returns:
      a result stream from executing the statement
      Throws:
      Exception
    • removeJar

      public static Stream<Object> removeJar(Connection c, String jarName, boolean undeploy) throws Exception
      Removes a jar.
      Returns:
      a result stream from executing the statement
      Throws:
      Exception
    • setClasspath

      public static Stream<Object> setClasspath(Connection c, String schema, String... jarNames) throws Exception
      Sets the class path for a schema.
      Returns:
      a result stream from executing the statement
      Throws:
      Exception
    • appendClasspathIf

      public static Stream<Object> appendClasspathIf(Connection c, String schema, String jarName) throws Exception
      Appends a jar to a schema's class path if not already included.
      Returns:
      a result stream that includes, on success, a one-column void result set with a single row if the jar was added to the path, and no rows if the jar was already included.
      Throws:
      Exception
    • q

      public static Stream<Object> q(Connection c, String sql) throws Exception
      Executes some arbitrary SQL
      Returns:
      a result stream from executing the statement
      Throws:
      Exception
    • q

      public static Stream<Object> q(Statement s, Callable<Boolean> work) throws Exception
      Produces a Stream of the (in JDBC, possibly multiple) results from some execute method on a Statement.

      This is how, for example, to prepare, then examine the results of, a PreparedStatement:

       PreparedStatement ps = conn.prepareStatement("select foo(?,?)");
       ps.setInt(1, 42);
       ps.setString(2, "surprise!");
       q(ps, ps::execute);
      

      Each result in the stream will be an instance of one of: ResultSet, Long (an update count, positive or zero), SQLWarning, or some other SQLException. A warning or exception may have others chained to it, which its own iterator or forEach methods should be used to traverse; or, use flatMap(Node::flattenDiagnostics) to obtain a stream presenting each diagnostic in a chain in turn. The Callable interface supplying the work to be done allows any checked exception, but any Throwable outside the SQLException hierarchy will simply be rethrown from here rather than delivered in the stream. Any Throwable thrown by work will result in the Statement being closed. Otherwise, it remains open until the returned stream is closed.

      Exists mainly to encapsulate the rather fiddly logic of extracting that sequence of results using the Statement API.

      Parameters:
      s - the Statement from which to extract results
      work - a Callable that will invoke one of the Statement's execute methods returning a boolean that indicates whether the first result is a ResultSet. Although the Callable interface requires the boolean result to be boxed, it must not return null.
      Returns:
      a Stream as described above.
      Throws:
      Exception
    • q

      public static Stream<Object> q(ResultSet rs) throws Exception
      Analogously to q(Statement,...), produces a Stream with an element for each row of a ResultSet, interleaved with any SQLWarnings reported on the result set, or an SQLException if one is thrown.

      This is supplied chiefly for use driving a state machine to verify contents of a result set. For each row, the element in the stream will be an instance of Long, counting up from 1 (intended to match the result set's getRow but without relying on it, as JDBC does not require every implementation to support it). By itself, of course, this does not convey any of the content of the row; the lambdas representing the machine states should close over the result set and query it for content, perhaps not even using the object supplied here (except to detect when it is a warning or exception rather than a row number). The row position of the result set will have been updated, and should not be otherwise modified when this method is being used to walk through the results.

      For the same reason, don't try any funny business like sorting the stream in any way. The ResultSet will only be read forward, and each row only once. Simple filtering, dropWhile/takeWhile, and so on will work, but may be more conveniently rolled into the design of a state machine, as nearly any use of a ResultSet can throw SQLException and therefore isn't convenient in the stream API.

      Passing this result to qp as if it came from a Statement could lead to confusion, as the Long elements would be printed as update counts rather than row numbers.

      Parameters:
      rs - a ResultSet
      Returns:
      a Stream as described above
      Throws:
      Exception
    • q

      public static Stream<Object> q(ResultSetMetaData rsmd) throws Exception
      Produces a Stream with an element for each column of a ResultSet.

      This is another convenience method for use chiefly in driving a state machine to check per-column values or metadata for a ResultSet. It is, in fact, nothing other than IntStream.rangeClosed(1, rsmd.getColumnCount()).boxed() but typed as Stream<Object>.

      As with q(ResultSet), the column number supplied here conveys no actual column data or metadata. The lambdas representing the machine states should close over the ResultSetMetaData or corresponding ResultSet object, or both, and use the column number from this stream to index them.

      Parameters:
      rsmd - a ResultSetMetaData object
      Returns:
      a Stream as described above
      Throws:
      Exception
    • q

      public static Stream<Object> q(ParameterMetaData pmd) throws Exception
      Produces a Stream with an element for each parameter of a PreparedStatement.

      This is another convenience method for use chiefly in driving a state machine to check per-parameter metadata. It is, in fact, nothing other than IntStream.rangeClosed(1, rsmd.getParameterCount()).boxed() but typed as Stream<Object>.

      As with q(ResultSet), the column number supplied here conveys no actual parameter metadata. The lambdas representing the machine states should close over the ParameterMetaData object and use the parameter number from this stream to index it.

      Parameters:
      pmd - a ParameterMetaData object
      Returns:
      a Stream as described above
      Throws:
      Exception
    • qp

      public static void qp(Connection c, String sql) throws Exception
      Executes some arbitrary SQL and passes the result stream to qp(Stream) for printing to standard output.
      Throws:
      Exception
    • qp

      public static void qp(Statement s, Callable<Boolean> work) throws Exception
      Invokes some execute method on a Statement and passes the result stream to qp(Stream) for printing to standard output.

      This is how, for example, to prepare, then print the results of, a PreparedStatement:

       PreparedStatement ps = conn.prepareStatement("select foo(?,?)");
       ps.setInt(1, 42);
       ps.setString(2, "surprise!");
       qp(ps, ps::execute);
      
      The Statement will be closed.
      Throws:
      Exception
    • examplesNeedSaxon

      public static boolean examplesNeedSaxon() throws Exception
      Returns true if the examples jar includes the org.postgresql.pljava.example.saxon.S9 class (meaning the appropriate Saxon jar must be installed and on the classpath first before the examples jar can be deployed, unless check_function_bodies is off to skip dependency checking).
      Throws:
      Exception
    • installExamples

      public static Stream<Object> installExamples(Connection c, boolean deploy) throws Exception
      Installs the examples jar, under the name examples.

      The jar is specified by a file: URI and the path is the one where this installer installed (or would have installed) it.

      Returns:
      a result stream from executing the statement
      Throws:
      Exception
    • installExamplesAndPath

      public static Stream<Object> installExamplesAndPath(Connection c, boolean deploy) throws Exception
      Installs the examples jar, under the name examples, and appends it to the class path for schema public.

      The return of a concatenated result stream from two consecutive statements might be likely to fail in cases where the first statement has any appreciable data to return, but the drivers seem to handle it at least in this case where each statement just returns one row / one column of void. And it is convenient.

      Returns:
      a combined result stream from executing the statements
      Throws:
      Exception
    • installSaxon

      public static Stream<Object> installSaxon(Connection c, String repo, String version) throws Exception
      Installs a Saxon jar under the name saxon, given the path to a local Maven repo and the needed version of Saxon, assuming the jar has been downloaded there already.
      Returns:
      a result stream from executing the statement
      Throws:
      Exception
    • installSaxonAndPath

      public static Stream<Object> installSaxonAndPath(Connection c, String repo, String version) throws Exception
      Installs a Saxon jar under the name saxon, and appends it to the class path for schema public.
      Returns:
      a combined result stream from executing the statements
      Throws:
      Exception
    • installSaxonAndExamplesAndPath

      public static Stream<Object> installSaxonAndExamplesAndPath(Connection c, String repo, String version, boolean deploy) throws Exception
      A four-fer: installs Saxon, adds it to the class path, then installs the examples jar, and updates the classpath to include both.
      Parameters:
      repo - the base directory of a local Maven repository into which the Saxon jar has been downloaded
      version - the needed version of Saxon
      deploy - whether to run the example jar's deployment code
      Returns:
      a combined result stream from executing the statements
      Throws:
      Exception
    • flattenDiagnostics

      public static Stream<Object> flattenDiagnostics(Object oneResult)
      A flat-mapping function to expand any SQLException or SQLWarning instance in a result stream into the stream of possibly multiple linked diagnostics and causes in the encounter order of the SQLException iterator.

      Any other object is returned in a singleton stream.

      To flatten just the chain of SQLWarning or SQLException but with each of those retaining its own list of causes, see semiFlattenDiagnostics.

    • semiFlattenDiagnostics

      public static Stream<Object> semiFlattenDiagnostics(Object oneResult)
      A flat-mapping function to expand any SQLException or SQLWarning instance in a result stream into the stream of possibly multiple linked diagnostics in the order produced by getNextException or getNextWarning.

      Unlike flattenDiagnostics, this method does not descend into chains of causes; those may be retrieved in the usual way from the throwables returned on this stream.

      Any other object is returned in a singleton stream.

    • qp

      public static void qp(Stream<Object> s) throws Exception
      Prints streamed results of a Statement in (somewhat) readable fashion.

      Uses writeXml of WebRowSet, which is very verbose, but about the easiest way to readably dump a ResultSet in just a couple lines of code.

      The supplied stream is flattened (see semiFlattenDiagnostics) so that any chained SQLExceptions or SQLWarnings are printed in sequence.

      Throws:
      Exception
    • qp

      public static void qp(Stream<Object> s, Function<Object,Stream<Object>> flattener) throws Exception
      Prints streamed results of a Statement in (somewhat) readable fashion, with a choice of flattener for diagnostics.

      For flattener, see flattenDiagnostics or semiFlattenDiagnostics.

      Throws:
      Exception
    • qp

      public static void qp(Object o) throws Exception
      Overload of qp for direct application to any one Object obtained from a result stream.

      Simply applies the specialized treatment appropriate to the class of the object.

      Throws:
      Exception
    • peek

      public static void peek(Object o)
      Prints an object in the manner of qp, but in a way suitable for use in Stream.peek.

      If o is a ResultSet, only its metadata will be printed; its position will not be disturbed and it will not be closed. This method throws no checked exceptions, as the Stream API requires; any that is caught will be printed as if by qp.

    • qp

      public static void qp(ResultSet rs) throws Exception
      Overload of qp for direct application to a ResultSet.

      Sometimes one has a ResultSet that didn't come from executing a query, such as from a JDBC metadata method. This prints it the same way qp on a query result would. The ResultSet is not closed (but will have been read through the last row).

      A result set with no columns of type other than void will be printed in an abbreviated form, showing its number of rows and columns as reported by voidResultSetDims.

      Throws:
      Exception
    • qp

      public static void qp(ParameterMetaData md) throws Exception
      Overload of qp for examining ParameterMetaData.

      Continuing in the spirit of getting something reasonably usable without a lot of code, this fakes up a ResultSetMetaData with the same values present in the ParameterMetaData (and nothing for the ones that aren't, like names), and then uses WebRowSet.writeXml as if dumping a result set.

      For getting a quick idea what the parameters are, it's good enough.

      Throws:
      Exception
    • qp

      public static void qp(ResultSetMetaData md) throws Exception
      Overload of qp for examining ResultSetMetaData.

      This makes an empty WebRowSet with the copied metadata, and dumps it with writeXml. Owing to a few missing setters on Java's RowSetMetaDataImpl, a few ResultSetMetaData attributes will not have been copied; they'll be wrong (unless the real values happen to match the defaults). That could be fixed by extending that class, but that would require yet another extra class file added to the installer jar.

      Throws:
      Exception
    • qp

      public static void qp(Throwable t)
      Prints a Throwable retrieved from a result stream, with special handling for SQLException and SQLWarning.

      In keeping with the XMLish vibe established by qp for other items in a result stream, this will render a Throwable as an error, warning, or info element (PostgreSQL's finer distinctions of severity are not exposed by every JDBC driver's API.)

      An element will have a message attribute if it has a message. It will have a code attribute containing the SQLState, if it is an instance of SQLException, unless it is rendered as an info element and the state is 00000. An instance of SQLWarning will be rendered as a warning unless its class (two leftmost code positions) is 00, in which case it will be info. Anything else is an error.

    • classify

      public static String[] classify(Throwable t)
      Returns an array of three Strings, element, sqlState, and message, as would be printed by qp(Throwable).

      The first string will be: (1) if the throwable is an SQLWarning, "info" if its class (leftmost two positions of SQLState) is 00, otherwise "warning"; (2) for any other throwable, "error". These are constant strings and therefore interned.

      The second string will be null if the throwable is outside the SQLException hierarchy, or if the first string is "info" and the SQLState is exactly 00000; otherwise it will be the SQLState.

      The third string will be as returned by getMessage, and may be null if the throwable was not constructed with a message.

      If an SQLWarning is of the PGJDBC driver's PSQLWarning class and the backend's severity tag is available, it will be used to determine the first string, in place of the "starts with 00" rule. A tag of "WARNING" (or null) produces "warning", while any other tag produces "info".

    • voidResultSetDims

      public static int[] voidResultSetDims(Object o, boolean peek) throws Exception
      Determines whether an object is a ResultSet with no columns of any type other than void, to allow abbreviated output of result sets produced by the common case of queries that call void functions.

      Returns null if o is not a ResultSet, or if its columns are not all of void type. Otherwise, returns a two-element integer array giving the rows (index 0 in the array) and columns (index 1) of the result set.

      If this method returns non-null, the result set is left positioned on its last row.

      Parameters:
      o - Object to check
      peek - whether to avoid moving the row cursor. If true, and all of the columns are indeed void, the result array will have the column count at index 1 and -1 at index 0.
      Returns:
      null or a two-element int[], as described above
      Throws:
      Exception
    • voidResultSetDims

      public static int[] voidResultSetDims(Object o) throws Exception
      Throws:
      Exception
    • isVoidResultSet

      public static boolean isVoidResultSet(Object o, int rows, int columns) throws Exception
      A predicate testing that an object is a ResultSet that has only columns of void type, and the expected number of rows and columns.

      The expected result of a query that calls one void-typed, non-set-returning function could be checked with isVoidResultSet(rs, 1, 1).

      Throws:
      Exception
    • stateMachine

      public static boolean stateMachine(String name, Consumer<String> reporter, Stream<Object> input, InvocationHandler... states) throws Exception
      Executes a state machine specified in the form of a list of lambdas representing its states, to verify that a result stream is as expected.

      Treats the list of lambdas as a set of consecutively-numbered states (the first in the list is state number 1, and is the initial state). At each step of the machine, the current state is applied to the current input object, and may return an Integer or a Boolean.

      If an integer, its absolute value selects the next state. A positive integer consumes the current input item, so the next state will be applied to the next item of input. A negative integer transitions to the selected next state without consuming the current input item, so it will be examined again in the newly selected state.

      If boolean, false indicates that the machine cannot proceed; the supplied reporter will be passed an explanatory string and this method returns false. A state that returns true indicates the machine has reached an accepting state.

      No item of input is allowed to be null; null is reserved to be the end-of-input symbol. If a state returns true (accept) when applied to null at the end of input, the machine has matched and this method returns true. A state may also return a negative integer in this case, to shift to another state while looking at the end of input. A positive integer (attempting to consume the end of input), or a false, return will cause an explanatory message to the reporter and a false return from this method.

      A state may return true (accept) when looking at a non-null input item, but the input will be checked to confirm it has no more elements. Otherwise, the machine has tried to accept before matching all the input, and this method will return false.

      To avoid defining a new functional interface, each state is represented by InvocationHandler, an existing functional interface with a versatile argument list and permissive throws clause. Each state must be represented as a lambda with three parameters (the convention (o,p,q) is suggested), of which only the first is normally used. If Java ever completes the transition to _ as an unused-parameter marker, the suggested convention will be (o,_,_), unless the third (q) is also needed for special purposes (more below).

      As the input item passed to each state is typed Object, and as null can only represent the end of input, it may be common for a state to both cast an input to an expected type and confirm it is not null. The as method combines those operations. If its argument either is null or cannot be cast to the wanted type, as will throw a specific instance of ClassCastException, which will be treated, when caught by stateMachine, just as if the state had returned false.

      The third parameter to an InvocationHandler is an Object array, and is here used to pass additional information that may at times be of use in a state. The first element of the array holds the boxed form of the current (1-based) state number. As a state must indicate the next state by returning an absolute state number, having the state's own number available opens the possibility of reusable presupplied state implementations that do not depend on their absolute position.

      Parameters:
      name - A name for this state machine, used only in exception messages if it fails to match all the input
      reporter - a Consumer to accept a diagnostic string if the machine fails to match, defaulting if null to System.err::println
      input - A Stream of input items, of which none may be null
      states - Lambdas representing states of the machine
      Returns:
      true if an accepting state was reached coinciding with the end of input
      Throws:
      Exception - Anything that could be thrown during evaluation of the input stream or any state
    • as

      public static <T> T as(Class<T> clazz, Object o)
      Casts o to class clazz, testing it also for null.

      This is meant as a shorthand in implementing states for stateMachine. If o either is null or is not castable to the desired type, a distinguished instance of ClassCastException will be thrown, which is treated specially if caught by stateMachine while evaluating a state.

    • forWindowsCRuntime

      public static ProcessBuilder forWindowsCRuntime(ProcessBuilder pb)
      Adjusts the command arguments of a ProcessBuilder so that they will be recovered correctly on Windows by a target C/C++ program using the argument parsing algorithm of the usual C run-time code, when it is known that the command will not be handled first by cmd.

      This transformation must account for the way the C runtime will ultimately parse the parameters apart, and also for the behavior of Java's runtime in assembling the command line that the invoked process will receive.

      Parameters:
      pb - a ProcessBuilder whose command has been set to an executable that parses parameters using the C runtime rules, and arguments as they should result from parsing.
      Returns:
      The same ProcessBuilder, with the argument list rewritten as necessary to produce the original list as a result of Windows C runtime parsing,
      Throws:
      IllegalArgumentException - if the ProcessBuilder does not have at least the first command element (the executable to run)
      UnsupportedOperationException - if the arguments passed, or system properties in effect, produce a case this transformation cannot handle
    • asPgCtlInvocation

      public static ProcessBuilder asPgCtlInvocation(ProcessBuilder pb)
      Adjusts the command arguments of a ProcessBuilder that would directly invoke postgres to start a server, so that it will instead start postgres via pg_ctl.

      pg_ctl constructs a command line for cmd.exe (on Windows) or /bin/sh (elsewhere), which in turn will launch postgres. The way pg_ctl handles options (-o) requires this transformation to be platform-aware and quote them correctly for sh or cmd as appropriate.

      The result of this transformation still has to be received intact by pg_ctl itself, which requires (on Windows) a subsequent application of forWindowsCRuntime as well.

      Parameters:
      pb - a ProcessBuilder whose command has been set to an executable path for postgres, with only -D and -c options.
      Returns:
      The same ProcessBuilder, with the argument list rewritten to invoke pg_ctl start with the same -D and any other options supplied by -o.
      Throws:
      IllegalArgumentException - if the ProcessBuilder does not have at least the first command element (the executable to run)
      UnsupportedOperationException - if the arguments passed produce a case this transformation cannot handle