Class 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 PostgresNode.pm, with the idea that they 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 PostgresNode.pm, 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 Detail

      • 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:
        Constant Field Values
      • 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:
        Constant Field Values
      • 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:
        Constant Field Values
      • 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 Detail

      • 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
      • 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​(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
      • 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​(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​(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
      • 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​(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
      • 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