# HG changeset patch # User František Kučera # Date 1551817353 -3600 # Node ID b6ff5b7a842255c17ec33d238fc9e4fcc3daf8df # Parent 6bdb45af26d946e7a36e82ed927dc2bea3468f46 sqldk-relpipe convergence started diff -r 6bdb45af26d9 -r b6ff5b7a8422 java/sql-dk/src/main/java/info/globalcode/sql/dk/Xmlns.java --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/Xmlns.java Mon Mar 04 22:28:29 2019 +0100 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/Xmlns.java Tue Mar 05 21:22:33 2019 +0100 @@ -25,7 +25,8 @@ 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 RELPIPE = "tag:globalcode.info,2018:relpipe"; + public static final String SQLDK = "tag:globalcode.info,2018:sqldk"; public static final String XHTML = "http://www.w3.org/1999/xhtml"; private Xmlns() { diff -r 6bdb45af26d9 -r b6ff5b7a8422 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java Mon Mar 04 22:28:29 2019 +0100 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/AbstractXmlFormatter.java Tue Mar 05 21:22:33 2019 +0100 @@ -61,7 +61,7 @@ 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 Stack treePosition = new Stack<>(); private final ColorfulPrintWriter out; private final String indent; private final boolean indentText; @@ -205,14 +205,6 @@ } } - 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()); diff -r 6bdb45af26d9 -r b6ff5b7a8422 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnDescriptor.java --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnDescriptor.java Mon Mar 04 22:28:29 2019 +0100 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnDescriptor.java Tue Mar 05 21:22:33 2019 +0100 @@ -32,6 +32,7 @@ private boolean firstColumn; private boolean lastColumn; private int columnNumber; + private String tableName; /** * @return column name @@ -99,6 +100,14 @@ this.columnNumber = columnNumber; } + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + public boolean isBoolean() { return type == Types.BOOLEAN; } diff -r 6bdb45af26d9 -r b6ff5b7a8422 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnsHeader.java --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnsHeader.java Mon Mar 04 22:28:29 2019 +0100 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/ColumnsHeader.java Tue Mar 05 21:22:33 2019 +0100 @@ -27,13 +27,13 @@ * @author Ing. František Kučera (frantovo.cz) */ public class ColumnsHeader { - - private ResultSetMetaData metaData; - + + private final ResultSetMetaData metaData; + public ColumnsHeader(ResultSetMetaData metaData) { this.metaData = metaData; } - + public int getColumnCount() { try { return metaData.getColumnCount(); @@ -41,27 +41,27 @@ 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 */ + cd.setTableName(metaData.getTableName(i)); list.add(cd); } - + return list; } catch (SQLException e) { throw new IllegalStateException("Error during building column descriptors.", e); diff -r 6bdb45af26d9 -r b6ff5b7a8422 java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XhtmlFormatter.java --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XhtmlFormatter.java Mon Mar 04 22:28:29 2019 +0100 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/XhtmlFormatter.java Tue Mar 05 21:22:33 2019 +0100 @@ -24,7 +24,6 @@ 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; @@ -55,6 +54,10 @@ public XhtmlFormatter(FormatterContext formatterContext) { super(addDefaults(formatterContext)); } + + private QName qname(String localPart) { + return new QName(Xmlns.XHTML, localPart); + } /** * Do not indent text – preserve whitespace for pre elements 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) {