# HG changeset patch # User František Kučera # Date 1551726924 -3600 # Node ID 4a1864c3e8677599dbaeddf0c3fbf8b298243d6f # Parent 7e08730da258ba16803a0f48cee435cc3d889668 mavenized: sql-dk diff -r 7e08730da258 -r 4a1864c3e867 .hgignore --- 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 diff -r 7e08730da258 -r 4a1864c3e867 distributions/debian/build.sh --- 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>" && diff -r 7e08730da258 -r 4a1864c3e867 distributions/fedora/sql-dk.spec --- 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/* - diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/bash-completion.sh --- 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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/build.xml --- 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 @@ - - - - - - - - - - - - - Builds, tests, and runs the project sql-dk. - - - - - - - - - - - - - diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/data/info/globalcode/sql/dk/example-config.xml --- 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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/data/info/globalcode/sql/dk/formatter/XhtmlFormatter.css --- 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; -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/data/info/globalcode/sql/dk/license.txt --- 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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/help-generator.sh --- 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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/manifest.mf --- 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 - diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/nbproject/build-impl.xml --- 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set src.dir - Must set src.data.dir - Must set test.src.dir - Must set build.dir - Must set dist.dir - Must set build.classes.dir - Must set dist.javadoc.dir - Must set build.test.classes.dir - Must set build.test.results.dir - Must set build.classes.excludes - Must set dist.jar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set javac.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No tests executed. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set JVM to use for profiling in profiler.info.jvm - Must set profiler agent JVM arguments in profiler.info.jvmargs.agent - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select some files in the IDE or set javac.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - To run this application from the command line without Ant, try: - - java -jar "${dist.jar.resolved}" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select one file in the IDE or set run.class - - - - Must select one file in the IDE or set run.class - - - - - - - - - - - - - - - - - - - - - - - Must select one file in the IDE or set debug.class - - - - - Must select one file in the IDE or set debug.class - - - - - Must set fix.includes - - - - - - - - - - This target only works when run from inside the NetBeans IDE. - - - - - - - - - Must select one file in the IDE or set profile.class - This target only works when run from inside the NetBeans IDE. - - - - - - - - - This target only works when run from inside the NetBeans IDE. - - - - - - - - - - - - - This target only works when run from inside the NetBeans IDE. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select one file in the IDE or set run.class - - - - - - Must select some files in the IDE or set test.includes - - - - - Must select one file in the IDE or set run.class - - - - - Must select one file in the IDE or set applet.url - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select some files in the IDE or set javac.includes - - - - - - - - - - - - - - - - - - - - Some tests failed; see details above. - - - - - - - - - Must select some files in the IDE or set test.includes - - - - Some tests failed; see details above. - - - - Must select some files in the IDE or set test.class - Must select some method in the IDE or set test.method - - - - Some tests failed; see details above. - - - - - Must select one file in the IDE or set test.class - - - - Must select one file in the IDE or set test.class - Must select some method in the IDE or set test.method - - - - - - - - - - - - - - Must select one file in the IDE or set applet.url - - - - - - - - - Must select one file in the IDE or set applet.url - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/nbproject/genfiles.properties --- 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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/nbproject/project.properties --- 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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/nbproject/project.xml --- 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 @@ - - - org.netbeans.modules.java.j2seproject - - - sql-dk - - - - - - - - - - diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/pom.xml --- /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 @@ + + + 4.0.0 + info.globalcode.sql.dk + sql-dk + 0.10-SNAPSHOT + jar + + + + org.testng + testng + 6.9.9 + test + + + + + UTF-8 + 1.8 + 1.8 + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + + version-info + generate-sources + + exec + + + ./version-info.sh + src/main/resources/info/globalcode/sql/dk/version.txt + + + + help-generator + generate-sources + + exec + + + ./help-generator.sh + src/main/resources/info/globalcode/sql/dk/help.txt + + + + bash-completion + generate-sources + + exec + + + ./bash-completion.sh + target/bash-completion.sh + + + + + + + + diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/CLIOptions.java --- 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 . - */ -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 databaseNamesToTest = new LinkedHashSet<>(); - private final Set databaseNamesToListProperties = new LinkedHashSet<>(); - private final Set 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 namedParameters = new ArrayList<>(); - private final List numberedParameters = new ArrayList<>(); - private final EnumSet 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 getNamedParameters() { - return namedParameters; - } - - public List 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 getShowInfo() { - return showInfo; - } - - public Set getDatabaseNamesToTest() { - return databaseNamesToTest; - } - - public void addDatabaseNameToTest(String name) { - databaseNamesToTest.add(name); - } - - public Set getDatabaseNamesToListProperties() { - return databaseNamesToListProperties; - } - - public void addDatabaseNameToListProperties(String name) { - databaseNamesToListProperties.add(name); - } - - public Set 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; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/CLIParser.java --- 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 . - */ -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 numberedTypes = new ArrayList<>(); - Map 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); - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/CLIParserException.java --- 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 . - */ -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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/CLIStarter.java --- 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 . - */ -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 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 formatterClass = (Class) Class.forName(fd.getClassName()); - List> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class); - Collections.reverse(hierarchy); - for (Class 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}); - } - - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/ColorfulPrintWriter.java --- 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 . - */ -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 styles) { - printCodes(getStyleCodes(styles)); - } - - private static int[] getStyleCodes(EnumSet 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 styles, String string) { - setForegroundColor(foregroundColor); - setBackgroundColor(backgroundColor); - setStyle(styles); - print(string); - resetAll(); - } - - public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, EnumSet 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 styles, String string) { - setForegroundColor(foregroundColor); - setStyle(styles); - print(string); - resetAll(); - } - - public void println(TerminalColor foregroundColor, EnumSet 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 styles, String string) { - setStyle(styles); - print(string); - resetAll(); - } - - public void println(EnumSet 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; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/Constants.java --- 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 . - */ -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() { - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/DKException.java --- 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 . - */ -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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/DatabaseConnection.java --- 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 . - */ -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(); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/Functions.java --- 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 . - */ -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 Collection notNull(Collection c) { - if (c == null) { - return Collections.emptyList(); - } else { - return c; - } - } - - public static T findByName(Collection 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

type of the last parent - * @param type of the examined class - * @param type examined class - * @param lastParent the last parent type to stop at - * @return list of types starting with type and ending with lastParent - */ - public static List> getClassHierarchy(Class type, Class

lastParent) { - List> 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 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); - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/InfoLister.java --- 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 . - */ -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 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 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 data = new ArrayList<>(); - for (Entry 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 data = new ArrayList<>(); - for (Entry 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 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 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 data = new HashMap<>(); - Class formatterClass = (Class) Class.forName(fd.getClassName()); - List> 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 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 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 data = new ArrayList<>(); - - final List 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 data = new ArrayList<>(); - - final ServiceLoader 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 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 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 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 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 parameters, List data) throws ConfigurationException, FormatterException { - printTable(formatter, header, sql, parameters, data, null); - } - - private void printTable(Formatter formatter, ColumnsHeader header, String sql, List parameters, List data, final Integer sortByColumn) throws ConfigurationException, FormatterException { - printHeader(formatter, header, sql, parameters); - - if (sortByColumn != null) { - Collections.sort(data, new Comparator() { - - @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 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; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/InvalidOptionsException.java --- 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 . - */ -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 problems = new ArrayList<>(); - - public Collection 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; - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/NamedParameter.java --- 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 . - */ -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() + "}"; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/Parameter.java --- 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 . - */ -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; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/SQLCommand.java --- 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 . - */ -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 getParameters(); - - public String getQuery() { - return query; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNamed.java --- 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 . - */ -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 parameters; - private List parametersUsed = new ArrayList<>(); - private StringBuilder updatedQuery; - private Pattern pattern; - private SQLCommandNumbered numbered; - - public SQLCommandNamed(String query, List 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("(?"); - 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 + "(?.+?)" + 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 getParameters() { - return parameters; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/SQLCommandNumbered.java --- 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 . - */ -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 parameters; - - public SQLCommandNumbered(String query, List 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 getParameters() { - return parameters; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/SQLType.java --- 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 . - */ -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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/Xmlns.java --- 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 . - */ -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() { - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/batch/Batch.java --- 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 . - */ -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; -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/batch/BatchConstants.java --- 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 . - */ -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() { - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/batch/BatchDecoder.java --- 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 . - */ -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 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); - } - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/batch/BatchEncoder.java --- 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 . - */ -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 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); - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/batch/BatchException.java --- 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 . - */ -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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/CommandArgument.java --- 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 . - */ -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; - } - -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/Configuration.java --- 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 . - */ -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 databases = new ArrayList<>(); - private List 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 buildInFormatters; - - static { - Collection 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 getDatabases() { - return databases; - } - - public void setDatabases(List 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 getFormatters() { - return formatters; - } - - public void setFormatters(List 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 getBuildInFormatters() { - return buildInFormatters; - } - - /** - * @return built-in + configured formatters - * @see #getFormatters() - */ - @XmlTransient - public Collection getAllFormatters() { - Collection 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; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/ConfigurationException.java --- 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 . - */ -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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/ConfigurationProvider.java --- 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 . - */ -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; -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/DatabaseDefinition.java --- 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 . - */ -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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/FormatterDefinition.java --- 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 . - */ -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 - * info.globalcode.sql.dk.formatting.Formatter interface. - * Subclassing the - * info.globalcode.sql.dk.formatting.AbstractFormatter is strongly recommended. - * The constructor must accept one parameter: - * info.globalcode.sql.dk.formatting.FormatterContext - * - * @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); - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/Loader.java --- 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 . - */ -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 driverClass = (Class) 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); - } - } - } - -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/NameIdentified.java --- 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 . - */ -package info.globalcode.sql.dk.configuration; - -/** - * - * @author Ing. František Kučera (frantovo.cz) - */ -public interface NameIdentified { - - public String getName(); -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/Properties.java --- 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 . - */ -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; - -/** - *

- * List of configurables.

- * - *

- * 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.

- * - *

- * Typical use:

- *
    - *
  • this instance – ad-hoc properties from CLI options
  • - *
  • default properties – from config file
  • - *
  • defaultValue – hardcoded default
  • - *
- * - * @author Ing. František Kučera (frantovo.cz) - */ -public class Properties extends ArrayList 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); - } - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/Property.java --- 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 . - */ -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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/PropertyDeclaration.java --- 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 . - */ -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(); -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/PropertyDeclarations.java --- 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 . - */ -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(); - -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/TunnelDefinition.java --- 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 . - */ -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 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 getArguments() { - return arguments; - } - - public void setArguments(List arguments) { - this.arguments = arguments; - } - -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/configuration/jaxb.index --- 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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractFormatter.java --- 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 . - */ -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; - -/** - *
    - *
  1. ensures integrity – if methods are called in correct order and context
  2. - *
  3. provides default implmentations of methods that does not produce any output for given - * events
  4. - *
- * - * @author Ing. František Kučera (frantovo.cz) - */ -public abstract class AbstractFormatter implements Formatter { - - private Stack 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 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; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java --- 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 . - */ -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; - -/** - *

- * Provides helper methods for printing pretty intended and optionally colorful (syntax highlighted) - * XML output. - *

- * - *

- * Must be used with care – bad usage can lead to invalid XML (e.g. using undeclared namespaces). - *

- * - * @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 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, ""); - } - - protected void printDoctype(String doctype) { - out.print(XML_DOCTYPE_COLOR, "\n"); - } - - 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 singleAttribute(QName name, String value) { - Map attributes = new HashMap<>(2); - attributes.put(name, value); - return attributes; - } - - protected void printStartElement(QName element, Map attributes) { - printStartElement(element, attributes, false); - } - - /** - * @param empty whether element should be closed … /> (has no content, do not - * call {@linkplain #printEndElement()}) - */ - private void printStartElement(QName element, Map attributes, boolean empty) { - printIndent(); - - out.print(ELEMENT_COLOR, "<" + toString(element)); - - if (attributes != null) { - for (Entry 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 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 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, ""); - 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 - return s; - } - - private static String escapeXmlText(String s) { - return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">"); - // Not needed: - // return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'"); - } - - /** - * Expects attribute values enclosed in "quotes" not 'apostrophes'. - */ - private static String escapeXmlAttribute(String s) { - return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/BarChartFormatter.java --- 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 . - */ -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 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; - } - } - -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/ColumnDescriptor.java --- 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 . - */ -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; - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/ColumnsHeader.java --- 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 . - */ -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 getColumnDescriptors() { - try { - int count = metaData.getColumnCount(); - List 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); - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/CommonProperties.java --- 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 . - */ -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 TYPE_SIMPLE_NAMES; - - static { - Map 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)"; -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/FakeSqlArray.java --- 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 . - */ -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> 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> map) throws SQLException { - throw exception; - } - - @Override - public ResultSet getResultSet() throws SQLException { - throw exception; - } - - @Override - public ResultSet getResultSet(Map> 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> map) throws SQLException { - throw exception; - } - - @Override - public void free() throws SQLException { - throw exception; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/Formatter.java --- 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 . - */ -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 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; -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/FormatterContext.java --- 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 . - */ -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; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/FormatterException.java --- 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 . - */ -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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/SilentFormatter.java --- 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 . - */ -package info.globalcode.sql.dk.formatting; - -/** - * Does not output anything, can be used instead of - * /dev/null. - * - * @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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/SingleRecordFormatter.java --- 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 . - */ -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(); - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/SingleValueFormatter.java --- 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 . - */ -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(); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularFormatter.java --- 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 . - */ -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; - -/** - *

- * Prints human-readable output – tables of result sets and text messages with update counts. - *

- * - *

- * Longer values might break the table – overflow the cells – see alternative tabular formatters and - * the {@linkplain #PROPERTY_TRIM} property. - *

- * - * @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 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; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java --- 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 . - */ -package info.globalcode.sql.dk.formatting; - -import java.util.ArrayList; -import java.util.List; - -/** - *

- * Prefetches whole result set and computes column widths. Whole table is flushed at once in - * {@linkplain #writeEndResultSet()}. - *

- * - *

- * Long values will not overflow the cells, but whole result set must be loaded into the memory. - *

- * - * @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 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 currentResultSet) { - } - -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/TabularWrappingFormatter.java --- 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 . - */ -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 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; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/TeXFormatter.java --- 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 . - */ -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 TEX_ESCAPE_MAP; - private final ColorfulPrintWriter out; - - static { - Map 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 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); - } - -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/XhtmlFormatter.java --- 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 . - */ -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 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 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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/formatting/XmlFormatter.java --- 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 . - */ -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; - -/** - *

- * Prints machine-readable output – XML document containing resultsets and updates count. Good - * choice for further processing – e.g. XSL transformation.

- * - *

- * TODO: XSD

- * - * @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 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 parameters) { - super.writeParameters(parameters); - - for (Parameter p : notNull(parameters)) { - - Map 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 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 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 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); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagement.java --- 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 . - */ -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 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); - } - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java --- 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 . - */ -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(); - -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/jmx/ManagementUtils.java --- 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 . - */ -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 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() { - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/logging/ColorfulConsoleFormatter.java --- 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 . - */ -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; - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/logging/LoggerInitializer.java --- 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 . - */ -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()); - } -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/info/globalcode/sql/dk/logging/LoggerProducer.java --- 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 . - */ -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); - } - -} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIOptions.java --- /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 . + */ +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 databaseNamesToTest = new LinkedHashSet<>(); + private final Set databaseNamesToListProperties = new LinkedHashSet<>(); + private final Set 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 namedParameters = new ArrayList<>(); + private final List numberedParameters = new ArrayList<>(); + private final EnumSet 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 getNamedParameters() { + return namedParameters; + } + + public List 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 getShowInfo() { + return showInfo; + } + + public Set getDatabaseNamesToTest() { + return databaseNamesToTest; + } + + public void addDatabaseNameToTest(String name) { + databaseNamesToTest.add(name); + } + + public Set getDatabaseNamesToListProperties() { + return databaseNamesToListProperties; + } + + public void addDatabaseNameToListProperties(String name) { + databaseNamesToListProperties.add(name); + } + + public Set 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; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIParser.java --- /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 . + */ +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 numberedTypes = new ArrayList<>(); + Map 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); + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIParserException.java --- /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 . + */ +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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/CLIStarter.java --- /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 . + */ +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 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 formatterClass = (Class) Class.forName(fd.getClassName()); + List> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class); + Collections.reverse(hierarchy); + for (Class 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}); + } + + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/ColorfulPrintWriter.java --- /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 . + */ +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 styles) { + printCodes(getStyleCodes(styles)); + } + + private static int[] getStyleCodes(EnumSet 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 styles, String string) { + setForegroundColor(foregroundColor); + setBackgroundColor(backgroundColor); + setStyle(styles); + print(string); + resetAll(); + } + + public void println(TerminalColor foregroundColor, TerminalColor backgroundColor, EnumSet 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 styles, String string) { + setForegroundColor(foregroundColor); + setStyle(styles); + print(string); + resetAll(); + } + + public void println(TerminalColor foregroundColor, EnumSet 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 styles, String string) { + setStyle(styles); + print(string); + resetAll(); + } + + public void println(EnumSet 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; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/Constants.java --- /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 . + */ +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() { + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/DKException.java --- /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 . + */ +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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/DatabaseConnection.java --- /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 . + */ +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(); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/Functions.java --- /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 . + */ +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 Collection notNull(Collection c) { + if (c == null) { + return Collections.emptyList(); + } else { + return c; + } + } + + public static T findByName(Collection 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

type of the last parent + * @param type of the examined class + * @param type examined class + * @param lastParent the last parent type to stop at + * @return list of types starting with type and ending with lastParent + */ + public static List> getClassHierarchy(Class type, Class

lastParent) { + List> 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 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); + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/InfoLister.java --- /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 . + */ +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 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 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 data = new ArrayList<>(); + for (Entry 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 data = new ArrayList<>(); + for (Entry 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 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 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 data = new HashMap<>(); + Class formatterClass = (Class) Class.forName(fd.getClassName()); + List> 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 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 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 data = new ArrayList<>(); + + final List 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 data = new ArrayList<>(); + + final ServiceLoader 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 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 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 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 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 parameters, List data) throws ConfigurationException, FormatterException { + printTable(formatter, header, sql, parameters, data, null); + } + + private void printTable(Formatter formatter, ColumnsHeader header, String sql, List parameters, List data, final Integer sortByColumn) throws ConfigurationException, FormatterException { + printHeader(formatter, header, sql, parameters); + + if (sortByColumn != null) { + Collections.sort(data, new Comparator() { + + @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 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; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/InvalidOptionsException.java --- /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 . + */ +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 problems = new ArrayList<>(); + + public Collection 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; + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/NamedParameter.java --- /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 . + */ +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() + "}"; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/Parameter.java --- /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 . + */ +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; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommand.java --- /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 . + */ +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 getParameters(); + + public String getQuery() { + return query; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNamed.java --- /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 . + */ +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 parameters; + private List parametersUsed = new ArrayList<>(); + private StringBuilder updatedQuery; + private Pattern pattern; + private SQLCommandNumbered numbered; + + public SQLCommandNamed(String query, List 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("(?"); + 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 + "(?.+?)" + 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 getParameters() { + return parameters; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNumbered.java --- /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 . + */ +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 parameters; + + public SQLCommandNumbered(String query, List 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 getParameters() { + return parameters; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLType.java --- /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 . + */ +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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/Xmlns.java --- /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 . + */ +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() { + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/Batch.java --- /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 . + */ +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; +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchConstants.java --- /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 . + */ +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() { + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchDecoder.java --- /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 . + */ +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 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); + } + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchEncoder.java --- /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 . + */ +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 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); + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/batch/BatchException.java --- /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 . + */ +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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/CommandArgument.java --- /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 . + */ +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; + } + +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Configuration.java --- /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 . + */ +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 databases = new ArrayList<>(); + private List 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 buildInFormatters; + + static { + Collection 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 getDatabases() { + return databases; + } + + public void setDatabases(List 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 getFormatters() { + return formatters; + } + + public void setFormatters(List 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 getBuildInFormatters() { + return buildInFormatters; + } + + /** + * @return built-in + configured formatters + * @see #getFormatters() + */ + @XmlTransient + public Collection getAllFormatters() { + Collection 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; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/ConfigurationException.java --- /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 . + */ +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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/ConfigurationProvider.java --- /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 . + */ +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; +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/DatabaseDefinition.java --- /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 . + */ +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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/FormatterDefinition.java --- /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 . + */ +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 + * info.globalcode.sql.dk.formatting.Formatter interface. + * Subclassing the + * info.globalcode.sql.dk.formatting.AbstractFormatter is strongly recommended. + * The constructor must accept one parameter: + * info.globalcode.sql.dk.formatting.FormatterContext + * + * @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); + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Loader.java --- /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 . + */ +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 driverClass = (Class) 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); + } + } + } + +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/NameIdentified.java --- /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 . + */ +package info.globalcode.sql.dk.configuration; + +/** + * + * @author Ing. František Kučera (frantovo.cz) + */ +public interface NameIdentified { + + public String getName(); +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Properties.java --- /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 . + */ +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; + +/** + *

+ * List of configurables.

+ * + *

+ * 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.

+ * + *

+ * Typical use:

+ *
    + *
  • this instance – ad-hoc properties from CLI options
  • + *
  • default properties – from config file
  • + *
  • defaultValue – hardcoded default
  • + *
+ * + * @author Ing. František Kučera (frantovo.cz) + */ +public class Properties extends ArrayList 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); + } + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Property.java --- /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 . + */ +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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/PropertyDeclaration.java --- /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 . + */ +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(); +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/PropertyDeclarations.java --- /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 . + */ +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(); + +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/TunnelDefinition.java --- /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 . + */ +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 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 getArguments() { + return arguments; + } + + public void setArguments(List arguments) { + this.arguments = arguments; + } + +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractFormatter.java --- /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 . + */ +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; + +/** + *
    + *
  1. ensures integrity – if methods are called in correct order and context
  2. + *
  3. provides default implmentations of methods that does not produce any output for given + * events
  4. + *
+ * + * @author Ing. František Kučera (frantovo.cz) + */ +public abstract class AbstractFormatter implements Formatter { + + private Stack 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 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; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java --- /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 . + */ +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; + +/** + *

+ * Provides helper methods for printing pretty intended and optionally colorful (syntax highlighted) + * XML output. + *

+ * + *

+ * Must be used with care – bad usage can lead to invalid XML (e.g. using undeclared namespaces). + *

+ * + * @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 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, ""); + } + + protected void printDoctype(String doctype) { + out.print(XML_DOCTYPE_COLOR, "\n"); + } + + 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 singleAttribute(QName name, String value) { + Map attributes = new HashMap<>(2); + attributes.put(name, value); + return attributes; + } + + protected void printStartElement(QName element, Map attributes) { + printStartElement(element, attributes, false); + } + + /** + * @param empty whether element should be closed … /> (has no content, do not + * call {@linkplain #printEndElement()}) + */ + private void printStartElement(QName element, Map attributes, boolean empty) { + printIndent(); + + out.print(ELEMENT_COLOR, "<" + toString(element)); + + if (attributes != null) { + for (Entry 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 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 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, ""); + 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 + return s; + } + + private static String escapeXmlText(String s) { + return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">"); + // Not needed: + // return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'"); + } + + /** + * Expects attribute values enclosed in "quotes" not 'apostrophes'. + */ + private static String escapeXmlAttribute(String s) { + return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/BarChartFormatter.java --- /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 . + */ +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 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; + } + } + +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnDescriptor.java --- /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 . + */ +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; + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnsHeader.java --- /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 . + */ +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 getColumnDescriptors() { + try { + int count = metaData.getColumnCount(); + List 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); + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/CommonProperties.java --- /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 . + */ +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 TYPE_SIMPLE_NAMES; + + static { + Map 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)"; +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FakeSqlArray.java --- /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 . + */ +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> 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> map) throws SQLException { + throw exception; + } + + @Override + public ResultSet getResultSet() throws SQLException { + throw exception; + } + + @Override + public ResultSet getResultSet(Map> 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> map) throws SQLException { + throw exception; + } + + @Override + public void free() throws SQLException { + throw exception; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/Formatter.java --- /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 . + */ +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 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; +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FormatterContext.java --- /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 . + */ +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; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/FormatterException.java --- /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 . + */ +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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SilentFormatter.java --- /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 . + */ +package info.globalcode.sql.dk.formatting; + +/** + * Does not output anything, can be used instead of + * /dev/null. + * + * @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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SingleRecordFormatter.java --- /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 . + */ +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(); + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/SingleValueFormatter.java --- /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 . + */ +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(); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java --- /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 . + */ +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; + +/** + *

+ * Prints human-readable output – tables of result sets and text messages with update counts. + *

+ * + *

+ * Longer values might break the table – overflow the cells – see alternative tabular formatters and + * the {@linkplain #PROPERTY_TRIM} property. + *

+ * + * @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 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; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularPrefetchingFormatter.java --- /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 . + */ +package info.globalcode.sql.dk.formatting; + +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * Prefetches whole result set and computes column widths. Whole table is flushed at once in + * {@linkplain #writeEndResultSet()}. + *

+ * + *

+ * Long values will not overflow the cells, but whole result set must be loaded into the memory. + *

+ * + * @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 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 currentResultSet) { + } + +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularWrappingFormatter.java --- /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 . + */ +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 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; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TeXFormatter.java --- /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 . + */ +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 TEX_ESCAPE_MAP; + private final ColorfulPrintWriter out; + + static { + Map 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 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); + } + +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XhtmlFormatter.java --- /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 . + */ +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 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 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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XmlFormatter.java --- /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 . + */ +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; + +/** + *

+ * Prints machine-readable output – XML document containing resultsets and updates count. Good + * choice for further processing – e.g. XSL transformation.

+ * + *

+ * TODO: XSD

+ * + * @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 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 parameters) { + super.writeParameters(parameters); + + for (Parameter p : notNull(parameters)) { + + Map 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 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 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 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); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ConnectionManagement.java --- /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 . + */ +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 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); + } + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ConnectionManagementMBean.java --- /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 . + */ +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(); + +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/jmx/ManagementUtils.java --- /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 . + */ +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 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() { + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/ColorfulConsoleFormatter.java --- /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 . + */ +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; + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/LoggerInitializer.java --- /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 . + */ +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()); + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/logging/LoggerProducer.java --- /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 . + */ +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); + } + +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/resources/info/globalcode/sql/dk/configuration/jaxb.index --- /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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/resources/info/globalcode/sql/dk/example-config.xml --- /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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/resources/info/globalcode/sql/dk/formatter/XhtmlFormatter.css --- /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; +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/resources/info/globalcode/sql/dk/license.txt --- /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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/test/java/info/globalcode/sql/dk/CLIParserTest.java --- /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 . + */ +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 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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/test/java/info/globalcode/sql/dk/FunctionsTest.java --- /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 . + */ +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 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> 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 { + } +} diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/test/info/globalcode/sql/dk/CLIParserTest.java --- 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 . - */ -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 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 diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/test/info/globalcode/sql/dk/FunctionsTest.java --- 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 . - */ -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 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> 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 { - } -}