mavenized: sql-dk v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Mon, 04 Mar 2019 20:15:24 +0100
branchv_0
changeset 238 4a1864c3e867
parent 237 7e08730da258
child 239 39e6c2ad3571
mavenized: sql-dk
.hgignore
distributions/debian/build.sh
distributions/fedora/sql-dk.spec
java/sql-dk/bash-completion.sh
java/sql-dk/build.xml
java/sql-dk/data/info/globalcode/sql/dk/example-config.xml
java/sql-dk/data/info/globalcode/sql/dk/formatter/XhtmlFormatter.css
java/sql-dk/data/info/globalcode/sql/dk/license.txt
java/sql-dk/help-generator.sh
java/sql-dk/manifest.mf
java/sql-dk/nbproject/build-impl.xml
java/sql-dk/nbproject/genfiles.properties
java/sql-dk/nbproject/project.properties
java/sql-dk/nbproject/project.xml
java/sql-dk/pom.xml
java/sql-dk/src/info/globalcode/sql/dk/CLIOptions.java
java/sql-dk/src/info/globalcode/sql/dk/CLIParser.java
java/sql-dk/src/info/globalcode/sql/dk/CLIParserException.java
java/sql-dk/src/info/globalcode/sql/dk/CLIStarter.java
java/sql-dk/src/info/globalcode/sql/dk/ColorfulPrintWriter.java
java/sql-dk/src/info/globalcode/sql/dk/Constants.java
java/sql-dk/src/info/globalcode/sql/dk/DKException.java
java/sql-dk/src/info/globalcode/sql/dk/DatabaseConnection.java
java/sql-dk/src/info/globalcode/sql/dk/Functions.java
java/sql-dk/src/info/globalcode/sql/dk/InfoLister.java
java/sql-dk/src/info/globalcode/sql/dk/InvalidOptionsException.java
java/sql-dk/src/info/globalcode/sql/dk/NamedParameter.java
java/sql-dk/src/info/globalcode/sql/dk/Parameter.java
java/sql-dk/src/info/globalcode/sql/dk/SQLCommand.java
java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNamed.java
java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNumbered.java
java/sql-dk/src/info/globalcode/sql/dk/SQLType.java
java/sql-dk/src/info/globalcode/sql/dk/Xmlns.java
java/sql-dk/src/info/globalcode/sql/dk/batch/Batch.java
java/sql-dk/src/info/globalcode/sql/dk/batch/BatchConstants.java
java/sql-dk/src/info/globalcode/sql/dk/batch/BatchDecoder.java
java/sql-dk/src/info/globalcode/sql/dk/batch/BatchEncoder.java
java/sql-dk/src/info/globalcode/sql/dk/batch/BatchException.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/CommandArgument.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/ConfigurationException.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/ConfigurationProvider.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/DatabaseDefinition.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/FormatterDefinition.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/Loader.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/NameIdentified.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/Properties.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/Property.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/PropertyDeclaration.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/PropertyDeclarations.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/TunnelDefinition.java
java/sql-dk/src/info/globalcode/sql/dk/configuration/jaxb.index
java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/BarChartFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/ColumnDescriptor.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/ColumnsHeader.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/CommonProperties.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/FakeSqlArray.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/Formatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/FormatterContext.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/FormatterException.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/SilentFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/SingleRecordFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/SingleValueFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularWrappingFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/TeXFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/XhtmlFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/formatting/XmlFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagement.java
java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java
java/sql-dk/src/info/globalcode/sql/dk/jmx/ManagementUtils.java
java/sql-dk/src/info/globalcode/sql/dk/logging/ColorfulConsoleFormatter.java
java/sql-dk/src/info/globalcode/sql/dk/logging/LoggerInitializer.java
java/sql-dk/src/info/globalcode/sql/dk/logging/LoggerProducer.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIOptions.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIParser.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIParserException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIStarter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/ColorfulPrintWriter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/Constants.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/DKException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/DatabaseConnection.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/Functions.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/InfoLister.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/InvalidOptionsException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/NamedParameter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/Parameter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommand.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNamed.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNumbered.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLType.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/Xmlns.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/Batch.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchConstants.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchDecoder.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchEncoder.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/CommandArgument.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Configuration.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/ConfigurationException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/ConfigurationProvider.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/DatabaseDefinition.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/FormatterDefinition.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Loader.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/NameIdentified.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Properties.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Property.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/PropertyDeclaration.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/PropertyDeclarations.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/TunnelDefinition.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/BarChartFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnDescriptor.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnsHeader.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/CommonProperties.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FakeSqlArray.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/Formatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FormatterContext.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FormatterException.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SilentFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SingleRecordFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SingleValueFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularWrappingFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TeXFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XhtmlFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XmlFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ConnectionManagement.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ManagementUtils.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/ColorfulConsoleFormatter.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/LoggerInitializer.java
java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/LoggerProducer.java
java/sql-dk/src/main/resources/info/globalcode/sql/dk/configuration/jaxb.index
java/sql-dk/src/main/resources/info/globalcode/sql/dk/example-config.xml
java/sql-dk/src/main/resources/info/globalcode/sql/dk/formatter/XhtmlFormatter.css
java/sql-dk/src/main/resources/info/globalcode/sql/dk/license.txt
java/sql-dk/src/test/java/info/globalcode/sql/dk/CLIParserTest.java
java/sql-dk/src/test/java/info/globalcode/sql/dk/FunctionsTest.java
java/sql-dk/test/info/globalcode/sql/dk/CLIParserTest.java
java/sql-dk/test/info/globalcode/sql/dk/FunctionsTest.java
--- a/.hgignore	Mon Mar 04 17:06:42 2019 +0100
+++ b/.hgignore	Mon Mar 04 20:15:24 2019 +0100
@@ -3,8 +3,8 @@
 *~
 temp/*
 
-java/sql-dk/data/info/globalcode/sql/dk/version.txt
-java/sql-dk/data/info/globalcode/sql/dk/help.txt
+java/sql-dk/src/main/resources/info/globalcode/sql/dk/version.txt
+java/sql-dk/src/main/resources/info/globalcode/sql/dk/help.txt
 
 syntax: regexp
 
--- a/distributions/debian/build.sh	Mon Mar 04 17:06:42 2019 +0100
+++ b/distributions/debian/build.sh	Mon Mar 04 20:15:24 2019 +0100
@@ -29,12 +29,12 @@
 cp ../../../xml/config.xsd                                                      config.xsd &&
 cp ../../../xml/config.rnc                                                      config.rnc &&
 cp ../../../xml/config.xsl                                                      config.xsl &&
-cp ../../../java/sql-dk/dist/sql-dk.jar                                         sql-dk.jar &&
+cp ../../../java/sql-dk/target/sql-dk-*.jar                                     sql-dk.jar &&
 cp ../../../java/jdbc-loopback-driver/target/jdbc-loopback-driver-*.jar         jdbc-loopback-driver.jar &&
-cp ../../../java/sql-dk/dist/bash-completion.sh     SQL-DK && # TODO: should be sql-dk – name conflict with sql-dk in /usr/bin/ (equivs bug)
+cp ../../../java/sql-dk/target/bash-completion.sh                               SQL-DK && # TODO: should be sql-dk – name conflict with sql-dk in /usr/bin/ (equivs bug)
 
 chmod 755 sql-dk &&
-chmod 755 SQL-DK &&
+chmod 644 SQL-DK &&
 
 EMAIL=`echo c3FsLWRrLmRlYmlhbkBwdWIuZnJhbnRvdm8uY3oK | base64 -d` &&
 NAME="Ing. František Kučera <$EMAIL>" &&
--- a/distributions/fedora/sql-dk.spec	Mon Mar 04 17:06:42 2019 +0100
+++ b/distributions/fedora/sql-dk.spec	Mon Mar 04 20:15:24 2019 +0100
@@ -71,9 +71,9 @@
 cp ../../../../xml/config.xsd                                                      ${RPM_BUILD_ROOT}/usr/share/doc/sql-dk/
 cp ../../../../xml/config.rnc                                                      ${RPM_BUILD_ROOT}/usr/share/doc/sql-dk/
 cp ../../../../xml/config.xsl                                                      ${RPM_BUILD_ROOT}/usr/share/doc/sql-dk/
-cp ../../../../java/sql-dk/dist/sql-dk.jar                                         ${RPM_BUILD_ROOT}/usr/share/sql-dk/
-cp ../../../../java/jdbc-loopback-driver/dist/jdbc-loopback-driver.jar             ${RPM_BUILD_ROOT}/usr/share/sql-dk/
-cp ../../../../java/sql-dk/dist/bash-completion.sh                                 ${RPM_BUILD_ROOT}/etc/bash_completion.d/sql-dk
+cp ../../../../java/sql-dk/target/sql-dk-*.jar                                     ${RPM_BUILD_ROOT}/usr/share/sql-dk/
+cp ../../../../java/jdbc-loopback-driver/target/jdbc-loopback-driver-*.jar         ${RPM_BUILD_ROOT}/usr/share/sql-dk/
+cp ../../../../java/sql-dk/target/bash-completion.sh                               ${RPM_BUILD_ROOT}/etc/bash_completion.d/sql-dk
 
 %files
 %defattr(-,root,root)
@@ -81,4 +81,3 @@
 /usr/share/sql-dk/*
 /usr/share/doc/sql-dk/*
 /etc/bash_completion.d/*
-
--- a/java/sql-dk/bash-completion.sh	Mon Mar 04 17:06:42 2019 +0100
+++ b/java/sql-dk/bash-completion.sh	Mon Mar 04 20:15:24 2019 +0100
@@ -1,8 +1,8 @@
 #!/bin/bash
 
 cat \
-	src/info/globalcode/sql/dk/Constants.java \
-	src/info/globalcode/sql/dk/formatting/* \
-	src/info/globalcode/sql/dk/CLIParser.java \
+	src/main/java/info/globalcode/sql/dk/Constants.java \
+	src/main/java/info/globalcode/sql/dk/formatting/* \
+	src/main/java/info/globalcode/sql/dk/CLIParser.java \
 	| ../../scripts/bash_completion.pl
 
--- a/java/sql-dk/build.xml	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- SQL-DK
- Copyright © 2013 František Kučera (frantovo.cz)
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
--->
-
-<!-- You may freely edit this file. See commented blocks below for -->
-<!-- some examples of how to customize the build. -->
-<!-- (If you delete it and reopen the project it will be recreated.) -->
-<!-- By default, only the Clean and Build commands use this build script. -->
-<!-- Commands such as Run, Debug, and Test only use this build script if -->
-<!-- the Compile on Save feature is turned off for the project. -->
-<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
-<!-- in the project's Project Properties dialog box.-->
-<project name="sql-dk" default="default" basedir=".">
-    <description>Builds, tests, and runs the project sql-dk.</description>
-    <import file="nbproject/build-impl.xml"/>
-    <!--
-
-    There exist several targets which are by default empty and which can be 
-    used for execution of your tasks. These targets are usually executed 
-    before and after some main targets. They are: 
-
-      -pre-init:                 called before initialization of project properties
-      -post-init:                called after initialization of project properties
-      -pre-compile:              called before javac compilation
-      -post-compile:             called after javac compilation
-      -pre-compile-single:       called before javac compilation of single file
-      -post-compile-single:      called after javac compilation of single file
-      -pre-compile-test:         called before javac compilation of JUnit tests
-      -post-compile-test:        called after javac compilation of JUnit tests
-      -pre-compile-test-single:  called before javac compilation of single JUnit test
-      -post-compile-test-single: called after javac compilation of single JUunit test
-      -pre-jar:                  called before JAR building
-      -post-jar:                 called after JAR building
-      -post-clean:               called after cleaning build products
-
-    (Targets beginning with '-' are not intended to be called on their own.)
-
-    Example of inserting an obfuscator after compilation could look like this:
-
-        <target name="-post-compile">
-            <obfuscate>
-                <fileset dir="${build.classes.dir}"/>
-            </obfuscate>
-        </target>
-
-    For list of available properties check the imported 
-    nbproject/build-impl.xml file. 
-
-
-    Another way to customize the build is by overriding existing main targets.
-    The targets of interest are: 
-
-      -init-macrodef-javac:     defines macro for javac compilation
-      -init-macrodef-junit:     defines macro for junit execution
-      -init-macrodef-debug:     defines macro for class debugging
-      -init-macrodef-java:      defines macro for class execution
-      -do-jar-with-manifest:    JAR building (if you are using a manifest)
-      -do-jar-without-manifest: JAR building (if you are not using a manifest)
-      run:                      execution of project 
-      -javadoc-build:           Javadoc generation
-      test-report:              JUnit report generation
-
-    An example of overriding the target for project execution could look like this:
-
-        <target name="run" depends="sql-dk-impl.jar">
-            <exec dir="bin" executable="launcher.exe">
-                <arg file="${dist.jar}"/>
-            </exec>
-        </target>
-
-    Notice that the overridden target depends on the jar target and not only on 
-    the compile target as the regular run target does. Again, for a list of available 
-    properties which you can use, check the target you are overriding in the
-    nbproject/build-impl.xml file. 
-
-	-->
-	
-	<target name="-pre-compile">
-		<exec executable="./version-info.sh" output="data/info/globalcode/sql/dk/version.txt"/>
-		<exec executable="./help-generator.sh" output="data/info/globalcode/sql/dk/help.txt"/>
-	</target>
-	
-	<target name="-post-jar">
-		<exec executable="./bash-completion.sh" output="dist/bash-completion.sh"/>
-	</target>
-	
-</project>
--- a/java/sql-dk/data/info/globalcode/sql/dk/example-config.xml	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../../../../../../../xml/config.xml
\ No newline at end of file
--- a/java/sql-dk/data/info/globalcode/sql/dk/formatter/XhtmlFormatter.css	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-body {
-	font-family: sans-serif;
-	font-size: 16px;
-	padding-left: 16px;
-	padding-right: 16px;
-}
-
-pre {
-	background-color: #ddd;
-	padding: 6px;
-	border-radius: 4px;
-	overflow: auto;
-
-	-moz-tab-size: 4;
-	-o-tab-size: 4;
-	tab-size: 4;
-}
-
-table {
-	border-collapse:collapse;
-	box-shadow: 3px 3px 3px grey;
-	margin-top: 10px;
-	margin-bottom: 20px;
-}
-td, th {
-	border: 1px solid black;
-	padding-top: 4px;
-	padding-bottom: 4px;
-	padding-left: 6px;
-	padding-right: 6px;
-	font-weight: normal;
-}
-td.number {
-	text-align: right;
-}
-td.boolean {
-	text-align: right;
-}
-thead tr {
-	background: #ddd;
-	color:black;
-}
-tbody tr:hover {
-	background-color: #eee;
-	color:black;
-}
-
-table ul {
-	margin: 0px;
-}
-
-table li {
-	padding-right: 10px;
-}
--- a/java/sql-dk/data/info/globalcode/sql/dk/license.txt	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-../../../../../../../license/gpl.txt
\ No newline at end of file
--- a/java/sql-dk/help-generator.sh	Mon Mar 04 17:06:42 2019 +0100
+++ b/java/sql-dk/help-generator.sh	Mon Mar 04 20:15:24 2019 +0100
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 cat \
-	src/info/globalcode/sql/dk/CLIParser.java \
-	src/info/globalcode/sql/dk/CLIStarter.java \
+	src/main/java/info/globalcode/sql/dk/CLIParser.java \
+	src/main/java/info/globalcode/sql/dk/CLIStarter.java \
 	| ../../scripts/help_generator.pl
 
--- a/java/sql-dk/manifest.mf	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-Manifest-Version: 1.0
-X-COMMENT: Main-Class will be added automatically by build
-
--- a/java/sql-dk/nbproject/build-impl.xml	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1429 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-*** GENERATED FROM project.xml - DO NOT EDIT  ***
-***         EDIT ../build.xml INSTEAD         ***
-
-For the purpose of easier reading the script
-is divided into following sections:
-
-  - initialization
-  - compilation
-  - jar
-  - execution
-  - debugging
-  - javadoc
-  - test compilation
-  - test execution
-  - test debugging
-  - applet
-  - cleanup
-
-        -->
-<project xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" basedir=".." default="default" name="sql-dk-impl">
-    <fail message="Please build using Ant 1.8.0 or higher.">
-        <condition>
-            <not>
-                <antversion atleast="1.8.0"/>
-            </not>
-        </condition>
-    </fail>
-    <target depends="test,jar,javadoc" description="Build and test whole project." name="default"/>
-    <!-- 
-                ======================
-                INITIALIZATION SECTION 
-                ======================
-            -->
-    <target name="-pre-init">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="-pre-init" name="-init-private">
-        <property file="nbproject/private/config.properties"/>
-        <property file="nbproject/private/configs/${config}.properties"/>
-        <property file="nbproject/private/private.properties"/>
-    </target>
-    <target depends="-pre-init,-init-private" name="-init-user">
-        <property file="${user.properties.file}"/>
-        <!-- The two properties below are usually overridden -->
-        <!-- by the active platform. Just a fallback. -->
-        <property name="default.javac.source" value="1.6"/>
-        <property name="default.javac.target" value="1.6"/>
-    </target>
-    <target depends="-pre-init,-init-private,-init-user" name="-init-project">
-        <property file="nbproject/configs/${config}.properties"/>
-        <property file="nbproject/project.properties"/>
-    </target>
-    <target depends="-pre-init,-init-private,-init-user,-init-project,-init-macrodef-property" name="-do-init">
-        <property name="platform.java" value="${java.home}/bin/java"/>
-        <available file="${manifest.file}" property="manifest.available"/>
-        <condition property="splashscreen.available">
-            <and>
-                <not>
-                    <equals arg1="${application.splash}" arg2="" trim="true"/>
-                </not>
-                <available file="${application.splash}"/>
-            </and>
-        </condition>
-        <condition property="main.class.available">
-            <and>
-                <isset property="main.class"/>
-                <not>
-                    <equals arg1="${main.class}" arg2="" trim="true"/>
-                </not>
-            </and>
-        </condition>
-        <condition property="profile.available">
-            <and>
-                <isset property="javac.profile"/>
-                <length length="0" string="${javac.profile}" when="greater"/>
-                <matches pattern="((1\.[89])|9)(\..*)?" string="${javac.source}"/>
-            </and>
-        </condition>
-        <condition property="do.archive">
-            <or>
-                <not>
-                    <istrue value="${jar.archive.disabled}"/>
-                </not>
-                <istrue value="${not.archive.disabled}"/>
-            </or>
-        </condition>
-        <condition property="do.mkdist">
-            <and>
-                <isset property="do.archive"/>
-                <isset property="libs.CopyLibs.classpath"/>
-                <not>
-                    <istrue value="${mkdist.disabled}"/>
-                </not>
-            </and>
-        </condition>
-        <condition property="do.archive+manifest.available">
-            <and>
-                <isset property="manifest.available"/>
-                <istrue value="${do.archive}"/>
-            </and>
-        </condition>
-        <condition property="do.archive+main.class.available">
-            <and>
-                <isset property="main.class.available"/>
-                <istrue value="${do.archive}"/>
-            </and>
-        </condition>
-        <condition property="do.archive+splashscreen.available">
-            <and>
-                <isset property="splashscreen.available"/>
-                <istrue value="${do.archive}"/>
-            </and>
-        </condition>
-        <condition property="do.archive+profile.available">
-            <and>
-                <isset property="profile.available"/>
-                <istrue value="${do.archive}"/>
-            </and>
-        </condition>
-        <condition property="have.tests">
-            <or>
-                <available file="${test.src.dir}"/>
-            </or>
-        </condition>
-        <condition property="have.sources">
-            <or>
-                <available file="${src.dir}"/>
-                <available file="${src.data.dir}"/>
-            </or>
-        </condition>
-        <condition property="netbeans.home+have.tests">
-            <and>
-                <isset property="netbeans.home"/>
-                <isset property="have.tests"/>
-            </and>
-        </condition>
-        <condition property="no.javadoc.preview">
-            <and>
-                <isset property="javadoc.preview"/>
-                <isfalse value="${javadoc.preview}"/>
-            </and>
-        </condition>
-        <property name="run.jvmargs" value=""/>
-        <property name="run.jvmargs.ide" value=""/>
-        <property name="javac.compilerargs" value=""/>
-        <property name="work.dir" value="${basedir}"/>
-        <condition property="no.deps">
-            <and>
-                <istrue value="${no.dependencies}"/>
-            </and>
-        </condition>
-        <property name="javac.debug" value="true"/>
-        <property name="javadoc.preview" value="true"/>
-        <property name="application.args" value=""/>
-        <property name="source.encoding" value="${file.encoding}"/>
-        <property name="runtime.encoding" value="${source.encoding}"/>
-        <property name="manifest.encoding" value="${source.encoding}"/>
-        <condition property="javadoc.encoding.used" value="${javadoc.encoding}">
-            <and>
-                <isset property="javadoc.encoding"/>
-                <not>
-                    <equals arg1="${javadoc.encoding}" arg2=""/>
-                </not>
-            </and>
-        </condition>
-        <property name="javadoc.encoding.used" value="${source.encoding}"/>
-        <property name="includes" value="**"/>
-        <property name="excludes" value=""/>
-        <property name="do.depend" value="false"/>
-        <condition property="do.depend.true">
-            <istrue value="${do.depend}"/>
-        </condition>
-        <path id="endorsed.classpath.path" path="${endorsed.classpath}"/>
-        <condition else="" property="endorsed.classpath.cmd.line.arg" value="-Xbootclasspath/p:'${toString:endorsed.classpath.path}'">
-            <and>
-                <isset property="endorsed.classpath"/>
-                <not>
-                    <equals arg1="${endorsed.classpath}" arg2="" trim="true"/>
-                </not>
-            </and>
-        </condition>
-        <condition else="" property="javac.profile.cmd.line.arg" value="-profile ${javac.profile}">
-            <isset property="profile.available"/>
-        </condition>
-        <condition else="false" property="jdkBug6558476">
-            <and>
-                <matches pattern="1\.[56]" string="${java.specification.version}"/>
-                <not>
-                    <os family="unix"/>
-                </not>
-            </and>
-        </condition>
-        <condition else="false" property="javac.fork">
-            <or>
-                <istrue value="${jdkBug6558476}"/>
-                <istrue value="${javac.external.vm}"/>
-            </or>
-        </condition>
-        <property name="jar.index" value="false"/>
-        <property name="jar.index.metainf" value="${jar.index}"/>
-        <property name="copylibs.rebase" value="true"/>
-        <available file="${meta.inf.dir}/persistence.xml" property="has.persistence.xml"/>
-        <condition property="junit.available">
-            <or>
-                <available classname="org.junit.Test" classpath="${run.test.classpath}"/>
-                <available classname="junit.framework.Test" classpath="${run.test.classpath}"/>
-            </or>
-        </condition>
-        <condition property="testng.available">
-            <available classname="org.testng.annotations.Test" classpath="${run.test.classpath}"/>
-        </condition>
-        <condition property="junit+testng.available">
-            <and>
-                <istrue value="${junit.available}"/>
-                <istrue value="${testng.available}"/>
-            </and>
-        </condition>
-        <condition else="testng" property="testng.mode" value="mixed">
-            <istrue value="${junit+testng.available}"/>
-        </condition>
-        <condition else="" property="testng.debug.mode" value="-mixed">
-            <istrue value="${junit+testng.available}"/>
-        </condition>
-        <property name="java.failonerror" value="true"/>
-    </target>
-    <target name="-post-init">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init" name="-init-check">
-        <fail unless="src.dir">Must set src.dir</fail>
-        <fail unless="src.data.dir">Must set src.data.dir</fail>
-        <fail unless="test.src.dir">Must set test.src.dir</fail>
-        <fail unless="build.dir">Must set build.dir</fail>
-        <fail unless="dist.dir">Must set dist.dir</fail>
-        <fail unless="build.classes.dir">Must set build.classes.dir</fail>
-        <fail unless="dist.javadoc.dir">Must set dist.javadoc.dir</fail>
-        <fail unless="build.test.classes.dir">Must set build.test.classes.dir</fail>
-        <fail unless="build.test.results.dir">Must set build.test.results.dir</fail>
-        <fail unless="build.classes.excludes">Must set build.classes.excludes</fail>
-        <fail unless="dist.jar">Must set dist.jar</fail>
-    </target>
-    <target name="-init-macrodef-property">
-        <macrodef name="property" uri="http://www.netbeans.org/ns/j2se-project/1">
-            <attribute name="name"/>
-            <attribute name="value"/>
-            <sequential>
-                <property name="@{name}" value="${@{value}}"/>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-macrodef-javac-with-processors">
-        <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${src.dir}:${src.data.dir}" name="srcdir"/>
-            <attribute default="${build.classes.dir}" name="destdir"/>
-            <attribute default="${javac.classpath}" name="classpath"/>
-            <attribute default="${javac.processorpath}" name="processorpath"/>
-            <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="${javac.debug}" name="debug"/>
-            <attribute default="${empty.dir}" name="sourcepath"/>
-            <attribute default="${empty.dir}" name="gensrcdir"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property location="${build.dir}/empty" name="empty.dir"/>
-                <mkdir dir="${empty.dir}"/>
-                <mkdir dir="@{apgeneratedsrcdir}"/>
-                <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
-                    <src>
-                        <dirset dir="@{gensrcdir}" erroronmissingdir="false">
-                            <include name="*"/>
-                        </dirset>
-                    </src>
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                    <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
-                    <compilerarg line="${javac.profile.cmd.line.arg}"/>
-                    <compilerarg line="${javac.compilerargs}"/>
-                    <compilerarg value="-processorpath"/>
-                    <compilerarg path="@{processorpath}:${empty.dir}"/>
-                    <compilerarg line="${ap.processors.internal}"/>
-                    <compilerarg line="${annotation.processing.processor.options}"/>
-                    <compilerarg value="-s"/>
-                    <compilerarg path="@{apgeneratedsrcdir}"/>
-                    <compilerarg line="${ap.proc.none.internal}"/>
-                    <customize/>
-                </javac>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-ap-cmdline-properties" name="-init-macrodef-javac-without-processors" unless="ap.supported.internal">
-        <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${src.dir}:${src.data.dir}" name="srcdir"/>
-            <attribute default="${build.classes.dir}" name="destdir"/>
-            <attribute default="${javac.classpath}" name="classpath"/>
-            <attribute default="${javac.processorpath}" name="processorpath"/>
-            <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="${javac.debug}" name="debug"/>
-            <attribute default="${empty.dir}" name="sourcepath"/>
-            <attribute default="${empty.dir}" name="gensrcdir"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property location="${build.dir}/empty" name="empty.dir"/>
-                <mkdir dir="${empty.dir}"/>
-                <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
-                    <src>
-                        <dirset dir="@{gensrcdir}" erroronmissingdir="false">
-                            <include name="*"/>
-                        </dirset>
-                    </src>
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                    <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
-                    <compilerarg line="${javac.profile.cmd.line.arg}"/>
-                    <compilerarg line="${javac.compilerargs}"/>
-                    <customize/>
-                </javac>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-javac-with-processors,-init-macrodef-javac-without-processors" name="-init-macrodef-javac">
-        <macrodef name="depend" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${src.dir}:${src.data.dir}" name="srcdir"/>
-            <attribute default="${build.classes.dir}" name="destdir"/>
-            <attribute default="${javac.classpath}" name="classpath"/>
-            <sequential>
-                <depend cache="${build.dir}/depcache" destdir="@{destdir}" excludes="${excludes}" includes="${includes}" srcdir="@{srcdir}">
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                </depend>
-            </sequential>
-        </macrodef>
-        <macrodef name="force-recompile" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${build.classes.dir}" name="destdir"/>
-            <sequential>
-                <fail unless="javac.includes">Must set javac.includes</fail>
-                <pathconvert pathsep="${line.separator}" property="javac.includes.binary">
-                    <path>
-                        <filelist dir="@{destdir}" files="${javac.includes}"/>
-                    </path>
-                    <globmapper from="*.java" to="*.class"/>
-                </pathconvert>
-                <tempfile deleteonexit="true" property="javac.includesfile.binary"/>
-                <echo file="${javac.includesfile.binary}" message="${javac.includes.binary}"/>
-                <delete>
-                    <files includesfile="${javac.includesfile.binary}"/>
-                </delete>
-                <delete>
-                    <fileset file="${javac.includesfile.binary}"/>
-                </delete>
-            </sequential>
-        </macrodef>
-    </target>
-    <target if="${junit.available}" name="-init-macrodef-junit-init">
-        <condition else="false" property="nb.junit.batch" value="true">
-            <and>
-                <istrue value="${junit.available}"/>
-                <not>
-                    <isset property="test.method"/>
-                </not>
-            </and>
-        </condition>
-        <condition else="false" property="nb.junit.single" value="true">
-            <and>
-                <istrue value="${junit.available}"/>
-                <isset property="test.method"/>
-            </and>
-        </condition>
-    </target>
-    <target name="-init-test-properties">
-        <property name="test.binaryincludes" value="&lt;nothing&gt;"/>
-        <property name="test.binarytestincludes" value=""/>
-        <property name="test.binaryexcludes" value=""/>
-    </target>
-    <target if="${nb.junit.single}" name="-init-macrodef-junit-single" unless="${nb.junit.batch}">
-        <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property name="junit.forkmode" value="perTest"/>
-                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
-                    <test methods="@{testmethods}" name="@{testincludes}" todir="${build.test.results.dir}"/>
-                    <syspropertyset>
-                        <propertyref prefix="test-sys-prop."/>
-                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
-                    </syspropertyset>
-                    <formatter type="brief" usefile="false"/>
-                    <formatter type="xml"/>
-                    <jvmarg value="-ea"/>
-                    <customize/>
-                </junit>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-test-properties" if="${nb.junit.batch}" name="-init-macrodef-junit-batch" unless="${nb.junit.single}">
-        <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property name="junit.forkmode" value="perTest"/>
-                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
-                    <batchtest todir="${build.test.results.dir}">
-                        <fileset dir="${test.src.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
-                            <filename name="@{testincludes}"/>
-                        </fileset>
-                        <fileset dir="${build.test.classes.dir}" excludes="@{excludes},${excludes},${test.binaryexcludes}" includes="${test.binaryincludes}">
-                            <filename name="${test.binarytestincludes}"/>
-                        </fileset>
-                    </batchtest>
-                    <syspropertyset>
-                        <propertyref prefix="test-sys-prop."/>
-                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
-                    </syspropertyset>
-                    <formatter type="brief" usefile="false"/>
-                    <formatter type="xml"/>
-                    <jvmarg value="-ea"/>
-                    <customize/>
-                </junit>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-junit-init,-init-macrodef-junit-single, -init-macrodef-junit-batch" if="${junit.available}" name="-init-macrodef-junit"/>
-    <target if="${testng.available}" name="-init-macrodef-testng">
-        <macrodef name="testng" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <condition else="" property="testng.methods.arg" value="@{testincludes}.@{testmethods}">
-                    <isset property="test.method"/>
-                </condition>
-                <union id="test.set">
-                    <fileset dir="${test.src.dir}" excludes="@{excludes},**/*.xml,${excludes}" includes="@{includes}">
-                        <filename name="@{testincludes}"/>
-                    </fileset>
-                </union>
-                <taskdef classname="org.testng.TestNGAntTask" classpath="${run.test.classpath}" name="testng"/>
-                <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="sql-dk" testname="TestNG tests" workingDir="${work.dir}">
-                    <xmlfileset dir="${build.test.classes.dir}" includes="@{testincludes}"/>
-                    <propertyset>
-                        <propertyref prefix="test-sys-prop."/>
-                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
-                    </propertyset>
-                    <customize/>
-                </testng>
-            </sequential>
-        </macrodef>
-    </target>
-    <target name="-init-macrodef-test-impl">
-        <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <element implicit="true" name="customize" optional="true"/>
-            <sequential>
-                <echo>No tests executed.</echo>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-junit" if="${junit.available}" name="-init-macrodef-junit-impl">
-        <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <element implicit="true" name="customize" optional="true"/>
-            <sequential>
-                <j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
-                    <customize/>
-                </j2seproject3:junit>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-testng" if="${testng.available}" name="-init-macrodef-testng-impl">
-        <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <element implicit="true" name="customize" optional="true"/>
-            <sequential>
-                <j2seproject3:testng excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
-                    <customize/>
-                </j2seproject3:testng>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-test-impl,-init-macrodef-junit-impl,-init-macrodef-testng-impl" name="-init-macrodef-test">
-        <macrodef name="test" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <sequential>
-                <j2seproject3:test-impl excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
-                    <customize>
-                        <classpath>
-                            <path path="${run.test.classpath}"/>
-                        </classpath>
-                        <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
-                        <jvmarg line="${run.jvmargs}"/>
-                        <jvmarg line="${run.jvmargs.ide}"/>
-                    </customize>
-                </j2seproject3:test-impl>
-            </sequential>
-        </macrodef>
-    </target>
-    <target if="${junit.available}" name="-init-macrodef-junit-debug" unless="${nb.junit.batch}">
-        <macrodef name="junit-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property name="junit.forkmode" value="perTest"/>
-                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
-                    <test methods="@{testmethods}" name="@{testincludes}" todir="${build.test.results.dir}"/>
-                    <syspropertyset>
-                        <propertyref prefix="test-sys-prop."/>
-                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
-                    </syspropertyset>
-                    <formatter type="brief" usefile="false"/>
-                    <formatter type="xml"/>
-                    <jvmarg value="-ea"/>
-                    <jvmarg line="${debug-args-line}"/>
-                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
-                    <customize/>
-                </junit>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-test-properties" if="${nb.junit.batch}" name="-init-macrodef-junit-debug-batch">
-        <macrodef name="junit-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property name="junit.forkmode" value="perTest"/>
-                <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
-                    <batchtest todir="${build.test.results.dir}">
-                        <fileset dir="${test.src.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
-                            <filename name="@{testincludes}"/>
-                        </fileset>
-                        <fileset dir="${build.test.classes.dir}" excludes="@{excludes},${excludes},${test.binaryexcludes}" includes="${test.binaryincludes}">
-                            <filename name="${test.binarytestincludes}"/>
-                        </fileset>
-                    </batchtest>
-                    <syspropertyset>
-                        <propertyref prefix="test-sys-prop."/>
-                        <mapper from="test-sys-prop.*" to="*" type="glob"/>
-                    </syspropertyset>
-                    <formatter type="brief" usefile="false"/>
-                    <formatter type="xml"/>
-                    <jvmarg value="-ea"/>
-                    <jvmarg line="${debug-args-line}"/>
-                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
-                    <customize/>
-                </junit>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-junit-debug,-init-macrodef-junit-debug-batch" if="${junit.available}" name="-init-macrodef-junit-debug-impl">
-        <macrodef name="test-debug-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <element implicit="true" name="customize" optional="true"/>
-            <sequential>
-                <j2seproject3:junit-debug excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
-                    <customize/>
-                </j2seproject3:junit-debug>
-            </sequential>
-        </macrodef>
-    </target>
-    <target if="${testng.available}" name="-init-macrodef-testng-debug">
-        <macrodef name="testng-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${main.class}" name="testClass"/>
-            <attribute default="" name="testMethod"/>
-            <element name="customize2" optional="true"/>
-            <sequential>
-                <condition else="-testclass @{testClass}" property="test.class.or.method" value="-methods @{testClass}.@{testMethod}">
-                    <isset property="test.method"/>
-                </condition>
-                <condition else="-suitename sql-dk -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
-                    <matches pattern=".*\.xml" string="@{testClass}"/>
-                </condition>
-                <delete dir="${build.test.results.dir}" quiet="true"/>
-                <mkdir dir="${build.test.results.dir}"/>
-                <j2seproject3:debug classname="org.testng.TestNG" classpath="${debug.test.classpath}">
-                    <customize>
-                        <customize2/>
-                        <jvmarg value="-ea"/>
-                        <arg line="${testng.debug.mode}"/>
-                        <arg line="-d ${build.test.results.dir}"/>
-                        <arg line="-listener org.testng.reporters.VerboseReporter"/>
-                        <arg line="${testng.cmd.args}"/>
-                    </customize>
-                </j2seproject3:debug>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-testng-debug" if="${testng.available}" name="-init-macrodef-testng-debug-impl">
-        <macrodef name="testng-debug-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${main.class}" name="testClass"/>
-            <attribute default="" name="testMethod"/>
-            <element implicit="true" name="customize2" optional="true"/>
-            <sequential>
-                <j2seproject3:testng-debug testClass="@{testClass}" testMethod="@{testMethod}">
-                    <customize2/>
-                </j2seproject3:testng-debug>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-junit-debug-impl" if="${junit.available}" name="-init-macrodef-test-debug-junit">
-        <macrodef name="test-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <attribute default="${main.class}" name="testClass"/>
-            <attribute default="" name="testMethod"/>
-            <sequential>
-                <j2seproject3:test-debug-impl excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
-                    <customize>
-                        <classpath>
-                            <path path="${run.test.classpath}"/>
-                        </classpath>
-                        <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
-                        <jvmarg line="${run.jvmargs}"/>
-                        <jvmarg line="${run.jvmargs.ide}"/>
-                    </customize>
-                </j2seproject3:test-debug-impl>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-testng-debug-impl" if="${testng.available}" name="-init-macrodef-test-debug-testng">
-        <macrodef name="test-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${includes}" name="includes"/>
-            <attribute default="${excludes}" name="excludes"/>
-            <attribute default="**" name="testincludes"/>
-            <attribute default="" name="testmethods"/>
-            <attribute default="${main.class}" name="testClass"/>
-            <attribute default="" name="testMethod"/>
-            <sequential>
-                <j2seproject3:testng-debug-impl testClass="@{testClass}" testMethod="@{testMethod}">
-                    <customize2>
-                        <syspropertyset>
-                            <propertyref prefix="test-sys-prop."/>
-                            <mapper from="test-sys-prop.*" to="*" type="glob"/>
-                        </syspropertyset>
-                    </customize2>
-                </j2seproject3:testng-debug-impl>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-init-macrodef-test-debug-junit,-init-macrodef-test-debug-testng" name="-init-macrodef-test-debug"/>
-    <!--
-                pre NB7.2 profiling section; consider it deprecated
-            -->
-    <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile, -profile-init-check" if="profiler.info.jvmargs.agent" name="profile-init"/>
-    <target if="profiler.info.jvmargs.agent" name="-profile-pre-init">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target if="profiler.info.jvmargs.agent" name="-profile-post-init">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target if="profiler.info.jvmargs.agent" name="-profile-init-macrodef-profile">
-        <macrodef name="resolve">
-            <attribute name="name"/>
-            <attribute name="value"/>
-            <sequential>
-                <property name="@{name}" value="${env.@{value}}"/>
-            </sequential>
-        </macrodef>
-        <macrodef name="profile">
-            <attribute default="${main.class}" name="classname"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property environment="env"/>
-                <resolve name="profiler.current.path" value="${profiler.info.pathvar}"/>
-                <java classname="@{classname}" dir="${profiler.info.dir}" failonerror="${java.failonerror}" fork="true" jvm="${profiler.info.jvm}">
-                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
-                    <jvmarg value="${profiler.info.jvmargs.agent}"/>
-                    <jvmarg line="${profiler.info.jvmargs}"/>
-                    <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
-                    <arg line="${application.args}"/>
-                    <classpath>
-                        <path path="${run.classpath}"/>
-                    </classpath>
-                    <syspropertyset>
-                        <propertyref prefix="run-sys-prop."/>
-                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
-                    </syspropertyset>
-                    <customize/>
-                </java>
-            </sequential>
-        </macrodef>
-    </target>
-    <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile" if="profiler.info.jvmargs.agent" name="-profile-init-check">
-        <fail unless="profiler.info.jvm">Must set JVM to use for profiling in profiler.info.jvm</fail>
-        <fail unless="profiler.info.jvmargs.agent">Must set profiler agent JVM arguments in profiler.info.jvmargs.agent</fail>
-    </target>
-    <!--
-                end of pre NB7.2 profiling section
-            -->
-    <target depends="-init-debug-args" name="-init-macrodef-nbjpda">
-        <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
-            <attribute default="${main.class}" name="name"/>
-            <attribute default="${debug.classpath}" name="classpath"/>
-            <attribute default="" name="stopclassname"/>
-            <sequential>
-                <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                </nbjpdastart>
-            </sequential>
-        </macrodef>
-        <macrodef name="nbjpdareload" uri="http://www.netbeans.org/ns/j2se-project/1">
-            <attribute default="${build.classes.dir}" name="dir"/>
-            <sequential>
-                <nbjpdareload>
-                    <fileset dir="@{dir}" includes="${fix.classes}">
-                        <include name="${fix.includes}*.class"/>
-                    </fileset>
-                </nbjpdareload>
-            </sequential>
-        </macrodef>
-    </target>
-    <target name="-init-debug-args">
-        <property name="version-output" value="java version &quot;${ant.java.version}"/>
-        <condition property="have-jdk-older-than-1.4">
-            <or>
-                <contains string="${version-output}" substring="java version &quot;1.0"/>
-                <contains string="${version-output}" substring="java version &quot;1.1"/>
-                <contains string="${version-output}" substring="java version &quot;1.2"/>
-                <contains string="${version-output}" substring="java version &quot;1.3"/>
-            </or>
-        </condition>
-        <condition else="-Xdebug" property="debug-args-line" value="-Xdebug -Xnoagent -Djava.compiler=none">
-            <istrue value="${have-jdk-older-than-1.4}"/>
-        </condition>
-        <condition else="dt_socket" property="debug-transport-by-os" value="dt_shmem">
-            <os family="windows"/>
-        </condition>
-        <condition else="${debug-transport-by-os}" property="debug-transport" value="${debug.transport}">
-            <isset property="debug.transport"/>
-        </condition>
-    </target>
-    <target depends="-init-debug-args" name="-init-macrodef-debug">
-        <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${main.class}" name="classname"/>
-            <attribute default="${debug.classpath}" name="classpath"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <java classname="@{classname}" dir="${work.dir}" failonerror="${java.failonerror}" fork="true">
-                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
-                    <jvmarg line="${debug-args-line}"/>
-                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
-                    <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
-                    <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
-                    <jvmarg line="${run.jvmargs}"/>
-                    <jvmarg line="${run.jvmargs.ide}"/>
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                    <syspropertyset>
-                        <propertyref prefix="run-sys-prop."/>
-                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
-                    </syspropertyset>
-                    <customize/>
-                </java>
-            </sequential>
-        </macrodef>
-    </target>
-    <target name="-init-macrodef-java">
-        <macrodef name="java" uri="http://www.netbeans.org/ns/j2se-project/1">
-            <attribute default="${main.class}" name="classname"/>
-            <attribute default="${run.classpath}" name="classpath"/>
-            <attribute default="jvm" name="jvm"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <java classname="@{classname}" dir="${work.dir}" failonerror="${java.failonerror}" fork="true">
-                    <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
-                    <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
-                    <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
-                    <jvmarg line="${run.jvmargs}"/>
-                    <jvmarg line="${run.jvmargs.ide}"/>
-                    <classpath>
-                        <path path="@{classpath}"/>
-                    </classpath>
-                    <syspropertyset>
-                        <propertyref prefix="run-sys-prop."/>
-                        <mapper from="run-sys-prop.*" to="*" type="glob"/>
-                    </syspropertyset>
-                    <customize/>
-                </java>
-            </sequential>
-        </macrodef>
-    </target>
-    <target name="-init-macrodef-copylibs">
-        <macrodef name="copylibs" uri="http://www.netbeans.org/ns/j2se-project/3">
-            <attribute default="${manifest.file}" name="manifest"/>
-            <element name="customize" optional="true"/>
-            <sequential>
-                <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
-                <pathconvert property="run.classpath.without.build.classes.dir">
-                    <path path="${run.classpath}"/>
-                    <map from="${build.classes.dir.resolved}" to=""/>
-                </pathconvert>
-                <pathconvert pathsep=" " property="jar.classpath">
-                    <path path="${run.classpath.without.build.classes.dir}"/>
-                    <chainedmapper>
-                        <flattenmapper/>
-                        <filtermapper>
-                            <replacestring from=" " to="%20"/>
-                        </filtermapper>
-                        <globmapper from="*" to="lib/*"/>
-                    </chainedmapper>
-                </pathconvert>
-                <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
-                <copylibs compress="${jar.compress}" excludeFromCopy="${copylibs.excludes}" index="${jar.index}" indexMetaInf="${jar.index.metainf}" jarfile="${dist.jar}" manifest="@{manifest}" manifestencoding="UTF-8" rebase="${copylibs.rebase}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
-                    <fileset dir="${build.classes.dir}" excludes="${dist.archive.excludes}"/>
-                    <manifest>
-                        <attribute name="Class-Path" value="${jar.classpath}"/>
-                        <customize/>
-                    </manifest>
-                </copylibs>
-            </sequential>
-        </macrodef>
-    </target>
-    <target name="-init-presetdef-jar">
-        <presetdef name="jar" uri="http://www.netbeans.org/ns/j2se-project/1">
-            <jar compress="${jar.compress}" index="${jar.index}" jarfile="${dist.jar}" manifestencoding="UTF-8">
-                <j2seproject1:fileset dir="${build.classes.dir}" excludes="${dist.archive.excludes}"/>
-            </jar>
-        </presetdef>
-    </target>
-    <target name="-init-ap-cmdline-properties">
-        <property name="annotation.processing.enabled" value="true"/>
-        <property name="annotation.processing.processors.list" value=""/>
-        <property name="annotation.processing.processor.options" value=""/>
-        <property name="annotation.processing.run.all.processors" value="true"/>
-        <property name="javac.processorpath" value="${javac.classpath}"/>
-        <property name="javac.test.processorpath" value="${javac.test.classpath}"/>
-        <condition property="ap.supported.internal" value="true">
-            <not>
-                <matches pattern="1\.[0-5](\..*)?" string="${javac.source}"/>
-            </not>
-        </condition>
-    </target>
-    <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-ap-cmdline-supported">
-        <condition else="" property="ap.processors.internal" value="-processor ${annotation.processing.processors.list}">
-            <isfalse value="${annotation.processing.run.all.processors}"/>
-        </condition>
-        <condition else="" property="ap.proc.none.internal" value="-proc:none">
-            <isfalse value="${annotation.processing.enabled}"/>
-        </condition>
-    </target>
-    <target depends="-init-ap-cmdline-properties,-init-ap-cmdline-supported" name="-init-ap-cmdline">
-        <property name="ap.cmd.line.internal" value=""/>
-    </target>
-    <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init,-post-init,-init-check,-init-macrodef-property,-init-macrodef-javac,-init-macrodef-test,-init-macrodef-test-debug,-init-macrodef-nbjpda,-init-macrodef-debug,-init-macrodef-java,-init-presetdef-jar,-init-ap-cmdline" name="init"/>
-    <!--
-                ===================
-                COMPILATION SECTION
-                ===================
-            -->
-    <target name="-deps-jar-init" unless="built-jar.properties">
-        <property location="${build.dir}/built-jar.properties" name="built-jar.properties"/>
-        <delete file="${built-jar.properties}" quiet="true"/>
-    </target>
-    <target if="already.built.jar.${basedir}" name="-warn-already-built-jar">
-        <echo level="warn" message="Cycle detected: sql-dk was already built"/>
-    </target>
-    <target depends="init,-deps-jar-init" name="deps-jar" unless="no.deps">
-        <mkdir dir="${build.dir}"/>
-        <touch file="${built-jar.properties}" verbose="false"/>
-        <property file="${built-jar.properties}" prefix="already.built.jar."/>
-        <antcall target="-warn-already-built-jar"/>
-        <propertyfile file="${built-jar.properties}">
-            <entry key="${basedir}" value=""/>
-        </propertyfile>
-    </target>
-    <target depends="init,-check-automatic-build,-clean-after-automatic-build" name="-verify-automatic-build"/>
-    <target depends="init" name="-check-automatic-build">
-        <available file="${build.classes.dir}/.netbeans_automatic_build" property="netbeans.automatic.build"/>
-    </target>
-    <target depends="init" if="netbeans.automatic.build" name="-clean-after-automatic-build">
-        <antcall target="clean"/>
-    </target>
-    <target depends="init,deps-jar" name="-pre-pre-compile">
-        <mkdir dir="${build.classes.dir}"/>
-    </target>
-    <target name="-pre-compile">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target if="do.depend.true" name="-compile-depend">
-        <pathconvert property="build.generated.subdirs">
-            <dirset dir="${build.generated.sources.dir}" erroronmissingdir="false">
-                <include name="*"/>
-            </dirset>
-        </pathconvert>
-        <j2seproject3:depend srcdir="${src.dir}:${src.data.dir}:${build.generated.subdirs}"/>
-    </target>
-    <target depends="init,deps-jar,-pre-pre-compile,-pre-compile, -copy-persistence-xml,-compile-depend" if="have.sources" name="-do-compile">
-        <j2seproject3:javac gensrcdir="${build.generated.sources.dir}"/>
-        <copy todir="${build.classes.dir}">
-            <fileset dir="${src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
-            <fileset dir="${src.data.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
-        </copy>
-    </target>
-    <target if="has.persistence.xml" name="-copy-persistence-xml">
-        <mkdir dir="${build.classes.dir}/META-INF"/>
-        <copy todir="${build.classes.dir}/META-INF">
-            <fileset dir="${meta.inf.dir}" includes="persistence.xml orm.xml"/>
-        </copy>
-    </target>
-    <target name="-post-compile">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
-    <target name="-pre-compile-single">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,deps-jar,-pre-pre-compile" name="-do-compile-single">
-        <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
-        <j2seproject3:force-recompile/>
-        <j2seproject3:javac excludes="" gensrcdir="${build.generated.sources.dir}" includes="${javac.includes}" sourcepath="${src.dir}:${src.data.dir}"/>
-    </target>
-    <target name="-post-compile-single">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
-    <!--
-                ====================
-                JAR BUILDING SECTION
-                ====================
-            -->
-    <target depends="init" name="-pre-pre-jar">
-        <dirname file="${dist.jar}" property="dist.jar.dir"/>
-        <mkdir dir="${dist.jar.dir}"/>
-    </target>
-    <target name="-pre-jar">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init" if="do.archive" name="-do-jar-create-manifest" unless="manifest.available">
-        <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
-        <touch file="${tmp.manifest.file}" verbose="false"/>
-    </target>
-    <target depends="init" if="do.archive+manifest.available" name="-do-jar-copy-manifest">
-        <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
-        <copy encoding="${manifest.encoding}" file="${manifest.file}" outputencoding="UTF-8" tofile="${tmp.manifest.file}"/>
-    </target>
-    <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+main.class.available" name="-do-jar-set-mainclass">
-        <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
-            <attribute name="Main-Class" value="${main.class}"/>
-        </manifest>
-    </target>
-    <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+profile.available" name="-do-jar-set-profile">
-        <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
-            <attribute name="Profile" value="${javac.profile}"/>
-        </manifest>
-    </target>
-    <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+splashscreen.available" name="-do-jar-set-splashscreen">
-        <basename file="${application.splash}" property="splashscreen.basename"/>
-        <mkdir dir="${build.classes.dir}/META-INF"/>
-        <copy failonerror="false" file="${application.splash}" todir="${build.classes.dir}/META-INF"/>
-        <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
-            <attribute name="SplashScreen-Image" value="META-INF/${splashscreen.basename}"/>
-        </manifest>
-    </target>
-    <target depends="init,-init-macrodef-copylibs,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen" if="do.mkdist" name="-do-jar-copylibs">
-        <j2seproject3:copylibs manifest="${tmp.manifest.file}"/>
-        <echo level="info">To run this application from the command line without Ant, try:</echo>
-        <property location="${dist.jar}" name="dist.jar.resolved"/>
-        <echo level="info">java -jar "${dist.jar.resolved}"</echo>
-    </target>
-    <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen" if="do.archive" name="-do-jar-jar" unless="do.mkdist">
-        <j2seproject1:jar manifest="${tmp.manifest.file}"/>
-        <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
-        <property location="${dist.jar}" name="dist.jar.resolved"/>
-        <pathconvert property="run.classpath.with.dist.jar">
-            <path path="${run.classpath}"/>
-            <map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
-        </pathconvert>
-        <condition else="" property="jar.usage.message" value="To run this application from the command line without Ant, try:${line.separator}${platform.java} -cp ${run.classpath.with.dist.jar} ${main.class}">
-            <isset property="main.class.available"/>
-        </condition>
-        <condition else="debug" property="jar.usage.level" value="info">
-            <isset property="main.class.available"/>
-        </condition>
-        <echo level="${jar.usage.level}" message="${jar.usage.message}"/>
-    </target>
-    <target depends="-do-jar-copylibs" if="do.archive" name="-do-jar-delete-manifest">
-        <delete>
-            <fileset file="${tmp.manifest.file}"/>
-        </delete>
-    </target>
-    <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen,-do-jar-jar,-do-jar-delete-manifest" name="-do-jar-without-libraries"/>
-    <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen,-do-jar-copylibs,-do-jar-delete-manifest" name="-do-jar-with-libraries"/>
-    <target name="-post-jar">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,compile,-pre-jar,-do-jar-without-libraries,-do-jar-with-libraries,-post-jar" name="-do-jar"/>
-    <target depends="init,compile,-pre-jar,-do-jar,-post-jar" description="Build JAR." name="jar"/>
-    <!--
-                =================
-                EXECUTION SECTION
-                =================
-            -->
-    <target depends="init,compile" description="Run a main class." name="run">
-        <j2seproject1:java>
-            <customize>
-                <arg line="${application.args}"/>
-            </customize>
-        </j2seproject1:java>
-    </target>
-    <target name="-do-not-recompile">
-        <property name="javac.includes.binary" value=""/>
-    </target>
-    <target depends="init,compile-single" name="run-single">
-        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
-        <j2seproject1:java classname="${run.class}"/>
-    </target>
-    <target depends="init,compile-test-single" name="run-test-with-main">
-        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
-        <j2seproject1:java classname="${run.class}" classpath="${run.test.classpath}"/>
-    </target>
-    <!--
-                =================
-                DEBUGGING SECTION
-                =================
-            -->
-    <target depends="init" if="netbeans.home" name="-debug-start-debugger">
-        <j2seproject1:nbjpdastart name="${debug.class}"/>
-    </target>
-    <target depends="init" if="netbeans.home" name="-debug-start-debugger-main-test">
-        <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${debug.class}"/>
-    </target>
-    <target depends="init,compile" name="-debug-start-debuggee">
-        <j2seproject3:debug>
-            <customize>
-                <arg line="${application.args}"/>
-            </customize>
-        </j2seproject3:debug>
-    </target>
-    <target depends="init,compile,-debug-start-debugger,-debug-start-debuggee" description="Debug project in IDE." if="netbeans.home" name="debug"/>
-    <target depends="init" if="netbeans.home" name="-debug-start-debugger-stepinto">
-        <j2seproject1:nbjpdastart stopclassname="${main.class}"/>
-    </target>
-    <target depends="init,compile,-debug-start-debugger-stepinto,-debug-start-debuggee" if="netbeans.home" name="debug-stepinto"/>
-    <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-single">
-        <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
-        <j2seproject3:debug classname="${debug.class}"/>
-    </target>
-    <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-single" if="netbeans.home" name="debug-single"/>
-    <target depends="init,compile-test-single" if="netbeans.home" name="-debug-start-debuggee-main-test">
-        <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
-        <j2seproject3:debug classname="${debug.class}" classpath="${debug.test.classpath}"/>
-    </target>
-    <target depends="init,compile-test-single,-debug-start-debugger-main-test,-debug-start-debuggee-main-test" if="netbeans.home" name="debug-test-with-main"/>
-    <target depends="init" name="-pre-debug-fix">
-        <fail unless="fix.includes">Must set fix.includes</fail>
-        <property name="javac.includes" value="${fix.includes}.java"/>
-    </target>
-    <target depends="init,-pre-debug-fix,compile-single" if="netbeans.home" name="-do-debug-fix">
-        <j2seproject1:nbjpdareload/>
-    </target>
-    <target depends="init,-pre-debug-fix,-do-debug-fix" if="netbeans.home" name="debug-fix"/>
-    <!--
-                =================
-                PROFILING SECTION
-                =================
-            -->
-    <!--
-                pre NB7.2 profiler integration
-            -->
-    <target depends="profile-init,compile" description="Profile a project in the IDE." if="profiler.info.jvmargs.agent" name="-profile-pre72">
-        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
-        <nbprofiledirect>
-            <classpath>
-                <path path="${run.classpath}"/>
-            </classpath>
-        </nbprofiledirect>
-        <profile/>
-    </target>
-    <target depends="profile-init,compile-single" description="Profile a selected class in the IDE." if="profiler.info.jvmargs.agent" name="-profile-single-pre72">
-        <fail unless="profile.class">Must select one file in the IDE or set profile.class</fail>
-        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
-        <nbprofiledirect>
-            <classpath>
-                <path path="${run.classpath}"/>
-            </classpath>
-        </nbprofiledirect>
-        <profile classname="${profile.class}"/>
-    </target>
-    <target depends="profile-init,compile-single" if="profiler.info.jvmargs.agent" name="-profile-applet-pre72">
-        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
-        <nbprofiledirect>
-            <classpath>
-                <path path="${run.classpath}"/>
-            </classpath>
-        </nbprofiledirect>
-        <profile classname="sun.applet.AppletViewer">
-            <customize>
-                <arg value="${applet.url}"/>
-            </customize>
-        </profile>
-    </target>
-    <target depends="profile-init,compile-test-single" if="profiler.info.jvmargs.agent" name="-profile-test-single-pre72">
-        <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
-        <nbprofiledirect>
-            <classpath>
-                <path path="${run.test.classpath}"/>
-            </classpath>
-        </nbprofiledirect>
-        <junit dir="${profiler.info.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" jvm="${profiler.info.jvm}" showoutput="true">
-            <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
-            <jvmarg value="${profiler.info.jvmargs.agent}"/>
-            <jvmarg line="${profiler.info.jvmargs}"/>
-            <test name="${profile.class}"/>
-            <classpath>
-                <path path="${run.test.classpath}"/>
-            </classpath>
-            <syspropertyset>
-                <propertyref prefix="test-sys-prop."/>
-                <mapper from="test-sys-prop.*" to="*" type="glob"/>
-            </syspropertyset>
-            <formatter type="brief" usefile="false"/>
-            <formatter type="xml"/>
-        </junit>
-    </target>
-    <!--
-                end of pre NB72 profiling section
-            -->
-    <target if="netbeans.home" name="-profile-check">
-        <condition property="profiler.configured">
-            <or>
-                <contains casesensitive="true" string="${run.jvmargs.ide}" substring="-agentpath:"/>
-                <contains casesensitive="true" string="${run.jvmargs.ide}" substring="-javaagent:"/>
-            </or>
-        </condition>
-    </target>
-    <target depends="-profile-check,-profile-pre72" description="Profile a project in the IDE." if="profiler.configured" name="profile" unless="profiler.info.jvmargs.agent">
-        <startprofiler/>
-        <antcall target="run"/>
-    </target>
-    <target depends="-profile-check,-profile-single-pre72" description="Profile a selected class in the IDE." if="profiler.configured" name="profile-single" unless="profiler.info.jvmargs.agent">
-        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
-        <startprofiler/>
-        <antcall target="run-single"/>
-    </target>
-    <target depends="-profile-test-single-pre72" description="Profile a selected test in the IDE." name="profile-test-single"/>
-    <target depends="-profile-check" description="Profile a selected test in the IDE." if="profiler.configured" name="profile-test" unless="profiler.info.jvmargs">
-        <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
-        <startprofiler/>
-        <antcall target="test-single"/>
-    </target>
-    <target depends="-profile-check" description="Profile a selected class in the IDE." if="profiler.configured" name="profile-test-with-main">
-        <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
-        <startprofiler/>
-        <antcall target="run-test-with-main"/>
-    </target>
-    <target depends="-profile-check,-profile-applet-pre72" if="profiler.configured" name="profile-applet" unless="profiler.info.jvmargs.agent">
-        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
-        <startprofiler/>
-        <antcall target="run-applet"/>
-    </target>
-    <!--
-                ===============
-                JAVADOC SECTION
-                ===============
-            -->
-    <target depends="init" if="have.sources" name="-javadoc-build">
-        <mkdir dir="${dist.javadoc.dir}"/>
-        <condition else="" property="javadoc.endorsed.classpath.cmd.line.arg" value="-J${endorsed.classpath.cmd.line.arg}">
-            <and>
-                <isset property="endorsed.classpath.cmd.line.arg"/>
-                <not>
-                    <equals arg1="${endorsed.classpath.cmd.line.arg}" arg2=""/>
-                </not>
-            </and>
-        </condition>
-        <condition else="" property="bug5101868workaround" value="*.java">
-            <matches pattern="1\.[56](\..*)?" string="${java.version}"/>
-        </condition>
-        <javadoc additionalparam="-J-Dfile.encoding=${file.encoding} ${javadoc.additionalparam}" author="${javadoc.author}" charset="UTF-8" destdir="${dist.javadoc.dir}" docencoding="UTF-8" encoding="${javadoc.encoding.used}" failonerror="true" noindex="${javadoc.noindex}" nonavbar="${javadoc.nonavbar}" notree="${javadoc.notree}" private="${javadoc.private}" source="${javac.source}" splitindex="${javadoc.splitindex}" use="${javadoc.use}" useexternalfile="true" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}">
-            <classpath>
-                <path path="${javac.classpath}"/>
-            </classpath>
-            <fileset dir="${src.dir}" excludes="${bug5101868workaround},${excludes}" includes="${includes}">
-                <filename name="**/*.java"/>
-            </fileset>
-            <fileset dir="${src.data.dir}" excludes="${bug5101868workaround},${excludes}" includes="${includes}">
-                <filename name="**/*.java"/>
-            </fileset>
-            <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
-                <include name="**/*.java"/>
-                <exclude name="*.java"/>
-            </fileset>
-            <arg line="${javadoc.endorsed.classpath.cmd.line.arg}"/>
-        </javadoc>
-        <copy todir="${dist.javadoc.dir}">
-            <fileset dir="${src.dir}" excludes="${excludes}" includes="${includes}">
-                <filename name="**/doc-files/**"/>
-            </fileset>
-            <fileset dir="${src.data.dir}" excludes="${excludes}" includes="${includes}">
-                <filename name="**/doc-files/**"/>
-            </fileset>
-            <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
-                <include name="**/doc-files/**"/>
-            </fileset>
-        </copy>
-    </target>
-    <target depends="init,-javadoc-build" if="netbeans.home" name="-javadoc-browse" unless="no.javadoc.preview">
-        <nbbrowse file="${dist.javadoc.dir}/index.html"/>
-    </target>
-    <target depends="init,-javadoc-build,-javadoc-browse" description="Build Javadoc." name="javadoc"/>
-    <!--
-                =========================
-                TEST COMPILATION SECTION
-                =========================
-            -->
-    <target depends="init,compile" if="have.tests" name="-pre-pre-compile-test">
-        <mkdir dir="${build.test.classes.dir}"/>
-    </target>
-    <target name="-pre-compile-test">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target if="do.depend.true" name="-compile-test-depend">
-        <j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir="${test.src.dir}"/>
-    </target>
-    <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test">
-        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" processorpath="${javac.test.processorpath}" srcdir="${test.src.dir}"/>
-        <copy todir="${build.test.classes.dir}">
-            <fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
-        </copy>
-    </target>
-    <target name="-post-compile-test">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-do-compile-test,-post-compile-test" name="compile-test"/>
-    <target name="-pre-compile-test-single">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single">
-        <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
-        <j2seproject3:force-recompile destdir="${build.test.classes.dir}"/>
-        <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}" processorpath="${javac.test.processorpath}" sourcepath="${test.src.dir}" srcdir="${test.src.dir}"/>
-        <copy todir="${build.test.classes.dir}">
-            <fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
-        </copy>
-    </target>
-    <target name="-post-compile-test-single">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single,-do-compile-test-single,-post-compile-test-single" name="compile-test-single"/>
-    <!--
-                =======================
-                TEST EXECUTION SECTION
-                =======================
-            -->
-    <target depends="init" if="have.tests" name="-pre-test-run">
-        <mkdir dir="${build.test.results.dir}"/>
-    </target>
-    <target depends="init,compile-test,-pre-test-run" if="have.tests" name="-do-test-run">
-        <j2seproject3:test includes="${includes}" testincludes="**/*Test.java"/>
-    </target>
-    <target depends="init,compile-test,-pre-test-run,-do-test-run" if="have.tests" name="-post-test-run">
-        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
-    </target>
-    <target depends="init" if="have.tests" name="test-report"/>
-    <target depends="init" if="netbeans.home+have.tests" name="-test-browse"/>
-    <target depends="init,compile-test,-pre-test-run,-do-test-run,test-report,-post-test-run,-test-browse" description="Run unit tests." name="test"/>
-    <target depends="init" if="have.tests" name="-pre-test-run-single">
-        <mkdir dir="${build.test.results.dir}"/>
-    </target>
-    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single">
-        <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
-        <j2seproject3:test excludes="" includes="${test.includes}" testincludes="${test.includes}"/>
-    </target>
-    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single" if="have.tests" name="-post-test-run-single">
-        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
-    </target>
-    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single,-post-test-run-single" description="Run single unit test." name="test-single"/>
-    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single-method">
-        <fail unless="test.class">Must select some files in the IDE or set test.class</fail>
-        <fail unless="test.method">Must select some method in the IDE or set test.method</fail>
-        <j2seproject3:test excludes="" includes="${javac.includes}" testincludes="${test.class}" testmethods="${test.method}"/>
-    </target>
-    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-method" if="have.tests" name="-post-test-run-single-method">
-        <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
-    </target>
-    <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-method,-post-test-run-single-method" description="Run single unit test." name="test-single-method"/>
-    <!--
-                =======================
-                TEST DEBUGGING SECTION
-                =======================
-            -->
-    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-debug-start-debuggee-test">
-        <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
-        <j2seproject3:test-debug excludes="" includes="${javac.includes}" testClass="${test.class}" testincludes="${javac.includes}"/>
-    </target>
-    <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-debug-start-debuggee-test-method">
-        <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
-        <fail unless="test.method">Must select some method in the IDE or set test.method</fail>
-        <j2seproject3:test-debug excludes="" includes="${javac.includes}" testClass="${test.class}" testMethod="${test.method}" testincludes="${test.class}" testmethods="${test.method}"/>
-    </target>
-    <target depends="init,compile-test" if="netbeans.home+have.tests" name="-debug-start-debugger-test">
-        <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${test.class}"/>
-    </target>
-    <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test" name="debug-test"/>
-    <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test-method" name="debug-test-method"/>
-    <target depends="init,-pre-debug-fix,compile-test-single" if="netbeans.home" name="-do-debug-fix-test">
-        <j2seproject1:nbjpdareload dir="${build.test.classes.dir}"/>
-    </target>
-    <target depends="init,-pre-debug-fix,-do-debug-fix-test" if="netbeans.home" name="debug-fix-test"/>
-    <!--
-                =========================
-                APPLET EXECUTION SECTION
-                =========================
-            -->
-    <target depends="init,compile-single" name="run-applet">
-        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
-        <j2seproject1:java classname="sun.applet.AppletViewer">
-            <customize>
-                <arg value="${applet.url}"/>
-            </customize>
-        </j2seproject1:java>
-    </target>
-    <!--
-                =========================
-                APPLET DEBUGGING  SECTION
-                =========================
-            -->
-    <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-applet">
-        <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
-        <j2seproject3:debug classname="sun.applet.AppletViewer">
-            <customize>
-                <arg value="${applet.url}"/>
-            </customize>
-        </j2seproject3:debug>
-    </target>
-    <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-applet" if="netbeans.home" name="debug-applet"/>
-    <!--
-                ===============
-                CLEANUP SECTION
-                ===============
-            -->
-    <target name="-deps-clean-init" unless="built-clean.properties">
-        <property location="${build.dir}/built-clean.properties" name="built-clean.properties"/>
-        <delete file="${built-clean.properties}" quiet="true"/>
-    </target>
-    <target if="already.built.clean.${basedir}" name="-warn-already-built-clean">
-        <echo level="warn" message="Cycle detected: sql-dk was already built"/>
-    </target>
-    <target depends="init,-deps-clean-init" name="deps-clean" unless="no.deps">
-        <mkdir dir="${build.dir}"/>
-        <touch file="${built-clean.properties}" verbose="false"/>
-        <property file="${built-clean.properties}" prefix="already.built.clean."/>
-        <antcall target="-warn-already-built-clean"/>
-        <propertyfile file="${built-clean.properties}">
-            <entry key="${basedir}" value=""/>
-        </propertyfile>
-    </target>
-    <target depends="init" name="-do-clean">
-        <delete dir="${build.dir}"/>
-        <delete dir="${dist.dir}" followsymlinks="false" includeemptydirs="true"/>
-    </target>
-    <target name="-post-clean">
-        <!-- Empty placeholder for easier customization. -->
-        <!-- You can override this target in the ../build.xml file. -->
-    </target>
-    <target depends="init,deps-clean,-do-clean,-post-clean" description="Clean build products." name="clean"/>
-    <target name="-check-call-dep">
-        <property file="${call.built.properties}" prefix="already.built."/>
-        <condition property="should.call.dep">
-            <and>
-                <not>
-                    <isset property="already.built.${call.subproject}"/>
-                </not>
-                <available file="${call.script}"/>
-            </and>
-        </condition>
-    </target>
-    <target depends="-check-call-dep" if="should.call.dep" name="-maybe-call-dep">
-        <ant antfile="${call.script}" inheritall="false" target="${call.target}">
-            <propertyset>
-                <propertyref prefix="transfer."/>
-                <mapper from="transfer.*" to="*" type="glob"/>
-            </propertyset>
-        </ant>
-    </target>
-</project>
--- a/java/sql-dk/nbproject/genfiles.properties	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-build.xml.data.CRC32=b51b939b
-build.xml.script.CRC32=f55b3340
-build.xml.stylesheet.CRC32=28e38971@1.56.1.46
-# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
-# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
-nbproject/build-impl.xml.data.CRC32=b51b939b
-nbproject/build-impl.xml.script.CRC32=6a0815e1
-nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48
--- a/java/sql-dk/nbproject/project.properties	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-annotation.processing.enabled=true
-annotation.processing.enabled.in.editor=false
-annotation.processing.processors.list=
-annotation.processing.run.all.processors=true
-annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
-application.title=sql-dk
-application.vendor=fiki
-build.classes.dir=${build.dir}/classes
-build.classes.excludes=**/*.java,**/*.form
-# This directory is removed when the project is cleaned:
-build.dir=build
-build.generated.dir=${build.dir}/generated
-build.generated.sources.dir=${build.dir}/generated-sources
-# Only compile against the classpath explicitly listed here:
-build.sysclasspath=ignore
-build.test.classes.dir=${build.dir}/test/classes
-build.test.results.dir=${build.dir}/test/results
-# Uncomment to specify the preferred debugger connection transport:
-#debug.transport=dt_socket
-debug.classpath=\
-    ${run.classpath}
-debug.test.classpath=\
-    ${run.test.classpath}
-# This directory is removed when the project is cleaned:
-dist.dir=dist
-dist.jar=${dist.dir}/sql-dk.jar
-dist.javadoc.dir=${dist.dir}/javadoc
-endorsed.classpath=
-excludes=
-includes=**
-jar.compress=false
-javac.classpath=
-# Space-separated list of extra javac options
-javac.compilerargs=
-javac.deprecation=false
-javac.processorpath=\
-    ${javac.classpath}
-javac.source=1.8
-javac.target=1.8
-javac.test.classpath=\
-    ${javac.classpath}:\
-    ${build.classes.dir}:\
-    ${libs.testng.classpath}
-javac.test.processorpath=\
-    ${javac.test.classpath}
-javadoc.additionalparam=
-javadoc.author=false
-javadoc.encoding=${source.encoding}
-javadoc.noindex=false
-javadoc.nonavbar=false
-javadoc.notree=false
-javadoc.private=false
-javadoc.splitindex=true
-javadoc.use=true
-javadoc.version=false
-javadoc.windowtitle=
-main.class=info.globalcode.sql.dk.CLIStarter
-manifest.file=manifest.mf
-meta.inf.dir=${src.dir}/META-INF
-mkdist.disabled=false
-platform.active=default_platform
-run.classpath=\
-    ${javac.classpath}:\
-    ${build.classes.dir}
-# Space-separated list of JVM arguments used when running the project.
-# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
-# To set system properties for unit tests define test-sys-prop.name=value:
-run.jvmargs=
-run.test.classpath=\
-    ${javac.test.classpath}:\
-    ${build.test.classes.dir}
-source.encoding=UTF-8
-src.data.dir=data
-src.dir=src
-test.src.dir=test
--- a/java/sql-dk/nbproject/project.xml	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://www.netbeans.org/ns/project/1">
-    <type>org.netbeans.modules.java.j2seproject</type>
-    <configuration>
-        <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
-            <name>sql-dk</name>
-            <source-roots>
-                <root id="src.dir"/>
-                <root id="src.data.dir"/>
-            </source-roots>
-            <test-roots>
-                <root id="test.src.dir"/>
-            </test-roots>
-        </data>
-    </configuration>
-</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/pom.xml	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>info.globalcode.sql.dk</groupId>
+	<artifactId>sql-dk</artifactId>
+	<version>0.10-SNAPSHOT</version>
+	<packaging>jar</packaging>
+	
+	<dependencies>
+		<dependency>
+			<groupId>org.testng</groupId>
+			<artifactId>testng</artifactId>
+			<version>6.9.9</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+	
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<maven.compiler.source>1.8</maven.compiler.source>
+		<maven.compiler.target>1.8</maven.compiler.target>
+	</properties>
+	
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>exec-maven-plugin</artifactId>
+				<version>1.6.0</version>
+				<executions>
+					<execution>
+						<id>version-info</id>
+						<phase>generate-sources</phase>
+						<goals>
+							<goal>exec</goal>
+						</goals>
+						<configuration>
+							<executable>./version-info.sh</executable>
+							<outputFile>src/main/resources/info/globalcode/sql/dk/version.txt</outputFile><!-- TODO: move to target/generated-sources/ -->
+						</configuration>
+					</execution>
+					<execution>
+						<id>help-generator</id>
+						<phase>generate-sources</phase>
+						<goals>
+							<goal>exec</goal>
+						</goals>
+						<configuration>
+							<executable>./help-generator.sh</executable>
+							<outputFile>src/main/resources/info/globalcode/sql/dk/help.txt</outputFile><!-- TODO: move to target/generated-sources/ -->
+						</configuration>
+					</execution>
+					<execution>
+						<id>bash-completion</id>
+						<phase>generate-sources</phase>
+						<goals>
+							<goal>exec</goal>
+						</goals>
+						<configuration>
+							<executable>./bash-completion.sh</executable>
+							<outputFile>target/bash-completion.sh</outputFile>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+	
+</project>
--- a/java/sql-dk/src/info/globalcode/sql/dk/CLIOptions.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,283 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import static info.globalcode.sql.dk.Functions.isNotEmpty;
-import static info.globalcode.sql.dk.Functions.equalz;
-import info.globalcode.sql.dk.InfoLister.InfoType;
-import info.globalcode.sql.dk.configuration.Properties;
-import info.globalcode.sql.dk.configuration.Property;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * Holds options from command line, validates them, combines with configuration and provides derived
- * objects.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class CLIOptions {
-
-	public static final String DEFAULT_NAME_PREFIX = ":";
-	public static final String DEFAULT_NAME_SUFFIX = "(?=([^\\w]|$))";
-	private String sql;
-	private String databaseName;
-	private final Set<String> databaseNamesToTest = new LinkedHashSet<>();
-	private final Set<String> databaseNamesToListProperties = new LinkedHashSet<>();
-	private final Set<String> formatterNamesToListProperties = new LinkedHashSet<>();
-	private String namePrefix = DEFAULT_NAME_PREFIX;
-	private String nameSuffix = DEFAULT_NAME_SUFFIX;
-	private String formatterName;
-	private boolean batch;
-	private final Properties formatterProperties = new Properties();
-	private final Properties databaseProperties = new Properties();
-
-	public enum MODE {
-
-		QUERY_NOW,
-		PREPARE_BATCH,
-		EXECUTE_BATCH,
-		JUST_SHOW_INFO
-	}
-	private final List<NamedParameter> namedParameters = new ArrayList<>();
-	private final List<Parameter> numberedParameters = new ArrayList<>();
-	private final EnumSet<InfoType> showInfo = EnumSet.noneOf(InfoType.class);
-
-	public void validate() throws InvalidOptionsException {
-		InvalidOptionsException e = new InvalidOptionsException();
-
-		MODE mode = getMode();
-		if (mode == null) {
-			e.addProblem(new InvalidOptionsException.OptionProblem("Invalid combination of DB, SQL and BATCH – please specify just 2 of this 3 options"));
-		} else if (mode == MODE.JUST_SHOW_INFO) {
-			if (!namedParameters.isEmpty()) {
-				e.addProblem(new InvalidOptionsException.OptionProblem("Do not use named parameters if just showing info."));
-			}
-			if (!numberedParameters.isEmpty()) {
-				e.addProblem(new InvalidOptionsException.OptionProblem("Do not use numbered parameters if just showing info."));
-			}
-			if (isNotEmpty(sql, false)) {
-				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify SQL if just showing info."));
-			}
-			if (isNotEmpty(databaseName, false)) {
-				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify database if just showing info."));
-			}
-			if (batch) {
-				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify batch if just showing info."));
-			}
-			if (!equalz(namePrefix, DEFAULT_NAME_PREFIX)) {
-				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify name prefix if just showing info."));
-			}
-			if (!equalz(nameSuffix, DEFAULT_NAME_SUFFIX)) {
-				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify name suffix if just showing info."));
-			}
-			if (showInfo.contains(InfoType.CONNECTION) && databaseNamesToTest.isEmpty()) {
-				e.addProblem(new InvalidOptionsException.OptionProblem("Please specify which database should be tested."));
-			}
-			if (showInfo.contains(InfoType.JDBC_PROPERTIES) && databaseNamesToListProperties.isEmpty()) {
-				e.addProblem(new InvalidOptionsException.OptionProblem("Please specify for which database the properties should be listed."));
-			}
-		}
-
-		if (!namedParameters.isEmpty() && !numberedParameters.isEmpty()) {
-			e.addProblem(new InvalidOptionsException.OptionProblem("Named and numbered parameters can not be used together in one command."));
-		}
-
-		try {
-			Pattern.compile(namePrefix + "test" + nameSuffix);
-		} catch (PatternSyntaxException regexException) {
-			e.addProblem(new InvalidOptionsException.OptionProblem("Ivalid regular expression in name prefix or suffix", regexException));
-		}
-
-		if (e.hasProblems()) {
-			throw e;
-		}
-	}
-
-	private boolean hasSql() {
-		return isNotEmpty(getSql(), true);
-	}
-
-	private boolean hasDb() {
-		return isNotEmpty(getDatabaseName(), true);
-	}
-
-	/**
-	 * Depends on options: DB, BATCH, SQL
-	 *
-	 * @return mode | or null if options are not yet initialized or combination of options is
-	 * invalid
-	 */
-	public MODE getMode() {
-		if (hasDb() && !batch && hasSql()) {
-			return MODE.QUERY_NOW;
-		} else if (!hasDb() && batch && hasSql()) {
-			return MODE.PREPARE_BATCH;
-		} else if (hasDb() && batch && !hasSql()) {
-			return MODE.EXECUTE_BATCH;
-		} else {
-			return showInfo.isEmpty() ? null : MODE.JUST_SHOW_INFO;
-		}
-	}
-
-	public String getSql() {
-		return sql;
-	}
-
-	public void setSql(String sql) {
-		this.sql = sql;
-	}
-
-	public String getDatabaseName() {
-		return databaseName;
-	}
-
-	public void setDatabaseName(String databaseName) {
-		this.databaseName = databaseName;
-	}
-
-	public void setBatch(boolean batch) {
-		this.batch = batch;
-	}
-
-	public Collection<NamedParameter> getNamedParameters() {
-		return namedParameters;
-	}
-
-	public List<Parameter> getNumberedParameters() {
-		return numberedParameters;
-	}
-
-	public void addNumberedParameter(Parameter p) {
-		numberedParameters.add(p);
-	}
-
-	public void addNamedParameter(NamedParameter p) {
-		namedParameters.add(p);
-	}
-
-	public Properties getDatabaseProperties() {
-		return databaseProperties;
-	}
-
-	public Properties getFormatterProperties() {
-		return formatterProperties;
-	}
-
-	public void addDatabaseProperty(Property p) {
-		databaseProperties.add(p);
-	}
-
-	public void addFormatterProperty(Property p) {
-		formatterProperties.add(p);
-	}
-
-	/**
-	 * @return regular expression describing the name prefix
-	 */
-	public String getNamePrefix() {
-		return namePrefix;
-	}
-
-	/**
-	 * @param namePrefix
-	 * @see #getNamePrefix()
-	 */
-	public void setNamePrefix(String namePrefix) {
-		this.namePrefix = namePrefix;
-	}
-
-	/**
-	 * @return regular expression describing the name prefix
-	 */
-	public String getNameSuffix() {
-		return nameSuffix;
-	}
-
-	/**
-	 * @param nameSuffix
-	 * @see #getNameSuffix()
-	 */
-	public void setNameSuffix(String nameSuffix) {
-		this.nameSuffix = nameSuffix;
-	}
-
-	public String getFormatterName() {
-		return formatterName;
-	}
-
-	public void setFormatterName(String formatterName) {
-		this.formatterName = formatterName;
-	}
-
-	public void addShowInfo(InfoType info) {
-		showInfo.add(info);
-	}
-
-	public EnumSet<InfoType> getShowInfo() {
-		return showInfo;
-	}
-
-	public Set<String> getDatabaseNamesToTest() {
-		return databaseNamesToTest;
-	}
-
-	public void addDatabaseNameToTest(String name) {
-		databaseNamesToTest.add(name);
-	}
-
-	public Set<String> getDatabaseNamesToListProperties() {
-		return databaseNamesToListProperties;
-	}
-
-	public void addDatabaseNameToListProperties(String name) {
-		databaseNamesToListProperties.add(name);
-	}
-
-	public Set<String> getFormatterNamesToListProperties() {
-		return formatterNamesToListProperties;
-	}
-
-	public void addFormatterNameToListProperties(String name) {
-		formatterNamesToListProperties.add(name);
-	}
-
-	public SQLCommand getSQLCommand() {
-		if (namedParameters.isEmpty()) {
-			return new SQLCommandNumbered(sql, numberedParameters);
-		} else {
-			return new SQLCommandNamed(sql, namedParameters, namePrefix, nameSuffix);
-		}
-	}
-
-	public OutputStream getOutputStream() {
-		return System.out;
-	}
-
-	public InputStream getInputStream() {
-		return System.in;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/CLIParser.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,216 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import static info.globalcode.sql.dk.Functions.readString;
-import info.globalcode.sql.dk.InfoLister.InfoType;
-import info.globalcode.sql.dk.configuration.Property;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Converts command line arguments from String array to object.
- * Checks basic constraints (if only supported options are used and if they have correct number of
- * parameters)
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class CLIParser {
-
-	public static final String TYPE_NAME_SEPARATOR = ":";
-
-	public CLIOptions parseOptions(String[] args, InputStream in) throws CLIParserException {
-		CLIOptions options = new CLIOptions();
-
-		List<SQLType> numberedTypes = new ArrayList<>();
-		Map<String, SQLType> namedTypes = new HashMap<>();
-
-		for (int i = 0; i < args.length; i++) {
-			String arg = args[i];
-			switch (arg) {
-				case Tokens.TYPES:
-					String typesString = fetchNext(args, ++i);
-
-					for (String oneType : typesString.split(",")) {
-						int sepatratorIndex = oneType.indexOf(TYPE_NAME_SEPARATOR);
-						if (sepatratorIndex == -1) {
-							numberedTypes.add(getType(oneType.toUpperCase()));
-						} else {
-							String namePart = oneType.substring(0, sepatratorIndex).trim();
-							String typePart = oneType.substring(sepatratorIndex + TYPE_NAME_SEPARATOR.length(), oneType.length());
-							namedTypes.put(namePart, getType(typePart.toUpperCase()));
-						}
-					}
-					break;
-				case Tokens.NAME_PREFIX:
-					options.setNamePrefix(fetchNext(args, ++i));
-					break;
-				case Tokens.NAME_SUFFIX:
-					options.setNameSuffix(fetchNext(args, ++i));
-					break;
-				case Tokens.DB:
-					options.setDatabaseName(fetchNext(args, ++i));
-					break;
-				case Tokens.SQL:
-					options.setSql(fetchNext(args, ++i));
-					break;
-				case Tokens.SQL_IN:
-					try {
-						options.setSql(readString(in));
-					} catch (IOException e) {
-						throw new CLIParserException("Unable to read SQL from the input stream", e);
-					}
-					break;
-				case Tokens.BATCH:
-					options.setBatch(true);
-					break;
-				case Tokens.DATA: // --data is the last option
-					for (i++; i < args.length; i++) {
-						arg = args[i];
-						Parameter parameter;
-						if (numberedTypes.isEmpty()) {
-							parameter = new Parameter(arg, null);
-						} else {
-							int paramIndex = options.getNumberedParameters().size();
-							SQLType paramType;
-							try {
-								paramType = numberedTypes.get(paramIndex);
-							} catch (IndexOutOfBoundsException e) {
-								throw new CLIParserException("Missing type for parameter #" + paramIndex, e);
-							} catch (NullPointerException e) {
-								throw new CLIParserException("Invalid type definition for parameter #" + paramIndex, e);
-							}
-							parameter = new Parameter(arg, paramType);
-						}
-						options.addNumberedParameter(parameter);
-					}
-					break;
-				case Tokens.DATA_NAMED:
-					for (i++; i < args.length; i++) {
-						String paramName = args[i];
-						String paramValue = fetchNext(args, ++i);
-						options.addNamedParameter(new NamedParameter(paramName, paramValue, namedTypes.get(paramName)));
-					}
-					break;
-				case Tokens.FORMATTER:
-					options.setFormatterName(fetchNext(args, ++i));
-					break;
-				case Tokens.DB_PROPERTY:
-					options.addDatabaseProperty(new Property(fetchNext(args, ++i), fetchNext(args, ++i)));
-					break;
-				case Tokens.FORMATTER_PROPERTY:
-					options.addFormatterProperty(new Property(fetchNext(args, ++i), fetchNext(args, ++i)));
-					break;
-				case Tokens.INFO_HELP:
-					options.addShowInfo(InfoType.HELP);
-					break;
-				case Tokens.INFO_FORMATTERS:
-					options.addShowInfo(InfoType.FORMATTERS);
-					break;
-				case Tokens.INFO_FORMATTER_PROPERTIES:
-					options.addShowInfo(InfoType.FORMATTER_PROPERTIES);
-					options.addFormatterNameToListProperties(fetchNext(args, ++i));
-					break;
-				case Tokens.INFO_LICENSE:
-					options.addShowInfo(InfoType.LICENSE);
-					break;
-				case Tokens.INFO_JAVA_PROPERTIES:
-					options.addShowInfo(InfoType.JAVA_PROPERTIES);
-					break;
-				case Tokens.INFO_ENVIRONMENT_VARIABLES:
-					options.addShowInfo(InfoType.ENVIRONMENT_VARIABLES);
-					break;
-				case Tokens.INFO_TYPES:
-					options.addShowInfo(InfoType.TYPES);
-					break;
-				case Tokens.INFO_VERSION:
-					options.addShowInfo(InfoType.VERSION);
-					break;
-				case Tokens.INFO_JDBC_DRIVERS:
-					options.addShowInfo(InfoType.JDBC_DRIVERS);
-					break;
-				case Tokens.INFO_JDBC_PROPERTIES:
-					options.addShowInfo(InfoType.JDBC_PROPERTIES);
-					options.addDatabaseNameToListProperties(fetchNext(args, ++i));
-					break;
-				case Tokens.INFO_DATABASES:
-					options.addShowInfo(InfoType.DATABASES);
-					break;
-				case Tokens.INFO_CONNECTION:
-					options.addShowInfo(InfoType.CONNECTION);
-					options.addDatabaseNameToTest(fetchNext(args, ++i));
-					break;
-				default:
-					throw new CLIParserException("Unknown option: " + arg);
-			}
-		}
-		return options;
-	}
-
-	private String fetchNext(String[] args, int index) throws CLIParserException {
-		if (index < args.length) {
-			return args[index];
-		} else {
-			throw new CLIParserException("Expecting value for option: " + args[index - 1]);
-		}
-	}
-
-	public static class Tokens {
-
-		// bash-completion:options:
-		public static final String DB = "--db"; // bash-completion:option // help: database name
-		public static final String DB_PROPERTY = "--db-property"; // bash-completion:option // help: name and value
-		public static final String SQL = "--sql"; // bash-completion:option // help: SQL query/command
-		public static final String SQL_IN = "--sql-in"; // bash-completion:option // help: SQL query/command
-		public static final String BATCH = "--batch"; // bash-completion:option // help: batch mode (no argument)
-		public static final String DATA = "--data"; // bash-completion:option // help: list of ordinal parameters
-		public static final String DATA_NAMED = "--data-named"; // bash-completion:option // help: list of named parameters
-		public static final String NAME_PREFIX = "--name-prefix"; // bash-completion:option // help: parameter name prefix – regular expression
-		public static final String NAME_SUFFIX = "--name-suffix"; // bash-completion:option // help: parameter name suffix – regular expression
-		public static final String TYPES = "--types"; // bash-completion:option // help: comma separated list of parameter types
-		public static final String FORMATTER = "--formatter"; // bash-completion:option // help: name of the output formatter
-		public static final String FORMATTER_PROPERTY = "--formatter-property"; // bash-completion:option // help: name and value
-		public static final String INFO_HELP = "--help"; // bash-completion:option // help: print this help
-		public static final String INFO_VERSION = "--version"; // bash-completion:option // help: print version info
-		public static final String INFO_LICENSE = "--license"; // bash-completion:option // help: print license
-		public static final String INFO_JAVA_PROPERTIES = "--list-java-properties"; // bash-completion:option // help: list of Java system properties
-		public static final String INFO_ENVIRONMENT_VARIABLES = "--list-environment-variables"; // bash-completion:option // help: list of environment variables
-		public static final String INFO_FORMATTERS = "--list-formatters"; // bash-completion:option // help: print list of available formatters
-		public static final String INFO_FORMATTER_PROPERTIES = "--list-formatter-properties"; // bash-completion:option // help: print list of available properties for given formatter
-		public static final String INFO_TYPES = "--list-types"; // bash-completion:option // help: print list of available data types
-		public static final String INFO_JDBC_DRIVERS = "--list-jdbc-drivers"; // bash-completion:option // help: list of available JDBC drivers
-		public static final String INFO_JDBC_PROPERTIES = "--list-jdbc-properties"; // bash-completion:option // help: list of available JDBC properties for given database
-		public static final String INFO_DATABASES = "--list-databases"; // bash-completion:option // help: print list of configured databases
-		public static final String INFO_CONNECTION = "--test-connection"; // bash-completion:option // help: test connection to particular database
-
-		private Tokens() {
-		}
-	}
-
-	private SQLType getType(String typeString) throws CLIParserException {
-		try {
-			return SQLType.valueOf(typeString.trim());
-		} catch (IllegalArgumentException e) {
-			throw new CLIParserException("Unsupported type: " + typeString, e);
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/CLIParserException.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class CLIParserException extends DKException {
-
-	public CLIParserException() {
-	}
-
-	public CLIParserException(String message) {
-		super(message);
-	}
-
-	public CLIParserException(Throwable cause) {
-		super(cause);
-	}
-
-	public CLIParserException(String message, Throwable cause) {
-		super(message, cause);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/CLIStarter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,274 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import info.globalcode.sql.dk.configuration.ConfigurationProvider;
-import info.globalcode.sql.dk.CLIOptions.MODE;
-import info.globalcode.sql.dk.batch.Batch;
-import info.globalcode.sql.dk.batch.BatchDecoder;
-import info.globalcode.sql.dk.batch.BatchException;
-import info.globalcode.sql.dk.batch.BatchEncoder;
-import info.globalcode.sql.dk.configuration.Configuration;
-import info.globalcode.sql.dk.configuration.ConfigurationException;
-import info.globalcode.sql.dk.configuration.DatabaseDefinition;
-import info.globalcode.sql.dk.configuration.FormatterDefinition;
-import info.globalcode.sql.dk.configuration.Loader;
-import info.globalcode.sql.dk.configuration.NameIdentified;
-import info.globalcode.sql.dk.configuration.PropertyDeclaration;
-import info.globalcode.sql.dk.formatting.Formatter;
-import info.globalcode.sql.dk.formatting.FormatterContext;
-import info.globalcode.sql.dk.formatting.FormatterException;
-import info.globalcode.sql.dk.jmx.ConnectionManagement;
-import info.globalcode.sql.dk.jmx.ManagementUtils;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.io.PrintWriter;
-import java.sql.SQLException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-import java.util.logging.Logger;
-
-/**
- * Entry point of the command line interface of SQL-DK.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class CLIStarter implements ConfigurationProvider {
-
-	// help:exit-codes
-	public static final int EXIT_SUCCESS = 0; // doc:success
-	public static final int EXIT_UNEXPECTED_ERROR = 1; // doc:unexpected error (probably bug)
-	// 2 is reserved: http://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF
-	public static final int EXIT_SQL_ERROR = 3; // doc:SQL error
-	public static final int EXIT_CLI_PARSE_ERROR = 4; // doc:CLI options parse error
-	public static final int EXIT_CLI_VALIDATE_ERROR = 5; // doc:CLI options validation error
-	public static final int EXIT_CONFIGURATION_ERROR = 6; // doc:configuration error
-	public static final int EXIT_FORMATTING_ERROR = 7; // doc:formatting error
-	public static final int EXIT_BATCH_ERROR = 8; // doc:batch error
-	private static final Logger log = Logger.getLogger(CLIStarter.class.getName());
-	private final CLIOptions options;
-	private final Loader configurationLoader = new Loader();
-	private Configuration configuration;
-
-	public static void main(String[] args) {
-		log.log(Level.FINE, "Starting " + Constants.PROGRAM_NAME);
-		int exitCode;
-
-		if (args.length == 0) {
-			args = new String[]{CLIParser.Tokens.INFO_HELP};
-		}
-
-		try {
-			CLIParser parser = new CLIParser();
-			CLIOptions options = parser.parseOptions(args, System.in);
-			options.validate();
-			CLIStarter starter = new CLIStarter(options);
-			starter.installDefaultConfiguration();
-			starter.process();
-			log.log(Level.FINE, "All done");
-			exitCode = EXIT_SUCCESS;
-		} catch (CLIParserException e) {
-			log.log(Level.SEVERE, "Unable to parse CLI options", e);
-			exitCode = EXIT_CLI_PARSE_ERROR;
-		} catch (InvalidOptionsException e) {
-			log.log(Level.SEVERE, "Invalid CLI options", e);
-			for (InvalidOptionsException.OptionProblem p : e.getProblems()) {
-				LogRecord r = new LogRecord(Level.SEVERE, "Option problem: {0}");
-				r.setThrown(p.getException());
-				r.setParameters(new Object[]{p.getDescription()});
-				log.log(r);
-			}
-			exitCode = EXIT_CLI_VALIDATE_ERROR;
-		} catch (ConfigurationException e) {
-			log.log(Level.SEVERE, "Configuration problem", e);
-			exitCode = EXIT_CONFIGURATION_ERROR;
-		} catch (SQLException e) {
-			log.log(Level.SEVERE, "SQL problem", e);
-			exitCode = EXIT_SQL_ERROR;
-		} catch (FormatterException e) {
-			log.log(Level.SEVERE, "Formatting problem", e);
-			exitCode = EXIT_FORMATTING_ERROR;
-		} catch (BatchException e) {
-			log.log(Level.SEVERE, "Batch problem", e);
-			exitCode = EXIT_BATCH_ERROR;
-		}
-
-		System.exit(exitCode);
-	}
-
-	public CLIStarter(CLIOptions options) {
-		this.options = options;
-	}
-
-	private void process() throws ConfigurationException, SQLException, FormatterException, BatchException {
-		MODE mode = options.getMode();
-
-		/** Show info */
-		if (!options.getShowInfo().isEmpty()) {
-			PrintStream infoOut = mode == MODE.JUST_SHOW_INFO ? System.out : System.err;
-			InfoLister infoLister = new InfoLister(infoOut, this, options);
-			infoLister.showInfo();
-		}
-
-		switch (mode) {
-			case QUERY_NOW:
-				processQueryNow();
-				break;
-			case PREPARE_BATCH:
-				processPrepareBatch();
-				break;
-			case EXECUTE_BATCH:
-				processExecuteBatch();
-				break;
-			case JUST_SHOW_INFO:
-				// already done above
-				break;
-			default:
-				log.log(Level.SEVERE, "Unsupported mode: {0}", mode);
-				break;
-		}
-
-		generateBashCompletion();
-	}
-
-	private void processQueryNow() throws ConfigurationException, SQLException, FormatterException {
-		DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName());
-		FormatterDefinition fd = configuration.getFormatter(options.getFormatterName());
-		ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName());
-
-		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) {
-			log.log(Level.FINE, "Database connected");
-			try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) {
-				c.executeQuery(options.getSQLCommand(), f);
-			}
-		}
-	}
-
-	private void processPrepareBatch() throws BatchException {
-		BatchEncoder enc = new BatchEncoder();
-		int length = enc.encode(options.getSQLCommand(), options.getOutputStream());
-		log.log(Level.FINE, "Prepared batch size: {0} bytes", length);
-	}
-
-	private void processExecuteBatch() throws ConfigurationException, SQLException, FormatterException, BatchException {
-		BatchDecoder dec = new BatchDecoder();
-		Batch b = dec.decode(options.getInputStream());
-
-		DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName());
-		FormatterDefinition fd = configuration.getFormatter(options.getFormatterName());
-		ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName());
-
-		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) {
-			log.log(Level.FINE, "Database connected");
-			try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) {
-				c.executeBatch(b, f);
-			}
-		}
-	}
-
-	@Override
-	public Configuration getConfiguration() throws ConfigurationException {
-		if (configuration == null) {
-			configuration = configurationLoader.loadConfiguration();
-		}
-		return configuration;
-	}
-
-	private void installDefaultConfiguration() throws ConfigurationException {
-		Constants.DIR.mkdir();
-
-		if (Constants.CONFIG_FILE.exists()) {
-			log.log(Level.FINER, "Config file already exists: {0}", Constants.CONFIG_FILE);
-		} else {
-			try {
-				Functions.installResource(Constants.EXAMPLE_CONFIG_FILE, Constants.CONFIG_FILE);
-				log.log(Level.FINE, "Installing default config file: {0}", Constants.CONFIG_FILE);
-			} catch (IOException e) {
-				throw new ConfigurationException("Unable to write example configuration to " + Constants.CONFIG_FILE, e);
-			}
-		}
-	}
-
-	private void generateBashCompletion() {
-		if (configuration == null) {
-			log.log(Level.FINER, "Not writing Bash completion helper files. In order to generate these files please run some command which requires configuration.");
-		} else {
-			try {
-				File dir = new File(Constants.DIR, "bash-completion");
-				dir.mkdir();
-				writeBashCompletionHelperFile(configuration.getDatabases(), new File(dir, "databases"));
-				writeBashCompletionHelperFile(configuration.getAllFormatters(), new File(dir, "formatters"));
-				writeBashCompletionHelperFileForFormatterProperties(new File(dir, "formatter-properties"));
-			} catch (Exception e) {
-				log.log(Level.WARNING, "Unable to generate Bash completion helper files", e);
-			}
-		}
-	}
-
-	private void writeBashCompletionHelperFile(Collection<? extends NameIdentified> items, File target) throws FileNotFoundException {
-		if (Constants.CONFIG_FILE.lastModified() > target.lastModified()) {
-			try (PrintWriter fw = new PrintWriter(target)) {
-				for (NameIdentified dd : items) {
-					fw.println(dd.getName());
-				}
-				fw.close();
-				log.log(Level.FINE, "Bash completion helper file was written: {0}", target);
-			}
-		} else {
-			log.log(Level.FINER, "Not writing Bash completion helper file: {0} because configuration {1} has not been changed", new Object[]{target, Constants.CONFIG_FILE});
-		}
-	}
-
-	private void writeBashCompletionHelperFileForFormatterProperties(File formattersDir) throws ClassNotFoundException, FileNotFoundException {
-		if (Constants.CONFIG_FILE.lastModified() > formattersDir.lastModified()) {
-			// TODO: delete old directory
-			formattersDir.mkdir();
-			for (FormatterDefinition fd : configuration.getAllFormatters()) {
-				File formatterDir = new File(formattersDir, fd.getName());
-				formatterDir.mkdir();
-
-				Class<Formatter> formatterClass = (Class<Formatter>) Class.forName(fd.getClassName());
-				List<Class<? extends Formatter>> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class);
-				Collections.reverse(hierarchy);
-				for (Class<? extends Formatter> c : hierarchy) {
-					for (PropertyDeclaration p : Functions.getPropertyDeclarations(c)) {
-						File propertyDir = new File(formatterDir, p.name());
-						propertyDir.mkdir();
-						File choicesFile = new File(propertyDir, "choices");
-						try (PrintWriter fw = new PrintWriter(choicesFile)) {
-							// TODO: refactor, move
-							if (p.type() == Boolean.class) {
-								fw.println("true");
-								fw.println("false");
-							}
-						}
-					}
-				}
-			}
-			log.log(Level.FINE, "Bash completion helper files was written in: {0}", formattersDir);
-		} else {
-			log.log(Level.FINER, "Not writing Bash completion helper directory: {0} because configuration {1} has not been changed", new Object[]{formattersDir, Constants.CONFIG_FILE});
-		}
-
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/ColorfulPrintWriter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,358 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.util.EnumSet;
-
-/**
- * PrintWriter with convenience methods for printing color and formatted text.
- *
- * Uses ANSI Escape Sequences.
- * See: http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class ColorfulPrintWriter extends PrintWriter {
-
-	public enum TerminalColor {
-
-		Black(30, 40),
-		Red(31, 41),
-		Green(32, 42),
-		Yellow(33, 43),
-		Blue(34, 44),
-		Magenta(35, 45),
-		Cyan(36, 46),
-		White(37, 47);
-		private final int foregroundCode;
-		private final int backgroundCode;
-
-		private TerminalColor(int foregroundCode, int backgroundCode) {
-			this.foregroundCode = foregroundCode;
-			this.backgroundCode = backgroundCode;
-		}
-
-		public int getForegroundCode() {
-			return foregroundCode;
-		}
-
-		public int getBackgroundCode() {
-			return backgroundCode;
-		}
-	}
-
-	public enum TerminalStyle {
-
-		Reset(0),
-		Bright(1),
-		Dim(2),
-		Underscore(4),
-		Blink(5),
-		Reverse(7),
-		Hidden(8);
-		private int code;
-
-		private TerminalStyle(int code) {
-			this.code = code;
-		}
-
-		public int getCode() {
-			return code;
-		}
-	}
-	private final boolean COLOR_ENABLED;
-	private boolean colorful = true;
-
-	public void setStyle(TerminalStyle style) {
-		setStyle(EnumSet.of(style));
-	}
-
-	public void setStyle(EnumSet<TerminalStyle> styles) {
-		printCodes(getStyleCodes(styles));
-	}
-
-	private static int[] getStyleCodes(EnumSet<TerminalStyle> styles) {
-		int[] array = new int[styles.size()];
-		int i = 0;
-		for (TerminalStyle s : styles) {
-			array[i++] = s.getCode();
-		}
-		return array;
-	}
-
-	/**
-	 * Print (usually audible) bell code (\007, \a, ^G)
-	 */
-	public void bell() {
-		print("\007");
-	}
-
-	/**
-	 * Eat the last character
-	 */
-	public void backspace() {
-		print("\b");
-	}
-
-	/**
-	 * Eat n last characters
-	 *
-	 * @param count n
-	 */
-	public void backspace(int count) {
-		for (int i = 0; i < count; i++) {
-			backspace();
-		}
-	}
-
-	/**
-	 * With 100 ms delay and all colors.
-	 *
-	 * @see #printRainbow(java.lang.String, int,
-	 * info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor[])
-	 */
-	public void printRainbow(String string) {
-		printRainbow(string, 100);
-	}
-
-	/**
-	 * With all colors.
-	 *
-	 * @see #printRainbow(java.lang.String, int,
-	 * info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor[])
-	 */
-	public void printRainbow(String string, int delay) {
-		printRainbow(string, delay, TerminalColor.values());
-	}
-
-	/**
-	 * Prints rainbow text – (re)writes same text subsequently in given colors and then in default
-	 * color.
-	 *
-	 * @param string text to be printed, should not contain \n new line (then rainbow does not work
-	 * – use println() after printRainbow() instead)
-	 * @param delay delay between rewrites
-	 * @param colors list of colors to be used
-	 */
-	public void printRainbow(String string, int delay, TerminalColor... colors) {
-		for (TerminalColor c : colors) {
-			print(c, string);
-			try {
-				Thread.sleep(delay);
-			} catch (InterruptedException e) {
-				// no time to sleep
-				break;
-			}
-			backspace(string.length());
-			flush();
-		}
-		print(string);
-	}
-
-	public void setForegroundColor(TerminalColor color) {
-		printCodes(color.getForegroundCode());
-	}
-
-	public void setBackgroundColor(TerminalColor color) {
-		printCodes(color.getBackgroundCode());
-	}
-
-	public void print(TerminalColor foregroundColor, String string) {
-		setForegroundColor(foregroundColor);
-		print(string);
-		resetAll();
-	}
-
-	public void println(TerminalColor foregroundColor, String string) {
-		print(foregroundColor, string);
-		println();
-	}
-
-	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, String string) {
-		setForegroundColor(foregroundColor);
-		setBackgroundColor(backgroundColor);
-		print(string);
-		resetAll();
-	}
-
-	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, String string) {
-		print(foregroundColor, backgroundColor, string);
-		println();
-	}
-
-	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, EnumSet<TerminalStyle> styles, String string) {
-		setForegroundColor(foregroundColor);
-		setBackgroundColor(backgroundColor);
-		setStyle(styles);
-		print(string);
-		resetAll();
-	}
-
-	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, EnumSet<TerminalStyle> styles, String string) {
-		print(foregroundColor, backgroundColor, styles, string);
-		println();
-	}
-
-	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, TerminalStyle style, String string) {
-		print(foregroundColor, backgroundColor, EnumSet.of(style), string);
-	}
-
-	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, TerminalStyle style, String string) {
-		print(foregroundColor, backgroundColor, style, string);
-		println();
-	}
-
-	public void print(TerminalColor foregroundColor, EnumSet<TerminalStyle> styles, String string) {
-		setForegroundColor(foregroundColor);
-		setStyle(styles);
-		print(string);
-		resetAll();
-	}
-
-	public void println(TerminalColor foregroundColor, EnumSet<TerminalStyle> styles, String string) {
-		print(foregroundColor, styles, string);
-		println();
-	}
-
-	public void print(TerminalColor foregroundColor, TerminalStyle style, String string) {
-		print(foregroundColor, EnumSet.of(style), string);
-	}
-
-	public void println(TerminalColor foregroundColor, TerminalStyle style, String string) {
-		print(foregroundColor, style, string);
-		println();
-	}
-
-	public void print(EnumSet<TerminalStyle> styles, String string) {
-		setStyle(styles);
-		print(string);
-		resetAll();
-	}
-
-	public void println(EnumSet<TerminalStyle> styles, String string) {
-		print(styles, string);
-		println();
-	}
-
-	public void print(TerminalStyle style, String string) {
-		print(EnumSet.of(style), string);
-	}
-
-	public void println(TerminalStyle style, String string) {
-		print(style, string);
-		println();
-	}
-
-	public void resetAll() {
-		printCodes(TerminalStyle.Reset.code);
-	}
-
-	private void printCodes(int... codes) {
-		if (COLOR_ENABLED && colorful) {
-			print("\033[");
-			for (int i = 0; i < codes.length; i++) {
-				print(codes[i]);
-				if (i < codes.length - 1 && codes.length > 1) {
-					print(";");
-				}
-			}
-			print("m");
-		}
-	}
-
-	/**
-	 * Colors can be switched on/off during usage of this writer.
-	 *
-	 * @return whether colors are currently turned on
-	 * @see #isColorEnabled()
-	 */
-	public boolean isColorful() {
-		return colorful;
-	}
-
-	/**
-	 * Collors might be definitively disabled in constructor. If not, they can be turned on/off
-	 * during usage of this writer by {@linkplain #setColorful(boolean)}
-	 *
-	 * @return whether colors are allowed for this instance of this class
-	 * @see #isColorful()
-	 */
-	public boolean isColorEnabled() {
-		return COLOR_ENABLED;
-	}
-
-	/**
-	 * @see #isColorful()
-	 * @see #isColorEnabled()
-	 */
-	public void setColorful(boolean colorful) {
-		this.colorful = colorful;
-	}
-
-	public ColorfulPrintWriter(File file) throws FileNotFoundException {
-		super(file);
-		COLOR_ENABLED = true;
-	}
-
-	public ColorfulPrintWriter(OutputStream out) {
-		super(out);
-		COLOR_ENABLED = true;
-	}
-
-	public ColorfulPrintWriter(String fileName) throws FileNotFoundException {
-		super(fileName);
-		COLOR_ENABLED = true;
-	}
-
-	public ColorfulPrintWriter(Writer out) {
-		super(out);
-		COLOR_ENABLED = true;
-	}
-
-	public ColorfulPrintWriter(File file, String csn) throws FileNotFoundException, UnsupportedEncodingException {
-		super(file, csn);
-		COLOR_ENABLED = true;
-	}
-
-	/**
-	 * @param colorEnabled colors might be definitively disabled by this option – this might be more
-	 * optimalizable than dynamic turning off colors by {@linkplain #setColorful(boolean)} which is
-	 * not definitive (colors can be turned on during live of this instance). This might be useful
-	 * if you need an instance of this class but don't need colors at all.
-	 */
-	public ColorfulPrintWriter(OutputStream out, boolean autoFlush, boolean colorEnabled) {
-		super(out, autoFlush);
-		COLOR_ENABLED = colorEnabled;
-	}
-
-	public ColorfulPrintWriter(String fileName, String csn) throws FileNotFoundException, UnsupportedEncodingException {
-		super(fileName, csn);
-		COLOR_ENABLED = true;
-	}
-
-	public ColorfulPrintWriter(Writer out, boolean autoFlush) {
-		super(out, autoFlush);
-		COLOR_ENABLED = true;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/Constants.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import java.io.File;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class Constants {
-
-	public static final String PROGRAM_NAME = "SQL-DK";
-	public static final String JAVA_PACKAGE = Constants.class.getPackage().getName();
-	public static final String WEBSITE = "https://sql-dk.globalcode.info/";
-	public static final String LICENSE_FILE = "info/globalcode/sql/dk/license.txt";
-	public static final String VERSION_FILE = "info/globalcode/sql/dk/version.txt";
-	public static final String HELP_FILE = "info/globalcode/sql/dk/help.txt";
-	private static final File HOME_DIR = new File(System.getProperty("user.home"));
-	/**
-	 * Directory where config and log files are stored.
-	 */
-	public static final File DIR = new File(HOME_DIR, ".sql-dk"); // bash-completion:dir
-	public static final File CONFIG_FILE = new File(DIR, "config.xml");
-	public static final String EXAMPLE_CONFIG_FILE = "info/globalcode/sql/dk/example-config.xml";
-
-	private Constants() {
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/DKException.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-/**
- * TODO: GEC
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class DKException extends Exception {
-
-	public DKException() {
-	}
-
-	public DKException(String message) {
-		super(message);
-	}
-
-	public DKException(Throwable cause) {
-		super(cause);
-	}
-
-	public DKException(String message, Throwable cause) {
-		super(message, cause);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/DatabaseConnection.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,192 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import static info.globalcode.sql.dk.jmx.ConnectionManagement.incrementCounter;
-import static info.globalcode.sql.dk.jmx.ConnectionManagement.resetCounter;
-import info.globalcode.sql.dk.batch.Batch;
-import info.globalcode.sql.dk.batch.BatchException;
-import info.globalcode.sql.dk.configuration.DatabaseDefinition;
-import info.globalcode.sql.dk.configuration.Loader;
-import info.globalcode.sql.dk.configuration.Properties;
-import info.globalcode.sql.dk.formatting.ColumnsHeader;
-import info.globalcode.sql.dk.formatting.Formatter;
-import info.globalcode.sql.dk.jmx.ConnectionManagement;
-import info.globalcode.sql.dk.jmx.ConnectionManagement.COUNTER;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.SQLWarning;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Represents connected database. Is derived from {@linkplain DatabaseDefinition}.
- * Wraps {@linkplain Connection}.
- *
- * Is responsible for executing {@linkplain SQLCommand} and passing results to the
- * {@linkplain Formatter}.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class DatabaseConnection implements AutoCloseable {
-
-	private static final Logger log = Logger.getLogger(DatabaseConnection.class.getName());
-	public static final String JDBC_PROPERTY_USER = "user";
-	public static final String JDBC_PROPERTY_PASSWORD = "password";
-	private final DatabaseDefinition databaseDefinition;
-	private final Connection connection;
-	private final Properties properties;
-	/**
-	 * Could be null = JMX is disabled → must check, see functions in
-	 * {@linkplain ConnectionManagement}
-	 */
-	private final ConnectionManagement connectionMBean;
-
-	/**
-	 *
-	 * @param databaseDefinition DB url, name, password etc.
-	 * @param properties additional properties from CLI
-	 * @param connectionMBean JMX management bean | null = disabled JMX reporting
-	 * @throws SQLException
-	 */
-	public DatabaseConnection(DatabaseDefinition databaseDefinition, Properties properties, ConnectionManagement connectionMBean) throws SQLException {
-		this.databaseDefinition = databaseDefinition;
-		this.properties = properties;
-		this.connectionMBean = connectionMBean;
-		this.connection = Loader.jdbcConnect(databaseDefinition, properties);
-	}
-
-	public void executeQuery(SQLCommand sqlCommand, Formatter formatter) throws SQLException {
-		formatter.writeStartBatch();
-		formatter.writeStartDatabase(databaseDefinition);
-		formatter.writeStartStatement();
-		formatter.writeQuery(sqlCommand.getQuery());
-		formatter.writeParameters(sqlCommand.getParameters());
-		processCommand(sqlCommand, formatter);
-		formatter.writeEndStatement();
-		formatter.writeEndDatabase();
-		formatter.writeEndBatch();
-	}
-
-	public void executeBatch(Batch batch, Formatter formatter) throws SQLException, BatchException {
-		formatter.writeStartBatch();
-		formatter.writeStartDatabase(databaseDefinition);
-		while (batch.hasNext()) {
-			SQLCommand sqlCommand = batch.next();
-			formatter.writeStartStatement();
-			formatter.writeQuery(sqlCommand.getQuery());
-			formatter.writeParameters(sqlCommand.getParameters());
-			processCommand(sqlCommand, formatter);
-			formatter.writeEndStatement();
-		}
-		formatter.writeEndDatabase();
-		formatter.writeEndBatch();
-	}
-
-	private void processCommand(SQLCommand sqlCommand, Formatter formatter) throws SQLException {
-		incrementCounter(connectionMBean, COUNTER.COMMAND);
-		resetCounter(connectionMBean, COUNTER.RECORD_CURRENT);
-
-		try (PreparedStatement ps = sqlCommand.prepareStatement(connection)) {
-			log.log(Level.FINE, "Statement prepared");
-			sqlCommand.parametrize(ps);
-
-			boolean isRS = ps.execute();
-			log.log(Level.FINE, "Statement executed");
-			if (isRS) {
-				try (ResultSet rs = ps.getResultSet()) {
-					processResultSet(rs, formatter);
-				}
-			} else {
-				processUpdateResult(ps, formatter);
-			}
-			logWarnings(ps);
-
-			while (ps.getMoreResults() || ps.getUpdateCount() > -1) {
-				ResultSet rs = ps.getResultSet();
-				if (rs == null) {
-					processUpdateResult(ps, formatter);
-				} else {
-					processResultSet(rs, formatter);
-					rs.close();
-				}
-				logWarnings(ps);
-			}
-		}
-	}
-
-	private void processUpdateResult(PreparedStatement ps, Formatter formatter) throws SQLException {
-		formatter.writeUpdatesResult(ps.getUpdateCount());
-	}
-
-	private void processResultSet(ResultSet rs, Formatter formatter) throws SQLException {
-		formatter.writeStartResultSet(new ColumnsHeader(rs.getMetaData()));
-
-		int columnCount = rs.getMetaData().getColumnCount();
-
-		while (rs.next()) {
-			incrementCounter(connectionMBean, COUNTER.RECORD_CURRENT);
-			incrementCounter(connectionMBean, COUNTER.RECORD_TOTAL);
-
-			formatter.writeStartRow();
-
-			for (int i = 1; i <= columnCount; i++) {
-				formatter.writeColumnValue(rs.getObject(i));
-			}
-
-			formatter.writeEndRow();
-		}
-
-		formatter.writeEndResultSet();
-	}
-
-	private void logWarnings(PreparedStatement ps) throws SQLException {
-		SQLWarning w = ps.getWarnings();
-		while (w != null) {
-			log.log(Level.WARNING, "SQL: {0}", w.getLocalizedMessage());
-			w = w.getNextWarning();
-		}
-		ps.clearWarnings();
-	}
-
-	/**
-	 * Tests if this connection is live.
-	 *
-	 * @return true if test was successful
-	 * @throws SQLException if test fails
-	 */
-	public boolean test() throws SQLException {
-		connection.getAutoCommit();
-		return true;
-	}
-
-	public String getProductName() throws SQLException {
-		return connection.getMetaData().getDatabaseProductName();
-	}
-
-	public String getProductVersion() throws SQLException {
-		return connection.getMetaData().getDatabaseProductVersion();
-	}
-
-	@Override
-	public void close() throws SQLException {
-		connection.close();
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/Functions.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,254 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import info.globalcode.sql.dk.configuration.NameIdentified;
-import info.globalcode.sql.dk.configuration.PropertyDeclaration;
-import info.globalcode.sql.dk.configuration.PropertyDeclarations;
-import info.globalcode.sql.dk.formatting.Formatter;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class Functions {
-
-	private static final String NBSP = " ";
-	private static final Pattern WHITESPACE_TO_REPLACE = Pattern.compile("\\n|\\r|\\t|" + NBSP);
-
-	private Functions() {
-	}
-
-	public static boolean equalz(Object a, Object b) {
-		return a == null ? b == null : a.equals(b);
-	}
-
-	/**
-	 *
-	 * @param text String to be examinated
-	 * @param trim whether text should be trimmed before examination
-	 * @return whether text is not empty and one or more characters long (after prospective trim)
-	 */
-	public static boolean isEmpty(String text, boolean trim) {
-		if (text == null) {
-			return true;
-		} else {
-			if (trim) {
-				text = text.trim();
-			}
-			return text.isEmpty();
-		}
-	}
-
-	/**
-	 * @see #isEmpty(java.lang.String, boolean)
-	 */
-	public static boolean isNotEmpty(String text, boolean trim) {
-		return !isEmpty(text, trim);
-	}
-
-	public boolean isEmpty(Collection c) {
-		return c == null || c.isEmpty();
-	}
-
-	public boolean isNotEmpty(Collection c) {
-		return !isEmpty(c);
-	}
-
-	public boolean isEmpty(Map m) {
-		return m == null || m.isEmpty();
-	}
-
-	public boolean isNotEmpty(Map m) {
-		return !isEmpty(m);
-	}
-
-	/**
-	 * @return empty collection if given one is null | or the original one
-	 */
-	public static <T> Collection<T> notNull(Collection<T> c) {
-		if (c == null) {
-			return Collections.emptyList();
-		} else {
-			return c;
-		}
-	}
-
-	public static <T extends NameIdentified> T findByName(Collection<T> collection, String name) {
-		for (T element : notNull(collection)) {
-			if (element != null && equalz(element.getName(), name)) {
-				return element;
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * Copy file from Java resources to file system.
-	 */
-	public static void installResource(String resourceName, File target) throws IOException {
-		try (BufferedReader reader = new BufferedReader(new InputStreamReader(Functions.class.getClassLoader().getResourceAsStream(resourceName)))) {
-			try (PrintWriter writer = new PrintWriter(target)) {
-				while (true) {
-					String line = reader.readLine();
-					if (line == null) {
-						break;
-					} else {
-						writer.println(line);
-					}
-				}
-			}
-		}
-	}
-
-	public static String rpad(String s, int n) {
-		if (n > 0) {
-			return String.format("%1$-" + n + "s", s);
-		} else {
-			return s;
-		}
-	}
-
-	public static String lpad(String s, int n) {
-		if (n > 0) {
-			return String.format("%1$" + n + "s", s);
-		} else {
-			return s;
-		}
-	}
-
-	public static String repeat(char ch, int count) {
-		char[] array = new char[count];
-		Arrays.fill(array, ch);
-		return new String(array);
-	}
-	private final static char[] HEX_ALPHABET = "0123456789abcdef".toCharArray();
-
-	public static String toHex(byte[] bytes) {
-		char[] hexChars = new char[bytes.length * 2];
-		for (int j = 0; j < bytes.length; j++) {
-			int v = bytes[j] & 0xFF;
-			hexChars[j * 2] = HEX_ALPHABET[v >>> 4];
-			hexChars[j * 2 + 1] = HEX_ALPHABET[v & 0x0F];
-		}
-		return new String(hexChars);
-	}
-
-	public static String readString(InputStream in) throws IOException {
-		try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
-			StringBuilder result = new StringBuilder();
-			for (String line = br.readLine(); line != null; line = br.readLine()) {
-				result.append(line);
-				result.append('\n');
-			}
-			return result.toString();
-		}
-	}
-
-	/**
-	 * @param <P> type of the last parent
-	 * @param <T> type of the examined class
-	 * @param type examined class
-	 * @param lastParent the last parent type to stop at
-	 * @return list of types starting with <code>type</code> and ending with <code>lastParent</code>
-	 */
-	public static <P, T extends P> List<Class<? extends P>> getClassHierarchy(Class<T> type, Class<P> lastParent) {
-		List<Class<? extends P>> hierarchy = new ArrayList<>();
-
-		for (Class current = type; current != null && lastParent.isAssignableFrom(current); current = current.getSuperclass()) {
-			hierarchy.add(current);
-		}
-
-		return hierarchy;
-	}
-
-	public static PropertyDeclaration[] getPropertyDeclarations(Class<? extends Formatter> formatterClass) {
-		PropertyDeclarations properties = formatterClass.getAnnotation(PropertyDeclarations.class);
-
-		if (properties == null) {
-			PropertyDeclaration p = formatterClass.getAnnotation(PropertyDeclaration.class);
-			return p == null ? new PropertyDeclaration[]{} : new PropertyDeclaration[]{p};
-		} else {
-			return properties.value();
-		}
-	}
-
-	/**
-	 * TODO: support background or styles and move to ColorfulPrintWriter
-	 *
-	 * @param out
-	 * @param valueString
-	 * @param basicColor
-	 * @param escapeColor
-	 */
-	public static void printValueWithWhitespaceReplaced(ColorfulPrintWriter out, String valueString, ColorfulPrintWriter.TerminalColor basicColor, ColorfulPrintWriter.TerminalColor escapeColor) {
-
-		Matcher m = WHITESPACE_TO_REPLACE.matcher(valueString);
-
-		int start = 0;
-
-		while (m.find(start)) {
-
-			printColorOrNot(out, basicColor, valueString.substring(start, m.start()));
-
-			switch (m.group()) {
-				case "\n":
-					out.print(escapeColor, "↲");
-					break;
-				case "\r":
-					out.print(escapeColor, "⏎");
-					break;
-				case "\t":
-					out.print(escapeColor, "↹");
-					break;
-				case NBSP:
-					out.print(escapeColor, "⎵");
-					break;
-				default:
-					throw new IllegalStateException("Unexpected whitespace token: „" + m.group() + "“");
-			}
-
-			start = m.end();
-		}
-
-		printColorOrNot(out, basicColor, valueString.substring(start, valueString.length()));
-	}
-
-	private static void printColorOrNot(ColorfulPrintWriter out, ColorfulPrintWriter.TerminalColor color, String text) {
-		if (color == null) {
-			out.print(text);
-		} else {
-			out.print(color, text);
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/InfoLister.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,673 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import info.globalcode.sql.dk.configuration.CommandArgument;
-import info.globalcode.sql.dk.configuration.Configuration;
-import info.globalcode.sql.dk.configuration.ConfigurationException;
-import info.globalcode.sql.dk.configuration.ConfigurationProvider;
-import info.globalcode.sql.dk.configuration.DatabaseDefinition;
-import info.globalcode.sql.dk.configuration.FormatterDefinition;
-import info.globalcode.sql.dk.configuration.Properties;
-import info.globalcode.sql.dk.configuration.Property;
-import info.globalcode.sql.dk.configuration.PropertyDeclaration;
-import info.globalcode.sql.dk.configuration.TunnelDefinition;
-import info.globalcode.sql.dk.formatting.ColumnsHeader;
-import info.globalcode.sql.dk.formatting.CommonProperties;
-import info.globalcode.sql.dk.formatting.FakeSqlArray;
-import info.globalcode.sql.dk.formatting.Formatter;
-import info.globalcode.sql.dk.formatting.FormatterContext;
-import info.globalcode.sql.dk.formatting.FormatterException;
-import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
-import java.sql.Array;
-import java.sql.Driver;
-import java.sql.DriverManager;
-import java.sql.DriverPropertyInfo;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.ServiceLoader;
-import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-import java.util.logging.Logger;
-import javax.sql.rowset.RowSetMetaDataImpl;
-
-/**
- * Displays info like help, version etc.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class InfoLister {
-
-	private static final Logger log = Logger.getLogger(InfoLister.class.getName());
-	/**
-	 * Fake database name for output formatting
-	 */
-	public static final String CONFIG_DB_NAME = "sqldk_configuration";
-	private final PrintStream out;
-	private final ConfigurationProvider configurationProvider;
-	private final CLIOptions options;
-	private Formatter formatter;
-
-	public InfoLister(PrintStream out, ConfigurationProvider configurationProvider, CLIOptions options) {
-		this.out = out;
-		this.configurationProvider = configurationProvider;
-		this.options = options;
-	}
-
-	public void showInfo() throws ConfigurationException, FormatterException {
-		EnumSet<InfoType> commands = options.getShowInfo();
-
-		boolean formattinNeeded = false;
-
-		for (InfoType infoType : commands) {
-			switch (infoType) {
-				case CONNECTION:
-				case JDBC_DRIVERS:
-				case JDBC_PROPERTIES:
-				case DATABASES:
-				case FORMATTERS:
-				case FORMATTER_PROPERTIES:
-				case TYPES:
-				case JAVA_PROPERTIES:
-				case ENVIRONMENT_VARIABLES:
-					formattinNeeded = true;
-					break;
-			}
-		}
-
-		if (formattinNeeded) {
-			try (Formatter f = getFormatter()) {
-				formatter = f;
-				formatter.writeStartBatch();
-				DatabaseDefinition dd = new DatabaseDefinition();
-				dd.setName(CONFIG_DB_NAME);
-				formatter.writeStartDatabase(dd);
-				showInfos(commands);
-				formatter.writeEndDatabase();
-				formatter.writeEndBatch();
-				formatter.close();
-			}
-		} else {
-			showInfos(commands);
-		}
-	}
-
-	private void showInfos(EnumSet<InfoType> commands) throws ConfigurationException, FormatterException {
-		for (InfoType infoType : commands) {
-			infoType.showInfo(this);
-		}
-	}
-
-	private void listJavaProperties() throws FormatterException, ConfigurationException {
-		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("value", SQLType.VARCHAR));
-		List<Object[]> data = new ArrayList<>();
-		for (Entry<Object, Object> e : System.getProperties().entrySet()) {
-			data.add(new Object[]{e.getKey(), e.getValue()});
-		}
-		printTable(formatter, header, "-- Java system properties", null, data, 0);
-	}
-
-	private void listEnvironmentVariables() throws FormatterException, ConfigurationException {
-		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("value", SQLType.VARCHAR));
-		List<Object[]> data = new ArrayList<>();
-		for (Entry<String, String> e : System.getenv().entrySet()) {
-			data.add(new Object[]{e.getKey(), e.getValue()});
-		}
-		printTable(formatter, header, "-- environment variables", null, data, 0);
-	}
-
-	private void listFormatters() throws ConfigurationException, FormatterException {
-		ColumnsHeader header = constructHeader(
-				new HeaderField("name", SQLType.VARCHAR),
-				new HeaderField("built_in", SQLType.BOOLEAN),
-				new HeaderField("default", SQLType.BOOLEAN),
-				new HeaderField("class_name", SQLType.VARCHAR),
-				new HeaderField("valid", SQLType.BOOLEAN));
-		List<Object[]> data = new ArrayList<>();
-
-		String defaultFormatter = configurationProvider.getConfiguration().getDefaultFormatter();
-		defaultFormatter = defaultFormatter == null ? Configuration.DEFAULT_FORMATTER : defaultFormatter;
-
-		for (FormatterDefinition fd : configurationProvider.getConfiguration().getBuildInFormatters()) {
-			data.add(new Object[]{fd.getName(), true, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)});
-		}
-
-		for (FormatterDefinition fd : configurationProvider.getConfiguration().getFormatters()) {
-			data.add(new Object[]{fd.getName(), false, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)});
-		}
-
-		printTable(formatter, header, "-- configured and built-in output formatters", null, data);
-	}
-
-	private boolean isInstantiable(FormatterDefinition fd) {
-		try {
-			try (ByteArrayOutputStream testStream = new ByteArrayOutputStream()) {
-				fd.getInstance(new FormatterContext(testStream, new Properties(0)));
-				return true;
-			}
-		} catch (Exception e) {
-			log.log(Level.SEVERE, "Unable to create an instance of formatter: " + fd.getName(), e);
-			return false;
-		}
-	}
-
-	private void listFormatterProperties() throws FormatterException, ConfigurationException {
-		for (String formatterName : options.getFormatterNamesToListProperties()) {
-			listFormatterProperties(formatterName);
-		}
-	}
-
-	private void listFormatterProperties(String formatterName) throws FormatterException, ConfigurationException {
-		FormatterDefinition fd = configurationProvider.getConfiguration().getFormatter(formatterName);
-		try {
-
-			// currently only for debugging purposes
-			// TODO: introduce --info-lister-property or generic filtering capability in printTable() ?
-			boolean printDeclaredIn = options.getFormatterProperties().getBoolean("InfoLister:print:declared_in", false);
-
-			List<HeaderField> headerFields = new ArrayList<>();
-			headerFields.add(new HeaderField("name", SQLType.VARCHAR));
-			headerFields.add(new HeaderField("type", SQLType.VARCHAR));
-			headerFields.add(new HeaderField("default", SQLType.VARCHAR));
-			headerFields.add(new HeaderField("description", SQLType.VARCHAR));
-			if (printDeclaredIn) {
-				headerFields.add(new HeaderField("declared_in", SQLType.VARCHAR));
-			}
-
-			ColumnsHeader header = constructHeader(headerFields.toArray(new HeaderField[0]));
-
-			Map<String, Object[]> data = new HashMap<>();
-			Class<Formatter> formatterClass = (Class<Formatter>) Class.forName(fd.getClassName());
-			List<Class<? extends Formatter>> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class);
-			Collections.reverse(hierarchy);
-			hierarchy.stream().forEach((c) -> {
-				for (PropertyDeclaration p : Functions.getPropertyDeclarations(c)) {
-					data.put(p.name(), propertyDeclarationToRow(p, c, printDeclaredIn));
-				}
-			});
-
-			List<Parameter> parameters = new ArrayList<>();
-			parameters.add(new NamedParameter("formatter", formatterName, SQLType.VARCHAR));
-
-			printTable(formatter, header, "-- formatter properties", parameters, new ArrayList<>(data.values()));
-		} catch (ClassNotFoundException e) {
-			throw new ConfigurationException("Unable to find class " + fd.getClassName() + " of formatter" + fd.getName(), e);
-		}
-	}
-
-	private static Object[] propertyDeclarationToRow(PropertyDeclaration p, Class formatterClass, boolean printDeclaredIn) {
-		List list = new ArrayList();
-
-		list.add(p.name());
-		list.add(CommonProperties.getSimpleTypeName(p.type()));
-		list.add(p.defaultValue());
-		list.add(p.description());
-		if (printDeclaredIn) {
-			list.add(formatterClass.getName());
-		}
-
-		return list.toArray();
-	}
-
-	private void listTypes() throws FormatterException, ConfigurationException {
-		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("code", SQLType.INTEGER));
-		List<Object[]> data = new ArrayList<>();
-		for (SQLType sqlType : SQLType.values()) {
-			data.add(new Object[]{sqlType.name(), sqlType.getCode()});
-		}
-		printTable(formatter, header, "-- data types", null, data);
-		log.log(Level.INFO, "Type names in --types option are case insensitive");
-	}
-
-	private void listDatabases() throws ConfigurationException, FormatterException {
-		ColumnsHeader header = constructHeader(
-				new HeaderField("database_name", SQLType.VARCHAR),
-				new HeaderField("user_name", SQLType.VARCHAR),
-				new HeaderField("database_url", SQLType.VARCHAR));
-		List<Object[]> data = new ArrayList<>();
-
-		final List<DatabaseDefinition> configuredDatabases = configurationProvider.getConfiguration().getDatabases();
-		if (configuredDatabases.isEmpty()) {
-			log.log(Level.WARNING, "No databases are configured.");
-		} else {
-			for (DatabaseDefinition dd : configuredDatabases) {
-				data.add(new Object[]{dd.getName(), dd.getUserName(), dd.getUrl()});
-
-				final TunnelDefinition tunnel = dd.getTunnel();
-				if (tunnel != null) {
-					log.log(Level.INFO, "Tunnel command: {0}", tunnel.getCommand());
-					for (CommandArgument ca : Functions.notNull(tunnel.getArguments())) {
-						log.log(Level.INFO, "\targument: {0}/{1}", new Object[]{ca.getType(), ca.getValue()});
-					}
-				}
-
-			}
-		}
-
-		printTable(formatter, header, "-- configured databases", null, data);
-	}
-
-	private void listJdbcDrivers() throws FormatterException, ConfigurationException {
-		ColumnsHeader header = constructHeader(
-				new HeaderField("class", SQLType.VARCHAR),
-				new HeaderField("version", SQLType.VARCHAR),
-				new HeaderField("major", SQLType.INTEGER),
-				new HeaderField("minor", SQLType.INTEGER),
-				new HeaderField("jdbc_compliant", SQLType.BOOLEAN));
-		List<Object[]> data = new ArrayList<>();
-
-		final ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
-		for (Driver d : drivers) {
-			data.add(new Object[]{
-				d.getClass().getName(),
-				d.getMajorVersion() + "." + d.getMinorVersion(),
-				d.getMajorVersion(),
-				d.getMinorVersion(),
-				d.jdbcCompliant()
-			});
-		}
-
-		printTable(formatter, header, "-- discovered JDBC drivers (available on the CLASSPATH)", null, data);
-	}
-
-	private void listJdbcProperties() throws FormatterException, ConfigurationException {
-		for (String dbName : options.getDatabaseNamesToListProperties()) {
-			ColumnsHeader header = constructHeader(
-					new HeaderField("property_name", SQLType.VARCHAR),
-					new HeaderField("required", SQLType.BOOLEAN),
-					new HeaderField("choices", SQLType.ARRAY),
-					new HeaderField("configured_value", SQLType.VARCHAR),
-					new HeaderField("description", SQLType.VARCHAR));
-			List<Object[]> data = new ArrayList<>();
-
-			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
-
-			Driver driver = findDriver(dd);
-
-			if (driver == null) {
-				log.log(Level.WARNING, "No JDBC driver was found for DB: {0} with URL: {1}", new Object[]{dd.getName(), dd.getUrl()});
-			} else {
-				log.log(Level.INFO, "For DB: {0} was found JDBC driver: {1}", new Object[]{dd.getName(), driver.getClass().getName()});
-
-				try {
-					DriverPropertyInfo[] propertyInfos = driver.getPropertyInfo(dd.getUrl(), dd.getProperties().getJavaProperties());
-
-					Set<String> standardProperties = new HashSet<>();
-
-					for (DriverPropertyInfo pi : propertyInfos) {
-						Array choices = new FakeSqlArray(pi.choices, SQLType.VARCHAR);
-						data.add(new Object[]{
-							pi.name,
-							pi.required,
-							choices.getArray() == null ? "" : choices,
-							pi.value == null ? "" : pi.value,
-							pi.description
-						});
-						standardProperties.add(pi.name);
-					}
-
-					for (Property p : dd.getProperties()) {
-						if (!standardProperties.contains(p.getName())) {
-							data.add(new Object[]{
-								p.getName(),
-								"",
-								"",
-								p.getValue(),
-								""
-							});
-							log.log(Level.WARNING, "Your configuration contains property „{0}“ not declared by the JDBC driver.", p.getName());
-						}
-					}
-
-				} catch (SQLException e) {
-					log.log(Level.WARNING, "Error during getting property infos.", e);
-				}
-
-				List<Parameter> parameters = new ArrayList<>();
-				parameters.add(new NamedParameter("database", dbName, SQLType.VARCHAR));
-				parameters.add(new NamedParameter("driver_class", driver.getClass().getName(), SQLType.VARCHAR));
-				parameters.add(new NamedParameter("driver_major_version", driver.getMajorVersion(), SQLType.INTEGER));
-				parameters.add(new NamedParameter("driver_minor_version", driver.getMinorVersion(), SQLType.INTEGER));
-
-				printTable(formatter, header, "-- configured and configurable JDBC driver properties", parameters, data);
-			}
-		}
-
-	}
-
-	private Driver findDriver(DatabaseDefinition dd) {
-		final ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
-		for (Driver d : drivers) {
-			try {
-				if (d.acceptsURL(dd.getUrl())) {
-					return d;
-				}
-			} catch (SQLException e) {
-				log.log(Level.WARNING, "Error during finding JDBC driver for: " + dd.getName(), e);
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Parallelism for connection testing – maximum concurrent database connections.
-	 */
-	private static final int TESTING_THREAD_COUNT = 64;
-	/**
-	 * Time limit for all connection testing threads – particular timeouts per connection will be
-	 * much smaller.
-	 */
-	private static final long TESTING_AWAIT_LIMIT = 1;
-	private static final TimeUnit TESTING_AWAIT_UNIT = TimeUnit.DAYS;
-
-	private void testConnections() throws FormatterException, ConfigurationException {
-		ColumnsHeader header = constructHeader(
-				new HeaderField("database_name", SQLType.VARCHAR),
-				new HeaderField("configured", SQLType.BOOLEAN),
-				new HeaderField("connected", SQLType.BOOLEAN),
-				new HeaderField("product_name", SQLType.VARCHAR),
-				new HeaderField("product_version", SQLType.VARCHAR));
-
-		log.log(Level.FINE, "Testing DB connections in {0} threads", TESTING_THREAD_COUNT);
-
-		ExecutorService es = Executors.newFixedThreadPool(TESTING_THREAD_COUNT);
-
-		final Formatter currentFormatter = formatter;
-
-		printHeader(currentFormatter, header, "-- database configuration and connectivity test", null);
-
-		for (final String dbName : options.getDatabaseNamesToTest()) {
-			preloadDriver(dbName);
-		}
-
-		for (final String dbName : options.getDatabaseNamesToTest()) {
-			es.submit(() -> {
-				final Object[] row = testConnection(dbName);
-				synchronized (currentFormatter) {
-					printRow(currentFormatter, row);
-				}
-			}
-			);
-		}
-
-		es.shutdown();
-
-		try {
-			log.log(Level.FINEST, "Waiting for test results: {0} {1}", new Object[]{TESTING_AWAIT_LIMIT, TESTING_AWAIT_UNIT.name()});
-			boolean finished = es.awaitTermination(TESTING_AWAIT_LIMIT, TESTING_AWAIT_UNIT);
-			if (finished) {
-				log.log(Level.FINEST, "All testing threads finished in time limit.");
-			} else {
-				throw new FormatterException("Exceeded total time limit for test threads – this should never happen");
-			}
-		} catch (InterruptedException e) {
-			throw new FormatterException("Interrupted while waiting for test results", e);
-		}
-
-		printFooter(currentFormatter);
-	}
-
-	/**
-	 * JDBC driver classes should be preloaded in single thread to avoid deadlocks while doing
-	 * {@linkplain DriverManager#registerDriver(java.sql.Driver)} during parallel connections.
-	 *
-	 * @param dbName
-	 */
-	private void preloadDriver(String dbName) {
-		try {
-			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
-			Driver driver = findDriver(dd);
-			if (driver == null) {
-				log.log(Level.WARNING, "No Driver found for DB: {0}", dbName);
-			} else {
-				log.log(Level.FINEST, "Driver preloading for DB: {0} was successfull", dbName);
-			}
-		} catch (Exception e) {
-			LogRecord r = new LogRecord(Level.WARNING, "Failed to preload the Driver for DB: {0}");
-			r.setParameters(new Object[]{dbName});
-			r.setThrown(e);
-			log.log(r);
-		}
-	}
-
-	private Object[] testConnection(String dbName) {
-		log.log(Level.FINE, "Testing connection to database: {0}", dbName);
-
-		boolean succesfullyConnected = false;
-		boolean succesfullyConfigured = false;
-		String productName = null;
-		String productVersion = null;
-
-		try {
-			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
-			log.log(Level.FINE, "Database definition was loaded from configuration");
-			succesfullyConfigured = true;
-			try (DatabaseConnection dc = dd.connect(options.getDatabaseProperties())) {
-				succesfullyConnected = dc.test();
-				productName = dc.getProductName();
-				productVersion = dc.getProductVersion();
-			}
-			log.log(Level.FINE, "Database connection test was successful");
-		} catch (ConfigurationException | SQLException | RuntimeException e) {
-			log.log(Level.SEVERE, "Error during testing connection " + dbName, e);
-		}
-
-		return new Object[]{dbName, succesfullyConfigured, succesfullyConnected, productName, productVersion};
-	}
-
-	private void printResource(String fileName) {
-		try (BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(fileName)))) {
-			while (true) {
-				String line = reader.readLine();
-				if (line == null) {
-					break;
-				} else {
-					println(line);
-				}
-			}
-		} catch (Exception e) {
-			log.log(Level.SEVERE, "Unable to print this info. Please see our website for it: " + Constants.WEBSITE, e);
-		}
-	}
-
-	private void println(String line) {
-		out.println(line);
-	}
-
-	private void printTable(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters, List<Object[]> data) throws ConfigurationException, FormatterException {
-		printTable(formatter, header, sql, parameters, data, null);
-	}
-
-	private void printTable(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters, List<Object[]> data, final Integer sortByColumn) throws ConfigurationException, FormatterException {
-		printHeader(formatter, header, sql, parameters);
-
-		if (sortByColumn != null) {
-			Collections.sort(data, new Comparator<Object[]>() {
-
-				@Override
-				public int compare(Object[] o1, Object[] o2) {
-					String s1 = String.valueOf(o1[sortByColumn]);
-					String s2 = String.valueOf(o2[sortByColumn]);
-					return s1.compareTo(s2);
-				}
-			});
-		}
-
-		for (Object[] row : data) {
-			printRow(formatter, row);
-		}
-
-		printFooter(formatter);
-	}
-
-	private void printHeader(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters) {
-		formatter.writeStartStatement();
-		if (sql != null) {
-			formatter.writeQuery(sql);
-			if (parameters != null) {
-				formatter.writeParameters(parameters);
-			}
-		}
-		formatter.writeStartResultSet(header);
-	}
-
-	private void printRow(Formatter formatter, Object[] row) {
-		formatter.writeStartRow();
-		for (Object cell : row) {
-			formatter.writeColumnValue(cell);
-		}
-		formatter.writeEndRow();
-	}
-
-	private void printFooter(Formatter formatter) {
-		formatter.writeEndResultSet();
-		formatter.writeEndStatement();
-	}
-
-	private Formatter getFormatter() throws ConfigurationException, FormatterException {
-		String formatterName = options.getFormatterName();
-		formatterName = formatterName == null ? Configuration.DEFAULT_FORMATTER_PREFETCHING : formatterName;
-		FormatterDefinition fd = configurationProvider.getConfiguration().getFormatter(formatterName);
-		FormatterContext context = new FormatterContext(out, options.getFormatterProperties());
-		return fd.getInstance(context);
-	}
-
-	private ColumnsHeader constructHeader(HeaderField... fields) throws FormatterException {
-		try {
-			RowSetMetaDataImpl metaData = new RowSetMetaDataImpl();
-			metaData.setColumnCount(fields.length);
-
-			for (int i = 0; i < fields.length; i++) {
-				HeaderField hf = fields[i];
-				int sqlIndex = i + 1;
-				metaData.setColumnName(sqlIndex, hf.name);
-				metaData.setColumnLabel(sqlIndex, hf.name);
-				metaData.setColumnType(sqlIndex, hf.type.getCode());
-				metaData.setColumnTypeName(sqlIndex, hf.type.name());
-			}
-
-			return new ColumnsHeader(metaData);
-		} catch (SQLException e) {
-			throw new FormatterException("Error while constructing table headers", e);
-		}
-	}
-
-	private static class HeaderField {
-
-		String name;
-		SQLType type;
-
-		public HeaderField(String name, SQLType type) {
-			this.name = name;
-			this.type = type;
-		}
-	}
-
-	public enum InfoType {
-
-		HELP {
-			@Override
-			public void showInfo(InfoLister infoLister) {
-				infoLister.printResource(Constants.HELP_FILE);
-			}
-		},
-		VERSION {
-			@Override
-			public void showInfo(InfoLister infoLister) {
-				infoLister.printResource(Constants.VERSION_FILE);
-			}
-		},
-		LICENSE {
-			@Override
-			public void showInfo(InfoLister infoLister) {
-				infoLister.printResource(Constants.LICENSE_FILE);
-			}
-		},
-		JAVA_PROPERTIES {
-			@Override
-			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
-				infoLister.listJavaProperties();
-			}
-		},
-		ENVIRONMENT_VARIABLES {
-			@Override
-			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
-				infoLister.listEnvironmentVariables();
-			}
-		},
-		FORMATTERS {
-			@Override
-			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
-				infoLister.listFormatters();
-			}
-		},
-		FORMATTER_PROPERTIES {
-			@Override
-			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
-				infoLister.listFormatterProperties();
-			}
-		},
-		TYPES {
-			@Override
-			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
-				infoLister.listTypes();
-			}
-		},
-		JDBC_DRIVERS {
-			@Override
-			public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException {
-				infoLister.listJdbcDrivers();
-			}
-		},
-		JDBC_PROPERTIES {
-			@Override
-			public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException {
-				infoLister.listJdbcProperties();
-			}
-		},
-		DATABASES {
-			@Override
-			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
-				infoLister.listDatabases();
-			}
-		},
-		CONNECTION {
-			@Override
-			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
-				infoLister.testConnections();
-			}
-		};
-
-		public abstract void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/InvalidOptionsException.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class InvalidOptionsException extends Exception {
-
-	private final Collection<OptionProblem> problems = new ArrayList<>();
-
-	public Collection<OptionProblem> getProblems() {
-		return Collections.unmodifiableCollection(problems);
-	}
-
-	public void addProblem(OptionProblem p) {
-		problems.add(p);
-	}
-
-	public boolean hasProblems() {
-		return !problems.isEmpty();
-	}
-
-	public static class OptionProblem {
-
-		private String description;
-		private Throwable exception;
-
-		public OptionProblem(String description) {
-			this.description = description;
-		}
-
-		public OptionProblem(String description, Throwable exception) {
-			this.description = description;
-			this.exception = exception;
-		}
-
-		public String getDescription() {
-			return description;
-		}
-
-		public Throwable getException() {
-			return exception;
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/NamedParameter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import info.globalcode.sql.dk.configuration.NameIdentified;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class NamedParameter extends Parameter implements NameIdentified {
-
-	private String name;
-
-	public NamedParameter(String name, Object value, SQLType type) {
-		super(value, type);
-		this.name = name;
-	}
-
-	@Override
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	@Override
-	public String toString() {
-		return "NamedParameter {" + name + " = " + getValue() + "; " + getType() + "}";
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/Parameter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import java.sql.Types;
-
-/**
- * Parameter for {@linkplain SQLCommand}
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class Parameter {
-
-	/**
-	 * @see Types
-	 */
-	public static final SQLType DEFAULT_TYPE = SQLType.VARCHAR;
-	private Object value;
-	private SQLType type;
-
-	public Parameter() {
-	}
-
-	public Parameter(Object value, SQLType type) {
-		this.value = value;
-		if (type == null) {
-			this.type = DEFAULT_TYPE;
-		} else {
-			this.type = type;
-		}
-	}
-
-	public Object getValue() {
-		return value;
-	}
-
-	public void setValue(Object value) {
-		this.value = value;
-	}
-
-	/**
-	 * @see java.sql.Types
-	 */
-	public SQLType getType() {
-		return type;
-	}
-
-	/**
-	 * @see java.sql.Types
-	 */
-	public void setType(SQLType type) {
-		this.type = type;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/SQLCommand.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.util.List;
-
-/**
- * Represents SQL string and its parameters (if there are any).
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public abstract class SQLCommand {
-
-	private String query;
-
-	public SQLCommand(String query) {
-		this.query = query;
-	}
-
-	public PreparedStatement prepareStatement(Connection c) throws SQLException {
-		return c.prepareStatement(query);
-	}
-
-	public abstract void parametrize(PreparedStatement ps) throws SQLException;
-
-	public abstract List<? extends Parameter> getParameters();
-
-	public String getQuery() {
-		return query;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNamed.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import static info.globalcode.sql.dk.Functions.findByName;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * Has named parameters.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class SQLCommandNamed extends SQLCommand {
-
-	private static final Logger log = Logger.getLogger(SQLCommandNamed.class.getName());
-	private String namePrefix;
-	private String nameSuffix;
-	private List<NamedParameter> parameters;
-	private List<NamedParameter> parametersUsed = new ArrayList<>();
-	private StringBuilder updatedQuery;
-	private Pattern pattern;
-	private SQLCommandNumbered numbered;
-
-	public SQLCommandNamed(String query, List<NamedParameter> parameters, String namePrefix, String nameSuffix) {
-		super(query);
-		this.updatedQuery = new StringBuilder(query.length());
-		this.parameters = parameters;
-		this.namePrefix = namePrefix;
-		this.nameSuffix = nameSuffix;
-	}
-
-	@Override
-	public PreparedStatement prepareStatement(Connection c) throws SQLException {
-		return getSQLCommandNumbered().prepareStatement(c);
-	}
-
-	@Override
-	public void parametrize(PreparedStatement ps) throws SQLException {
-		getSQLCommandNumbered().parametrize(ps);
-	}
-
-	private void prepare() throws SQLException {
-		try {
-			buildPattern();
-			placeParametersAndUpdateQuery();
-			logPossiblyMissingParameters();
-		} catch (PatternSyntaxException e) {
-			throw new SQLException("Name prefix „" + namePrefix + "“ or suffix „" + nameSuffix + "“ contain a wrong regular expression. " + e.getLocalizedMessage(), e);
-		}
-	}
-
-	/**
-	 * @return SQL command with named parameters converted to SQL command with numbered parameters
-	 */
-	public SQLCommandNumbered getSQLCommandNumbered() throws SQLException {
-		if (numbered == null) {
-			prepare();
-			numbered = new SQLCommandNumbered(updatedQuery.toString(), parametersUsed);
-		}
-
-		return numbered;
-	}
-
-	/**
-	 * Builds a regexp pattern that matches all parameter names (with prefix/suffix) and which has
-	 * one group: parameter name (without prefix/suffix)
-	 */
-	private void buildPattern() throws PatternSyntaxException {
-		StringBuilder patternString = new StringBuilder();
-
-		patternString.append(namePrefix);
-		patternString.append("(?<paramName>");
-		for (int i = 0; i < parameters.size(); i++) {
-			patternString.append(Pattern.quote(parameters.get(i).getName()));
-			if (i < parameters.size() - 1) {
-				patternString.append("|");
-			}
-		}
-		patternString.append(")");
-		patternString.append(nameSuffix);
-
-		pattern = Pattern.compile(patternString.toString());
-	}
-
-	private void placeParametersAndUpdateQuery() {
-		final String originalQuery = getQuery();
-		Matcher m = pattern.matcher(originalQuery);
-
-		int lastPosition = 0;
-		while (m.find(lastPosition)) {
-			String name = m.group("paramName");
-
-			updatedQuery.append(originalQuery.substring(lastPosition, m.start()));
-			updatedQuery.append("?");
-
-			parametersUsed.add(findByName(parameters, name));
-
-			lastPosition = m.end();
-		}
-		updatedQuery.append(originalQuery.substring(lastPosition, originalQuery.length()));
-
-		for (NamedParameter definedParameter : parameters) {
-			if (findByName(parametersUsed, definedParameter.getName()) == null) {
-				/**
-				 * User can have predefined set of parameters and use them with different SQL
-				 * queries that use only subset of these parameters → just warning, not exception.
-				 */
-				log.log(Level.WARNING, "Parameter „{0}“ is defined but not used in the query: „{1}“", new Object[]{definedParameter.getName(), originalQuery});
-			}
-		}
-	}
-
-	private void logPossiblyMissingParameters() {
-		Pattern p = Pattern.compile(namePrefix + "(?<paramName>.+?)" + nameSuffix);
-		Matcher m = p.matcher(updatedQuery);
-		int lastPosition = 0;
-		while (m.find(lastPosition)) {
-			/**
-			 * We have not parsed and understood the SQL query; the parameter-like looking string
-			 * could be inside a literal part of the query → just warning, not exception.
-			 */
-			log.log(Level.WARNING, "Possibly missing parameter „{0}“ in the query: „{1}“", new Object[]{m.group("paramName"), getQuery()});
-			lastPosition = m.end();
-		}
-	}
-
-	@Override
-	public List<NamedParameter> getParameters() {
-		return parameters;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNumbered.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import static info.globalcode.sql.dk.Functions.notNull;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.util.List;
-
-/**
- * Has ordinal/numbered parameters.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class SQLCommandNumbered extends SQLCommand {
-
-	private List<? extends Parameter> parameters;
-
-	public SQLCommandNumbered(String query, List<? extends Parameter> parameters) {
-		super(query);
-		this.parameters = parameters;
-	}
-
-	@Override
-	public void parametrize(PreparedStatement ps) throws SQLException {
-		int i = 1;
-		for (Parameter p : notNull(parameters)) {
-			ps.setObject(i++, p.getValue(), p.getType().getCode());
-		}
-	}
-
-	@Override
-	public List<? extends Parameter> getParameters() {
-		return parameters;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/SQLType.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import java.sql.Types;
-
-/**
- * Data types of SQL parameters.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public enum SQLType {
-
-	/**
-	 * Names must be upper case – user input is also converted to upper case → case insensitive
-	 */
-	BIT(Types.BIT),
-	TINYINT(Types.TINYINT),
-	SMALLINT(Types.SMALLINT),
-	INTEGER(Types.INTEGER),
-	BIGINT(Types.BIGINT),
-	FLOAT(Types.FLOAT),
-	REAL(Types.REAL),
-	DOUBLE(Types.DOUBLE),
-	NUMERIC(Types.NUMERIC),
-	DECIMAL(Types.DECIMAL),
-	CHAR(Types.CHAR),
-	VARCHAR(Types.VARCHAR),
-	LONGVARCHAR(Types.LONGVARCHAR),
-	DATE(Types.DATE),
-	TIME(Types.TIME),
-	TIMESTAMP(Types.TIMESTAMP),
-	BINARY(Types.BINARY),
-	VARBINARY(Types.VARBINARY),
-	LONGVARBINARY(Types.LONGVARBINARY),
-	NULL(Types.NULL),
-	OTHER(Types.OTHER),
-	JAVA_OBJECT(Types.JAVA_OBJECT),
-	DISTINCT(Types.DISTINCT),
-	STRUCT(Types.STRUCT),
-	ARRAY(Types.ARRAY),
-	BLOB(Types.BLOB),
-	CLOB(Types.CLOB),
-	REF(Types.REF),
-	DATALINK(Types.DATALINK),
-	BOOLEAN(Types.BOOLEAN),
-	ROWID(Types.ROWID),
-	NCHAR(Types.NCHAR),
-	NVARCHAR(Types.NVARCHAR),
-	LONGNVARCHAR(Types.LONGNVARCHAR),
-	NCLOB(Types.NCLOB),
-	SQLXML(Types.SQLXML);
-	/** value from java.sql.Types */
-	private int code;
-
-	private SQLType(int code) {
-		this.code = code;
-	}
-
-	/**
-	 * @see java.sql.Types.Types
-	 */
-	public int getCode() {
-		return code;
-	}
-
-	/**
-	 * @param code see {@linkplain java.sql.Types.Types}
-	 * @return found SQLType
-	 * @throws IllegalArgumentException if no data type has given code
-	 */
-	public static SQLType valueOf(int code) {
-		for (SQLType t : values()) {
-			if (t.code == code) {
-				return t;
-			}
-		}
-		throw new IllegalArgumentException("No data type has code: " + code);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/Xmlns.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-/**
- * XML namespaces
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class Xmlns {
-
-	public static final String CONFIGURATION = "https://sql-dk.globalcode.info/xmlns/configuration";
-	public static final String BATCH_RESULT = "https://sql-dk.globalcode.info/xmlns/batchResult";
-	public static final String XHTML = "http://www.w3.org/1999/xhtml";
-
-	private Xmlns() {
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/batch/Batch.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.batch;
-
-import info.globalcode.sql.dk.SQLCommand;
-
-/**
- * Iterator which reads SQL commands from encoded (serialized) batch.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public interface Batch {
-
-	public boolean hasNext() throws BatchException;
-
-	public SQLCommand next() throws BatchException;
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/batch/BatchConstants.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.batch;
-
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class BatchConstants {
-
-	public static final Charset CHARSET = StandardCharsets.UTF_8;
-	public static final byte VERSION = 0x01;
-	public static final byte[] BATCH_HEADER = {0x00, 0x53, 0x51, 0x4C, VERSION};
-
-	private BatchConstants() {
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/batch/BatchDecoder.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.batch;
-
-import info.globalcode.sql.dk.Parameter;
-import info.globalcode.sql.dk.SQLCommand;
-import info.globalcode.sql.dk.SQLCommandNumbered;
-import java.io.DataInputStream;
-import java.io.InputStream;
-import static info.globalcode.sql.dk.batch.BatchConstants.*;
-import static info.globalcode.sql.dk.Functions.toHex;
-import info.globalcode.sql.dk.SQLType;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class BatchDecoder {
-
-	public Batch decode(InputStream in) throws BatchException {
-		return new BatchFromStream(new DataInputStream(in));
-	}
-
-	private class BatchFromStream implements Batch {
-
-		private DataInputStream in;
-		private boolean hasNext;
-
-		public BatchFromStream(DataInputStream in) throws BatchException {
-			this.in = in;
-			hasNext = verifyHeader();
-		}
-
-		@Override
-		public boolean hasNext() throws BatchException {
-			return hasNext;
-		}
-
-		@Override
-		public SQLCommand next() throws BatchException {
-			try {
-				String sql = readNextString();
-
-				int paramCount = in.readInt();
-				List<Parameter> parameters = new ArrayList<>(paramCount);
-
-				for (int i = 0; i < paramCount; i++) {
-					SQLType type = SQLType.valueOf(in.readInt());
-					String value = readNextString();
-					parameters.add(new Parameter(value, type));
-				}
-
-				hasNext = verifyHeader();
-
-				SQLCommand sqlCommand = new SQLCommandNumbered(sql, parameters);
-				return sqlCommand;
-			} catch (IOException e) {
-				throw new BatchException("Unable to read batch", e);
-			}
-		}
-
-		private String readNextString() throws IOException {
-			byte[] buffer = new byte[in.readInt()];
-			in.read(buffer);
-			return new String(buffer, CHARSET);
-		}
-
-		/**
-		 * @return true if correct batch header was found | false if EOF was found
-		 * @throws BatchException if unexpected data was found (not batch header nor EOF)
-		 */
-		private boolean verifyHeader() throws BatchException {
-			try {
-				byte[] buffer = new byte[BATCH_HEADER.length];
-				int bytesRead = in.read(buffer);
-
-				if (bytesRead == BATCH_HEADER.length && Arrays.equals(buffer, BATCH_HEADER)) {
-					return true;
-				} else if (bytesRead == -1) {
-					return false;
-				} else {
-					throw new BatchException("This is not SQL-DK batch: " + toHex(buffer));
-				}
-			} catch (IOException e) {
-				throw new BatchException("Unable to read batch header", e);
-			}
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/batch/BatchEncoder.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.batch;
-
-import info.globalcode.sql.dk.Parameter;
-import info.globalcode.sql.dk.SQLCommand;
-import info.globalcode.sql.dk.SQLCommandNamed;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import static info.globalcode.sql.dk.batch.BatchConstants.*;
-import java.io.ByteArrayOutputStream;
-import java.sql.SQLException;
-import java.util.List;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class BatchEncoder {
-
-	public int encode(SQLCommand sqlCommand, OutputStream out) throws BatchException {
-		try {
-			ByteArrayOutputStream bufferAOS = new ByteArrayOutputStream();
-			DataOutputStream buffer = new DataOutputStream(bufferAOS);
-
-			buffer.write(BATCH_HEADER);
-
-			if (sqlCommand instanceof SQLCommandNamed) {
-				sqlCommand = ((SQLCommandNamed) sqlCommand).getSQLCommandNumbered();
-			}
-
-			writeNextString(sqlCommand.getQuery(), buffer);
-
-			List<? extends Parameter> parameters = sqlCommand.getParameters();
-
-			buffer.writeInt(parameters.size());
-
-			for (Parameter p : parameters) {
-				buffer.writeInt(p.getType().getCode());
-				writeNextString((String) p.getValue(), buffer); // parameters are encoded before any preprocessing
-			}
-
-			buffer.flush();
-			bufferAOS.writeTo(out);
-			out.flush();
-			return bufferAOS.size();
-		} catch (IOException e) {
-			throw new BatchException("Unable to write SQL command: " + sqlCommand, e);
-		} catch (SQLException e) {
-			throw new BatchException("Unable to converd named SQL command to numbered: " + sqlCommand, e);
-		}
-	}
-
-	private void writeNextString(String s, DataOutputStream out) throws IOException {
-		byte[] bytes = toBytes(s);
-		out.writeInt(bytes.length);
-		out.write(bytes);
-	}
-
-	private static byte[] toBytes(String s) {
-		if (s == null) {
-			return new byte[]{};
-		} else {
-			return s.getBytes(CHARSET);
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/batch/BatchException.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.batch;
-
-import info.globalcode.sql.dk.DKException;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class BatchException extends DKException {
-
-	public BatchException() {
-	}
-
-	public BatchException(String message) {
-		super(message);
-	}
-
-	public BatchException(Throwable cause) {
-		super(cause);
-	}
-
-	public BatchException(String message, Throwable cause) {
-		super(message, cause);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/CommandArgument.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2015 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlEnum;
-import javax.xml.bind.annotation.XmlEnumValue;
-import javax.xml.bind.annotation.XmlValue;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class CommandArgument {
-
-	private String value;
-	private TYPE type;
-
-	@XmlEnum
-	public static enum TYPE {
-
-		/**
-		 * value = literal (text) argument
-		 */
-		@XmlEnumValue("literal")
-		LITERAL,
-		/**
-		 * value will be substituted by hostname or IP address of the DB server
-		 */
-		@XmlEnumValue("host")
-		HOST,
-		/**
-		 * value will be substituted by the port of the DB server
-		 */
-		@XmlEnumValue("port")
-		PORT,
-		/**
-		 * value will be substituted by environmental variable of given name
-		 */
-		@XmlEnumValue("env")
-		ENVIRONMENT_VARIABLE,
-		/**
-		 * value will be substituted by database property of given name
-		 */
-		@XmlEnumValue("dbProperty")
-		DB_PROPERTY;
-	}
-
-	@XmlValue
-	public String getValue() {
-		return value;
-	}
-
-	public void setValue(String value) {
-		this.value = value;
-	}
-
-	@XmlAttribute(name = "type")
-	public TYPE getType() {
-		return type;
-	}
-
-	public void setType(TYPE type) {
-		this.type = type;
-	}
-
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
-import static info.globalcode.sql.dk.Functions.findByName;
-import info.globalcode.sql.dk.formatting.BarChartFormatter;
-import info.globalcode.sql.dk.formatting.SilentFormatter;
-import info.globalcode.sql.dk.formatting.SingleRecordFormatter;
-import info.globalcode.sql.dk.formatting.SingleValueFormatter;
-import info.globalcode.sql.dk.formatting.TabularFormatter;
-import info.globalcode.sql.dk.formatting.TabularPrefetchingFormatter;
-import info.globalcode.sql.dk.formatting.TabularWrappingFormatter;
-import info.globalcode.sql.dk.formatting.TeXFormatter;
-import info.globalcode.sql.dk.formatting.XhtmlFormatter;
-import info.globalcode.sql.dk.formatting.XmlFormatter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlTransient;
-
-/**
- * Object representation of user configuration loaded from XML.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-@XmlRootElement(name = "configuration", namespace = CONFIGURATION)
-public class Configuration {
-
-	private List<DatabaseDefinition> databases = new ArrayList<>();
-	private List<FormatterDefinition> formatters = new ArrayList<>();
-	/**
-	 * is used if no formatter is specified on CLI nor in user configuration
-	 */
-	public static final String DEFAULT_FORMATTER = TabularFormatter.NAME;
-	/**
-	 * Can be used as default if prefetching is ok – for configuration listings (config is alread in
-	 * memory, so this does not matter)
-	 */
-	public static final String DEFAULT_FORMATTER_PREFETCHING = TabularPrefetchingFormatter.NAME;
-	private String defaultFormatter;
-	/**
-	 * Default list of formatters. Is used if particular name is not found in user configuration.
-	 */
-	private static final Collection<FormatterDefinition> buildInFormatters;
-
-	static {
-		Collection<FormatterDefinition> l = new ArrayList<>();
-		l.add(new FormatterDefinition(SilentFormatter.NAME, SilentFormatter.class.getName()));
-		l.add(new FormatterDefinition(SingleValueFormatter.NAME, SingleValueFormatter.class.getName()));
-		l.add(new FormatterDefinition(SingleRecordFormatter.NAME, SingleRecordFormatter.class.getName()));
-		l.add(new FormatterDefinition(XmlFormatter.NAME, XmlFormatter.class.getName()));
-		l.add(new FormatterDefinition(XhtmlFormatter.NAME, XhtmlFormatter.class.getName()));
-		l.add(new FormatterDefinition(TabularFormatter.NAME, TabularFormatter.class.getName()));
-		l.add(new FormatterDefinition(TabularPrefetchingFormatter.NAME, TabularPrefetchingFormatter.class.getName()));
-		l.add(new FormatterDefinition(TabularWrappingFormatter.NAME, TabularWrappingFormatter.class.getName()));
-		l.add(new FormatterDefinition(TeXFormatter.NAME, TeXFormatter.class.getName()));
-		//l.add(new FormatterDefinition(DsvFormatter.NAME, DsvFormatter.class.getName()));
-		//l.add(new FormatterDefinition(SystemCommandExecutor.NAME, SystemCommandExecutor.class.getName()));
-		l.add(new FormatterDefinition(BarChartFormatter.NAME, BarChartFormatter.class.getName()));
-		buildInFormatters = Collections.unmodifiableCollection(l);
-	}
-
-	@XmlElement(name = "database", namespace = CONFIGURATION)
-	public List<DatabaseDefinition> getDatabases() {
-		return databases;
-	}
-
-	public void setDatabases(List<DatabaseDefinition> databases) {
-		this.databases = databases;
-	}
-
-	/**
-	 * @param name
-	 * @return
-	 * @throws ConfigurationException if no database with this name is configured
-	 */
-	public DatabaseDefinition getDatabase(String name) throws ConfigurationException {
-		DatabaseDefinition dd = findByName(databases, name);
-		if (dd == null) {
-			throw new ConfigurationException("Database is not configured: " + name);
-		} else {
-			return dd;
-		}
-	}
-
-	/**
-	 * @return only configured formatters
-	 * @see #getBuildInFormatters()
-	 * @see #getAllFormatters()
-	 */
-	@XmlElement(name = "formatter", namespace = CONFIGURATION)
-	public List<FormatterDefinition> getFormatters() {
-		return formatters;
-	}
-
-	public void setFormatters(List<FormatterDefinition> formatters) {
-		this.formatters = formatters;
-	}
-
-	/**
-	 * @param name name of desired formatter. Looking for this name in user configuration, then in
-	 * buil-in formatters. If null, default from configuration or (if not configured) built-in
-	 * default is used.
-	 * @return formatter definition
-	 * @throws ConfigurationException if no formatter with this name was found
-	 */
-	public FormatterDefinition getFormatter(String name) throws ConfigurationException {
-		if (name == null) {
-			return defaultFormatter == null ? getFormatter(DEFAULT_FORMATTER) : getFormatter(defaultFormatter);
-		} else {
-			FormatterDefinition fd = findByName(formatters, name);
-			fd = fd == null ? findByName(buildInFormatters, name) : fd;
-			if (fd == null) {
-				throw new ConfigurationException("Formatter is not configured: " + name);
-			} else {
-				return fd;
-			}
-		}
-	}
-
-	/**
-	 * @return only built-in formatters
-	 * @see #getAllFormatters()
-	 * @see #getFormatters()
-	 */
-	@XmlTransient
-	public Collection<FormatterDefinition> getBuildInFormatters() {
-		return buildInFormatters;
-	}
-
-	/**
-	 * @return built-in + configured formatters
-	 * @see #getFormatters()
-	 */
-	@XmlTransient
-	public Collection<FormatterDefinition> getAllFormatters() {
-		Collection<FormatterDefinition> allFormatters = new ArrayList<>();
-		allFormatters.addAll(buildInFormatters);
-		allFormatters.addAll(formatters);
-		return allFormatters;
-	}
-
-	/**
-	 * @return name of default formatter, is used if name is not specified on CLI
-	 */
-	@XmlElement(name = "defaultFormatter", namespace = CONFIGURATION)
-	public String getDefaultFormatter() {
-		return defaultFormatter;
-	}
-
-	public void setDefaultFormatter(String defaultFormatter) {
-		this.defaultFormatter = defaultFormatter;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/ConfigurationException.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import info.globalcode.sql.dk.DKException;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class ConfigurationException extends DKException {
-
-	public ConfigurationException() {
-	}
-
-	public ConfigurationException(String message) {
-		super(message);
-	}
-
-	public ConfigurationException(Throwable cause) {
-		super(cause);
-	}
-
-	public ConfigurationException(String message, Throwable cause) {
-		super(message, cause);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/ConfigurationProvider.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-/**
- * Use for lazy-loading of the configuration.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public interface ConfigurationProvider {
-
-	public Configuration getConfiguration() throws ConfigurationException;
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/DatabaseDefinition.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,147 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
-import info.globalcode.sql.dk.DatabaseConnection;
-import info.globalcode.sql.dk.jmx.ConnectionManagement;
-import java.sql.SQLException;
-import java.util.logging.Logger;
-import javax.xml.bind.annotation.XmlElement;
-
-/**
- * Configured (but not yet connected) database connection.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class DatabaseDefinition implements NameIdentified {
-
-	private static final Logger log = Logger.getLogger(DatabaseDefinition.class.getName());
-	/**
-	 * database name in SQL-DK configuration
-	 */
-	private String name;
-	/**
-	 * JDBC URL
-	 */
-	private String url;
-	/**
-	 * JDBC user name
-	 */
-	private String userName;
-	/**
-	 * JDBC password
-	 */
-	private String password;
-	/**
-	 * optional JDBC driver – if empty, the DriverManager is used to lookup specific Driver for
-	 * given URL
-	 */
-	private String driver;
-	/**
-	 * JDBC properties
-	 */
-	private Properties properties = new Properties();
-	/**
-	 * optional definition of tunnel to the remote database
-	 */
-	private TunnelDefinition tunnel;
-
-	@XmlElement(name = "name", namespace = CONFIGURATION)
-	@Override
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	@XmlElement(name = "url", namespace = CONFIGURATION)
-	public String getUrl() {
-		return url;
-	}
-
-	public void setUrl(String url) {
-		this.url = url;
-	}
-
-	@XmlElement(name = "userName", namespace = CONFIGURATION)
-	public String getUserName() {
-		return userName;
-	}
-
-	public void setUserName(String userName) {
-		this.userName = userName;
-	}
-
-	@XmlElement(name = "password", namespace = CONFIGURATION)
-	public String getPassword() {
-		return password;
-	}
-
-	public void setPassword(String password) {
-		this.password = password;
-	}
-
-	public String getDriver() {
-		return driver;
-	}
-
-	public void setDriver(String driver) {
-		this.driver = driver;
-	}
-
-	@XmlElement(name = "property", namespace = CONFIGURATION)
-	public Properties getProperties() {
-		return properties;
-	}
-
-	public void setProperties(Properties properties) {
-		this.properties = properties;
-	}
-
-	public TunnelDefinition getTunnel() {
-		return tunnel;
-	}
-
-	public void setTunnel(TunnelDefinition tunnel) {
-		this.tunnel = tunnel;
-	}
-
-	/**
-	 * @param properties ad-hoc properties from CLI options (for the JDBC driver)
-	 * @param jmxBean JMX management bean for progress reporting | null = disable JMX
-	 * @return
-	 * @throws java.sql.SQLException
-	 */
-	public DatabaseConnection connect(Properties properties, ConnectionManagement jmxBean) throws SQLException {
-		return new DatabaseConnection(this, properties, jmxBean);
-	}
-
-	/**
-	 * @param properties
-	 * @return
-	 * @throws java.sql.SQLException
-	 * @see #connect(info.globalcode.sql.dk.configuration.Properties, java.lang.String)
-	 * With disabled JMX reporting.
-	 */
-	public DatabaseConnection connect(Properties properties) throws SQLException {
-		return new DatabaseConnection(this, properties, null);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/FormatterDefinition.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,114 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
-import info.globalcode.sql.dk.formatting.Formatter;
-import info.globalcode.sql.dk.formatting.FormatterContext;
-import info.globalcode.sql.dk.formatting.FormatterException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import javax.xml.bind.annotation.XmlElement;
-
-/**
- * Configured (but not yet instantiated) formatter.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class FormatterDefinition implements NameIdentified {
-
-	private String name;
-	private String className;
-	private Properties properties = new Properties();
-
-	public FormatterDefinition() {
-	}
-
-	public FormatterDefinition(String name, String className) {
-		this.name = name;
-		this.className = className;
-	}
-
-	public FormatterDefinition(String name, String className, Properties properties) {
-		this(name, className);
-		this.properties = properties;
-	}
-
-	@XmlElement(name = "name", namespace = CONFIGURATION)
-	@Override
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	/**
-	 * Filter's class. Must implement the
-	 * <code>info.globalcode.sql.dk.formatting.Formatter</code> interface.
-	 * Subclassing the
-	 * <code>info.globalcode.sql.dk.formatting.AbstractFormatter</code> is strongly recommended.
-	 * The constructor must accept one parameter:
-	 * <code>info.globalcode.sql.dk.formatting.FormatterContext</code>
-	 *
-	 * @return fully qualified class name
-	 */
-	@XmlElement(name = "class", namespace = CONFIGURATION)
-	public String getClassName() {
-		return className;
-	}
-
-	public void setClassName(String className) {
-		this.className = className;
-	}
-
-	@XmlElement(name = "property", namespace = CONFIGURATION)
-	public Properties getProperties() {
-		return properties;
-	}
-
-	public void setProperties(Properties properties) {
-		this.properties = properties;
-	}
-
-	/**
-	 * @param context
-	 * @return
-	 * @throws FormatterException
-	 */
-	public Formatter getInstance(FormatterContext context) throws FormatterException {
-		context.getProperties().setDefaults(properties);
-		try {
-			Constructor constructor = Class.forName(className).getConstructor(context.getClass());
-
-			Object instance = constructor.newInstance(context);
-			if (instance instanceof Formatter) {
-				return (Formatter) instance;
-			} else {
-				throw new FormatterException("Formatter " + instance + " does not implement the " + Formatter.class.getName() + " interface");
-			}
-		} catch (ClassNotFoundException e) {
-			throw new FormatterException("Formatter class does not exist: " + className, e);
-		} catch (NoSuchMethodException e) {
-			throw new FormatterException("Formatter class with no valid constructor: " + className, e);
-		} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
-			throw new FormatterException("Formatter's constructor caused an error: " + className, e);
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Loader.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2015 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import info.globalcode.sql.dk.*;
-import static info.globalcode.sql.dk.DatabaseConnection.JDBC_PROPERTY_USER;
-import static info.globalcode.sql.dk.DatabaseConnection.JDBC_PROPERTY_PASSWORD;
-import java.sql.Connection;
-import java.sql.Driver;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.Unmarshaller;
-
-/**
- * Configuration loader – deserializes Configuration from the XML file.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class Loader {
-
-	private static final Logger log = Logger.getLogger(Loader.class.getName());
-
-	public Configuration loadConfiguration() throws ConfigurationException {
-		try {
-			JAXBContext jaxb = JAXBContext.newInstance(Configuration.class.getPackage().getName(), Configuration.class.getClassLoader());
-			Unmarshaller u = jaxb.createUnmarshaller();
-			return (Configuration) u.unmarshal(Constants.CONFIG_FILE);
-		} catch (Exception e) {
-			throw new ConfigurationException("Unable to load configuration from " + Constants.CONFIG_FILE, e);
-		}
-	}
-
-	/**
-	 * JDBC connection should not be used directly in SQL-DK.
-	 *
-	 * @see DatabaseDefinition#connect(info.globalcode.sql.dk.configuration.Properties)
-	 * @param properties
-	 * @param databaseDefinition
-	 * @return
-	 * @throws java.sql.SQLException
-	 */
-	public static Connection jdbcConnect(DatabaseDefinition databaseDefinition, Properties properties) throws SQLException {
-		synchronized (properties) {
-			/**
-			 * Avoid rewriting the properties. Usually, the connection is created only once, but
-			 * with --test-connection and with SQL-DK JDBC driver, it might be reused.
-			 */
-			properties = properties.clone();
-		}
-		if (properties.hasProperty(JDBC_PROPERTY_PASSWORD)) {
-			log.log(Level.WARNING, "Passing DB password as CLI parameter is insecure!");
-		}
-		Properties credentials = new Properties();
-		credentials.add(new Property(JDBC_PROPERTY_USER, databaseDefinition.getUserName()));
-		credentials.add(new Property(JDBC_PROPERTY_PASSWORD, databaseDefinition.getPassword()));
-		credentials.setDefaults(databaseDefinition.getProperties());
-		properties.setDefaults(credentials);
-		java.util.Properties javaProperties = properties.getJavaProperties();
-
-		String driverClassName = databaseDefinition.getDriver();
-		final String url = databaseDefinition.getUrl();
-		if (driverClassName == null) {
-			log.log(Level.FINE, "Using DriverManager to create connection for „{0}“", url);
-			return DriverManager.getConnection(url, javaProperties);
-		} else {
-			log.log(Level.FINE, "Using custom Driver „{0}“ to create connection for „{1}“", new Object[]{driverClassName, url});
-			try {
-				Class<Driver> driverClass = (Class<Driver>) Class.forName(driverClassName);
-				Driver driver = driverClass.newInstance();
-				Connection connection = driver.connect(url, javaProperties);
-				if (connection == null) {
-					log.log(Level.SEVERE, "Driver „{0}“ returend null → it does not accept the URL: „{1}“", new Object[]{driverClassName, url});
-					throw new SQLException("Unable to connect: driver returned null.");
-				} else {
-					return connection;
-				}
-			} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | SQLException e) {
-				throw new SQLException("Unable to connect usig specific driver: " + driverClassName, e);
-			}
-		}
-	}
-
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/NameIdentified.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public interface NameIdentified {
-
-	public String getName();
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Properties.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import java.util.ArrayList;
-import javax.xml.bind.annotation.XmlTransient;
-import static info.globalcode.sql.dk.Functions.findByName;
-import java.util.Collections;
-
-/**
- * <p>
- * List of configurables.</p>
- *
- * <p>
- * Can be backed by defaults – if value for given name is nof found in this instance, we will
- * look into defaults. Methods also accept defaultValue parameter – is used if property is nof found
- * even in default properties.</p>
- *
- * <p>
- * Typical use: </p>
- * <ul>
- * <li>this instance – ad-hoc properties from CLI options</li>
- * <li>default properties – from config file</li>
- * <li>defaultValue – hardcoded default</li>
- * </ul>
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class Properties extends ArrayList<Property> implements Cloneable {
-
-	private Properties defaults;
-
-	public Properties() {
-	}
-
-	public Properties(int initialCapacity) {
-		super(initialCapacity);
-	}
-
-	@XmlTransient
-	public Properties getDefaults() {
-		return defaults;
-	}
-
-	public void setDefaults(Properties defaults) {
-		this.defaults = defaults;
-	}
-
-	/**
-	 * @param defaults the last/deepest defaults
-	 */
-	public void setLastDefaults(Properties defaults) {
-		if (this.defaults == null) {
-			this.defaults = defaults;
-		} else {
-			this.defaults.setLastDefaults(defaults);
-		}
-	}
-
-	private Property findProperty(String name) {
-		Property p = findByName(this, name);
-		if (p == null && defaults != null) {
-			p = defaults.findProperty(name);
-		}
-		return p;
-	}
-
-	public String getString(String name, String defaultValue) {
-		Property p = findProperty(name);
-		return p == null ? defaultValue : p.getValue();
-	}
-
-	public boolean getBoolean(String name, boolean defaultValue) {
-		Property p = findProperty(name);
-		return p == null ? defaultValue : Boolean.valueOf(p.getValue());
-	}
-
-	public int getInteger(String name, int defaultValue) {
-		Property p = findProperty(name);
-		return p == null ? defaultValue : Integer.valueOf(p.getValue());
-	}
-
-	public boolean hasProperty(String name) {
-		return findByName(this, name) != null;
-	}
-
-	@Override
-	public Properties clone() {
-		Properties clone = new Properties(size());
-		Collections.copy(clone, this);
-		return clone;
-	}
-
-	/**
-	 * @return merged this and backing defaults as Java Properties
-	 */
-	public java.util.Properties getJavaProperties() {
-		java.util.Properties javaProperties = new java.util.Properties();
-		duplicateTo(javaProperties);
-		return javaProperties;
-	}
-
-	private void duplicateTo(java.util.Properties javaProperties) {
-		if (defaults != null) {
-			defaults.duplicateTo(javaProperties);
-		}
-		for (Property p : this) {
-			String value = p.getValue();
-			if (value != null) {
-				javaProperties.setProperty(p.getName(), value);
-			}
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/Property.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlValue;
-
-/**
- * One configurable
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class Property implements NameIdentified, Cloneable {
-
-	private String name;
-	private String value;
-
-	public Property() {
-	}
-
-	public Property(String name, String value) {
-		this.name = name;
-		this.value = value;
-	}
-
-	@XmlAttribute(name = "name")
-	@Override
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	@XmlValue
-	public String getValue() {
-		return value;
-	}
-
-	public void setValue(String value) {
-		this.value = value;
-	}
-
-	@Override
-	public String toString() {
-		return name + "='" + value + "'";
-	}
-
-	@Override
-	protected Property clone() {
-		return new Property(name, value);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/PropertyDeclaration.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2015 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import java.lang.annotation.Target;
-
-/**
- * Declaration of the (formatter) properties – for documentation purposes.
- *
- * TODO: automatically inject properties (configured, ad-hoc, default ones) to the formatters
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-@Retention(RUNTIME)
-@Target({ElementType.TYPE})
-@Repeatable(PropertyDeclarations.class)
-public @interface PropertyDeclaration {
-
-	/**
-	 * @return name of the property
-	 */
-	String name();
-
-	/**
-	 * @return data type of the value: String, numbers, Boolean or Enum
-	 */
-	Class type();
-	
-	/**
-	 * @return documentation for the users
-	 */
-	String description();
-
-	/**
-	 * @return default value of this property
-	 */
-	String defaultValue();
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/PropertyDeclarations.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2015 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import java.lang.annotation.Target;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-@Retention(RUNTIME)
-@Target({ElementType.TYPE})
-public @interface PropertyDeclarations {
-
-	PropertyDeclaration[] value();
-
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/TunnelDefinition.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2015 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.configuration;
-
-import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
-import java.util.List;
-import javax.xml.bind.annotation.XmlElement;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class TunnelDefinition {
-
-	private String command;
-	private List<CommandArgument> arguments;
-
-	@XmlElement(name = "command", namespace = CONFIGURATION)
-	public String getCommand() {
-		return command;
-	}
-
-	public void setCommand(String command) {
-		this.command = command;
-	}
-
-	@XmlElement(name = "argument", namespace = CONFIGURATION)
-	public List<CommandArgument> getArguments() {
-		return arguments;
-	}
-
-	public void setArguments(List<CommandArgument> arguments) {
-		this.arguments = arguments;
-	}
-
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/configuration/jaxb.index	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-Configuration
\ No newline at end of file
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,254 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.Parameter;
-import info.globalcode.sql.dk.configuration.DatabaseDefinition;
-import java.util.EmptyStackException;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Stack;
-
-/**
- * <ol>
- * <li>ensures integrity – if methods are called in correct order and context</li>
- * <li>provides default implmentations of methods that does not produce any output for given
- * events</li>
- * </ol>
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public abstract class AbstractFormatter implements Formatter {
-
-	private Stack<State> state = new Stack<>();
-	private FormatterContext formatterContext;
-	private ColumnsHeader currentColumnsHeader;
-	private String currentQuery;
-	private int currentColumnsCount;
-	private int currentRowCount;
-
-	public AbstractFormatter(FormatterContext formatterContext) {
-		this.formatterContext = formatterContext;
-		state.push(State.ROOT);
-	}
-
-	/*
-	 * root
-	 * .batch
-	 * ..database
-	 * ...statement
-	 * ....@query
-	 * ....@parameters
-	 * ....resultSet
-	 * .....row
-	 * ......@columnValue
-	 * ....@updatesResult
-	 */
-	protected enum State {
-
-		ROOT,
-		BATCH,
-		DATABASE,
-		STATEMENT,
-		RESULT_SET,
-		ROW
-	}
-
-	/**
-	 * Go down in hierarchy.
-	 * Pushes new state and verifies the old one.
-	 *
-	 * @param current the new state – currently entering
-	 * @param expected expected previous states (any of them is valid)
-	 * @return previous state
-	 * @throws IllegalStateException if previous state was not one from expected
-	 */
-	private State pushState(State current, EnumSet expected) {
-		State previous = state.peek();
-
-		if (expected.contains(previous)) {
-			state.push(current);
-			return previous;
-		} else {
-			throw new IllegalStateException("Formatter was in wrong state: " + previous + " when it should be in one of: " + expected);
-		}
-	}
-
-	protected State peekState(EnumSet expected) {
-		State current = state.peek();
-
-		if (expected.contains(current)) {
-			return current;
-		} else {
-			throw new IllegalStateException("Formatter is in wrong state: " + current + " when it should be in one of: " + expected);
-		}
-
-	}
-
-	/**
-	 * Go up in hierarchy.
-	 * Pops the superior state/branch.
-	 *
-	 * @param expected expected superior state
-	 * @return the superior state
-	 * @throws IllegalStateException if superior state was not one from expected or if there is no
-	 * more superior state (we are at root level)
-	 */
-	private State popState(EnumSet expected) {
-		try {
-			state.pop();
-			State superior = state.peek();
-			if (expected.contains(superior)) {
-				return superior;
-			} else {
-				throw new IllegalStateException("Formatter had wrong superior state: " + superior + " when it should be in one of: " + expected);
-			}
-		} catch (EmptyStackException e) {
-			throw new IllegalStateException("Formatter was already at root level – there is nothing above that.", e);
-		}
-	}
-
-	@Override
-	public void writeStartBatch() {
-		pushState(State.BATCH, EnumSet.of(State.ROOT));
-	}
-
-	@Override
-	public void writeEndBatch() {
-		popState(EnumSet.of(State.ROOT));
-	}
-
-	@Override
-	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
-		pushState(State.DATABASE, EnumSet.of(State.BATCH));
-	}
-
-	@Override
-	public void writeEndDatabase() {
-		popState(EnumSet.of(State.BATCH));
-	}
-
-	@Override
-	public void writeStartStatement() {
-		pushState(State.STATEMENT, EnumSet.of(State.DATABASE));
-	}
-
-	@Override
-	public void writeEndStatement() {
-		popState(EnumSet.of(State.DATABASE));
-	}
-
-	@Override
-	public void writeStartResultSet(ColumnsHeader header) {
-		pushState(State.RESULT_SET, EnumSet.of(State.STATEMENT));
-		currentRowCount = 0;
-		currentColumnsHeader = header;
-	}
-
-	@Override
-	public void writeEndResultSet() {
-		popState(EnumSet.of(State.STATEMENT));
-		currentColumnsHeader = null;
-	}
-
-	@Override
-	public void writeQuery(String sql) {
-		peekState(EnumSet.of(State.STATEMENT));
-
-		if (currentColumnsHeader == null) {
-			currentQuery = sql;
-		} else {
-			throw new IllegalStateException("Query string '" + sql + "' must be set before columns header – was already set: " + currentColumnsHeader);
-		}
-	}
-
-	@Override
-	public void writeParameters(List<? extends Parameter> parameters) {
-		peekState(EnumSet.of(State.STATEMENT));
-
-		if (currentColumnsHeader != null) {
-			throw new IllegalStateException("Parameters '" + parameters + "' must be set before columns header – was already set: " + currentColumnsHeader);
-		}
-
-		if (currentQuery == null && parameters != null) {
-			throw new IllegalStateException("Parameters '" + parameters + "' must be set after query – was not yet set.");
-		}
-	}
-
-	@Override
-	public void writeStartRow() {
-		pushState(State.ROW, EnumSet.of(State.RESULT_SET));
-		currentColumnsCount = 0;
-		currentRowCount++;
-	}
-
-	@Override
-	public void writeEndRow() {
-		popState(EnumSet.of(State.RESULT_SET));
-	}
-
-	@Override
-	public void writeColumnValue(Object value) {
-		peekState(EnumSet.of(State.ROW));
-		currentColumnsCount++;
-
-		int declaredCount = currentColumnsHeader.getColumnCount();
-		if (currentColumnsCount > declaredCount) {
-			throw new IllegalStateException("Current columns count is " + currentColumnsCount + " which is more than declared " + declaredCount + " in header.");
-		}
-	}
-
-	@Override
-	public void writeUpdatesResult(int updatedRowsCount) {
-		peekState(EnumSet.of(State.STATEMENT));
-	}
-
-	@Override
-	public void close() throws FormatterException {
-	}
-
-	public FormatterContext getFormatterContext() {
-		return formatterContext;
-	}
-
-	protected ColumnsHeader getCurrentColumnsHeader() {
-		return currentColumnsHeader;
-	}
-
-	/**
-	 * @return column number, 1 = first
-	 */
-	protected int getCurrentColumnsCount() {
-		return currentColumnsCount;
-	}
-
-	protected boolean isCurrentColumnFirst() {
-		return currentColumnsCount == 1;
-	}
-
-	protected boolean isCurrentColumnLast() {
-		return currentColumnsCount == currentColumnsHeader.getColumnCount();
-	}
-
-	/**
-	 * @return row number, 1 = first
-	 */
-	protected int getCurrentRowCount() {
-		return currentRowCount;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,241 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.ColorfulPrintWriter;
-import info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
-import java.util.Stack;
-import javax.xml.namespace.QName;
-import static info.globalcode.sql.dk.Functions.isEmpty;
-import static info.globalcode.sql.dk.Functions.toHex;
-import info.globalcode.sql.dk.configuration.PropertyDeclaration;
-import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
-import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
-import java.nio.charset.Charset;
-import java.util.EmptyStackException;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * <p>
- * Provides helper methods for printing pretty intended and optionally colorful (syntax highlighted)
- * XML output.
- * </p>
- *
- * <p>
- * Must be used with care – bad usage can lead to invalid XML (e.g. using undeclared namespaces).
- * </p>
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-@PropertyDeclaration(name = COLORFUL, defaultValue = "false", type = Boolean.class, description = COLORFUL_DESCRIPTION)
-@PropertyDeclaration(name = AbstractXmlFormatter.PROPERTY_INDENT, defaultValue = AbstractXmlFormatter.PROPERTY_INDENT_DEFAULT, type = String.class, description = "tab or sequence of spaces used for indentation of nested elements")
-@PropertyDeclaration(name = AbstractXmlFormatter.PROPERTY_INDENT_TEXT, defaultValue = "true", type = Boolean.class, description = "whether text with line breaks should be indented; if not original whitespace will be preserved.")
-public abstract class AbstractXmlFormatter extends AbstractFormatter {
-
-	private static final Logger log = Logger.getLogger(AbstractXmlFormatter.class.getName());
-	public static final String PROPERTY_INDENT = "indent";
-	protected static final String PROPERTY_INDENT_DEFAULT = "\t";
-	public static final String PROPERTY_INDENT_TEXT = "indentText";
-	private static final TerminalColor ELEMENT_COLOR = TerminalColor.Magenta;
-	private static final TerminalColor ATTRIBUTE_NAME_COLOR = TerminalColor.Green;
-	private static final TerminalColor ATTRIBUTE_VALUE_COLOR = TerminalColor.Yellow;
-	private static final TerminalColor XML_DECLARATION_COLOR = TerminalColor.Red;
-	private static final TerminalColor XML_DOCTYPE_COLOR = TerminalColor.Cyan;
-	private Stack<QName> treePosition = new Stack<>();
-	private final ColorfulPrintWriter out;
-	private final String indent;
-	private final boolean indentText;
-
-	public AbstractXmlFormatter(FormatterContext formatterContext) {
-		super(formatterContext);
-		boolean colorful = formatterContext.getProperties().getBoolean(COLORFUL, false);
-		out = new ColorfulPrintWriter(formatterContext.getOutputStream(), false, colorful);
-		indent = formatterContext.getProperties().getString(PROPERTY_INDENT, PROPERTY_INDENT_DEFAULT);
-		indentText = formatterContext.getProperties().getBoolean(PROPERTY_INDENT_TEXT, true);
-
-		if (!indent.matches("\\s*")) {
-			log.log(Level.WARNING, "Setting indent to „{0}“ is weird & freaky; in hex: {1}", new Object[]{indent, toHex(indent.getBytes())});
-		}
-
-	}
-
-	protected void printStartDocument() {
-		out.print(XML_DECLARATION_COLOR, "<?xml version=\"1.0\" encoding=\"" + Charset.defaultCharset().name() + "\"?>");
-	}
-
-	protected void printDoctype(String doctype) {
-		out.print(XML_DOCTYPE_COLOR, "\n<!DOCTYPE " + doctype + ">");
-	}
-
-	protected void printEndDocument() {
-		out.println();
-		out.flush();
-		if (!treePosition.empty()) {
-			throw new IllegalStateException("Some elements are not closed: " + treePosition);
-		}
-	}
-
-	protected void printStartElement(QName element) {
-		printStartElement(element, null);
-	}
-
-	protected Map<QName, String> singleAttribute(QName name, String value) {
-		Map<QName, String> attributes = new HashMap<>(2);
-		attributes.put(name, value);
-		return attributes;
-	}
-
-	protected void printStartElement(QName element, Map<QName, String> attributes) {
-		printStartElement(element, attributes, false);
-	}
-
-	/**
-	 * @param empty whether element should be closed <codfe>… /&gt;</code> (has no content, do not
-	 * call {@linkplain #printEndElement()})
-	 */
-	private void printStartElement(QName element, Map<QName, String> attributes, boolean empty) {
-		printIndent();
-
-		out.print(ELEMENT_COLOR, "<" + toString(element));
-
-		if (attributes != null) {
-			for (Entry<QName, String> attribute : attributes.entrySet()) {
-				out.print(" ");
-				out.print(ATTRIBUTE_NAME_COLOR, toString(attribute.getKey()));
-				out.print("=");
-				out.print(ATTRIBUTE_VALUE_COLOR, '"' + escapeXmlAttribute(attribute.getValue()) + '"');
-			}
-		}
-
-		if (empty) {
-			out.print(ELEMENT_COLOR, "/>");
-		} else {
-			out.print(ELEMENT_COLOR, ">");
-			treePosition.add(element);
-		}
-
-		out.flush();
-	}
-
-	/**
-	 * Prints text node wrapped in given element without indenting the text and adding line breaks
-	 * (useful for short texts).
-	 *
-	 * @param attributes use {@linkplain  LinkedHashMap} to preserve attributes order
-	 */
-	protected void printTextElement(QName element, Map<QName, String> attributes, String text) {
-		printStartElement(element, attributes);
-
-		String[] lines = text.split("\\n");
-
-		if (indentText && lines.length > 1) {
-			for (String line : lines) {
-				printText(line, true);
-			}
-			printEndElement(true);
-		} else {
-			/*
-			 * line breaks at the end of the text will be eaten – if you need them, use indentText = false
-			 */
-			if (lines.length == 1 && text.endsWith("\n")) {
-				text = text.substring(0, text.length() - 1);
-			}
-
-			printText(text, false);
-			printEndElement(false);
-		}
-	}
-
-	protected void printEmptyElement(QName element, Map<QName, String> attributes) {
-		printStartElement(element, attributes, true);
-	}
-
-	protected void printEndElement() {
-		printEndElement(true);
-	}
-
-	private void printEndElement(boolean indent) {
-		try {
-			QName name = treePosition.pop();
-
-			if (indent) {
-				printIndent();
-			}
-
-			out.print(ELEMENT_COLOR, "</" + toString(name) + ">");
-			out.flush();
-
-		} catch (EmptyStackException e) {
-			throw new IllegalStateException("No more elements to end.", e);
-		}
-	}
-
-	protected void printText(String s, boolean indent) {
-		if (indent) {
-			printIndent();
-		}
-		out.print(escapeXmlText(s));
-		out.flush();
-	}
-
-	protected void printIndent() {
-		out.println();
-		for (int i = 0; i < treePosition.size(); i++) {
-			out.print(indent);
-		}
-	}
-
-	protected static QName qname(String name) {
-		return new QName(name);
-	}
-
-	protected static QName qname(String prefix, String name) {
-		return new QName(null, name, prefix);
-	}
-
-	private String toString(QName name) {
-		if (isEmpty(name.getPrefix(), true)) {
-			return escapeName(name.getLocalPart());
-		} else {
-			return escapeName(name.getPrefix()) + ":" + escapeName(name.getLocalPart());
-		}
-	}
-
-	private String escapeName(String s) {
-		// TODO: avoid ugly values in <name name="…"/>		
-		return s;
-	}
-
-	private static String escapeXmlText(String s) {
-		return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
-		// Not needed:
-		// return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
-	}
-
-	/**
-	 * Expects attribute values enclosed in "quotes" not 'apostrophes'.
-	 */
-	private static String escapeXmlAttribute(String s) {
-		return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/BarChartFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2015 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.Functions;
-import info.globalcode.sql.dk.configuration.PropertyDeclaration;
-import info.globalcode.sql.dk.logging.LoggerProducer;
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.math.RoundingMode;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * TODO: min/max values – range for case that no value is 100 %
- *
- * TODO: multiple barcharts in same table (last column is still default) + multiple resultsets
- *
- * TODO: negative values - bar starting from the middle, not always from the left
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-@PropertyDeclaration(name = BarChartFormatter.PROPERTY_PRECISION, type = Integer.class, defaultValue = BarChartFormatter.PROPERTY_PRECISION_DEFAULT, description = "number of characters representing 100 % in the bar chart")
-public class BarChartFormatter extends TabularPrefetchingFormatter {
-
-	public static final String NAME = "barchart"; // bash-completion:formatter
-	public static final String PROPERTY_PRECISION = "precision";
-	protected static final String PROPERTY_PRECISION_DEFAULT = "100";
-	private static final MathContext mathContext = MathContext.DECIMAL128;
-	public static final Logger log = LoggerProducer.getLogger();
-	private final BigDecimal chartPrecision;
-	private final char chartFull;
-	private final char chartEmpty;
-
-	public BarChartFormatter(FormatterContext formatterContext) {
-		super(formatterContext);
-		chartPrecision = BigDecimal.valueOf(formatterContext.getProperties().getInteger(PROPERTY_PRECISION, Integer.parseInt(PROPERTY_PRECISION_DEFAULT)));
-		chartFull = isAsciiNostalgia() ? '#' : '█';
-		chartEmpty = isAsciiNostalgia() ? '~' : '░';
-		// TODO: consider using partial blocks for more precision: https://en.wikipedia.org/wiki/Block_Elements
-	}
-
-	@Override
-	protected void postprocessPrefetchedResultSet(ColumnsHeader currentHeader, List<Object[]> currentResultSet) {
-		super.postprocessPrefetchedResultSet(currentHeader, currentResultSet);
-
-		updateColumnWidth(currentHeader.getColumnCount(), chartPrecision.intValue());
-
-		BigDecimal maximum = BigDecimal.ZERO;
-		BigDecimal minimum = BigDecimal.ZERO;
-		int lastIndex = currentHeader.getColumnCount() - 1;
-
-		Object valueObject = null;
-		try {
-			for (Object[] row : currentResultSet) {
-				valueObject = row[lastIndex];
-				if (valueObject != null) {
-					BigDecimal value = new BigDecimal(valueObject.toString());
-					maximum = maximum.max(value);
-					minimum = minimum.min(value);
-				}
-			}
-
-			BigDecimal range = maximum.subtract(minimum);
-
-			for (Object[] row : currentResultSet) {
-				valueObject = row[lastIndex];
-				if (valueObject == null) {
-					row[lastIndex] = "";
-				} else {
-					BigDecimal value = new BigDecimal(valueObject.toString());
-					BigDecimal valueFromMinimum = value.subtract(minimum);
-
-					BigDecimal points = chartPrecision.divide(range, mathContext).multiply(valueFromMinimum, mathContext);
-					int pointsRounded = points.setScale(0, RoundingMode.HALF_UP).intValue();
-					row[lastIndex] = Functions.repeat(chartFull, pointsRounded) + Functions.repeat(chartEmpty, chartPrecision.intValue() - pointsRounded);
-				}
-			}
-
-		} catch (NumberFormatException e) {
-			// https://en.wiktionary.org/wiki/parsable
-			log.log(Level.SEVERE, "Last column must be number or an object with toString() value parsable to a number. But was „{0}“", valueObject);
-			// FIXME: throw FormatterException
-			throw e;
-		}
-	}
-
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/ColumnDescriptor.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import java.sql.Types;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class ColumnDescriptor {
-
-	private String name;
-	private String label;
-	private int type;
-	private String typeName;
-	private boolean firstColumn;
-	private boolean lastColumn;
-	private int columnNumber;
-
-	/**
-	 * @return column name
-	 * @see #getLabel()
-	 */
-	public String getName() {
-		return name;
-	}
-
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	/**
-	 * @return label specified by the SQL AS clause
-	 */
-	public String getLabel() {
-		return label;
-	}
-
-	public void setLabel(String label) {
-		this.label = label;
-	}
-
-	public int getType() {
-		return type;
-	}
-
-	public void setType(int type) {
-		this.type = type;
-	}
-
-	public String getTypeName() {
-		return typeName;
-	}
-
-	public void setTypeName(String typeName) {
-		this.typeName = typeName;
-	}
-
-	public boolean isFirstColumn() {
-		return firstColumn;
-	}
-
-	public void setFirstColumn(boolean firstColumn) {
-		this.firstColumn = firstColumn;
-	}
-
-	public boolean isLastColumn() {
-		return lastColumn;
-	}
-
-	public void setLastColumn(boolean lastColumn) {
-		this.lastColumn = lastColumn;
-	}
-
-	/**
-	 * @return number of this column, 1 = first
-	 */
-	public int getColumnNumber() {
-		return columnNumber;
-	}
-
-	public void setColumnNumber(int columnNumber) {
-		this.columnNumber = columnNumber;
-	}
-
-	public boolean isBoolean() {
-		return type == Types.BOOLEAN;
-	}
-
-	public boolean isNumeric() {
-		switch (type) {
-			case Types.BIGINT:
-			case Types.DECIMAL:
-			case Types.DOUBLE:
-			case Types.FLOAT:
-			case Types.INTEGER:
-			case Types.NUMERIC:
-			case Types.REAL:
-			case Types.SMALLINT:
-			case Types.TINYINT:
-				return true;
-			default:
-				return false;
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/ColumnsHeader.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class ColumnsHeader {
-	
-	private ResultSetMetaData metaData;
-	
-	public ColumnsHeader(ResultSetMetaData metaData) {
-		this.metaData = metaData;
-	}
-	
-	public int getColumnCount() {
-		try {
-			return metaData.getColumnCount();
-		} catch (SQLException e) {
-			throw new IllegalStateException("Error during getting column count.", e);
-		}
-	}
-	
-	public List<ColumnDescriptor> getColumnDescriptors() {
-		try {
-			int count = metaData.getColumnCount();
-			List<ColumnDescriptor> list = new ArrayList<>(count);
-			
-			for (int i = 1; i <= count; i++) {
-				ColumnDescriptor cd = new ColumnDescriptor();
-				
-				cd.setFirstColumn(i == 1);
-				cd.setLastColumn(i == count);
-				cd.setColumnNumber(i);
-				
-				cd.setLabel(metaData.getColumnLabel(i));
-				cd.setName(metaData.getColumnName(i));
-				cd.setType(metaData.getColumnType(i));
-				cd.setTypeName(metaData.getColumnTypeName(i));
-				/** TODO: more properties */
-				list.add(cd);
-			}
-			
-			return list;
-		} catch (SQLException e) {
-			throw new IllegalStateException("Error during building column descriptors.", e);
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/CommonProperties.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2015 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class CommonProperties {
-
-	private static final Map<Class, String> TYPE_SIMPLE_NAMES;
-
-	static {
-		Map<Class, String> m = new HashMap<>();
-		m.put(Boolean.class, "boolean");
-		m.put(String.class, "String");
-		m.put(Character.class, "char");
-		m.put(Integer.class, "int");
-		m.put(Long.class, "long");
-		m.put(Double.class, "double");
-		TYPE_SIMPLE_NAMES = Collections.unmodifiableMap(m);
-	}
-
-	public static String getSimpleTypeName(Class type) {
-		String name = TYPE_SIMPLE_NAMES.get(type);
-		return name == null ? type.getName() : name;
-	}
-
-	public static final String COLORFUL = "color";
-	public static final String COLORFUL_DESCRIPTION = "whether the output should be printed in color (ANSI Escape Sequences)";
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/FakeSqlArray.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.SQLType;
-import java.sql.Array;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Map;
-
-/**
- * Fake SQL array, for formatting purposes only
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class FakeSqlArray implements Array {
-
-	private static final UnsupportedOperationException exception = new UnsupportedOperationException("This is just a fake SQL array.");
-	private final Object[] data;
-	private final SQLType baseType;
-
-	public FakeSqlArray(Object[] data, SQLType baseType) {
-		this.data = data;
-		this.baseType = baseType;
-	}
-
-	@Override
-	public String toString() {
-		StringBuilder string = new StringBuilder();
-		for (Object o : data) {
-			string.append(o);
-			string.append("\n");
-		}
-		return string.toString();
-	}
-
-	@Override
-	public String getBaseTypeName() throws SQLException {
-		return baseType.name();
-	}
-
-	@Override
-	public int getBaseType() throws SQLException {
-		return baseType.getCode();
-	}
-
-	@Override
-	public Object getArray() throws SQLException {
-		return data;
-	}
-
-	@Override
-	public Object getArray(Map<String, Class<?>> map) throws SQLException {
-		throw exception;
-	}
-
-	@Override
-	public Object getArray(long index, int count) throws SQLException {
-		throw exception;
-	}
-
-	@Override
-	public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException {
-		throw exception;
-	}
-
-	@Override
-	public ResultSet getResultSet() throws SQLException {
-		throw exception;
-	}
-
-	@Override
-	public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {
-		throw exception;
-	}
-
-	@Override
-	public ResultSet getResultSet(long index, int count) throws SQLException {
-		throw exception;
-	}
-
-	@Override
-	public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) throws SQLException {
-		throw exception;
-	}
-
-	@Override
-	public void free() throws SQLException {
-		throw exception;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/Formatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.Parameter;
-import info.globalcode.sql.dk.configuration.DatabaseDefinition;
-import java.util.List;
-
-/**
- * The formatter is responsible for printing the result sets and/or updates result (count of
- * inserted/updated rows). The formatter can produce output in arbitrary format – text, some markup
- * or even binary data.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public interface Formatter extends AutoCloseable {
-
-	void writeStartBatch();
-
-	void writeStartDatabase(DatabaseDefinition databaseDefinition);
-
-	void writeEndDatabase();
-
-	void writeStartStatement();
-
-	void writeEndStatement();
-
-	void writeQuery(String sql);
-
-	void writeParameters(List<? extends Parameter> parameters);
-
-	void writeStartResultSet(ColumnsHeader header);
-
-	void writeEndResultSet();
-
-	void writeStartRow();
-
-	void writeColumnValue(Object value);
-
-	void writeEndRow();
-
-	void writeUpdatesResult(int updatedRowsCount);
-
-	void writeEndBatch();
-
-	/**
-	 * If an error occurs (e.g. lost connection during result set reading) this method will be
-	 * called even if there was no {@linkplain #writeEndBach()}.
-	 */
-	@Override
-	void close() throws FormatterException;
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/FormatterContext.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.configuration.Properties;
-import java.io.OutputStream;
-
-/**
- * To be passed from the SQL-DK core to the formatter.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class FormatterContext {
-
-	private OutputStream outputStream;
-	private Properties properties;
-
-	public FormatterContext(OutputStream outputStream, Properties properties) {
-		this.outputStream = outputStream;
-		this.properties = properties;
-	}
-
-	public OutputStream getOutputStream() {
-		return outputStream;
-	}
-
-	public Properties getProperties() {
-		return properties;
-	}
-
-	public void setProperties(Properties properties) {
-		this.properties = properties;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/FormatterException.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.DKException;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class FormatterException extends DKException {
-
-	public FormatterException() {
-	}
-
-	public FormatterException(String message) {
-		super(message);
-	}
-
-	public FormatterException(Throwable cause) {
-		super(cause);
-	}
-
-	public FormatterException(String message, Throwable cause) {
-		super(message, cause);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/SilentFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-/**
- * Does not output anything, can be used instead of
- * <code>/dev/null</code>.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class SilentFormatter extends AbstractFormatter {
-	
-	public static final String NAME = "silent"; // bash-completion:formatter
-
-	public SilentFormatter(FormatterContext formatterContext) {
-		super(formatterContext);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/SingleRecordFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2015 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.ColorfulPrintWriter;
-import info.globalcode.sql.dk.Functions;
-import info.globalcode.sql.dk.configuration.PropertyDeclaration;
-import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
-import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
-
-/**
- * Formatter intended for printing one record (or few records) with many columns.
- * Prints each colum name and its value on separate line.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-@PropertyDeclaration(name = COLORFUL, defaultValue = "true", type = Boolean.class, description = COLORFUL_DESCRIPTION)
-public class SingleRecordFormatter extends AbstractFormatter {
-
-	public static final String NAME = "record"; // bash-completion:formatter
-	private final ColorfulPrintWriter out;
-	private boolean firstResult = true;
-
-	public SingleRecordFormatter(FormatterContext formatterContext) {
-		super(formatterContext);
-		out = new ColorfulPrintWriter(formatterContext.getOutputStream());
-		out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
-	}
-
-	@Override
-	public void writeStartResultSet(ColumnsHeader header) {
-		super.writeStartResultSet(header);
-		printResultSeparator();
-	}
-
-	@Override
-	public void writeStartRow() {
-		super.writeStartRow();
-		printRecordSeparator();
-		out.print(ColorfulPrintWriter.TerminalColor.Red, "Record: ");
-		out.print(getCurrentRowCount());
-		println();
-	}
-
-	@Override
-	public void writeColumnValue(Object value) {
-		super.writeColumnValue(value);
-		String columnName = getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel();
-		out.print(ColorfulPrintWriter.TerminalColor.Green, columnName + ": ");
-		Functions.printValueWithWhitespaceReplaced(out, toString(value), null, ColorfulPrintWriter.TerminalColor.Red);
-		println();
-	}
-
-	private static String toString(Object value) {
-		return String.valueOf(value);
-	}
-
-	@Override
-	public void writeUpdatesResult(int updatedRowsCount) {
-		super.writeUpdatesResult(updatedRowsCount);
-		printResultSeparator();
-		out.print(ColorfulPrintWriter.TerminalColor.Red, "Updated records: ");
-		out.println(updatedRowsCount);
-		printBellAndFlush();
-	}
-
-	private void printBellAndFlush() {
-		out.bell();
-		out.flush();
-	}
-
-	private void println() {
-		out.println();
-		printBellAndFlush();
-	}
-
-	private void printRecordSeparator() {
-		if (getCurrentRowCount() > 1) {
-			println();
-		}
-	}
-
-	private void printResultSeparator() {
-		if (firstResult) {
-			firstResult = false;
-		} else {
-			println();
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/SingleValueFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import java.io.PrintWriter;
-
-/**
- * Prints just the value without any formatting. If the result set contains multiple records or
- * columns, the values are simply concatenate without any separators. If updates result is returned,
- * the updated records count is printed.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class SingleValueFormatter extends AbstractFormatter {
-
-	public static final String NAME = "single"; // bash-completion:formatter
-	private PrintWriter out;
-
-	public SingleValueFormatter(FormatterContext formatterContext) {
-		super(formatterContext);
-		this.out = new PrintWriter(formatterContext.getOutputStream());
-	}
-
-	@Override
-	public void writeColumnValue(Object value) {
-		super.writeColumnValue(value);
-		out.print(String.valueOf(value));
-		out.flush();
-	}
-
-	@Override
-	public void writeUpdatesResult(int updatedRowsCount) {
-		super.writeUpdatesResult(updatedRowsCount);
-		out.print(updatedRowsCount);
-		out.flush();
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,308 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.ColorfulPrintWriter;
-import static info.globalcode.sql.dk.ColorfulPrintWriter.*;
-import info.globalcode.sql.dk.Functions;
-import static info.globalcode.sql.dk.Functions.lpad;
-import static info.globalcode.sql.dk.Functions.rpad;
-import static info.globalcode.sql.dk.Functions.repeat;
-import info.globalcode.sql.dk.configuration.PropertyDeclaration;
-import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
-import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
-import java.sql.SQLException;
-import java.sql.SQLXML;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * <p>
- * Prints human-readable output – tables of result sets and text messages with update counts.
- * </p>
- *
- * <p>
- * Longer values might break the table – overflow the cells – see alternative tabular formatters and
- * the {@linkplain #PROPERTY_TRIM} property.
- * </p>
- *
- * @author Ing. František Kučera (frantovo.cz)
- * @see TabularPrefetchingFormatter
- * @see TabularWrappingFormatter
- */
-@PropertyDeclaration(name = COLORFUL, defaultValue = "true", type = Boolean.class, description = COLORFUL_DESCRIPTION)
-@PropertyDeclaration(name = TabularFormatter.PROPERTY_ASCII, defaultValue = "false", type = Boolean.class, description = "whether to use ASCII table borders instead of unicode ones")
-@PropertyDeclaration(name = TabularFormatter.PROPERTY_TRIM, defaultValue = "false", type = Boolean.class, description = "whether to trim the values to fit the column width")
-@PropertyDeclaration(name = TabularFormatter.PROPERTY_HEADER_TYPE, defaultValue = "true", type = Boolean.class, description = "whether to print data types in column headers")
-public class TabularFormatter extends AbstractFormatter {
-
-	private static final Logger log = Logger.getLogger(TabularFormatter.class.getName());
-	public static final String NAME = "tabular"; // bash-completion:formatter
-	private static final String HEADER_TYPE_PREFIX = " (";
-	private static final String HEADER_TYPE_SUFFIX = ")";
-	public static final String PROPERTY_ASCII = "ascii";
-	public static final String PROPERTY_TRIM = "trim";
-	public static final String PROPERTY_HEADER_TYPE = "headerTypes";
-	protected ColorfulPrintWriter out;
-	private boolean firstResult = true;
-	private int[] columnWidth;
-	/**
-	 * use ASCII borders instead of unicode ones
-	 */
-	private final boolean asciiNostalgia;
-	/**
-	 * Trim values if they are longer than cell size
-	 */
-	private final boolean trimValues;
-	/**
-	 * Print data type of each column in the header
-	 */
-	private final boolean printHeaderTypes;
-
-	public TabularFormatter(FormatterContext formatterContext) {
-		super(formatterContext);
-		out = new ColorfulPrintWriter(formatterContext.getOutputStream());
-		asciiNostalgia = formatterContext.getProperties().getBoolean(PROPERTY_ASCII, false);
-		trimValues = formatterContext.getProperties().getBoolean(PROPERTY_TRIM, false);
-		printHeaderTypes = formatterContext.getProperties().getBoolean(PROPERTY_HEADER_TYPE, true);
-		out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
-	}
-
-	@Override
-	public void writeStartResultSet(ColumnsHeader header) {
-		super.writeStartResultSet(header);
-		printResultSeparator();
-
-		initColumnWidths(header.getColumnCount());
-
-		printTableIndent();
-		printTableBorder("╭");
-
-		List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
-
-		for (ColumnDescriptor cd : columnDescriptors) {
-			// padding: make header cell at least same width as data cells in this column
-			int typeWidth = printHeaderTypes ? cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length() : 0;
-			cd.setLabel(rpad(cd.getLabel(), getColumnWidth(cd.getColumnNumber()) - typeWidth));
-			updateColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + typeWidth);
-
-			if (!cd.isFirstColumn()) {
-				printTableBorder("┬");
-			}
-			printTableBorder(repeat('─', getColumnWidth(cd.getColumnNumber()) + 2));
-		}
-		printTableBorder("╮");
-		out.println();
-
-		for (ColumnDescriptor cd : columnDescriptors) {
-			if (cd.isFirstColumn()) {
-				printTableIndent();
-				printTableBorder("│ ");
-			} else {
-				printTableBorder(" │ ");
-			}
-			out.print(TerminalStyle.Bright, cd.getLabel());
-			if (printHeaderTypes) {
-				out.print(HEADER_TYPE_PREFIX);
-				out.print(cd.getTypeName());
-				out.print(HEADER_TYPE_SUFFIX);
-			}
-			if (cd.isLastColumn()) {
-				printTableBorder(" │");
-			}
-		}
-		out.println();
-
-		printTableIndent();
-		printTableBorder("├");
-		for (int i = 1; i <= header.getColumnCount(); i++) {
-			if (i > 1) {
-				printTableBorder("┼");
-			}
-			printTableBorder(repeat('─', getColumnWidth(i) + 2));
-		}
-		printTableBorder("┤");
-		out.println();
-
-		out.flush();
-	}
-
-	/**
-	 * Must be called before {@linkplain #updateColumnWidth(int, int)} and
-	 * {@linkplain #getColumnWidth(int)} for each result set.
-	 *
-	 * @param columnCount number of columns in current result set
-	 */
-	protected void initColumnWidths(int columnCount) {
-		if (columnWidth == null) {
-			columnWidth = new int[columnCount];
-		}
-	}
-
-	protected void cleanColumnWidths() {
-		columnWidth = null;
-	}
-
-	@Override
-	public void writeColumnValue(Object value) {
-		super.writeColumnValue(value);
-		writeColumnValueInternal(value);
-	}
-
-	protected void writeColumnValueInternal(Object value) {
-
-		if (isCurrentColumnFirst()) {
-			printTableIndent();
-			printTableBorder("│ ");
-		} else {
-			printTableBorder(" │ ");
-		}
-
-		printValueWithWhitespaceReplaced(toString(value));
-
-		if (isCurrentColumnLast()) {
-			printTableBorder(" │");
-		}
-
-	}
-
-	protected void printValueWithWhitespaceReplaced(String text) {
-		Functions.printValueWithWhitespaceReplaced(out, text, TerminalColor.Cyan, TerminalColor.Red);
-	}
-
-	protected int getColumnWidth(int columnNumber) {
-		return columnWidth[columnNumber - 1];
-	}
-
-	private void setColumnWidth(int columnNumber, int width) {
-		columnWidth[columnNumber - 1] = width;
-	}
-
-	protected void updateColumnWidth(int columnNumber, int width) {
-		int oldWidth = getColumnWidth(columnNumber);
-		setColumnWidth(columnNumber, Math.max(width, oldWidth));
-
-	}
-
-	protected String toString(Object value) {
-		final int width = getColumnWidth(getCurrentColumnsCount());
-		String result;
-		if (value instanceof Number || value instanceof Boolean) {
-			result = lpad(String.valueOf(value), width);
-		} else {
-			if (value instanceof SQLXML) {
-				// TODO: move to a common method, share with other formatters
-				try {
-					value = ((SQLXML) value).getString();
-				} catch (SQLException e) {
-					log.log(Level.SEVERE, "Unable to format XML", e);
-				}
-			}
-
-			result = rpad(String.valueOf(value), width);
-		}
-		// ?	value = (boolean) value ? "✔" : "✗";
-
-		if (trimValues && result.length() > width) {
-			result = result.substring(0, width - 1) + "…";
-		}
-
-		return result;
-	}
-
-	@Override
-	public void writeEndRow() {
-		super.writeEndRow();
-		writeEndRowInternal();
-	}
-
-	public void writeEndRowInternal() {
-		out.println();
-		out.flush();
-	}
-
-	@Override
-	public void writeEndResultSet() {
-		int columnCount = getCurrentColumnsHeader().getColumnCount();
-		super.writeEndResultSet();
-
-		printTableIndent();
-		printTableBorder("╰");
-		for (int i = 1; i <= columnCount; i++) {
-			if (i > 1) {
-				printTableBorder("┴");
-			}
-			printTableBorder(repeat('─', getColumnWidth(i) + 2));
-		}
-		printTableBorder("╯");
-		out.println();
-
-		cleanColumnWidths();
-
-		out.print(TerminalColor.Yellow, "Record count: ");
-		out.println(getCurrentRowCount());
-		out.bell();
-		out.flush();
-	}
-
-	@Override
-	public void writeUpdatesResult(int updatedRowsCount) {
-		super.writeUpdatesResult(updatedRowsCount);
-		printResultSeparator();
-		out.print(TerminalColor.Red, "Updated records: ");
-		out.println(updatedRowsCount);
-		out.bell();
-		out.flush();
-	}
-
-	@Override
-	public void writeEndDatabase() {
-		super.writeEndDatabase();
-		out.flush();
-	}
-
-	private void printResultSeparator() {
-		if (firstResult) {
-			firstResult = false;
-		} else {
-			out.println();
-		}
-	}
-
-	protected void printTableBorder(String border) {
-		if (asciiNostalgia) {
-			border = border.replaceAll("─", "-");
-			border = border.replaceAll("│", "|");
-			border = border.replaceAll("[╭┬╮├┼┤╰┴╯]", "+");
-		}
-
-		out.print(TerminalColor.Green, border);
-	}
-
-	protected void printTableIndent() {
-		out.print(" ");
-	}
-
-	/**
-	 * @return whether should print only ASCII characters instead of unlimited Unicode.
-	 */
-	protected boolean isAsciiNostalgia() {
-		return asciiNostalgia;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * <p>
- * Prefetches whole result set and computes column widths. Whole table is flushed at once in
- * {@linkplain #writeEndResultSet()}.
- * </p>
- *
- * <p>
- * Long values will not overflow the cells, but whole result set must be loaded into the memory.
- * </p>
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class TabularPrefetchingFormatter extends TabularFormatter {
-
-	public static final String NAME = "tabular-prefetching"; // bash-completion:formatter
-	private ColumnsHeader currentHeader;
-	private List<Object[]> currentResultSet;
-	private Object[] currentRow;
-	private int currentColumnsCount;
-	private boolean prefetchDone = false;
-
-	public TabularPrefetchingFormatter(FormatterContext formatterContext) {
-		super(formatterContext);
-	}
-
-	@Override
-	protected int getCurrentColumnsCount() {
-		if (prefetchDone) {
-			return super.getCurrentColumnsCount();
-		} else {
-			return currentColumnsCount;
-		}
-	}
-
-	@Override
-	public void writeStartResultSet(ColumnsHeader header) {
-		currentResultSet = new ArrayList<>();
-		currentHeader = header;
-		initColumnWidths(header.getColumnCount());
-	}
-
-	@Override
-	public void writeStartRow() {
-		currentRow = new Object[currentHeader.getColumnCount()];
-		currentResultSet.add(currentRow);
-		currentColumnsCount = 0;
-	}
-
-	@Override
-	public void writeColumnValue(Object value) {
-		currentRow[currentColumnsCount] = value;
-		currentColumnsCount++;
-		String textRepresentation = toString(value);
-		/** TODO: count only printable characters (currently not an issue) */
-		updateColumnWidth(currentColumnsCount, textRepresentation.length());
-	}
-
-	@Override
-	public void writeEndRow() {
-		// do nothing
-	}
-
-	@Override
-	public void writeEndResultSet() {
-		prefetchDone = true;
-
-		postprocessPrefetchedResultSet(currentHeader, currentResultSet);
-
-		super.writeStartResultSet(currentHeader);
-
-		for (Object[] row : currentResultSet) {
-			super.writeStartRow();
-			for (Object cell : row) {
-				super.writeColumnValue(cell);
-			}
-			super.writeEndRow();
-		}
-
-		currentColumnsCount = 0;
-		currentHeader = null;
-		currentRow = null;
-		currentResultSet = null;
-		super.writeEndResultSet();
-		prefetchDone = false;
-	}
-
-	/**
-	 * Optional post-processing – override in sub-classes if needed.
-	 * Don't forget to {@linkplain #updateColumnWidth(int, int)}
-	 *
-	 * @param currentHeader
-	 * @param currentResultSet
-	 */
-	protected void postprocessPrefetchedResultSet(ColumnsHeader currentHeader, List<Object[]> currentResultSet) {
-	}
-
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularWrappingFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
-import java.util.ArrayList;
-import java.util.List;
-import static info.globalcode.sql.dk.Functions.lpad;
-import static info.globalcode.sql.dk.Functions.rpad;
-import static info.globalcode.sql.dk.Functions.repeat;
-
-/**
- * Longer values are line-wrapped – the cell then contains multiple lines. Marks are added to
- * signalize forced line ends (not present in original data).
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class TabularWrappingFormatter extends TabularFormatter {
-
-	public static final String NAME = "tabular-wrapping"; // bash-completion:formatter
-	private List<String[]> currentRow;
-
-	public TabularWrappingFormatter(FormatterContext formatterContext) {
-		super(formatterContext);
-	}
-
-	@Override
-	public void writeStartResultSet(ColumnsHeader header) {
-		super.writeStartResultSet(header);
-		currentRow = new ArrayList<>(header.getColumnCount());
-	}
-
-	@Override
-	protected void writeColumnValueInternal(Object value) {
-		boolean rightAlign = value instanceof Number || value instanceof Boolean;
-		String valueString = String.valueOf(value);
-		int columnWidth = getColumnWidth(getCurrentColumnsCount()) - 1;  // -1 = space for new line symbol
-		currentRow.add(wrapLines(valueString, columnWidth, rightAlign));
-	}
-
-	@Override
-	public void writeEndRow() {
-		super.writeEndRow();
-
-		int wrappedLine = 0;
-		boolean hasMoreWrappedLines;
-
-		do {
-			hasMoreWrappedLines = false;
-			for (int i = 0; i < currentRow.size(); i++) {
-				if (i == 0) {
-					printTableIndent();
-					printTableBorder("│ ");
-				} else {
-					printTableBorder(" │ ");
-				}
-				String[] columnArray = currentRow.get(i);
-				if (wrappedLine < columnArray.length) {
-					printValueWithWhitespaceReplaced(columnArray[wrappedLine]);
-
-					if (wrappedLine < columnArray.length - 1) {
-						out.print(TerminalColor.Red, "↩");
-						hasMoreWrappedLines = true;
-					} else {
-						out.print(" ");
-					}
-
-				} else {
-					out.print(repeat(' ', getColumnWidth(i + 1)));
-				}
-
-				if (i == (currentRow.size() - 1)) {
-					printTableBorder(" │");
-				}
-			}
-			out.println();
-			out.flush();
-			wrappedLine++;
-		} while (hasMoreWrappedLines);
-
-		currentRow.clear();
-	}
-
-	@Override
-	public void writeEndRowInternal() {
-		// already done – wrapped row ends
-	}
-
-	private static String[] wrapLines(String s, int width, boolean rightAlign) {
-		String[] array = new String[(s.length() - 1) / width + 1];
-		for (int i = 0; i < array.length; i++) {
-			if (i == array.length - 1) {
-				String part = s.substring(i * width, s.length());
-				array[i] = rightAlign ? lpad(part, width) : rpad(part, width);
-			} else {
-				array[i] = s.substring(i * width, (i + 1) * width);
-			}
-		}
-		return array;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/TeXFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,208 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.ColorfulPrintWriter;
-import info.globalcode.sql.dk.Constants;
-import info.globalcode.sql.dk.configuration.PropertyDeclaration;
-import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
-import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Outputs result sets in (La)TeX format.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-@PropertyDeclaration(name = COLORFUL, defaultValue = "false", type = Boolean.class, description = COLORFUL_DESCRIPTION)
-public class TeXFormatter extends AbstractFormatter {
-
-	public static final String NAME = "tex"; // bash-completion:formatter
-	private static final ColorfulPrintWriter.TerminalColor COMMAND_COLOR = ColorfulPrintWriter.TerminalColor.Magenta;
-	private static final ColorfulPrintWriter.TerminalColor OPTIONS_COLOR = ColorfulPrintWriter.TerminalColor.Yellow;
-	private static final Map<Character, String> TEX_ESCAPE_MAP;
-	private final ColorfulPrintWriter out;
-
-	static {
-		Map<Character, String> replacements = new HashMap<>();
-
-		replacements.put('\\', "\\textbackslash{}");
-		replacements.put('{', "\\{{}");
-		replacements.put('}', "\\}{}");
-		replacements.put('_', "\\_{}");
-		replacements.put('^', "\\textasciicircum{}");
-		replacements.put('#', "\\#{}");
-		replacements.put('&', "\\&{}");
-		replacements.put('$', "\\${}");
-		replacements.put('%', "\\%{}");
-		replacements.put('~', "\\textasciitilde{}");
-		replacements.put('-', "{-}");
-
-		TEX_ESCAPE_MAP = Collections.unmodifiableMap(replacements);
-	}
-
-	public TeXFormatter(FormatterContext formatterContext) {
-		super(formatterContext);
-		boolean colorful = formatterContext.getProperties().getBoolean(COLORFUL, false);
-		out = new ColorfulPrintWriter(formatterContext.getOutputStream(), false, colorful);
-	}
-
-	@Override
-	public void writeStartBatch() {
-		super.writeStartBatch();
-
-		printCommand("documentclass", "a4paper,twoside", "article", true);
-		printCommand("usepackage", "T1", "fontenc", true);
-		printCommand("usepackage", "utf8x", "inputenc", true);
-		printCommand("usepackage", "pdfauthor={" + Constants.WEBSITE + "}, bookmarks=true,unicode,colorlinks=true,linkcolor=black,urlcolor=blue,citecolor=blue", "hyperref", true);
-		printBegin("document");
-	}
-
-	@Override
-	public void writeEndBatch() {
-		super.writeEndBatch();
-		printEnd("document");
-	}
-
-	@Override
-	public void writeColumnValue(Object value) {
-		super.writeColumnValue(value);
-		// TODO: arrays, numbers, booleans, nulls etc.:
-		out.print(escapeTex(toString(value)));
-
-		if (!isCurrentColumnLast()) {
-			printColumnSeparator();
-		}
-	}
-
-	@Override
-	public void writeEndRow() {
-		super.writeEndRow();
-		printEndRow();
-	}
-
-	@Override
-	public void writeStartResultSet(ColumnsHeader header) {
-		super.writeStartResultSet(header);
-		printCommand("begin", null, "tabular", false);
-
-		List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
-
-		StringBuilder columnAlignments = new StringBuilder();
-		for (ColumnDescriptor cd : columnDescriptors) {
-			if (cd.isNumeric() || cd.isBoolean()) {
-				columnAlignments.append('r');
-			} else {
-				columnAlignments.append('l');
-			}
-		}
-
-		printCommand(null, null, columnAlignments.toString(), true);
-		printCommand("hline", null, null, true);
-
-		for (ColumnDescriptor cd : columnDescriptors) {
-			printCommand("textbf", null, cd.getLabel(), false);
-			if (cd.isLastColumn()) {
-				printEndRow();
-			} else {
-				printColumnSeparator();
-			}
-		}
-
-		printCommand("hline", null, null, true);
-	}
-
-	@Override
-	public void writeEndResultSet() {
-		super.writeEndResultSet();
-		printCommand("hline", null, null, true);
-		printEnd("tabular");
-	}
-
-	private String escapeTex(String text) {
-		if (text == null) {
-			return null;
-		} else {
-			StringBuilder result = new StringBuilder(text.length() * 2);
-
-			for (char ch : text.toCharArray()) {
-				String replacement = TEX_ESCAPE_MAP.get(ch);
-				result.append(replacement == null ? ch : replacement);
-			}
-
-			return result.toString();
-		}
-	}
-
-	protected String toString(Object value) {
-		return String.valueOf(value);
-	}
-
-	private void printColumnSeparator() {
-		out.print(COMMAND_COLOR, " & ");
-	}
-
-	private void printEndRow() {
-		out.println(COMMAND_COLOR, " \\\\");
-		out.flush();
-	}
-
-	/**
-	 *
-	 * @param command will not be escaped – should contain just a valid TeX command name
-	 * @param options will not be escaped – should be properly formatted to be printed inside [
-	 * and ]
-	 * @param value will be escaped
-	 * @param println whether to print line end and flush
-	 */
-	private void printCommand(String command, String options, String value, boolean println) {
-
-		if (command != null) {
-			out.print(COMMAND_COLOR, "\\" + command);
-		}
-
-		if (options != null) {
-			out.print(COMMAND_COLOR, "[");
-			out.print(OPTIONS_COLOR, options);
-			out.print(COMMAND_COLOR, "]");
-		}
-
-		if (value != null) {
-			out.print(COMMAND_COLOR, "{");
-			out.print(escapeTex(value));
-			out.print(COMMAND_COLOR, "}");
-		}
-
-		if (println) {
-			out.println();
-			out.flush();
-		}
-	}
-
-	private void printBegin(String environment) {
-		printCommand("begin", null, environment, true);
-	}
-
-	private void printEnd(String environment) {
-		printCommand("end", null, environment, true);
-	}
-
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/XhtmlFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,262 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.Constants;
-import info.globalcode.sql.dk.NamedParameter;
-import info.globalcode.sql.dk.Parameter;
-import info.globalcode.sql.dk.Xmlns;
-import info.globalcode.sql.dk.configuration.DatabaseDefinition;
-import info.globalcode.sql.dk.configuration.Properties;
-import info.globalcode.sql.dk.configuration.Property;
-import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
-import java.sql.Array;
-import java.sql.SQLException;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.xml.namespace.QName;
-
-/**
- * Prints result sets and parameters as tables, SQL as preformatted and updates counts as
- * paragraphs. You can pick XHTML fragments (usually tabular data) and use it on your website or use
- * whole output as preview or report.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class XhtmlFormatter extends AbstractXmlFormatter {
-
-	private static final Logger log = Logger.getLogger(XhtmlFormatter.class.getName());
-	public static final String NAME = "xhtml"; // bash-completion:formatter
-	private static final String DOCTYPE = "html PUBLIC \"-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN\" \"http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd\"";
-	private static final String CSS_FILE = "info/globalcode/sql/dk/formatter/XhtmlFormatter.css";
-	private int statementCounter = 0;
-	private int resultSetCounter = 0;
-	private int updatesResultCounter = 0;
-
-	public XhtmlFormatter(FormatterContext formatterContext) {
-		super(addDefaults(formatterContext));
-	}
-
-	/**
-	 * Do not indent text – preserve whitespace for pre elements
-	 */
-	private static FormatterContext addDefaults(FormatterContext formatterContext) {
-		Properties defaults = new Properties(1);
-		defaults.add(new Property(PROPERTY_INDENT_TEXT, "false"));
-		formatterContext.getProperties().setLastDefaults(defaults);
-		return formatterContext;
-	}
-
-	@Override
-	public void writeStartBatch() {
-		super.writeStartBatch();
-		printStartDocument();
-		printDoctype(DOCTYPE);
-		printStartElement(qname("html"), singleAttribute(qname("xmlns"), Xmlns.XHTML));
-
-		printStartElement(qname("head"));
-		printTextElement(qname("title"), null, Constants.PROGRAM_NAME + ": batch results");
-		printCss();
-		printEndElement();
-
-		printStartElement(qname("body"));
-	}
-
-	private void printCss() {
-
-		try (Scanner css = new Scanner(getClass().getClassLoader().getResourceAsStream(CSS_FILE))) {
-			printStartElement(qname("style"), singleAttribute(qname("type"), "text/css"));
-			while (css.hasNext()) {
-				printText(css.nextLine(), true);
-			}
-			printEndElement();
-		}
-	}
-
-	@Override
-	public void writeEndBatch() {
-		super.writeEndBatch();
-		printEndElement();
-		printEndElement();
-		printEndDocument();
-	}
-
-	@Override
-	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
-		super.writeStartDatabase(databaseDefinition);
-		printTextElement(qname("h1"), null, "Database: " + databaseDefinition.getName());
-
-		printStartElement(qname("p"));
-		printText("This is XHTML output of batch executed at: ", true);
-		printText(new Date().toString(), true);
-		printEndElement();
-	}
-
-	@Override
-	public void writeQuery(String sql) {
-		super.writeQuery(sql);
-		printTextElement(qname("pre"), null, sql);
-	}
-
-	@Override
-	public void writeParameters(List<? extends Parameter> parameters) {
-		super.writeParameters(parameters);
-
-		if (parameters == null || parameters.isEmpty()) {
-			printTextElement(qname("p"), null, "(this query has no parameters)");
-		} else {
-			printTextElement(qname("h3"), null, "Parameters:");
-
-			printStartElement(qname("table"));
-
-			printStartElement(qname("thead"));
-			printStartElement(qname("tr"));
-			printTextElement(qname("td"), null, "id");
-			printTextElement(qname("td"), null, "type");
-			printTextElement(qname("td"), null, "value");
-			printEndElement();
-			printEndElement();
-
-			printStartElement(qname("tbody"));
-			for (int i = 0; i < parameters.size(); i++) {
-				Parameter p = parameters.get(i);
-				printStartElement(qname("tr"));
-				String numberOrName;
-				if (p instanceof NamedParameter) {
-					numberOrName = ((NamedParameter) p).getName();
-				} else {
-					numberOrName = String.valueOf(i + 1);
-				}
-				printTextElement(qname("td"), null, numberOrName);
-				printTextElement(qname("td"), null, p.getType().name());
-				printTableData(p.getValue());
-				printEndElement();
-			}
-			printEndElement();
-
-			printEndElement();
-		}
-	}
-
-	private void printTableData(Object value) {
-
-		if (value instanceof Array) {
-			Array sqlArray = (Array) value;
-			try {
-				Object[] array = (Object[]) sqlArray.getArray();
-				printStartElement(qname("td"));
-				printArray(array);
-				printEndElement();
-			} catch (SQLException e) {
-				log.log(Level.SEVERE, "Unable to format array", e);
-				printTableData(String.valueOf(value));
-			}
-		} else {
-			Map<QName, String> attributes = null;
-			if (value instanceof Number) {
-				attributes = singleAttribute(qname("class"), "number");
-			} else if (value instanceof Boolean) {
-				attributes = singleAttribute(qname("class"), "boolean");
-			}
-			printTextElement(qname("td"), attributes, String.valueOf(value));
-		}
-	}
-
-	private void printArray(Object[] array) {
-		printStartElement(qname("ul"));
-		for (Object o : array) {
-			if (o instanceof Object[]) {
-				printStartElement(qname("li"));
-				printTextElement(qname("p"), null, "nested array:");
-				printArray((Object[]) o);
-				printEndElement();
-			} else {
-				printTextElement(qname("li"), null, String.valueOf(o));
-			}
-		}
-		printEndElement();
-	}
-
-	@Override
-	public void writeStartResultSet(ColumnsHeader header) {
-		super.writeStartResultSet(header);
-		resultSetCounter++;
-		printEmptyElement(qname("hr"), null);
-		printTextElement(qname("h3"), null, "Result set #" + resultSetCounter);
-		printStartElement(qname("table"));
-		printStartElement(qname("thead"));
-		printStartElement(qname("tr"));
-		for (ColumnDescriptor cd : header.getColumnDescriptors()) {
-			// TODO: type
-			printTextElement(qname("td"), null, cd.getLabel());
-		}
-		printEndElement();
-		printEndElement();
-
-		printStartElement(qname("tbody"));
-	}
-
-	@Override
-	public void writeEndResultSet() {
-		super.writeEndResultSet();
-		printEndElement();
-		printEndElement();
-		printTextElement(qname("p"), null, "Record count: " + getCurrentRowCount());
-	}
-
-	@Override
-	public void writeStartRow() {
-		super.writeStartRow();
-		printStartElement(qname("tr"));
-	}
-
-	@Override
-	public void writeColumnValue(Object value) {
-		super.writeColumnValue(value);
-		printTableData(value);
-	}
-
-	@Override
-	public void writeEndRow() {
-		super.writeEndRow();
-		printEndElement();
-	}
-
-	@Override
-	public void writeStartStatement() {
-		super.writeStartStatement();
-		statementCounter++;
-		printEmptyElement(qname("hr"), null);
-		printTextElement(qname("h2"), null, "SQL statement #" + statementCounter);
-		resultSetCounter = 0;
-		updatesResultCounter = 0;
-	}
-
-	@Override
-	public void writeUpdatesResult(int updatedRowsCount) {
-		super.writeUpdatesResult(updatedRowsCount);
-		updatesResultCounter++;
-		printEmptyElement(qname("hr"), null);
-		printTextElement(qname("h3"), null, "Updates result #" + updatesResultCounter);
-		printTextElement(qname("p"), null, "Updated rows: " + updatedRowsCount);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/formatting/XmlFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,245 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.formatting;
-
-import info.globalcode.sql.dk.Parameter;
-import info.globalcode.sql.dk.Xmlns;
-import info.globalcode.sql.dk.configuration.DatabaseDefinition;
-import static info.globalcode.sql.dk.Functions.notNull;
-import info.globalcode.sql.dk.NamedParameter;
-import info.globalcode.sql.dk.configuration.PropertyDeclaration;
-import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
-import java.sql.Array;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.SQLXML;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.xml.namespace.QName;
-
-/**
- * <p>
- * Prints machine-readable output – XML document containing resultsets and updates count. Good
- * choice for further processing – e.g. XSL transformation.</p>
- *
- * <p>
- * TODO: XSD</p>
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-@PropertyDeclaration(name = XmlFormatter.PROPERTY_LABELED_COLUMNS, defaultValue = "false", type = Boolean.class, description = "whether to add 'label' attribute to each 'column' element")
-public class XmlFormatter extends AbstractXmlFormatter {
-
-	public static final String NAME = "xml"; // bash-completion:formatter
-	public static final String PROPERTY_LABELED_COLUMNS = "labeledColumns";
-	private static final Logger log = Logger.getLogger(XmlFormatter.class.getName());
-	private final boolean labeledColumns;
-
-	public XmlFormatter(FormatterContext formatterContext) {
-		super(formatterContext);
-		labeledColumns = formatterContext.getProperties().getBoolean(PROPERTY_LABELED_COLUMNS, false);
-	}
-
-	@Override
-	public void writeStartBatch() {
-		super.writeStartBatch();
-		printStartDocument();
-		printStartElement(qname("batchResult"), singleAttribute(qname("xmlns"), Xmlns.BATCH_RESULT));
-	}
-
-	@Override
-	public void writeEndBatch() {
-		super.writeEndBatch();
-		printEndElement();
-		printEndDocument();
-	}
-
-	@Override
-	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
-		super.writeStartDatabase(databaseDefinition);
-		Map<QName, String> attributes = databaseDefinition.getName() == null ? null : singleAttribute(qname("name"), databaseDefinition.getName());
-		printStartElement(qname("database"), attributes);
-	}
-
-	@Override
-	public void writeEndDatabase() {
-		super.writeEndDatabase();
-		printEndElement();
-	}
-
-	@Override
-	public void writeStartStatement() {
-		super.writeStartStatement();
-		printStartElement(qname("statement"));
-	}
-
-	@Override
-	public void writeEndStatement() {
-		super.writeEndStatement();
-		printEndElement();
-	}
-
-	@Override
-	public void writeQuery(String sql) {
-		super.writeQuery(sql);
-		printTextElement(qname("sql"), null, sql);
-	}
-
-	@Override
-	public void writeParameters(List<? extends Parameter> parameters) {
-		super.writeParameters(parameters);
-
-		for (Parameter p : notNull(parameters)) {
-
-			Map<QName, String> attributes = new LinkedHashMap<>(2);
-			if (p instanceof NamedParameter) {
-				attributes.put(qname("name"), ((NamedParameter) p).getName());
-			}
-			attributes.put(qname("type"), p.getType().name());
-
-			printTextElement(qname("parameter"), attributes, String.valueOf(p.getValue()));
-		}
-
-	}
-
-	@Override
-	public void writeStartResultSet(ColumnsHeader header) {
-		super.writeStartResultSet(header);
-		printStartElement(qname("resultSet"));
-
-		for (ColumnDescriptor cd : header.getColumnDescriptors()) {
-			Map<QName, String> attributes = new LinkedHashMap<>(4);
-			attributes.put(qname("label"), cd.getLabel());
-			attributes.put(qname("name"), cd.getName());
-			attributes.put(qname("typeName"), cd.getTypeName());
-			attributes.put(qname("type"), String.valueOf(cd.getType()));
-			printEmptyElement(qname("columnHeader"), attributes);
-		}
-	}
-
-	@Override
-	public void writeEndResultSet() {
-		super.writeEndResultSet();
-		printEndElement();
-	}
-
-	@Override
-	public void writeStartRow() {
-		super.writeStartRow();
-		printStartElement(qname("row"));
-	}
-
-	@Override
-	public void writeColumnValue(Object value) {
-		super.writeColumnValue(value);
-
-		Map<QName, String> attributes = null;
-		if (labeledColumns) {
-			attributes = new LinkedHashMap<>(2);
-			attributes.put(qname("label"), getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel());
-		}
-
-		if (value == null) {
-			if (attributes == null) {
-				attributes = new LinkedHashMap<>(2);
-			}
-			attributes.put(qname("null"), "true");
-			printEmptyElement(qname("column"), attributes);
-		} else if (value instanceof Array) {
-
-			Array sqlArray = (Array) value;
-			try {
-				Object[] array = (Object[]) sqlArray.getArray();
-				printStartElement(qname("column"), attributes);
-				printArray(array);
-				printEndElement();
-			} catch (SQLException e) {
-				// FIXME: rewrite array formatting, remember array mode, don't try sqlArray.getArray() again and again if it has failed
-				log.log(Level.SEVERE, "Unable to format array", e);
-				try {
-					ResultSet arrayResultSet = sqlArray.getResultSet();
-					//int columnCount = arrayResultSet.getMetaData().getColumnCount();
-					ArrayList<Object> arrayList = new ArrayList<>();
-					while (arrayResultSet.next()) {
-						arrayList.add(arrayResultSet.getObject(2));
-						// for (int i = 1; i <= columnCount; i++) {
-						// 	log.log(Level.INFO, "Array column {0} = {1}", new Object[]{i, arrayResultSet.getObject(i)});
-						// }
-					}
-
-					printStartElement(qname("column"), attributes);
-					// FIXME: instanceof SQLXML, see below
-					printArray(arrayList.toArray());
-					printEndElement();
-
-				} catch (SQLException e2) {
-					// FIXME: fix logging, error recovery
-					log.log(Level.SEVERE, "Second level fuck up !!!", e2);
-				}
-
-				writeColumnValue(String.valueOf(value));
-			}
-
-		} else if (value instanceof SQLXML) { // FIXME: move to separate method, to AbstractFormatter?
-			SQLXML xml = (SQLXML) value;
-			// TODO: parse DOM/SAX and transplant XML, don't escape (optional)
-			try {
-				printTextElement(qname("column"), attributes, xml.getString());
-			} catch (SQLException e) {
-				log.log(Level.SEVERE, "Unable to format XML", e);
-				writeColumnValue(String.valueOf(value));
-			}
-		} else {
-			printTextElement(qname("column"), attributes, toString(value));
-		}
-	}
-
-	private void printArray(Object[] array) {
-		printStartElement(qname("array"));
-		for (Object o : array) {
-			if (o instanceof Object[]) {
-				printStartElement(qname("item"));
-				printArray((Object[]) o);
-				printEndElement();
-			} else {
-				printTextElement(qname("item"), null, String.valueOf(o));
-			}
-		}
-		printEndElement();
-	}
-
-	@Override
-	public void writeEndRow() {
-		super.writeEndRow();
-		printEndElement();
-	}
-
-	@Override
-	public void writeUpdatesResult(int updatedRowsCount) {
-		super.writeUpdatesResult(updatedRowsCount);
-		printTextElement(qname("updatedRows"), null, String.valueOf(updatedRowsCount));
-	}
-
-	protected String toString(Object value) {
-		return String.valueOf(value);
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagement.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.jmx;
-
-import java.util.EnumMap;
-import java.util.Map;
-
-/**
- * JMX management bean for progress reporting.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class ConnectionManagement implements ConnectionManagementMBean {
-
-	private final String databaseName;
-	private final Map<COUNTER, Integer> counters = new EnumMap(COUNTER.class);
-
-	public ConnectionManagement(String databaseName) {
-		this.databaseName = databaseName;
-		for (COUNTER c : COUNTER.values()) {
-			counters.put(c, 0);
-		}
-	}
-
-	public enum COUNTER {
-
-		COMMAND,
-		RECORD_CURRENT,
-		RECORD_TOTAL
-	};
-
-	public void incrementCounter(COUNTER counter) {
-		synchronized (counters) {
-			int old = counters.get(counter);
-			counters.put(counter, old + 1);
-		}
-	}
-
-	public void resetCounter(COUNTER counter) {
-		synchronized (counters) {
-			counters.put(counter, 0);
-		}
-	}
-
-	public static void incrementCounter(ConnectionManagement mbean, COUNTER counter) {
-		if (mbean != null) {
-			mbean.incrementCounter(counter);
-		}
-	}
-
-	public static void resetCounter(ConnectionManagement mbean, COUNTER counter) {
-		if (mbean != null) {
-			mbean.resetCounter(counter);
-		}
-	}
-
-	@Override
-	public String getDatabaseName() {
-		return databaseName;
-	}
-
-	@Override
-	public int getCommandCount() {
-		synchronized (counters) {
-			return counters.get(COUNTER.COMMAND);
-		}
-	}
-
-	@Override
-	public int getCurrentRecordCount() {
-		synchronized (counters) {
-			return counters.get(COUNTER.RECORD_CURRENT);
-		}
-	}
-	
-	@Override
-	public int getTotalRecordCount() {
-		synchronized (counters) {
-			return counters.get(COUNTER.RECORD_TOTAL);
-		}
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.jmx;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public interface ConnectionManagementMBean {
-
-	public String getDatabaseName();
-
-	public int getCommandCount();
-
-	public int getCurrentRecordCount();
-
-	public int getTotalRecordCount();
-
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/jmx/ManagementUtils.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2014 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.jmx;
-
-import java.lang.management.ManagementFactory;
-import java.util.Hashtable;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.management.MBeanServer;
-import javax.management.ObjectName;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class ManagementUtils {
-
-	private static final Logger log = Logger.getLogger(ManagementUtils.class.getName());
-	public static final String DEFAULT_CONNECTION_JMX_NAME = "main";
-
-	/**
-	 * @see #registerMBean(java.lang.String, java.lang.String) with default JMX name
-	 */
-	public static ConnectionManagement registerMBean(String dbName) {
-		return registerMBean(dbName, DEFAULT_CONNECTION_JMX_NAME);
-	}
-
-	/**
-	 *
-	 * @param dbName database name
-	 * @param jmxName name of JMX bean
-	 * @return registered JMX bean | or null if registration fails (should not)
-	 */
-	public static ConnectionManagement registerMBean(String dbName, String jmxName) {
-		try {
-			ConnectionManagement mbean = new ConnectionManagement(dbName);
-			MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
-			Hashtable<String, String> objectProperties = new Hashtable<>();
-			objectProperties.put("type", "Connection");
-			objectProperties.put("name", jmxName);
-			ObjectName objectName = new ObjectName("info.globalcode.sql.dk", objectProperties);
-			mbs.registerMBean(mbean, objectName);
-			log.log(Level.FINE, "JMX MBean was registered as: {0}", objectName);
-			return mbean;
-		} catch (Exception e) {
-			log.log(Level.WARNING, "Unable to register JMX MBean", e);
-			return null;
-		}
-	}
-
-	private ManagementUtils() {
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/logging/ColorfulConsoleFormatter.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,97 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.logging;
-
-import info.globalcode.sql.dk.ColorfulPrintWriter;
-import static info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
-import static info.globalcode.sql.dk.ColorfulPrintWriter.TerminalStyle;
-import static info.globalcode.sql.dk.Functions.rpad;
-import java.io.StringWriter;
-import java.util.logging.Formatter;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
-
-/**
- * For console/terminal log output. Log messages are printed in brief and colorful form.
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class ColorfulConsoleFormatter extends Formatter {
-
-	private boolean printStacktrace = false;
-
-	@Override
-	public String format(LogRecord r) {
-		StringWriter sw = new StringWriter();
-		try (ColorfulPrintWriter out = new ColorfulPrintWriter(sw)) {
-			printLevel(out, r.getLevel());
-			printMessage(out, r);
-			printThrowable(out, r);
-			out.println();
-		}
-		return sw.toString();
-	}
-
-	private void printLevel(ColorfulPrintWriter out, Level l) {
-		TerminalColor color = TerminalColor.Magenta;
-
-		if (l == Level.SEVERE) {
-			color = TerminalColor.Red;
-		} else if (l == Level.WARNING) {
-			color = TerminalColor.Yellow;
-		}
-
-		out.print(color, rpad(l.getLocalizedName() + ": ", 10));
-	}
-
-	private void printMessage(ColorfulPrintWriter out, LogRecord r) {
-		out.print(formatMessage(r));
-	}
-
-	private void printThrowable(ColorfulPrintWriter out, LogRecord r) {
-		Throwable t = r.getThrown();
-		if (t != null) {
-			out.print(": ");
-			out.print(TerminalColor.Red, t.getClass().getSimpleName());
-			String message = t.getLocalizedMessage();
-			if (message != null) {
-				out.print(": ");
-				if (printStacktrace) {
-					out.print(message);
-				} else {
-					out.print(message.replaceAll("\\n", " "));
-				}
-			}
-			if (printStacktrace) {
-				out.println();
-				out.setForegroundColor(TerminalColor.Yellow);
-				out.setStyle(TerminalStyle.Dim);
-				t.printStackTrace(out);
-				out.resetAll();
-			}
-		}
-	}
-
-	public boolean isPrintStacktrace() {
-		return printStacktrace;
-	}
-
-	public void setPrintStacktrace(boolean printStacktrace) {
-		this.printStacktrace = printStacktrace;
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/logging/LoggerInitializer.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.logging;
-
-import info.globalcode.sql.dk.Constants;
-import java.util.logging.ConsoleHandler;
-import java.util.logging.Handler;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Configures logging subsystem.
- * Usage: java -Djava.util.logging.config.class=info.globalcode.sql.dk.logging.LoggerInitializer …
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class LoggerInitializer {
-
-	private static final Logger log = Logger.getLogger(LoggerInitializer.class.getName());
-	public static final String LEVEL_PROPERTY = LoggerInitializer.class.getName() + ".level";
-	private static final Level DEFAULT_LEVEL = Level.INFO;
-
-	public LoggerInitializer() {
-		Logger logger = Logger.getLogger(Constants.JAVA_PACKAGE);
-		ConsoleHandler handler = new ConsoleHandler();
-		ColorfulConsoleFormatter formatter = new ColorfulConsoleFormatter();
-
-		logger.addHandler(handler);
-		handler.setFormatter(formatter);
-
-		setLevel(logger, handler, formatter);
-
-
-		/**
-		 * TODO: optional FileHandler – detailed logs in file in ~/sql-dk/log/…
-		 */
-	}
-
-	private void setLevel(Logger logger, Handler handler, ColorfulConsoleFormatter formatter) {
-		boolean levelParseError = false;
-		Level level;
-		String cliLevel = System.getProperty(LEVEL_PROPERTY);
-		if (cliLevel == null) {
-			level = DEFAULT_LEVEL;
-		} else {
-			try {
-				level = Level.parse(cliLevel);
-			} catch (IllegalArgumentException e) {
-				level = DEFAULT_LEVEL;
-				levelParseError = true;
-			}
-		}
-
-		handler.setLevel(level);
-		logger.setLevel(level);
-
-		if (levelParseError) {
-			log.log(Level.WARNING, "Invalid logging level „{0}“ specified in „{1}“ → using default level „{2}“", new Object[]{cliLevel, LEVEL_PROPERTY, DEFAULT_LEVEL});
-		}
-
-		formatter.setPrintStacktrace(level.intValue() < Level.INFO.intValue());
-	}
-}
--- a/java/sql-dk/src/info/globalcode/sql/dk/logging/LoggerProducer.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2015 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk.logging;
-
-import java.util.logging.Logger;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class LoggerProducer {
-
-	/**
-	 * @return created logger for the caller class
-	 */
-	public static Logger getLogger() {
-		String className = Thread.currentThread().getStackTrace()[2].getClassName();
-		return Logger.getLogger(className);
-	}
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIOptions.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,283 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import static info.globalcode.sql.dk.Functions.isNotEmpty;
+import static info.globalcode.sql.dk.Functions.equalz;
+import info.globalcode.sql.dk.InfoLister.InfoType;
+import info.globalcode.sql.dk.configuration.Properties;
+import info.globalcode.sql.dk.configuration.Property;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Holds options from command line, validates them, combines with configuration and provides derived
+ * objects.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class CLIOptions {
+
+	public static final String DEFAULT_NAME_PREFIX = ":";
+	public static final String DEFAULT_NAME_SUFFIX = "(?=([^\\w]|$))";
+	private String sql;
+	private String databaseName;
+	private final Set<String> databaseNamesToTest = new LinkedHashSet<>();
+	private final Set<String> databaseNamesToListProperties = new LinkedHashSet<>();
+	private final Set<String> formatterNamesToListProperties = new LinkedHashSet<>();
+	private String namePrefix = DEFAULT_NAME_PREFIX;
+	private String nameSuffix = DEFAULT_NAME_SUFFIX;
+	private String formatterName;
+	private boolean batch;
+	private final Properties formatterProperties = new Properties();
+	private final Properties databaseProperties = new Properties();
+
+	public enum MODE {
+
+		QUERY_NOW,
+		PREPARE_BATCH,
+		EXECUTE_BATCH,
+		JUST_SHOW_INFO
+	}
+	private final List<NamedParameter> namedParameters = new ArrayList<>();
+	private final List<Parameter> numberedParameters = new ArrayList<>();
+	private final EnumSet<InfoType> showInfo = EnumSet.noneOf(InfoType.class);
+
+	public void validate() throws InvalidOptionsException {
+		InvalidOptionsException e = new InvalidOptionsException();
+
+		MODE mode = getMode();
+		if (mode == null) {
+			e.addProblem(new InvalidOptionsException.OptionProblem("Invalid combination of DB, SQL and BATCH – please specify just 2 of this 3 options"));
+		} else if (mode == MODE.JUST_SHOW_INFO) {
+			if (!namedParameters.isEmpty()) {
+				e.addProblem(new InvalidOptionsException.OptionProblem("Do not use named parameters if just showing info."));
+			}
+			if (!numberedParameters.isEmpty()) {
+				e.addProblem(new InvalidOptionsException.OptionProblem("Do not use numbered parameters if just showing info."));
+			}
+			if (isNotEmpty(sql, false)) {
+				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify SQL if just showing info."));
+			}
+			if (isNotEmpty(databaseName, false)) {
+				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify database if just showing info."));
+			}
+			if (batch) {
+				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify batch if just showing info."));
+			}
+			if (!equalz(namePrefix, DEFAULT_NAME_PREFIX)) {
+				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify name prefix if just showing info."));
+			}
+			if (!equalz(nameSuffix, DEFAULT_NAME_SUFFIX)) {
+				e.addProblem(new InvalidOptionsException.OptionProblem("Do not specify name suffix if just showing info."));
+			}
+			if (showInfo.contains(InfoType.CONNECTION) && databaseNamesToTest.isEmpty()) {
+				e.addProblem(new InvalidOptionsException.OptionProblem("Please specify which database should be tested."));
+			}
+			if (showInfo.contains(InfoType.JDBC_PROPERTIES) && databaseNamesToListProperties.isEmpty()) {
+				e.addProblem(new InvalidOptionsException.OptionProblem("Please specify for which database the properties should be listed."));
+			}
+		}
+
+		if (!namedParameters.isEmpty() && !numberedParameters.isEmpty()) {
+			e.addProblem(new InvalidOptionsException.OptionProblem("Named and numbered parameters can not be used together in one command."));
+		}
+
+		try {
+			Pattern.compile(namePrefix + "test" + nameSuffix);
+		} catch (PatternSyntaxException regexException) {
+			e.addProblem(new InvalidOptionsException.OptionProblem("Ivalid regular expression in name prefix or suffix", regexException));
+		}
+
+		if (e.hasProblems()) {
+			throw e;
+		}
+	}
+
+	private boolean hasSql() {
+		return isNotEmpty(getSql(), true);
+	}
+
+	private boolean hasDb() {
+		return isNotEmpty(getDatabaseName(), true);
+	}
+
+	/**
+	 * Depends on options: DB, BATCH, SQL
+	 *
+	 * @return mode | or null if options are not yet initialized or combination of options is
+	 * invalid
+	 */
+	public MODE getMode() {
+		if (hasDb() && !batch && hasSql()) {
+			return MODE.QUERY_NOW;
+		} else if (!hasDb() && batch && hasSql()) {
+			return MODE.PREPARE_BATCH;
+		} else if (hasDb() && batch && !hasSql()) {
+			return MODE.EXECUTE_BATCH;
+		} else {
+			return showInfo.isEmpty() ? null : MODE.JUST_SHOW_INFO;
+		}
+	}
+
+	public String getSql() {
+		return sql;
+	}
+
+	public void setSql(String sql) {
+		this.sql = sql;
+	}
+
+	public String getDatabaseName() {
+		return databaseName;
+	}
+
+	public void setDatabaseName(String databaseName) {
+		this.databaseName = databaseName;
+	}
+
+	public void setBatch(boolean batch) {
+		this.batch = batch;
+	}
+
+	public Collection<NamedParameter> getNamedParameters() {
+		return namedParameters;
+	}
+
+	public List<Parameter> getNumberedParameters() {
+		return numberedParameters;
+	}
+
+	public void addNumberedParameter(Parameter p) {
+		numberedParameters.add(p);
+	}
+
+	public void addNamedParameter(NamedParameter p) {
+		namedParameters.add(p);
+	}
+
+	public Properties getDatabaseProperties() {
+		return databaseProperties;
+	}
+
+	public Properties getFormatterProperties() {
+		return formatterProperties;
+	}
+
+	public void addDatabaseProperty(Property p) {
+		databaseProperties.add(p);
+	}
+
+	public void addFormatterProperty(Property p) {
+		formatterProperties.add(p);
+	}
+
+	/**
+	 * @return regular expression describing the name prefix
+	 */
+	public String getNamePrefix() {
+		return namePrefix;
+	}
+
+	/**
+	 * @param namePrefix
+	 * @see #getNamePrefix()
+	 */
+	public void setNamePrefix(String namePrefix) {
+		this.namePrefix = namePrefix;
+	}
+
+	/**
+	 * @return regular expression describing the name prefix
+	 */
+	public String getNameSuffix() {
+		return nameSuffix;
+	}
+
+	/**
+	 * @param nameSuffix
+	 * @see #getNameSuffix()
+	 */
+	public void setNameSuffix(String nameSuffix) {
+		this.nameSuffix = nameSuffix;
+	}
+
+	public String getFormatterName() {
+		return formatterName;
+	}
+
+	public void setFormatterName(String formatterName) {
+		this.formatterName = formatterName;
+	}
+
+	public void addShowInfo(InfoType info) {
+		showInfo.add(info);
+	}
+
+	public EnumSet<InfoType> getShowInfo() {
+		return showInfo;
+	}
+
+	public Set<String> getDatabaseNamesToTest() {
+		return databaseNamesToTest;
+	}
+
+	public void addDatabaseNameToTest(String name) {
+		databaseNamesToTest.add(name);
+	}
+
+	public Set<String> getDatabaseNamesToListProperties() {
+		return databaseNamesToListProperties;
+	}
+
+	public void addDatabaseNameToListProperties(String name) {
+		databaseNamesToListProperties.add(name);
+	}
+
+	public Set<String> getFormatterNamesToListProperties() {
+		return formatterNamesToListProperties;
+	}
+
+	public void addFormatterNameToListProperties(String name) {
+		formatterNamesToListProperties.add(name);
+	}
+
+	public SQLCommand getSQLCommand() {
+		if (namedParameters.isEmpty()) {
+			return new SQLCommandNumbered(sql, numberedParameters);
+		} else {
+			return new SQLCommandNamed(sql, namedParameters, namePrefix, nameSuffix);
+		}
+	}
+
+	public OutputStream getOutputStream() {
+		return System.out;
+	}
+
+	public InputStream getInputStream() {
+		return System.in;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIParser.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,216 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import static info.globalcode.sql.dk.Functions.readString;
+import info.globalcode.sql.dk.InfoLister.InfoType;
+import info.globalcode.sql.dk.configuration.Property;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Converts command line arguments from String array to object.
+ * Checks basic constraints (if only supported options are used and if they have correct number of
+ * parameters)
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class CLIParser {
+
+	public static final String TYPE_NAME_SEPARATOR = ":";
+
+	public CLIOptions parseOptions(String[] args, InputStream in) throws CLIParserException {
+		CLIOptions options = new CLIOptions();
+
+		List<SQLType> numberedTypes = new ArrayList<>();
+		Map<String, SQLType> namedTypes = new HashMap<>();
+
+		for (int i = 0; i < args.length; i++) {
+			String arg = args[i];
+			switch (arg) {
+				case Tokens.TYPES:
+					String typesString = fetchNext(args, ++i);
+
+					for (String oneType : typesString.split(",")) {
+						int sepatratorIndex = oneType.indexOf(TYPE_NAME_SEPARATOR);
+						if (sepatratorIndex == -1) {
+							numberedTypes.add(getType(oneType.toUpperCase()));
+						} else {
+							String namePart = oneType.substring(0, sepatratorIndex).trim();
+							String typePart = oneType.substring(sepatratorIndex + TYPE_NAME_SEPARATOR.length(), oneType.length());
+							namedTypes.put(namePart, getType(typePart.toUpperCase()));
+						}
+					}
+					break;
+				case Tokens.NAME_PREFIX:
+					options.setNamePrefix(fetchNext(args, ++i));
+					break;
+				case Tokens.NAME_SUFFIX:
+					options.setNameSuffix(fetchNext(args, ++i));
+					break;
+				case Tokens.DB:
+					options.setDatabaseName(fetchNext(args, ++i));
+					break;
+				case Tokens.SQL:
+					options.setSql(fetchNext(args, ++i));
+					break;
+				case Tokens.SQL_IN:
+					try {
+						options.setSql(readString(in));
+					} catch (IOException e) {
+						throw new CLIParserException("Unable to read SQL from the input stream", e);
+					}
+					break;
+				case Tokens.BATCH:
+					options.setBatch(true);
+					break;
+				case Tokens.DATA: // --data is the last option
+					for (i++; i < args.length; i++) {
+						arg = args[i];
+						Parameter parameter;
+						if (numberedTypes.isEmpty()) {
+							parameter = new Parameter(arg, null);
+						} else {
+							int paramIndex = options.getNumberedParameters().size();
+							SQLType paramType;
+							try {
+								paramType = numberedTypes.get(paramIndex);
+							} catch (IndexOutOfBoundsException e) {
+								throw new CLIParserException("Missing type for parameter #" + paramIndex, e);
+							} catch (NullPointerException e) {
+								throw new CLIParserException("Invalid type definition for parameter #" + paramIndex, e);
+							}
+							parameter = new Parameter(arg, paramType);
+						}
+						options.addNumberedParameter(parameter);
+					}
+					break;
+				case Tokens.DATA_NAMED:
+					for (i++; i < args.length; i++) {
+						String paramName = args[i];
+						String paramValue = fetchNext(args, ++i);
+						options.addNamedParameter(new NamedParameter(paramName, paramValue, namedTypes.get(paramName)));
+					}
+					break;
+				case Tokens.FORMATTER:
+					options.setFormatterName(fetchNext(args, ++i));
+					break;
+				case Tokens.DB_PROPERTY:
+					options.addDatabaseProperty(new Property(fetchNext(args, ++i), fetchNext(args, ++i)));
+					break;
+				case Tokens.FORMATTER_PROPERTY:
+					options.addFormatterProperty(new Property(fetchNext(args, ++i), fetchNext(args, ++i)));
+					break;
+				case Tokens.INFO_HELP:
+					options.addShowInfo(InfoType.HELP);
+					break;
+				case Tokens.INFO_FORMATTERS:
+					options.addShowInfo(InfoType.FORMATTERS);
+					break;
+				case Tokens.INFO_FORMATTER_PROPERTIES:
+					options.addShowInfo(InfoType.FORMATTER_PROPERTIES);
+					options.addFormatterNameToListProperties(fetchNext(args, ++i));
+					break;
+				case Tokens.INFO_LICENSE:
+					options.addShowInfo(InfoType.LICENSE);
+					break;
+				case Tokens.INFO_JAVA_PROPERTIES:
+					options.addShowInfo(InfoType.JAVA_PROPERTIES);
+					break;
+				case Tokens.INFO_ENVIRONMENT_VARIABLES:
+					options.addShowInfo(InfoType.ENVIRONMENT_VARIABLES);
+					break;
+				case Tokens.INFO_TYPES:
+					options.addShowInfo(InfoType.TYPES);
+					break;
+				case Tokens.INFO_VERSION:
+					options.addShowInfo(InfoType.VERSION);
+					break;
+				case Tokens.INFO_JDBC_DRIVERS:
+					options.addShowInfo(InfoType.JDBC_DRIVERS);
+					break;
+				case Tokens.INFO_JDBC_PROPERTIES:
+					options.addShowInfo(InfoType.JDBC_PROPERTIES);
+					options.addDatabaseNameToListProperties(fetchNext(args, ++i));
+					break;
+				case Tokens.INFO_DATABASES:
+					options.addShowInfo(InfoType.DATABASES);
+					break;
+				case Tokens.INFO_CONNECTION:
+					options.addShowInfo(InfoType.CONNECTION);
+					options.addDatabaseNameToTest(fetchNext(args, ++i));
+					break;
+				default:
+					throw new CLIParserException("Unknown option: " + arg);
+			}
+		}
+		return options;
+	}
+
+	private String fetchNext(String[] args, int index) throws CLIParserException {
+		if (index < args.length) {
+			return args[index];
+		} else {
+			throw new CLIParserException("Expecting value for option: " + args[index - 1]);
+		}
+	}
+
+	public static class Tokens {
+
+		// bash-completion:options:
+		public static final String DB = "--db"; // bash-completion:option // help: database name
+		public static final String DB_PROPERTY = "--db-property"; // bash-completion:option // help: name and value
+		public static final String SQL = "--sql"; // bash-completion:option // help: SQL query/command
+		public static final String SQL_IN = "--sql-in"; // bash-completion:option // help: SQL query/command
+		public static final String BATCH = "--batch"; // bash-completion:option // help: batch mode (no argument)
+		public static final String DATA = "--data"; // bash-completion:option // help: list of ordinal parameters
+		public static final String DATA_NAMED = "--data-named"; // bash-completion:option // help: list of named parameters
+		public static final String NAME_PREFIX = "--name-prefix"; // bash-completion:option // help: parameter name prefix – regular expression
+		public static final String NAME_SUFFIX = "--name-suffix"; // bash-completion:option // help: parameter name suffix – regular expression
+		public static final String TYPES = "--types"; // bash-completion:option // help: comma separated list of parameter types
+		public static final String FORMATTER = "--formatter"; // bash-completion:option // help: name of the output formatter
+		public static final String FORMATTER_PROPERTY = "--formatter-property"; // bash-completion:option // help: name and value
+		public static final String INFO_HELP = "--help"; // bash-completion:option // help: print this help
+		public static final String INFO_VERSION = "--version"; // bash-completion:option // help: print version info
+		public static final String INFO_LICENSE = "--license"; // bash-completion:option // help: print license
+		public static final String INFO_JAVA_PROPERTIES = "--list-java-properties"; // bash-completion:option // help: list of Java system properties
+		public static final String INFO_ENVIRONMENT_VARIABLES = "--list-environment-variables"; // bash-completion:option // help: list of environment variables
+		public static final String INFO_FORMATTERS = "--list-formatters"; // bash-completion:option // help: print list of available formatters
+		public static final String INFO_FORMATTER_PROPERTIES = "--list-formatter-properties"; // bash-completion:option // help: print list of available properties for given formatter
+		public static final String INFO_TYPES = "--list-types"; // bash-completion:option // help: print list of available data types
+		public static final String INFO_JDBC_DRIVERS = "--list-jdbc-drivers"; // bash-completion:option // help: list of available JDBC drivers
+		public static final String INFO_JDBC_PROPERTIES = "--list-jdbc-properties"; // bash-completion:option // help: list of available JDBC properties for given database
+		public static final String INFO_DATABASES = "--list-databases"; // bash-completion:option // help: print list of configured databases
+		public static final String INFO_CONNECTION = "--test-connection"; // bash-completion:option // help: test connection to particular database
+
+		private Tokens() {
+		}
+	}
+
+	private SQLType getType(String typeString) throws CLIParserException {
+		try {
+			return SQLType.valueOf(typeString.trim());
+		} catch (IllegalArgumentException e) {
+			throw new CLIParserException("Unsupported type: " + typeString, e);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIParserException.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,40 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class CLIParserException extends DKException {
+
+	public CLIParserException() {
+	}
+
+	public CLIParserException(String message) {
+		super(message);
+	}
+
+	public CLIParserException(Throwable cause) {
+		super(cause);
+	}
+
+	public CLIParserException(String message, Throwable cause) {
+		super(message, cause);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIStarter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,274 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import info.globalcode.sql.dk.configuration.ConfigurationProvider;
+import info.globalcode.sql.dk.CLIOptions.MODE;
+import info.globalcode.sql.dk.batch.Batch;
+import info.globalcode.sql.dk.batch.BatchDecoder;
+import info.globalcode.sql.dk.batch.BatchException;
+import info.globalcode.sql.dk.batch.BatchEncoder;
+import info.globalcode.sql.dk.configuration.Configuration;
+import info.globalcode.sql.dk.configuration.ConfigurationException;
+import info.globalcode.sql.dk.configuration.DatabaseDefinition;
+import info.globalcode.sql.dk.configuration.FormatterDefinition;
+import info.globalcode.sql.dk.configuration.Loader;
+import info.globalcode.sql.dk.configuration.NameIdentified;
+import info.globalcode.sql.dk.configuration.PropertyDeclaration;
+import info.globalcode.sql.dk.formatting.Formatter;
+import info.globalcode.sql.dk.formatting.FormatterContext;
+import info.globalcode.sql.dk.formatting.FormatterException;
+import info.globalcode.sql.dk.jmx.ConnectionManagement;
+import info.globalcode.sql.dk.jmx.ManagementUtils;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+/**
+ * Entry point of the command line interface of SQL-DK.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class CLIStarter implements ConfigurationProvider {
+
+	// help:exit-codes
+	public static final int EXIT_SUCCESS = 0; // doc:success
+	public static final int EXIT_UNEXPECTED_ERROR = 1; // doc:unexpected error (probably bug)
+	// 2 is reserved: http://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF
+	public static final int EXIT_SQL_ERROR = 3; // doc:SQL error
+	public static final int EXIT_CLI_PARSE_ERROR = 4; // doc:CLI options parse error
+	public static final int EXIT_CLI_VALIDATE_ERROR = 5; // doc:CLI options validation error
+	public static final int EXIT_CONFIGURATION_ERROR = 6; // doc:configuration error
+	public static final int EXIT_FORMATTING_ERROR = 7; // doc:formatting error
+	public static final int EXIT_BATCH_ERROR = 8; // doc:batch error
+	private static final Logger log = Logger.getLogger(CLIStarter.class.getName());
+	private final CLIOptions options;
+	private final Loader configurationLoader = new Loader();
+	private Configuration configuration;
+
+	public static void main(String[] args) {
+		log.log(Level.FINE, "Starting " + Constants.PROGRAM_NAME);
+		int exitCode;
+
+		if (args.length == 0) {
+			args = new String[]{CLIParser.Tokens.INFO_HELP};
+		}
+
+		try {
+			CLIParser parser = new CLIParser();
+			CLIOptions options = parser.parseOptions(args, System.in);
+			options.validate();
+			CLIStarter starter = new CLIStarter(options);
+			starter.installDefaultConfiguration();
+			starter.process();
+			log.log(Level.FINE, "All done");
+			exitCode = EXIT_SUCCESS;
+		} catch (CLIParserException e) {
+			log.log(Level.SEVERE, "Unable to parse CLI options", e);
+			exitCode = EXIT_CLI_PARSE_ERROR;
+		} catch (InvalidOptionsException e) {
+			log.log(Level.SEVERE, "Invalid CLI options", e);
+			for (InvalidOptionsException.OptionProblem p : e.getProblems()) {
+				LogRecord r = new LogRecord(Level.SEVERE, "Option problem: {0}");
+				r.setThrown(p.getException());
+				r.setParameters(new Object[]{p.getDescription()});
+				log.log(r);
+			}
+			exitCode = EXIT_CLI_VALIDATE_ERROR;
+		} catch (ConfigurationException e) {
+			log.log(Level.SEVERE, "Configuration problem", e);
+			exitCode = EXIT_CONFIGURATION_ERROR;
+		} catch (SQLException e) {
+			log.log(Level.SEVERE, "SQL problem", e);
+			exitCode = EXIT_SQL_ERROR;
+		} catch (FormatterException e) {
+			log.log(Level.SEVERE, "Formatting problem", e);
+			exitCode = EXIT_FORMATTING_ERROR;
+		} catch (BatchException e) {
+			log.log(Level.SEVERE, "Batch problem", e);
+			exitCode = EXIT_BATCH_ERROR;
+		}
+
+		System.exit(exitCode);
+	}
+
+	public CLIStarter(CLIOptions options) {
+		this.options = options;
+	}
+
+	private void process() throws ConfigurationException, SQLException, FormatterException, BatchException {
+		MODE mode = options.getMode();
+
+		/** Show info */
+		if (!options.getShowInfo().isEmpty()) {
+			PrintStream infoOut = mode == MODE.JUST_SHOW_INFO ? System.out : System.err;
+			InfoLister infoLister = new InfoLister(infoOut, this, options);
+			infoLister.showInfo();
+		}
+
+		switch (mode) {
+			case QUERY_NOW:
+				processQueryNow();
+				break;
+			case PREPARE_BATCH:
+				processPrepareBatch();
+				break;
+			case EXECUTE_BATCH:
+				processExecuteBatch();
+				break;
+			case JUST_SHOW_INFO:
+				// already done above
+				break;
+			default:
+				log.log(Level.SEVERE, "Unsupported mode: {0}", mode);
+				break;
+		}
+
+		generateBashCompletion();
+	}
+
+	private void processQueryNow() throws ConfigurationException, SQLException, FormatterException {
+		DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName());
+		FormatterDefinition fd = configuration.getFormatter(options.getFormatterName());
+		ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName());
+
+		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) {
+			log.log(Level.FINE, "Database connected");
+			try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) {
+				c.executeQuery(options.getSQLCommand(), f);
+			}
+		}
+	}
+
+	private void processPrepareBatch() throws BatchException {
+		BatchEncoder enc = new BatchEncoder();
+		int length = enc.encode(options.getSQLCommand(), options.getOutputStream());
+		log.log(Level.FINE, "Prepared batch size: {0} bytes", length);
+	}
+
+	private void processExecuteBatch() throws ConfigurationException, SQLException, FormatterException, BatchException {
+		BatchDecoder dec = new BatchDecoder();
+		Batch b = dec.decode(options.getInputStream());
+
+		DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName());
+		FormatterDefinition fd = configuration.getFormatter(options.getFormatterName());
+		ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName());
+
+		try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) {
+			log.log(Level.FINE, "Database connected");
+			try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) {
+				c.executeBatch(b, f);
+			}
+		}
+	}
+
+	@Override
+	public Configuration getConfiguration() throws ConfigurationException {
+		if (configuration == null) {
+			configuration = configurationLoader.loadConfiguration();
+		}
+		return configuration;
+	}
+
+	private void installDefaultConfiguration() throws ConfigurationException {
+		Constants.DIR.mkdir();
+
+		if (Constants.CONFIG_FILE.exists()) {
+			log.log(Level.FINER, "Config file already exists: {0}", Constants.CONFIG_FILE);
+		} else {
+			try {
+				Functions.installResource(Constants.EXAMPLE_CONFIG_FILE, Constants.CONFIG_FILE);
+				log.log(Level.FINE, "Installing default config file: {0}", Constants.CONFIG_FILE);
+			} catch (IOException e) {
+				throw new ConfigurationException("Unable to write example configuration to " + Constants.CONFIG_FILE, e);
+			}
+		}
+	}
+
+	private void generateBashCompletion() {
+		if (configuration == null) {
+			log.log(Level.FINER, "Not writing Bash completion helper files. In order to generate these files please run some command which requires configuration.");
+		} else {
+			try {
+				File dir = new File(Constants.DIR, "bash-completion");
+				dir.mkdir();
+				writeBashCompletionHelperFile(configuration.getDatabases(), new File(dir, "databases"));
+				writeBashCompletionHelperFile(configuration.getAllFormatters(), new File(dir, "formatters"));
+				writeBashCompletionHelperFileForFormatterProperties(new File(dir, "formatter-properties"));
+			} catch (Exception e) {
+				log.log(Level.WARNING, "Unable to generate Bash completion helper files", e);
+			}
+		}
+	}
+
+	private void writeBashCompletionHelperFile(Collection<? extends NameIdentified> items, File target) throws FileNotFoundException {
+		if (Constants.CONFIG_FILE.lastModified() > target.lastModified()) {
+			try (PrintWriter fw = new PrintWriter(target)) {
+				for (NameIdentified dd : items) {
+					fw.println(dd.getName());
+				}
+				fw.close();
+				log.log(Level.FINE, "Bash completion helper file was written: {0}", target);
+			}
+		} else {
+			log.log(Level.FINER, "Not writing Bash completion helper file: {0} because configuration {1} has not been changed", new Object[]{target, Constants.CONFIG_FILE});
+		}
+	}
+
+	private void writeBashCompletionHelperFileForFormatterProperties(File formattersDir) throws ClassNotFoundException, FileNotFoundException {
+		if (Constants.CONFIG_FILE.lastModified() > formattersDir.lastModified()) {
+			// TODO: delete old directory
+			formattersDir.mkdir();
+			for (FormatterDefinition fd : configuration.getAllFormatters()) {
+				File formatterDir = new File(formattersDir, fd.getName());
+				formatterDir.mkdir();
+
+				Class<Formatter> formatterClass = (Class<Formatter>) Class.forName(fd.getClassName());
+				List<Class<? extends Formatter>> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class);
+				Collections.reverse(hierarchy);
+				for (Class<? extends Formatter> c : hierarchy) {
+					for (PropertyDeclaration p : Functions.getPropertyDeclarations(c)) {
+						File propertyDir = new File(formatterDir, p.name());
+						propertyDir.mkdir();
+						File choicesFile = new File(propertyDir, "choices");
+						try (PrintWriter fw = new PrintWriter(choicesFile)) {
+							// TODO: refactor, move
+							if (p.type() == Boolean.class) {
+								fw.println("true");
+								fw.println("false");
+							}
+						}
+					}
+				}
+			}
+			log.log(Level.FINE, "Bash completion helper files was written in: {0}", formattersDir);
+		} else {
+			log.log(Level.FINER, "Not writing Bash completion helper directory: {0} because configuration {1} has not been changed", new Object[]{formattersDir, Constants.CONFIG_FILE});
+		}
+
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/ColorfulPrintWriter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,358 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.EnumSet;
+
+/**
+ * PrintWriter with convenience methods for printing color and formatted text.
+ *
+ * Uses ANSI Escape Sequences.
+ * See: http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class ColorfulPrintWriter extends PrintWriter {
+
+	public enum TerminalColor {
+
+		Black(30, 40),
+		Red(31, 41),
+		Green(32, 42),
+		Yellow(33, 43),
+		Blue(34, 44),
+		Magenta(35, 45),
+		Cyan(36, 46),
+		White(37, 47);
+		private final int foregroundCode;
+		private final int backgroundCode;
+
+		private TerminalColor(int foregroundCode, int backgroundCode) {
+			this.foregroundCode = foregroundCode;
+			this.backgroundCode = backgroundCode;
+		}
+
+		public int getForegroundCode() {
+			return foregroundCode;
+		}
+
+		public int getBackgroundCode() {
+			return backgroundCode;
+		}
+	}
+
+	public enum TerminalStyle {
+
+		Reset(0),
+		Bright(1),
+		Dim(2),
+		Underscore(4),
+		Blink(5),
+		Reverse(7),
+		Hidden(8);
+		private int code;
+
+		private TerminalStyle(int code) {
+			this.code = code;
+		}
+
+		public int getCode() {
+			return code;
+		}
+	}
+	private final boolean COLOR_ENABLED;
+	private boolean colorful = true;
+
+	public void setStyle(TerminalStyle style) {
+		setStyle(EnumSet.of(style));
+	}
+
+	public void setStyle(EnumSet<TerminalStyle> styles) {
+		printCodes(getStyleCodes(styles));
+	}
+
+	private static int[] getStyleCodes(EnumSet<TerminalStyle> styles) {
+		int[] array = new int[styles.size()];
+		int i = 0;
+		for (TerminalStyle s : styles) {
+			array[i++] = s.getCode();
+		}
+		return array;
+	}
+
+	/**
+	 * Print (usually audible) bell code (\007, \a, ^G)
+	 */
+	public void bell() {
+		print("\007");
+	}
+
+	/**
+	 * Eat the last character
+	 */
+	public void backspace() {
+		print("\b");
+	}
+
+	/**
+	 * Eat n last characters
+	 *
+	 * @param count n
+	 */
+	public void backspace(int count) {
+		for (int i = 0; i < count; i++) {
+			backspace();
+		}
+	}
+
+	/**
+	 * With 100 ms delay and all colors.
+	 *
+	 * @see #printRainbow(java.lang.String, int,
+	 * info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor[])
+	 */
+	public void printRainbow(String string) {
+		printRainbow(string, 100);
+	}
+
+	/**
+	 * With all colors.
+	 *
+	 * @see #printRainbow(java.lang.String, int,
+	 * info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor[])
+	 */
+	public void printRainbow(String string, int delay) {
+		printRainbow(string, delay, TerminalColor.values());
+	}
+
+	/**
+	 * Prints rainbow text – (re)writes same text subsequently in given colors and then in default
+	 * color.
+	 *
+	 * @param string text to be printed, should not contain \n new line (then rainbow does not work
+	 * – use println() after printRainbow() instead)
+	 * @param delay delay between rewrites
+	 * @param colors list of colors to be used
+	 */
+	public void printRainbow(String string, int delay, TerminalColor... colors) {
+		for (TerminalColor c : colors) {
+			print(c, string);
+			try {
+				Thread.sleep(delay);
+			} catch (InterruptedException e) {
+				// no time to sleep
+				break;
+			}
+			backspace(string.length());
+			flush();
+		}
+		print(string);
+	}
+
+	public void setForegroundColor(TerminalColor color) {
+		printCodes(color.getForegroundCode());
+	}
+
+	public void setBackgroundColor(TerminalColor color) {
+		printCodes(color.getBackgroundCode());
+	}
+
+	public void print(TerminalColor foregroundColor, String string) {
+		setForegroundColor(foregroundColor);
+		print(string);
+		resetAll();
+	}
+
+	public void println(TerminalColor foregroundColor, String string) {
+		print(foregroundColor, string);
+		println();
+	}
+
+	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, String string) {
+		setForegroundColor(foregroundColor);
+		setBackgroundColor(backgroundColor);
+		print(string);
+		resetAll();
+	}
+
+	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, String string) {
+		print(foregroundColor, backgroundColor, string);
+		println();
+	}
+
+	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, EnumSet<TerminalStyle> styles, String string) {
+		setForegroundColor(foregroundColor);
+		setBackgroundColor(backgroundColor);
+		setStyle(styles);
+		print(string);
+		resetAll();
+	}
+
+	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, EnumSet<TerminalStyle> styles, String string) {
+		print(foregroundColor, backgroundColor, styles, string);
+		println();
+	}
+
+	public void print(TerminalColor foregroundColor, TerminalColor backgroundColor, TerminalStyle style, String string) {
+		print(foregroundColor, backgroundColor, EnumSet.of(style), string);
+	}
+
+	public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, TerminalStyle style, String string) {
+		print(foregroundColor, backgroundColor, style, string);
+		println();
+	}
+
+	public void print(TerminalColor foregroundColor, EnumSet<TerminalStyle> styles, String string) {
+		setForegroundColor(foregroundColor);
+		setStyle(styles);
+		print(string);
+		resetAll();
+	}
+
+	public void println(TerminalColor foregroundColor, EnumSet<TerminalStyle> styles, String string) {
+		print(foregroundColor, styles, string);
+		println();
+	}
+
+	public void print(TerminalColor foregroundColor, TerminalStyle style, String string) {
+		print(foregroundColor, EnumSet.of(style), string);
+	}
+
+	public void println(TerminalColor foregroundColor, TerminalStyle style, String string) {
+		print(foregroundColor, style, string);
+		println();
+	}
+
+	public void print(EnumSet<TerminalStyle> styles, String string) {
+		setStyle(styles);
+		print(string);
+		resetAll();
+	}
+
+	public void println(EnumSet<TerminalStyle> styles, String string) {
+		print(styles, string);
+		println();
+	}
+
+	public void print(TerminalStyle style, String string) {
+		print(EnumSet.of(style), string);
+	}
+
+	public void println(TerminalStyle style, String string) {
+		print(style, string);
+		println();
+	}
+
+	public void resetAll() {
+		printCodes(TerminalStyle.Reset.code);
+	}
+
+	private void printCodes(int... codes) {
+		if (COLOR_ENABLED && colorful) {
+			print("\033[");
+			for (int i = 0; i < codes.length; i++) {
+				print(codes[i]);
+				if (i < codes.length - 1 && codes.length > 1) {
+					print(";");
+				}
+			}
+			print("m");
+		}
+	}
+
+	/**
+	 * Colors can be switched on/off during usage of this writer.
+	 *
+	 * @return whether colors are currently turned on
+	 * @see #isColorEnabled()
+	 */
+	public boolean isColorful() {
+		return colorful;
+	}
+
+	/**
+	 * Collors might be definitively disabled in constructor. If not, they can be turned on/off
+	 * during usage of this writer by {@linkplain #setColorful(boolean)}
+	 *
+	 * @return whether colors are allowed for this instance of this class
+	 * @see #isColorful()
+	 */
+	public boolean isColorEnabled() {
+		return COLOR_ENABLED;
+	}
+
+	/**
+	 * @see #isColorful()
+	 * @see #isColorEnabled()
+	 */
+	public void setColorful(boolean colorful) {
+		this.colorful = colorful;
+	}
+
+	public ColorfulPrintWriter(File file) throws FileNotFoundException {
+		super(file);
+		COLOR_ENABLED = true;
+	}
+
+	public ColorfulPrintWriter(OutputStream out) {
+		super(out);
+		COLOR_ENABLED = true;
+	}
+
+	public ColorfulPrintWriter(String fileName) throws FileNotFoundException {
+		super(fileName);
+		COLOR_ENABLED = true;
+	}
+
+	public ColorfulPrintWriter(Writer out) {
+		super(out);
+		COLOR_ENABLED = true;
+	}
+
+	public ColorfulPrintWriter(File file, String csn) throws FileNotFoundException, UnsupportedEncodingException {
+		super(file, csn);
+		COLOR_ENABLED = true;
+	}
+
+	/**
+	 * @param colorEnabled colors might be definitively disabled by this option – this might be more
+	 * optimalizable than dynamic turning off colors by {@linkplain #setColorful(boolean)} which is
+	 * not definitive (colors can be turned on during live of this instance). This might be useful
+	 * if you need an instance of this class but don't need colors at all.
+	 */
+	public ColorfulPrintWriter(OutputStream out, boolean autoFlush, boolean colorEnabled) {
+		super(out, autoFlush);
+		COLOR_ENABLED = colorEnabled;
+	}
+
+	public ColorfulPrintWriter(String fileName, String csn) throws FileNotFoundException, UnsupportedEncodingException {
+		super(fileName, csn);
+		COLOR_ENABLED = true;
+	}
+
+	public ColorfulPrintWriter(Writer out, boolean autoFlush) {
+		super(out, autoFlush);
+		COLOR_ENABLED = true;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/Constants.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,44 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import java.io.File;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class Constants {
+
+	public static final String PROGRAM_NAME = "SQL-DK";
+	public static final String JAVA_PACKAGE = Constants.class.getPackage().getName();
+	public static final String WEBSITE = "https://sql-dk.globalcode.info/";
+	public static final String LICENSE_FILE = "info/globalcode/sql/dk/license.txt";
+	public static final String VERSION_FILE = "info/globalcode/sql/dk/version.txt";
+	public static final String HELP_FILE = "info/globalcode/sql/dk/help.txt";
+	private static final File HOME_DIR = new File(System.getProperty("user.home"));
+	/**
+	 * Directory where config and log files are stored.
+	 */
+	public static final File DIR = new File(HOME_DIR, ".sql-dk"); // bash-completion:dir
+	public static final File CONFIG_FILE = new File(DIR, "config.xml");
+	public static final String EXAMPLE_CONFIG_FILE = "info/globalcode/sql/dk/example-config.xml";
+
+	private Constants() {
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/DKException.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,41 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+/**
+ * TODO: GEC
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class DKException extends Exception {
+
+	public DKException() {
+	}
+
+	public DKException(String message) {
+		super(message);
+	}
+
+	public DKException(Throwable cause) {
+		super(cause);
+	}
+
+	public DKException(String message, Throwable cause) {
+		super(message, cause);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/DatabaseConnection.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,192 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import static info.globalcode.sql.dk.jmx.ConnectionManagement.incrementCounter;
+import static info.globalcode.sql.dk.jmx.ConnectionManagement.resetCounter;
+import info.globalcode.sql.dk.batch.Batch;
+import info.globalcode.sql.dk.batch.BatchException;
+import info.globalcode.sql.dk.configuration.DatabaseDefinition;
+import info.globalcode.sql.dk.configuration.Loader;
+import info.globalcode.sql.dk.configuration.Properties;
+import info.globalcode.sql.dk.formatting.ColumnsHeader;
+import info.globalcode.sql.dk.formatting.Formatter;
+import info.globalcode.sql.dk.jmx.ConnectionManagement;
+import info.globalcode.sql.dk.jmx.ConnectionManagement.COUNTER;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Represents connected database. Is derived from {@linkplain DatabaseDefinition}.
+ * Wraps {@linkplain Connection}.
+ *
+ * Is responsible for executing {@linkplain SQLCommand} and passing results to the
+ * {@linkplain Formatter}.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class DatabaseConnection implements AutoCloseable {
+
+	private static final Logger log = Logger.getLogger(DatabaseConnection.class.getName());
+	public static final String JDBC_PROPERTY_USER = "user";
+	public static final String JDBC_PROPERTY_PASSWORD = "password";
+	private final DatabaseDefinition databaseDefinition;
+	private final Connection connection;
+	private final Properties properties;
+	/**
+	 * Could be null = JMX is disabled → must check, see functions in
+	 * {@linkplain ConnectionManagement}
+	 */
+	private final ConnectionManagement connectionMBean;
+
+	/**
+	 *
+	 * @param databaseDefinition DB url, name, password etc.
+	 * @param properties additional properties from CLI
+	 * @param connectionMBean JMX management bean | null = disabled JMX reporting
+	 * @throws SQLException
+	 */
+	public DatabaseConnection(DatabaseDefinition databaseDefinition, Properties properties, ConnectionManagement connectionMBean) throws SQLException {
+		this.databaseDefinition = databaseDefinition;
+		this.properties = properties;
+		this.connectionMBean = connectionMBean;
+		this.connection = Loader.jdbcConnect(databaseDefinition, properties);
+	}
+
+	public void executeQuery(SQLCommand sqlCommand, Formatter formatter) throws SQLException {
+		formatter.writeStartBatch();
+		formatter.writeStartDatabase(databaseDefinition);
+		formatter.writeStartStatement();
+		formatter.writeQuery(sqlCommand.getQuery());
+		formatter.writeParameters(sqlCommand.getParameters());
+		processCommand(sqlCommand, formatter);
+		formatter.writeEndStatement();
+		formatter.writeEndDatabase();
+		formatter.writeEndBatch();
+	}
+
+	public void executeBatch(Batch batch, Formatter formatter) throws SQLException, BatchException {
+		formatter.writeStartBatch();
+		formatter.writeStartDatabase(databaseDefinition);
+		while (batch.hasNext()) {
+			SQLCommand sqlCommand = batch.next();
+			formatter.writeStartStatement();
+			formatter.writeQuery(sqlCommand.getQuery());
+			formatter.writeParameters(sqlCommand.getParameters());
+			processCommand(sqlCommand, formatter);
+			formatter.writeEndStatement();
+		}
+		formatter.writeEndDatabase();
+		formatter.writeEndBatch();
+	}
+
+	private void processCommand(SQLCommand sqlCommand, Formatter formatter) throws SQLException {
+		incrementCounter(connectionMBean, COUNTER.COMMAND);
+		resetCounter(connectionMBean, COUNTER.RECORD_CURRENT);
+
+		try (PreparedStatement ps = sqlCommand.prepareStatement(connection)) {
+			log.log(Level.FINE, "Statement prepared");
+			sqlCommand.parametrize(ps);
+
+			boolean isRS = ps.execute();
+			log.log(Level.FINE, "Statement executed");
+			if (isRS) {
+				try (ResultSet rs = ps.getResultSet()) {
+					processResultSet(rs, formatter);
+				}
+			} else {
+				processUpdateResult(ps, formatter);
+			}
+			logWarnings(ps);
+
+			while (ps.getMoreResults() || ps.getUpdateCount() > -1) {
+				ResultSet rs = ps.getResultSet();
+				if (rs == null) {
+					processUpdateResult(ps, formatter);
+				} else {
+					processResultSet(rs, formatter);
+					rs.close();
+				}
+				logWarnings(ps);
+			}
+		}
+	}
+
+	private void processUpdateResult(PreparedStatement ps, Formatter formatter) throws SQLException {
+		formatter.writeUpdatesResult(ps.getUpdateCount());
+	}
+
+	private void processResultSet(ResultSet rs, Formatter formatter) throws SQLException {
+		formatter.writeStartResultSet(new ColumnsHeader(rs.getMetaData()));
+
+		int columnCount = rs.getMetaData().getColumnCount();
+
+		while (rs.next()) {
+			incrementCounter(connectionMBean, COUNTER.RECORD_CURRENT);
+			incrementCounter(connectionMBean, COUNTER.RECORD_TOTAL);
+
+			formatter.writeStartRow();
+
+			for (int i = 1; i <= columnCount; i++) {
+				formatter.writeColumnValue(rs.getObject(i));
+			}
+
+			formatter.writeEndRow();
+		}
+
+		formatter.writeEndResultSet();
+	}
+
+	private void logWarnings(PreparedStatement ps) throws SQLException {
+		SQLWarning w = ps.getWarnings();
+		while (w != null) {
+			log.log(Level.WARNING, "SQL: {0}", w.getLocalizedMessage());
+			w = w.getNextWarning();
+		}
+		ps.clearWarnings();
+	}
+
+	/**
+	 * Tests if this connection is live.
+	 *
+	 * @return true if test was successful
+	 * @throws SQLException if test fails
+	 */
+	public boolean test() throws SQLException {
+		connection.getAutoCommit();
+		return true;
+	}
+
+	public String getProductName() throws SQLException {
+		return connection.getMetaData().getDatabaseProductName();
+	}
+
+	public String getProductVersion() throws SQLException {
+		return connection.getMetaData().getDatabaseProductVersion();
+	}
+
+	@Override
+	public void close() throws SQLException {
+		connection.close();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/Functions.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,254 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import info.globalcode.sql.dk.configuration.NameIdentified;
+import info.globalcode.sql.dk.configuration.PropertyDeclaration;
+import info.globalcode.sql.dk.configuration.PropertyDeclarations;
+import info.globalcode.sql.dk.formatting.Formatter;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class Functions {
+
+	private static final String NBSP = " ";
+	private static final Pattern WHITESPACE_TO_REPLACE = Pattern.compile("\\n|\\r|\\t|" + NBSP);
+
+	private Functions() {
+	}
+
+	public static boolean equalz(Object a, Object b) {
+		return a == null ? b == null : a.equals(b);
+	}
+
+	/**
+	 *
+	 * @param text String to be examinated
+	 * @param trim whether text should be trimmed before examination
+	 * @return whether text is not empty and one or more characters long (after prospective trim)
+	 */
+	public static boolean isEmpty(String text, boolean trim) {
+		if (text == null) {
+			return true;
+		} else {
+			if (trim) {
+				text = text.trim();
+			}
+			return text.isEmpty();
+		}
+	}
+
+	/**
+	 * @see #isEmpty(java.lang.String, boolean)
+	 */
+	public static boolean isNotEmpty(String text, boolean trim) {
+		return !isEmpty(text, trim);
+	}
+
+	public boolean isEmpty(Collection c) {
+		return c == null || c.isEmpty();
+	}
+
+	public boolean isNotEmpty(Collection c) {
+		return !isEmpty(c);
+	}
+
+	public boolean isEmpty(Map m) {
+		return m == null || m.isEmpty();
+	}
+
+	public boolean isNotEmpty(Map m) {
+		return !isEmpty(m);
+	}
+
+	/**
+	 * @return empty collection if given one is null | or the original one
+	 */
+	public static <T> Collection<T> notNull(Collection<T> c) {
+		if (c == null) {
+			return Collections.emptyList();
+		} else {
+			return c;
+		}
+	}
+
+	public static <T extends NameIdentified> T findByName(Collection<T> collection, String name) {
+		for (T element : notNull(collection)) {
+			if (element != null && equalz(element.getName(), name)) {
+				return element;
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * Copy file from Java resources to file system.
+	 */
+	public static void installResource(String resourceName, File target) throws IOException {
+		try (BufferedReader reader = new BufferedReader(new InputStreamReader(Functions.class.getClassLoader().getResourceAsStream(resourceName)))) {
+			try (PrintWriter writer = new PrintWriter(target)) {
+				while (true) {
+					String line = reader.readLine();
+					if (line == null) {
+						break;
+					} else {
+						writer.println(line);
+					}
+				}
+			}
+		}
+	}
+
+	public static String rpad(String s, int n) {
+		if (n > 0) {
+			return String.format("%1$-" + n + "s", s);
+		} else {
+			return s;
+		}
+	}
+
+	public static String lpad(String s, int n) {
+		if (n > 0) {
+			return String.format("%1$" + n + "s", s);
+		} else {
+			return s;
+		}
+	}
+
+	public static String repeat(char ch, int count) {
+		char[] array = new char[count];
+		Arrays.fill(array, ch);
+		return new String(array);
+	}
+	private final static char[] HEX_ALPHABET = "0123456789abcdef".toCharArray();
+
+	public static String toHex(byte[] bytes) {
+		char[] hexChars = new char[bytes.length * 2];
+		for (int j = 0; j < bytes.length; j++) {
+			int v = bytes[j] & 0xFF;
+			hexChars[j * 2] = HEX_ALPHABET[v >>> 4];
+			hexChars[j * 2 + 1] = HEX_ALPHABET[v & 0x0F];
+		}
+		return new String(hexChars);
+	}
+
+	public static String readString(InputStream in) throws IOException {
+		try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
+			StringBuilder result = new StringBuilder();
+			for (String line = br.readLine(); line != null; line = br.readLine()) {
+				result.append(line);
+				result.append('\n');
+			}
+			return result.toString();
+		}
+	}
+
+	/**
+	 * @param <P> type of the last parent
+	 * @param <T> type of the examined class
+	 * @param type examined class
+	 * @param lastParent the last parent type to stop at
+	 * @return list of types starting with <code>type</code> and ending with <code>lastParent</code>
+	 */
+	public static <P, T extends P> List<Class<? extends P>> getClassHierarchy(Class<T> type, Class<P> lastParent) {
+		List<Class<? extends P>> hierarchy = new ArrayList<>();
+
+		for (Class current = type; current != null && lastParent.isAssignableFrom(current); current = current.getSuperclass()) {
+			hierarchy.add(current);
+		}
+
+		return hierarchy;
+	}
+
+	public static PropertyDeclaration[] getPropertyDeclarations(Class<? extends Formatter> formatterClass) {
+		PropertyDeclarations properties = formatterClass.getAnnotation(PropertyDeclarations.class);
+
+		if (properties == null) {
+			PropertyDeclaration p = formatterClass.getAnnotation(PropertyDeclaration.class);
+			return p == null ? new PropertyDeclaration[]{} : new PropertyDeclaration[]{p};
+		} else {
+			return properties.value();
+		}
+	}
+
+	/**
+	 * TODO: support background or styles and move to ColorfulPrintWriter
+	 *
+	 * @param out
+	 * @param valueString
+	 * @param basicColor
+	 * @param escapeColor
+	 */
+	public static void printValueWithWhitespaceReplaced(ColorfulPrintWriter out, String valueString, ColorfulPrintWriter.TerminalColor basicColor, ColorfulPrintWriter.TerminalColor escapeColor) {
+
+		Matcher m = WHITESPACE_TO_REPLACE.matcher(valueString);
+
+		int start = 0;
+
+		while (m.find(start)) {
+
+			printColorOrNot(out, basicColor, valueString.substring(start, m.start()));
+
+			switch (m.group()) {
+				case "\n":
+					out.print(escapeColor, "↲");
+					break;
+				case "\r":
+					out.print(escapeColor, "⏎");
+					break;
+				case "\t":
+					out.print(escapeColor, "↹");
+					break;
+				case NBSP:
+					out.print(escapeColor, "⎵");
+					break;
+				default:
+					throw new IllegalStateException("Unexpected whitespace token: „" + m.group() + "“");
+			}
+
+			start = m.end();
+		}
+
+		printColorOrNot(out, basicColor, valueString.substring(start, valueString.length()));
+	}
+
+	private static void printColorOrNot(ColorfulPrintWriter out, ColorfulPrintWriter.TerminalColor color, String text) {
+		if (color == null) {
+			out.print(text);
+		} else {
+			out.print(color, text);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/InfoLister.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,673 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import info.globalcode.sql.dk.configuration.CommandArgument;
+import info.globalcode.sql.dk.configuration.Configuration;
+import info.globalcode.sql.dk.configuration.ConfigurationException;
+import info.globalcode.sql.dk.configuration.ConfigurationProvider;
+import info.globalcode.sql.dk.configuration.DatabaseDefinition;
+import info.globalcode.sql.dk.configuration.FormatterDefinition;
+import info.globalcode.sql.dk.configuration.Properties;
+import info.globalcode.sql.dk.configuration.Property;
+import info.globalcode.sql.dk.configuration.PropertyDeclaration;
+import info.globalcode.sql.dk.configuration.TunnelDefinition;
+import info.globalcode.sql.dk.formatting.ColumnsHeader;
+import info.globalcode.sql.dk.formatting.CommonProperties;
+import info.globalcode.sql.dk.formatting.FakeSqlArray;
+import info.globalcode.sql.dk.formatting.Formatter;
+import info.globalcode.sql.dk.formatting.FormatterContext;
+import info.globalcode.sql.dk.formatting.FormatterException;
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.sql.Array;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.DriverPropertyInfo;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import javax.sql.rowset.RowSetMetaDataImpl;
+
+/**
+ * Displays info like help, version etc.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class InfoLister {
+
+	private static final Logger log = Logger.getLogger(InfoLister.class.getName());
+	/**
+	 * Fake database name for output formatting
+	 */
+	public static final String CONFIG_DB_NAME = "sqldk_configuration";
+	private final PrintStream out;
+	private final ConfigurationProvider configurationProvider;
+	private final CLIOptions options;
+	private Formatter formatter;
+
+	public InfoLister(PrintStream out, ConfigurationProvider configurationProvider, CLIOptions options) {
+		this.out = out;
+		this.configurationProvider = configurationProvider;
+		this.options = options;
+	}
+
+	public void showInfo() throws ConfigurationException, FormatterException {
+		EnumSet<InfoType> commands = options.getShowInfo();
+
+		boolean formattinNeeded = false;
+
+		for (InfoType infoType : commands) {
+			switch (infoType) {
+				case CONNECTION:
+				case JDBC_DRIVERS:
+				case JDBC_PROPERTIES:
+				case DATABASES:
+				case FORMATTERS:
+				case FORMATTER_PROPERTIES:
+				case TYPES:
+				case JAVA_PROPERTIES:
+				case ENVIRONMENT_VARIABLES:
+					formattinNeeded = true;
+					break;
+			}
+		}
+
+		if (formattinNeeded) {
+			try (Formatter f = getFormatter()) {
+				formatter = f;
+				formatter.writeStartBatch();
+				DatabaseDefinition dd = new DatabaseDefinition();
+				dd.setName(CONFIG_DB_NAME);
+				formatter.writeStartDatabase(dd);
+				showInfos(commands);
+				formatter.writeEndDatabase();
+				formatter.writeEndBatch();
+				formatter.close();
+			}
+		} else {
+			showInfos(commands);
+		}
+	}
+
+	private void showInfos(EnumSet<InfoType> commands) throws ConfigurationException, FormatterException {
+		for (InfoType infoType : commands) {
+			infoType.showInfo(this);
+		}
+	}
+
+	private void listJavaProperties() throws FormatterException, ConfigurationException {
+		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("value", SQLType.VARCHAR));
+		List<Object[]> data = new ArrayList<>();
+		for (Entry<Object, Object> e : System.getProperties().entrySet()) {
+			data.add(new Object[]{e.getKey(), e.getValue()});
+		}
+		printTable(formatter, header, "-- Java system properties", null, data, 0);
+	}
+
+	private void listEnvironmentVariables() throws FormatterException, ConfigurationException {
+		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("value", SQLType.VARCHAR));
+		List<Object[]> data = new ArrayList<>();
+		for (Entry<String, String> e : System.getenv().entrySet()) {
+			data.add(new Object[]{e.getKey(), e.getValue()});
+		}
+		printTable(formatter, header, "-- environment variables", null, data, 0);
+	}
+
+	private void listFormatters() throws ConfigurationException, FormatterException {
+		ColumnsHeader header = constructHeader(
+				new HeaderField("name", SQLType.VARCHAR),
+				new HeaderField("built_in", SQLType.BOOLEAN),
+				new HeaderField("default", SQLType.BOOLEAN),
+				new HeaderField("class_name", SQLType.VARCHAR),
+				new HeaderField("valid", SQLType.BOOLEAN));
+		List<Object[]> data = new ArrayList<>();
+
+		String defaultFormatter = configurationProvider.getConfiguration().getDefaultFormatter();
+		defaultFormatter = defaultFormatter == null ? Configuration.DEFAULT_FORMATTER : defaultFormatter;
+
+		for (FormatterDefinition fd : configurationProvider.getConfiguration().getBuildInFormatters()) {
+			data.add(new Object[]{fd.getName(), true, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)});
+		}
+
+		for (FormatterDefinition fd : configurationProvider.getConfiguration().getFormatters()) {
+			data.add(new Object[]{fd.getName(), false, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)});
+		}
+
+		printTable(formatter, header, "-- configured and built-in output formatters", null, data);
+	}
+
+	private boolean isInstantiable(FormatterDefinition fd) {
+		try {
+			try (ByteArrayOutputStream testStream = new ByteArrayOutputStream()) {
+				fd.getInstance(new FormatterContext(testStream, new Properties(0)));
+				return true;
+			}
+		} catch (Exception e) {
+			log.log(Level.SEVERE, "Unable to create an instance of formatter: " + fd.getName(), e);
+			return false;
+		}
+	}
+
+	private void listFormatterProperties() throws FormatterException, ConfigurationException {
+		for (String formatterName : options.getFormatterNamesToListProperties()) {
+			listFormatterProperties(formatterName);
+		}
+	}
+
+	private void listFormatterProperties(String formatterName) throws FormatterException, ConfigurationException {
+		FormatterDefinition fd = configurationProvider.getConfiguration().getFormatter(formatterName);
+		try {
+
+			// currently only for debugging purposes
+			// TODO: introduce --info-lister-property or generic filtering capability in printTable() ?
+			boolean printDeclaredIn = options.getFormatterProperties().getBoolean("InfoLister:print:declared_in", false);
+
+			List<HeaderField> headerFields = new ArrayList<>();
+			headerFields.add(new HeaderField("name", SQLType.VARCHAR));
+			headerFields.add(new HeaderField("type", SQLType.VARCHAR));
+			headerFields.add(new HeaderField("default", SQLType.VARCHAR));
+			headerFields.add(new HeaderField("description", SQLType.VARCHAR));
+			if (printDeclaredIn) {
+				headerFields.add(new HeaderField("declared_in", SQLType.VARCHAR));
+			}
+
+			ColumnsHeader header = constructHeader(headerFields.toArray(new HeaderField[0]));
+
+			Map<String, Object[]> data = new HashMap<>();
+			Class<Formatter> formatterClass = (Class<Formatter>) Class.forName(fd.getClassName());
+			List<Class<? extends Formatter>> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class);
+			Collections.reverse(hierarchy);
+			hierarchy.stream().forEach((c) -> {
+				for (PropertyDeclaration p : Functions.getPropertyDeclarations(c)) {
+					data.put(p.name(), propertyDeclarationToRow(p, c, printDeclaredIn));
+				}
+			});
+
+			List<Parameter> parameters = new ArrayList<>();
+			parameters.add(new NamedParameter("formatter", formatterName, SQLType.VARCHAR));
+
+			printTable(formatter, header, "-- formatter properties", parameters, new ArrayList<>(data.values()));
+		} catch (ClassNotFoundException e) {
+			throw new ConfigurationException("Unable to find class " + fd.getClassName() + " of formatter" + fd.getName(), e);
+		}
+	}
+
+	private static Object[] propertyDeclarationToRow(PropertyDeclaration p, Class formatterClass, boolean printDeclaredIn) {
+		List list = new ArrayList();
+
+		list.add(p.name());
+		list.add(CommonProperties.getSimpleTypeName(p.type()));
+		list.add(p.defaultValue());
+		list.add(p.description());
+		if (printDeclaredIn) {
+			list.add(formatterClass.getName());
+		}
+
+		return list.toArray();
+	}
+
+	private void listTypes() throws FormatterException, ConfigurationException {
+		ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("code", SQLType.INTEGER));
+		List<Object[]> data = new ArrayList<>();
+		for (SQLType sqlType : SQLType.values()) {
+			data.add(new Object[]{sqlType.name(), sqlType.getCode()});
+		}
+		printTable(formatter, header, "-- data types", null, data);
+		log.log(Level.INFO, "Type names in --types option are case insensitive");
+	}
+
+	private void listDatabases() throws ConfigurationException, FormatterException {
+		ColumnsHeader header = constructHeader(
+				new HeaderField("database_name", SQLType.VARCHAR),
+				new HeaderField("user_name", SQLType.VARCHAR),
+				new HeaderField("database_url", SQLType.VARCHAR));
+		List<Object[]> data = new ArrayList<>();
+
+		final List<DatabaseDefinition> configuredDatabases = configurationProvider.getConfiguration().getDatabases();
+		if (configuredDatabases.isEmpty()) {
+			log.log(Level.WARNING, "No databases are configured.");
+		} else {
+			for (DatabaseDefinition dd : configuredDatabases) {
+				data.add(new Object[]{dd.getName(), dd.getUserName(), dd.getUrl()});
+
+				final TunnelDefinition tunnel = dd.getTunnel();
+				if (tunnel != null) {
+					log.log(Level.INFO, "Tunnel command: {0}", tunnel.getCommand());
+					for (CommandArgument ca : Functions.notNull(tunnel.getArguments())) {
+						log.log(Level.INFO, "\targument: {0}/{1}", new Object[]{ca.getType(), ca.getValue()});
+					}
+				}
+
+			}
+		}
+
+		printTable(formatter, header, "-- configured databases", null, data);
+	}
+
+	private void listJdbcDrivers() throws FormatterException, ConfigurationException {
+		ColumnsHeader header = constructHeader(
+				new HeaderField("class", SQLType.VARCHAR),
+				new HeaderField("version", SQLType.VARCHAR),
+				new HeaderField("major", SQLType.INTEGER),
+				new HeaderField("minor", SQLType.INTEGER),
+				new HeaderField("jdbc_compliant", SQLType.BOOLEAN));
+		List<Object[]> data = new ArrayList<>();
+
+		final ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
+		for (Driver d : drivers) {
+			data.add(new Object[]{
+				d.getClass().getName(),
+				d.getMajorVersion() + "." + d.getMinorVersion(),
+				d.getMajorVersion(),
+				d.getMinorVersion(),
+				d.jdbcCompliant()
+			});
+		}
+
+		printTable(formatter, header, "-- discovered JDBC drivers (available on the CLASSPATH)", null, data);
+	}
+
+	private void listJdbcProperties() throws FormatterException, ConfigurationException {
+		for (String dbName : options.getDatabaseNamesToListProperties()) {
+			ColumnsHeader header = constructHeader(
+					new HeaderField("property_name", SQLType.VARCHAR),
+					new HeaderField("required", SQLType.BOOLEAN),
+					new HeaderField("choices", SQLType.ARRAY),
+					new HeaderField("configured_value", SQLType.VARCHAR),
+					new HeaderField("description", SQLType.VARCHAR));
+			List<Object[]> data = new ArrayList<>();
+
+			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
+
+			Driver driver = findDriver(dd);
+
+			if (driver == null) {
+				log.log(Level.WARNING, "No JDBC driver was found for DB: {0} with URL: {1}", new Object[]{dd.getName(), dd.getUrl()});
+			} else {
+				log.log(Level.INFO, "For DB: {0} was found JDBC driver: {1}", new Object[]{dd.getName(), driver.getClass().getName()});
+
+				try {
+					DriverPropertyInfo[] propertyInfos = driver.getPropertyInfo(dd.getUrl(), dd.getProperties().getJavaProperties());
+
+					Set<String> standardProperties = new HashSet<>();
+
+					for (DriverPropertyInfo pi : propertyInfos) {
+						Array choices = new FakeSqlArray(pi.choices, SQLType.VARCHAR);
+						data.add(new Object[]{
+							pi.name,
+							pi.required,
+							choices.getArray() == null ? "" : choices,
+							pi.value == null ? "" : pi.value,
+							pi.description
+						});
+						standardProperties.add(pi.name);
+					}
+
+					for (Property p : dd.getProperties()) {
+						if (!standardProperties.contains(p.getName())) {
+							data.add(new Object[]{
+								p.getName(),
+								"",
+								"",
+								p.getValue(),
+								""
+							});
+							log.log(Level.WARNING, "Your configuration contains property „{0}“ not declared by the JDBC driver.", p.getName());
+						}
+					}
+
+				} catch (SQLException e) {
+					log.log(Level.WARNING, "Error during getting property infos.", e);
+				}
+
+				List<Parameter> parameters = new ArrayList<>();
+				parameters.add(new NamedParameter("database", dbName, SQLType.VARCHAR));
+				parameters.add(new NamedParameter("driver_class", driver.getClass().getName(), SQLType.VARCHAR));
+				parameters.add(new NamedParameter("driver_major_version", driver.getMajorVersion(), SQLType.INTEGER));
+				parameters.add(new NamedParameter("driver_minor_version", driver.getMinorVersion(), SQLType.INTEGER));
+
+				printTable(formatter, header, "-- configured and configurable JDBC driver properties", parameters, data);
+			}
+		}
+
+	}
+
+	private Driver findDriver(DatabaseDefinition dd) {
+		final ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
+		for (Driver d : drivers) {
+			try {
+				if (d.acceptsURL(dd.getUrl())) {
+					return d;
+				}
+			} catch (SQLException e) {
+				log.log(Level.WARNING, "Error during finding JDBC driver for: " + dd.getName(), e);
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Parallelism for connection testing – maximum concurrent database connections.
+	 */
+	private static final int TESTING_THREAD_COUNT = 64;
+	/**
+	 * Time limit for all connection testing threads – particular timeouts per connection will be
+	 * much smaller.
+	 */
+	private static final long TESTING_AWAIT_LIMIT = 1;
+	private static final TimeUnit TESTING_AWAIT_UNIT = TimeUnit.DAYS;
+
+	private void testConnections() throws FormatterException, ConfigurationException {
+		ColumnsHeader header = constructHeader(
+				new HeaderField("database_name", SQLType.VARCHAR),
+				new HeaderField("configured", SQLType.BOOLEAN),
+				new HeaderField("connected", SQLType.BOOLEAN),
+				new HeaderField("product_name", SQLType.VARCHAR),
+				new HeaderField("product_version", SQLType.VARCHAR));
+
+		log.log(Level.FINE, "Testing DB connections in {0} threads", TESTING_THREAD_COUNT);
+
+		ExecutorService es = Executors.newFixedThreadPool(TESTING_THREAD_COUNT);
+
+		final Formatter currentFormatter = formatter;
+
+		printHeader(currentFormatter, header, "-- database configuration and connectivity test", null);
+
+		for (final String dbName : options.getDatabaseNamesToTest()) {
+			preloadDriver(dbName);
+		}
+
+		for (final String dbName : options.getDatabaseNamesToTest()) {
+			es.submit(() -> {
+				final Object[] row = testConnection(dbName);
+				synchronized (currentFormatter) {
+					printRow(currentFormatter, row);
+				}
+			}
+			);
+		}
+
+		es.shutdown();
+
+		try {
+			log.log(Level.FINEST, "Waiting for test results: {0} {1}", new Object[]{TESTING_AWAIT_LIMIT, TESTING_AWAIT_UNIT.name()});
+			boolean finished = es.awaitTermination(TESTING_AWAIT_LIMIT, TESTING_AWAIT_UNIT);
+			if (finished) {
+				log.log(Level.FINEST, "All testing threads finished in time limit.");
+			} else {
+				throw new FormatterException("Exceeded total time limit for test threads – this should never happen");
+			}
+		} catch (InterruptedException e) {
+			throw new FormatterException("Interrupted while waiting for test results", e);
+		}
+
+		printFooter(currentFormatter);
+	}
+
+	/**
+	 * JDBC driver classes should be preloaded in single thread to avoid deadlocks while doing
+	 * {@linkplain DriverManager#registerDriver(java.sql.Driver)} during parallel connections.
+	 *
+	 * @param dbName
+	 */
+	private void preloadDriver(String dbName) {
+		try {
+			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
+			Driver driver = findDriver(dd);
+			if (driver == null) {
+				log.log(Level.WARNING, "No Driver found for DB: {0}", dbName);
+			} else {
+				log.log(Level.FINEST, "Driver preloading for DB: {0} was successfull", dbName);
+			}
+		} catch (Exception e) {
+			LogRecord r = new LogRecord(Level.WARNING, "Failed to preload the Driver for DB: {0}");
+			r.setParameters(new Object[]{dbName});
+			r.setThrown(e);
+			log.log(r);
+		}
+	}
+
+	private Object[] testConnection(String dbName) {
+		log.log(Level.FINE, "Testing connection to database: {0}", dbName);
+
+		boolean succesfullyConnected = false;
+		boolean succesfullyConfigured = false;
+		String productName = null;
+		String productVersion = null;
+
+		try {
+			DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName);
+			log.log(Level.FINE, "Database definition was loaded from configuration");
+			succesfullyConfigured = true;
+			try (DatabaseConnection dc = dd.connect(options.getDatabaseProperties())) {
+				succesfullyConnected = dc.test();
+				productName = dc.getProductName();
+				productVersion = dc.getProductVersion();
+			}
+			log.log(Level.FINE, "Database connection test was successful");
+		} catch (ConfigurationException | SQLException | RuntimeException e) {
+			log.log(Level.SEVERE, "Error during testing connection " + dbName, e);
+		}
+
+		return new Object[]{dbName, succesfullyConfigured, succesfullyConnected, productName, productVersion};
+	}
+
+	private void printResource(String fileName) {
+		try (BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(fileName)))) {
+			while (true) {
+				String line = reader.readLine();
+				if (line == null) {
+					break;
+				} else {
+					println(line);
+				}
+			}
+		} catch (Exception e) {
+			log.log(Level.SEVERE, "Unable to print this info. Please see our website for it: " + Constants.WEBSITE, e);
+		}
+	}
+
+	private void println(String line) {
+		out.println(line);
+	}
+
+	private void printTable(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters, List<Object[]> data) throws ConfigurationException, FormatterException {
+		printTable(formatter, header, sql, parameters, data, null);
+	}
+
+	private void printTable(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters, List<Object[]> data, final Integer sortByColumn) throws ConfigurationException, FormatterException {
+		printHeader(formatter, header, sql, parameters);
+
+		if (sortByColumn != null) {
+			Collections.sort(data, new Comparator<Object[]>() {
+
+				@Override
+				public int compare(Object[] o1, Object[] o2) {
+					String s1 = String.valueOf(o1[sortByColumn]);
+					String s2 = String.valueOf(o2[sortByColumn]);
+					return s1.compareTo(s2);
+				}
+			});
+		}
+
+		for (Object[] row : data) {
+			printRow(formatter, row);
+		}
+
+		printFooter(formatter);
+	}
+
+	private void printHeader(Formatter formatter, ColumnsHeader header, String sql, List<Parameter> parameters) {
+		formatter.writeStartStatement();
+		if (sql != null) {
+			formatter.writeQuery(sql);
+			if (parameters != null) {
+				formatter.writeParameters(parameters);
+			}
+		}
+		formatter.writeStartResultSet(header);
+	}
+
+	private void printRow(Formatter formatter, Object[] row) {
+		formatter.writeStartRow();
+		for (Object cell : row) {
+			formatter.writeColumnValue(cell);
+		}
+		formatter.writeEndRow();
+	}
+
+	private void printFooter(Formatter formatter) {
+		formatter.writeEndResultSet();
+		formatter.writeEndStatement();
+	}
+
+	private Formatter getFormatter() throws ConfigurationException, FormatterException {
+		String formatterName = options.getFormatterName();
+		formatterName = formatterName == null ? Configuration.DEFAULT_FORMATTER_PREFETCHING : formatterName;
+		FormatterDefinition fd = configurationProvider.getConfiguration().getFormatter(formatterName);
+		FormatterContext context = new FormatterContext(out, options.getFormatterProperties());
+		return fd.getInstance(context);
+	}
+
+	private ColumnsHeader constructHeader(HeaderField... fields) throws FormatterException {
+		try {
+			RowSetMetaDataImpl metaData = new RowSetMetaDataImpl();
+			metaData.setColumnCount(fields.length);
+
+			for (int i = 0; i < fields.length; i++) {
+				HeaderField hf = fields[i];
+				int sqlIndex = i + 1;
+				metaData.setColumnName(sqlIndex, hf.name);
+				metaData.setColumnLabel(sqlIndex, hf.name);
+				metaData.setColumnType(sqlIndex, hf.type.getCode());
+				metaData.setColumnTypeName(sqlIndex, hf.type.name());
+			}
+
+			return new ColumnsHeader(metaData);
+		} catch (SQLException e) {
+			throw new FormatterException("Error while constructing table headers", e);
+		}
+	}
+
+	private static class HeaderField {
+
+		String name;
+		SQLType type;
+
+		public HeaderField(String name, SQLType type) {
+			this.name = name;
+			this.type = type;
+		}
+	}
+
+	public enum InfoType {
+
+		HELP {
+			@Override
+			public void showInfo(InfoLister infoLister) {
+				infoLister.printResource(Constants.HELP_FILE);
+			}
+		},
+		VERSION {
+			@Override
+			public void showInfo(InfoLister infoLister) {
+				infoLister.printResource(Constants.VERSION_FILE);
+			}
+		},
+		LICENSE {
+			@Override
+			public void showInfo(InfoLister infoLister) {
+				infoLister.printResource(Constants.LICENSE_FILE);
+			}
+		},
+		JAVA_PROPERTIES {
+			@Override
+			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
+				infoLister.listJavaProperties();
+			}
+		},
+		ENVIRONMENT_VARIABLES {
+			@Override
+			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
+				infoLister.listEnvironmentVariables();
+			}
+		},
+		FORMATTERS {
+			@Override
+			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
+				infoLister.listFormatters();
+			}
+		},
+		FORMATTER_PROPERTIES {
+			@Override
+			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
+				infoLister.listFormatterProperties();
+			}
+		},
+		TYPES {
+			@Override
+			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
+				infoLister.listTypes();
+			}
+		},
+		JDBC_DRIVERS {
+			@Override
+			public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException {
+				infoLister.listJdbcDrivers();
+			}
+		},
+		JDBC_PROPERTIES {
+			@Override
+			public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException {
+				infoLister.listJdbcProperties();
+			}
+		},
+		DATABASES {
+			@Override
+			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
+				infoLister.listDatabases();
+			}
+		},
+		CONNECTION {
+			@Override
+			public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException {
+				infoLister.testConnections();
+			}
+		};
+
+		public abstract void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/InvalidOptionsException.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,66 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class InvalidOptionsException extends Exception {
+
+	private final Collection<OptionProblem> problems = new ArrayList<>();
+
+	public Collection<OptionProblem> getProblems() {
+		return Collections.unmodifiableCollection(problems);
+	}
+
+	public void addProblem(OptionProblem p) {
+		problems.add(p);
+	}
+
+	public boolean hasProblems() {
+		return !problems.isEmpty();
+	}
+
+	public static class OptionProblem {
+
+		private String description;
+		private Throwable exception;
+
+		public OptionProblem(String description) {
+			this.description = description;
+		}
+
+		public OptionProblem(String description, Throwable exception) {
+			this.description = description;
+			this.exception = exception;
+		}
+
+		public String getDescription() {
+			return description;
+		}
+
+		public Throwable getException() {
+			return exception;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/NamedParameter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,48 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import info.globalcode.sql.dk.configuration.NameIdentified;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class NamedParameter extends Parameter implements NameIdentified {
+
+	private String name;
+
+	public NamedParameter(String name, Object value, SQLType type) {
+		super(value, type);
+		this.name = name;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	@Override
+	public String toString() {
+		return "NamedParameter {" + name + " = " + getValue() + "; " + getType() + "}";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/Parameter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,69 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import java.sql.Types;
+
+/**
+ * Parameter for {@linkplain SQLCommand}
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class Parameter {
+
+	/**
+	 * @see Types
+	 */
+	public static final SQLType DEFAULT_TYPE = SQLType.VARCHAR;
+	private Object value;
+	private SQLType type;
+
+	public Parameter() {
+	}
+
+	public Parameter(Object value, SQLType type) {
+		this.value = value;
+		if (type == null) {
+			this.type = DEFAULT_TYPE;
+		} else {
+			this.type = type;
+		}
+	}
+
+	public Object getValue() {
+		return value;
+	}
+
+	public void setValue(Object value) {
+		this.value = value;
+	}
+
+	/**
+	 * @see java.sql.Types
+	 */
+	public SQLType getType() {
+		return type;
+	}
+
+	/**
+	 * @see java.sql.Types
+	 */
+	public void setType(SQLType type) {
+		this.type = type;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommand.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,49 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * Represents SQL string and its parameters (if there are any).
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public abstract class SQLCommand {
+
+	private String query;
+
+	public SQLCommand(String query) {
+		this.query = query;
+	}
+
+	public PreparedStatement prepareStatement(Connection c) throws SQLException {
+		return c.prepareStatement(query);
+	}
+
+	public abstract void parametrize(PreparedStatement ps) throws SQLException;
+
+	public abstract List<? extends Parameter> getParameters();
+
+	public String getQuery() {
+		return query;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNamed.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,155 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import static info.globalcode.sql.dk.Functions.findByName;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Has named parameters.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class SQLCommandNamed extends SQLCommand {
+
+	private static final Logger log = Logger.getLogger(SQLCommandNamed.class.getName());
+	private String namePrefix;
+	private String nameSuffix;
+	private List<NamedParameter> parameters;
+	private List<NamedParameter> parametersUsed = new ArrayList<>();
+	private StringBuilder updatedQuery;
+	private Pattern pattern;
+	private SQLCommandNumbered numbered;
+
+	public SQLCommandNamed(String query, List<NamedParameter> parameters, String namePrefix, String nameSuffix) {
+		super(query);
+		this.updatedQuery = new StringBuilder(query.length());
+		this.parameters = parameters;
+		this.namePrefix = namePrefix;
+		this.nameSuffix = nameSuffix;
+	}
+
+	@Override
+	public PreparedStatement prepareStatement(Connection c) throws SQLException {
+		return getSQLCommandNumbered().prepareStatement(c);
+	}
+
+	@Override
+	public void parametrize(PreparedStatement ps) throws SQLException {
+		getSQLCommandNumbered().parametrize(ps);
+	}
+
+	private void prepare() throws SQLException {
+		try {
+			buildPattern();
+			placeParametersAndUpdateQuery();
+			logPossiblyMissingParameters();
+		} catch (PatternSyntaxException e) {
+			throw new SQLException("Name prefix „" + namePrefix + "“ or suffix „" + nameSuffix + "“ contain a wrong regular expression. " + e.getLocalizedMessage(), e);
+		}
+	}
+
+	/**
+	 * @return SQL command with named parameters converted to SQL command with numbered parameters
+	 */
+	public SQLCommandNumbered getSQLCommandNumbered() throws SQLException {
+		if (numbered == null) {
+			prepare();
+			numbered = new SQLCommandNumbered(updatedQuery.toString(), parametersUsed);
+		}
+
+		return numbered;
+	}
+
+	/**
+	 * Builds a regexp pattern that matches all parameter names (with prefix/suffix) and which has
+	 * one group: parameter name (without prefix/suffix)
+	 */
+	private void buildPattern() throws PatternSyntaxException {
+		StringBuilder patternString = new StringBuilder();
+
+		patternString.append(namePrefix);
+		patternString.append("(?<paramName>");
+		for (int i = 0; i < parameters.size(); i++) {
+			patternString.append(Pattern.quote(parameters.get(i).getName()));
+			if (i < parameters.size() - 1) {
+				patternString.append("|");
+			}
+		}
+		patternString.append(")");
+		patternString.append(nameSuffix);
+
+		pattern = Pattern.compile(patternString.toString());
+	}
+
+	private void placeParametersAndUpdateQuery() {
+		final String originalQuery = getQuery();
+		Matcher m = pattern.matcher(originalQuery);
+
+		int lastPosition = 0;
+		while (m.find(lastPosition)) {
+			String name = m.group("paramName");
+
+			updatedQuery.append(originalQuery.substring(lastPosition, m.start()));
+			updatedQuery.append("?");
+
+			parametersUsed.add(findByName(parameters, name));
+
+			lastPosition = m.end();
+		}
+		updatedQuery.append(originalQuery.substring(lastPosition, originalQuery.length()));
+
+		for (NamedParameter definedParameter : parameters) {
+			if (findByName(parametersUsed, definedParameter.getName()) == null) {
+				/**
+				 * User can have predefined set of parameters and use them with different SQL
+				 * queries that use only subset of these parameters → just warning, not exception.
+				 */
+				log.log(Level.WARNING, "Parameter „{0}“ is defined but not used in the query: „{1}“", new Object[]{definedParameter.getName(), originalQuery});
+			}
+		}
+	}
+
+	private void logPossiblyMissingParameters() {
+		Pattern p = Pattern.compile(namePrefix + "(?<paramName>.+?)" + nameSuffix);
+		Matcher m = p.matcher(updatedQuery);
+		int lastPosition = 0;
+		while (m.find(lastPosition)) {
+			/**
+			 * We have not parsed and understood the SQL query; the parameter-like looking string
+			 * could be inside a literal part of the query → just warning, not exception.
+			 */
+			log.log(Level.WARNING, "Possibly missing parameter „{0}“ in the query: „{1}“", new Object[]{m.group("paramName"), getQuery()});
+			lastPosition = m.end();
+		}
+	}
+
+	@Override
+	public List<NamedParameter> getParameters() {
+		return parameters;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNumbered.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,51 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import static info.globalcode.sql.dk.Functions.notNull;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * Has ordinal/numbered parameters.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class SQLCommandNumbered extends SQLCommand {
+
+	private List<? extends Parameter> parameters;
+
+	public SQLCommandNumbered(String query, List<? extends Parameter> parameters) {
+		super(query);
+		this.parameters = parameters;
+	}
+
+	@Override
+	public void parametrize(PreparedStatement ps) throws SQLException {
+		int i = 1;
+		for (Parameter p : notNull(parameters)) {
+			ps.setObject(i++, p.getValue(), p.getType().getCode());
+		}
+	}
+
+	@Override
+	public List<? extends Parameter> getParameters() {
+		return parameters;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLType.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,95 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import java.sql.Types;
+
+/**
+ * Data types of SQL parameters.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public enum SQLType {
+
+	/**
+	 * Names must be upper case – user input is also converted to upper case → case insensitive
+	 */
+	BIT(Types.BIT),
+	TINYINT(Types.TINYINT),
+	SMALLINT(Types.SMALLINT),
+	INTEGER(Types.INTEGER),
+	BIGINT(Types.BIGINT),
+	FLOAT(Types.FLOAT),
+	REAL(Types.REAL),
+	DOUBLE(Types.DOUBLE),
+	NUMERIC(Types.NUMERIC),
+	DECIMAL(Types.DECIMAL),
+	CHAR(Types.CHAR),
+	VARCHAR(Types.VARCHAR),
+	LONGVARCHAR(Types.LONGVARCHAR),
+	DATE(Types.DATE),
+	TIME(Types.TIME),
+	TIMESTAMP(Types.TIMESTAMP),
+	BINARY(Types.BINARY),
+	VARBINARY(Types.VARBINARY),
+	LONGVARBINARY(Types.LONGVARBINARY),
+	NULL(Types.NULL),
+	OTHER(Types.OTHER),
+	JAVA_OBJECT(Types.JAVA_OBJECT),
+	DISTINCT(Types.DISTINCT),
+	STRUCT(Types.STRUCT),
+	ARRAY(Types.ARRAY),
+	BLOB(Types.BLOB),
+	CLOB(Types.CLOB),
+	REF(Types.REF),
+	DATALINK(Types.DATALINK),
+	BOOLEAN(Types.BOOLEAN),
+	ROWID(Types.ROWID),
+	NCHAR(Types.NCHAR),
+	NVARCHAR(Types.NVARCHAR),
+	LONGNVARCHAR(Types.LONGNVARCHAR),
+	NCLOB(Types.NCLOB),
+	SQLXML(Types.SQLXML);
+	/** value from java.sql.Types */
+	private int code;
+
+	private SQLType(int code) {
+		this.code = code;
+	}
+
+	/**
+	 * @see java.sql.Types.Types
+	 */
+	public int getCode() {
+		return code;
+	}
+
+	/**
+	 * @param code see {@linkplain java.sql.Types.Types}
+	 * @return found SQLType
+	 * @throws IllegalArgumentException if no data type has given code
+	 */
+	public static SQLType valueOf(int code) {
+		for (SQLType t : values()) {
+			if (t.code == code) {
+				return t;
+			}
+		}
+		throw new IllegalArgumentException("No data type has code: " + code);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/Xmlns.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,33 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+/**
+ * XML namespaces
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class Xmlns {
+
+	public static final String CONFIGURATION = "https://sql-dk.globalcode.info/xmlns/configuration";
+	public static final String BATCH_RESULT = "https://sql-dk.globalcode.info/xmlns/batchResult";
+	public static final String XHTML = "http://www.w3.org/1999/xhtml";
+
+	private Xmlns() {
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/Batch.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,32 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.batch;
+
+import info.globalcode.sql.dk.SQLCommand;
+
+/**
+ * Iterator which reads SQL commands from encoded (serialized) batch.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public interface Batch {
+
+	public boolean hasNext() throws BatchException;
+
+	public SQLCommand next() throws BatchException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchConstants.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,35 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.batch;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class BatchConstants {
+
+	public static final Charset CHARSET = StandardCharsets.UTF_8;
+	public static final byte VERSION = 0x01;
+	public static final byte[] BATCH_HEADER = {0x00, 0x53, 0x51, 0x4C, VERSION};
+
+	private BatchConstants() {
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchDecoder.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,108 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.batch;
+
+import info.globalcode.sql.dk.Parameter;
+import info.globalcode.sql.dk.SQLCommand;
+import info.globalcode.sql.dk.SQLCommandNumbered;
+import java.io.DataInputStream;
+import java.io.InputStream;
+import static info.globalcode.sql.dk.batch.BatchConstants.*;
+import static info.globalcode.sql.dk.Functions.toHex;
+import info.globalcode.sql.dk.SQLType;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class BatchDecoder {
+
+	public Batch decode(InputStream in) throws BatchException {
+		return new BatchFromStream(new DataInputStream(in));
+	}
+
+	private class BatchFromStream implements Batch {
+
+		private DataInputStream in;
+		private boolean hasNext;
+
+		public BatchFromStream(DataInputStream in) throws BatchException {
+			this.in = in;
+			hasNext = verifyHeader();
+		}
+
+		@Override
+		public boolean hasNext() throws BatchException {
+			return hasNext;
+		}
+
+		@Override
+		public SQLCommand next() throws BatchException {
+			try {
+				String sql = readNextString();
+
+				int paramCount = in.readInt();
+				List<Parameter> parameters = new ArrayList<>(paramCount);
+
+				for (int i = 0; i < paramCount; i++) {
+					SQLType type = SQLType.valueOf(in.readInt());
+					String value = readNextString();
+					parameters.add(new Parameter(value, type));
+				}
+
+				hasNext = verifyHeader();
+
+				SQLCommand sqlCommand = new SQLCommandNumbered(sql, parameters);
+				return sqlCommand;
+			} catch (IOException e) {
+				throw new BatchException("Unable to read batch", e);
+			}
+		}
+
+		private String readNextString() throws IOException {
+			byte[] buffer = new byte[in.readInt()];
+			in.read(buffer);
+			return new String(buffer, CHARSET);
+		}
+
+		/**
+		 * @return true if correct batch header was found | false if EOF was found
+		 * @throws BatchException if unexpected data was found (not batch header nor EOF)
+		 */
+		private boolean verifyHeader() throws BatchException {
+			try {
+				byte[] buffer = new byte[BATCH_HEADER.length];
+				int bytesRead = in.read(buffer);
+
+				if (bytesRead == BATCH_HEADER.length && Arrays.equals(buffer, BATCH_HEADER)) {
+					return true;
+				} else if (bytesRead == -1) {
+					return false;
+				} else {
+					throw new BatchException("This is not SQL-DK batch: " + toHex(buffer));
+				}
+			} catch (IOException e) {
+				throw new BatchException("Unable to read batch header", e);
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchEncoder.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,83 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.batch;
+
+import info.globalcode.sql.dk.Parameter;
+import info.globalcode.sql.dk.SQLCommand;
+import info.globalcode.sql.dk.SQLCommandNamed;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import static info.globalcode.sql.dk.batch.BatchConstants.*;
+import java.io.ByteArrayOutputStream;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class BatchEncoder {
+
+	public int encode(SQLCommand sqlCommand, OutputStream out) throws BatchException {
+		try {
+			ByteArrayOutputStream bufferAOS = new ByteArrayOutputStream();
+			DataOutputStream buffer = new DataOutputStream(bufferAOS);
+
+			buffer.write(BATCH_HEADER);
+
+			if (sqlCommand instanceof SQLCommandNamed) {
+				sqlCommand = ((SQLCommandNamed) sqlCommand).getSQLCommandNumbered();
+			}
+
+			writeNextString(sqlCommand.getQuery(), buffer);
+
+			List<? extends Parameter> parameters = sqlCommand.getParameters();
+
+			buffer.writeInt(parameters.size());
+
+			for (Parameter p : parameters) {
+				buffer.writeInt(p.getType().getCode());
+				writeNextString((String) p.getValue(), buffer); // parameters are encoded before any preprocessing
+			}
+
+			buffer.flush();
+			bufferAOS.writeTo(out);
+			out.flush();
+			return bufferAOS.size();
+		} catch (IOException e) {
+			throw new BatchException("Unable to write SQL command: " + sqlCommand, e);
+		} catch (SQLException e) {
+			throw new BatchException("Unable to converd named SQL command to numbered: " + sqlCommand, e);
+		}
+	}
+
+	private void writeNextString(String s, DataOutputStream out) throws IOException {
+		byte[] bytes = toBytes(s);
+		out.writeInt(bytes.length);
+		out.write(bytes);
+	}
+
+	private static byte[] toBytes(String s) {
+		if (s == null) {
+			return new byte[]{};
+		} else {
+			return s.getBytes(CHARSET);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchException.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,42 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.batch;
+
+import info.globalcode.sql.dk.DKException;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class BatchException extends DKException {
+
+	public BatchException() {
+	}
+
+	public BatchException(String message) {
+		super(message);
+	}
+
+	public BatchException(Throwable cause) {
+		super(cause);
+	}
+
+	public BatchException(String message, Throwable cause) {
+		super(message, cause);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/CommandArgument.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,82 @@
+/**
+ * SQL-DK
+ * Copyright © 2015 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlEnumValue;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class CommandArgument {
+
+	private String value;
+	private TYPE type;
+
+	@XmlEnum
+	public static enum TYPE {
+
+		/**
+		 * value = literal (text) argument
+		 */
+		@XmlEnumValue("literal")
+		LITERAL,
+		/**
+		 * value will be substituted by hostname or IP address of the DB server
+		 */
+		@XmlEnumValue("host")
+		HOST,
+		/**
+		 * value will be substituted by the port of the DB server
+		 */
+		@XmlEnumValue("port")
+		PORT,
+		/**
+		 * value will be substituted by environmental variable of given name
+		 */
+		@XmlEnumValue("env")
+		ENVIRONMENT_VARIABLE,
+		/**
+		 * value will be substituted by database property of given name
+		 */
+		@XmlEnumValue("dbProperty")
+		DB_PROPERTY;
+	}
+
+	@XmlValue
+	public String getValue() {
+		return value;
+	}
+
+	public void setValue(String value) {
+		this.value = value;
+	}
+
+	@XmlAttribute(name = "type")
+	public TYPE getType() {
+		return type;
+	}
+
+	public void setType(TYPE type) {
+		this.type = type;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Configuration.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,173 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
+import static info.globalcode.sql.dk.Functions.findByName;
+import info.globalcode.sql.dk.formatting.BarChartFormatter;
+import info.globalcode.sql.dk.formatting.SilentFormatter;
+import info.globalcode.sql.dk.formatting.SingleRecordFormatter;
+import info.globalcode.sql.dk.formatting.SingleValueFormatter;
+import info.globalcode.sql.dk.formatting.TabularFormatter;
+import info.globalcode.sql.dk.formatting.TabularPrefetchingFormatter;
+import info.globalcode.sql.dk.formatting.TabularWrappingFormatter;
+import info.globalcode.sql.dk.formatting.TeXFormatter;
+import info.globalcode.sql.dk.formatting.XhtmlFormatter;
+import info.globalcode.sql.dk.formatting.XmlFormatter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ * Object representation of user configuration loaded from XML.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+@XmlRootElement(name = "configuration", namespace = CONFIGURATION)
+public class Configuration {
+
+	private List<DatabaseDefinition> databases = new ArrayList<>();
+	private List<FormatterDefinition> formatters = new ArrayList<>();
+	/**
+	 * is used if no formatter is specified on CLI nor in user configuration
+	 */
+	public static final String DEFAULT_FORMATTER = TabularFormatter.NAME;
+	/**
+	 * Can be used as default if prefetching is ok – for configuration listings (config is alread in
+	 * memory, so this does not matter)
+	 */
+	public static final String DEFAULT_FORMATTER_PREFETCHING = TabularPrefetchingFormatter.NAME;
+	private String defaultFormatter;
+	/**
+	 * Default list of formatters. Is used if particular name is not found in user configuration.
+	 */
+	private static final Collection<FormatterDefinition> buildInFormatters;
+
+	static {
+		Collection<FormatterDefinition> l = new ArrayList<>();
+		l.add(new FormatterDefinition(SilentFormatter.NAME, SilentFormatter.class.getName()));
+		l.add(new FormatterDefinition(SingleValueFormatter.NAME, SingleValueFormatter.class.getName()));
+		l.add(new FormatterDefinition(SingleRecordFormatter.NAME, SingleRecordFormatter.class.getName()));
+		l.add(new FormatterDefinition(XmlFormatter.NAME, XmlFormatter.class.getName()));
+		l.add(new FormatterDefinition(XhtmlFormatter.NAME, XhtmlFormatter.class.getName()));
+		l.add(new FormatterDefinition(TabularFormatter.NAME, TabularFormatter.class.getName()));
+		l.add(new FormatterDefinition(TabularPrefetchingFormatter.NAME, TabularPrefetchingFormatter.class.getName()));
+		l.add(new FormatterDefinition(TabularWrappingFormatter.NAME, TabularWrappingFormatter.class.getName()));
+		l.add(new FormatterDefinition(TeXFormatter.NAME, TeXFormatter.class.getName()));
+		//l.add(new FormatterDefinition(DsvFormatter.NAME, DsvFormatter.class.getName()));
+		//l.add(new FormatterDefinition(SystemCommandExecutor.NAME, SystemCommandExecutor.class.getName()));
+		l.add(new FormatterDefinition(BarChartFormatter.NAME, BarChartFormatter.class.getName()));
+		buildInFormatters = Collections.unmodifiableCollection(l);
+	}
+
+	@XmlElement(name = "database", namespace = CONFIGURATION)
+	public List<DatabaseDefinition> getDatabases() {
+		return databases;
+	}
+
+	public void setDatabases(List<DatabaseDefinition> databases) {
+		this.databases = databases;
+	}
+
+	/**
+	 * @param name
+	 * @return
+	 * @throws ConfigurationException if no database with this name is configured
+	 */
+	public DatabaseDefinition getDatabase(String name) throws ConfigurationException {
+		DatabaseDefinition dd = findByName(databases, name);
+		if (dd == null) {
+			throw new ConfigurationException("Database is not configured: " + name);
+		} else {
+			return dd;
+		}
+	}
+
+	/**
+	 * @return only configured formatters
+	 * @see #getBuildInFormatters()
+	 * @see #getAllFormatters()
+	 */
+	@XmlElement(name = "formatter", namespace = CONFIGURATION)
+	public List<FormatterDefinition> getFormatters() {
+		return formatters;
+	}
+
+	public void setFormatters(List<FormatterDefinition> formatters) {
+		this.formatters = formatters;
+	}
+
+	/**
+	 * @param name name of desired formatter. Looking for this name in user configuration, then in
+	 * buil-in formatters. If null, default from configuration or (if not configured) built-in
+	 * default is used.
+	 * @return formatter definition
+	 * @throws ConfigurationException if no formatter with this name was found
+	 */
+	public FormatterDefinition getFormatter(String name) throws ConfigurationException {
+		if (name == null) {
+			return defaultFormatter == null ? getFormatter(DEFAULT_FORMATTER) : getFormatter(defaultFormatter);
+		} else {
+			FormatterDefinition fd = findByName(formatters, name);
+			fd = fd == null ? findByName(buildInFormatters, name) : fd;
+			if (fd == null) {
+				throw new ConfigurationException("Formatter is not configured: " + name);
+			} else {
+				return fd;
+			}
+		}
+	}
+
+	/**
+	 * @return only built-in formatters
+	 * @see #getAllFormatters()
+	 * @see #getFormatters()
+	 */
+	@XmlTransient
+	public Collection<FormatterDefinition> getBuildInFormatters() {
+		return buildInFormatters;
+	}
+
+	/**
+	 * @return built-in + configured formatters
+	 * @see #getFormatters()
+	 */
+	@XmlTransient
+	public Collection<FormatterDefinition> getAllFormatters() {
+		Collection<FormatterDefinition> allFormatters = new ArrayList<>();
+		allFormatters.addAll(buildInFormatters);
+		allFormatters.addAll(formatters);
+		return allFormatters;
+	}
+
+	/**
+	 * @return name of default formatter, is used if name is not specified on CLI
+	 */
+	@XmlElement(name = "defaultFormatter", namespace = CONFIGURATION)
+	public String getDefaultFormatter() {
+		return defaultFormatter;
+	}
+
+	public void setDefaultFormatter(String defaultFormatter) {
+		this.defaultFormatter = defaultFormatter;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/ConfigurationException.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,42 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import info.globalcode.sql.dk.DKException;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class ConfigurationException extends DKException {
+
+	public ConfigurationException() {
+	}
+
+	public ConfigurationException(String message) {
+		super(message);
+	}
+
+	public ConfigurationException(Throwable cause) {
+		super(cause);
+	}
+
+	public ConfigurationException(String message, Throwable cause) {
+		super(message, cause);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/ConfigurationProvider.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,28 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+/**
+ * Use for lazy-loading of the configuration.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public interface ConfigurationProvider {
+
+	public Configuration getConfiguration() throws ConfigurationException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/DatabaseDefinition.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,147 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
+import info.globalcode.sql.dk.DatabaseConnection;
+import info.globalcode.sql.dk.jmx.ConnectionManagement;
+import java.sql.SQLException;
+import java.util.logging.Logger;
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * Configured (but not yet connected) database connection.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class DatabaseDefinition implements NameIdentified {
+
+	private static final Logger log = Logger.getLogger(DatabaseDefinition.class.getName());
+	/**
+	 * database name in SQL-DK configuration
+	 */
+	private String name;
+	/**
+	 * JDBC URL
+	 */
+	private String url;
+	/**
+	 * JDBC user name
+	 */
+	private String userName;
+	/**
+	 * JDBC password
+	 */
+	private String password;
+	/**
+	 * optional JDBC driver – if empty, the DriverManager is used to lookup specific Driver for
+	 * given URL
+	 */
+	private String driver;
+	/**
+	 * JDBC properties
+	 */
+	private Properties properties = new Properties();
+	/**
+	 * optional definition of tunnel to the remote database
+	 */
+	private TunnelDefinition tunnel;
+
+	@XmlElement(name = "name", namespace = CONFIGURATION)
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	@XmlElement(name = "url", namespace = CONFIGURATION)
+	public String getUrl() {
+		return url;
+	}
+
+	public void setUrl(String url) {
+		this.url = url;
+	}
+
+	@XmlElement(name = "userName", namespace = CONFIGURATION)
+	public String getUserName() {
+		return userName;
+	}
+
+	public void setUserName(String userName) {
+		this.userName = userName;
+	}
+
+	@XmlElement(name = "password", namespace = CONFIGURATION)
+	public String getPassword() {
+		return password;
+	}
+
+	public void setPassword(String password) {
+		this.password = password;
+	}
+
+	public String getDriver() {
+		return driver;
+	}
+
+	public void setDriver(String driver) {
+		this.driver = driver;
+	}
+
+	@XmlElement(name = "property", namespace = CONFIGURATION)
+	public Properties getProperties() {
+		return properties;
+	}
+
+	public void setProperties(Properties properties) {
+		this.properties = properties;
+	}
+
+	public TunnelDefinition getTunnel() {
+		return tunnel;
+	}
+
+	public void setTunnel(TunnelDefinition tunnel) {
+		this.tunnel = tunnel;
+	}
+
+	/**
+	 * @param properties ad-hoc properties from CLI options (for the JDBC driver)
+	 * @param jmxBean JMX management bean for progress reporting | null = disable JMX
+	 * @return
+	 * @throws java.sql.SQLException
+	 */
+	public DatabaseConnection connect(Properties properties, ConnectionManagement jmxBean) throws SQLException {
+		return new DatabaseConnection(this, properties, jmxBean);
+	}
+
+	/**
+	 * @param properties
+	 * @return
+	 * @throws java.sql.SQLException
+	 * @see #connect(info.globalcode.sql.dk.configuration.Properties, java.lang.String)
+	 * With disabled JMX reporting.
+	 */
+	public DatabaseConnection connect(Properties properties) throws SQLException {
+		return new DatabaseConnection(this, properties, null);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/FormatterDefinition.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,114 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
+import info.globalcode.sql.dk.formatting.Formatter;
+import info.globalcode.sql.dk.formatting.FormatterContext;
+import info.globalcode.sql.dk.formatting.FormatterException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ * Configured (but not yet instantiated) formatter.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class FormatterDefinition implements NameIdentified {
+
+	private String name;
+	private String className;
+	private Properties properties = new Properties();
+
+	public FormatterDefinition() {
+	}
+
+	public FormatterDefinition(String name, String className) {
+		this.name = name;
+		this.className = className;
+	}
+
+	public FormatterDefinition(String name, String className, Properties properties) {
+		this(name, className);
+		this.properties = properties;
+	}
+
+	@XmlElement(name = "name", namespace = CONFIGURATION)
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * Filter's class. Must implement the
+	 * <code>info.globalcode.sql.dk.formatting.Formatter</code> interface.
+	 * Subclassing the
+	 * <code>info.globalcode.sql.dk.formatting.AbstractFormatter</code> is strongly recommended.
+	 * The constructor must accept one parameter:
+	 * <code>info.globalcode.sql.dk.formatting.FormatterContext</code>
+	 *
+	 * @return fully qualified class name
+	 */
+	@XmlElement(name = "class", namespace = CONFIGURATION)
+	public String getClassName() {
+		return className;
+	}
+
+	public void setClassName(String className) {
+		this.className = className;
+	}
+
+	@XmlElement(name = "property", namespace = CONFIGURATION)
+	public Properties getProperties() {
+		return properties;
+	}
+
+	public void setProperties(Properties properties) {
+		this.properties = properties;
+	}
+
+	/**
+	 * @param context
+	 * @return
+	 * @throws FormatterException
+	 */
+	public Formatter getInstance(FormatterContext context) throws FormatterException {
+		context.getProperties().setDefaults(properties);
+		try {
+			Constructor constructor = Class.forName(className).getConstructor(context.getClass());
+
+			Object instance = constructor.newInstance(context);
+			if (instance instanceof Formatter) {
+				return (Formatter) instance;
+			} else {
+				throw new FormatterException("Formatter " + instance + " does not implement the " + Formatter.class.getName() + " interface");
+			}
+		} catch (ClassNotFoundException e) {
+			throw new FormatterException("Formatter class does not exist: " + className, e);
+		} catch (NoSuchMethodException e) {
+			throw new FormatterException("Formatter class with no valid constructor: " + className, e);
+		} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+			throw new FormatterException("Formatter's constructor caused an error: " + className, e);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Loader.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,101 @@
+/**
+ * SQL-DK
+ * Copyright © 2015 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import info.globalcode.sql.dk.*;
+import static info.globalcode.sql.dk.DatabaseConnection.JDBC_PROPERTY_USER;
+import static info.globalcode.sql.dk.DatabaseConnection.JDBC_PROPERTY_PASSWORD;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+
+/**
+ * Configuration loader – deserializes Configuration from the XML file.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class Loader {
+
+	private static final Logger log = Logger.getLogger(Loader.class.getName());
+
+	public Configuration loadConfiguration() throws ConfigurationException {
+		try {
+			JAXBContext jaxb = JAXBContext.newInstance(Configuration.class.getPackage().getName(), Configuration.class.getClassLoader());
+			Unmarshaller u = jaxb.createUnmarshaller();
+			return (Configuration) u.unmarshal(Constants.CONFIG_FILE);
+		} catch (Exception e) {
+			throw new ConfigurationException("Unable to load configuration from " + Constants.CONFIG_FILE, e);
+		}
+	}
+
+	/**
+	 * JDBC connection should not be used directly in SQL-DK.
+	 *
+	 * @see DatabaseDefinition#connect(info.globalcode.sql.dk.configuration.Properties)
+	 * @param properties
+	 * @param databaseDefinition
+	 * @return
+	 * @throws java.sql.SQLException
+	 */
+	public static Connection jdbcConnect(DatabaseDefinition databaseDefinition, Properties properties) throws SQLException {
+		synchronized (properties) {
+			/**
+			 * Avoid rewriting the properties. Usually, the connection is created only once, but
+			 * with --test-connection and with SQL-DK JDBC driver, it might be reused.
+			 */
+			properties = properties.clone();
+		}
+		if (properties.hasProperty(JDBC_PROPERTY_PASSWORD)) {
+			log.log(Level.WARNING, "Passing DB password as CLI parameter is insecure!");
+		}
+		Properties credentials = new Properties();
+		credentials.add(new Property(JDBC_PROPERTY_USER, databaseDefinition.getUserName()));
+		credentials.add(new Property(JDBC_PROPERTY_PASSWORD, databaseDefinition.getPassword()));
+		credentials.setDefaults(databaseDefinition.getProperties());
+		properties.setDefaults(credentials);
+		java.util.Properties javaProperties = properties.getJavaProperties();
+
+		String driverClassName = databaseDefinition.getDriver();
+		final String url = databaseDefinition.getUrl();
+		if (driverClassName == null) {
+			log.log(Level.FINE, "Using DriverManager to create connection for „{0}“", url);
+			return DriverManager.getConnection(url, javaProperties);
+		} else {
+			log.log(Level.FINE, "Using custom Driver „{0}“ to create connection for „{1}“", new Object[]{driverClassName, url});
+			try {
+				Class<Driver> driverClass = (Class<Driver>) Class.forName(driverClassName);
+				Driver driver = driverClass.newInstance();
+				Connection connection = driver.connect(url, javaProperties);
+				if (connection == null) {
+					log.log(Level.SEVERE, "Driver „{0}“ returend null → it does not accept the URL: „{1}“", new Object[]{driverClassName, url});
+					throw new SQLException("Unable to connect: driver returned null.");
+				} else {
+					return connection;
+				}
+			} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | SQLException e) {
+				throw new SQLException("Unable to connect usig specific driver: " + driverClassName, e);
+			}
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/NameIdentified.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,27 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public interface NameIdentified {
+
+	public String getName();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Properties.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,129 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import java.util.ArrayList;
+import javax.xml.bind.annotation.XmlTransient;
+import static info.globalcode.sql.dk.Functions.findByName;
+import java.util.Collections;
+
+/**
+ * <p>
+ * List of configurables.</p>
+ *
+ * <p>
+ * Can be backed by defaults – if value for given name is nof found in this instance, we will
+ * look into defaults. Methods also accept defaultValue parameter – is used if property is nof found
+ * even in default properties.</p>
+ *
+ * <p>
+ * Typical use: </p>
+ * <ul>
+ * <li>this instance – ad-hoc properties from CLI options</li>
+ * <li>default properties – from config file</li>
+ * <li>defaultValue – hardcoded default</li>
+ * </ul>
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class Properties extends ArrayList<Property> implements Cloneable {
+
+	private Properties defaults;
+
+	public Properties() {
+	}
+
+	public Properties(int initialCapacity) {
+		super(initialCapacity);
+	}
+
+	@XmlTransient
+	public Properties getDefaults() {
+		return defaults;
+	}
+
+	public void setDefaults(Properties defaults) {
+		this.defaults = defaults;
+	}
+
+	/**
+	 * @param defaults the last/deepest defaults
+	 */
+	public void setLastDefaults(Properties defaults) {
+		if (this.defaults == null) {
+			this.defaults = defaults;
+		} else {
+			this.defaults.setLastDefaults(defaults);
+		}
+	}
+
+	private Property findProperty(String name) {
+		Property p = findByName(this, name);
+		if (p == null && defaults != null) {
+			p = defaults.findProperty(name);
+		}
+		return p;
+	}
+
+	public String getString(String name, String defaultValue) {
+		Property p = findProperty(name);
+		return p == null ? defaultValue : p.getValue();
+	}
+
+	public boolean getBoolean(String name, boolean defaultValue) {
+		Property p = findProperty(name);
+		return p == null ? defaultValue : Boolean.valueOf(p.getValue());
+	}
+
+	public int getInteger(String name, int defaultValue) {
+		Property p = findProperty(name);
+		return p == null ? defaultValue : Integer.valueOf(p.getValue());
+	}
+
+	public boolean hasProperty(String name) {
+		return findByName(this, name) != null;
+	}
+
+	@Override
+	public Properties clone() {
+		Properties clone = new Properties(size());
+		Collections.copy(clone, this);
+		return clone;
+	}
+
+	/**
+	 * @return merged this and backing defaults as Java Properties
+	 */
+	public java.util.Properties getJavaProperties() {
+		java.util.Properties javaProperties = new java.util.Properties();
+		duplicateTo(javaProperties);
+		return javaProperties;
+	}
+
+	private void duplicateTo(java.util.Properties javaProperties) {
+		if (defaults != null) {
+			defaults.duplicateTo(javaProperties);
+		}
+		for (Property p : this) {
+			String value = p.getValue();
+			if (value != null) {
+				javaProperties.setProperty(p.getName(), value);
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Property.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,69 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ * One configurable
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class Property implements NameIdentified, Cloneable {
+
+	private String name;
+	private String value;
+
+	public Property() {
+	}
+
+	public Property(String name, String value) {
+		this.name = name;
+		this.value = value;
+	}
+
+	@XmlAttribute(name = "name")
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	@XmlValue
+	public String getValue() {
+		return value;
+	}
+
+	public void setValue(String value) {
+		this.value = value;
+	}
+
+	@Override
+	public String toString() {
+		return name + "='" + value + "'";
+	}
+
+	@Override
+	protected Property clone() {
+		return new Property(name, value);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/PropertyDeclaration.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,57 @@
+/**
+ * SQL-DK
+ * Copyright © 2015 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
+/**
+ * Declaration of the (formatter) properties – for documentation purposes.
+ *
+ * TODO: automatically inject properties (configured, ad-hoc, default ones) to the formatters
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+@Retention(RUNTIME)
+@Target({ElementType.TYPE})
+@Repeatable(PropertyDeclarations.class)
+public @interface PropertyDeclaration {
+
+	/**
+	 * @return name of the property
+	 */
+	String name();
+
+	/**
+	 * @return data type of the value: String, numbers, Boolean or Enum
+	 */
+	Class type();
+	
+	/**
+	 * @return documentation for the users
+	 */
+	String description();
+
+	/**
+	 * @return default value of this property
+	 */
+	String defaultValue();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/PropertyDeclarations.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,35 @@
+/**
+ * SQL-DK
+ * Copyright © 2015 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+@Retention(RUNTIME)
+@Target({ElementType.TYPE})
+public @interface PropertyDeclarations {
+
+	PropertyDeclaration[] value();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/TunnelDefinition.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,51 @@
+/**
+ * SQL-DK
+ * Copyright © 2015 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.configuration;
+
+import static info.globalcode.sql.dk.Xmlns.CONFIGURATION;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class TunnelDefinition {
+
+	private String command;
+	private List<CommandArgument> arguments;
+
+	@XmlElement(name = "command", namespace = CONFIGURATION)
+	public String getCommand() {
+		return command;
+	}
+
+	public void setCommand(String command) {
+		this.command = command;
+	}
+
+	@XmlElement(name = "argument", namespace = CONFIGURATION)
+	public List<CommandArgument> getArguments() {
+		return arguments;
+	}
+
+	public void setArguments(List<CommandArgument> arguments) {
+		this.arguments = arguments;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,254 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.Parameter;
+import info.globalcode.sql.dk.configuration.DatabaseDefinition;
+import java.util.EmptyStackException;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Stack;
+
+/**
+ * <ol>
+ * <li>ensures integrity – if methods are called in correct order and context</li>
+ * <li>provides default implmentations of methods that does not produce any output for given
+ * events</li>
+ * </ol>
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public abstract class AbstractFormatter implements Formatter {
+
+	private Stack<State> state = new Stack<>();
+	private FormatterContext formatterContext;
+	private ColumnsHeader currentColumnsHeader;
+	private String currentQuery;
+	private int currentColumnsCount;
+	private int currentRowCount;
+
+	public AbstractFormatter(FormatterContext formatterContext) {
+		this.formatterContext = formatterContext;
+		state.push(State.ROOT);
+	}
+
+	/*
+	 * root
+	 * .batch
+	 * ..database
+	 * ...statement
+	 * ....@query
+	 * ....@parameters
+	 * ....resultSet
+	 * .....row
+	 * ......@columnValue
+	 * ....@updatesResult
+	 */
+	protected enum State {
+
+		ROOT,
+		BATCH,
+		DATABASE,
+		STATEMENT,
+		RESULT_SET,
+		ROW
+	}
+
+	/**
+	 * Go down in hierarchy.
+	 * Pushes new state and verifies the old one.
+	 *
+	 * @param current the new state – currently entering
+	 * @param expected expected previous states (any of them is valid)
+	 * @return previous state
+	 * @throws IllegalStateException if previous state was not one from expected
+	 */
+	private State pushState(State current, EnumSet expected) {
+		State previous = state.peek();
+
+		if (expected.contains(previous)) {
+			state.push(current);
+			return previous;
+		} else {
+			throw new IllegalStateException("Formatter was in wrong state: " + previous + " when it should be in one of: " + expected);
+		}
+	}
+
+	protected State peekState(EnumSet expected) {
+		State current = state.peek();
+
+		if (expected.contains(current)) {
+			return current;
+		} else {
+			throw new IllegalStateException("Formatter is in wrong state: " + current + " when it should be in one of: " + expected);
+		}
+
+	}
+
+	/**
+	 * Go up in hierarchy.
+	 * Pops the superior state/branch.
+	 *
+	 * @param expected expected superior state
+	 * @return the superior state
+	 * @throws IllegalStateException if superior state was not one from expected or if there is no
+	 * more superior state (we are at root level)
+	 */
+	private State popState(EnumSet expected) {
+		try {
+			state.pop();
+			State superior = state.peek();
+			if (expected.contains(superior)) {
+				return superior;
+			} else {
+				throw new IllegalStateException("Formatter had wrong superior state: " + superior + " when it should be in one of: " + expected);
+			}
+		} catch (EmptyStackException e) {
+			throw new IllegalStateException("Formatter was already at root level – there is nothing above that.", e);
+		}
+	}
+
+	@Override
+	public void writeStartBatch() {
+		pushState(State.BATCH, EnumSet.of(State.ROOT));
+	}
+
+	@Override
+	public void writeEndBatch() {
+		popState(EnumSet.of(State.ROOT));
+	}
+
+	@Override
+	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
+		pushState(State.DATABASE, EnumSet.of(State.BATCH));
+	}
+
+	@Override
+	public void writeEndDatabase() {
+		popState(EnumSet.of(State.BATCH));
+	}
+
+	@Override
+	public void writeStartStatement() {
+		pushState(State.STATEMENT, EnumSet.of(State.DATABASE));
+	}
+
+	@Override
+	public void writeEndStatement() {
+		popState(EnumSet.of(State.DATABASE));
+	}
+
+	@Override
+	public void writeStartResultSet(ColumnsHeader header) {
+		pushState(State.RESULT_SET, EnumSet.of(State.STATEMENT));
+		currentRowCount = 0;
+		currentColumnsHeader = header;
+	}
+
+	@Override
+	public void writeEndResultSet() {
+		popState(EnumSet.of(State.STATEMENT));
+		currentColumnsHeader = null;
+	}
+
+	@Override
+	public void writeQuery(String sql) {
+		peekState(EnumSet.of(State.STATEMENT));
+
+		if (currentColumnsHeader == null) {
+			currentQuery = sql;
+		} else {
+			throw new IllegalStateException("Query string '" + sql + "' must be set before columns header – was already set: " + currentColumnsHeader);
+		}
+	}
+
+	@Override
+	public void writeParameters(List<? extends Parameter> parameters) {
+		peekState(EnumSet.of(State.STATEMENT));
+
+		if (currentColumnsHeader != null) {
+			throw new IllegalStateException("Parameters '" + parameters + "' must be set before columns header – was already set: " + currentColumnsHeader);
+		}
+
+		if (currentQuery == null && parameters != null) {
+			throw new IllegalStateException("Parameters '" + parameters + "' must be set after query – was not yet set.");
+		}
+	}
+
+	@Override
+	public void writeStartRow() {
+		pushState(State.ROW, EnumSet.of(State.RESULT_SET));
+		currentColumnsCount = 0;
+		currentRowCount++;
+	}
+
+	@Override
+	public void writeEndRow() {
+		popState(EnumSet.of(State.RESULT_SET));
+	}
+
+	@Override
+	public void writeColumnValue(Object value) {
+		peekState(EnumSet.of(State.ROW));
+		currentColumnsCount++;
+
+		int declaredCount = currentColumnsHeader.getColumnCount();
+		if (currentColumnsCount > declaredCount) {
+			throw new IllegalStateException("Current columns count is " + currentColumnsCount + " which is more than declared " + declaredCount + " in header.");
+		}
+	}
+
+	@Override
+	public void writeUpdatesResult(int updatedRowsCount) {
+		peekState(EnumSet.of(State.STATEMENT));
+	}
+
+	@Override
+	public void close() throws FormatterException {
+	}
+
+	public FormatterContext getFormatterContext() {
+		return formatterContext;
+	}
+
+	protected ColumnsHeader getCurrentColumnsHeader() {
+		return currentColumnsHeader;
+	}
+
+	/**
+	 * @return column number, 1 = first
+	 */
+	protected int getCurrentColumnsCount() {
+		return currentColumnsCount;
+	}
+
+	protected boolean isCurrentColumnFirst() {
+		return currentColumnsCount == 1;
+	}
+
+	protected boolean isCurrentColumnLast() {
+		return currentColumnsCount == currentColumnsHeader.getColumnCount();
+	}
+
+	/**
+	 * @return row number, 1 = first
+	 */
+	protected int getCurrentRowCount() {
+		return currentRowCount;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,241 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.ColorfulPrintWriter;
+import info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
+import java.util.Stack;
+import javax.xml.namespace.QName;
+import static info.globalcode.sql.dk.Functions.isEmpty;
+import static info.globalcode.sql.dk.Functions.toHex;
+import info.globalcode.sql.dk.configuration.PropertyDeclaration;
+import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
+import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
+import java.nio.charset.Charset;
+import java.util.EmptyStackException;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>
+ * Provides helper methods for printing pretty intended and optionally colorful (syntax highlighted)
+ * XML output.
+ * </p>
+ *
+ * <p>
+ * Must be used with care – bad usage can lead to invalid XML (e.g. using undeclared namespaces).
+ * </p>
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+@PropertyDeclaration(name = COLORFUL, defaultValue = "false", type = Boolean.class, description = COLORFUL_DESCRIPTION)
+@PropertyDeclaration(name = AbstractXmlFormatter.PROPERTY_INDENT, defaultValue = AbstractXmlFormatter.PROPERTY_INDENT_DEFAULT, type = String.class, description = "tab or sequence of spaces used for indentation of nested elements")
+@PropertyDeclaration(name = AbstractXmlFormatter.PROPERTY_INDENT_TEXT, defaultValue = "true", type = Boolean.class, description = "whether text with line breaks should be indented; if not original whitespace will be preserved.")
+public abstract class AbstractXmlFormatter extends AbstractFormatter {
+
+	private static final Logger log = Logger.getLogger(AbstractXmlFormatter.class.getName());
+	public static final String PROPERTY_INDENT = "indent";
+	protected static final String PROPERTY_INDENT_DEFAULT = "\t";
+	public static final String PROPERTY_INDENT_TEXT = "indentText";
+	private static final TerminalColor ELEMENT_COLOR = TerminalColor.Magenta;
+	private static final TerminalColor ATTRIBUTE_NAME_COLOR = TerminalColor.Green;
+	private static final TerminalColor ATTRIBUTE_VALUE_COLOR = TerminalColor.Yellow;
+	private static final TerminalColor XML_DECLARATION_COLOR = TerminalColor.Red;
+	private static final TerminalColor XML_DOCTYPE_COLOR = TerminalColor.Cyan;
+	private Stack<QName> treePosition = new Stack<>();
+	private final ColorfulPrintWriter out;
+	private final String indent;
+	private final boolean indentText;
+
+	public AbstractXmlFormatter(FormatterContext formatterContext) {
+		super(formatterContext);
+		boolean colorful = formatterContext.getProperties().getBoolean(COLORFUL, false);
+		out = new ColorfulPrintWriter(formatterContext.getOutputStream(), false, colorful);
+		indent = formatterContext.getProperties().getString(PROPERTY_INDENT, PROPERTY_INDENT_DEFAULT);
+		indentText = formatterContext.getProperties().getBoolean(PROPERTY_INDENT_TEXT, true);
+
+		if (!indent.matches("\\s*")) {
+			log.log(Level.WARNING, "Setting indent to „{0}“ is weird & freaky; in hex: {1}", new Object[]{indent, toHex(indent.getBytes())});
+		}
+
+	}
+
+	protected void printStartDocument() {
+		out.print(XML_DECLARATION_COLOR, "<?xml version=\"1.0\" encoding=\"" + Charset.defaultCharset().name() + "\"?>");
+	}
+
+	protected void printDoctype(String doctype) {
+		out.print(XML_DOCTYPE_COLOR, "\n<!DOCTYPE " + doctype + ">");
+	}
+
+	protected void printEndDocument() {
+		out.println();
+		out.flush();
+		if (!treePosition.empty()) {
+			throw new IllegalStateException("Some elements are not closed: " + treePosition);
+		}
+	}
+
+	protected void printStartElement(QName element) {
+		printStartElement(element, null);
+	}
+
+	protected Map<QName, String> singleAttribute(QName name, String value) {
+		Map<QName, String> attributes = new HashMap<>(2);
+		attributes.put(name, value);
+		return attributes;
+	}
+
+	protected void printStartElement(QName element, Map<QName, String> attributes) {
+		printStartElement(element, attributes, false);
+	}
+
+	/**
+	 * @param empty whether element should be closed <codfe>… /&gt;</code> (has no content, do not
+	 * call {@linkplain #printEndElement()})
+	 */
+	private void printStartElement(QName element, Map<QName, String> attributes, boolean empty) {
+		printIndent();
+
+		out.print(ELEMENT_COLOR, "<" + toString(element));
+
+		if (attributes != null) {
+			for (Entry<QName, String> attribute : attributes.entrySet()) {
+				out.print(" ");
+				out.print(ATTRIBUTE_NAME_COLOR, toString(attribute.getKey()));
+				out.print("=");
+				out.print(ATTRIBUTE_VALUE_COLOR, '"' + escapeXmlAttribute(attribute.getValue()) + '"');
+			}
+		}
+
+		if (empty) {
+			out.print(ELEMENT_COLOR, "/>");
+		} else {
+			out.print(ELEMENT_COLOR, ">");
+			treePosition.add(element);
+		}
+
+		out.flush();
+	}
+
+	/**
+	 * Prints text node wrapped in given element without indenting the text and adding line breaks
+	 * (useful for short texts).
+	 *
+	 * @param attributes use {@linkplain  LinkedHashMap} to preserve attributes order
+	 */
+	protected void printTextElement(QName element, Map<QName, String> attributes, String text) {
+		printStartElement(element, attributes);
+
+		String[] lines = text.split("\\n");
+
+		if (indentText && lines.length > 1) {
+			for (String line : lines) {
+				printText(line, true);
+			}
+			printEndElement(true);
+		} else {
+			/*
+			 * line breaks at the end of the text will be eaten – if you need them, use indentText = false
+			 */
+			if (lines.length == 1 && text.endsWith("\n")) {
+				text = text.substring(0, text.length() - 1);
+			}
+
+			printText(text, false);
+			printEndElement(false);
+		}
+	}
+
+	protected void printEmptyElement(QName element, Map<QName, String> attributes) {
+		printStartElement(element, attributes, true);
+	}
+
+	protected void printEndElement() {
+		printEndElement(true);
+	}
+
+	private void printEndElement(boolean indent) {
+		try {
+			QName name = treePosition.pop();
+
+			if (indent) {
+				printIndent();
+			}
+
+			out.print(ELEMENT_COLOR, "</" + toString(name) + ">");
+			out.flush();
+
+		} catch (EmptyStackException e) {
+			throw new IllegalStateException("No more elements to end.", e);
+		}
+	}
+
+	protected void printText(String s, boolean indent) {
+		if (indent) {
+			printIndent();
+		}
+		out.print(escapeXmlText(s));
+		out.flush();
+	}
+
+	protected void printIndent() {
+		out.println();
+		for (int i = 0; i < treePosition.size(); i++) {
+			out.print(indent);
+		}
+	}
+
+	protected static QName qname(String name) {
+		return new QName(name);
+	}
+
+	protected static QName qname(String prefix, String name) {
+		return new QName(null, name, prefix);
+	}
+
+	private String toString(QName name) {
+		if (isEmpty(name.getPrefix(), true)) {
+			return escapeName(name.getLocalPart());
+		} else {
+			return escapeName(name.getPrefix()) + ":" + escapeName(name.getLocalPart());
+		}
+	}
+
+	private String escapeName(String s) {
+		// TODO: avoid ugly values in <name name="…"/>		
+		return s;
+	}
+
+	private static String escapeXmlText(String s) {
+		return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
+		// Not needed:
+		// return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
+	}
+
+	/**
+	 * Expects attribute values enclosed in "quotes" not 'apostrophes'.
+	 */
+	private static String escapeXmlAttribute(String s) {
+		return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/BarChartFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,104 @@
+/**
+ * SQL-DK
+ * Copyright © 2015 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.Functions;
+import info.globalcode.sql.dk.configuration.PropertyDeclaration;
+import info.globalcode.sql.dk.logging.LoggerProducer;
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * TODO: min/max values – range for case that no value is 100 %
+ *
+ * TODO: multiple barcharts in same table (last column is still default) + multiple resultsets
+ *
+ * TODO: negative values - bar starting from the middle, not always from the left
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+@PropertyDeclaration(name = BarChartFormatter.PROPERTY_PRECISION, type = Integer.class, defaultValue = BarChartFormatter.PROPERTY_PRECISION_DEFAULT, description = "number of characters representing 100 % in the bar chart")
+public class BarChartFormatter extends TabularPrefetchingFormatter {
+
+	public static final String NAME = "barchart"; // bash-completion:formatter
+	public static final String PROPERTY_PRECISION = "precision";
+	protected static final String PROPERTY_PRECISION_DEFAULT = "100";
+	private static final MathContext mathContext = MathContext.DECIMAL128;
+	public static final Logger log = LoggerProducer.getLogger();
+	private final BigDecimal chartPrecision;
+	private final char chartFull;
+	private final char chartEmpty;
+
+	public BarChartFormatter(FormatterContext formatterContext) {
+		super(formatterContext);
+		chartPrecision = BigDecimal.valueOf(formatterContext.getProperties().getInteger(PROPERTY_PRECISION, Integer.parseInt(PROPERTY_PRECISION_DEFAULT)));
+		chartFull = isAsciiNostalgia() ? '#' : '█';
+		chartEmpty = isAsciiNostalgia() ? '~' : '░';
+		// TODO: consider using partial blocks for more precision: https://en.wikipedia.org/wiki/Block_Elements
+	}
+
+	@Override
+	protected void postprocessPrefetchedResultSet(ColumnsHeader currentHeader, List<Object[]> currentResultSet) {
+		super.postprocessPrefetchedResultSet(currentHeader, currentResultSet);
+
+		updateColumnWidth(currentHeader.getColumnCount(), chartPrecision.intValue());
+
+		BigDecimal maximum = BigDecimal.ZERO;
+		BigDecimal minimum = BigDecimal.ZERO;
+		int lastIndex = currentHeader.getColumnCount() - 1;
+
+		Object valueObject = null;
+		try {
+			for (Object[] row : currentResultSet) {
+				valueObject = row[lastIndex];
+				if (valueObject != null) {
+					BigDecimal value = new BigDecimal(valueObject.toString());
+					maximum = maximum.max(value);
+					minimum = minimum.min(value);
+				}
+			}
+
+			BigDecimal range = maximum.subtract(minimum);
+
+			for (Object[] row : currentResultSet) {
+				valueObject = row[lastIndex];
+				if (valueObject == null) {
+					row[lastIndex] = "";
+				} else {
+					BigDecimal value = new BigDecimal(valueObject.toString());
+					BigDecimal valueFromMinimum = value.subtract(minimum);
+
+					BigDecimal points = chartPrecision.divide(range, mathContext).multiply(valueFromMinimum, mathContext);
+					int pointsRounded = points.setScale(0, RoundingMode.HALF_UP).intValue();
+					row[lastIndex] = Functions.repeat(chartFull, pointsRounded) + Functions.repeat(chartEmpty, chartPrecision.intValue() - pointsRounded);
+				}
+			}
+
+		} catch (NumberFormatException e) {
+			// https://en.wiktionary.org/wiki/parsable
+			log.log(Level.SEVERE, "Last column must be number or an object with toString() value parsable to a number. But was „{0}“", valueObject);
+			// FIXME: throw FormatterException
+			throw e;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnDescriptor.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,122 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import java.sql.Types;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class ColumnDescriptor {
+
+	private String name;
+	private String label;
+	private int type;
+	private String typeName;
+	private boolean firstColumn;
+	private boolean lastColumn;
+	private int columnNumber;
+
+	/**
+	 * @return column name
+	 * @see #getLabel()
+	 */
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * @return label specified by the SQL AS clause
+	 */
+	public String getLabel() {
+		return label;
+	}
+
+	public void setLabel(String label) {
+		this.label = label;
+	}
+
+	public int getType() {
+		return type;
+	}
+
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	public String getTypeName() {
+		return typeName;
+	}
+
+	public void setTypeName(String typeName) {
+		this.typeName = typeName;
+	}
+
+	public boolean isFirstColumn() {
+		return firstColumn;
+	}
+
+	public void setFirstColumn(boolean firstColumn) {
+		this.firstColumn = firstColumn;
+	}
+
+	public boolean isLastColumn() {
+		return lastColumn;
+	}
+
+	public void setLastColumn(boolean lastColumn) {
+		this.lastColumn = lastColumn;
+	}
+
+	/**
+	 * @return number of this column, 1 = first
+	 */
+	public int getColumnNumber() {
+		return columnNumber;
+	}
+
+	public void setColumnNumber(int columnNumber) {
+		this.columnNumber = columnNumber;
+	}
+
+	public boolean isBoolean() {
+		return type == Types.BOOLEAN;
+	}
+
+	public boolean isNumeric() {
+		switch (type) {
+			case Types.BIGINT:
+			case Types.DECIMAL:
+			case Types.DOUBLE:
+			case Types.FLOAT:
+			case Types.INTEGER:
+			case Types.NUMERIC:
+			case Types.REAL:
+			case Types.SMALLINT:
+			case Types.TINYINT:
+				return true;
+			default:
+				return false;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnsHeader.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,70 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class ColumnsHeader {
+	
+	private ResultSetMetaData metaData;
+	
+	public ColumnsHeader(ResultSetMetaData metaData) {
+		this.metaData = metaData;
+	}
+	
+	public int getColumnCount() {
+		try {
+			return metaData.getColumnCount();
+		} catch (SQLException e) {
+			throw new IllegalStateException("Error during getting column count.", e);
+		}
+	}
+	
+	public List<ColumnDescriptor> getColumnDescriptors() {
+		try {
+			int count = metaData.getColumnCount();
+			List<ColumnDescriptor> list = new ArrayList<>(count);
+			
+			for (int i = 1; i <= count; i++) {
+				ColumnDescriptor cd = new ColumnDescriptor();
+				
+				cd.setFirstColumn(i == 1);
+				cd.setLastColumn(i == count);
+				cd.setColumnNumber(i);
+				
+				cd.setLabel(metaData.getColumnLabel(i));
+				cd.setName(metaData.getColumnName(i));
+				cd.setType(metaData.getColumnType(i));
+				cd.setTypeName(metaData.getColumnTypeName(i));
+				/** TODO: more properties */
+				list.add(cd);
+			}
+			
+			return list;
+		} catch (SQLException e) {
+			throw new IllegalStateException("Error during building column descriptors.", e);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/CommonProperties.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,50 @@
+/**
+ * SQL-DK
+ * Copyright © 2015 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class CommonProperties {
+
+	private static final Map<Class, String> TYPE_SIMPLE_NAMES;
+
+	static {
+		Map<Class, String> m = new HashMap<>();
+		m.put(Boolean.class, "boolean");
+		m.put(String.class, "String");
+		m.put(Character.class, "char");
+		m.put(Integer.class, "int");
+		m.put(Long.class, "long");
+		m.put(Double.class, "double");
+		TYPE_SIMPLE_NAMES = Collections.unmodifiableMap(m);
+	}
+
+	public static String getSimpleTypeName(Class type) {
+		String name = TYPE_SIMPLE_NAMES.get(type);
+		return name == null ? type.getName() : name;
+	}
+
+	public static final String COLORFUL = "color";
+	public static final String COLORFUL_DESCRIPTION = "whether the output should be printed in color (ANSI Escape Sequences)";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FakeSqlArray.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,106 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.SQLType;
+import java.sql.Array;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+
+/**
+ * Fake SQL array, for formatting purposes only
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class FakeSqlArray implements Array {
+
+	private static final UnsupportedOperationException exception = new UnsupportedOperationException("This is just a fake SQL array.");
+	private final Object[] data;
+	private final SQLType baseType;
+
+	public FakeSqlArray(Object[] data, SQLType baseType) {
+		this.data = data;
+		this.baseType = baseType;
+	}
+
+	@Override
+	public String toString() {
+		StringBuilder string = new StringBuilder();
+		for (Object o : data) {
+			string.append(o);
+			string.append("\n");
+		}
+		return string.toString();
+	}
+
+	@Override
+	public String getBaseTypeName() throws SQLException {
+		return baseType.name();
+	}
+
+	@Override
+	public int getBaseType() throws SQLException {
+		return baseType.getCode();
+	}
+
+	@Override
+	public Object getArray() throws SQLException {
+		return data;
+	}
+
+	@Override
+	public Object getArray(Map<String, Class<?>> map) throws SQLException {
+		throw exception;
+	}
+
+	@Override
+	public Object getArray(long index, int count) throws SQLException {
+		throw exception;
+	}
+
+	@Override
+	public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException {
+		throw exception;
+	}
+
+	@Override
+	public ResultSet getResultSet() throws SQLException {
+		throw exception;
+	}
+
+	@Override
+	public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {
+		throw exception;
+	}
+
+	@Override
+	public ResultSet getResultSet(long index, int count) throws SQLException {
+		throw exception;
+	}
+
+	@Override
+	public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) throws SQLException {
+		throw exception;
+	}
+
+	@Override
+	public void free() throws SQLException {
+		throw exception;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/Formatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,67 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.Parameter;
+import info.globalcode.sql.dk.configuration.DatabaseDefinition;
+import java.util.List;
+
+/**
+ * The formatter is responsible for printing the result sets and/or updates result (count of
+ * inserted/updated rows). The formatter can produce output in arbitrary format – text, some markup
+ * or even binary data.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public interface Formatter extends AutoCloseable {
+
+	void writeStartBatch();
+
+	void writeStartDatabase(DatabaseDefinition databaseDefinition);
+
+	void writeEndDatabase();
+
+	void writeStartStatement();
+
+	void writeEndStatement();
+
+	void writeQuery(String sql);
+
+	void writeParameters(List<? extends Parameter> parameters);
+
+	void writeStartResultSet(ColumnsHeader header);
+
+	void writeEndResultSet();
+
+	void writeStartRow();
+
+	void writeColumnValue(Object value);
+
+	void writeEndRow();
+
+	void writeUpdatesResult(int updatedRowsCount);
+
+	void writeEndBatch();
+
+	/**
+	 * If an error occurs (e.g. lost connection during result set reading) this method will be
+	 * called even if there was no {@linkplain #writeEndBach()}.
+	 */
+	@Override
+	void close() throws FormatterException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FormatterContext.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,49 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.configuration.Properties;
+import java.io.OutputStream;
+
+/**
+ * To be passed from the SQL-DK core to the formatter.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class FormatterContext {
+
+	private OutputStream outputStream;
+	private Properties properties;
+
+	public FormatterContext(OutputStream outputStream, Properties properties) {
+		this.outputStream = outputStream;
+		this.properties = properties;
+	}
+
+	public OutputStream getOutputStream() {
+		return outputStream;
+	}
+
+	public Properties getProperties() {
+		return properties;
+	}
+
+	public void setProperties(Properties properties) {
+		this.properties = properties;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FormatterException.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,42 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.DKException;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class FormatterException extends DKException {
+
+	public FormatterException() {
+	}
+
+	public FormatterException(String message) {
+		super(message);
+	}
+
+	public FormatterException(Throwable cause) {
+		super(cause);
+	}
+
+	public FormatterException(String message, Throwable cause) {
+		super(message, cause);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SilentFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,33 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+/**
+ * Does not output anything, can be used instead of
+ * <code>/dev/null</code>.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class SilentFormatter extends AbstractFormatter {
+	
+	public static final String NAME = "silent"; // bash-completion:formatter
+
+	public SilentFormatter(FormatterContext formatterContext) {
+		super(formatterContext);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SingleRecordFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,105 @@
+/**
+ * SQL-DK
+ * Copyright © 2015 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.ColorfulPrintWriter;
+import info.globalcode.sql.dk.Functions;
+import info.globalcode.sql.dk.configuration.PropertyDeclaration;
+import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
+import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
+
+/**
+ * Formatter intended for printing one record (or few records) with many columns.
+ * Prints each colum name and its value on separate line.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+@PropertyDeclaration(name = COLORFUL, defaultValue = "true", type = Boolean.class, description = COLORFUL_DESCRIPTION)
+public class SingleRecordFormatter extends AbstractFormatter {
+
+	public static final String NAME = "record"; // bash-completion:formatter
+	private final ColorfulPrintWriter out;
+	private boolean firstResult = true;
+
+	public SingleRecordFormatter(FormatterContext formatterContext) {
+		super(formatterContext);
+		out = new ColorfulPrintWriter(formatterContext.getOutputStream());
+		out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
+	}
+
+	@Override
+	public void writeStartResultSet(ColumnsHeader header) {
+		super.writeStartResultSet(header);
+		printResultSeparator();
+	}
+
+	@Override
+	public void writeStartRow() {
+		super.writeStartRow();
+		printRecordSeparator();
+		out.print(ColorfulPrintWriter.TerminalColor.Red, "Record: ");
+		out.print(getCurrentRowCount());
+		println();
+	}
+
+	@Override
+	public void writeColumnValue(Object value) {
+		super.writeColumnValue(value);
+		String columnName = getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel();
+		out.print(ColorfulPrintWriter.TerminalColor.Green, columnName + ": ");
+		Functions.printValueWithWhitespaceReplaced(out, toString(value), null, ColorfulPrintWriter.TerminalColor.Red);
+		println();
+	}
+
+	private static String toString(Object value) {
+		return String.valueOf(value);
+	}
+
+	@Override
+	public void writeUpdatesResult(int updatedRowsCount) {
+		super.writeUpdatesResult(updatedRowsCount);
+		printResultSeparator();
+		out.print(ColorfulPrintWriter.TerminalColor.Red, "Updated records: ");
+		out.println(updatedRowsCount);
+		printBellAndFlush();
+	}
+
+	private void printBellAndFlush() {
+		out.bell();
+		out.flush();
+	}
+
+	private void println() {
+		out.println();
+		printBellAndFlush();
+	}
+
+	private void printRecordSeparator() {
+		if (getCurrentRowCount() > 1) {
+			println();
+		}
+	}
+
+	private void printResultSeparator() {
+		if (firstResult) {
+			firstResult = false;
+		} else {
+			println();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SingleValueFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,52 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import java.io.PrintWriter;
+
+/**
+ * Prints just the value without any formatting. If the result set contains multiple records or
+ * columns, the values are simply concatenate without any separators. If updates result is returned,
+ * the updated records count is printed.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class SingleValueFormatter extends AbstractFormatter {
+
+	public static final String NAME = "single"; // bash-completion:formatter
+	private PrintWriter out;
+
+	public SingleValueFormatter(FormatterContext formatterContext) {
+		super(formatterContext);
+		this.out = new PrintWriter(formatterContext.getOutputStream());
+	}
+
+	@Override
+	public void writeColumnValue(Object value) {
+		super.writeColumnValue(value);
+		out.print(String.valueOf(value));
+		out.flush();
+	}
+
+	@Override
+	public void writeUpdatesResult(int updatedRowsCount) {
+		super.writeUpdatesResult(updatedRowsCount);
+		out.print(updatedRowsCount);
+		out.flush();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,308 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.ColorfulPrintWriter;
+import static info.globalcode.sql.dk.ColorfulPrintWriter.*;
+import info.globalcode.sql.dk.Functions;
+import static info.globalcode.sql.dk.Functions.lpad;
+import static info.globalcode.sql.dk.Functions.rpad;
+import static info.globalcode.sql.dk.Functions.repeat;
+import info.globalcode.sql.dk.configuration.PropertyDeclaration;
+import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
+import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
+import java.sql.SQLException;
+import java.sql.SQLXML;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>
+ * Prints human-readable output – tables of result sets and text messages with update counts.
+ * </p>
+ *
+ * <p>
+ * Longer values might break the table – overflow the cells – see alternative tabular formatters and
+ * the {@linkplain #PROPERTY_TRIM} property.
+ * </p>
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ * @see TabularPrefetchingFormatter
+ * @see TabularWrappingFormatter
+ */
+@PropertyDeclaration(name = COLORFUL, defaultValue = "true", type = Boolean.class, description = COLORFUL_DESCRIPTION)
+@PropertyDeclaration(name = TabularFormatter.PROPERTY_ASCII, defaultValue = "false", type = Boolean.class, description = "whether to use ASCII table borders instead of unicode ones")
+@PropertyDeclaration(name = TabularFormatter.PROPERTY_TRIM, defaultValue = "false", type = Boolean.class, description = "whether to trim the values to fit the column width")
+@PropertyDeclaration(name = TabularFormatter.PROPERTY_HEADER_TYPE, defaultValue = "true", type = Boolean.class, description = "whether to print data types in column headers")
+public class TabularFormatter extends AbstractFormatter {
+
+	private static final Logger log = Logger.getLogger(TabularFormatter.class.getName());
+	public static final String NAME = "tabular"; // bash-completion:formatter
+	private static final String HEADER_TYPE_PREFIX = " (";
+	private static final String HEADER_TYPE_SUFFIX = ")";
+	public static final String PROPERTY_ASCII = "ascii";
+	public static final String PROPERTY_TRIM = "trim";
+	public static final String PROPERTY_HEADER_TYPE = "headerTypes";
+	protected ColorfulPrintWriter out;
+	private boolean firstResult = true;
+	private int[] columnWidth;
+	/**
+	 * use ASCII borders instead of unicode ones
+	 */
+	private final boolean asciiNostalgia;
+	/**
+	 * Trim values if they are longer than cell size
+	 */
+	private final boolean trimValues;
+	/**
+	 * Print data type of each column in the header
+	 */
+	private final boolean printHeaderTypes;
+
+	public TabularFormatter(FormatterContext formatterContext) {
+		super(formatterContext);
+		out = new ColorfulPrintWriter(formatterContext.getOutputStream());
+		asciiNostalgia = formatterContext.getProperties().getBoolean(PROPERTY_ASCII, false);
+		trimValues = formatterContext.getProperties().getBoolean(PROPERTY_TRIM, false);
+		printHeaderTypes = formatterContext.getProperties().getBoolean(PROPERTY_HEADER_TYPE, true);
+		out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
+	}
+
+	@Override
+	public void writeStartResultSet(ColumnsHeader header) {
+		super.writeStartResultSet(header);
+		printResultSeparator();
+
+		initColumnWidths(header.getColumnCount());
+
+		printTableIndent();
+		printTableBorder("╭");
+
+		List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
+
+		for (ColumnDescriptor cd : columnDescriptors) {
+			// padding: make header cell at least same width as data cells in this column
+			int typeWidth = printHeaderTypes ? cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length() : 0;
+			cd.setLabel(rpad(cd.getLabel(), getColumnWidth(cd.getColumnNumber()) - typeWidth));
+			updateColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + typeWidth);
+
+			if (!cd.isFirstColumn()) {
+				printTableBorder("┬");
+			}
+			printTableBorder(repeat('─', getColumnWidth(cd.getColumnNumber()) + 2));
+		}
+		printTableBorder("╮");
+		out.println();
+
+		for (ColumnDescriptor cd : columnDescriptors) {
+			if (cd.isFirstColumn()) {
+				printTableIndent();
+				printTableBorder("│ ");
+			} else {
+				printTableBorder(" │ ");
+			}
+			out.print(TerminalStyle.Bright, cd.getLabel());
+			if (printHeaderTypes) {
+				out.print(HEADER_TYPE_PREFIX);
+				out.print(cd.getTypeName());
+				out.print(HEADER_TYPE_SUFFIX);
+			}
+			if (cd.isLastColumn()) {
+				printTableBorder(" │");
+			}
+		}
+		out.println();
+
+		printTableIndent();
+		printTableBorder("├");
+		for (int i = 1; i <= header.getColumnCount(); i++) {
+			if (i > 1) {
+				printTableBorder("┼");
+			}
+			printTableBorder(repeat('─', getColumnWidth(i) + 2));
+		}
+		printTableBorder("┤");
+		out.println();
+
+		out.flush();
+	}
+
+	/**
+	 * Must be called before {@linkplain #updateColumnWidth(int, int)} and
+	 * {@linkplain #getColumnWidth(int)} for each result set.
+	 *
+	 * @param columnCount number of columns in current result set
+	 */
+	protected void initColumnWidths(int columnCount) {
+		if (columnWidth == null) {
+			columnWidth = new int[columnCount];
+		}
+	}
+
+	protected void cleanColumnWidths() {
+		columnWidth = null;
+	}
+
+	@Override
+	public void writeColumnValue(Object value) {
+		super.writeColumnValue(value);
+		writeColumnValueInternal(value);
+	}
+
+	protected void writeColumnValueInternal(Object value) {
+
+		if (isCurrentColumnFirst()) {
+			printTableIndent();
+			printTableBorder("│ ");
+		} else {
+			printTableBorder(" │ ");
+		}
+
+		printValueWithWhitespaceReplaced(toString(value));
+
+		if (isCurrentColumnLast()) {
+			printTableBorder(" │");
+		}
+
+	}
+
+	protected void printValueWithWhitespaceReplaced(String text) {
+		Functions.printValueWithWhitespaceReplaced(out, text, TerminalColor.Cyan, TerminalColor.Red);
+	}
+
+	protected int getColumnWidth(int columnNumber) {
+		return columnWidth[columnNumber - 1];
+	}
+
+	private void setColumnWidth(int columnNumber, int width) {
+		columnWidth[columnNumber - 1] = width;
+	}
+
+	protected void updateColumnWidth(int columnNumber, int width) {
+		int oldWidth = getColumnWidth(columnNumber);
+		setColumnWidth(columnNumber, Math.max(width, oldWidth));
+
+	}
+
+	protected String toString(Object value) {
+		final int width = getColumnWidth(getCurrentColumnsCount());
+		String result;
+		if (value instanceof Number || value instanceof Boolean) {
+			result = lpad(String.valueOf(value), width);
+		} else {
+			if (value instanceof SQLXML) {
+				// TODO: move to a common method, share with other formatters
+				try {
+					value = ((SQLXML) value).getString();
+				} catch (SQLException e) {
+					log.log(Level.SEVERE, "Unable to format XML", e);
+				}
+			}
+
+			result = rpad(String.valueOf(value), width);
+		}
+		// ?	value = (boolean) value ? "✔" : "✗";
+
+		if (trimValues && result.length() > width) {
+			result = result.substring(0, width - 1) + "…";
+		}
+
+		return result;
+	}
+
+	@Override
+	public void writeEndRow() {
+		super.writeEndRow();
+		writeEndRowInternal();
+	}
+
+	public void writeEndRowInternal() {
+		out.println();
+		out.flush();
+	}
+
+	@Override
+	public void writeEndResultSet() {
+		int columnCount = getCurrentColumnsHeader().getColumnCount();
+		super.writeEndResultSet();
+
+		printTableIndent();
+		printTableBorder("╰");
+		for (int i = 1; i <= columnCount; i++) {
+			if (i > 1) {
+				printTableBorder("┴");
+			}
+			printTableBorder(repeat('─', getColumnWidth(i) + 2));
+		}
+		printTableBorder("╯");
+		out.println();
+
+		cleanColumnWidths();
+
+		out.print(TerminalColor.Yellow, "Record count: ");
+		out.println(getCurrentRowCount());
+		out.bell();
+		out.flush();
+	}
+
+	@Override
+	public void writeUpdatesResult(int updatedRowsCount) {
+		super.writeUpdatesResult(updatedRowsCount);
+		printResultSeparator();
+		out.print(TerminalColor.Red, "Updated records: ");
+		out.println(updatedRowsCount);
+		out.bell();
+		out.flush();
+	}
+
+	@Override
+	public void writeEndDatabase() {
+		super.writeEndDatabase();
+		out.flush();
+	}
+
+	private void printResultSeparator() {
+		if (firstResult) {
+			firstResult = false;
+		} else {
+			out.println();
+		}
+	}
+
+	protected void printTableBorder(String border) {
+		if (asciiNostalgia) {
+			border = border.replaceAll("─", "-");
+			border = border.replaceAll("│", "|");
+			border = border.replaceAll("[╭┬╮├┼┤╰┴╯]", "+");
+		}
+
+		out.print(TerminalColor.Green, border);
+	}
+
+	protected void printTableIndent() {
+		out.print(" ");
+	}
+
+	/**
+	 * @return whether should print only ASCII characters instead of unlimited Unicode.
+	 */
+	protected boolean isAsciiNostalgia() {
+		return asciiNostalgia;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,119 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * Prefetches whole result set and computes column widths. Whole table is flushed at once in
+ * {@linkplain #writeEndResultSet()}.
+ * </p>
+ *
+ * <p>
+ * Long values will not overflow the cells, but whole result set must be loaded into the memory.
+ * </p>
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class TabularPrefetchingFormatter extends TabularFormatter {
+
+	public static final String NAME = "tabular-prefetching"; // bash-completion:formatter
+	private ColumnsHeader currentHeader;
+	private List<Object[]> currentResultSet;
+	private Object[] currentRow;
+	private int currentColumnsCount;
+	private boolean prefetchDone = false;
+
+	public TabularPrefetchingFormatter(FormatterContext formatterContext) {
+		super(formatterContext);
+	}
+
+	@Override
+	protected int getCurrentColumnsCount() {
+		if (prefetchDone) {
+			return super.getCurrentColumnsCount();
+		} else {
+			return currentColumnsCount;
+		}
+	}
+
+	@Override
+	public void writeStartResultSet(ColumnsHeader header) {
+		currentResultSet = new ArrayList<>();
+		currentHeader = header;
+		initColumnWidths(header.getColumnCount());
+	}
+
+	@Override
+	public void writeStartRow() {
+		currentRow = new Object[currentHeader.getColumnCount()];
+		currentResultSet.add(currentRow);
+		currentColumnsCount = 0;
+	}
+
+	@Override
+	public void writeColumnValue(Object value) {
+		currentRow[currentColumnsCount] = value;
+		currentColumnsCount++;
+		String textRepresentation = toString(value);
+		/** TODO: count only printable characters (currently not an issue) */
+		updateColumnWidth(currentColumnsCount, textRepresentation.length());
+	}
+
+	@Override
+	public void writeEndRow() {
+		// do nothing
+	}
+
+	@Override
+	public void writeEndResultSet() {
+		prefetchDone = true;
+
+		postprocessPrefetchedResultSet(currentHeader, currentResultSet);
+
+		super.writeStartResultSet(currentHeader);
+
+		for (Object[] row : currentResultSet) {
+			super.writeStartRow();
+			for (Object cell : row) {
+				super.writeColumnValue(cell);
+			}
+			super.writeEndRow();
+		}
+
+		currentColumnsCount = 0;
+		currentHeader = null;
+		currentRow = null;
+		currentResultSet = null;
+		super.writeEndResultSet();
+		prefetchDone = false;
+	}
+
+	/**
+	 * Optional post-processing – override in sub-classes if needed.
+	 * Don't forget to {@linkplain #updateColumnWidth(int, int)}
+	 *
+	 * @param currentHeader
+	 * @param currentResultSet
+	 */
+	protected void postprocessPrefetchedResultSet(ColumnsHeader currentHeader, List<Object[]> currentResultSet) {
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularWrappingFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,116 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
+import java.util.ArrayList;
+import java.util.List;
+import static info.globalcode.sql.dk.Functions.lpad;
+import static info.globalcode.sql.dk.Functions.rpad;
+import static info.globalcode.sql.dk.Functions.repeat;
+
+/**
+ * Longer values are line-wrapped – the cell then contains multiple lines. Marks are added to
+ * signalize forced line ends (not present in original data).
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class TabularWrappingFormatter extends TabularFormatter {
+
+	public static final String NAME = "tabular-wrapping"; // bash-completion:formatter
+	private List<String[]> currentRow;
+
+	public TabularWrappingFormatter(FormatterContext formatterContext) {
+		super(formatterContext);
+	}
+
+	@Override
+	public void writeStartResultSet(ColumnsHeader header) {
+		super.writeStartResultSet(header);
+		currentRow = new ArrayList<>(header.getColumnCount());
+	}
+
+	@Override
+	protected void writeColumnValueInternal(Object value) {
+		boolean rightAlign = value instanceof Number || value instanceof Boolean;
+		String valueString = String.valueOf(value);
+		int columnWidth = getColumnWidth(getCurrentColumnsCount()) - 1;  // -1 = space for new line symbol
+		currentRow.add(wrapLines(valueString, columnWidth, rightAlign));
+	}
+
+	@Override
+	public void writeEndRow() {
+		super.writeEndRow();
+
+		int wrappedLine = 0;
+		boolean hasMoreWrappedLines;
+
+		do {
+			hasMoreWrappedLines = false;
+			for (int i = 0; i < currentRow.size(); i++) {
+				if (i == 0) {
+					printTableIndent();
+					printTableBorder("│ ");
+				} else {
+					printTableBorder(" │ ");
+				}
+				String[] columnArray = currentRow.get(i);
+				if (wrappedLine < columnArray.length) {
+					printValueWithWhitespaceReplaced(columnArray[wrappedLine]);
+
+					if (wrappedLine < columnArray.length - 1) {
+						out.print(TerminalColor.Red, "↩");
+						hasMoreWrappedLines = true;
+					} else {
+						out.print(" ");
+					}
+
+				} else {
+					out.print(repeat(' ', getColumnWidth(i + 1)));
+				}
+
+				if (i == (currentRow.size() - 1)) {
+					printTableBorder(" │");
+				}
+			}
+			out.println();
+			out.flush();
+			wrappedLine++;
+		} while (hasMoreWrappedLines);
+
+		currentRow.clear();
+	}
+
+	@Override
+	public void writeEndRowInternal() {
+		// already done – wrapped row ends
+	}
+
+	private static String[] wrapLines(String s, int width, boolean rightAlign) {
+		String[] array = new String[(s.length() - 1) / width + 1];
+		for (int i = 0; i < array.length; i++) {
+			if (i == array.length - 1) {
+				String part = s.substring(i * width, s.length());
+				array[i] = rightAlign ? lpad(part, width) : rpad(part, width);
+			} else {
+				array[i] = s.substring(i * width, (i + 1) * width);
+			}
+		}
+		return array;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TeXFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,208 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.ColorfulPrintWriter;
+import info.globalcode.sql.dk.Constants;
+import info.globalcode.sql.dk.configuration.PropertyDeclaration;
+import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL;
+import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Outputs result sets in (La)TeX format.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+@PropertyDeclaration(name = COLORFUL, defaultValue = "false", type = Boolean.class, description = COLORFUL_DESCRIPTION)
+public class TeXFormatter extends AbstractFormatter {
+
+	public static final String NAME = "tex"; // bash-completion:formatter
+	private static final ColorfulPrintWriter.TerminalColor COMMAND_COLOR = ColorfulPrintWriter.TerminalColor.Magenta;
+	private static final ColorfulPrintWriter.TerminalColor OPTIONS_COLOR = ColorfulPrintWriter.TerminalColor.Yellow;
+	private static final Map<Character, String> TEX_ESCAPE_MAP;
+	private final ColorfulPrintWriter out;
+
+	static {
+		Map<Character, String> replacements = new HashMap<>();
+
+		replacements.put('\\', "\\textbackslash{}");
+		replacements.put('{', "\\{{}");
+		replacements.put('}', "\\}{}");
+		replacements.put('_', "\\_{}");
+		replacements.put('^', "\\textasciicircum{}");
+		replacements.put('#', "\\#{}");
+		replacements.put('&', "\\&{}");
+		replacements.put('$', "\\${}");
+		replacements.put('%', "\\%{}");
+		replacements.put('~', "\\textasciitilde{}");
+		replacements.put('-', "{-}");
+
+		TEX_ESCAPE_MAP = Collections.unmodifiableMap(replacements);
+	}
+
+	public TeXFormatter(FormatterContext formatterContext) {
+		super(formatterContext);
+		boolean colorful = formatterContext.getProperties().getBoolean(COLORFUL, false);
+		out = new ColorfulPrintWriter(formatterContext.getOutputStream(), false, colorful);
+	}
+
+	@Override
+	public void writeStartBatch() {
+		super.writeStartBatch();
+
+		printCommand("documentclass", "a4paper,twoside", "article", true);
+		printCommand("usepackage", "T1", "fontenc", true);
+		printCommand("usepackage", "utf8x", "inputenc", true);
+		printCommand("usepackage", "pdfauthor={" + Constants.WEBSITE + "}, bookmarks=true,unicode,colorlinks=true,linkcolor=black,urlcolor=blue,citecolor=blue", "hyperref", true);
+		printBegin("document");
+	}
+
+	@Override
+	public void writeEndBatch() {
+		super.writeEndBatch();
+		printEnd("document");
+	}
+
+	@Override
+	public void writeColumnValue(Object value) {
+		super.writeColumnValue(value);
+		// TODO: arrays, numbers, booleans, nulls etc.:
+		out.print(escapeTex(toString(value)));
+
+		if (!isCurrentColumnLast()) {
+			printColumnSeparator();
+		}
+	}
+
+	@Override
+	public void writeEndRow() {
+		super.writeEndRow();
+		printEndRow();
+	}
+
+	@Override
+	public void writeStartResultSet(ColumnsHeader header) {
+		super.writeStartResultSet(header);
+		printCommand("begin", null, "tabular", false);
+
+		List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
+
+		StringBuilder columnAlignments = new StringBuilder();
+		for (ColumnDescriptor cd : columnDescriptors) {
+			if (cd.isNumeric() || cd.isBoolean()) {
+				columnAlignments.append('r');
+			} else {
+				columnAlignments.append('l');
+			}
+		}
+
+		printCommand(null, null, columnAlignments.toString(), true);
+		printCommand("hline", null, null, true);
+
+		for (ColumnDescriptor cd : columnDescriptors) {
+			printCommand("textbf", null, cd.getLabel(), false);
+			if (cd.isLastColumn()) {
+				printEndRow();
+			} else {
+				printColumnSeparator();
+			}
+		}
+
+		printCommand("hline", null, null, true);
+	}
+
+	@Override
+	public void writeEndResultSet() {
+		super.writeEndResultSet();
+		printCommand("hline", null, null, true);
+		printEnd("tabular");
+	}
+
+	private String escapeTex(String text) {
+		if (text == null) {
+			return null;
+		} else {
+			StringBuilder result = new StringBuilder(text.length() * 2);
+
+			for (char ch : text.toCharArray()) {
+				String replacement = TEX_ESCAPE_MAP.get(ch);
+				result.append(replacement == null ? ch : replacement);
+			}
+
+			return result.toString();
+		}
+	}
+
+	protected String toString(Object value) {
+		return String.valueOf(value);
+	}
+
+	private void printColumnSeparator() {
+		out.print(COMMAND_COLOR, " & ");
+	}
+
+	private void printEndRow() {
+		out.println(COMMAND_COLOR, " \\\\");
+		out.flush();
+	}
+
+	/**
+	 *
+	 * @param command will not be escaped – should contain just a valid TeX command name
+	 * @param options will not be escaped – should be properly formatted to be printed inside [
+	 * and ]
+	 * @param value will be escaped
+	 * @param println whether to print line end and flush
+	 */
+	private void printCommand(String command, String options, String value, boolean println) {
+
+		if (command != null) {
+			out.print(COMMAND_COLOR, "\\" + command);
+		}
+
+		if (options != null) {
+			out.print(COMMAND_COLOR, "[");
+			out.print(OPTIONS_COLOR, options);
+			out.print(COMMAND_COLOR, "]");
+		}
+
+		if (value != null) {
+			out.print(COMMAND_COLOR, "{");
+			out.print(escapeTex(value));
+			out.print(COMMAND_COLOR, "}");
+		}
+
+		if (println) {
+			out.println();
+			out.flush();
+		}
+	}
+
+	private void printBegin(String environment) {
+		printCommand("begin", null, environment, true);
+	}
+
+	private void printEnd(String environment) {
+		printCommand("end", null, environment, true);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XhtmlFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,262 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.Constants;
+import info.globalcode.sql.dk.NamedParameter;
+import info.globalcode.sql.dk.Parameter;
+import info.globalcode.sql.dk.Xmlns;
+import info.globalcode.sql.dk.configuration.DatabaseDefinition;
+import info.globalcode.sql.dk.configuration.Properties;
+import info.globalcode.sql.dk.configuration.Property;
+import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
+import java.sql.Array;
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.namespace.QName;
+
+/**
+ * Prints result sets and parameters as tables, SQL as preformatted and updates counts as
+ * paragraphs. You can pick XHTML fragments (usually tabular data) and use it on your website or use
+ * whole output as preview or report.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class XhtmlFormatter extends AbstractXmlFormatter {
+
+	private static final Logger log = Logger.getLogger(XhtmlFormatter.class.getName());
+	public static final String NAME = "xhtml"; // bash-completion:formatter
+	private static final String DOCTYPE = "html PUBLIC \"-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN\" \"http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd\"";
+	private static final String CSS_FILE = "info/globalcode/sql/dk/formatter/XhtmlFormatter.css";
+	private int statementCounter = 0;
+	private int resultSetCounter = 0;
+	private int updatesResultCounter = 0;
+
+	public XhtmlFormatter(FormatterContext formatterContext) {
+		super(addDefaults(formatterContext));
+	}
+
+	/**
+	 * Do not indent text – preserve whitespace for pre elements
+	 */
+	private static FormatterContext addDefaults(FormatterContext formatterContext) {
+		Properties defaults = new Properties(1);
+		defaults.add(new Property(PROPERTY_INDENT_TEXT, "false"));
+		formatterContext.getProperties().setLastDefaults(defaults);
+		return formatterContext;
+	}
+
+	@Override
+	public void writeStartBatch() {
+		super.writeStartBatch();
+		printStartDocument();
+		printDoctype(DOCTYPE);
+		printStartElement(qname("html"), singleAttribute(qname("xmlns"), Xmlns.XHTML));
+
+		printStartElement(qname("head"));
+		printTextElement(qname("title"), null, Constants.PROGRAM_NAME + ": batch results");
+		printCss();
+		printEndElement();
+
+		printStartElement(qname("body"));
+	}
+
+	private void printCss() {
+
+		try (Scanner css = new Scanner(getClass().getClassLoader().getResourceAsStream(CSS_FILE))) {
+			printStartElement(qname("style"), singleAttribute(qname("type"), "text/css"));
+			while (css.hasNext()) {
+				printText(css.nextLine(), true);
+			}
+			printEndElement();
+		}
+	}
+
+	@Override
+	public void writeEndBatch() {
+		super.writeEndBatch();
+		printEndElement();
+		printEndElement();
+		printEndDocument();
+	}
+
+	@Override
+	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
+		super.writeStartDatabase(databaseDefinition);
+		printTextElement(qname("h1"), null, "Database: " + databaseDefinition.getName());
+
+		printStartElement(qname("p"));
+		printText("This is XHTML output of batch executed at: ", true);
+		printText(new Date().toString(), true);
+		printEndElement();
+	}
+
+	@Override
+	public void writeQuery(String sql) {
+		super.writeQuery(sql);
+		printTextElement(qname("pre"), null, sql);
+	}
+
+	@Override
+	public void writeParameters(List<? extends Parameter> parameters) {
+		super.writeParameters(parameters);
+
+		if (parameters == null || parameters.isEmpty()) {
+			printTextElement(qname("p"), null, "(this query has no parameters)");
+		} else {
+			printTextElement(qname("h3"), null, "Parameters:");
+
+			printStartElement(qname("table"));
+
+			printStartElement(qname("thead"));
+			printStartElement(qname("tr"));
+			printTextElement(qname("td"), null, "id");
+			printTextElement(qname("td"), null, "type");
+			printTextElement(qname("td"), null, "value");
+			printEndElement();
+			printEndElement();
+
+			printStartElement(qname("tbody"));
+			for (int i = 0; i < parameters.size(); i++) {
+				Parameter p = parameters.get(i);
+				printStartElement(qname("tr"));
+				String numberOrName;
+				if (p instanceof NamedParameter) {
+					numberOrName = ((NamedParameter) p).getName();
+				} else {
+					numberOrName = String.valueOf(i + 1);
+				}
+				printTextElement(qname("td"), null, numberOrName);
+				printTextElement(qname("td"), null, p.getType().name());
+				printTableData(p.getValue());
+				printEndElement();
+			}
+			printEndElement();
+
+			printEndElement();
+		}
+	}
+
+	private void printTableData(Object value) {
+
+		if (value instanceof Array) {
+			Array sqlArray = (Array) value;
+			try {
+				Object[] array = (Object[]) sqlArray.getArray();
+				printStartElement(qname("td"));
+				printArray(array);
+				printEndElement();
+			} catch (SQLException e) {
+				log.log(Level.SEVERE, "Unable to format array", e);
+				printTableData(String.valueOf(value));
+			}
+		} else {
+			Map<QName, String> attributes = null;
+			if (value instanceof Number) {
+				attributes = singleAttribute(qname("class"), "number");
+			} else if (value instanceof Boolean) {
+				attributes = singleAttribute(qname("class"), "boolean");
+			}
+			printTextElement(qname("td"), attributes, String.valueOf(value));
+		}
+	}
+
+	private void printArray(Object[] array) {
+		printStartElement(qname("ul"));
+		for (Object o : array) {
+			if (o instanceof Object[]) {
+				printStartElement(qname("li"));
+				printTextElement(qname("p"), null, "nested array:");
+				printArray((Object[]) o);
+				printEndElement();
+			} else {
+				printTextElement(qname("li"), null, String.valueOf(o));
+			}
+		}
+		printEndElement();
+	}
+
+	@Override
+	public void writeStartResultSet(ColumnsHeader header) {
+		super.writeStartResultSet(header);
+		resultSetCounter++;
+		printEmptyElement(qname("hr"), null);
+		printTextElement(qname("h3"), null, "Result set #" + resultSetCounter);
+		printStartElement(qname("table"));
+		printStartElement(qname("thead"));
+		printStartElement(qname("tr"));
+		for (ColumnDescriptor cd : header.getColumnDescriptors()) {
+			// TODO: type
+			printTextElement(qname("td"), null, cd.getLabel());
+		}
+		printEndElement();
+		printEndElement();
+
+		printStartElement(qname("tbody"));
+	}
+
+	@Override
+	public void writeEndResultSet() {
+		super.writeEndResultSet();
+		printEndElement();
+		printEndElement();
+		printTextElement(qname("p"), null, "Record count: " + getCurrentRowCount());
+	}
+
+	@Override
+	public void writeStartRow() {
+		super.writeStartRow();
+		printStartElement(qname("tr"));
+	}
+
+	@Override
+	public void writeColumnValue(Object value) {
+		super.writeColumnValue(value);
+		printTableData(value);
+	}
+
+	@Override
+	public void writeEndRow() {
+		super.writeEndRow();
+		printEndElement();
+	}
+
+	@Override
+	public void writeStartStatement() {
+		super.writeStartStatement();
+		statementCounter++;
+		printEmptyElement(qname("hr"), null);
+		printTextElement(qname("h2"), null, "SQL statement #" + statementCounter);
+		resultSetCounter = 0;
+		updatesResultCounter = 0;
+	}
+
+	@Override
+	public void writeUpdatesResult(int updatedRowsCount) {
+		super.writeUpdatesResult(updatedRowsCount);
+		updatesResultCounter++;
+		printEmptyElement(qname("hr"), null);
+		printTextElement(qname("h3"), null, "Updates result #" + updatesResultCounter);
+		printTextElement(qname("p"), null, "Updated rows: " + updatedRowsCount);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XmlFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,245 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.formatting;
+
+import info.globalcode.sql.dk.Parameter;
+import info.globalcode.sql.dk.Xmlns;
+import info.globalcode.sql.dk.configuration.DatabaseDefinition;
+import static info.globalcode.sql.dk.Functions.notNull;
+import info.globalcode.sql.dk.NamedParameter;
+import info.globalcode.sql.dk.configuration.PropertyDeclaration;
+import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
+import java.sql.Array;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLXML;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.namespace.QName;
+
+/**
+ * <p>
+ * Prints machine-readable output – XML document containing resultsets and updates count. Good
+ * choice for further processing – e.g. XSL transformation.</p>
+ *
+ * <p>
+ * TODO: XSD</p>
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+@PropertyDeclaration(name = XmlFormatter.PROPERTY_LABELED_COLUMNS, defaultValue = "false", type = Boolean.class, description = "whether to add 'label' attribute to each 'column' element")
+public class XmlFormatter extends AbstractXmlFormatter {
+
+	public static final String NAME = "xml"; // bash-completion:formatter
+	public static final String PROPERTY_LABELED_COLUMNS = "labeledColumns";
+	private static final Logger log = Logger.getLogger(XmlFormatter.class.getName());
+	private final boolean labeledColumns;
+
+	public XmlFormatter(FormatterContext formatterContext) {
+		super(formatterContext);
+		labeledColumns = formatterContext.getProperties().getBoolean(PROPERTY_LABELED_COLUMNS, false);
+	}
+
+	@Override
+	public void writeStartBatch() {
+		super.writeStartBatch();
+		printStartDocument();
+		printStartElement(qname("batchResult"), singleAttribute(qname("xmlns"), Xmlns.BATCH_RESULT));
+	}
+
+	@Override
+	public void writeEndBatch() {
+		super.writeEndBatch();
+		printEndElement();
+		printEndDocument();
+	}
+
+	@Override
+	public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
+		super.writeStartDatabase(databaseDefinition);
+		Map<QName, String> attributes = databaseDefinition.getName() == null ? null : singleAttribute(qname("name"), databaseDefinition.getName());
+		printStartElement(qname("database"), attributes);
+	}
+
+	@Override
+	public void writeEndDatabase() {
+		super.writeEndDatabase();
+		printEndElement();
+	}
+
+	@Override
+	public void writeStartStatement() {
+		super.writeStartStatement();
+		printStartElement(qname("statement"));
+	}
+
+	@Override
+	public void writeEndStatement() {
+		super.writeEndStatement();
+		printEndElement();
+	}
+
+	@Override
+	public void writeQuery(String sql) {
+		super.writeQuery(sql);
+		printTextElement(qname("sql"), null, sql);
+	}
+
+	@Override
+	public void writeParameters(List<? extends Parameter> parameters) {
+		super.writeParameters(parameters);
+
+		for (Parameter p : notNull(parameters)) {
+
+			Map<QName, String> attributes = new LinkedHashMap<>(2);
+			if (p instanceof NamedParameter) {
+				attributes.put(qname("name"), ((NamedParameter) p).getName());
+			}
+			attributes.put(qname("type"), p.getType().name());
+
+			printTextElement(qname("parameter"), attributes, String.valueOf(p.getValue()));
+		}
+
+	}
+
+	@Override
+	public void writeStartResultSet(ColumnsHeader header) {
+		super.writeStartResultSet(header);
+		printStartElement(qname("resultSet"));
+
+		for (ColumnDescriptor cd : header.getColumnDescriptors()) {
+			Map<QName, String> attributes = new LinkedHashMap<>(4);
+			attributes.put(qname("label"), cd.getLabel());
+			attributes.put(qname("name"), cd.getName());
+			attributes.put(qname("typeName"), cd.getTypeName());
+			attributes.put(qname("type"), String.valueOf(cd.getType()));
+			printEmptyElement(qname("columnHeader"), attributes);
+		}
+	}
+
+	@Override
+	public void writeEndResultSet() {
+		super.writeEndResultSet();
+		printEndElement();
+	}
+
+	@Override
+	public void writeStartRow() {
+		super.writeStartRow();
+		printStartElement(qname("row"));
+	}
+
+	@Override
+	public void writeColumnValue(Object value) {
+		super.writeColumnValue(value);
+
+		Map<QName, String> attributes = null;
+		if (labeledColumns) {
+			attributes = new LinkedHashMap<>(2);
+			attributes.put(qname("label"), getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel());
+		}
+
+		if (value == null) {
+			if (attributes == null) {
+				attributes = new LinkedHashMap<>(2);
+			}
+			attributes.put(qname("null"), "true");
+			printEmptyElement(qname("column"), attributes);
+		} else if (value instanceof Array) {
+
+			Array sqlArray = (Array) value;
+			try {
+				Object[] array = (Object[]) sqlArray.getArray();
+				printStartElement(qname("column"), attributes);
+				printArray(array);
+				printEndElement();
+			} catch (SQLException e) {
+				// FIXME: rewrite array formatting, remember array mode, don't try sqlArray.getArray() again and again if it has failed
+				log.log(Level.SEVERE, "Unable to format array", e);
+				try {
+					ResultSet arrayResultSet = sqlArray.getResultSet();
+					//int columnCount = arrayResultSet.getMetaData().getColumnCount();
+					ArrayList<Object> arrayList = new ArrayList<>();
+					while (arrayResultSet.next()) {
+						arrayList.add(arrayResultSet.getObject(2));
+						// for (int i = 1; i <= columnCount; i++) {
+						// 	log.log(Level.INFO, "Array column {0} = {1}", new Object[]{i, arrayResultSet.getObject(i)});
+						// }
+					}
+
+					printStartElement(qname("column"), attributes);
+					// FIXME: instanceof SQLXML, see below
+					printArray(arrayList.toArray());
+					printEndElement();
+
+				} catch (SQLException e2) {
+					// FIXME: fix logging, error recovery
+					log.log(Level.SEVERE, "Second level fuck up !!!", e2);
+				}
+
+				writeColumnValue(String.valueOf(value));
+			}
+
+		} else if (value instanceof SQLXML) { // FIXME: move to separate method, to AbstractFormatter?
+			SQLXML xml = (SQLXML) value;
+			// TODO: parse DOM/SAX and transplant XML, don't escape (optional)
+			try {
+				printTextElement(qname("column"), attributes, xml.getString());
+			} catch (SQLException e) {
+				log.log(Level.SEVERE, "Unable to format XML", e);
+				writeColumnValue(String.valueOf(value));
+			}
+		} else {
+			printTextElement(qname("column"), attributes, toString(value));
+		}
+	}
+
+	private void printArray(Object[] array) {
+		printStartElement(qname("array"));
+		for (Object o : array) {
+			if (o instanceof Object[]) {
+				printStartElement(qname("item"));
+				printArray((Object[]) o);
+				printEndElement();
+			} else {
+				printTextElement(qname("item"), null, String.valueOf(o));
+			}
+		}
+		printEndElement();
+	}
+
+	@Override
+	public void writeEndRow() {
+		super.writeEndRow();
+		printEndElement();
+	}
+
+	@Override
+	public void writeUpdatesResult(int updatedRowsCount) {
+		super.writeUpdatesResult(updatedRowsCount);
+		printTextElement(qname("updatedRows"), null, String.valueOf(updatedRowsCount));
+	}
+
+	protected String toString(Object value) {
+		return String.valueOf(value);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ConnectionManagement.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,97 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.jmx;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * JMX management bean for progress reporting.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class ConnectionManagement implements ConnectionManagementMBean {
+
+	private final String databaseName;
+	private final Map<COUNTER, Integer> counters = new EnumMap(COUNTER.class);
+
+	public ConnectionManagement(String databaseName) {
+		this.databaseName = databaseName;
+		for (COUNTER c : COUNTER.values()) {
+			counters.put(c, 0);
+		}
+	}
+
+	public enum COUNTER {
+
+		COMMAND,
+		RECORD_CURRENT,
+		RECORD_TOTAL
+	};
+
+	public void incrementCounter(COUNTER counter) {
+		synchronized (counters) {
+			int old = counters.get(counter);
+			counters.put(counter, old + 1);
+		}
+	}
+
+	public void resetCounter(COUNTER counter) {
+		synchronized (counters) {
+			counters.put(counter, 0);
+		}
+	}
+
+	public static void incrementCounter(ConnectionManagement mbean, COUNTER counter) {
+		if (mbean != null) {
+			mbean.incrementCounter(counter);
+		}
+	}
+
+	public static void resetCounter(ConnectionManagement mbean, COUNTER counter) {
+		if (mbean != null) {
+			mbean.resetCounter(counter);
+		}
+	}
+
+	@Override
+	public String getDatabaseName() {
+		return databaseName;
+	}
+
+	@Override
+	public int getCommandCount() {
+		synchronized (counters) {
+			return counters.get(COUNTER.COMMAND);
+		}
+	}
+
+	@Override
+	public int getCurrentRecordCount() {
+		synchronized (counters) {
+			return counters.get(COUNTER.RECORD_CURRENT);
+		}
+	}
+	
+	@Override
+	public int getTotalRecordCount() {
+		synchronized (counters) {
+			return counters.get(COUNTER.RECORD_TOTAL);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,34 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.jmx;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public interface ConnectionManagementMBean {
+
+	public String getDatabaseName();
+
+	public int getCommandCount();
+
+	public int getCurrentRecordCount();
+
+	public int getTotalRecordCount();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ManagementUtils.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,68 @@
+/**
+ * SQL-DK
+ * Copyright © 2014 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.jmx;
+
+import java.lang.management.ManagementFactory;
+import java.util.Hashtable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class ManagementUtils {
+
+	private static final Logger log = Logger.getLogger(ManagementUtils.class.getName());
+	public static final String DEFAULT_CONNECTION_JMX_NAME = "main";
+
+	/**
+	 * @see #registerMBean(java.lang.String, java.lang.String) with default JMX name
+	 */
+	public static ConnectionManagement registerMBean(String dbName) {
+		return registerMBean(dbName, DEFAULT_CONNECTION_JMX_NAME);
+	}
+
+	/**
+	 *
+	 * @param dbName database name
+	 * @param jmxName name of JMX bean
+	 * @return registered JMX bean | or null if registration fails (should not)
+	 */
+	public static ConnectionManagement registerMBean(String dbName, String jmxName) {
+		try {
+			ConnectionManagement mbean = new ConnectionManagement(dbName);
+			MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+			Hashtable<String, String> objectProperties = new Hashtable<>();
+			objectProperties.put("type", "Connection");
+			objectProperties.put("name", jmxName);
+			ObjectName objectName = new ObjectName("info.globalcode.sql.dk", objectProperties);
+			mbs.registerMBean(mbean, objectName);
+			log.log(Level.FINE, "JMX MBean was registered as: {0}", objectName);
+			return mbean;
+		} catch (Exception e) {
+			log.log(Level.WARNING, "Unable to register JMX MBean", e);
+			return null;
+		}
+	}
+
+	private ManagementUtils() {
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/ColorfulConsoleFormatter.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,97 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.logging;
+
+import info.globalcode.sql.dk.ColorfulPrintWriter;
+import static info.globalcode.sql.dk.ColorfulPrintWriter.TerminalColor;
+import static info.globalcode.sql.dk.ColorfulPrintWriter.TerminalStyle;
+import static info.globalcode.sql.dk.Functions.rpad;
+import java.io.StringWriter;
+import java.util.logging.Formatter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/**
+ * For console/terminal log output. Log messages are printed in brief and colorful form.
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class ColorfulConsoleFormatter extends Formatter {
+
+	private boolean printStacktrace = false;
+
+	@Override
+	public String format(LogRecord r) {
+		StringWriter sw = new StringWriter();
+		try (ColorfulPrintWriter out = new ColorfulPrintWriter(sw)) {
+			printLevel(out, r.getLevel());
+			printMessage(out, r);
+			printThrowable(out, r);
+			out.println();
+		}
+		return sw.toString();
+	}
+
+	private void printLevel(ColorfulPrintWriter out, Level l) {
+		TerminalColor color = TerminalColor.Magenta;
+
+		if (l == Level.SEVERE) {
+			color = TerminalColor.Red;
+		} else if (l == Level.WARNING) {
+			color = TerminalColor.Yellow;
+		}
+
+		out.print(color, rpad(l.getLocalizedName() + ": ", 10));
+	}
+
+	private void printMessage(ColorfulPrintWriter out, LogRecord r) {
+		out.print(formatMessage(r));
+	}
+
+	private void printThrowable(ColorfulPrintWriter out, LogRecord r) {
+		Throwable t = r.getThrown();
+		if (t != null) {
+			out.print(": ");
+			out.print(TerminalColor.Red, t.getClass().getSimpleName());
+			String message = t.getLocalizedMessage();
+			if (message != null) {
+				out.print(": ");
+				if (printStacktrace) {
+					out.print(message);
+				} else {
+					out.print(message.replaceAll("\\n", " "));
+				}
+			}
+			if (printStacktrace) {
+				out.println();
+				out.setForegroundColor(TerminalColor.Yellow);
+				out.setStyle(TerminalStyle.Dim);
+				t.printStackTrace(out);
+				out.resetAll();
+			}
+		}
+	}
+
+	public boolean isPrintStacktrace() {
+		return printStacktrace;
+	}
+
+	public void setPrintStacktrace(boolean printStacktrace) {
+		this.printStacktrace = printStacktrace;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/LoggerInitializer.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,78 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.logging;
+
+import info.globalcode.sql.dk.Constants;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Configures logging subsystem.
+ * Usage: java -Djava.util.logging.config.class=info.globalcode.sql.dk.logging.LoggerInitializer …
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class LoggerInitializer {
+
+	private static final Logger log = Logger.getLogger(LoggerInitializer.class.getName());
+	public static final String LEVEL_PROPERTY = LoggerInitializer.class.getName() + ".level";
+	private static final Level DEFAULT_LEVEL = Level.INFO;
+
+	public LoggerInitializer() {
+		Logger logger = Logger.getLogger(Constants.JAVA_PACKAGE);
+		ConsoleHandler handler = new ConsoleHandler();
+		ColorfulConsoleFormatter formatter = new ColorfulConsoleFormatter();
+
+		logger.addHandler(handler);
+		handler.setFormatter(formatter);
+
+		setLevel(logger, handler, formatter);
+
+
+		/**
+		 * TODO: optional FileHandler – detailed logs in file in ~/sql-dk/log/…
+		 */
+	}
+
+	private void setLevel(Logger logger, Handler handler, ColorfulConsoleFormatter formatter) {
+		boolean levelParseError = false;
+		Level level;
+		String cliLevel = System.getProperty(LEVEL_PROPERTY);
+		if (cliLevel == null) {
+			level = DEFAULT_LEVEL;
+		} else {
+			try {
+				level = Level.parse(cliLevel);
+			} catch (IllegalArgumentException e) {
+				level = DEFAULT_LEVEL;
+				levelParseError = true;
+			}
+		}
+
+		handler.setLevel(level);
+		logger.setLevel(level);
+
+		if (levelParseError) {
+			log.log(Level.WARNING, "Invalid logging level „{0}“ specified in „{1}“ → using default level „{2}“", new Object[]{cliLevel, LEVEL_PROPERTY, DEFAULT_LEVEL});
+		}
+
+		formatter.setPrintStacktrace(level.intValue() < Level.INFO.intValue());
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/LoggerProducer.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,36 @@
+/**
+ * SQL-DK
+ * Copyright © 2015 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk.logging;
+
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class LoggerProducer {
+
+	/**
+	 * @return created logger for the caller class
+	 */
+	public static Logger getLogger() {
+		String className = Thread.currentThread().getStackTrace()[2].getClassName();
+		return Logger.getLogger(className);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/resources/info/globalcode/sql/dk/configuration/jaxb.index	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,1 @@
+Configuration
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/resources/info/globalcode/sql/dk/example-config.xml	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,1 @@
+../../../../../../../../../xml/config.xml
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/resources/info/globalcode/sql/dk/formatter/XhtmlFormatter.css	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,54 @@
+body {
+	font-family: sans-serif;
+	font-size: 16px;
+	padding-left: 16px;
+	padding-right: 16px;
+}
+
+pre {
+	background-color: #ddd;
+	padding: 6px;
+	border-radius: 4px;
+	overflow: auto;
+
+	-moz-tab-size: 4;
+	-o-tab-size: 4;
+	tab-size: 4;
+}
+
+table {
+	border-collapse:collapse;
+	box-shadow: 3px 3px 3px grey;
+	margin-top: 10px;
+	margin-bottom: 20px;
+}
+td, th {
+	border: 1px solid black;
+	padding-top: 4px;
+	padding-bottom: 4px;
+	padding-left: 6px;
+	padding-right: 6px;
+	font-weight: normal;
+}
+td.number {
+	text-align: right;
+}
+td.boolean {
+	text-align: right;
+}
+thead tr {
+	background: #ddd;
+	color:black;
+}
+tbody tr:hover {
+	background-color: #eee;
+	color:black;
+}
+
+table ul {
+	margin: 0px;
+}
+
+table li {
+	padding-right: 10px;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/main/resources/info/globalcode/sql/dk/license.txt	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,1 @@
+../../../../../../../../../license/gpl.txt
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/test/java/info/globalcode/sql/dk/CLIParserTest.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,195 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import info.globalcode.sql.dk.CLIParser.Tokens;
+import static info.globalcode.sql.dk.CLIParser.TYPE_NAME_SEPARATOR;
+import info.globalcode.sql.dk.InfoLister.InfoType;
+import java.io.ByteArrayInputStream;
+import java.util.Collection;
+import static org.testng.Assert.*;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class CLIParserTest {
+
+	private static final String DATABASE_NAME_1 = "some database 1";
+	private static final String SQL_1 = "SELECT * FROM table1";
+	private static final String DATA_1 = "aaa";
+	private static final String DATA_2 = "bbb";
+	private static final String DATA_3 = "ccc";
+	private static final String NAME_1 = "param1";
+	private static final String NAME_2 = "param2";
+	private static final String NAME_3 = "param3";
+	private CLIParser parser;
+
+	@BeforeMethod
+	public void setUpMethod() throws Exception {
+		parser = new CLIParser();
+	}
+
+	private CLIOptions parseOptions(String[] args) throws CLIParserException {
+		return parser.parseOptions(args, new ByteArrayInputStream("".getBytes()));
+	}
+
+	@Test
+	public void testParseOptions_QueryNow_NoParams() throws InvalidOptionsException, CLIParserException {
+		String[] args = new String[]{
+			Tokens.DB, DATABASE_NAME_1,
+			Tokens.SQL, SQL_1};
+		CLIOptions options = parseOptions(args);
+		options.validate();
+
+		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
+		assertEquals(options.getSql(), SQL_1);
+		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
+		assertTrue(options.getNamedParameters().isEmpty(), "Named parameters should be empty.");
+		assertTrue(options.getNumberedParameters().isEmpty(), "Numbered parameters should be empty.");
+	}
+
+	@Test
+	public void testParseOptions_QueryNow_Numbered() throws InvalidOptionsException, CLIParserException {
+		String[] args = new String[]{
+			Tokens.DB, DATABASE_NAME_1,
+			Tokens.SQL, SQL_1,
+			Tokens.DATA, DATA_1, DATA_2, DATA_3};
+		CLIOptions options = parseOptions(args);
+		options.validate();
+
+		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
+		assertEquals(options.getSql(), SQL_1);
+		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
+		assertEquals(options.getNumberedParameters().size(), 3);
+		assertEquals(options.getNumberedParameters().get(0).getValue(), DATA_1);
+		assertEquals(options.getNumberedParameters().get(1).getValue(), DATA_2);
+		assertEquals(options.getNumberedParameters().get(2).getValue(), DATA_3);
+		assertEquals(options.getNumberedParameters().get(0).getType(), Parameter.DEFAULT_TYPE);
+		assertEquals(options.getNumberedParameters().get(1).getType(), Parameter.DEFAULT_TYPE);
+		assertEquals(options.getNumberedParameters().get(2).getType(), Parameter.DEFAULT_TYPE);
+	}
+
+	@Test
+	public void testParseOptions_QueryNow_Numbered_withTypes() throws InvalidOptionsException, CLIParserException {
+		String[] args = new String[]{
+			Tokens.DB, DATABASE_NAME_1,
+			Tokens.SQL, SQL_1,
+			Tokens.TYPES, " INTEGER,VARCHAR, BOOLEAN",
+			Tokens.DATA, DATA_1, DATA_2, DATA_3};
+		CLIOptions options = parseOptions(args);
+		options.validate();
+
+		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
+		assertEquals(options.getSql(), SQL_1);
+		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
+		assertEquals(options.getNumberedParameters().size(), 3);
+		assertEquals(options.getNumberedParameters().get(0).getValue(), DATA_1);
+		assertEquals(options.getNumberedParameters().get(1).getValue(), DATA_2);
+		assertEquals(options.getNumberedParameters().get(2).getValue(), DATA_3);
+		assertEquals(options.getNumberedParameters().get(0).getType(), SQLType.INTEGER);
+		assertEquals(options.getNumberedParameters().get(1).getType(), SQLType.VARCHAR);
+		assertEquals(options.getNumberedParameters().get(2).getType(), SQLType.BOOLEAN);
+	}
+
+	@Test
+	public void testParseOptions_QueryNow_Named() throws InvalidOptionsException, CLIParserException {
+		String[] args = new String[]{
+			Tokens.DB, DATABASE_NAME_1,
+			Tokens.SQL, SQL_1,
+			Tokens.DATA_NAMED, NAME_1, DATA_1, NAME_2, DATA_2, NAME_3, DATA_3};
+		CLIOptions options = parseOptions(args);
+		options.validate();
+
+		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
+		assertEquals(options.getSql(), SQL_1);
+		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
+		assertEquals(options.getNamedParameters().size(), 3);
+		assertNamedParameter(options.getNamedParameters(), NAME_1, DATA_1, Parameter.DEFAULT_TYPE);
+		assertNamedParameter(options.getNamedParameters(), NAME_2, DATA_2, Parameter.DEFAULT_TYPE);
+		assertNamedParameter(options.getNamedParameters(), NAME_3, DATA_3, Parameter.DEFAULT_TYPE);
+	}
+
+	@Test
+	public void testParseOptions_QueryNow_Named_withTypes() throws InvalidOptionsException, CLIParserException {
+		String[] args = new String[]{
+			Tokens.DB, DATABASE_NAME_1,
+			Tokens.SQL, SQL_1,
+			Tokens.NAME_PREFIX, "$",
+			Tokens.TYPES, " " + NAME_1 + TYPE_NAME_SEPARATOR + "INTEGER" + "," + NAME_3 + TYPE_NAME_SEPARATOR + "BOOLEAN",
+			Tokens.DATA_NAMED, NAME_1, DATA_1, NAME_2, DATA_2, NAME_3, DATA_3};
+		CLIOptions options = parseOptions(args);
+		options.validate();
+
+		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
+		assertEquals(options.getSql(), SQL_1);
+		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
+		assertEquals(options.getNamedParameters().size(), 3);
+		assertNamedParameter(options.getNamedParameters(), NAME_1, DATA_1, SQLType.INTEGER);
+		assertNamedParameter(options.getNamedParameters(), NAME_2, DATA_2, Parameter.DEFAULT_TYPE);
+		assertNamedParameter(options.getNamedParameters(), NAME_3, DATA_3, SQLType.BOOLEAN);
+	}
+
+	private void assertNamedParameter(Collection<NamedParameter> params, String name, Object value, SQLType type) {
+		for (NamedParameter p : params) {
+			if (name.equals(p.getName())) {
+				assertEquals(p.getValue(), value, "value does not match – name: " + name);
+				assertEquals(p.getType(), type, "value does not match – name: " + name);
+				return;
+			}
+		}
+		fail("Named parameter not found: " + name);
+	}
+
+	@Test
+	public void testParseOptions_PrepareBatch() throws InvalidOptionsException, CLIParserException {
+		String[] args = new String[]{
+			Tokens.BATCH,
+			Tokens.SQL, SQL_1};
+		CLIOptions options = parseOptions(args);
+		options.validate();
+
+		assertEquals(options.getSql(), SQL_1);
+		assertEquals(options.getMode(), CLIOptions.MODE.PREPARE_BATCH);
+	}
+
+	@Test
+	public void testParseOptions_ExecuteBatch() throws InvalidOptionsException, CLIParserException {
+		String[] args = new String[]{
+			Tokens.BATCH,
+			Tokens.DB, DATABASE_NAME_1};
+		CLIOptions options = parseOptions(args);
+		options.validate();
+
+		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
+		assertEquals(options.getMode(), CLIOptions.MODE.EXECUTE_BATCH);
+	}
+
+	@Test
+	public void testParseOptions_ShowInfo_Help() throws InvalidOptionsException, CLIParserException {
+		String[] args = new String[]{Tokens.INFO_HELP};
+		CLIOptions options = parseOptions(args);
+		options.validate();
+
+		assertEquals(options.getMode(), CLIOptions.MODE.JUST_SHOW_INFO);
+		assertEquals(options.getShowInfo().size(), 1);
+		assertTrue(options.getShowInfo().contains(InfoType.HELP));
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/java/sql-dk/src/test/java/info/globalcode/sql/dk/FunctionsTest.java	Mon Mar 04 20:15:24 2019 +0100
@@ -0,0 +1,96 @@
+/**
+ * SQL-DK
+ * Copyright © 2013 František Kučera (frantovo.cz)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package info.globalcode.sql.dk;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import static org.testng.Assert.*;
+import org.testng.annotations.*;
+
+/**
+ *
+ * @author Ing. František Kučera (frantovo.cz)
+ */
+public class FunctionsTest {
+
+	@Test
+	public void testNotNull() {
+		Collection<String> c = null;
+		for (String s : Functions.notNull(c)) {
+			fail("Should not iterate through null collection");
+		}
+
+		c = new ArrayList<>();
+		c.add("ahoj");
+		int count = 0;
+		for (String s : Functions.notNull(c)) {
+			assertEquals(s, "ahoj", "Wrong item in collection");
+			count++;
+		}
+		assertEquals(count, 1, "Wrong number of iterations");
+	}
+
+	@Test
+	public void testLpad() {
+		String original = "abc";
+		String padded;
+
+		padded = Functions.lpad(original, 5);
+		assertEquals(padded, "  abc");
+
+		padded = Functions.lpad(original, 2);
+		assertEquals(padded, original);
+	}
+
+	@Test
+	public void testRpad() {
+		String original = "abc";
+		String padded;
+
+		padded = Functions.rpad(original, 5);
+		assertEquals(padded, "abc  ");
+
+		padded = Functions.rpad(original, 2);
+		assertEquals(padded, original);
+	}
+
+	@Test
+	public void testRepeat() {
+		assertEquals(Functions.repeat('f', 0), "");
+		assertEquals(Functions.repeat('f', 3), "fff");
+	}
+
+	@Test
+	public void testGetClassHierarchy() {
+		List<Class<? extends HierarchyMockClass2>> hierarchy = Functions.getClassHierarchy(HierarchyMockClass0.class, HierarchyMockClass2.class);
+		assertEquals(hierarchy.size(), 3, "invalid number of classes in the hierarchy");
+		assertEquals(hierarchy.get(0), HierarchyMockClass0.class);
+		assertEquals(hierarchy.get(1), HierarchyMockClass1.class);
+		assertEquals(hierarchy.get(2), HierarchyMockClass2.class);
+	}
+
+	private static class HierarchyMockClass0 extends HierarchyMockClass1 {
+	}
+
+	private static class HierarchyMockClass1 extends HierarchyMockClass2 {
+	}
+
+	private static class HierarchyMockClass2 {
+	}
+}
--- a/java/sql-dk/test/info/globalcode/sql/dk/CLIParserTest.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,195 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import info.globalcode.sql.dk.CLIParser.Tokens;
-import static info.globalcode.sql.dk.CLIParser.TYPE_NAME_SEPARATOR;
-import info.globalcode.sql.dk.InfoLister.InfoType;
-import java.io.ByteArrayInputStream;
-import java.util.Collection;
-import static org.testng.Assert.*;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class CLIParserTest {
-
-	private static final String DATABASE_NAME_1 = "some database 1";
-	private static final String SQL_1 = "SELECT * FROM table1";
-	private static final String DATA_1 = "aaa";
-	private static final String DATA_2 = "bbb";
-	private static final String DATA_3 = "ccc";
-	private static final String NAME_1 = "param1";
-	private static final String NAME_2 = "param2";
-	private static final String NAME_3 = "param3";
-	private CLIParser parser;
-
-	@BeforeMethod
-	public void setUpMethod() throws Exception {
-		parser = new CLIParser();
-	}
-
-	private CLIOptions parseOptions(String[] args) throws CLIParserException {
-		return parser.parseOptions(args, new ByteArrayInputStream("".getBytes()));
-	}
-
-	@Test
-	public void testParseOptions_QueryNow_NoParams() throws InvalidOptionsException, CLIParserException {
-		String[] args = new String[]{
-			Tokens.DB, DATABASE_NAME_1,
-			Tokens.SQL, SQL_1};
-		CLIOptions options = parseOptions(args);
-		options.validate();
-
-		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
-		assertEquals(options.getSql(), SQL_1);
-		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
-		assertTrue(options.getNamedParameters().isEmpty(), "Named parameters should be empty.");
-		assertTrue(options.getNumberedParameters().isEmpty(), "Numbered parameters should be empty.");
-	}
-
-	@Test
-	public void testParseOptions_QueryNow_Numbered() throws InvalidOptionsException, CLIParserException {
-		String[] args = new String[]{
-			Tokens.DB, DATABASE_NAME_1,
-			Tokens.SQL, SQL_1,
-			Tokens.DATA, DATA_1, DATA_2, DATA_3};
-		CLIOptions options = parseOptions(args);
-		options.validate();
-
-		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
-		assertEquals(options.getSql(), SQL_1);
-		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
-		assertEquals(options.getNumberedParameters().size(), 3);
-		assertEquals(options.getNumberedParameters().get(0).getValue(), DATA_1);
-		assertEquals(options.getNumberedParameters().get(1).getValue(), DATA_2);
-		assertEquals(options.getNumberedParameters().get(2).getValue(), DATA_3);
-		assertEquals(options.getNumberedParameters().get(0).getType(), Parameter.DEFAULT_TYPE);
-		assertEquals(options.getNumberedParameters().get(1).getType(), Parameter.DEFAULT_TYPE);
-		assertEquals(options.getNumberedParameters().get(2).getType(), Parameter.DEFAULT_TYPE);
-	}
-
-	@Test
-	public void testParseOptions_QueryNow_Numbered_withTypes() throws InvalidOptionsException, CLIParserException {
-		String[] args = new String[]{
-			Tokens.DB, DATABASE_NAME_1,
-			Tokens.SQL, SQL_1,
-			Tokens.TYPES, " INTEGER,VARCHAR, BOOLEAN",
-			Tokens.DATA, DATA_1, DATA_2, DATA_3};
-		CLIOptions options = parseOptions(args);
-		options.validate();
-
-		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
-		assertEquals(options.getSql(), SQL_1);
-		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
-		assertEquals(options.getNumberedParameters().size(), 3);
-		assertEquals(options.getNumberedParameters().get(0).getValue(), DATA_1);
-		assertEquals(options.getNumberedParameters().get(1).getValue(), DATA_2);
-		assertEquals(options.getNumberedParameters().get(2).getValue(), DATA_3);
-		assertEquals(options.getNumberedParameters().get(0).getType(), SQLType.INTEGER);
-		assertEquals(options.getNumberedParameters().get(1).getType(), SQLType.VARCHAR);
-		assertEquals(options.getNumberedParameters().get(2).getType(), SQLType.BOOLEAN);
-	}
-
-	@Test
-	public void testParseOptions_QueryNow_Named() throws InvalidOptionsException, CLIParserException {
-		String[] args = new String[]{
-			Tokens.DB, DATABASE_NAME_1,
-			Tokens.SQL, SQL_1,
-			Tokens.DATA_NAMED, NAME_1, DATA_1, NAME_2, DATA_2, NAME_3, DATA_3};
-		CLIOptions options = parseOptions(args);
-		options.validate();
-
-		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
-		assertEquals(options.getSql(), SQL_1);
-		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
-		assertEquals(options.getNamedParameters().size(), 3);
-		assertNamedParameter(options.getNamedParameters(), NAME_1, DATA_1, Parameter.DEFAULT_TYPE);
-		assertNamedParameter(options.getNamedParameters(), NAME_2, DATA_2, Parameter.DEFAULT_TYPE);
-		assertNamedParameter(options.getNamedParameters(), NAME_3, DATA_3, Parameter.DEFAULT_TYPE);
-	}
-
-	@Test
-	public void testParseOptions_QueryNow_Named_withTypes() throws InvalidOptionsException, CLIParserException {
-		String[] args = new String[]{
-			Tokens.DB, DATABASE_NAME_1,
-			Tokens.SQL, SQL_1,
-			Tokens.NAME_PREFIX, "$",
-			Tokens.TYPES, " " + NAME_1 + TYPE_NAME_SEPARATOR + "INTEGER" + "," + NAME_3 + TYPE_NAME_SEPARATOR + "BOOLEAN",
-			Tokens.DATA_NAMED, NAME_1, DATA_1, NAME_2, DATA_2, NAME_3, DATA_3};
-		CLIOptions options = parseOptions(args);
-		options.validate();
-
-		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
-		assertEquals(options.getSql(), SQL_1);
-		assertEquals(options.getMode(), CLIOptions.MODE.QUERY_NOW);
-		assertEquals(options.getNamedParameters().size(), 3);
-		assertNamedParameter(options.getNamedParameters(), NAME_1, DATA_1, SQLType.INTEGER);
-		assertNamedParameter(options.getNamedParameters(), NAME_2, DATA_2, Parameter.DEFAULT_TYPE);
-		assertNamedParameter(options.getNamedParameters(), NAME_3, DATA_3, SQLType.BOOLEAN);
-	}
-
-	private void assertNamedParameter(Collection<NamedParameter> params, String name, Object value, SQLType type) {
-		for (NamedParameter p : params) {
-			if (name.equals(p.getName())) {
-				assertEquals(p.getValue(), value, "value does not match – name: " + name);
-				assertEquals(p.getType(), type, "value does not match – name: " + name);
-				return;
-			}
-		}
-		fail("Named parameter not found: " + name);
-	}
-
-	@Test
-	public void testParseOptions_PrepareBatch() throws InvalidOptionsException, CLIParserException {
-		String[] args = new String[]{
-			Tokens.BATCH,
-			Tokens.SQL, SQL_1};
-		CLIOptions options = parseOptions(args);
-		options.validate();
-
-		assertEquals(options.getSql(), SQL_1);
-		assertEquals(options.getMode(), CLIOptions.MODE.PREPARE_BATCH);
-	}
-
-	@Test
-	public void testParseOptions_ExecuteBatch() throws InvalidOptionsException, CLIParserException {
-		String[] args = new String[]{
-			Tokens.BATCH,
-			Tokens.DB, DATABASE_NAME_1};
-		CLIOptions options = parseOptions(args);
-		options.validate();
-
-		assertEquals(options.getDatabaseName(), DATABASE_NAME_1);
-		assertEquals(options.getMode(), CLIOptions.MODE.EXECUTE_BATCH);
-	}
-
-	@Test
-	public void testParseOptions_ShowInfo_Help() throws InvalidOptionsException, CLIParserException {
-		String[] args = new String[]{Tokens.INFO_HELP};
-		CLIOptions options = parseOptions(args);
-		options.validate();
-
-		assertEquals(options.getMode(), CLIOptions.MODE.JUST_SHOW_INFO);
-		assertEquals(options.getShowInfo().size(), 1);
-		assertTrue(options.getShowInfo().contains(InfoType.HELP));
-	}
-}
\ No newline at end of file
--- a/java/sql-dk/test/info/globalcode/sql/dk/FunctionsTest.java	Mon Mar 04 17:06:42 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/**
- * SQL-DK
- * Copyright © 2013 František Kučera (frantovo.cz)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package info.globalcode.sql.dk;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import static org.testng.Assert.*;
-import org.testng.annotations.*;
-
-/**
- *
- * @author Ing. František Kučera (frantovo.cz)
- */
-public class FunctionsTest {
-
-	@Test
-	public void testNotNull() {
-		Collection<String> c = null;
-		for (String s : Functions.notNull(c)) {
-			fail("Should not iterate through null collection");
-		}
-
-		c = new ArrayList<>();
-		c.add("ahoj");
-		int count = 0;
-		for (String s : Functions.notNull(c)) {
-			assertEquals(s, "ahoj", "Wrong item in collection");
-			count++;
-		}
-		assertEquals(count, 1, "Wrong number of iterations");
-	}
-
-	@Test
-	public void testLpad() {
-		String original = "abc";
-		String padded;
-
-		padded = Functions.lpad(original, 5);
-		assertEquals(padded, "  abc");
-
-		padded = Functions.lpad(original, 2);
-		assertEquals(padded, original);
-	}
-
-	@Test
-	public void testRpad() {
-		String original = "abc";
-		String padded;
-
-		padded = Functions.rpad(original, 5);
-		assertEquals(padded, "abc  ");
-
-		padded = Functions.rpad(original, 2);
-		assertEquals(padded, original);
-	}
-
-	@Test
-	public void testRepeat() {
-		assertEquals(Functions.repeat('f', 0), "");
-		assertEquals(Functions.repeat('f', 3), "fff");
-	}
-
-	@Test
-	public void testGetClassHierarchy() {
-		List<Class<? extends HierarchyMockClass2>> hierarchy = Functions.getClassHierarchy(HierarchyMockClass0.class, HierarchyMockClass2.class);
-		assertEquals(hierarchy.size(), 3, "invalid number of classes in the hierarchy");
-		assertEquals(hierarchy.get(0), HierarchyMockClass0.class);
-		assertEquals(hierarchy.get(1), HierarchyMockClass1.class);
-		assertEquals(hierarchy.get(2), HierarchyMockClass2.class);
-	}
-
-	private static class HierarchyMockClass0 extends HierarchyMockClass1 {
-	}
-
-	private static class HierarchyMockClass1 extends HierarchyMockClass2 {
-	}
-
-	private static class HierarchyMockClass2 {
-	}
-}