logo

abstract const class

haven::ColDef

sys::Obj
  haven::ColDef
//
// Copyright (c) 2008, John Sublett
// Licensed under the Academic Free License version 3.0
//
// History:
//   14 Jan 08  John Sublett  Creation
//

using sql

**
** ColDef defines the mapping of a field in a Type
** to a column in a relational table.
**
@serializable abstract const class ColDef
{

  **
  ** Protected no argument constructor for subclasses.
  **
  protected new make()
  {
  }

  **
  ** Make a new instance for the specified field.
  **
  protected new makeForField(HavenService haven, Field field)
  {
    this.haven = haven
    this.fieldName = field.qname
  }

  **
  ** Validate the field for this column from the
  ** default row.
  **
  virtual Void validate(Obj defaultRow)
  {
    // make sure the column has a default value if it
    // does not allow null
    if (!allowNull && !isRef)
    {
      def := field.get(defaultRow)
      if (def == null)
        throw HavenErr.make
          ("Default value required for fields that do not allow null. ($field.qname)")
    }
  }

  **
  ** Get the database column name.
  **
  virtual Str columnName()
  {
    return field.name
  }

  **
  ** Get the definition of the column as it appears in a CREATE TABLE
  ** statement.
  **
  virtual Str definition()
  {
    pk := isKey
    dialect := haven.dialect

    s := StrBuf.make(64)
    s.add(columnName).add(" ").add(sqlType)
    if (!pk && isUnique) s.add(" ").add(dialect.unique())
    if (!allowNull) s.add(" ").add(dialect.notNull())
    if (isAuto) s.add(" ").add(dialect.auto())

    return s.toStr
  }

  **
  ** Convert the field value of this column for the specified object
  ** to SQL text.
  **
  Str fieldToSql(Obj o)
  {
    fieldVal := field.get(o)
    if (fieldVal == null)
      return "NULL"
    else
      return toSql(fieldVal)
  }

  **
  ** Set the field for this column on the specified object by
  ** mapping the field value from the specified row.
  **
  abstract Void setFieldFromRow(Obj obj, Row row, HavenNamespace ns)

  **
  ** Get a value for this column type from the specified row.
  ** This is used by ref columns to read their database values.
  **
  abstract Obj rowValue(Row row, Col col)

  **
  ** Convert an object to its equivalent SQL syntax.
  **
  abstract Str toSql(Obj o)

  **
  ** Add a translated parameter or parameters to the specified
  ** map for use in a prepared statement.
  **
  virtual Obj paramValue(Obj o, Str:Obj params := null)
  {
    if (params != null) params[field.name] = o
    return o
  }

  **
  ** Get the SQL type definition for this translator
  **
  abstract Str sqlType()

  **
  ** Get the column name.
  **
  Str name()
  {
    return field.name
  }

  **
  ** Is this column part of the primary key?
  **
  Bool isKey()
  {
    return field.facet("key", false)
  }

  **
  ** Is this column value auto-generated?
  **
  Bool isAuto()
  {
    return field.facet("auto", false)
  }

  **
  ** Is this column a foreign key to another table?
  **
  Bool isRef()
  {
    return (field.of == Uri.type) && (field.facet("ref") != null)
  }

  **
  ** Is each value in this column unique?
  **
  Bool isUnique()
  {
    return field.facet("unique", false)
  }

  **
  ** Does this column allow null values to be persisted?
  **
  Bool allowNull()
  {
    return field.facet("allowNull", false);
  }

  **
  ** Is this column indexed?
  **
  Bool isIndexed()
  {
    return field.facet("index", false)
  }

  **
  ** Get the index definition for this column.
  ** If the column is not indexed, null is returned.
  **
  IndexDef index()
  {
    fv := field.facet("index", false);
    if (fv.type == Bool.type)
    {
      if (!fv as Bool) return null
      return IndexDef.make(
        haven,
        haven.indexName(field.parent, field.name),
        [this])
    }
    else if (fv.type == Str.type)
    {
      sv := fv as Str
      if (sv == "desc")
        return IndexDef.make(
          haven,
          haven.indexName(field.parent, field.name),
          [this], true)
      else
      {
        elems := sv.split(" ")
        if (elems.size == 1)
          return IndexDef.make(
            haven,
            haven.indexName(field.parent, elems[0]),
            [this])
        else
        {
          if (elems[1] == "desc")
            return IndexDef.make(
              haven,
              haven.indexName(field.parent, elems[0]),
              [this], true)
          else
            throw HavenErr.make("Invalid index facet: $sv")
        }
      }
    }
    else
      throw HavenErr.make("index facet must be a boolean or a string.")
  }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  **
  ** The haven used by this column.
  **
  const HavenService haven

  **
  ** The field that this column represents.  This field
  ** may be lazily loaded from the field qname if this
  ** column definition has been serialized.
  **
  const Str fieldName

  Field field()
  {
    return Slot.findField(fieldName)
  }

}