Skip to content

feat: #312 Add support for table modifiers and storage parameters#2756

Merged
obabichevjb merged 10 commits intomainfrom
claude/312-database-engines
Apr 14, 2026
Merged

feat: #312 Add support for table modifiers and storage parameters#2756
obabichevjb merged 10 commits intomainfrom
claude/312-database-engines

Conversation

@obabichevjb
Copy link
Copy Markdown
Collaborator

Description

Summary of the change: This PR adds support for database-specific table options through two new properties: modifiers and storageParameters.

Detailed description:

  • Why: Users requested the ability to specify MySQL/MariaDB storage engines (like ENGINE=MEMORY) and other database-specific table options. Issue Allow use of other database engines #312 specifically requested ENGINE support for MySQL/MariaDB, but the implementation is flexible enough to support other database-specific options.
  • What: Added two new open properties to the Table class:
    • modifiers: List<String> - for options appended after table definition (e.g., ENGINE=InnoDB, DEFAULT CHARSET=utf8mb4)
    • storageParameters: List<String> - for options in the WITH clause (e.g., fillfactor=70 for PostgreSQL)
  • How: Modified the createStatement() method in Table.kt to append these options to the generated CREATE TABLE DDL. The pattern follows the existing approach used for primaryKey property.

Example usage:

object Users : Table("users") {
    val id = integer("id")
    override val primaryKey = PrimaryKey(id)
    override val modifiers = listOf("ENGINE=InnoDB", "DEFAULT CHARSET=utf8mb4")
    override val storageParameters = listOf("fillfactor=70")
}

This generates:

CREATE TABLE users (id INT NOT NULL, PRIMARY KEY (id)) 
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 WITH (fillfactor=70)

Type of Change

Please mark the relevant options with an "X":

  • Bug fix
  • New feature
  • Documentation update

Updates/remove existing public API methods:

  • Is breaking change

Affected databases:

  • MariaDB
  • Mysql5
  • Mysql8
  • Oracle
  • Postgres
  • SqlServer
  • H2
  • SQLite

Checklist

  • Unit tests are in place
  • The build is green (including the Detekt check)
  • All public methods affected by my PR has up to date API docs
  • Documentation for my change is up to date

Related Issues

Closes #312

🤖 Generated with Claude Code

obabichevjb and others added 3 commits March 10, 2026 15:13
Add tests to demonstrate the need for tableOption() extension function
that allows specifying MySQL/MariaDB table options like ENGINE=MEMORY.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add two new open properties to the Table class:
- modifiers: List<String> - for table modifiers like ENGINE=InnoDB (MySQL/MariaDB)
- storageParameters: List<String> - for WITH clause parameters (PostgreSQL, SQL Server)

These properties allow users to specify database-specific table options:
- MySQL/MariaDB: ENGINE, DEFAULT CHARSET, etc.
- PostgreSQL: fillfactor, autovacuum_enabled, etc.
- SQL Server: various table options

Example usage:
```kotlin
object Users : Table("users") {
    val id = integer("id")
    override val primaryKey = PrimaryKey(id)
    override val modifiers = listOf("ENGINE=InnoDB", "DEFAULT CHARSET=utf8mb4")
    override val storageParameters = listOf("fillfactor=70")
}
```

This generates:
```sql
CREATE TABLE users (id INT NOT NULL, PRIMARY KEY (id))
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 WITH (fillfactor=70)
```

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comprehensive documentation to Working-with-Tables.topic explaining:
- Table modifiers for MySQL/MariaDB (ENGINE, CHARSET, etc.)
- Storage parameters for PostgreSQL (fillfactor, autovacuum, etc.)
- Common use cases and examples
- Notes about database-specific behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@obabichevjb obabichevjb force-pushed the claude/312-database-engines branch from 55176e8 to 730cc3a Compare March 10, 2026 14:13
@obabichevjb obabichevjb requested review from bog-walk and e5l March 10, 2026 14:15
@obabichevjb
Copy link
Copy Markdown
Collaborator Author

This feature was made using the new claude skill.

The skill works better, it stopped after creating a test and making proposal. It suggested one API, which actually could not be compiled.. and I suggested him the API which is in the PR.

I also added the section about website documentation to the skill, so it verifies if the changes should be reflected in the documentation.

obabichevjb and others added 2 commits March 10, 2026 15:26
…meters

Add comprehensive integration tests that actually create tables in the database:
- testTableWithMemoryEngineCreatedSuccessfully: Creates MEMORY engine table,
  inserts data, and verifies engine type via information_schema
- testTableWithCharsetModifierCreatedSuccessfully: Creates utf8mb4 table,
  inserts Unicode/emoji data, and verifies charset configuration
- testTableWithStorageParametersCreatedSuccessfully: Creates PostgreSQL table
  with fillfactor parameter and verifies via pg_class

These tests complement the DDL generation tests by ensuring the modifiers
actually work in real database operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@e5l e5l left a comment

Choose a reason for hiding this comment

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

Could you add a test to execute a query (both for JDBC and R2DBC)? It looks like an impossible combination of modifiers is possible

Comment thread .claude/skills/fix-bug/SKILL.md Outdated
Comment thread exposed-core/src/main/kotlin/org/jetbrains/exposed/v1/core/Table.kt Outdated
Comment thread exposed-core/src/main/kotlin/org/jetbrains/exposed/v1/core/Table.kt Outdated
Comment thread exposed-core/src/main/kotlin/org/jetbrains/exposed/v1/core/Table.kt Outdated
Comment thread exposed-core/src/main/kotlin/org/jetbrains/exposed/v1/core/Table.kt Outdated
Comment on lines +699 to +700
*/
open val storageParameters: List<String> = emptyList()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see a problem with having a database-specific syntactic sugar, I just don't think it should be biased towards one, if it's potentially more general. I get that for PostgreSQL they may all be storage-related properties, but are 100% of the SQL Server WITH properties really the same?
Maybe it would be more unopinionated to call it something everybody would understand like literally:

  • withOptions
  • withParameters

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One of the questions I'm thinking of is, why are we adding this when we already have the new property above? Is it because it will get special coverage in migration methods after? Or it was specifically requested?
Why not open val partitionOption (PARTITION BY / ON) too, which seems to be more common across databases?

Is it just for ease? Because there's nothing stopping a user from relying entirely on modifiers for basically every possible SQL syntax under the sun now (which I think personally is a great thing):

override val modifiers = listOf(
    "PARTITION BY HASH (order_id)",
    "WITH (fillfactor=70)",
    "TABLESPACE dbspace"
)

I guess my question is:
Would it be more simple to give a single API to append whatever they want to the end of a table definition?
Otherwise, what is the value between 1 and the other, other than not needing to type WITH?
Is it now potentially confusing that they need to remember that:

  1. modifiers comes after (column value,...) block
  2. And storageParameters comes after that, at the absolute end
  3. But if they want anything after WITH ..., they need to go back and only use modifiers (not sure about this last one, whether the order is important; i.e. is there any clause that must go after WITH?)

Copy link
Copy Markdown
Collaborator Author

@obabichevjb obabichevjb Mar 16, 2026

Choose a reason for hiding this comment

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

It's a good question, I made it as 2 different parameters (options and storage parameters) mostly because they have quite different meaning from database perspective.

Technically it's possible to have options as a one string only and put there all the parameters which are needed.

After the review changes I made it also better types (with separate classes for known options and parameters), so now it's possible to avoid using of row strings (and making errors inside them) and use class instances.

Minor, but if I'm right all the storage parameters should be inside one with, so it will not be possible to write something like options = listOf("WITH(fillfactor=75)", "WITH(log_autovacuum_min_duration=10)"), but I'm not sure

btw I updated api with better typing for parameters and we can discuss it again, probably we will find a better option.

I had an idea to make something like:

override val modifiers = onModifiers { //withModifiers/buildModifiers
  tableSpace(DB_SPACE),
  engine(INNO_DB)
}

but it was looking so non-Exposed way...

Comment thread documentation-website/Writerside/topics/Working-with-Tables.topic Outdated
@obabichevjb obabichevjb force-pushed the claude/312-database-engines branch from 0d3d778 to eebbde0 Compare March 16, 2026 11:57
@obabichevjb obabichevjb requested review from bog-walk and e5l March 16, 2026 11:58
@obabichevjb
Copy link
Copy Markdown
Collaborator Author

I'm still not sure about the final API,

I can's say I like that we add classes for particular options/parameters to the global scope, it will be possible to call them from anywhere, what will add noise. Probably we should define it inside Table class, what do you think?

At the current moment all the options/parameters have no information about database it should be used with. Another options is to defined them like:

class PostgresOptions {
  class Engine {}
  ...
}

I didn't do that because wanted to avoid mentioning of particular databases on the table definition level, because in this case it will be potentially necessary to create different definitions for different databases. But since the options are quite db specific it will be necessary to do with the current variant too.

Copy link
Copy Markdown
Member

@e5l e5l left a comment

Choose a reason for hiding this comment

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

lgtm

@bog-walk bog-walk changed the title fix: #312 Add support for table modifiers and storage parameters feat: #312 Add support for table modifiers and storage parameters Mar 19, 2026
Copy link
Copy Markdown
Member

@bog-walk bog-walk left a comment

Choose a reason for hiding this comment

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

Just a comment about R2DBC tests

@obabichevjb obabichevjb merged commit 13dcd90 into main Apr 14, 2026
6 of 8 checks passed
@obabichevjb obabichevjb deleted the claude/312-database-engines branch April 14, 2026 08:20
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.

Allow use of other database engines

3 participants