1.1 --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/Xmlns.java Mon Mar 04 22:28:29 2019 +0100
1.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/Xmlns.java Tue Mar 05 21:22:33 2019 +0100
1.3 @@ -25,7 +25,8 @@
1.4 public class Xmlns {
1.5
1.6 public static final String CONFIGURATION = "https://sql-dk.globalcode.info/xmlns/configuration";
1.7 - public static final String BATCH_RESULT = "https://sql-dk.globalcode.info/xmlns/batchResult";
1.8 + public static final String RELPIPE = "tag:globalcode.info,2018:relpipe";
1.9 + public static final String SQLDK = "tag:globalcode.info,2018:sqldk";
1.10 public static final String XHTML = "http://www.w3.org/1999/xhtml";
1.11
1.12 private Xmlns() {
2.1 --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java Mon Mar 04 22:28:29 2019 +0100
2.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java Tue Mar 05 21:22:33 2019 +0100
2.3 @@ -61,7 +61,7 @@
2.4 private static final TerminalColor ATTRIBUTE_VALUE_COLOR = TerminalColor.Yellow;
2.5 private static final TerminalColor XML_DECLARATION_COLOR = TerminalColor.Red;
2.6 private static final TerminalColor XML_DOCTYPE_COLOR = TerminalColor.Cyan;
2.7 - private Stack<QName> treePosition = new Stack<>();
2.8 + private final Stack<QName> treePosition = new Stack<>();
2.9 private final ColorfulPrintWriter out;
2.10 private final String indent;
2.11 private final boolean indentText;
2.12 @@ -205,14 +205,6 @@
2.13 }
2.14 }
2.15
2.16 - protected static QName qname(String name) {
2.17 - return new QName(name);
2.18 - }
2.19 -
2.20 - protected static QName qname(String prefix, String name) {
2.21 - return new QName(null, name, prefix);
2.22 - }
2.23 -
2.24 private String toString(QName name) {
2.25 if (isEmpty(name.getPrefix(), true)) {
2.26 return escapeName(name.getLocalPart());
3.1 --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnDescriptor.java Mon Mar 04 22:28:29 2019 +0100
3.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnDescriptor.java Tue Mar 05 21:22:33 2019 +0100
3.3 @@ -32,6 +32,7 @@
3.4 private boolean firstColumn;
3.5 private boolean lastColumn;
3.6 private int columnNumber;
3.7 + private String tableName;
3.8
3.9 /**
3.10 * @return column name
3.11 @@ -99,6 +100,14 @@
3.12 this.columnNumber = columnNumber;
3.13 }
3.14
3.15 + public String getTableName() {
3.16 + return tableName;
3.17 + }
3.18 +
3.19 + public void setTableName(String tableName) {
3.20 + this.tableName = tableName;
3.21 + }
3.22 +
3.23 public boolean isBoolean() {
3.24 return type == Types.BOOLEAN;
3.25 }
4.1 --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnsHeader.java Mon Mar 04 22:28:29 2019 +0100
4.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnsHeader.java Tue Mar 05 21:22:33 2019 +0100
4.3 @@ -27,13 +27,13 @@
4.4 * @author Ing. František Kučera (frantovo.cz)
4.5 */
4.6 public class ColumnsHeader {
4.7 -
4.8 - private ResultSetMetaData metaData;
4.9 -
4.10 +
4.11 + private final ResultSetMetaData metaData;
4.12 +
4.13 public ColumnsHeader(ResultSetMetaData metaData) {
4.14 this.metaData = metaData;
4.15 }
4.16 -
4.17 +
4.18 public int getColumnCount() {
4.19 try {
4.20 return metaData.getColumnCount();
4.21 @@ -41,27 +41,27 @@
4.22 throw new IllegalStateException("Error during getting column count.", e);
4.23 }
4.24 }
4.25 -
4.26 +
4.27 public List<ColumnDescriptor> getColumnDescriptors() {
4.28 try {
4.29 int count = metaData.getColumnCount();
4.30 List<ColumnDescriptor> list = new ArrayList<>(count);
4.31 -
4.32 +
4.33 for (int i = 1; i <= count; i++) {
4.34 ColumnDescriptor cd = new ColumnDescriptor();
4.35 -
4.36 +
4.37 cd.setFirstColumn(i == 1);
4.38 cd.setLastColumn(i == count);
4.39 cd.setColumnNumber(i);
4.40 -
4.41 +
4.42 cd.setLabel(metaData.getColumnLabel(i));
4.43 cd.setName(metaData.getColumnName(i));
4.44 cd.setType(metaData.getColumnType(i));
4.45 cd.setTypeName(metaData.getColumnTypeName(i));
4.46 - /** TODO: more properties */
4.47 + cd.setTableName(metaData.getTableName(i));
4.48 list.add(cd);
4.49 }
4.50 -
4.51 +
4.52 return list;
4.53 } catch (SQLException e) {
4.54 throw new IllegalStateException("Error during building column descriptors.", e);
5.1 --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XhtmlFormatter.java Mon Mar 04 22:28:29 2019 +0100
5.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XhtmlFormatter.java Tue Mar 05 21:22:33 2019 +0100
5.3 @@ -24,7 +24,6 @@
5.4 import info.globalcode.sql.dk.configuration.DatabaseDefinition;
5.5 import info.globalcode.sql.dk.configuration.Properties;
5.6 import info.globalcode.sql.dk.configuration.Property;
5.7 -import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
5.8 import java.sql.Array;
5.9 import java.sql.SQLException;
5.10 import java.util.Date;
5.11 @@ -55,6 +54,10 @@
5.12 public XhtmlFormatter(FormatterContext formatterContext) {
5.13 super(addDefaults(formatterContext));
5.14 }
5.15 +
5.16 + private QName qname(String localPart) {
5.17 + return new QName(Xmlns.XHTML, localPart);
5.18 + }
5.19
5.20 /**
5.21 * Do not indent text – preserve whitespace for pre elements
6.1 --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XmlFormatter.java Mon Mar 04 22:28:29 2019 +0100
6.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XmlFormatter.java Tue Mar 05 21:22:33 2019 +0100
6.3 @@ -23,12 +23,14 @@
6.4 import static info.globalcode.sql.dk.Functions.notNull;
6.5 import info.globalcode.sql.dk.NamedParameter;
6.6 import info.globalcode.sql.dk.configuration.PropertyDeclaration;
6.7 -import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname;
6.8 import java.sql.Array;
6.9 import java.sql.ResultSet;
6.10 import java.sql.SQLException;
6.11 import java.sql.SQLXML;
6.12 +import java.sql.Types;
6.13 import java.util.ArrayList;
6.14 +import java.util.Collections;
6.15 +import java.util.HashMap;
6.16 import java.util.LinkedHashMap;
6.17 import java.util.List;
6.18 import java.util.Map;
6.19 @@ -42,28 +44,73 @@
6.20 * choice for further processing – e.g. XSL transformation.</p>
6.21 *
6.22 * <p>
6.23 - * TODO: XSD</p>
6.24 + * XML format is defined in the <a href="https://relational-pipes.globalcode.info/">Relational
6.25 + * pipes</a> specification.
6.26 + * </p>
6.27 *
6.28 * @author Ing. František Kučera (frantovo.cz)
6.29 */
6.30 @PropertyDeclaration(name = XmlFormatter.PROPERTY_LABELED_COLUMNS, defaultValue = "false", type = Boolean.class, description = "whether to add 'label' attribute to each 'column' element")
6.31 public class XmlFormatter extends AbstractXmlFormatter {
6.32
6.33 + private static final String XML_ELEMENT_RELATION = "relation";
6.34 + private static final String XML_ELEMENT_NAME = "name";
6.35 + private static final String XML_ELEMENT_RECORD = "record";
6.36 + private static final String XML_ELEMENT_ATTRIBUTE = "attribute";
6.37 + private static final String XML_ATTRIBUTE_NAME = "name";
6.38 + private static final String XML_ATTRIBUTE_TYPE = "type";
6.39 + private static final String XML_ATTRIBUTE_STATEMENT = "statement";
6.40 + private static final String XML_NS_PREFIX_SQLDK = "sql-dk";
6.41 +
6.42 + private static final String RELPIPE_TYPE_BOOLEAN = "boolean";
6.43 + private static final String RELPIPE_TYPE_INTEGER = "integer";
6.44 + private static final String RELPIPE_TYPE_STRING = "string";
6.45 + private static final Map<Integer, String> RELPIPE_TYPES;
6.46 +
6.47 + static {
6.48 + Map<Integer, String> m = new HashMap<>();
6.49 + m.put(Types.BOOLEAN, RELPIPE_TYPE_BOOLEAN);
6.50 + m.put(Types.BIT, RELPIPE_TYPE_BOOLEAN); // TODO: relpipe "boolean" can not be null in the current version
6.51 + // m.put(Types.INTEGER, RELPIPE_TYPE_INTEGER); // relpipe "integer" is unsigned
6.52 + // TODO: add more types when supported in Relational pipes
6.53 + m.put(Types.CHAR, RELPIPE_TYPE_STRING);
6.54 + m.put(Types.VARCHAR, RELPIPE_TYPE_STRING);
6.55 + RELPIPE_TYPES = Collections.unmodifiableMap(m);
6.56 + }
6.57 +
6.58 public static final String NAME = "xml"; // bash-completion:formatter
6.59 public static final String PROPERTY_LABELED_COLUMNS = "labeledColumns";
6.60 private static final Logger log = Logger.getLogger(XmlFormatter.class.getName());
6.61 private final boolean labeledColumns;
6.62
6.63 + private String currentDatabaseName;
6.64 + private int statementCounter;
6.65 + private int resultSetCounter;
6.66 +
6.67 public XmlFormatter(FormatterContext formatterContext) {
6.68 super(formatterContext);
6.69 labeledColumns = formatterContext.getProperties().getBoolean(PROPERTY_LABELED_COLUMNS, false);
6.70 }
6.71
6.72 + private QName qname(String localPart) {
6.73 + return new QName(Xmlns.RELPIPE, localPart);
6.74 + }
6.75 +
6.76 + private QName qnameDK(String localPart) {
6.77 + return new QName(Xmlns.SQLDK, localPart, XML_NS_PREFIX_SQLDK);
6.78 + }
6.79 +
6.80 @Override
6.81 public void writeStartBatch() {
6.82 super.writeStartBatch();
6.83 printStartDocument();
6.84 - printStartElement(qname("batchResult"), singleAttribute(qname("xmlns"), Xmlns.BATCH_RESULT));
6.85 + Map<QName, String> attributes = new LinkedHashMap<>(2);
6.86 + attributes.put(qname("xmlns"), Xmlns.RELPIPE);
6.87 + attributes.put(new QName(null, XML_NS_PREFIX_SQLDK, "xmlns"), Xmlns.SQLDK);
6.88 + printStartElement(qname("relpipe"), attributes);
6.89 + statementCounter = 0;
6.90 + resultSetCounter = 0;
6.91 +
6.92 }
6.93
6.94 @Override
6.95 @@ -76,64 +123,101 @@
6.96 @Override
6.97 public void writeStartDatabase(DatabaseDefinition databaseDefinition) {
6.98 super.writeStartDatabase(databaseDefinition);
6.99 + currentDatabaseName = databaseDefinition.getName();
6.100 Map<QName, String> attributes = databaseDefinition.getName() == null ? null : singleAttribute(qname("name"), databaseDefinition.getName());
6.101 - printStartElement(qname("database"), attributes);
6.102 + printEmptyElement(qnameDK("database-start"), attributes);
6.103 }
6.104
6.105 @Override
6.106 public void writeEndDatabase() {
6.107 super.writeEndDatabase();
6.108 - printEndElement();
6.109 + Map<QName, String> attributes = currentDatabaseName == null ? null : singleAttribute(qname("name"), currentDatabaseName);
6.110 + printEmptyElement(qnameDK("database-end"), attributes);
6.111 + }
6.112 +
6.113 + private String getCurrentStatementName() {
6.114 + return "s" + statementCounter;
6.115 }
6.116
6.117 @Override
6.118 public void writeStartStatement() {
6.119 super.writeStartStatement();
6.120 - printStartElement(qname("statement"));
6.121 + statementCounter++;
6.122 + printEmptyElement(qnameDK("statement-start"), singleAttribute(qname("id"), getCurrentStatementName()));
6.123 }
6.124
6.125 @Override
6.126 public void writeEndStatement() {
6.127 super.writeEndStatement();
6.128 - printEndElement();
6.129 + printEmptyElement(qnameDK("statement-end"), singleAttribute(qname("id"), getCurrentStatementName()));
6.130 }
6.131
6.132 @Override
6.133 public void writeQuery(String sql) {
6.134 super.writeQuery(sql);
6.135 - printTextElement(qname("sql"), null, sql);
6.136 + printTextElement(qnameDK("sql"), singleAttribute(qname(XML_ATTRIBUTE_STATEMENT), getCurrentStatementName()), sql);
6.137 }
6.138
6.139 @Override
6.140 public void writeParameters(List<? extends Parameter> parameters) {
6.141 super.writeParameters(parameters);
6.142
6.143 - for (Parameter p : notNull(parameters)) {
6.144 + if (parameters != null && parameters.size() > 0) {
6.145
6.146 - Map<QName, String> attributes = new LinkedHashMap<>(2);
6.147 - if (p instanceof NamedParameter) {
6.148 - attributes.put(qname("name"), ((NamedParameter) p).getName());
6.149 + printStartElement(qnameDK("parameters"), singleAttribute(qname(XML_ATTRIBUTE_STATEMENT), getCurrentStatementName()));
6.150 +
6.151 + for (Parameter p : notNull(parameters)) {
6.152 +
6.153 + Map<QName, String> attributes = new LinkedHashMap<>(2);
6.154 + if (p instanceof NamedParameter) {
6.155 + attributes.put(qname(XML_ATTRIBUTE_NAME), ((NamedParameter) p).getName());
6.156 + }
6.157 + attributes.put(qname(XML_ATTRIBUTE_TYPE), p.getType().name());
6.158 +
6.159 + printTextElement(qnameDK("parameter"), attributes, String.valueOf(p.getValue()));
6.160 }
6.161 - attributes.put(qname("type"), p.getType().name());
6.162
6.163 - printTextElement(qname("parameter"), attributes, String.valueOf(p.getValue()));
6.164 + printEndElement();
6.165 +
6.166 }
6.167
6.168 }
6.169
6.170 + private String getCurrentRelationName() {
6.171 + // TODO: support custom names (add CLI option)
6.172 + return "r" + resultSetCounter;
6.173 + }
6.174 +
6.175 @Override
6.176 public void writeStartResultSet(ColumnsHeader header) {
6.177 super.writeStartResultSet(header);
6.178 - printStartElement(qname("resultSet"));
6.179 + resultSetCounter++;
6.180 + printStartElement(qname(XML_ELEMENT_RELATION), singleAttribute(qnameDK(XML_ATTRIBUTE_STATEMENT), getCurrentStatementName()));
6.181 + printTextElement(qname(XML_ELEMENT_NAME), null, getCurrentRelationName());
6.182
6.183 + printStartElement(qname("attributes-metadata"));
6.184 for (ColumnDescriptor cd : header.getColumnDescriptors()) {
6.185 - Map<QName, String> attributes = new LinkedHashMap<>(4);
6.186 - attributes.put(qname("label"), cd.getLabel());
6.187 - attributes.put(qname("name"), cd.getName());
6.188 - attributes.put(qname("typeName"), cd.getTypeName());
6.189 - attributes.put(qname("type"), String.valueOf(cd.getType()));
6.190 - printEmptyElement(qname("columnHeader"), attributes);
6.191 + Map<QName, String> attributes = new LinkedHashMap<>(6);
6.192 +
6.193 + attributes.put(qname(XML_ATTRIBUTE_NAME), cd.getLabel());
6.194 + attributes.put(qname(XML_ATTRIBUTE_TYPE), toRelpipeType(cd.getType()));
6.195 +
6.196 + attributes.put(qnameDK("tableName"), cd.getTableName());
6.197 + attributes.put(qnameDK("columnName"), cd.getName());
6.198 + attributes.put(qnameDK("jdbcTypeName"), cd.getTypeName());
6.199 + attributes.put(qnameDK("jdbcType"), String.valueOf(cd.getType()));
6.200 +
6.201 + printEmptyElement(qname("attribute-metadata"), attributes);
6.202 }
6.203 + printEndElement();
6.204 + }
6.205 +
6.206 + /**
6.207 + * @param jdbcType value from {@linkplain Types}
6.208 + * @return
6.209 + */
6.210 + private String toRelpipeType(int jdbcType) {
6.211 + return RELPIPE_TYPES.getOrDefault(jdbcType, RELPIPE_TYPE_STRING);
6.212 }
6.213
6.214 @Override
6.215 @@ -145,7 +229,7 @@
6.216 @Override
6.217 public void writeStartRow() {
6.218 super.writeStartRow();
6.219 - printStartElement(qname("row"));
6.220 + printStartElement(qname(XML_ELEMENT_RECORD));
6.221 }
6.222
6.223 @Override
6.224 @@ -155,21 +239,23 @@
6.225 Map<QName, String> attributes = null;
6.226 if (labeledColumns) {
6.227 attributes = new LinkedHashMap<>(2);
6.228 - attributes.put(qname("label"), getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel());
6.229 + attributes.put(qname(XML_ATTRIBUTE_NAME), getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel());
6.230 + attributes.put(qname(XML_ATTRIBUTE_TYPE), toRelpipeType(getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getType()));
6.231 }
6.232
6.233 if (value == null) {
6.234 if (attributes == null) {
6.235 attributes = new LinkedHashMap<>(2);
6.236 }
6.237 - attributes.put(qname("null"), "true");
6.238 - printEmptyElement(qname("column"), attributes);
6.239 + // TODO: synchronize syntax with Relational pipes (after adding support of null values)
6.240 + attributes.put(qnameDK("null"), "true");
6.241 + printEmptyElement(qname(XML_ELEMENT_ATTRIBUTE), attributes);
6.242 } else if (value instanceof Array) {
6.243
6.244 Array sqlArray = (Array) value;
6.245 try {
6.246 Object[] array = (Object[]) sqlArray.getArray();
6.247 - printStartElement(qname("column"), attributes);
6.248 + printStartElement(qname(XML_ELEMENT_ATTRIBUTE), attributes);
6.249 printArray(array);
6.250 printEndElement();
6.251 } catch (SQLException e) {
6.252 @@ -186,7 +272,7 @@
6.253 // }
6.254 }
6.255
6.256 - printStartElement(qname("column"), attributes);
6.257 + printStartElement(qname(XML_ELEMENT_ATTRIBUTE), attributes);
6.258 // FIXME: instanceof SQLXML, see below
6.259 printArray(arrayList.toArray());
6.260 printEndElement();
6.261 @@ -203,25 +289,26 @@
6.262 SQLXML xml = (SQLXML) value;
6.263 // TODO: parse DOM/SAX and transplant XML, don't escape (optional)
6.264 try {
6.265 - printTextElement(qname("column"), attributes, xml.getString());
6.266 + printTextElement(qname(XML_ELEMENT_ATTRIBUTE), attributes, xml.getString());
6.267 } catch (SQLException e) {
6.268 log.log(Level.SEVERE, "Unable to format XML", e);
6.269 writeColumnValue(String.valueOf(value));
6.270 }
6.271 } else {
6.272 - printTextElement(qname("column"), attributes, toString(value));
6.273 + printTextElement(qname(XML_ELEMENT_ATTRIBUTE), attributes, toString(value));
6.274 }
6.275 }
6.276
6.277 private void printArray(Object[] array) {
6.278 - printStartElement(qname("array"));
6.279 + // TODO: synchronize array syntax with Relational pipes
6.280 + printStartElement(qnameDK("array"));
6.281 for (Object o : array) {
6.282 if (o instanceof Object[]) {
6.283 - printStartElement(qname("item"));
6.284 + printStartElement(qnameDK("item"));
6.285 printArray((Object[]) o);
6.286 printEndElement();
6.287 } else {
6.288 - printTextElement(qname("item"), null, String.valueOf(o));
6.289 + printTextElement(qnameDK("item"), null, String.valueOf(o));
6.290 }
6.291 }
6.292 printEndElement();
6.293 @@ -236,7 +323,7 @@
6.294 @Override
6.295 public void writeUpdatesResult(int updatedRowsCount) {
6.296 super.writeUpdatesResult(updatedRowsCount);
6.297 - printTextElement(qname("updatedRows"), null, String.valueOf(updatedRowsCount));
6.298 + printTextElement(qnameDK("updatedRecords"), singleAttribute(qnameDK(XML_ATTRIBUTE_STATEMENT), getCurrentStatementName()), String.valueOf(updatedRowsCount));
6.299 }
6.300
6.301 protected String toString(Object value) {