diff -r 6bdb45af26d9 -r b6ff5b7a8422 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XmlFormatter.java --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XmlFormatter.java Mon Mar 04 22:28:29 2019 +0100 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XmlFormatter.java Tue Mar 05 21:22:33 2019 +0100 @@ -23,12 +23,14 @@ 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.sql.Types; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -42,28 +44,73 @@ * choice for further processing – e.g. XSL transformation.

* *

- * TODO: XSD

+ * XML format is defined in the Relational + * pipes specification. + *

* * @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 { + private static final String XML_ELEMENT_RELATION = "relation"; + private static final String XML_ELEMENT_NAME = "name"; + private static final String XML_ELEMENT_RECORD = "record"; + private static final String XML_ELEMENT_ATTRIBUTE = "attribute"; + private static final String XML_ATTRIBUTE_NAME = "name"; + private static final String XML_ATTRIBUTE_TYPE = "type"; + private static final String XML_ATTRIBUTE_STATEMENT = "statement"; + private static final String XML_NS_PREFIX_SQLDK = "sql-dk"; + + private static final String RELPIPE_TYPE_BOOLEAN = "boolean"; + private static final String RELPIPE_TYPE_INTEGER = "integer"; + private static final String RELPIPE_TYPE_STRING = "string"; + private static final Map RELPIPE_TYPES; + + static { + Map m = new HashMap<>(); + m.put(Types.BOOLEAN, RELPIPE_TYPE_BOOLEAN); + m.put(Types.BIT, RELPIPE_TYPE_BOOLEAN); // TODO: relpipe "boolean" can not be null in the current version + // m.put(Types.INTEGER, RELPIPE_TYPE_INTEGER); // relpipe "integer" is unsigned + // TODO: add more types when supported in Relational pipes + m.put(Types.CHAR, RELPIPE_TYPE_STRING); + m.put(Types.VARCHAR, RELPIPE_TYPE_STRING); + RELPIPE_TYPES = Collections.unmodifiableMap(m); + } + 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; + private String currentDatabaseName; + private int statementCounter; + private int resultSetCounter; + public XmlFormatter(FormatterContext formatterContext) { super(formatterContext); labeledColumns = formatterContext.getProperties().getBoolean(PROPERTY_LABELED_COLUMNS, false); } + private QName qname(String localPart) { + return new QName(Xmlns.RELPIPE, localPart); + } + + private QName qnameDK(String localPart) { + return new QName(Xmlns.SQLDK, localPart, XML_NS_PREFIX_SQLDK); + } + @Override public void writeStartBatch() { super.writeStartBatch(); printStartDocument(); - printStartElement(qname("batchResult"), singleAttribute(qname("xmlns"), Xmlns.BATCH_RESULT)); + Map attributes = new LinkedHashMap<>(2); + attributes.put(qname("xmlns"), Xmlns.RELPIPE); + attributes.put(new QName(null, XML_NS_PREFIX_SQLDK, "xmlns"), Xmlns.SQLDK); + printStartElement(qname("relpipe"), attributes); + statementCounter = 0; + resultSetCounter = 0; + } @Override @@ -76,64 +123,101 @@ @Override public void writeStartDatabase(DatabaseDefinition databaseDefinition) { super.writeStartDatabase(databaseDefinition); + currentDatabaseName = databaseDefinition.getName(); Map attributes = databaseDefinition.getName() == null ? null : singleAttribute(qname("name"), databaseDefinition.getName()); - printStartElement(qname("database"), attributes); + printEmptyElement(qnameDK("database-start"), attributes); } @Override public void writeEndDatabase() { super.writeEndDatabase(); - printEndElement(); + Map attributes = currentDatabaseName == null ? null : singleAttribute(qname("name"), currentDatabaseName); + printEmptyElement(qnameDK("database-end"), attributes); + } + + private String getCurrentStatementName() { + return "s" + statementCounter; } @Override public void writeStartStatement() { super.writeStartStatement(); - printStartElement(qname("statement")); + statementCounter++; + printEmptyElement(qnameDK("statement-start"), singleAttribute(qname("id"), getCurrentStatementName())); } @Override public void writeEndStatement() { super.writeEndStatement(); - printEndElement(); + printEmptyElement(qnameDK("statement-end"), singleAttribute(qname("id"), getCurrentStatementName())); } @Override public void writeQuery(String sql) { super.writeQuery(sql); - printTextElement(qname("sql"), null, sql); + printTextElement(qnameDK("sql"), singleAttribute(qname(XML_ATTRIBUTE_STATEMENT), getCurrentStatementName()), sql); } @Override public void writeParameters(List parameters) { super.writeParameters(parameters); - for (Parameter p : notNull(parameters)) { + if (parameters != null && parameters.size() > 0) { - Map attributes = new LinkedHashMap<>(2); - if (p instanceof NamedParameter) { - attributes.put(qname("name"), ((NamedParameter) p).getName()); + printStartElement(qnameDK("parameters"), singleAttribute(qname(XML_ATTRIBUTE_STATEMENT), getCurrentStatementName())); + + for (Parameter p : notNull(parameters)) { + + Map attributes = new LinkedHashMap<>(2); + if (p instanceof NamedParameter) { + attributes.put(qname(XML_ATTRIBUTE_NAME), ((NamedParameter) p).getName()); + } + attributes.put(qname(XML_ATTRIBUTE_TYPE), p.getType().name()); + + printTextElement(qnameDK("parameter"), attributes, String.valueOf(p.getValue())); } - attributes.put(qname("type"), p.getType().name()); - printTextElement(qname("parameter"), attributes, String.valueOf(p.getValue())); + printEndElement(); + } } + private String getCurrentRelationName() { + // TODO: support custom names (add CLI option) + return "r" + resultSetCounter; + } + @Override public void writeStartResultSet(ColumnsHeader header) { super.writeStartResultSet(header); - printStartElement(qname("resultSet")); + resultSetCounter++; + printStartElement(qname(XML_ELEMENT_RELATION), singleAttribute(qnameDK(XML_ATTRIBUTE_STATEMENT), getCurrentStatementName())); + printTextElement(qname(XML_ELEMENT_NAME), null, getCurrentRelationName()); + printStartElement(qname("attributes-metadata")); 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); + Map attributes = new LinkedHashMap<>(6); + + attributes.put(qname(XML_ATTRIBUTE_NAME), cd.getLabel()); + attributes.put(qname(XML_ATTRIBUTE_TYPE), toRelpipeType(cd.getType())); + + attributes.put(qnameDK("tableName"), cd.getTableName()); + attributes.put(qnameDK("columnName"), cd.getName()); + attributes.put(qnameDK("jdbcTypeName"), cd.getTypeName()); + attributes.put(qnameDK("jdbcType"), String.valueOf(cd.getType())); + + printEmptyElement(qname("attribute-metadata"), attributes); } + printEndElement(); + } + + /** + * @param jdbcType value from {@linkplain Types} + * @return + */ + private String toRelpipeType(int jdbcType) { + return RELPIPE_TYPES.getOrDefault(jdbcType, RELPIPE_TYPE_STRING); } @Override @@ -145,7 +229,7 @@ @Override public void writeStartRow() { super.writeStartRow(); - printStartElement(qname("row")); + printStartElement(qname(XML_ELEMENT_RECORD)); } @Override @@ -155,21 +239,23 @@ Map attributes = null; if (labeledColumns) { attributes = new LinkedHashMap<>(2); - attributes.put(qname("label"), getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel()); + attributes.put(qname(XML_ATTRIBUTE_NAME), getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel()); + attributes.put(qname(XML_ATTRIBUTE_TYPE), toRelpipeType(getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getType())); } if (value == null) { if (attributes == null) { attributes = new LinkedHashMap<>(2); } - attributes.put(qname("null"), "true"); - printEmptyElement(qname("column"), attributes); + // TODO: synchronize syntax with Relational pipes (after adding support of null values) + attributes.put(qnameDK("null"), "true"); + printEmptyElement(qname(XML_ELEMENT_ATTRIBUTE), attributes); } else if (value instanceof Array) { Array sqlArray = (Array) value; try { Object[] array = (Object[]) sqlArray.getArray(); - printStartElement(qname("column"), attributes); + printStartElement(qname(XML_ELEMENT_ATTRIBUTE), attributes); printArray(array); printEndElement(); } catch (SQLException e) { @@ -186,7 +272,7 @@ // } } - printStartElement(qname("column"), attributes); + printStartElement(qname(XML_ELEMENT_ATTRIBUTE), attributes); // FIXME: instanceof SQLXML, see below printArray(arrayList.toArray()); printEndElement(); @@ -203,25 +289,26 @@ SQLXML xml = (SQLXML) value; // TODO: parse DOM/SAX and transplant XML, don't escape (optional) try { - printTextElement(qname("column"), attributes, xml.getString()); + printTextElement(qname(XML_ELEMENT_ATTRIBUTE), 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)); + printTextElement(qname(XML_ELEMENT_ATTRIBUTE), attributes, toString(value)); } } private void printArray(Object[] array) { - printStartElement(qname("array")); + // TODO: synchronize array syntax with Relational pipes + printStartElement(qnameDK("array")); for (Object o : array) { if (o instanceof Object[]) { - printStartElement(qname("item")); + printStartElement(qnameDK("item")); printArray((Object[]) o); printEndElement(); } else { - printTextElement(qname("item"), null, String.valueOf(o)); + printTextElement(qnameDK("item"), null, String.valueOf(o)); } } printEndElement(); @@ -236,7 +323,7 @@ @Override public void writeUpdatesResult(int updatedRowsCount) { super.writeUpdatesResult(updatedRowsCount); - printTextElement(qname("updatedRows"), null, String.valueOf(updatedRowsCount)); + printTextElement(qnameDK("updatedRecords"), singleAttribute(qnameDK(XML_ATTRIBUTE_STATEMENT), getCurrentStatementName()), String.valueOf(updatedRowsCount)); } protected String toString(Object value) {