Mercurial > hg > monetdb-java
view src/main/java/org/monetdb/util/XMLExporter.java @ 973:32f246853ec4 default tip
Optimisation, call connection.mapClobAsVarChar() and connection.mapBlobAsVarBinary() outside the for-loop, as it does not change.
author | Martin van Dinther <martin.van.dinther@monetdbsolutions.com> |
---|---|
date | Thu, 10 Apr 2025 19:26:59 +0200 (4 hours ago) |
parents | d416e9b6b3d0 |
children |
line wrap: on
line source
/* * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2024, 2025 MonetDB Foundation; * Copyright August 2008 - 2023 MonetDB B.V.; * Copyright 1997 - July 2008 CWI. */ package org.monetdb.util; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.text.SimpleDateFormat; public final class XMLExporter extends Exporter { private boolean useNil; public static final short TYPE_NIL = 1; public static final short VALUE_OMIT = 0; public static final short VALUE_XSI = 1; public XMLExporter(final java.io.PrintWriter out) { super(out); } /** * A helper method to generate XML xsd schema code for a given table. * This method performs all required lookups to find all relations and * column information. * * @param dbmd a DatabaseMetaData object to query on (not null) * @param type the type of the object, e.g. VIEW, TABLE (not null) * @param schema the schema the object is in (not null) * @param name the table to describe (not null) * @throws SQLException if a database related error occurs */ public void dumpSchema( final java.sql.DatabaseMetaData dbmd, final String type, final String schema, final String name) throws SQLException { // handle views directly if (type.endsWith("VIEW")) { // for types: VIEW and SYSTEM VIEW final String viewDDL = fetchSysTablesQueryValue(dbmd.getConnection(), schema, name); if (viewDDL != null) out.println("<!-- " + viewDDL + " -->"); else out.println("<!-- unknown " + type + " " + dq(schema) + "." + dq(name) + ": no SQL view definition found! -->"); return; } out.println("<xsd:schema>"); final ResultSet cols = dbmd.getColumns(null, schema, name, null); final int colNmIndex = cols.findColumn("COLUMN_NAME"); final int colTypeNmIndex = cols.findColumn("TYPE_NAME"); final int datatypeIndex = cols.findColumn("DATA_TYPE"); final int sizeIndex = cols.findColumn("COLUMN_SIZE"); final int digitsIndex = cols.findColumn("DECIMAL_DIGITS"); String ident; final java.util.HashSet<String> types = new java.util.HashSet<String>(); // walk through the ResultSet and create the types // for a bit of a clue on the types, see this url: // http://books.xmlschemata.org/relaxng/relax-CHP-19.html while (cols.next()) { switch (cols.getInt(datatypeIndex)) { case Types.CHAR: ident = "CHAR_" + cols.getString(sizeIndex); if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:string\">"); out.print(" <xsd:length value="); out.print(dq(cols.getString(sizeIndex))); out.println(" />"); out.println(" </xsd:restriction>"); out.println(" </xsd:simpleType>"); break; case Types.VARCHAR: case Types.LONGVARCHAR: ident = "VARCHAR_" + cols.getString(sizeIndex); if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:string\">"); out.print(" <xsd:maxLength value="); out.print(dq(cols.getString(sizeIndex))); out.println(" />"); out.println(" </xsd:restriction>"); out.println(" </xsd:simpleType>"); break; case Types.CLOB: ident = "CLOB"; if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:string\" />"); out.println(" </xsd:simpleType>"); break; case Types.DECIMAL: case Types.NUMERIC: ident = "DECIMAL_" + cols.getString(sizeIndex) + "_" + cols.getString(digitsIndex); if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:decimal\">"); out.print(" <xsd:totalDigits value="); out.print(dq(cols.getString(sizeIndex))); out.println(" />"); out.print(" <xsd:fractionDigits value="); out.print(dq(cols.getString(digitsIndex))); out.println(" />"); out.println(" </xsd:restriction>"); out.println(" </xsd:simpleType>"); break; case Types.TINYINT: ident = "TINYINT"; if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:byte\" />"); out.println(" </xsd:simpleType>"); break; case Types.SMALLINT: ident = "SMALLINT"; if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:short\" />"); out.println(" </xsd:simpleType>"); break; case Types.INTEGER: ident = "INTEGER"; if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:integer\" />"); out.println(" </xsd:simpleType>"); break; case Types.BIGINT: ident = "BIGINT"; if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:long\" />"); out.println(" </xsd:simpleType>"); break; case Types.BIT: ident = "BIT"; if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:bit\" />"); out.println(" </xsd:simpleType>"); break; case Types.BOOLEAN: ident = "BOOLEAN"; if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:boolean\" />"); out.println(" </xsd:simpleType>"); break; case Types.DATE: ident = "DATE"; if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:date\" />"); out.println(" </xsd:simpleType>"); break; case Types.TIME: case Types.TIME_WITH_TIMEZONE: if ("timetz".equals(cols.getString(colTypeNmIndex))) { ident = "TIME_WTZ"; } else { ident = "TIME"; } if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:time\" />"); out.println(" </xsd:simpleType>"); break; case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: if ("timestamptz".equals(cols.getString(colTypeNmIndex))) { ident = "TIMESTAMP_WTZ"; } else { ident = "TIMESTAMP"; } if (types.contains(ident)) break; types.add(ident); out.print(" <xsd:simpleType name="); out.print(dq(ident)); out.println(">"); out.println(" <xsd:restriction base=\"xsd:dateTime\" />"); out.println(" </xsd:simpleType>"); break; } } // rewind the ResultSet cols.beforeFirst(); // create the RowType final String tablenm = schema.replaceAll("\\.", "_x002e_") + "." + name.replaceAll("\\.", "_x002e_"); out.print(" <xsd:complexType name="); out.print(dq("RowType." + tablenm)); out.println(">"); out.println(" <xsd:sequence>"); while (cols.next()) { out.print(" <xsd:element name="); out.print(dq(cols.getString(colNmIndex))); out.print(" type="); switch (cols.getInt(datatypeIndex)) { case Types.CHAR: ident = "CHAR_" + cols.getString(sizeIndex); break; case Types.VARCHAR: case Types.LONGVARCHAR: ident = "VARCHAR_" + cols.getString(sizeIndex); break; case Types.CLOB: ident = "CLOB"; break; case Types.DECIMAL: case Types.NUMERIC: ident = "DECIMAL_" + cols.getString(sizeIndex) + "_" + cols.getString(digitsIndex); break; case Types.TINYINT: ident = "TINYINT"; break; case Types.SMALLINT: ident = "SMALLINT"; break; case Types.INTEGER: ident = "INTEGER"; break; case Types.BIGINT: ident = "BIGINT"; break; case Types.BIT: ident = "BIT"; break; case Types.BOOLEAN: ident = "BOOLEAN"; break; case Types.DATE: ident = "DATE"; break; case Types.TIME: case Types.TIME_WITH_TIMEZONE: if ("timetz".equals(cols.getString(colTypeNmIndex))) { ident = "TIME_WTZ"; } else { ident = "TIME"; } break; case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: if ("timestamptz".equals(cols.getString(colTypeNmIndex))) { ident = "TIMESTAMP_WTZ"; } else { ident = "TIMESTAMP"; } break; default: ident = "(unknown)"; break; } out.print(dq(ident)); out.println(" />"); } out.println(" </xsd:sequence>"); out.println(" </xsd:complexType>"); out.print(" <xsd:complexType name="); out.print(dq("TableType." + tablenm)); out.println(">"); out.println(" <xsd:sequence>"); out.print(" <xsd:element name=\"row\" type="); out.print(dq("RowType." + tablenm)); out.println(" minOccurs=\"0\" maxOccurs=\"unbounded\" />"); out.println(" </xsd:sequence>"); out.println(" </xsd:complexType>"); out.println("</xsd:schema>"); } private static SimpleDateFormat xsd_ts; private static SimpleDateFormat xsd_tstz; /** * Generates an XML representation of the given ResultSet. * * @param rs the ResultSet */ public void dumpResultSet(final ResultSet rs) throws SQLException { // write simple XML serialisation final java.sql.ResultSetMetaData rsmd = rs.getMetaData(); final String schema = rsmd.getSchemaName(1); final String fqname = (schema != null && !schema.isEmpty() ? schema + "." : "") + rsmd.getTableName(1); out.println("<" + fqname + ">"); String data; while (rs.next()) { out.println(" <row>"); for (int i = 1; i <= rsmd.getColumnCount(); i++) { switch (rsmd.getColumnType(i)) { case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: final Timestamp ts = rs.getTimestamp(i); if ("timestamptz".equals(rsmd.getColumnTypeName(i))) { if (xsd_tstz == null) { // first time it is needed, create it xsd_tstz = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); } data = xsd_tstz.format(ts).toString(); } else { if (xsd_ts == null) { // first time it is needed, create it xsd_ts = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); } data = xsd_ts.format(ts).toString(); } break; default: data = rs.getString(i); break; } if (data == null) { if (useNil) { // "nil" method: write <tag xsi:nil="true" /> out.println(" <" + rsmd.getColumnLabel(i) + " xsi:nil=\"true\" />"); } else { // This is the "absent" method (of completely // hiding the tag if null } } else { if (data.length() == 0) { out.println(" <" + rsmd.getColumnLabel(i) + " />"); } else { final String colLabel = rsmd.getColumnLabel(i); out.println(" <" + colLabel + ">" + escapeSpecialXMLChars(data) + "</" + colLabel + ">"); } } } out.println(" </row>"); } out.println("</" + fqname + ">"); } // https://en.wikipedia.org/wiki/XML#Escaping private static final String escapeSpecialXMLChars(final String val) { final int len = val.length(); final StringBuilder sb = new StringBuilder(len + 50); // replace each & or < or > or ' or " by special XML escape code for (int i = 0; i < len; i++) { char c = val.charAt(i); switch (c) { case '&': sb.append("&"); break; case '<': sb.append("<"); break; case '>': sb.append(">"); break; case '\'': sb.append("'"); break; case '"': sb.append("""); break; default: sb.append(c); } } return (sb.length() > len) ? sb.toString() : val; } public void setProperty(final int type, final int value) throws Exception { switch (type) { case TYPE_NIL: switch (value) { case VALUE_OMIT: useNil = false; break; case VALUE_XSI: useNil = true; break; default: throw new Exception("Illegal value " + value + " for TYPE_NIL"); } break; default: throw new Exception("Illegal type " + type); } } public int getProperty(final int type) throws Exception { switch (type) { case TYPE_NIL: return useNil ? VALUE_XSI : VALUE_OMIT; default: throw new Exception("Illegal type " + type); } } }