Breaking Changes
0.50.0
The
Transaction
class propertyrepetitionAttempts
is being deprecated in favor ofmaxAttempts
. Additionally, the propertyminRepetitionDelay
should be replaced withminRetryDelay
, andmaxRepetitionDelay
withmaxRetryDelay
. These changes also affect the default variants of these properties inDatabaseConfig
.The property
maxAttempts
represents the maximum amount of attempts to perform a transaction block. Setting it, or the now deprecatedrepetitionAttempts
, to a value less than 1 now throws anIllegalArgumentException
.IColumnType
andColumnType
now expect a type argument.IColumnType.valueFromDB()
also no longer has a default implementation, so an override for this method must be provided in any custom column type implementation. Check this pull request for details regarding this change.
0.49.0
For SQLite database, Exposed now requires bumping the SQLite JDBC driver version to a minimum of 3.45.0.0.
0.48.0
In
nonNullValueToString
forKotlinInstantColumnType
andJavaDateColumnType
, the formatted String for MySQL did not match the format received from the metadata whenisFractionDateTimeSupported
is true, so a new formatter specific to that is now used.In
nonNullValueToString
forKotlinLocalDateTimeColumnType
, the formatted String for MySQL did not match the format received from the metadata whenisFractionDateTimeSupported
is true, so a new formatter specific to MySQL is now used.In
nonNullValueToString
forDateColumnType
,JavaLocalDateTimeColumnType
,JavaLocalTimeColumnType
,JavaInstantColumnType
,KotlinLocalDateTimeColumnType
,KotlinLocalTimeColumnType
, andKotlinInstantColumnType
, the correct formatter for MySQL is used when the version (below 5.6) does not support fractional seconds.In
nonNullValueToString
forDateColumnType
andDateTimeWithTimeZoneColumnType
, the formatters used are changed to reflect the fact that Joda-Time stores date/time values only down to the millisecond (up to SSS and not SSSSSS).Functions
anyFrom(array)
andallFrom(array)
now useArrayColumnType
to process the provided array argument when query building.ArrayColumnType
requires a base column type to process contents correctly, and Exposed attempts to resolve the best match internally based on the array content type. A specific column type argument should be provided to the function parameterdelegateType
if the content requires either an unsupported or custom column type, or a column type not defined in theexposed-core
module.exposed-crypt
module now uses Spring Security Crypto 6.+, which requires Java 17 as a minimum version.
0.47.0
The function SchemaUtils.checkExcessiveIndices
is used to check both excessive indices and excessive foreign key constraints. It now has a different behavior and deals with excessive indices only. Also, its return type is now List<Index>
instead of Unit
. A new function, SchemaUtils.checkExcessiveForeignKeyConstraints
, deals with excessive foreign key constraints and has a return type List<ForeignKeyConstraint>
.
0.46.0
When an Exposed table object is created with a keyword identifier (a table or column name) it now retains the exact case used before being automatically quoted in generated SQL. This primarily affects H2 and Oracle, both of which support folding identifiers to uppercase, and PostgresSQL, which folds identifiers to a lower case.
If
preserveKeywordCasing = true
had been previously set inDatabaseConfig
to remove logged warnings about any keyword identifiers, this can now be removed as the property istrue
by default.To temporarily opt out of this behavior and to not keep the defined casing of keyword identifiers, please set
preserveKeywordCasing = false
inDatabaseConfig
:
Exposed 0.50.0 Help
Breaking Changes
0.50.0
The
Transaction
class propertyrepetitionAttempts
is being deprecated in favor ofmaxAttempts
. Additionally, the propertyminRepetitionDelay
should be replaced withminRetryDelay
, andmaxRepetitionDelay
withmaxRetryDelay
. These changes also affect the default variants of these properties inDatabaseConfig
.The property
maxAttempts
represents the maximum amount of attempts to perform a transaction block. Setting it, or the now deprecatedrepetitionAttempts
, to a value less than 1 now throws anIllegalArgumentException
.IColumnType
andColumnType
now expect a type argument.IColumnType.valueFromDB()
also no longer has a default implementation, so an override for this method must be provided in any custom column type implementation. Check this pull request for details regarding this change.
0.49.0
For SQLite database, Exposed now requires bumping the SQLite JDBC driver version to a minimum of 3.45.0.0.
0.48.0
In
nonNullValueToString
forKotlinInstantColumnType
andJavaDateColumnType
, the formatted String for MySQL did not match the format received from the metadata whenisFractionDateTimeSupported
is true, so a new formatter specific to that is now used.In
nonNullValueToString
forKotlinLocalDateTimeColumnType
, the formatted String for MySQL did not match the format received from the metadata whenisFractionDateTimeSupported
is true, so a new formatter specific to MySQL is now used.In
nonNullValueToString
forDateColumnType
,JavaLocalDateTimeColumnType
,JavaLocalTimeColumnType
,JavaInstantColumnType
,KotlinLocalDateTimeColumnType
,KotlinLocalTimeColumnType
, andKotlinInstantColumnType
, the correct formatter for MySQL is used when the version (below 5.6) does not support fractional seconds.In
nonNullValueToString
forDateColumnType
andDateTimeWithTimeZoneColumnType
, the formatters used are changed to reflect the fact that Joda-Time stores date/time values only down to the millisecond (up to SSS and not SSSSSS).Functions
anyFrom(array)
andallFrom(array)
now useArrayColumnType
to process the provided array argument when query building.ArrayColumnType
requires a base column type to process contents correctly, and Exposed attempts to resolve the best match internally based on the array content type. A specific column type argument should be provided to the function parameterdelegateType
if the content requires either an unsupported or custom column type, or a column type not defined in theexposed-core
module.exposed-crypt
module now uses Spring Security Crypto 6.+, which requires Java 17 as a minimum version.
0.47.0
The function SchemaUtils.checkExcessiveIndices
is used to check both excessive indices and excessive foreign key constraints. It now has a different behavior and deals with excessive indices only. Also, its return type is now List<Index>
instead of Unit
. A new function, SchemaUtils.checkExcessiveForeignKeyConstraints
, deals with excessive foreign key constraints and has a return type List<ForeignKeyConstraint>
.
0.46.0
When an Exposed table object is created with a keyword identifier (a table or column name) it now retains the exact case used before being automatically quoted in generated SQL. This primarily affects H2 and Oracle, both of which support folding identifiers to uppercase, and PostgresSQL, which folds identifiers to a lower case.
If
preserveKeywordCasing = true
had been previously set inDatabaseConfig
to remove logged warnings about any keyword identifiers, this can now be removed as the property istrue
by default.To temporarily opt out of this behavior and to not keep the defined casing of keyword identifiers, please set
preserveKeywordCasing = false
inDatabaseConfig
:
0.44.0
SpringTransactionManager
no longer extendsDataSourceTransactionManager
; instead, it directly extendsAbstractPlatformTransactionManager
while retaining the previous basic functionality. The class also no longer implements the Exposed interfaceTransactionManager
, as transaction operations are instead delegated to Spring. These changes ensure that Exposed's underlying transaction management no longer interferes with the expected behavior of Spring's transaction management, for example, when using nested transactions or with@Transactional
elements likepropagation
orisolation
.If integration still requires a
DataSourceTransactionManager
, please add two bean declarations to the configuration: one forSpringTransactionManager
and one forDataSourceTransactionManager
. Then define a composite transaction manager that combines these two managers.If
TransactionManager
functions were being invoked by aSpringTransactionManager
instance, please replace these calls with the appropriate Spring annotation or, if necessary, by using the companion object ofTransactionManager
directly (for example,TransactionManager.currentOrNull()
).spring-transaction
andexposed-spring-boot-starter
modules now use Spring Framework 6.0 and Spring Boot 3.0, which require Java 17 as a minimum version.A table that is created with a keyword identifier (a table or column name) now logs a warning that the identifier's case may be lost when it is automatically quoted in generated SQL. This primarily affects H2 and Oracle, both of which support folding identifiers to uppercase, and PostgreSQL, which folds identifiers to a lower case.
To remove these warnings and to ensure that the keyword identifier sent to the database matches the exact case used in the Exposed table object, please set
preserveKeywordCasing = true
inDatabaseConfig
:
0.44.0
SpringTransactionManager
no longer extendsDataSourceTransactionManager
; instead, it directly extendsAbstractPlatformTransactionManager
while retaining the previous basic functionality. The class also no longer implements the Exposed interfaceTransactionManager
, as transaction operations are instead delegated to Spring. These changes ensure that Exposed's underlying transaction management no longer interferes with the expected behavior of Spring's transaction management, for example, when using nested transactions or with@Transactional
elements likepropagation
orisolation
.If integration still requires a
DataSourceTransactionManager
, please add two bean declarations to the configuration: one forSpringTransactionManager
and one forDataSourceTransactionManager
. Then define a composite transaction manager that combines these two managers.If
TransactionManager
functions were being invoked by aSpringTransactionManager
instance, please replace these calls with the appropriate Spring annotation or, if necessary, by using the companion object ofTransactionManager
directly (for example,TransactionManager.currentOrNull()
).spring-transaction
andexposed-spring-boot-starter
modules now use Spring Framework 6.0 and Spring Boot 3.0, which require Java 17 as a minimum version.A table that is created with a keyword identifier (a table or column name) now logs a warning that the identifier's case may be lost when it is automatically quoted in generated SQL. This primarily affects H2 and Oracle, both of which support folding identifiers to uppercase, and PostgreSQL, which folds identifiers to a lower case.
To remove these warnings and to ensure that the keyword identifier sent to the database matches the exact case used in the Exposed table object, please set
preserveKeywordCasing = true
inDatabaseConfig
:
0.43.0
In all databases except MySQL, MariaDB, and SQL Server, the
ubyte()
column now maps to data typeSMALLINT
instead ofTINYINT
, which allows the full range ofUByte
values to be inserted without any overflow. Registering the column on a table also creates a check constraint that restricts inserted data to the range between 0 andUByte.MAX_VALUE
. If a column that only uses 1 byte of storage is needed, but without allowing any non-negative values to be inserted, please use a signedbyte()
column instead with a manually created check constraint:
0.43.0
In all databases except MySQL, MariaDB, and SQL Server, the
ubyte()
column now maps to data typeSMALLINT
instead ofTINYINT
, which allows the full range ofUByte
values to be inserted without any overflow. Registering the column on a table also creates a check constraint that restricts inserted data to the range between 0 andUByte.MAX_VALUE
. If a column that only uses 1 byte of storage is needed, but without allowing any non-negative values to be inserted, please use a signedbyte()
column instead with a manually created check constraint:
In all databases except MySQL and MariaDB, the
uint()
column now maps to data typeBIGINT
instead ofINT
, which allows the full range ofUInt
values to be inserted without any overflow. Registering the column on a table also creates a check constraint that restricts inserted data to the range between 0 andUInt.MAX_VALUE
. If a column that only uses 4 bytes of storage is needed, but without allowing any non-negative values to be inserted, please use a signedinteger()
column instead with a manually created check constraint:
In all databases except MySQL and MariaDB, the
uint()
column now maps to data typeBIGINT
instead ofINT
, which allows the full range ofUInt
values to be inserted without any overflow. Registering the column on a table also creates a check constraint that restricts inserted data to the range between 0 andUInt.MAX_VALUE
. If a column that only uses 4 bytes of storage is needed, but without allowing any non-negative values to be inserted, please use a signedinteger()
column instead with a manually created check constraint:
0.42.0
SQLite The table column created using
date()
now uses TEXT datatype instead of DATE (which the database mapped internally to NUMERIC type). This applies to the specificDateColumnType
in all 3 date/time modules and meansLocalDate
comparisons can now be done directly without conversions.H2, PostgreSQL Using
replace()
now throws an exception as the REPLACE command is not supported by these databases. Ifreplace()
was being used to perform an insert or update operation, all usages should instead be switched toupsert()
. See documentation for UPSERT detailsOperator classes
exists
andnotExists
have been renamed toExists
andNotExists
. The functionsexists()
andnotExists()
have been introduced to return an instance of their respectively-named classes and to avoid unresolved reference issues. Any usages of these classes should be renamed to their capitalized forms.customEnumeration()
now registers aCustomEnumerationColumnType
to allow referencing by another column. The signature ofcustomEnumeration()
has not changed and table columns initialized using it are still of typeColumn<DataClass>
.Transaction.suspendedTransaction()
has been renamed toTransaction.withSuspendTransaction()
. Please runEdit -> Find -> Replace in files...
twice withsuspendedTransaction(
andsuspendedTransaction
as the search options, to ensure that both variants are replaced without affectingsuspendedTransactionAsync()
(if used in code).The
repetitionAttempts
parameter intransaction()
has been removed and replaced with a mutable property in theTransaction
class. Please remove any arguments for this parameter and assign values to the property directly:
0.42.0
SQLite The table column created using
date()
now uses TEXT datatype instead of DATE (which the database mapped internally to NUMERIC type). This applies to the specificDateColumnType
in all 3 date/time modules and meansLocalDate
comparisons can now be done directly without conversions.H2, PostgreSQL Using
replace()
now throws an exception as the REPLACE command is not supported by these databases. Ifreplace()
was being used to perform an insert or update operation, all usages should instead be switched toupsert()
. See documentation for UPSERT detailsOperator classes
exists
andnotExists
have been renamed toExists
andNotExists
. The functionsexists()
andnotExists()
have been introduced to return an instance of their respectively-named classes and to avoid unresolved reference issues. Any usages of these classes should be renamed to their capitalized forms.customEnumeration()
now registers aCustomEnumerationColumnType
to allow referencing by another column. The signature ofcustomEnumeration()
has not changed and table columns initialized using it are still of typeColumn<DataClass>
.Transaction.suspendedTransaction()
has been renamed toTransaction.withSuspendTransaction()
. Please runEdit -> Find -> Replace in files...
twice withsuspendedTransaction(
andsuspendedTransaction
as the search options, to ensure that both variants are replaced without affectingsuspendedTransactionAsync()
(if used in code).The
repetitionAttempts
parameter intransaction()
has been removed and replaced with a mutable property in theTransaction
class. Please remove any arguments for this parameter and assign values to the property directly:
In all databases except MySQL and MariaDB, the
ushort()
column now maps to data typeINT
instead ofSMALLINT
, which allows the full range ofUShort
values to be inserted without any overflow. Registering the column on a table also creates a check constraint that restricts inserted data to the range between 0 andUShort.MAX_VALUE
. If a column that only uses 2 bytes of storage is needed, but without allowing any non-negative values to be inserted, please use a signedshort()
column instead with a manually created check constraint:
In all databases except MySQL and MariaDB, the
ushort()
column now maps to data typeINT
instead ofSMALLINT
, which allows the full range ofUShort
values to be inserted without any overflow. Registering the column on a table also creates a check constraint that restricts inserted data to the range between 0 andUShort.MAX_VALUE
. If a column that only uses 2 bytes of storage is needed, but without allowing any non-negative values to be inserted, please use a signedshort()
column instead with a manually created check constraint:
Exposed 0.49.0 Help
Contributing to Exposed
We're delighted that you're considering contributing to Exposed!
There are multiple ways you can contribute:
Code
Documentation
Community Support
Issues and Feature Requests
This project and the corresponding community is governed by the JetBrains Open Source and Community Code of Conduct. Independently of how you'd like to contribute, please make sure you read and comply with it.
Setup
Testing on Apple Silicon
To run Oracle XE tests, you need to install Colima container runtime. It will work in pair with your docker installation.
Exposed 0.50.0 Help
Contributing to Exposed
We're delighted that you're considering contributing to Exposed!
There are multiple ways you can contribute:
Code
Documentation
Community Support
Issues and Feature Requests
This project and the corresponding community is governed by the JetBrains Open Source and Community Code of Conduct. Independently of how you'd like to contribute, please make sure you read and comply with it.
Setup
Testing on Apple Silicon
To run Oracle XE tests, you need to install Colima container runtime. It will work in pair with your docker installation.
After installing, you need to start the colima daemon in arch x86_64 mode:
After installing, you need to start the colima daemon in arch x86_64 mode:
The test task can automatically use colima context when needed, and it's better to use default context for other tasks. To switch the context to default, run:
The test task can automatically use colima context when needed, and it's better to use default context for other tasks. To switch the context to default, run:
Make sure that default is used as default docker context:
Make sure that default is used as default docker context:
Code
Pull Requests
Contributions are made using Github pull requests:
Fork the Exposed repository, because imitation is the sincerest form of flattery.
Clone your fork to your local machine.
Create a new branch for your changes.
Create a new PR with a request to merge to the master branch.
Ensure that the description is clear and refers to an existing ticket/bug if applicable, prefixing the description with EXPOSED-, where refers to the YouTrack issue.
When contributing a new feature, provide motivation and use-cases describing why the feature not only provides value to Exposed, but also why it would make sense to be part of the Exposed framework itself.
If the contribution requires updates to documentation (be it updating existing contents or creating new one), please file a new ticket on YouTrack.
Make sure any code contributed is covered by tests and no existing tests are broken. We use Docker containers to run tests.
Execute the
detekt
task in Gradle to perform code style validation.Finally, make sure to run the
apiCheck
Gradle task. If it's not successful, run theapiDump
Gradle task. Further information can be found here.
Style Guides
A few things to remember:
Your code should conform to the official Kotlin code style guide except that star imports should always be enabled. (ensure Preferences | Editor | Code Style | Kotlin, tab Imports, both
Use import with '*'
should be checked).Every new source file should have a copyright header.
Every public API (including functions, classes, objects and so on) should be documented, every parameter, property, return types, and exceptions should be described properly.
Test functions:
Begin each test function name with the word
test
.Employ camelCase for test function names, such as
testInsertEmojisWithInvalidLength
.Refrain from using names enclosed in backticks for test functions, because
KDocs
cannot reference function names that contain spaces.In the definition of test functions, use a block body instead of an assignment operator. For example, do write
fun testMyTest() { withDb{} }
, and avoid writingfun testMyTest() = withDb{}
.
Commit messages
Commit messages should be written in English.
Their title should be prefixed according to Conventional Commits.
They should be written in present tense using imperative mood ("Fix" instead of "Fixes", "Improve" instead of "Improved"). See How to Write a Git Commit Message.
When applicable, prefix the commit message with EXPOSED- where represents the YouTrack issue number.
Add the related bug reference to a commit message (bug number after a hash character between round braces).
Documentation
There are multiple ways in which you can contribute to Exposed docs:
Create an issue in YouTrack.
Submit a pull request containing your proposed changes. Ensure that these modifications are applied directly within the
documentation-website
directory.
Community Support
If you'd like to help others, please join our Exposed channel on the Kotlin Slack workspace and help out. It's also a great way to learn!
Issues and Feature Requests
If you encounter a bug or have an idea for a new feature, please submit it to us through YouTrack, our issue tracker.
Before submitting an issue or feature request, please search YouTrack's existing issues to avoid reporting duplicates.
When submitting an issue or feature request, please provide as much detail as possible, including a clear and concise description of the problem or desired functionality, steps to reproduce the issue, and any relevant code snippets or error messages.
Thank you for your cooperation and for helping to improve Exposed.
Code
Pull Requests
Contributions are made using Github pull requests:
Fork the Exposed repository, because imitation is the sincerest form of flattery.
Clone your fork to your local machine.
Create a new branch for your changes.
Create a new PR with a request to merge to the master branch.
Ensure that the description is clear and refers to an existing ticket/bug if applicable, prefixing the description with EXPOSED-, where refers to the YouTrack issue.
When contributing a new feature, provide motivation and use-cases describing why the feature not only provides value to Exposed, but also why it would make sense to be part of the Exposed framework itself.
If the contribution requires updates to documentation (be it updating existing contents or creating new one), please file a new ticket on YouTrack.
Make sure any code contributed is covered by tests and no existing tests are broken. We use Docker containers to run tests.
Execute the
detekt
task in Gradle to perform code style validation.Finally, make sure to run the
apiCheck
Gradle task. If it's not successful, run theapiDump
Gradle task. Further information can be found here.
Style Guides
A few things to remember:
Your code should conform to the official Kotlin code style guide except that star imports should always be enabled. (ensure Preferences | Editor | Code Style | Kotlin, tab Imports, both
Use import with '*'
should be checked).Every new source file should have a copyright header.
Every public API (including functions, classes, objects and so on) should be documented, every parameter, property, return types, and exceptions should be described properly.
Test functions:
Begin each test function name with the word
test
.Employ camelCase for test function names, such as
testInsertEmojisWithInvalidLength
.Refrain from using names enclosed in backticks for test functions, because
KDocs
cannot reference function names that contain spaces.In the definition of test functions, use a block body instead of an assignment operator. For example, do write
fun testMyTest() { withDb{} }
, and avoid writingfun testMyTest() = withDb{}
.
Commit messages
Commit messages should be written in English.
Their title should be prefixed according to Conventional Commits.
They should be written in present tense using imperative mood ("Fix" instead of "Fixes", "Improve" instead of "Improved"). See How to Write a Git Commit Message.
When applicable, prefix the commit message with EXPOSED- where represents the YouTrack issue number.
Add the related bug reference to a commit message (bug number after a hash character between round braces).
Documentation
There are multiple ways in which you can contribute to Exposed docs:
Create an issue in YouTrack.
Submit a pull request containing your proposed changes. Ensure that these modifications are applied directly within the
documentation-website
directory.
Community Support
If you'd like to help others, please join our Exposed channel on the Kotlin Slack workspace and help out. It's also a great way to learn!
Issues and Feature Requests
If you encounter a bug or have an idea for a new feature, please submit it to us through YouTrack, our issue tracker.
Before submitting an issue or feature request, please search YouTrack's existing issues to avoid reporting duplicates.
When submitting an issue or feature request, please provide as much detail as possible, including a clear and concise description of the problem or desired functionality, steps to reproduce the issue, and any relevant code snippets or error messages.
Thank you for your cooperation and for helping to improve Exposed.
Exposed 0.49.0 Help
Data Types
Exposed supports the following data types in the table definition:
integer
- translates to DBINT
short
- translates to DBSMALLINT
long
-BIGINT
float
-FLOAT
decimal
-DECIMAL
with scale and precisionbool
-BOOLEAN
char
-CHAR
varchar
-VARCHAR
with lengthtext
-TEXT
enumeration
-INT
ordinal valueenumerationByName
-VARCHAR
customEnumeration
- see additional sectionblob
-BLOB
binary
-VARBINARY
with lengthuuid
-BINARY(16)
reference
- a foreign keyarray
-ARRAY
The exposed-java-time
extension (org.jetbrains.exposed:exposed-java-time:$exposed_version
) provides additional types:
date
-DATETIME
time
-TIME
datetime
-DATETIME
timestamp
-TIMESTAMP
duration
-DURATION
The exposed-json
extension (org.jetbrains.exposed:exposed-json:$exposed_version
) provides additional types (see how to use):
json
-JSON
jsonb
-JSONB
How to use database ENUM types
Some of the databases (e.g. MySQL, PostgreSQL, H2) support explicit ENUM types. Because keeping such columns in sync with Kotlin enumerations using only JDBC metadata could be a huge challenge, Exposed doesn't provide a possibility to manage such columns in an automatic way, but that doesn't mean that you can't use such column types.
You have two options to work with ENUM database types and you should use customEnumeration()
(available since version 0.10.3) in both cases:
Use an existing ENUM column from your table. In this case, the
sql
parameter incustomEnumeration()
can be left asnull
.Create a new ENUM column using Exposed by providing the raw definition SQL to the
sql
parameter incustomEnumeration()
.
As a JDBC driver can provide/expect specific classes for ENUM types, you must also provide from/to transformation functions for them when defining a customEnumeration
.
For a class like enum class Foo { BAR, BAZ }
, you can use the provided code below for your specific database:
MySQL, H2
Exposed 0.50.0 Help
Data Types
Exposed supports the following data types in the table definition:
integer
- translates to DBINT
short
- translates to DBSMALLINT
long
-BIGINT
float
-FLOAT
decimal
-DECIMAL
with scale and precisionbool
-BOOLEAN
char
-CHAR
varchar
-VARCHAR
with lengthtext
-TEXT
enumeration
-INT
ordinal valueenumerationByName
-VARCHAR
customEnumeration
- see additional sectionblob
-BLOB
binary
-VARBINARY
with lengthuuid
-BINARY(16)
reference
- a foreign keyarray
-ARRAY
The exposed-java-time
extension (org.jetbrains.exposed:exposed-java-time:$exposed_version
) provides additional types:
date
-DATETIME
time
-TIME
datetime
-DATETIME
timestamp
-TIMESTAMP
duration
-DURATION
The exposed-json
extension (org.jetbrains.exposed:exposed-json:$exposed_version
) provides additional types (see how to use):
json
-JSON
jsonb
-JSONB
How to use database ENUM types
Some of the databases (e.g. MySQL, PostgreSQL, H2) support explicit ENUM types. Because keeping such columns in sync with Kotlin enumerations using only JDBC metadata could be a huge challenge, Exposed doesn't provide a possibility to manage such columns in an automatic way, but that doesn't mean that you can't use such column types.
You have two options to work with ENUM database types and you should use customEnumeration()
(available since version 0.10.3) in both cases:
Use an existing ENUM column from your table. In this case, the
sql
parameter incustomEnumeration()
can be left asnull
.Create a new ENUM column using Exposed by providing the raw definition SQL to the
sql
parameter incustomEnumeration()
.
As a JDBC driver can provide/expect specific classes for ENUM types, you must also provide from/to transformation functions for them when defining a customEnumeration
.
For a class like enum class Foo { BAR, BAZ }
, you can use the provided code below for your specific database:
MySQL, H2
PostgreSQL
PostgreSQL requires that ENUM is defined as a separate type, so you have to create it before creating your table. Also, the PostgreSQL JDBC driver returns PGobject
instances for such values, so a PGobject
with its type manually set to the ENUM type needs to be used for the toDb
parameter. The full working sample is provided below:
PostgreSQL
PostgreSQL requires that ENUM is defined as a separate type, so you have to create it before creating your table. Also, the PostgreSQL JDBC driver returns PGobject
instances for such values, so a PGobject
with its type manually set to the ENUM type needs to be used for the toDb
parameter. The full working sample is provided below:
How to use Json and JsonB types
Add the following dependencies to your build.gradle.kts
:
How to use Json and JsonB types
Add the following dependencies to your build.gradle.kts
:
Exposed works together with the JSON serialization/deserialization library of your choice by allowing column definitions that accept generic serializer and deserializer arguments:
Exposed works together with the JSON serialization/deserialization library of your choice by allowing column definitions that accept generic serializer and deserializer arguments:
Here's an example that leverages kotlinx.serialization to support @Serializable
classes. It uses a simpler form of json()
that relies on the library's KSerializer
interface:
Here's an example that leverages kotlinx.serialization to support @Serializable
classes. It uses a simpler form of json()
that relies on the library's KSerializer
interface:
Here's how the same Project
and Teams
would be defined using Jackson with the jackson-module-kotlin
dependency and the full form of json()
:
Here's how the same Project
and Teams
would be defined using Jackson with the jackson-module-kotlin
dependency and the full form of json()
:
Json Functions
JSON path strings can be used to extract values (either as JSON or as a scalar value) at a specific field/key:
Json Functions
JSON path strings can be used to extract values (either as JSON or as a scalar value) at a specific field/key:
The JSON functions exists()
and contains()
are currently supported as well:
The JSON functions exists()
and contains()
are currently supported as well:
Json Arrays
JSON columns also accept JSON arrays as input values. For example, using the serializable data class Project
from the example above, the following details some ways to create such a column:
Json Arrays
JSON columns also accept JSON arrays as input values. For example, using the serializable data class Project
from the example above, the following details some ways to create such a column:
How to use Array types
PostgreSQL and H2 databases support the explicit ARRAY data type.
Exposed currently only supports columns defined as one-dimensional arrays, with the stored contents being any out-of-the-box or custom data type. If the contents are of a type with a supported ColumnType
in the exposed-core
module, the column can be simply defined with that type:
How to use Array types
PostgreSQL and H2 databases support the explicit ARRAY data type.
Exposed currently only supports columns defined as one-dimensional arrays, with the stored contents being any out-of-the-box or custom data type. If the contents are of a type with a supported ColumnType
in the exposed-core
module, the column can be simply defined with that type:
If more control is needed over the base content type, or if the latter is user-defined or from a non-core module, the explicit type should be provided to the function:
If more control is needed over the base content type, or if the latter is user-defined or from a non-core module, the explicit type should be provided to the function:
This will prevent an exception being thrown if Exposed cannot find an associated column mapping for the defined type. Null array contents are allowed, and the explicit column type should be provided for these columns as well.
An array column accepts inserts and retrieves stored array contents as a Kotlin List
:
This will prevent an exception being thrown if Exposed cannot find an associated column mapping for the defined type. Null array contents are allowed, and the explicit column type should be provided for these columns as well.
An array column accepts inserts and retrieves stored array contents as a Kotlin List
:
Array Functions
A single element in a stored array can be accessed using the index reference get()
operator:
Array Functions
A single element in a stored array can be accessed using the index reference get()
operator:
A new subarray can also be accessed by using slice()
, which takes a lower and upper bound (inclusive):
A new subarray can also be accessed by using slice()
, which takes a lower and upper bound (inclusive):
Both arguments for these bounds are optional if using PostgreSQL.
An array column can also be used as an argument for the ANY
and ALL
SQL operators, either by providing the entire column or a new array expression via slice()
:
Both arguments for these bounds are optional if using PostgreSQL.
An array column can also be used as an argument for the ANY
and ALL
SQL operators, either by providing the entire column or a new array expression via slice()
:
Exposed 0.49.0 Help
Working with Database and DataSource
Every database access using Exposed is started by obtaining a connection and creating a transaction.
First of all, you have to tell Exposed how to connect to a database by using the Database.connect
function. It won't create a real database connection but will only provide a descriptor for future usage.
A real connection will be instantiated later by calling the transaction
lambda (see Transactions for more details).
Use the following to get a Database instance by simply providing connection parameters:
Exposed 0.50.0 Help
Working with Database and DataSource
Every database access using Exposed is started by obtaining a connection and creating a transaction.
First of all, you have to tell Exposed how to connect to a database by using the Database.connect
function. It won't create a real database connection but will only provide a descriptor for future usage.
A real connection will be instantiated later by calling the transaction
lambda (see Transactions for more details).
Use the following to get a Database instance by simply providing connection parameters:
It is also possible to provide javax.sql.DataSource
for advanced behaviors such as connection pooling (see the HikariCP section):
It is also possible to provide javax.sql.DataSource
for advanced behaviors such as connection pooling (see the HikariCP section):
DataSource
PostgresSQL
PostgresSQL using the pgjdbc-ng JDBC driver
PostgresSQL using the pgjdbc-ng JDBC driver
MySQL
MySQL
MariaDB
MariaDB
Oracle
Oracle
SQLite
SQLite
H2
H2
SQL Server
SQL Server
HikariCP
To use a JDBC connection pool like HikariCP, first set up a HikariConfig
class. This example uses the MySQL JDBC driver (see the official reference for MySQL configuration details):
HikariCP
To use a JDBC connection pool like HikariCP, first set up a HikariConfig
class. This example uses the MySQL JDBC driver (see the official reference for MySQL configuration details):
Then instantiate a HikariDataSource
with this configuration class and provide it to Database.connect()
:
Then instantiate a HikariDataSource
with this configuration class and provide it to Database.connect()
:
Since version 0.46.0, when configured directly in the `HikariConfig` class, values like `transactionIsolation` and `isReadOnly` will be used by Exposed when creating transactions.
If they are duplicated or new values are set in DatabaseConfig
, the latter will be treated as an override in the same way that setting these parameters on an individual transaction block overrides the default settings.
It is therefore recommended to not set these values in DatabaseConfig
unless the intention is for the new value to override the Hikari settings.
Since version 0.46.0, when configured directly in the `HikariConfig` class, values like `transactionIsolation` and `isReadOnly` will be used by Exposed when creating transactions.
If they are duplicated or new values are set in DatabaseConfig
, the latter will be treated as an override in the same way that setting these parameters on an individual transaction block overrides the default settings.
It is therefore recommended to not set these values in DatabaseConfig
unless the intention is for the new value to override the Hikari settings.
Exposed 0.49.0 Help
Deep Dive into DAO
Overview
The DAO (Data Access Object) API of Exposed, is similar to ORM frameworks like Hibernate with a Kotlin-specific API.
A DB table is represented by an object
inherited from org.jetbrains.exposed.sql.Table
like this:
Exposed 0.50.0 Help
Deep Dive into DAO
Overview
The DAO (Data Access Object) API of Exposed, is similar to ORM frameworks like Hibernate with a Kotlin-specific API.
A DB table is represented by an object
inherited from org.jetbrains.exposed.sql.Table
like this:
Tables that contain an Int
id with the name id
can be declared like this:
Tables that contain an Int
id with the name id
can be declared like this:
Note that these Column types will be defined automatically, so you can also just leave them out. This would produce the same result as the example above:
Note that these Column types will be defined automatically, so you can also just leave them out. This would produce the same result as the example above:
An entity instance or a row in the table is defined as a class instance:
An entity instance or a row in the table is defined as a class instance:
Read
To get entities use one of the following
Read
To get entities use one of the following
For a list of available predicates, see DSL Where expression.
Read a value from a property similar to any property in a Kotlin class:
For a list of available predicates, see DSL Where expression.
Read a value from a property similar to any property in a Kotlin class:
Sort (Order-by)
Ascending order:
Sort (Order-by)
Ascending order:
Descending order:
Descending order:
Update
Update the value of a property similar to any property in a Kotlin class:
Update
Update the value of a property similar to any property in a Kotlin class:
Note: Exposed doesn't make an immediate update when you set a new value for Entity, it just stores it on the inner map. "Flushing" values to the database occurs at the end of the transaction, or before the next
select *
from the database.
Search for an entity by its id and apply an update:
Note: Exposed doesn't make an immediate update when you set a new value for Entity, it just stores it on the inner map. "Flushing" values to the database occurs at the end of the transaction, or before the next
select *
from the database.
Search for an entity by its id and apply an update:
Search for a single entity by a query and apply an update:
Search for a single entity by a query and apply an update:
Delete
Referencing
many-to-one reference
Let's say you have this table:
Referencing
many-to-one reference
Let's say you have this table:
And now you want to add a table referencing this table (and other tables!):
And now you want to add a table referencing this table (and other tables!):
Now you can get the film for a UserRating
object, filmRating
, in the same way you would get any other field:
Now you can get the film for a UserRating
object, filmRating
, in the same way you would get any other field:
Now if you wanted to get all the ratings for a film, you could do that by using the filmRating.find
function, but it is much easier to just add a referrersOn
field to the StarWarsFilm
class:
Now if you wanted to get all the ratings for a film, you could do that by using the filmRating.find
function, but it is much easier to just add a referrersOn
field to the StarWarsFilm
class:
You can then access this field on a StarWarsFilm
object, movie
:
You can then access this field on a StarWarsFilm
object, movie
:
Now imagine a scenario where a user only ever rates a single film. If you want to get the single rating for that user, you can add a backReferencedOn
field to the User
class to access the UserRating
table data:
Now imagine a scenario where a user only ever rates a single film. If you want to get the single rating for that user, you can add a backReferencedOn
field to the User
class to access the UserRating
table data:
You can then access this field on a User
object, user1
:
You can then access this field on a User
object, user1
:
Optional reference
You can also add an optional reference:
Optional reference
You can also add an optional reference:
Now secondUser
will be a nullable field, and optionalReferrersOn
should be used instead of referrersOn
to get all the ratings for a secondUser
.
Now secondUser
will be a nullable field, and optionalReferrersOn
should be used instead of referrersOn
to get all the ratings for a secondUser
.
many-to-many reference
In some cases, a many-to-many reference may be required. Let's assume you want to add a reference to the following Actors table to the StarWarsFilm class:
many-to-many reference
In some cases, a many-to-many reference may be required. Let's assume you want to add a reference to the following Actors table to the StarWarsFilm class:
Create an additional intermediate table to store the references:
Create an additional intermediate table to store the references:
Add a reference to StarWarsFilm
:
Add a reference to StarWarsFilm
:
Note: You can set up IDs manually inside a transaction like this:
Note: You can set up IDs manually inside a transaction like this:
Parent-Child reference
Parent-child reference is very similar to many-to-many version, but an intermediate table contains both references to the same table. Let's assume you want to build a hierarchical entity which could have parents and children. Our tables and an entity mapping will look like
Parent-Child reference
Parent-child reference is very similar to many-to-many version, but an intermediate table contains both references to the same table. Let's assume you want to build a hierarchical entity which could have parents and children. Our tables and an entity mapping will look like
As you can see NodeToNodes
columns target only NodeTable
and another version of via
function were used. Now you can create a hierarchy of nodes.
As you can see NodeToNodes
columns target only NodeTable
and another version of via
function were used. Now you can create a hierarchy of nodes.
Eager Loading
Available since 0.13.1. References in Exposed are lazily loaded, meaning queries to fetch the data for the reference are made at the moment the reference is first utilised. For scenarios wherefore you know you will require references ahead of time, Exposed can eager load them at the time of the parent query, this is prevents the classic "N+1" problem as references can be aggregated and loaded in a single query. To eager load a reference you can call the "load" function and pass the DAO's reference as a KProperty:
Eager Loading
Available since 0.13.1. References in Exposed are lazily loaded, meaning queries to fetch the data for the reference are made at the moment the reference is first utilised. For scenarios wherefore you know you will require references ahead of time, Exposed can eager load them at the time of the parent query, this is prevents the classic "N+1" problem as references can be aggregated and loaded in a single query. To eager load a reference you can call the "load" function and pass the DAO's reference as a KProperty:
This works for references of references also, for example if Actors had a rating reference you could:
This works for references of references also, for example if Actors had a rating reference you could:
Similarly, you can eagerly load references on Collections of DAO's such as Lists and SizedIterables, for collections you can use the with function in the same fashion as before, passing the DAO's references as KProperty's.
Similarly, you can eagerly load references on Collections of DAO's such as Lists and SizedIterables, for collections you can use the with function in the same fashion as before, passing the DAO's references as KProperty's.
NOTE: References that are eagerly loaded are stored inside the transaction cache; this means that they are not available in other transactions and thus must be loaded and referenced inside the same transaction. As of 0.35.1, enabling keepLoadedReferencesOutOfTransaction
in DatabaseConfig
will allow getting referenced values outside the transaction block.
Eager loading for Text Fields
Some database drivers do not load text content immediately (for performance and memory reasons) which means that you can obtain the column value only within the open transaction.
If you desire to make content available outside the transaction, you can use the eagerLoading param when defining the DB Table.
NOTE: References that are eagerly loaded are stored inside the transaction cache; this means that they are not available in other transactions and thus must be loaded and referenced inside the same transaction. As of 0.35.1, enabling keepLoadedReferencesOutOfTransaction
in DatabaseConfig
will allow getting referenced values outside the transaction block.
Eager loading for Text Fields
Some database drivers do not load text content immediately (for performance and memory reasons) which means that you can obtain the column value only within the open transaction.
If you desire to make content available outside the transaction, you can use the eagerLoading param when defining the DB Table.
Advanced CRUD operations
Read entity with a join to another table
Let's imagine that you want to find all users who rated second SW film with more than 5. First of all, we should write that query using Exposed DSL.
Advanced CRUD operations
Read entity with a join to another table
Let's imagine that you want to find all users who rated second SW film with more than 5. First of all, we should write that query using Exposed DSL.
After that all we have to do is to "wrap" a result with User entity:
After that all we have to do is to "wrap" a result with User entity:
Auto-fill created and updated columns on entity change
See example by @PaulMuriithi here.
Use queries as expressions
Imagine that you want to sort cities by how many users each city has. In order to do so, you can write a sub-query which counts users in each city and order by that number. Though in order to do so you'll have to convert Query
to Expression
. This can be done using wrapAsExpression
function:
Auto-fill created and updated columns on entity change
See example by @PaulMuriithi here.
Use queries as expressions
Imagine that you want to sort cities by how many users each city has. In order to do so, you can write a sub-query which counts users in each city and order by that number. Though in order to do so you'll have to convert Query
to Expression
. This can be done using wrapAsExpression
function:
Add computed fields to entity class
Imagine that you want to use a window function to rank films with each entity fetch. The companion object of the entity class can override any open function in EntityClass
, but to achieve this functionality only searchQuery()
needs to be overriden. The results of the function can then be accessed using a property of the entity class:
Add computed fields to entity class
Imagine that you want to use a window function to rank films with each entity fetch. The companion object of the entity class can override any open function in EntityClass
, but to achieve this functionality only searchQuery()
needs to be overriden. The results of the function can then be accessed using a property of the entity class:
Entities mapping
Fields transformation
As databases could store only basic types like integers and strings it's not always conveniently to keep the same simplicity on DAO level. Sometimes you may want to make some transformations like parsing json from a varchar column or get some value from a cache based on value from a database. In that case the preferred way is to use column transformations. Assume that we want to define unsigned integer field on Entity, but Exposed doesn't have such column type yet.
Entities mapping
Fields transformation
As databases could store only basic types like integers and strings it's not always conveniently to keep the same simplicity on DAO level. Sometimes you may want to make some transformations like parsing json from a varchar column or get some value from a cache based on value from a database. In that case the preferred way is to use column transformations. Assume that we want to define unsigned integer field on Entity, but Exposed doesn't have such column type yet.
transform
function accept two lambdas to convert values to and from an original column type. After that in your code you'll be able to put only UInt
instances into uint
field. It still possible to insert/update values with negative integers via DAO, but your business code becomes much cleaner. Please keep in mind what such transformations will aqure on every access to a field what means that you should avoid heavy transformations here.
transform
function accept two lambdas to convert values to and from an original column type. After that in your code you'll be able to put only UInt
instances into uint
field. It still possible to insert/update values with negative integers via DAO, but your business code becomes much cleaner. Please keep in mind what such transformations will aqure on every access to a field what means that you should avoid heavy transformations here.