View Javadoc

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