diff --git a/src/helma/objectmodel/db/DbColumn.java b/src/helma/objectmodel/db/DbColumn.java index 51c15b16..f2ce4dfc 100644 --- a/src/helma/objectmodel/db/DbColumn.java +++ b/src/helma/objectmodel/db/DbColumn.java @@ -26,10 +26,16 @@ public final class DbColumn { private final int type; private final Relation relation; + private final boolean isId; + private final boolean isPrototype; + private final boolean isName; + + private final boolean isMapped; + /** * Constructor */ - public DbColumn(String name, int type, Relation rel) { + public DbColumn(String name, int type, Relation rel, DbMapping dbmap) { this.name = name; this.type = type; this.relation = rel; @@ -37,6 +43,12 @@ public final class DbColumn { if (relation != null) { relation.setColumnType(type); } + + isId = name.equalsIgnoreCase(dbmap.getIDField()); + isPrototype = name.equalsIgnoreCase(dbmap.getPrototypeField()); + isName = name.equalsIgnoreCase(dbmap.getNameField()); + + isMapped = relation != null || isId || isPrototype || isName; } /** @@ -59,4 +71,33 @@ public final class DbColumn { public Relation getRelation() { return relation; } + + /** + * Returns true if this column serves as ID field for the prototype. + */ + public boolean isIdField() { + return isId; + } + + /** + * Returns true if this column serves as prototype field for the prototype. + */ + public boolean isPrototypeField() { + return isPrototype; + } + + /** + * Returns true if this column serves as name field for the prototype. + */ + public boolean isNameField() { + return isName; + } + + /** + * Returns true if this field is mapped by the prototype's db mapping. + */ + public boolean isMapped() { + return isMapped; + } + } diff --git a/src/helma/objectmodel/db/DbMapping.java b/src/helma/objectmodel/db/DbMapping.java index 77339311..6e894df7 100644 --- a/src/helma/objectmodel/db/DbMapping.java +++ b/src/helma/objectmodel/db/DbMapping.java @@ -24,6 +24,7 @@ import java.sql.*; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; +import java.util.ArrayList; import java.util.Iterator; import java.util.StringTokenizer; @@ -836,15 +837,19 @@ public final class DbMapping implements Updatable { // ok, we have the meta data, now loop through mapping... int ncols = meta.getColumnCount(); - - columns = new DbColumn[ncols]; + ArrayList list = new ArrayList(ncols); for (int i = 0; i < ncols; i++) { String colName = meta.getColumnName(i + 1); Relation rel = columnNameToRelation(colName); - columns[i] = new DbColumn(colName, meta.getColumnType(i + 1), rel); + DbColumn col = new DbColumn(colName, meta.getColumnType(i + 1), rel, this); + if (col.isMapped()) { + list.add(col); + } } + columns = new DbColumn[list.size()]; + columns = (DbColumn[]) list.toArray(columns); } return columns; @@ -862,6 +867,7 @@ public final class DbMapping implements Updatable { */ public DbColumn getColumn(String columnName) throws ClassNotFoundException, SQLException { + DbColumn col = (DbColumn) columnMap.get(columnName); if (col == null) { @@ -879,10 +885,6 @@ public final class DbMapping implements Updatable { } } - if (col == null) { - throw new SQLException("Column " + columnName + " not found in " + this); - } - columnMap.put(columnName, col); } @@ -890,12 +892,13 @@ public final class DbMapping implements Updatable { } /** + * Get a StringBuffer initialized to the first part of the select statement + * for objects defined by this DbMapping * + * @return the StringBuffer containing the first part of the select query * - * @return ... - * - * @throws SQLException ... - * @throws ClassNotFoundException ... + * @throws SQLException if the table meta data could not be retrieved + * @throws ClassNotFoundException if the JDBC driver class was not found */ public StringBuffer getSelect() throws SQLException, ClassNotFoundException { String sel = selectString; @@ -904,7 +907,22 @@ public final class DbMapping implements Updatable { return new StringBuffer(sel); } - StringBuffer s = new StringBuffer("SELECT * FROM "); + StringBuffer s = new StringBuffer("SELECT "); + + DbColumn[] cols = columns; + + if (cols == null) { + cols = getColumns(); + } + + for (int i = 0; i < cols.length; i++) { + s.append(cols[i].getName()); + if (i < cols.length-1) { + s.append(','); + } + } + + s.append(" FROM "); s.append(getTableName()); s.append(" "); @@ -974,6 +992,11 @@ public final class DbMapping implements Updatable { try { DbColumn col = getColumn(columnName); + // This is not a mapped column. In case of doubt, add quotes. + if (col == null) { + return true; + } + switch (col.getType()) { case Types.CHAR: case Types.VARCHAR: diff --git a/src/helma/objectmodel/db/Node.java b/src/helma/objectmodel/db/Node.java index 974f9ae5..ccc8da7d 100644 --- a/src/helma/objectmodel/db/Node.java +++ b/src/helma/objectmodel/db/Node.java @@ -148,45 +148,42 @@ public final class Node implements INode, Serializable { * Constructor used for nodes being stored in a relational database table. */ public Node(DbMapping dbm, ResultSet rs, DbColumn[] columns, WrappedNodeManager nmgr) - throws SQLException, IOException { + throws SQLException, IOException { this.nmgr = nmgr; // see what prototype/DbMapping this object should use dbmap = dbm; - String protoField = dbmap.getPrototypeField(); - - if (protoField != null) { - String protoName = rs.getString(protoField); - - if (protoName != null) { - dbmap = nmgr.getDbMapping(protoName); - - if (dbmap == null) { - // invalid prototype name! - System.err.println("Warning: Invalid prototype name: " + protoName + - " - using default"); - dbmap = dbm; - } - } - } - - setPrototype(dbmap.getTypeName()); - - id = rs.getString(dbmap.getIDField()); - - // checkWriteLock (); - String nameField = dbmap.getNameField(); - - name = (nameField == null) ? id : rs.getString(nameField); - - if ((name == null) || (name.length() == 0)) { - name = dbmap.getTypeName() + " " + id; - } - created = lastmodified = System.currentTimeMillis(); for (int i = 0; i < columns.length; i++) { + + // set prototype? + if (columns[i].isPrototypeField()) { + String protoName = rs.getString(i+1); + + if (protoName != null) { + dbmap = nmgr.getDbMapping(protoName); + + if (dbmap == null) { + // invalid prototype name! + System.err.println("Warning: Invalid prototype name: " + protoName + + " - using default"); + dbmap = dbm; + } + } + } + + // set id? + if (columns[i].isIdField()) { + id = rs.getString(i+1); + } + + // set name? + if (columns[i].isNameField()) { + name = rs.getString(i+1); + } + Relation rel = columns[i].getRelation(); if ((rel == null) || @@ -199,7 +196,7 @@ public final class Node implements INode, Serializable { switch (columns[i].getType()) { case Types.BIT: - newprop.setBooleanValue(rs.getBoolean(columns[i].getName())); + newprop.setBooleanValue(rs.getBoolean(i+1)); break; @@ -207,21 +204,21 @@ public final class Node implements INode, Serializable { case Types.BIGINT: case Types.SMALLINT: case Types.INTEGER: - newprop.setIntegerValue(rs.getLong(columns[i].getName())); + newprop.setIntegerValue(rs.getLong(i+1)); break; case Types.REAL: case Types.FLOAT: case Types.DOUBLE: - newprop.setFloatValue(rs.getDouble(columns[i].getName())); + newprop.setFloatValue(rs.getDouble(i+1)); break; case Types.DECIMAL: case Types.NUMERIC: - BigDecimal num = rs.getBigDecimal(columns[i].getName()); + BigDecimal num = rs.getBigDecimal(i+1); if (num == null) { break; @@ -237,7 +234,7 @@ public final class Node implements INode, Serializable { case Types.VARBINARY: case Types.BINARY: - newprop.setStringValue(rs.getString(columns[i].getName())); + newprop.setStringValue(rs.getString(i+1)); break; @@ -245,9 +242,9 @@ public final class Node implements INode, Serializable { case Types.LONGVARCHAR: try { - newprop.setStringValue(rs.getString(columns[i].getName())); + newprop.setStringValue(rs.getString(i+1)); } catch (SQLException x) { - Reader in = rs.getCharacterStream(columns[i].getName()); + Reader in = rs.getCharacterStream(i+1); char[] buffer = new char[2048]; int read = 0; int r = 0; @@ -272,22 +269,22 @@ public final class Node implements INode, Serializable { case Types.CHAR: case Types.VARCHAR: case Types.OTHER: - newprop.setStringValue(rs.getString(columns[i].getName())); + newprop.setStringValue(rs.getString(i+1)); break; case Types.DATE: - newprop.setDateValue(rs.getDate(columns[i].getName())); + newprop.setDateValue(rs.getDate(i+1)); break; case Types.TIME: - newprop.setDateValue(rs.getTime(columns[i].getName())); + newprop.setDateValue(rs.getTime(i+1)); break; case Types.TIMESTAMP: - newprop.setDateValue(rs.getTimestamp(columns[i].getName())); + newprop.setDateValue(rs.getTimestamp(i+1)); break; @@ -298,7 +295,7 @@ public final class Node implements INode, Serializable { // continue; default: - newprop.setStringValue(rs.getString(columns[i].getName())); + newprop.setStringValue(rs.getString(i+1)); break; } @@ -326,6 +323,15 @@ public final class Node implements INode, Serializable { newprop.dirty = false; } + // set the prototype from the dbmap, + // which was possibly modified while reading the resultset + setPrototype(dbmap.getTypeName()); + + // If name was not set from resultset, create a synthetical name now. + if ((name == null) || (name.length() == 0)) { + name = dbmap.getTypeName() + " " + id; + } + // again set created and lastmodified. This is because // lastmodified has been been updated, and we want both values to // be identical to show that the node hasn't been changed since @@ -1585,7 +1591,7 @@ public final class Node implements INode, Serializable { // do not fetch subnodes for nodes that haven't been persisted yet or are in // the process of being persistified - except if "manual" subnoderelation is set. - if (subRel.aggressiveLoading && + if (subRel.aggressiveLoading && subRel.getGroup() == null && (((state != TRANSIENT) && (state != NEW)) || (subnodeRelation != null))) { // we don't want to load *all* nodes if we just want to count them