@@ -45,6 +45,20 @@ object KotlinTypeMapper {
4545 /* * Default VARCHAR width for string-backed `field.enum` storage (v1). */
4646 const val ENUM_VARCHAR_LEN = 64
4747
48+ /* *
49+ * Threshold beyond which a [StringField]'s `@maxLength` is treated as unbounded text and
50+ * emitted as Exposed `text(name)` rather than `varchar(name, N)`. Chosen at 4000 — the
51+ * customary Postgres inline-VARCHAR cutoff (TOAST boundary); larger values are better
52+ * served by `TEXT`, which Exposed maps to `text(name)`.
53+ */
54+ private const val VARCHAR_TEXT_THRESHOLD = 4000
55+
56+ /* * Sentinel `@kind` value on a [StringField] that forces `text(name)` emission. */
57+ private const val KIND_TEXT = " text"
58+
59+ /* * Attribute name read off a [StringField] to dispatch to `text(name)` (kind=text). */
60+ private const val ATTR_KIND = " kind"
61+
4862 /* *
4963 * Compute the generated Kotlin enum-class name for an [EnumField] hung off [entity].
5064 *
@@ -105,7 +119,16 @@ object KotlinTypeMapper {
105119 * for nested object.value fields without mutating the underlying MetaField.
106120 */
107121 fun exposedColumnSpec (field : MetaField <* >, colName : String ): String = when (field) {
108- is StringField -> " varchar(\" $colName \" , ${stringMaxLength(field)} )"
122+ is StringField -> {
123+ // Dispatch to Exposed `text(name)` when the field is declared as unbounded text:
124+ // (1) explicit `@kind: "text"` opt-in, OR
125+ // (2) `@maxLength` exceeds the VARCHAR/TEXT cutoff (Postgres TOAST boundary).
126+ // Otherwise emit `varchar(name, N)` with N defaulting to 255.
127+ val kind = stringAttr(field, ATTR_KIND )
128+ val maxLen = stringMaxLength(field)
129+ if (kind == KIND_TEXT || maxLen > VARCHAR_TEXT_THRESHOLD ) " text(\" $colName \" )"
130+ else " varchar(\" $colName \" , $maxLen )"
131+ }
109132 is IntegerField -> " integer(\" $colName \" )"
110133 is LongField -> " long(\" $colName \" )"
111134 is DoubleField -> " double(\" $colName \" )"
@@ -127,6 +150,20 @@ object KotlinTypeMapper {
127150 )
128151 }
129152
153+ /* *
154+ * Best-effort read of a named string attribute (own-only) on [field]. Returns null when
155+ * the attribute is absent, throws during lookup, or isn't a [com.metaobjects.attr.MetaAttribute].
156+ * Used for non-typed dispatch keys (e.g. `@kind`) that aren't part of the registered
157+ * StringField attribute schema.
158+ */
159+ private fun stringAttr (field : MetaField <* >, name : String ): String? {
160+ if (! field.hasMetaAttr(name, false )) return null
161+ val attr = runCatching {
162+ field.getMetaAttr(name, false )
163+ }.getOrNull() as ? com.metaobjects.attr.MetaAttribute <* > ? : return null
164+ return attr.valueAsString
165+ }
166+
130167 /* * Resolve @maxLength on a StringField; default 255. */
131168 private fun stringMaxLength (field : StringField ): Int {
132169 if (! field.hasMetaAttr(StringField .ATTR_MAX_LENGTH , false )) return 255
0 commit comments