Skip to content

feat: EXPOSED-418 Support comments on table columns#2728

Open
obabichevjb wants to merge 2 commits intomainfrom
obabichev/exposed-418-comments-on-tables
Open

feat: EXPOSED-418 Support comments on table columns#2728
obabichevjb wants to merge 2 commits intomainfrom
obabichev/exposed-418-comments-on-tables

Conversation

@obabichevjb
Copy link
Collaborator

Description

Add support for database comments on table columns, allowing developers to document column purposes directly in the database schema using the .comment() extension function.

  • Added .comment(text: String?) extension function for columns with proper API documentation
  • Implemented database-specific comment DDL generation:
    • PostgreSQL/Oracle: COMMENT ON COLUMN table.column IS 'text' statements
    • MySQL/MariaDB: Inline COLUMN_NAME TYPE COMMENT 'text' syntax
    • H2: Comment support in MySQL/MariaDB compatibility modes only
    • SQLite/SQL Server: Comments not supported (gracefully handled)
  • Extended schema migration to detect and apply comment changes via ColumnDiff
  • Enhanced metadata reading to fetch existing comments from database:
    • JDBC: Added comment support with Oracle-specific enrichment using ALL_COL_COMMENTS catalog
    • R2DBC: Added REMARKS column to all database metadata queries
  • Preserved backward compatibility with old .withDefinition("COMMENT") API

Type of Change

Please mark the relevant options with an "X":

  • New feature

Affected databases:

  • MariaDB
  • Mysql5
  • Mysql8
  • Oracle
  • Postgres
  • SqlServer (not supported, gracefully handled)
  • H2 (MySQL/MariaDB modes only)
  • SQLite (not supported, gracefully handled)

Related Issues

Support comments on tables

@obabichevjb obabichevjb requested review from bog-walk and e5l February 3, 2026 13:29
Comment on lines +113 to +117
/**
* Returns SQL statements to add/update a comment on the specified column.
* Returns empty list if comments are not supported.
*/
fun commentOnColumn(column: Column<*>, comment: String?): List<String> = emptyList()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A suggestion:

All the other schema DDL methods in this class are named with an action/verb and a noun.
I think it would be clearer to change the name to something like:
addColumnComment()
setColumnComment()
modifyColumnComment()

listOf("ALTER TABLE $tableName ALTER COLUMN $columnDef")
} else {
if (comment != null) {
exposedLogger.warn("H2 doesn't support comments on columns. The comment on column of table $tableName could not be added.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Maybe we could more that "H2 supports comments on columns only in MySQL/MariaDB mode..."

Comment on lines +515 to +516
val tableName = tr.identity(column.table)
val columnName = tr.identity(column)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on how this is used in the string below, would it not be equivalent to just using tr.fullIdentity(column)?

Comment on lines +479 to +480
val tableName = tr.identity(column.table)
val columnName = tr.identity(column)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as previously, why not replace with single tr.fullIdentity(column)?

it.dbDefaultValue = this.dbDefaultValue
it.isDatabaseGenerated = this.isDatabaseGenerated
it.extraDefinitions = this.extraDefinitions
it.columnComment = this.columnComment
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add similar line to the 3 other places where we don't want to lose existing column settings by chaining:

  • Alias.kt -> private fun <T> Column<T>.clone()
  • Table.kt -> fun <T : Any> Column<T>.entityId()
  • Table.kt -> fun <T : Any> Column<T>.nullable()

""".trimIndent()
}

jdbcConnection.prepareStatement(sql).use { stmt ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this not all be replaced with existing metadata.connection.executeSQL()?
It's a private function, so it could be adjusted if placeholders are necessary.


// Apply migration
SchemaUtils.statementsRequiredToActualizeScheme(testTableV2).forEach { exec(it) }
commit()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, but it would be great if we didn't introduce more usage of this deprecated method in tests if we don't have to.

I would suggest creating a new test file in exposed-migration-r2dbc and just copying all these migration tests over.

Comment on lines +166 to +167
// Migration Tests

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as previously about not introducing more usage of this deprecated method in tests if we don't have to.

I would suggest creating a new test file in exposed-migration-jdbc and just copying all these migration tests over.

*/
fun <T> Column<T>.comment(text: String?): Column<T> = apply {
columnComment = text
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions about nullable text argument:

  • Is PostgreSQL the only db that accepts SQL NULL as a comment?
  • Does chaining .comment(null) actually remove the comment in other db? I didn't see example of this in tests, would it be possible to add a test?
  • Let's say a user had a previous db schema with comment on column. Now they want to remove comment. Is there an advantage to altering column property to have .comment(null) or is it same result as just removing .comment() entirely (like already seen in testRemoveComment())?

Comment on lines +20 to +36
/**
* Escapes special characters in comments to prevent SQL injection.
* Single quotes are doubled per SQL standard.
*/
protected fun String.escapeComment(): String = this.replace("'", "''")

/**
* Checks if the current dialect uses inline comments (MySQL syntax).
* Returns true for MySQL and H2 in MySQL/MariaDB compatibility modes.
*/
fun isInlineCommentDialect(): Boolean {
val isH2MysqlMode = currentDialect is H2Dialect && (
currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.MySQL ||
currentDialect.h2Mode == H2Dialect.H2CompatibilityMode.MariaDB
)
return currentDialect is MysqlDialect || isH2MysqlMode
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I see the value in adding these to the public API.
Maybe escapeComment() could have future general uses.
But isInlineCommentDialect() seems very unlikely and doesn't feel like it's in the right place. It's not actually used by any of the dialect classes directly and every use of it would require an odd cast first. Since DatabaseDialect is what is normally more accessible via currentDialect or db.dialect.

I think we should try to avoid noise in the public API and make these top-level internal functions instead.
Would also reduce the duplication of escapeComment() in descriptionDdl.
We can always open them up if requested later?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants