View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Copyright (C) 2000 William Chesters
6    *
7    * Part of Melati (http://melati.org), a framework for the rapid
8    * development of clean, maintainable web applications.
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     William Chesters <williamc At paneris.org>
42   *     http://paneris.org/~williamc
43   *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
44   */
45  
46  package org.melati.poem;
47  
48  import java.io.PrintStream;
49  import java.sql.PreparedStatement;
50  import java.sql.ResultSet;
51  import java.sql.SQLException;
52  import java.util.Enumeration;
53  
54  import org.melati.poem.dbms.Dbms;
55  import org.melati.poem.util.EmptyEnumeration;
56  import org.melati.poem.util.StringUtils;
57  
58  /**
59   * Abstract {@link Table} column which is extended by the generated classes.
60   *
61   * @author WilliamC At paneris.org
62   * @param <T> The type of the Column
63   * 
64   */
65  public abstract class Column<T> implements FieldAttributes<T> {
66    @SuppressWarnings("rawtypes")
67    private Table table = null;
68    private String name;
69    private String quotedName;
70    private SQLPoemType<T> type;
71    private DefinitionSource definitionSource;
72    private ColumnInfo info = null;
73  
74    /**
75     * Constructor.
76     * @param table this column belongs to
77     * @param name of this Column
78     * @param type datatype
79     * @param definitionSource where it is being defined from
80     */
81    public Column(
82      Table<?> table,
83      String name,
84      SQLPoemType<T> type,
85      DefinitionSource definitionSource) {
86      this.table = table;
87      this.name = name;
88      this.quotedName = table.getDatabase().quotedName(name);
89      this.type = type;
90      this.definitionSource = definitionSource;
91    }
92  
93    // 
94    // ================
95    //  Initialisation
96    // ================
97    // 
98  
99    /**
100    * @return the underlying Dbms
101    */
102   Dbms dbms() {
103     return getDatabase().getDbms();
104   }
105 
106   public <O> void unifyType(SQLPoemType<O> storeType, DefinitionSource source) {
107     PoemType<T> unified = dbms().canRepresent(storeType, type);
108     if (unified == null || !(unified instanceof SQLPoemType))
109       throw new TypeDefinitionMismatchException(this, storeType, source);
110 
111     type = (SQLPoemType<T>) unified;
112   }
113 
114   void assertMatches(ResultSet colDesc)
115       throws SQLException, TypeDefinitionMismatchException {
116     PoemType<?> dbType = getDatabase().defaultPoemTypeOfColumnMetaData(colDesc);
117 
118     if (dbms().canRepresent(dbType, type) == null)
119       throw new TypeDefinitionMismatchException(
120         this,
121         dbType,
122         DefinitionSource.sqlMetaData);
123   }
124 
125   @SuppressWarnings("unchecked")
126   void setColumnInfo(ColumnInfo columnInfo) {
127     try {
128       unifyType(columnInfo.getType(), DefinitionSource.infoTables);
129       columnInfo.setColumn(this);
130       if (columnInfo.getDisplaylevel() == DisplayLevel.primary)
131         table.setDisplayColumn(this);
132       if (columnInfo.getSearchability() == Searchability.primary)
133         table.setSearchColumn(this);
134       info = columnInfo;
135       table.notifyColumnInfo(info);
136     } catch (Exception e) {
137       throw new UnexpectedExceptionPoemException(
138         e,
139         "Setting column info for " + name + " to " + columnInfo);
140     }
141   }
142 
143   protected DisplayLevel defaultDisplayLevel() {
144     return DisplayLevel.summary;
145   }
146 
147   protected Searchability defaultSearchability() {
148     return Searchability.yes;
149   }
150 
151   protected Integer defaultDisplayOrderPriority() {
152     return null;
153   }
154 
155   protected boolean defaultSortDescending() {
156     return false;
157   }
158 
159   protected String defaultDisplayName() {
160     return StringUtils.capitalised(getName());
161   }
162 
163   protected int defaultDisplayOrder() {
164     return 100;
165   }
166 
167   protected String defaultDescription() {
168     return null;
169   }
170 
171   protected boolean defaultUserEditable() {
172     return true;
173   }
174 
175   protected boolean defaultUserCreateable() {
176     return true;
177   }
178 
179   protected boolean defaultIndexed() {
180     return isTroidColumn();
181   }
182 
183   protected boolean defaultUnique() {
184     return isTroidColumn();
185   }
186 
187   /**
188    * @return the StandardIntegrityFix prevent
189    */
190   protected StandardIntegrityFix defaultIntegrityFix() {
191     return StandardIntegrityFix.prevent;
192   }
193 
194   protected int defaultWidth() {
195     return 20;
196   }
197 
198   protected int defaultHeight() {
199     return 1;
200   }
201 
202   protected int defaultPrecision() {
203     return 22;
204   }
205 
206   protected int defaultScale() {
207     return 2;
208   }
209 
210   protected String defaultRenderinfo() {
211     return null;
212   }
213 
214   @SuppressWarnings("unchecked")
215   void createColumnInfo() throws PoemException {
216     if (info == null) {
217       info = (ColumnInfo)getDatabase().
218           getColumnInfoTable().create(new Initialiser() {
219         public void init(Persistent g) throws AccessPoemException {
220           ColumnInfo i = (ColumnInfo)g;
221           i.setName(getName());
222           i.setDisplayname(defaultDisplayName());
223           i.setDisplayorder(defaultDisplayOrder());
224           i.setDescription(defaultDescription());
225           i.setDisplaylevel(defaultDisplayLevel());
226           i.setSearchability(defaultSearchability());
227           i.setSortdescending(defaultSortDescending());
228           i.setDisplayorderpriority(defaultDisplayOrderPriority());
229           i.setTableinfoTroid(table.tableInfoID());
230           i.setUsereditable(defaultUserEditable());
231           i.setUsercreateable(defaultUserCreateable());
232           i.setIndexed(defaultIndexed());
233           i.setUnique(defaultUnique());
234           i.setWidth(defaultWidth());
235           i.setHeight(defaultHeight());
236           i.setRenderinfo(defaultRenderinfo());
237           i.setIntegrityfix(defaultIntegrityFix());
238           i.setPrecision(defaultPrecision());
239           i.setScale(defaultScale());
240           getType().saveColumnInfo(i);
241         }
242       });
243 
244       // FIXME Repeating this in setColumnInfo(ColumnInfo) is a bad sign
245 
246       if (defaultDisplayLevel() == DisplayLevel.primary)
247         table.setDisplayColumn(this);
248       if (defaultSearchability() == Searchability.primary)
249         table.setSearchColumn(this);
250     }
251   }
252 
253   void unifyWithIndex(String indexName, ResultSet index) throws SQLException, 
254                               IndexUniquenessPoemException {
255     boolean indexUnique = !index.getBoolean("NON_UNIQUE");
256     if (indexUnique != getUnique()) 
257         throw new IndexUniquenessPoemException(
258           this,
259           indexName,
260           getUnique());
261   }
262 
263   // 
264   // ===========
265   //  Accessors
266   // ===========
267   // 
268 
269   /**
270    * @return the Database our table is in
271    */
272   public final Database getDatabase() {
273     return getTable().getDatabase();
274   }
275 
276   /**
277    * @return our Table
278    */
279   @SuppressWarnings("unchecked")
280   public final Table<Persistent> getTable() {
281     return table;
282   }
283 
284   final void setTable(Table<?> table) {
285     this.table = table;
286   }
287 
288   /**
289    * {@inheritDoc}
290    * @see org.melati.poem.FieldAttributes#getName()
291    */
292   public final String getName() {
293     return name;
294   }
295 
296   /**
297    * @return the name quoted appropriately for the DBMS
298    */
299   public final String quotedName() {
300     return quotedName;
301   }
302 
303   /**
304    * @return the name in table.column notation
305    */
306   public final String fullQuotedName() {
307     return table.quotedName() + "." + quotedName;
308   }
309 
310   /**
311    * Return a human readable name from the metadata.
312    * 
313    * {@inheritDoc}
314    * @see org.melati.poem.FieldAttributes#getDisplayName()
315    */
316   public final String getDisplayName() {
317     return info.getDisplayname();
318   }
319 
320   /**
321    * {@inheritDoc}
322    * @see org.melati.poem.FieldAttributes#getDescription()
323    */
324   public final String getDescription() {
325     return info.getDescription();
326   }
327 
328   /**
329    * The troid (<TT>id</TT>) of the column's entry in the <TT>columninfo</TT>
330    * table.  It will always have one (except during initialisation, which the
331    * application programmer will never see).
332    * @return the troid of our record in the columnInfo table
333    */
334   final Integer columnInfoID() {
335     return info == null ? null : info.troid();
336   }
337 
338   /**
339    * @return the metadata record for this Column
340    */
341   public final ColumnInfo getColumnInfo() {
342     return info;
343   }
344 
345   /**
346    * @return the defined or default DsiplayLevel
347    */
348   public DisplayLevel getDisplayLevel() {
349     return info == null ? defaultDisplayLevel() : info.getDisplaylevel();
350   }
351 
352   /**
353    * @param level the DisplayLevel to set
354    */
355   public void setDisplayLevel(DisplayLevel level) {
356     if (info != null)
357       info.setDisplaylevel(level);
358   }
359 
360   /**
361    * @return our defined or default Searchabillity
362    */
363   public Searchability getSearchability() {
364     return info == null ? defaultSearchability() : info.getSearchability();
365   }
366 
367   /**
368    * @param searchability the Searchability to set
369    */
370   public void setSearchability(Searchability searchability) {
371     if (info != null)
372       info.setSearchability(searchability);
373   }
374 
375   /**
376    * {@inheritDoc}
377    * @see org.melati.poem.FieldAttributes#getUserEditable()
378    */
379   public final boolean getUserEditable() {
380     return !isTroidColumn()
381       && (info == null || info.getUsereditable().booleanValue());
382   }
383 
384   /**
385    * {@inheritDoc}
386    * @see org.melati.poem.FieldAttributes#getUserCreateable()
387    */
388   public final boolean getUserCreateable() {
389     return !isTroidColumn()
390       && (info == null || info.getUsercreateable().booleanValue());
391   }
392 
393   /**
394    * @return the SQLPoemType of this Column
395    */
396   public final SQLPoemType<T> getSQLType() {
397     return type;
398   }
399 
400   /**
401    * {@inheritDoc}
402    * @see org.melati.poem.FieldAttributes#getType()
403    */
404   public final PoemType<T> getType() {
405     return type;
406   }
407 
408   /**
409    * @return whether this is a Troid Column
410    */
411   public final boolean isTroidColumn() {
412     return getType() instanceof TroidPoemType;
413   }
414 
415   /**
416    * A Deleted Column is a Column which signal whether 
417    * the record has been soft-deleted.
418    * @return whether this is a Deleted Column
419    */
420   public final boolean isDeletedColumn() {
421     return getType() instanceof DeletedPoemType;
422   }
423 
424   /**
425    * {@inheritDoc}
426    * @see org.melati.poem.FieldAttributes#getIndexed()
427    */
428   public final boolean getIndexed() {
429     return getUnique() || info.getIndexed().booleanValue();
430   }
431 
432   /**
433    * @return whether this Column's values are unique in this table
434    */
435   public final boolean getUnique() {
436     return isTroidColumn() || info.getUnique().booleanValue();
437   }
438 
439   /**
440    * @return the specified or default IntegrityFix
441    */
442   public IntegrityFix getIntegrityFix() {
443     IntegrityFix it = info.getIntegrityfix();
444     return it == null ? defaultIntegrityFix() : it;
445   }
446 
447   /**
448    * @param fix the IntegrityFix to set
449    */
450   public void setIntegrityFix(StandardIntegrityFix fix) {
451     info.setIntegrityfix(fix);
452   }
453 
454   /**
455    * {@inheritDoc}
456    * @see org.melati.poem.FieldAttributes#getRenderInfo()
457    */
458   public final String getRenderInfo() {
459     return info.getRenderinfo();
460   }
461 
462   /**
463    * {@inheritDoc}
464    * @see org.melati.poem.FieldAttributes#getWidth()
465    */
466   public final int getWidth() {
467     return info.getWidth().intValue();
468   }
469 
470   /**
471    * {@inheritDoc}
472    * @see org.melati.poem.FieldAttributes#getHeight()
473    */
474   public final int getHeight() {
475     return info.getHeight().intValue();
476   }
477 
478   /**
479    * @return the set or default DisplayOrderPriority
480    */
481   public final Integer getDisplayOrderPriority() {
482     return info == null ? null : info.getDisplayorderpriority();
483   }
484 
485   /**
486    * Defaults to false.
487    * @return whether this Column should be sorted in descending order 
488    */
489   public final boolean getSortDescending() {
490     return info.getSortdescending() == null
491       ? false
492       : info.getSortdescending().booleanValue();
493   }
494 
495   // 
496   // ===========
497   //  Utilities
498   // ===========
499   // 
500 
501   /**
502    * {@inheritDoc}
503    * @see java.lang.Object#toString()
504    */
505   public String toString() {
506     return table.getName()
507       + "."
508       + name
509       + ": "
510       + getType().toString()
511       + " (from "
512       + definitionSource
513       + ")";
514   }
515 
516   /**
517    * Print information about the structure of the Column to stdout.
518    */
519   public void dump() {
520     dump(System.out);
521   }
522 
523   /**
524    * Print information to PrintStream. 
525    * 
526    * @param ps PrintStream to dump to
527    */
528   public void dump(PrintStream ps) {
529     ps.println(toString());
530   }
531 
532   /**
533    * 
534    * @param raw An object with an equivalent SQL type
535    * @return the SQL euals clause that would capture equality with the raw
536    */
537   public String eqClause(Object raw) {
538     return fullQuotedName()
539       + (raw == null ? " IS NULL" : " = " + type.quotedRaw(raw));
540   }
541 
542   private PreparedStatementFactory selectionWhereEq = null;
543 
544   private PreparedStatementFactory statementWhereEq() {
545     if (selectionWhereEq == null)
546       selectionWhereEq =
547         new PreparedStatementFactory(
548           getDatabase(),
549           getTable().selectionSQL(
550             getTable().quotedName(),
551             fullQuotedName()
552               + " = "
553               + dbms().preparedStatementPlaceholder(getType()),
554             null,
555             false,
556             true));
557 
558     return selectionWhereEq;
559   }
560 
561   ResultSet resultSetWhereEq(Object raw) {
562     SessionToken token = PoemThread.sessionToken();
563     PreparedStatement ps =
564       statementWhereEq().preparedStatement(token.transaction);
565     type.setRaw(ps, 1, raw);
566     return statementWhereEq().resultSet(token, ps);
567   }
568 
569   /**
570    * Not used in Melati or PanEris tree.
571    * 
572    * @param raw value 
573    * @return an Enumeration of Integers 
574    */
575   /*
576   Enumeration troidSelectionWhereEq(Object raw) {
577     return new ResultSetEnumeration(resultSetWhereEq(raw)) {
578       public Object mapped(ResultSet rs) throws SQLException {
579         return new Integer(rs.getInt(1));
580       }
581     };
582   }
583   */
584   
585   /**
586    * Get rows where column equal to value.
587    * 
588    * @param raw a raw value such as a String
589    * @return an enumeration of Persistents
590    */
591   public Enumeration<Persistent> selectionWhereEq(Object raw) {
592     return new ResultSetEnumeration<Persistent>(resultSetWhereEq(raw)) {
593       public Object mapped(ResultSet rs) throws SQLException {
594         return getTable().getObject(rs.getInt(1));
595       }
596     };
597   }
598 
599   /**
600    * Return the first one found or null if not found. 
601    * 
602    * @param raw Object of correct type for this Column
603    * @return the first one found based upon default ordering
604    */
605   public Persistent firstWhereEq(Object raw) {
606     Enumeration<Persistent> them = selectionWhereEq(raw);
607     return them.hasMoreElements() ? (Persistent)them.nextElement() : null;
608   }
609 
610   /**
611    * Create a new CachedSelection of objects equal to this raw parameter. 
612    * 
613    * @param raw Object of correct type for this Column
614    * @return a new CachedSelection of objects equal to raw.
615    */
616   @SuppressWarnings({ "rawtypes", "unchecked" })
617   public CachedSelection cachedSelectionWhereEq(Object raw) {
618     return new CachedSelection(getTable(), eqClause(raw), null);
619   }
620 
621   // 
622   // =======================================
623   //  Reading/setting the column in records
624   // =======================================
625   // 
626 
627  /**
628   * Retrieves the field value, with locking,
629   * for this {@link Column}.
630   * 
631   * @param g  the {@link Persistent} to read
632   * @return the Object itself
633   * @throws AccessPoemException 
634   *         if the current <code>AccessToken</code> 
635   *         does not confer read access rights
636   */
637   public abstract Object getRaw(Persistent g) throws AccessPoemException;
638 
639  /**
640   * Retrieves the field value, without locking,
641   * for this <code>Column</code>.
642   * 
643   * @param g  the {@link Persistent} to read
644   * @return the Object without checks
645   */
646   public abstract Object getRaw_unsafe(Persistent g);
647 
648  /**
649   * Sets the field value, with locking,
650   * for this <code>Column</code>.
651   * 
652   * @param g  the {@link Persistent} to modify
653   * @param raw the value to set the field to 
654   * @throws AccessPoemException 
655   *         if the current <code>AccessToken</code> 
656   *         does not confer write access rights
657   * @throws ValidationPoemException 
658   *         if the raw value is not valid
659   */
660   public abstract void setRaw(Persistent g, Object raw)
661     throws AccessPoemException, ValidationPoemException;
662 
663  /**
664   * Sets the field value, without locking,
665   * for this <code>Column</code>.
666   * 
667   * @param g  the {@link Persistent} to modify
668   * @param raw the value to set the field to 
669   */
670   public abstract void setRaw_unsafe(Persistent g, Object raw);
671 
672  /**
673   * Retrieves the field value, with locking and  access control 
674   * for this <code>Column</code>.
675   * 
676   * @param g  the {@link Persistent} to modify
677   * @return either the value or what is represented by the value
678   * @throws AccessPoemException 
679   *         if the current <code>AccessToken</code> 
680   *         does not confer read access rights
681   * @throws PoemException 
682   *         if any problem occurs
683   */
684   public abstract Object getCooked(Persistent g)
685     throws AccessPoemException, PoemException;
686 
687  /**
688   * Sets the field value, with locking, access control 
689   * and validation for this <code>Column</code>.
690   * 
691   * @param g  the {@link Persistent} to modify
692   * @param cooked  the value to set
693   * @throws AccessPoemException 
694   *         if the current <code>AccessToken</code> 
695   *         does not confer read access rights
696   * @throws ValidationPoemException 
697   *         if the value is not valid
698   */
699   public abstract void setCooked(Persistent g, Object cooked)
700     throws AccessPoemException, ValidationPoemException;
701 
702   /**
703    * Thrown when any unforeseen problem arises loading a {@link Column}.
704    */
705   public static class LoadException extends UnexpectedExceptionPoemException {
706 
707     private Column<?> column;
708 
709     /**
710      * Constructor.
711      * @param column Column relevant to
712      * @param problem the Exception
713      */
714     public LoadException(Column<?> column, Exception problem) {
715       super(problem);
716       this.column = column;
717     }
718 
719     /** @return Returns the message */
720     public String getMessage() {
721       return "An unexpected problem arose loading "
722         + column
723         + " from the "
724         + "database:\n"
725         + subException;
726     }
727 
728   
729     /**
730      * @return Returns the column.
731      */
732      @SuppressWarnings("rawtypes")
733     protected Column getColumn() {
734       return column;
735     }
736   }
737 
738   /**
739    * Load a Persistent field from a column of a ResultSet.
740    * 
741    * TODO Double validation
742    * @param rs A <code>ResultSet</code>containing the value(s) to load
743    * @param rsCol The index in the <tt>ResultSet</tt> of this {link column}
744    * @param g The {@link Persistent} to load db values into
745    * @throws LoadException
746    */
747   void load_unsafe(ResultSet rs, int rsCol, Persistent g)
748     throws LoadException {
749     try {
750       setRaw_unsafe(g, type.getRaw(rs, rsCol));
751     } catch (Exception e) {
752       throw new LoadException(this, e);
753     }
754   }
755 
756   /**
757    * Set value in a PreparedStatement which is to be used to save to database.
758    *
759    * TODO Double validation
760    * @param g The {@link Persistent} containing unsaved values
761    * @param ps <tt>PreparedStatement</tt> to save this column
762    * @param psCol index of this Column in the PreparedStatement
763    */
764   void save_unsafe(Persistent g, PreparedStatement ps, int psCol) {
765     try {
766       type.setRaw(ps, psCol, getRaw_unsafe(g));
767     } catch (Exception e) {
768       throw new FieldContentsPoemException(this, e); 
769     }
770   }
771 
772   // 
773   // ============
774   //  Operations
775   // ============
776   // 
777 
778   /**
779    * Return a Field of the same type as this Column from the Persistent.
780    * @param g the Persistent
781    * @return a Field
782    */
783   public abstract Field<T> asField(Persistent g);
784 
785   /**
786    * Return a Field of the same type as this Column with default attributes.
787    * @return the empty Field
788    */
789   public Field<T> asEmptyField() {
790     return new Field<T>((T) null, this);
791   }
792 
793   /**
794    * Thrown when any unforseen problem arises setting the value 
795    * of a {@link Column}.
796    */
797   public static class SettingException extends NormalPoemException {
798     /** The Persistent to which this Column belongs. */
799     public Persistent persistent;
800     /** The Column setting which caused the problem. */
801     public Column<?> column;
802     /** The description of the Column. */
803     public String columnDesc;
804 
805     /**
806      * Constructor.
807      * @param persistent the Persistent with the problem
808      * @param column he Column with the problem
809      * @param trouble the problem
810      */
811     public SettingException(Persistent persistent, Column<?> column, Exception trouble) {
812       super(trouble);
813       this.persistent = persistent;
814       this.column = column;
815       columnDesc =
816         "field `"
817           + column.getDisplayName()
818           + "' in object `"
819           + persistent.displayString()
820           + "' of type `"
821           + column.getTable().getDisplayName()
822           + "'";
823     }
824 
825     /** @return the message */
826     public String getMessage() {
827       return "Unable to set " + columnDesc + "\n" + subException;
828     }
829   }
830 
831   /**
832    * Set the value from its String representation, if possible.
833    * 
834    * Throws SettingException if the String value cannot be 
835    * converted to the appropriate type 
836    * @param g the Persistent to alter
837    * @param rawString the String representation of the value to set
838    */
839   public void setRawString(Persistent g, String rawString) {
840     Object raw;
841     try {
842       raw = getType().rawOfString(rawString);
843     } catch (Exception e) {
844       throw new SettingException(g, this, e);
845     }
846     setRaw(g, raw);
847   }
848 
849   /**
850    * Return an Enumeration of {@link Persistent}s from the 
851    * Table this column refers to, if this is a reference column, 
852    * otherwise the Empty Enumeration.
853    * @param object A persistent of the type referred to by this column
854    * @return an Enumeration {@link Persistent}s referencing this Column of the Persistent
855    */
856   public Enumeration<Persistent> referencesTo(Persistent object) {
857     if (getType() instanceof PersistentReferencePoemType)
858       if(((PersistentReferencePoemType) getType()).targetTable() == object.getTable()) { 
859         if (getType() instanceof ReferencePoemType)
860           return selectionWhereEq(object.troid());
861         else if (getType() instanceof StringKeyReferencePoemType)
862           return selectionWhereEq(
863               object.getField(
864                   ((StringKeyReferencePoemType)getType()).targetKeyName()).getRawString());
865         else throw new PoemBugPoemException("Unanticipated type " + getType());
866       }
867     return new EmptyEnumeration<Persistent>();
868   }
869 
870   /**
871    * Ensures a row exists for which this column matches when compared with
872    * the given {@link Persistent}.
873    * 
874    * The given object is used to create a new row if
875    * necessary, in which case it will be assigned the next troid and
876    * cached.
877    * @param orCreate the Persistent to use as criteria and ensure
878    * @return the existing or newly created Persistent
879    */
880   public Persistent ensure(Persistent orCreate) {
881     Persistent there = firstWhereEq(getRaw_unsafe(orCreate));
882     if (there == null) {
883       getTable().create(orCreate);
884       return orCreate;
885     } else
886       return there;
887   }
888 
889   
890   /**
891    * Find the next free value in an Integer column.
892    * 
893    * This is not used in Melati, but is used in Bibliomania. 
894    * Throws AppBugPoemException if this Column is not an Integer column.
895    * 
896    * @param whereClause the SQL fragment to use
897    * @return the incremented value 
898    * @since 04/05/2000
899    */
900   public int firstFree(String whereClause) {
901     if (! (getType() instanceof IntegerPoemType)) 
902       throw new AppBugPoemException("firstFree called on a non Integer column");
903     getTable().readLock();
904     String querySelection = 
905         quotedName
906       + " + 1 "
907       + "FROM "
908       + getTable().quotedName()
909       + " AS t1 "
910       + "WHERE "
911       + (whereClause == null ? "" : "(t1." + whereClause + ") AND ")
912       + "NOT EXISTS ("
913       + "SELECT * FROM "
914       + getTable().quotedName()
915       + " AS t2 "
916       + "WHERE "
917       + (whereClause == null ? "" : "(t2." + whereClause + ") AND ")
918       + "t2."
919       + quotedName
920       + " = t1."
921       + quotedName
922       + " + 1) ";
923 
924     String query = getDatabase().getDbms().selectLimit(querySelection, 1); 
925     ResultSet results = getDatabase().sqlQuery(query);
926     try {
927       if (results.next())
928         return results.getInt(1);
929       else
930         return 0;
931     } catch (SQLException e) {
932       throw new SQLSeriousPoemException(e);
933     }
934   }
935 
936   /**
937    * @param colDescs from DatabaseMetaData.getColumns, with cursor at current row
938    * @throws SQLException 
939    */
940   public void unifyWithMetadata(ResultSet colDescs) throws SQLException {
941     if (info == null)
942       return;
943     String remarks = colDescs.getString("REMARKS");
944     if (getDescription() == null) {
945       if (remarks != null && !remarks.trim().equals("")) {
946         info.setDescription(remarks);
947         getDatabase().log("Adding comment to column " + table + "." + name + 
948             " from SQL metadata:" + remarks);
949       }
950     } else {
951       if (!this.getDescription().equals(remarks)) {
952         String sql = this.dbms().alterColumnAddCommentSQL(this, null); 
953         if (sql != null)
954           this.getDatabase().modifyStructure(sql);          
955       }
956     }
957       
958     
959   }
960   
961 }