diff --git a/docs/HelpTOC.json b/docs/HelpTOC.json
index 04c37b84f8..9a2c845d25 100644
--- a/docs/HelpTOC.json
+++ b/docs/HelpTOC.json
@@ -1 +1 @@
-{"entities":{"pages":{"Home":{"id":"Home","title":"Home","url":"home.html","level":0,"tabIndex":0},"About":{"id":"About","title":"About","url":"about.html","level":0,"tabIndex":1},"Getting-Started-with-Exposed":{"id":"Getting-Started-with-Exposed","title":"Get started with Exposed","url":"getting-started-with-exposed.html","level":0,"tabIndex":2},"Exposed-Modules":{"id":"Exposed-Modules","title":"Modules","url":"exposed-modules.html","level":0,"tabIndex":3},"-qb7fbx_5":{"id":"-qb7fbx_5","title":"Databases","level":0,"pages":["Working-with-Database","Working-with-DataSource","Transactions"],"tabIndex":4},"Working-with-Database":{"id":"Working-with-Database","title":"Working with Databases","url":"working-with-database.html","level":1,"parentId":"-qb7fbx_5","tabIndex":0},"Working-with-DataSource":{"id":"Working-with-DataSource","title":"Working with DataSources","url":"working-with-datasource.html","level":1,"parentId":"-qb7fbx_5","tabIndex":1},"Transactions":{"id":"Transactions","title":"Working with Transactions","url":"transactions.html","level":1,"parentId":"-qb7fbx_5","tabIndex":2},"-qb7fbx_9":{"id":"-qb7fbx_9","title":"Schemas","level":0,"pages":["Working-with-Tables","Data-Types","SQL-Functions","Working-with-Schema"],"tabIndex":5},"Working-with-Tables":{"id":"Working-with-Tables","title":"Working with tables","url":"working-with-tables.html","level":1,"parentId":"-qb7fbx_9","tabIndex":0},"Data-Types":{"id":"Data-Types","title":"Data Types","url":"data-types.html","level":1,"parentId":"-qb7fbx_9","tabIndex":1},"SQL-Functions":{"id":"SQL-Functions","title":"SQL Functions","url":"sql-functions.html","level":1,"parentId":"-qb7fbx_9","tabIndex":2},"Working-with-Schema":{"id":"Working-with-Schema","title":"Working with Schema","url":"working-with-schema.html","level":1,"parentId":"-qb7fbx_9","tabIndex":3},"-qb7fbx_14":{"id":"-qb7fbx_14","title":"Deep Dive into DSL","level":0,"pages":["DSL-Table-Types","DSL-Joining-tables","DSL-CRUD-operations","Working-with-Sequence","DSL-Querying-data"],"tabIndex":6},"DSL-Table-Types":{"id":"DSL-Table-Types","title":"Table types","url":"dsl-table-types.html","level":1,"parentId":"-qb7fbx_14","tabIndex":0},"DSL-Joining-tables":{"id":"DSL-Joining-tables","title":"Joining tables","url":"dsl-joining-tables.html","level":1,"parentId":"-qb7fbx_14","tabIndex":1},"DSL-CRUD-operations":{"id":"DSL-CRUD-operations","title":"CRUD operations","url":"dsl-crud-operations.html","level":1,"parentId":"-qb7fbx_14","tabIndex":2},"Working-with-Sequence":{"id":"Working-with-Sequence","title":"Working with Sequence","url":"working-with-sequence.html","level":1,"parentId":"-qb7fbx_14","tabIndex":3},"DSL-Querying-data":{"id":"DSL-Querying-data","title":"Querying data","url":"dsl-querying-data.html","level":1,"parentId":"-qb7fbx_14","tabIndex":4},"-qb7fbx_20":{"id":"-qb7fbx_20","title":"Deep Dive into DAO","level":0,"pages":["DAO-Table-Types","DAO-Entity-definition","DAO-CRUD-Operations","DAO-Relationships"],"tabIndex":7},"DAO-Table-Types":{"id":"DAO-Table-Types","title":"Table types","url":"dao-table-types.html","level":1,"parentId":"-qb7fbx_20","tabIndex":0},"DAO-Entity-definition":{"id":"DAO-Entity-definition","title":"Entity definition","url":"dao-entity-definition.html","level":1,"parentId":"-qb7fbx_20","tabIndex":1},"DAO-CRUD-Operations":{"id":"DAO-CRUD-Operations","title":"CRUD operations","url":"dao-crud-operations.html","level":1,"parentId":"-qb7fbx_20","tabIndex":2},"DAO-Relationships":{"id":"DAO-Relationships","title":"Relationships","url":"dao-relationships.html","level":1,"parentId":"-qb7fbx_20","tabIndex":3},"-qb7fbx_25":{"id":"-qb7fbx_25","title":"Releases","level":0,"pages":["Breaking-Changes","Migration-Guide"],"tabIndex":8},"Breaking-Changes":{"id":"Breaking-Changes","title":"Breaking Changes","url":"breaking-changes.html","level":1,"parentId":"-qb7fbx_25","tabIndex":0},"Migration-Guide":{"id":"Migration-Guide","title":"Migrating from 0.45.0 to 0.46.0","url":"migration-guide.html","level":1,"parentId":"-qb7fbx_25","tabIndex":1},"Frequently-Asked-Questions":{"id":"Frequently-Asked-Questions","title":"Frequently Asked Questions","url":"frequently-asked-questions.html","level":0,"tabIndex":9},"-qb7fbx_29":{"id":"-qb7fbx_29","title":"Samples","url":"https://github.com/JetBrains/Exposed/tree/main/samples","level":0,"tabIndex":10},"Contributing":{"id":"Contributing","title":"Contributing to Exposed","url":"contributing.html","level":0,"tabIndex":11}}},"topLevelIds":["Home","About","Getting-Started-with-Exposed","Exposed-Modules","-qb7fbx_5","-qb7fbx_9","-qb7fbx_14","-qb7fbx_20","-qb7fbx_25","Frequently-Asked-Questions","-qb7fbx_29","Contributing"]}
\ No newline at end of file
+{"entities":{"pages":{"Home":{"id":"Home","title":"Home","url":"home.html","level":0,"tabIndex":0},"Contributing":{"id":"Contributing","title":"Contributing to Exposed","url":"contributing.html","level":0,"tabIndex":11}}},"topLevelIds":["Home","Contributing"]}
\ No newline at end of file
diff --git a/docs/Map.jhm b/docs/Map.jhm
index 3db829a329..171a10c96b 100644
--- a/docs/Map.jhm
+++ b/docs/Map.jhm
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/about.html b/docs/about.html
index 7ed3f82b9a..93556edf98 100644
--- a/docs/about.html
+++ b/docs/about.html
@@ -1,16 +1,9 @@
-
-
Exposed, an ORM framework for Kotlin | Exposed
Exposed 0.58.0 Help
Exposed, an ORM framework for Kotlin
Exposed is a lightweight SQL library on top of a JDBC driver for the Kotlin programming language. It offers two approaches for database access: the Domain-Specific Language (DSL) API and the Data Access Object (DAO) API.
The Domain-Specific Language (DSL) API of Exposed provides a Kotlin-based abstraction for interacting with databases . It closely mirrors actual SQL statements, allowing you to work with familiar SQL concepts while benefiting from the type safety that Kotlin offers.
The Data Access Object (DAO) API of Exposed provides an object-oriented approach for interacting with a database, similar to traditional Object-Relational Mapping (ORM) frameworks like Hibernate. This API is less verbose and provides a more intuitive and Kotlin-centric way to interact with your database.
Exposed's flexibility allows you to choose the approach that best suits your project's needs, whether you prefer the direct control of SQL with the DSL API or the higher-level abstraction of the DAO API.
The official mascot of Exposed is the cuttlefish, which is well-known for its outstanding mimicry ability that enables it to blend seamlessly into any environment. Similar to its mascot, Exposed can be used to mimic a variety of database engines, which helps you to build applications without dependencies on any specific database engine and to switch between them with very little or no changes.
Supported databases
Exposed currently supports the following databases:
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/breaking-changes.html b/docs/breaking-changes.html
index b8b4b202e2..0444042e9c 100644
--- a/docs/breaking-changes.html
+++ b/docs/breaking-changes.html
@@ -1,132 +1,9 @@
-
-Breaking Changes | Exposed
Exposed 0.58.0 Help
Breaking Changes
0.57.0
Insert, Upsert, and Replace statements will no longer implicitly send all default values (except for client-side default values) in every SQL request. This change will reduce the amount of data Exposed sends to the database and make Exposed rely more on the database's default values. However, this may uncover previously hidden issues related to actual database default values, which were masked by Exposed's insert/upsert statements. Also from InsertStatement was removed protected method isColumnValuePreferredFromResultSet() and method valuesAndDefaults() was marked as deprecated.
Let's say you have a table with columns that have default values, and you use an insert statement like this:
-object TestTable : IntIdTable("test") {
- val number = integer("number").default(100)
- val expression = integer("exp")
- .defaultExpression(intLiteral(100) + intLiteral(200))
-}
-
-TestTable.insert { }
-
This insert statement would generate the following SQL in the H2 database:
--- For versions before 0.57.0
-INSERT INTO TEST ("number", "exp") VALUES (100, (100 + 200))
-
--- Starting from version 0.57.0
-INSERT INTO TEST DEFAULT VALUES
-
The OptionalReferrers class is now deprecated as it is a complete duplicate of the Referrers class; therefore, the latter should be used instead.
0.56.0
If the distinct parameter of groupConcat() is set to true, when using Oracle or SQL Server, this will now fail early with an UnsupportedByDialectException. Previously, the setting would be ignored and SQL function generation would not include a DISTINCT clause.
In Oracle and H2 Oracle, the ubyte() column now maps to data type NUMBER(3) instead of NUMBER(4).
In Oracle and H2 Oracle, the ushort() column now maps to data type NUMBER(5) instead of NUMBER(6).
In Oracle and H2 Oracle, the uinteger() column now maps to data type NUMBER(10) instead of NUMBER(13).
In Oracle and H2 Oracle, the integer() column now maps to data type NUMBER(10) and INTEGER respectively, instead of NUMBER(12). In Oracle and SQLite, using the integer column in a table now also creates a CHECK constraint to ensure that no out-of-range values are inserted.
ArrayColumnType now supports multidimensional arrays and includes an additional generic parameter. If it was previously used for one-dimensional arrays with the parameter T like ArrayColumnType<T>, it should now be defined as ArrayColumnType<T, List<T>>. For instance, ArrayColumnType<Int> should now be ArrayColumnType<Int, List<Int>>.
EntityID and CompositeID no longer implement Comparable themselves, to allow their wrapped identity values to be of a type that is not necessarily Comparable, like kotlin.uuid.Uuid.
Any use of an entity's id with Kotlin comparison operators or compareTo() will now require that the wrapped value be used directly: entity1.id < entity2.id will need to become entity1.id.value < entity2.id.value. Any use of an entity's id with an Exposed function that is also type restricted to Comparable (for example, avg()) will also require defining a new function. In this event, please also leave a comment on YouTrack with a use case so the original function signature can be potentially reassessed.
0.55.0
The DeleteStatement property table is now deprecated in favor of targetsSet, which holds a ColumnSet that may be a Table or Join. This enables the use of the new Join.delete() function, which performs a delete operation on a specific table from the join relation. The original statement class constructor has also been deprecated in favor of the constructor that accepts targetsSet, as well as another additional parameter targetTables (for specifying which table from the join relation, if applicable, to delete from).
The DeleteStatement property offset was not being used and is now deprecated, as are the extension functions that have an offset parameter. deleteWhere() and deleteIgnoreWhere(), as well as the original statement class constructor, no longer accept an argument for offset.
SizedIterable.limit(n, offset) is now deprecated in favor of 2 independent methods, limit() and offset(). In supporting databases, this allows the generation of an OFFSET clause in the SELECT statement without any LIMIT clause. Any custom implementations of the SizedIterable interface with a limit() override will now show a warning that the declaration overrides a deprecated member. This override should be split into an implementation of the 2 new members instead.
The original FunctionProvider.queryLimit() is also being deprecated in favor of queryLimitAndOffset(), which takes a nullable size parameter to allow exclusion of the LIMIT clause. This latter deprecation only affects extensions of the FunctionProvider class when creating a custom VendorDialect class.
In Oracle, the short column now maps to data type NUMBER(5) instead of SMALLINT because SMALLINT is stored as NUMBER(38) in the database and takes up unnecessary storage. In Oracle and SQLite, using the short column in a table now also creates a check constraint to ensure that no out-of-range values are inserted.
In Oracle, the byte column now maps to data type NUMBER(3) instead of SMALLINT because SMALLINT is stored as NUMBER(38) in the database and takes up unnecessary storage. In SQL Server, the byte column now maps to data type SMALLINT instead of TINYINT because TINYINTallows values from 0 to 255. In SQL Server, SQLite, Oracle, PostgreSQL, and H2 PostgreSQL, using the byte column in a table now also creates a check constraint to ensure that no out-of-range values are inserted.
The transformation of a nullable column (Column<Unwrapped?>.transform()) requires handling null values. This enables conversions from null to a non-nullable value, and vice versa.
In H2 the definition of json column with default value changed from myColumn JSON DEFAULT '{"key": "value"}' to myColumn JSON DEFAULT JSON '{"key": "value"}'
0.54.0
All objects that are part of the sealed class ForUpdateOption are now converted to data object.
The onUpdate parameter in upsert(), upsertReturning(), and batchUpsert() will no longer accept a list of column-value pairs as an argument. The parameter now takes a lambda block with an UpdateStatement as its argument, so that column-value assignments for the UPDATE clause can be set in a similar way to update(). This enables the use of insertValue(column) in expressions to specify that the same value to be inserted into a column should be used when updating.
-// before
-TestTable.upsert(
- onUpdate = listOf(Words.count to Words.count.plus(1))
-) {
- it[word] = "Kotlin"
- it[count] = 3
-}
-
-// after
-TestTable.upsert(
- onUpdate = {
- it[Words.count] = Words.count + 1
- }
-) {
- it[word] = "Kotlin"
- it[count] = 3
-}
-
-// after - with new value from insert used in update expression
-TestTable.upsert(
- onUpdate = {
- it[Words.count] = Words.count + insertValue(Words.count)
- }
-) {
- it[word] = "Kotlin"
- it[count] = 3
-}
-
The function statementsRequiredForDatabaseMigration has been moved from SchemaUtils to MigrationUtils in the exposed-migration module.
A nested transaction (with useNestedTransactions = true) that throws any exception will now rollback any commits since the last savepoint. This ensures that the nested transaction is properly configured to act in the exact same way as a top-level transaction or inTopLevelTransaction().
An inner transaction (with useNestedTransactions = false) that throws any exception will also rollback any commits since the last savepoint. This ensures that any exception propagated from the inner transaction to the outer transaction will not be swallowed if caught by some exception handler wrapping the inner transaction, and any inner commits will not be saved. In version 0.55.0, this change will be reduced so that only inner transactions that throw an SQLException from the database will trigger such a rollback.
0.53.0
DAO Entity Transformation Changes
Parameter Renaming: transform() and memoizedTransform() now use wrap and unwrap instead of toColumn and toReal.
-// Old:
-var name by EmployeeTable.name.transform(toColumn = { it.uppercase() }, toReal = { it.lowercase() })
-// New:
-var name by EmployeeTable.name.transform(wrap = { it.uppercase() }, unwrap = { it.lowercase() })
-
Class Renaming: ColumnWithTransform is now EntityFieldWithTransform, consolidating properties into a single transformer.
-EntityFieldWithTransform(column, object : ColumnTransformer<String, Int> {
- override fun unwrap(value: Int): String = value.toString()
- override fun wrap(value: String): Int = value.toInt()
- })
-
Entity transformation via DAO is deprecated and should be replaced with DSL transformation.
The exposed-spring-boot-starter module no longer provides the entire spring-boot-starter-data-jdbc module. It now provides just the spring-boot-starter-jdbc. If there was a reliance on this transitive dependency, please directly include a dependency on Spring Data JDBC in your build files.
ulong column type is now NUMERIC(20) instead of BIGINT for H2 (excluding H2_PSQL), SQLite, and SQL Server to allow storing the full range of ULong, including ULong.MAX_VALUE.
0.50.0
The Transaction class property repetitionAttempts is being deprecated in favor of maxAttempts. Additionally, the property minRepetitionDelay should be replaced with minRetryDelay, and maxRepetitionDelay with maxRetryDelay. These changes also affect the default variants of these properties in DatabaseConfig.
The property maxAttempts represents the maximum amount of attempts to perform a transaction block. Setting it, or the now deprecated repetitionAttempts, to a value less than 1 now throws an IllegalArgumentException.
IColumnType and ColumnType 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 for KotlinInstantColumnType and JavaDateColumnType, the formatted String for MySQL did not match the format received from the metadata when isFractionDateTimeSupported is true, so a new formatter specific to that is now used.
In nonNullValueToString for KotlinLocalDateTimeColumnType, the formatted String for MySQL did not match the format received from the metadata when isFractionDateTimeSupported is true, so a new formatter specific to MySQL is now used.
In nonNullValueToString for DateColumnType, JavaLocalDateTimeColumnType, JavaLocalTimeColumnType, JavaInstantColumnType, KotlinLocalDateTimeColumnType, KotlinLocalTimeColumnType, and KotlinInstantColumnType, the correct formatter for MySQL is used when the version (below 5.6) does not support fractional seconds.
In nonNullValueToString for DateColumnType and DateTimeWithTimeZoneColumnType, 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) and allFrom(array) now use ArrayColumnType 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 parameter delegateType if the content requires either an unsupported or custom column type, or a column type not defined in the exposed-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 in DatabaseConfig to remove logged warnings about any keyword identifiers, this can now be removed as the property is true by default.
To temporarily opt out of this behavior and to not keep the defined casing of keyword identifiers, please set preserveKeywordCasing = false in DatabaseConfig:
-object TestTable : Table("table") {
- val col = integer("select")
-}
-
-// default behavior (preserveKeywordCasing is by default set to true)
-// H2 generates SQL -> CREATE TABLE IF NOT EXISTS "table" ("select" INT NOT NULL)
-
-// with opt-out
-Database.connect(
- url = "jdbc:h2:mem:test",
- driver = "org.h2.Driver",
- databaseConfig = DatabaseConfig {
- @OptIn(ExperimentalKeywordApi::class)
- preserveKeywordCasing = false
- }
-)
-// H2 generates SQL -> CREATE TABLE IF NOT EXISTS "TABLE" ("SELECT" INT NOT NULL)
-
0.44.0
SpringTransactionManager no longer extends DataSourceTransactionManager; instead, it directly extends AbstractPlatformTransactionManager while retaining the previous basic functionality. The class also no longer implements the Exposed interface TransactionManager, 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 like propagation or isolation.
If integration still requires a DataSourceTransactionManager, please add two bean declarations to the configuration: one for SpringTransactionManager and one for DataSourceTransactionManager. Then define a composite transaction manager that combines these two managers.
If TransactionManager functions were being invoked by a SpringTransactionManager instance, please replace these calls with the appropriate Spring annotation or, if necessary, by using the companion object of TransactionManager directly (for example, TransactionManager.currentOrNull()).
spring-transaction and exposed-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 in DatabaseConfig:
-object TestTable : Table("table") {
- val col = integer("select")
-}
-
-// without opt-in (default set to false)
-// H2 generates SQL -> CREATE TABLE IF NOT EXISTS "TABLE" ("SELECT" INT NOT NULL)
-
-// with opt-in
-Database.connect(
- url = "jdbc:h2:mem:test",
- driver = "org.h2.Driver",
- databaseConfig = DatabaseConfig {
- @OptIn(ExperimentalKeywordApi::class)
- preserveKeywordCasing = true
- }
-)
-// H2 generates SQL -> CREATE TABLE IF NOT EXISTS "table" ("select" INT NOT NULL)
-
0.43.0
In all databases except MySQL, MariaDB, and SQL Server, the ubyte() column now maps to data type SMALLINT instead of TINYINT, which allows the full range of UByte 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 and UByte.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 signed byte() column instead with a manually created check constraint:
-byte("number").check { it.between(0, Byte.MAX_VALUE) }
-// OR
-byte("number").check { (it greaterEq 0) and (it lessEq Byte.MAX_VALUE) }
-
In all databases except MySQL and MariaDB, the uint() column now maps to data type BIGINT instead of INT, which allows the full range of UInt 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 and UInt.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 signed integer() column instead with a manually created check constraint:
-integer("number").check { it.between(0, Int.MAX_VALUE) }
-// OR
-integer("number").check { (it greaterEq 0) and (it lessEq Int.MAX_VALUE) }
-
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 specific DateColumnType in all 3 date/time modules and means LocalDate 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. If replace() was being used to perform an insert or update operation, all usages should instead be switched to upsert(). See documentation for UPSERT details
Operator classes exists and notExists have been renamed to Exists and NotExists. The functions exists() and notExists() 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 a CustomEnumerationColumnType to allow referencing by another column. The signature of customEnumeration() has not changed and table columns initialized using it are still of type Column<DataClass>.
Transaction.suspendedTransaction() has been renamed to Transaction.withSuspendTransaction(). Please run Edit -> Find -> Replace in files... twice with suspendedTransaction( and suspendedTransaction as the search options, to ensure that both variants are replaced without affecting suspendedTransactionAsync() (if used in code).
The repetitionAttempts parameter in transaction() has been removed and replaced with a mutable property in the Transaction 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 type INT instead of SMALLINT, which allows the full range of UShort 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 and UShort.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 signed short() column instead with a manually created check constraint:
-short("number").check { it.between(0, Short.MAX_VALUE) }
-// OR
-short("number").check { (it greaterEq 0) and (it lessEq Short.MAX_VALUE) }
-
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/config.json b/docs/config.json
index 01b43012d6..6bf4f0ebe5 100644
--- a/docs/config.json
+++ b/docs/config.json
@@ -1 +1 @@
-{"productWebUrl":"https://jetbrains.github.io/Exposed/home.html","searchAlgoliaId":"MMA5Z3JT91","searchAlgoliaApiKey":"119a138f76e4c043bcbb9f0c16119094","searchAlgoliaIndexName":"prod_exposed","productId":"docs","keymaps":{},"logoUrl":"images/exposed-logo-white.svg","productName":"Exposed","productVersion":"0.58.0","stage":"release","downloadTitle":"Get Exposed","searchService":"algolia","searchMaxHits":75}
\ No newline at end of file
+{"primary-color":"#307FFF","productWebUrl":"https://www.jetbrains.com/help/exposed/","searchAlgoliaApiKey":"119a138f76e4c043bcbb9f0c16119094","searchAlgoliaId":"MMA5Z3JT91","searchAlgoliaIndexName":"prod_exposed","productId":"docs","keymaps":{},"logoUrl":"images/exposed-logo-white.svg","productName":"Exposed","productVersion":"0.59.0","stage":"release","downloadTitle":"Get Exposed","searchService":"algolia","searchMaxHits":75,"color-preset":"contrast"}
\ No newline at end of file
diff --git a/docs/contributing.html b/docs/contributing.html
index 5a46e1fc86..a6bb6432c5 100644
--- a/docs/contributing.html
+++ b/docs/contributing.html
@@ -1,24 +1,9 @@
-
-Contributing to Exposed | Exposed
Exposed 0.58.0 Help
Contributing to Exposed
We're delighted that you're considering contributing to Exposed!
There are multiple ways you can contribute:
Issues and Feature Requests
Documentation
Code
Community Support
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.
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. While issues are visible publicly, either creating a new issue or commenting on an existing one does require logging in to YouTrack.
Before submitting an issue or feature request, search YouTrack's existing issues to avoid reporting duplicates.
When submitting an issue or feature request, 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.
Documentation
There are multiple ways in which you can contribute to Exposed documentation:
Submit a pull request containing your proposed changes. Ensure that these modifications are applied directly within the documentation-website directory only, not to files in the docs folder.
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 main branch.
Ensure that the PR title is clear and refers to an existing ticket/bug if applicable, prefixing the title with both a conventional commit and EXPOSED-{NUM}, where {NUM} refers to the YouTrack issue code. For more details about the suggested format, see commit messages.
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. Complete as many sections of the PR template description as applicable.
If the contribution requires updates to documentation (be it updating existing content or creating new content), either do so in the same PR or file a new ticket on YouTrack. Any new public API objects should be documented with a KDoc in the same PR.
If the contribution makes any breaking changes, ensure that this is properly denoted in 3 ways:
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 the apiDump 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 writing fun testMyTest() = withDb{}.
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-{NUM} where {NUM} refers to the YouTrack issue code.
An example could be: fix: EXPOSED-123 Fix a specific bug
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.
-brew install colima
-
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:
-docker context use default
-
Make sure that default is used as default docker context:
-docker context list
-
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!
Thank you for your cooperation and for helping to improve Exposed.
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/current.help.version b/docs/current.help.version
index cb6b534abe..18aa1677cf 100644
--- a/docs/current.help.version
+++ b/docs/current.help.version
@@ -1 +1 @@
-0.59.0
+0.59.0
\ No newline at end of file
diff --git a/docs/dao-crud-operations.html b/docs/dao-crud-operations.html
index e6ef0a55d5..bf6a0e67cc 100644
--- a/docs/dao-crud-operations.html
+++ b/docs/dao-crud-operations.html
@@ -1,194 +1,9 @@
-
-CRUD operations | Exposed
Exposed 0.58.0 Help
CRUD operations
CRUD (Create, Read, Update, Delete) are the four basic operations supported by any database. This section demonstrates how to perform SQL CRUD operations using Exposed's DAO (Data Access Object) API.
These operations can be performed directly through the methods and properties of the EntityClass associated with the table. For more information, see Entity definition.
Create
To create a new table row, use the .new() function on the entity class:
val movie = StarWarsFilmEntity.new {
- name = "The Last Jedi"
- sequelId = MOVIE_SEQUEL_ID
- director = "Rian Johnson"
- }
-
In the above example StarWarsFilmEntity would be the entity instance linked to the StarWarsFilmsTable table.
To provide a manual id value to a new entity, pass the value as an argument to the id parameter:
val movie2 = StarWarsFilmEntity.new(id = 2) {
- name = "The Rise of Skywalker"
- sequelId = MOVIE2_SEQUEL_ID
- director = "J.J. Abrams"
- }
-
If the entity is a CompositeEntity, the ID value can be constructed by creating a component column-to-value association using the CompositeID class:
val directorId = CompositeID {
- it[DirectorsTable.name] = "J.J. Abrams"
- it[DirectorsTable.guildId] = UUID.randomUUID()
- }
-
- val director = DirectorEntity.new(directorId) {
- genre = Genre.SCI_FI
- }
-
If CompositeID contains autoincrement or autogenerated columns, values for such columns are not required to be provided in CompositeID building block. In this case they will be normally generated by database.
Read
To read a value from a property, simply access it as you would with any property in a Kotlin class:
val movieName = movie.name
-
To retrieve entities, use one of the following methods:
Retrieve all
To get all the entity instances associated with this entity class, use the .all() function:
val allMovies = StarWarsFilmEntity.all()
-
Find by condition
To get all the entity instances that conform to the conditional expression, use the .find() function:
val specificMovie = StarWarsFilmEntity.find { StarWarsFilmsTable.sequelId eq MOVIE_SEQUELID }
-
Find by ID
To get an entity by its ID value, use the .findById() function:
val movie = StarWarsFilmEntity.findById(2)
-
If the entity is a CompositeEntity, its id property can be used to refer to all composite columns and to get entities, much like the id column of its associated CompositeIdTable:
val directorId = CompositeID {
- it[DirectorsTable.name] = "J.J. Abrams"
- it[DirectorsTable.guildId] = UUID.randomUUID()
- }
-
- val director = DirectorEntity.findById(directorId)
-
- val directors = DirectorEntity.find { DirectorsTable.id eq directorId }
-
The SQL query would result in something like the following:
SELECT DIRECTORS."name", DIRECTORS.GUILD_ID, DIRECTORS.GENRE
- FROM DIRECTORS
- WHERE (DIRECTORS."name" = 'J.J. Abrams')
- AND (DIRECTORS.GUILD_ID = '2cc64f4f-1a2c-41ce-bda1-ee492f787f4b')
Read an entity with a join to another table
Suppose that you want to find all users who rated the second Star Wars film with a score greater than 5. First, you would write that query using Exposed DSL.
val query = UsersTable.innerJoin(UserRatingsTable).innerJoin(StarWarsFilmsTable)
- .select(UsersTable.columns)
- .where {
- StarWarsFilmsTable.sequelId eq MOVIE_SEQUELID and (UserRatingsTable.value greater MIN_MOVIE_RATING)
- }.withDistinct()
-
Once the query is defined, you can wrap the result in the User entity using the .wrapRows() function to generate entities from the retrieved data:
val users = UserEntity.wrapRows(query).toList()
-
Sort results
The .all() function returns a SizedIterable that stores all entity instances associated with the invoking entity class. SizedIterable implements the Kotlin Iterable interface, which allows calling any sorting methods from the Kotlin standard library.
Ascending order
To sort results in ascending order, use the .sortedBy() function:
val moviesByAscOrder = StarWarsFilmEntity.all().sortedBy { it.sequelId }
-
val moviesByDescOrder = StarWarsFilmEntity.all().sortedByDescending { it.sequelId }
-
Update
You can update the value of a property just as you would with any property in a Kotlin class:
movie.name = "Episode VIII – The Last Jedi"
Update by ID
To search for an entity by its ID and apply an update, use the .findByIdAndUpdate() function:
val updatedMovie = StarWarsFilmEntity.findByIdAndUpdate(2) {
- it.name = "Episode VIII – The Last Jedi"
- }
-
Update using a query
To search for a single entity by a query and apply an update, use the .findSingleByAndUpdate() function:
val updatedMovie2 = StarWarsFilmEntity.findSingleByAndUpdate(StarWarsFilmsTable.name eq "The Last Jedi") {
- it.name = "Episode VIII – The Last Jedi"
- }
-
Suppose that you want to sort cities by the number of users in each city. To achieve this, you can write a subquery which counts the users in each city and then order the result by that count. To do so, however, you need to convert the Query to an Expression.
Suppose 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. However, to achieve this functionality, you only need to override the searchQuery() function. The results of the function can then be accessed through a property of the entity class:
package org.example.entities
-
-import org.example.tables.StarWarsFilmsWithRankTable
-import org.jetbrains.exposed.dao.IntEntity
-import org.jetbrains.exposed.dao.IntEntityClass
-import org.jetbrains.exposed.dao.id.EntityID
-import org.jetbrains.exposed.sql.Op
-import org.jetbrains.exposed.sql.Query
-
-class StarWarsFilmWithRankEntity(id: EntityID<Int>) : IntEntity(id) {
- var sequelId by StarWarsFilmsWithRankTable.sequelId
- var name by StarWarsFilmsWithRankTable.name
- var rating by StarWarsFilmsWithRankTable.rating
-
- val rank: Long
- get() = readValues[StarWarsFilmsWithRankTable.rank]
-
- companion object : IntEntityClass<StarWarsFilmWithRankEntity>(StarWarsFilmsWithRankTable) {
- override fun searchQuery(op: Op<Boolean>): Query {
- return super.searchQuery(op).adjustSelect {
- select(columns + StarWarsFilmsWithRankTable.rank)
- }
- }
- }
-}
-
package org.example.tables
-
-import org.jetbrains.exposed.dao.id.IntIdTable
-import org.jetbrains.exposed.sql.Rank
-import org.jetbrains.exposed.sql.SortOrder
-
-const val MAX_NAME_LENGTH = 32
-
-object StarWarsFilmsWithRankTable : IntIdTable() {
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", MAX_NAME_LENGTH)
- val rating = double("rating")
-
- val rank = Rank().over().orderBy(rating, SortOrder.DESC)
-}
-
Then, creating and fetching entities would look like this:
fun readComputedField() {
- transaction {
- StarWarsFilmWithRankEntity.new {
- sequelId = MOVIE_SEQUELID
- name = "The Last Jedi"
- rating = MOVIE_RATING
- }
- }
-
- transaction {
- StarWarsFilmWithRankEntity
- .find { StarWarsFilmsWithRankTable.name like "The%" }
- .map { it.name to it.rank }
- }
- }
-
Auto-fill columns on entity change
Suppose that you need all your table objects to have at minimum two columns for storing the date and time when a record is created and modified. You could define a BaseTable to automatically handle registering these columns to any table that extends this abstract class. An abstract BaseEntity could also be defined to automatically associate fields to the relevant columns:
package org.example.entities
-
-import kotlinx.datetime.LocalDateTime
-import org.example.tables.BaseTable
-import org.jetbrains.exposed.dao.IntEntity
-import org.jetbrains.exposed.dao.id.EntityID
-
-abstract class BaseEntity(id: EntityID<Int>, table: BaseTable) : IntEntity(id) {
- val created: LocalDateTime by table.created
- var modified: LocalDateTime? by table.modified
-}
-
Whenever a new entity is instantiated, the created column will be filled with the database default defined by CurrentDateTime, while the modified column will be filled with a null value.
An entity lifecycle interceptor, EntityHook, can then be subscribed to in order to automatically populate the modified field whenever an existing entity is later updated:
The example above invokes EntityHook.subscribe() in an abstract BaseEntityClass that can be used as a companion object for any BaseEntity implementation, but the interceptor could be subscribed to (and unsubscribed from) on the transaction level as well.
The subscription only performs an action when a record is updated (detected by EntityChangeType.Updated).
When an update occurs, the modified column is populated with the current UTC time using the nowUTC() method. However, the update only happens if the modified field has not already been set. This is checked by using Entity.writeValues, which holds the column-value mappings for an entity instance before being flushed to the database. Performing this check ensures that filling the modified column does not trigger the interceptor itself.
Additionally, every change (creation, update, or deletion) is logged using the exposedLogger.
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/dao-field-transformations.html b/docs/dao-field-transformations.html
index 4f137bb9fa..6c9a86dd47 100644
--- a/docs/dao-field-transformations.html
+++ b/docs/dao-field-transformations.html
@@ -2,8 +2,8 @@
You will be redirected shortly
-
+
Redirecting…
-Click here if you are not redirected.
-
+Click here if you are not redirected.
+
diff --git a/docs/dao-relationships.html b/docs/dao-relationships.html
index 6e084592c3..20cded19d2 100644
--- a/docs/dao-relationships.html
+++ b/docs/dao-relationships.html
@@ -1,211 +1,9 @@
-
-Relationships | Exposed
Exposed 0.58.0 Help
Relationships
Many-to-one
Let's say you have this table:
- object Users : IntIdTable() {
- val name = varchar("name", 50)
- }
- class User(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<User>(Users)
-
- var name by Users.name
- }
-
And now you want to add a table referencing this table (and other tables!):
- object UserRatings : IntIdTable() {
- val value = long("value")
- val film = reference("film", StarWarsFilms)
- val user = reference("user", Users)
- }
- class UserRating(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<UserRating>(UserRatings)
-
- var value by UserRatings.value
- var film by StarWarsFilm referencedOn UserRatings.film // use referencedOn for normal references
- var user by User referencedOn UserRatings.user
- }
-
Now you can get the film for a UserRating object, filmRating, in the same way you would get any other field:
- filmRating.film // returns a StarWarsFilm object
-
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:
- class StarWarsFilm(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<StarWarsFilm>(StarWarsFilms)
- //...
- val ratings by UserRating referrersOn UserRatings.film // make sure to use val and referrersOn
- //...
- }
-
You can then access this field on a StarWarsFilm object, movie:
- movie.ratings // returns all UserRating objects with this movie as film
-
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:
- class User(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<User>(Users)
- //...
- val rating by UserRating backReferencedOn UserRatings.user // make sure to use val and backReferencedOn
- }
-
You can then access this field on a User object, user1:
- user1.rating // returns a UserRating object
-
Optional reference
You can also add an optional reference:
- object UserRatings : IntIdTable() {
- //...
- val secondUser = reference("second_user", Users).nullable() // this reference is nullable!
- //...
- }
- class UserRating(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<UserRating>(UserRatings)
- //...
- var secondUser by User optionalReferencedOn UserRatings.secondUser // use optionalReferencedOn for nullable references
- //...
- }
-
Now secondUser will be a nullable field, and optionalReferrersOn should be used instead of referrersOn to get all the ratings for a secondUser.
- class User(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<User>(Users)
- //...
- val secondRatings by UserRating optionalReferrersOn UserRatings.secondUser // make sure to use val and optionalReferrersOn
- //...
- }
-
Ordered reference
You can also define the order in which referenced entities appear:
- class User(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<User>(Users)
-
- //...
- val ratings by UserRating referrersOn UserRatings.user orderBy UserRatings.value
- //...
- }
-
In a more complex scenario, you can specify multiple columns along with the corresponding sort order for each:
- class User(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<User>(Users)
-
- //...
- val ratings by UserRating referrersOn UserRatings.user orderBy listOf(UserRatings.value to SortOrder.DESC, UserRatings.id to SortOrder.ASC)
- //...
- }
-
Without using the infix notation, the orderBy method is chained after referrersOn:
- class User(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<User>(Users)
-
- //...
- val ratings by UserRating.referrersOn(UserRatings.user)
- .orderBy(UserRatings.value to SortOrder.DESC, UserRatings.id to SortOrder.ASC)
- //...
- }
-
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:
- object Actors : IntIdTable() {
- val firstname = varchar("firstname", 50)
- val lastname = varchar("lastname", 50)
- }
- class Actor(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<Actor>(Actors)
-
- var firstname by Actors.firstname
- var lastname by Actors.lastname
- }
-
Create an additional intermediate table to store the references:
- object StarWarsFilmActors : Table() {
- val starWarsFilm = reference("starWarsFilm", StarWarsFilms)
- val actor = reference("actor", Actors)
- override val primaryKey = PrimaryKey(starWarsFilm, actor, name = "PK_StarWarsFilmActors_swf_act") // PK_StarWarsFilmActors_swf_act is optional here
- }
-
Add a reference to StarWarsFilm:
- class StarWarsFilm(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<StarWarsFilm>(StarWarsFilms)
- //...
- var actors by Actor via StarWarsFilmActors
- //...
- }
-
Note: You can set up IDs manually inside a transaction like this:
- transaction {
- // only works with UUIDTable and UUIDEntity
- StarWarsFilm.new (UUID.randomUUID()){
- //...
- actors = SizedCollection(listOf(actor))
- }
- }
-
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
- object NodeTable : IntIdTable() {
- val name = varchar("name", 50)
- }
- object NodeToNodes : Table() {
- val parent = reference("parent_node_id", NodeTable)
- val child = reference("child_user_id", NodeTable)
- }
- class Node(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<Node>(NodeTable)
-
- var name by NodeTable.name
- var parents by Node.via(NodeToNodes.child, NodeToNodes.parent)
- var children by Node.via(NodeToNodes.parent, NodeToNodes.child)
- }
-
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.
- val root = Node.new { name = "root" }
- val child1 = Node.new {
- name = "child1"
- }
- child1.parents = SizedCollection(root) // assign parent
- val child2 = Node.new { name = "child2" }
- root.children = SizedCollection(listOf(child1, child2)) // assign children
-
Composite primary key reference
Assuming that we have the following CompositeIdTable:
- object Directors : CompositeIdTable("directors") {
- val name = varchar("name", 50).entityId()
- val guildId = uuid("guild_id").autoGenerate().entityId()
- val genre = enumeration<Genre>("genre")
-
- override val primaryKey = PrimaryKey(name, guildId)
- }
-
- class Director(id: EntityID<CompositeID>) : CompositeEntity(id) {
- companion object : CompositeEntityClass<Director>(Directors)
-
- var genre by Directors.genre
- }
-
We can refactor the StarWarsFilms table to reference this table by adding columns to hold the appropriate primary key values and creating a table-level foreign key constraint:
- object StarWarsFilms : IntIdTable() {
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", 50)
- val directorName = varchar("director_name", 50)
- val directorGuildId = uuid("director_guild_id")
-
- init {
- foreignKey(directorName, directorGuildId, target = Directors.primaryKey)
- }
- }
-
- class StarWarsFilm(id: EntityID<Int>) : IntEntity(id) {
- companion object : IntEntityClass<StarWarsFilm>(StarWarsFilms)
-
- var sequelId by StarWarsFilms.sequelId
- var name by StarWarsFilms.name
- var director by Director referencedOn StarWarsFilms
- }
-
Now you can get the director for a StarWarsFilm object, movie, in the same way you would get any other field:
- movie.director // returns a Director object
-
Now if you wanted to get all the films made by a director, you could add a referrersOn field to the Director class:
- class Director(id: EntityID<CompositeID>) : CompositeEntity(id) {
- companion object : CompositeEntityClass<Director>(Directors)
-
- var genre by Directors.genre
- val films by StarWarsFilm referrersOn StarWarsFilms
- }
-
You can then access this field on a Director object, director:
- director.films // returns all StarWarsFilm objects that reference this director
-
Using other previously mentioned infix functions, like optionalReferencedOn, backReferencedOn, and optionalReferrersOn, is also supported for referencing or referenced CompositeEntity objects, by using the respective overloads that accept an IdTable as an argument. These overloads will automatically resolve the foreign key constraint associated with the composite primary key.
Eager Loading
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:
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.
- StarWarsFilm.all().with(StarWarsFilm::actors)
-
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.
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/dao-table-types.html b/docs/dao-table-types.html
index 99cf444d51..171737e400 100644
--- a/docs/dao-table-types.html
+++ b/docs/dao-table-types.html
@@ -1,105 +1,9 @@
-
-Table types | Exposed
Exposed 0.58.0 Help
Table types
In Exposed, the Table class is the core abstraction for defining database tables. This class provides methods to define various column types, constraints, and other table-specific properties.
Auto-incrementing ID column tables
Apart from the core Table class, Exposed provides the base IdTable class and its subclasses through the DAO API.
The IdTable class extends Table and is designed to simplify the definition of tables that use a standard id column as the primary key. These tables can be declared without explicitly including the id column, as IDs of the appropriate type are automatically generated when creating new table rows.
IdTable and its subclasses are located in the org.jetbrains.exposed.dao.id package of the exposed-core module.
The following example represents a table with custom columns sequel_id, name, and director:
import org.jetbrains.exposed.dao.id.IntIdTable
-
-const val MAX_VARCHAR_LENGTH = 50
-
-object StarWarsFilmsTable : IntIdTable() {
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", MAX_VARCHAR_LENGTH)
- val director = varchar("director", MAX_VARCHAR_LENGTH)
-}
The IntIdTable class automatically generates an auto-incrementing integer id column, which serves as the primary key for the table. When the table is created, it corresponds to the following SQL query:
CREATE TABLE IF NOT EXISTS STARWARSFILMS
- (ID INT AUTO_INCREMENT PRIMARY KEY,
- SEQUEL_ID INT NOT NULL,
- "name" VARCHAR(50) NOT NULL,
- DIRECTOR VARCHAR(50) NOT NULL);
For more information on defining and configuring tables in Exposed, see Working with tables.
Composite ID table
To define multiple columns as part of the primary key and ID, use CompositeIdTable and mark each composite column using .entityId() .Each component column will be available for CRUD operations either individually (as for any standard column) or all together as part of the id column:
package org.example.tables
-
-import org.jetbrains.exposed.dao.id.CompositeIdTable
-
-enum class Genre { HORROR, DRAMA, THRILLER, SCI_FI }
-
-const val NAME_LENGTH = 50
-
-object DirectorsTable : CompositeIdTable("directors") {
- val name = varchar("name", NAME_LENGTH).entityId()
- val guildId = uuid("guild_id").autoGenerate().entityId()
- val genre = enumeration<Genre>("genre")
-
- override val primaryKey = PrimaryKey(name, guildId)
-}
-
If any of the key component columns have already been marked by .entityId() in another table, they can still be identified using addIdColumn(). This might be useful for key columns that reference another IdTable:
package org.example.tables
-
-import org.jetbrains.exposed.dao.id.CompositeIdTable
-
-const val DIRECTOR_NAME_LENGTH = 50
-
-object DirectorsWithGuildRefTable : CompositeIdTable() {
- val name = varchar("name", DIRECTOR_NAME_LENGTH).entityId()
- val guildId = reference("guild_id", GuildsTable)
- val genre = enumeration<Genre>("genre")
-
- init {
- addIdColumn(guildId)
- }
-
- override val primaryKey = PrimaryKey(name, guildId)
-}
-
In the definition of DirectorsCustomTable, the id field is of type Column<EntityID<String>>, which will hold String values with a length of up to 32 characters. Using the override keyword indicates that this id column is overriding the default id column provided by IdTable.
Once all columns are defined, the id column is explicitly set as the primary key for the table, using the override keyword once again.
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/data-types.html b/docs/data-types.html
index 322a5ddbdc..16a64b8f2b 100644
--- a/docs/data-types.html
+++ b/docs/data-types.html
@@ -1,560 +1,9 @@
-
-Data Types | Exposed
Exposed 0.58.0 Help
Data Types
Exposed supports the following data types in the table definition:
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 the following additional types. For more information, see How to use Json and JsonB types:
json
JSON
jsonb
JSONB
How to use database enum types
Some 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.
To work with enum database types, use the .customEnumeration() function in one of the following ways:
Use an existing enum column from your table. In this case, the sql parameter in .customEnumeration() can be left as null.
Create a new ENUM column using Exposed by providing the raw definition SQL to the sql parameter in .customEnumeration().
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 custom enumeration.
For a class like enum class Foo { BAR, BAZ }, you can use the following code for your specific database:
MySQL and H2
- val existingEnumColumn = customEnumeration("enumColumn", { value -> Foo.valueOf(value as String) }, { it.name })
- val newEnumColumn = customEnumeration("enumColumn", "ENUM('BAR', 'BAZ')", { value -> Foo.valueOf(value as String) }, { it.name })
-
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:
- class PGEnum<T : Enum<T>>(enumTypeName: String, enumValue: T?) : PGobject() {
- init {
- value = enumValue?.name
- type = enumTypeName
- }
- }
-
- object EnumTable : Table() {
- val enumColumn = customEnumeration("enumColumn", "FooEnum", { value -> Foo.valueOf(value as String) }, { PGEnum("FooEnum", it) })
- }
-
- transaction {
- exec("CREATE TYPE FooEnum AS ENUM ('BAR', 'BAZ');")
- SchemaUtils.create(EnumTable)
- }
-
How to use Json and JsonB types
Add the following dependencies to your build.gradle.kts:
- val exposedVersion: String by project
-
- dependencies {
- implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
- implementation("org.jetbrains.exposed:exposed-json:$exposedVersion")
- }
-
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:
- @Serializable
- data class Project(val name: String, val language: String, val active: Boolean)
-
- val format = Json { prettyPrint = true }
-
- object Teams : Table("team") {
- val groupId = varchar("group_id", 32)
- val project = json<Project>("project", format) // equivalent to json("project", format, Project.serializer())
- }
-
- transaction {
- val mainProject = Project("Main", "Java", true)
- Teams.insert {
- it[groupId] = "A"
- it[project] = mainProject
- }
- Teams.update({ Teams.groupId eq "A" }) {
- it[project] = mainProject.copy(language = "Kotlin")
- }
-
- Teams.selectAll().map { "Team ${it[Teams.groupId]} -> ${it[Teams.project]}" }.forEach { println(it) }
- // Team A -> Project(name=Main, language=Kotlin, active=true)
- }
-
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():
- val mapper = jacksonObjectMapper()
-
- data class Project(val name: String, val language: String, val active: Boolean)
-
- object Teams : Table("team") {
- val groupId = varchar("group_id", 32)
- val project = json("project", { mapper.writeValueAsString(it) }, { mapper.readValue<Project>(it) })
- }
-
Json functions
JSON path strings can be used to extract values (either as JSON or as a scalar value) at a specific field/key:
- val hasActiveStatus = Teams.project.exists(".active")
- val activeProjects = Teams.selectAll().where { hasActiveStatus }.count()
-
- // Depending on the database, filter paths can be provided instead, as well as optional arguments
- // PostgreSQL example
- val mainId = "Main"
- val hasMainProject = Teams.project.exists(".name ? (@ == \$main)", optional = "{\"main\":\"$mainId\"}")
- val mainProjects = Teams.selectAll().where { hasMainProject }.map { it[Teams.groupId] }
-
- val usesKotlin = Teams.project.contains("{\"language\":\"Kotlin\"}")
- val kotlinTeams = Teams.selectAll().where { usesKotlin }.count()
-
- // Depending on the database, an optional path can be provided too
- // MySQL example
- val usesKotlin = Teams.project.contains("\"Kotlin\"", ".language")
- val kotlinTeams = Teams.selectAll().where { usesKotlin }.count()
-
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:
PostgreSQL and H2 databases support the explicit array data type, with multidimensional arrays being supported by PostgreSQL.
Exposed allows defining columns as 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:
- object Teams : Table("teams") {
- // Single-dimensional arrays
- val memberIds = array<UUID>("member_ids")
- val memberNames = array<String>("member_names")
- val budgets = array<Double>("budgets")
-
- // Multi-dimensional arrays
- val nestedMemberIds = array<UUID, List<List<UUID>>>(
- "nested_member_ids", dimensions = 2
- )
- val hierarchicalMemberNames = array<String, List<List<List<String>>>>(
- "hierarchical_member_names", dimensions = 3
- )
- }
-
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:
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():
If a database-specific data type is not immediately supported by Exposed, any existing and open column type class can be extended, or a custom ColumnType class can be implemented to achieve the same functionality.
The following examples describe different ways to customize a column type, register a column with the custom type, and then start using it in transactions.
Hierarchical tree-like data
PostgreSQL provides a data type, ltree, to represent hierarchical tree-like data.
The hierarchy labels are stored as strings, so the existing StringColumnType class be extended with a few overrides:
String values representing hierarchy labels can then be inserted and queried from the path column. The following block shows an update of all records that have a stored path either equal to or a descendant of the path Top.Science, by setting a subpath of the first 2 labels as the updated value:
MySQL and MariaDB provide a data type, YEAR, for 1-byte storage of year values in the range of 1901 to 2155.
This example assumes that the column accepts string input values, but a numerical format is also possible, in which case IntegerColumnType could be extended instead:
- class YearColumnType : StringColumnType(), IDateColumnType {
- override fun sqlType(): String = "YEAR"
-
- override val hasTimePart: Boolean = false
-
- override fun valueFromDB(value: Any): String = when (value) {
- is java.sql.Date -> value.toString().substringBefore('-')
- else -> error("Retrieved unexpected value of type ${value::class.simpleName}")
- }
- }
-
- fun Table.year(name: String): Column<String> = registerColumn(name, YearColumnType())
-
The IDateColumnType interface is implemented to ensure that any default expressions are handled appropriately. For example, a new object CurrentYear can be added as a default to avoid issues with the strict column typing:
If a custom Kotlin implementation for a DateRange is set up (using Iterable and ClosedRange), then a class for the type daterange can also be added. This implementation would require a dependency on exposed-kotlin-datetime:
- class DateRangeColumnType : RangeColumnType<LocalDate, DateRange>(KotlinLocalDateColumnType()) {
- override fun sqlType(): String = "DATERANGE"
-
- override fun List<String>.toRange(): DateRange {
- val endInclusive = LocalDate.parse(last()).minus(1, DateTimeUnit.DAY)
- return DateRange(LocalDate.parse(first()), endInclusive)
- }
- }
-
- fun Table.dateRange(name: String): Column<DateRange> = registerColumn(name, DateRangeColumnType())
-
These new column types can be used in a table definition:
- object TestTable : Table("test_table") {
- val amounts = intRange("amounts").default(1..10)
- val holidays = dateRange("holidays")
- }
-
With the addition of some custom functions, the stored data can then be queried to return the upper bound of the date range for all records that have an integer range within the specified bounds:
MySQL and MariaDB provide a data type, SET, for strings that can have zero or more values from a defined list of permitted values. This could be useful, for example, when storing a list of Kotlin enum constants.
To use this type, a new ColumnType could be implemented with all the necessary overrides. This example instead takes advantage of the existing logic in StringColumnType as the base for database storage, then uses a custom ColumnTransformer to achieve the final transformation between a set of enum constants and a string:
- class SetColumnType<T : Enum<T>>(
- private val enumClass: KClass<T>
- ) : StringColumnType() {
- // uses reflection to retrieve elements of the enum class
- private val enumConstants by lazy {
- enumClass.java.enumConstants?.map { it.name } ?: emptyList()
- }
-
- override fun sqlType(): String = enumConstants
- .takeUnless { it.isEmpty() }
- ?.let { "SET(${it.joinToString { e -> "'$e'" }})" }
- ?: error("SET column must be defined with a list of permitted values")
- }
-
- inline fun <reified T : Enum<T>> Table.set(name: String): Column<String> =
- registerColumn(name, SetColumnType(T::class))
-
- class EnumListColumnType<T : Enum<T>>(
- private val enumClass: KClass<T>
- ) : ColumnTransformer<String, List<T>> {
- private val enumConstants by lazy {
- enumClass.java.enumConstants?.associateBy { it.name } ?: emptyMap()
- }
-
- override fun unwrap(value: List<T>): String {
- return value.joinToString(separator = ",") { it.name }
- }
-
- override fun wrap(value: String): List<T> = value
- .takeUnless { it.isEmpty() }?.let {
- it.split(',').map { e ->
- enumConstants[e]
- ?: error("$it can't be associated with any value from ${enumClass.qualifiedName}")
- }
- }
- ?: emptyList()
- }
-
The new column type and transformer can then be used in a table definition:
- enum class Vowel { A, E, I, O, U }
-
- object TestTable : Table("test_table") {
- val vowel: Column<List<Vowel>> = set<Vowel>("vowel")
- .transform(EnumListColumnType(Vowel::class))
- .default(listOf(Vowel.A, Vowel.E))
- }
-
Lists of enum constants can then be inserted and queried from the set column. The following block shows a query for all records that have Vowel.O stored at any position in the set column string:
A table extension function can then be added to register a new column with this type. This example assumes that the input values will be of type Map<String, String>, so transform() is used on the string column to handle parsing:
Map values representing key-value pairs of strings can then be inserted and queried from the bookDetails column. The following block queries the value associated with the title key from all bookDetails records:
String values can then be inserted and queried from the firstName column in a case-insensitive manner:
- transaction {
- val allNames = listOf("Anna", "Anya", "Agna")
- TestTable.batchInsert(allNames) { name ->
- this[TestTable.firstName] = name
- }
-
- TestTable
- .selectAll()
- .where { TestTable.firstName like "an%" }
- .toList()
- }
-
Column transformation
Column transformations allow to define custom transformations between database column types and application's data types. This can be particularly useful when you need to store data in one format but work with it in another format within your application.
Consider the following example, where we define a table to store meal times and transform these times into meal types:
The .transform() function is used to apply custom transformations to the mealTime column:
The wrap() function transforms the stored LocalTime values into Meal enums. It checks the hour of the stored time and returns the corresponding meal type.
The unwrap() function transforms Meal enums back into LocalTime values for storage in the database.
Transformation could be also defined as an implementation of the ColumnTransformer interface and reused across different tables:
The .nullTransform() method applies a special transformation that allows a non-nullable database column to accept and/or return values as null on the client side.
This transformation does not alter the column's definition in the database, which will still be NON NULL. It enables reflecting non-null values from the database as null in Kotlin (e.g., converting an empty string from a non-nullable text column, empty lists, negative IDs, etc., to null).
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/dsl-crud-operations.html b/docs/dsl-crud-operations.html
index 3f04441252..91a1bdf4c0 100644
--- a/docs/dsl-crud-operations.html
+++ b/docs/dsl-crud-operations.html
@@ -1,195 +1,9 @@
-
-CRUD operations | Exposed
Exposed 0.58.0 Help
CRUD operations
CRUD stands for Create Read Update Delete, which are four basic operations for a database to support. This section shows how to perform SQL CRUD operations using Kotlin DSL.
Create
Exposed provides several functions to insert rows into a table:
Insert a single row
To create a new table row, use the .insert() function. If the same row already exists in the table, it throws an exception.
StarWarsFilmsTable.insert {
- it[sequelId] = MOVIE_SEQUEL_ID
- it[name] = "The Force Awakens"
- it[director] = "J.J. Abrams"
- }
The example corresponds to the following SQL statement:
INSERT INTO STARWARSFILMS (SEQUEL_ID, "name", DIRECTOR)
- VALUES (7, 'The Force Awakens', 'J.J. Abrams')
Insert and get ID
To add a new row and return its ID, use .insertAndGetId(). If the same row already exists in the table, it throws an exception.
val id = StarWarsFilmsIntIdTable.insertAndGetId {
- it[sequelId] = MOVIE_SEQUEL_ID
- it[name] = "The Force Awakens"
- it[director] = "J.J. Abrams"
- }
-
INSERT INTO STAR_WARS_FILMS_TABLE (SEQUEL_ID, "name", DIRECTOR)
- VALUES (7, 'The Force Awakens', 'J.J. Abrams')
Insert from select
For the INSERT INTO ... SELECT SQL clause, use the .insert() function with a Query parameter:
val substring = UsersTable.name.substring(1, 2)
-
- val insertedRows = CitiesTable.insert(UsersTable.select(substring).orderBy(UsersTable.id).limit(2))
-
By default, it will try to insert into all non auto-increment Table columns in the order they are defined in the Table instance. If you want to specify columns or change the order, provide a list of columns as the second parameter:
To allow insert statements to be executed without throwing any errors, use .insertIgnore(). This may be useful, for example, when insertion conflicts are possible:
StarWarsFilmsIntIdTable.insert {
- it[sequelId] = MOVIE_SEQUEL_2_ID // column pre-defined with a unique index
- it[name] = "The Last Jedi"
- it[director] = "Rian Johnson"
- }
-
- StarWarsFilmsIntIdTable.insertIgnore {
- it[sequelId] = MOVIE_SEQUEL_2_ID
- it[name] = "The Last Jedi"
- it[director] = "Rian Johnson"
- }
-
If .insert() was used instead of .insertIgnore(), this would throw a constraint violation exception. Instead, this new row is ignored and discarded.
Insert and ignore and get ID
.insertIgnoreAndGetId() adds a new row and returns its ID. If the same row already exists in the table, it ignores it and doesn't throw an exception.
val rowId = StarWarsFilmsIntIdTable.insertIgnoreAndGetId {
- it[sequelId] = MOVIE_SEQUEL_ID
- it[name] = "The Last Jedi"
- it[director] = "Rian Johnson"
- }
-
INSERT IGNORE INTO STAR_WARS_FILMS_TABLE (SEQUEL_ID, "name", DIRECTOR)
- VALUES (8, 'The Last Jedi', 'Rian Johnson')
Batch insert
.batchInsert() allows mapping a list of entities into table rows in a single SQL statement. It is more efficient than using the insert query for each row as it initiates only one statement.
The following example uses a simple list:
val cityNames = listOf("Paris", "Moscow", "Helsinki")
-
- CitiesTable.batchInsert(cityNames) { name ->
- this[CitiesTable.name] = name
- }
Here is an example that uses a list of data class instances:
data class SWFilmData(val sequelId: Int, val name: String, val director: String)
-
- val films = listOf(
- SWFilmData(MOVIE_ORIGINAL_ID, "A New Hope", "George Lucas"),
- SWFilmData(MOVIE_ORIGINAL_2_ID, "The Empire Strikes Back", "Irvin Kershner"),
- SWFilmData(MOVIE_ORIGINAL_3_ID, "Return of the Jedi", "Richard Marquand")
- )
-
- StarWarsFilmsTable.batchInsert(films) { (id, name, director) ->
- this[StarWarsFilmsTable.sequelId] = id
- this[StarWarsFilmsTable.name] = name
- this[StarWarsFilmsTable.director] = director
- }
If you don't need to get the newly generated values, such as the auto-incremented ID, set the shouldReturnGeneratedValues parameter to false. This increases the performance of batch inserts by batching them in chunks, instead of always waiting for the database to synchronize the newly inserted object state.
If you want to check if rewriteBatchedInserts and batchInsert are working correctly, you need to enable JDBC logging for your driver. This is necessary, as Exposed will always show the non-rewritten multiple inserts. For more information, see how to enable logging in PostgresSQL.
Read
Retrieve a record
The .select() function allows you to select specific columns or/and expressions.
val filmAndDirector = StarWarsFilmsTable.select(StarWarsFilmsTable.name, StarWarsFilmsTable.director).map {
- it[StarWarsFilmsTable.name] to it[StarWarsFilmsTable.director]
- }
-
If you want to select only distinct value then use .withDistinct() function:
Some databases return a count of the number of rows inserted, updated, or deleted by the CRUD operation. For .insert(), .upsert(), and .replace(), this value can be accessed using the statement class property insertedCount:
val insertStatement = StarWarsFilmsTable.insertIgnore {
- it[name] = "The Last Jedi"
- it[sequelId] = MOVIE_SEQUEL_3_ID
- it[director] = "Rian Johnson"
- }
-
- val rowCount: Int = insertStatement.insertedCount
-
Return data from modified rows
Some databases allow the return of additional data every time a row is either inserted, updated, or deleted. This can be accomplished by using one of the following functions:
Insert or update (Upsert) is a database operation that either inserts a new row or updates an existing row if a duplicate constraint already exists. The supported functionality of .upsert() is dependent on the specific database being used. For example, MySQL's INSERT ... ON DUPLICATE KEY UPDATE statement automatically assesses the primary key and unique indices for a duplicate value, so using the function in Exposed would look like this:
StarWarsFilmsTable.upsert {
- it[sequelId] = MOVIE_SEQUEL_ID // column pre-defined with a unique index
- it[name] = "The Rise of Skywalker"
- it[director] = "Rian Johnson"
- }
- // updates existing row with the correct [director]
- StarWarsFilmsTable.upsert {
- it[sequelId] = MOVIE_SEQUEL_ID
- it[name] = "The Rise of Skywalker"
- it[director] = "JJ Abrams"
- }
If none of the optional arguments are provided to .upsert(), and an onUpdate block is omitted, the statements in the body block will be used for both the insert and update parts of the operation. This means that, for example, if a table mapping has columns with default values and these columns are omitted from the body block, the default values will be used for insertion as well as for the update operation.
Using another example, PostgreSQL allows more control over which key constraint columns to check for conflict, whether different values should be used for an update, and whether the update statement should have a WHERE clause:
If the update operation should be identical to the insert operation except for a few columns, then onUpdateExclude should be provided as an argument with the specific columns to exclude. This parameter could also be used for the reverse case when only a small subset of columns should be updated but duplicating the insert values is tedious:
// on conflict, all columns EXCEPT [director] are updated with values from the lambda block
- StarWarsFilmsTable.upsert(onUpdateExclude = listOf(StarWarsFilmsTable.director)) {
- it[sequelId] = MOVIE_SEQUEL_ID
- it[name] = "The Rise of Skywalker"
- it[director] = "JJ Abrams"
- }
-
- // on conflict, ONLY column [director] is updated with value from the lambda block
- StarWarsFilmsTable.upsert(
- onUpdateExclude = StarWarsFilmsTable.columns - setOf(StarWarsFilmsTable.director)
- ) {
- it[sequelId] = MOVIE_SEQUEL_ID
- it[name] = "The Rise of Skywalker"
- it[director] = "JJ Abrams"
- }
If a specific database supports user-defined key columns and none are provided, the table's primary key is used. If there is no defined primary key, the first unique index is used. If there are no unique indices, each database handles this case differently, so it is strongly advised that keys are defined to avoid unexpected results.
Replace
The .replace() method acts in a similar manner to an .upsert(). The only difference is that if an insertion would violate a unique constraint, the existing row is deleted before the new row is inserted.
StarWarsFilmsTable.replace {
- it[sequelId] = MOVIE_SEQUEL_3_ID
- it[releaseYear] = MOVIE_3_RELEASE_YEAR
- it[name] = "The Rise of Skywalker"
- it[director] = "JJ Abrams"
- }
- // deletes existing row and inserts new row with set [rating]
- StarWarsFilmsTable.replace {
- it[sequelId] = MOVIE_SEQUEL_3_ID
- it[releaseYear] = MOVIE_3_RELEASE_YEAR
- it[name] = "The Rise of Skywalker"
- it[director] = "JJ Abrams"
- it[rating] = MOVIE_RATING
- }
Unlike .upsert(), none of the supporting databases allows a WHERE clause. Also, the constraints used to assess a violation are limited to the primary key and unique indexes, so there is no parameter for a custom key set.
The values specified in the statement block will be used for the insert statement, and any omitted columns are set to their default values, if applicable.
In the example above, if the original row was inserted with a user-defined rating and .replace() was executed with a block that omitted the rating column, the newly inserted row would store the default rating value. This is because the old row was completely deleted first.
The REPLACE INTO ... SELECT SQL clause can be used by instead providing a query to .replace():
val allRowsWithLowRating: Query = StarWarsFilmsTable.selectAll().where {
- StarWarsFilmsTable.rating less LOW_RAITING_THRESHOLD
- }
- StarWarsFilmsTable.replace(allRowsWithLowRating)
By default, it will try to insert into all non auto-increment Table columns in the order they are defined in the Table instance. If the columns need to be specified or the order should be changed, provide a list of columns as the second parameter:
To delete records and return the count of deleted rows, use the .deleteWhere() function.
val deletedRowsCount = StarWarsFilmsTable.deleteWhere { StarWarsFilmsTable.sequelId eq MOVIE_SEQUEL_ID }
-
Any SqlExpressionBuilder comparison operators or extension functions used in the op parameter lambda block will require inclusion of an import statement:
To delete records while ignoring any possible errors that occur during the process, use the .deleteIgnoreWhere() function. The function will return the count of deleted rows.
val deleteIgnoreRowsCount = StarWarsFilmsIntIdTable.deleteIgnoreWhere { StarWarsFilmsIntIdTable.sequelId eq MOVIE_SEQUEL_ID }
-
Delete all
To delete all rows in a table and return the count of deleted rows, use the .deleteAll() function.
val allDeletedRowsCount = StarWarsFilmsTable.deleteAll()
-
Join delete
To delete records from a table in a join relation, use the .delete() function with a Join as its receiver. Provide the specific table from which records should be deleted as the argument to the parameter targetTable.
val join = StarWarsFilmsIntIdTable.join(ActorsIntIdTable, JoinType.INNER, StarWarsFilmsIntIdTable.id, ActorsIntIdTable.sequelId)
-
- val deletedActorsCount = join.delete(ActorsIntIdTable) { ActorsIntIdTable.sequelId greater ACTORS_SEQUEL_ID }
-
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/dsl-joining-tables.html b/docs/dsl-joining-tables.html
index 45fdd8b6cb..43e31aefab 100644
--- a/docs/dsl-joining-tables.html
+++ b/docs/dsl-joining-tables.html
@@ -1,79 +1,9 @@
-
-Joining tables | Exposed
Exposed 0.58.0 Help
Joining tables
Join
For the join examples below, consider the following tables:
package org.example.tables
-
-import org.jetbrains.exposed.dao.id.IntIdTable
-
-object StarWarsFilmsIntIdTable : IntIdTable("star_wars_films_table") {
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", MAX_VARCHAR_LENGTH)
- val director = varchar("director", MAX_VARCHAR_LENGTH)
-}
-
package org.example.tables
-
-import org.jetbrains.exposed.dao.id.IntIdTable
-
-const val MAX_NAME_LENGTH = 50
-
-object ActorsIntIdTable : IntIdTable("actors") {
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", MAX_NAME_LENGTH)
-}
-
package org.example.tables
-
-import org.jetbrains.exposed.dao.id.IntIdTable
-
-const val MAX_CHARACTER_NAME_LENGTH = 50
-
-object RolesTable : IntIdTable() {
- val sequelId = integer("sequel_id")
- val actorId = reference("actor_id", ActorsIntIdTable)
- val characterName = varchar("name", MAX_CHARACTER_NAME_LENGTH)
-}
-
In the following example, the .join() function is used to count how many actors star in each movie:
To combine the results of multiple queries, use the .union() function. Per the SQL specification, the queries must have the same number of columns, and not be marked for update. Subqueries may be combined when supported by the database.
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/dsl-querying-data.html b/docs/dsl-querying-data.html
index f10424894c..d42f1eb4e4 100644
--- a/docs/dsl-querying-data.html
+++ b/docs/dsl-querying-data.html
@@ -1,131 +1,9 @@
-
-Querying data | Exposed
Exposed 0.58.0 Help
Querying data
Working with where expressions
The where expression is a core component of building queries in Exposed, allowing you to filter data by specifying conditions. A where expression expects a boolean operator (Op<Boolean>), which evaluates to either true or false.
We’ve categorized the available conditions into the following groups:
Checks whether an expression matches a given pattern based on a specific mode.
Supported only on MySQL and MariaDB.
Range conditions
To check if a value lies within a specified range, use the .between() function.
It checks if an expression is between the values from and to. Returns true if the expression is between the lower and upper range values (inclusive). Date and time values are also supported as arguments.
To check if an expression is equal or not to any element from a list, use the inList or notInList operators.
Using inList with lists
The inList operator checks if an expression matches any value in a list. In the following example, the query selects all films with sequelId values of 6 or 4.
The inList operator can also handle multiple expressions, such as pairs or triples, to check for equality across multiple columns.
val topRated = listOf(MOVIE_SEQUEL_ID to "Empire Strikes Back", MOVIE_ORIGINAL_ID to "A New Hope")
-
- val multipleInList = StarWarsFilmsTable.selectAll()
- .where {
- StarWarsFilmsTable.sequelId to StarWarsFilmsTable.name inList topRated
- }
-
Using ANY and ALL
In addition to the IN operator, the ANY and ALL operators are available with any preceding comparison operator:
anyFrom() and allFrom() also accept subqueries, tables, and array expressions as arguments.
Conditional WHERE
When working with SQL databases, it is a rather common case to have a query with a WHERE clause that depends on certain conditions. These conditions often come from application logic or user input. Managing such conditions can become complex, especially with independent or nested conditions.
Imagine a form on a website where a user can optionally filter "Star Wars" films by a director and/or a sequel. To construct such a query, you can use the .andWhere() function:
In the above example, the query starts with selectAll(), which retrieves all rows from the StarWarsFilmsTable. Then, the let function is used to ensure that conditional WHERE clauses for the directorName and sequelId are applied only if values are provided.
Conditional joins
Sometimes, you may need to modify not just the WHERE clause but also the underlying table joins based on certain conditions. For example, filtering by an actor's name might require joining ActorsTable conditionally.
In these cases, you can use the .adjustColumnSet() and .adjustSelect() functions, which allow to extend and modify JOIN and SELECT parts of a query:
Aliases allow preventing ambiguity between field names and table names. To use the aliased var instead of the original one, use the .alias() function:
val filmTable1 = StarWarsFilmsTable.alias("ft1")
-
- val allFilms = filmTable1.selectAll() // can be used in joins etc'
-
Aliases also allow you to use the same table in a join multiple times:
val sequelTable = StarWarsFilmsTable.alias("sql")
-
- val originalAndSequelNames = StarWarsFilmsTable
- .join(sequelTable, JoinType.INNER, StarWarsFilmsTable.sequelId, sequelTable[StarWarsFilmsTable.id])
- .select(StarWarsFilmsTable.name, sequelTable[StarWarsFilmsTable.name])
- .map { it[StarWarsFilmsTable.name] to it[sequelTable[StarWarsFilmsTable.name]] }
-
And they can be used when selecting from sub-queries:
val starWarsFilms = StarWarsFilmsTable
- .select(StarWarsFilmsTable.id, StarWarsFilmsTable.name)
- .alias("swf")
-
- val id = starWarsFilms[StarWarsFilmsTable.id]
-
- val name = starWarsFilms[StarWarsFilmsTable.name]
-
- val allStarWarsFilms = starWarsFilms
- .select(id, name)
- .map { it[id] to it[name] }
-
Custom Select Queries
A Query instance, which can be instantiated by calling .selectAll() or .select() on a Table or Join, has many extension functions for building complex queries. Some of these have already been mentioned above, like .where(), .groupBy(), and .orderBy().
If a SELECT query with a special clause is required, a custom extension function can be implemented to enable its use with other standard queries.
For example, MySQL index hints, which follow the table name in SQL, can be implemented on a SELECT query by using the following custom function and class:
import org.jetbrains.exposed.sql.Query
-import org.jetbrains.exposed.sql.QueryBuilder
-import org.jetbrains.exposed.sql.Table
-
-fun Query.indexHint(hint: String) = IndexHintQuery(this, hint)
-
-class IndexHintQuery(
- val source: Query,
- val indexHint: String
-) : Query(source.set, source.where) {
-
- init {
- // copies any stored properties from the original query
- source.copyTo(this)
- }
-
- override fun prepareSQL(builder: QueryBuilder): String {
- val originalSql = super.prepareSQL(builder)
- val fromTableSql = " FROM ${transaction.identity(set.source as Table)} "
- return originalSql.replace(fromTableSql, "$fromTableSql$indexHint ")
- }
-
- override fun copy(): IndexHintQuery = IndexHintQuery(source.copy(), indexHint).also { copy ->
- copyTo(copy)
- }
-}
val originalQuery = StarWarsFilmsIntIdTable
- .selectAll()
- .withDistinct()
- .where { StarWarsFilmsIntIdTable.sequelId less MOVIE_SEQUEL_ID }
- .groupBy(StarWarsFilmsIntIdTable.id)
-
- originalQuery.indexHint("FORCE INDEX (PRIMARY)")
- .orderBy(StarWarsFilmsIntIdTable.sequelId)
- .forEach { println(it[StarWarsFilmsIntIdTable.name]) }
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/dsl-table-types.html b/docs/dsl-table-types.html
index 805ca26cc5..ad11c536c3 100644
--- a/docs/dsl-table-types.html
+++ b/docs/dsl-table-types.html
@@ -1,35 +1,9 @@
-
-Table types | Exposed
Exposed 0.58.0 Help
Table types
In Exposed, the Table class is the core abstraction for defining database tables. This class provides methods to define various column types, constraints, and other table-specific properties.
Table is located in the org.jetbrains.exposed.sql package of the exposed-core module.
The following example defines a table with an auto-incrementing integer id column and string name and director column:
import org.jetbrains.exposed.sql.Table
-
-const val MAX_VARCHAR_LENGTH = 50
-
-object StarWarsFilmsTable : Table() {
- val id = integer("id").autoIncrement()
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", MAX_VARCHAR_LENGTH)
- val director = varchar("director", MAX_VARCHAR_LENGTH)
-}
CREATE TABLE IF NOT EXISTS STARWARSFILMS
- (ID INT AUTO_INCREMENT NOT NULL,
- SEQUEL_ID INT NOT NULL,
- "name" VARCHAR(50) NOT NULL,
- DIRECTOR VARCHAR(50) NOT NULL)
For more information on defining and configuring tables in Exposed, see Working with tables.
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/exposed-modules.html b/docs/exposed-modules.html
index 4a9906bc85..94afaa6058 100644
--- a/docs/exposed-modules.html
+++ b/docs/exposed-modules.html
@@ -1,141 +1,9 @@
-
-Modules | Exposed
Exposed 0.58.0 Help
Modules
Dependencies
Exposed modules are available from Maven Central repository. To use them you have to add appropriate dependency into your repositories mapping.
- repositories {
- mavenCentral()
- }
-
The Maven Central repository is enabled by default for Maven users.
- repositories {
- mavenCentral()
- }
-
Base Modules
Exposed 0.18.1 and higher
To move forward and support such features as Java 8 Time, async drivers, and so on, it was decided to split Exposed into more specific modules. It will allow you to take the only modules you need and will add flexibility in the future.
Exposed consists of the following modules:
exposed-core - base module, which contains both DSL api along with mapping
exposed-crypt - provides additional column types to store encrypted data in DB and encode/decode it on client-side
exposed-dao - DAO api
exposed-java-time - date-time extensions based on Java8 Time API
exposed-jdbc - transport level implementation based on Java JDBC API
exposed-jodatime - date-time extensions based on JodaTime library
exposed-json - JSON and JSONB data type extensions
exposed-kotlin-datetime - date-time extensions based on kotlinx-datetime
exposed-money - extensions to support MonetaryAmount from "javax.money:money-api"
exposed-spring-boot-starter - a starter for Spring Boot to utilize Exposed as the ORM instead of Hibernate
Dependencies mapping listed below is similar (by functionality) to the previous versions:
You also need a JDBC driver for the database system you are using (see Working with Databases) and a logger for addLogger(StdOutSqlLogger). Example (Gradle syntax):
-dependencies {
- // for H2
- implementation("com.h2database:h2:2.1.214")
- // for logging (StdOutSqlLogger), see
- // http://www.slf4j.org/codes.html#StaticLoggerBinder
- implementation("org.slf4j:slf4j-nop:1.7.30")
-}
-
Exposed 0.17.x and lower
Prior Exposed 0.18.1 there was only one base module exposed which contains everything you may need including JodaTime as date-time library. To add Exposed framework of that version, you had to use:
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/frequently-asked-questions.html b/docs/frequently-asked-questions.html
index cc2db33d5c..f1cb083ac6 100644
--- a/docs/frequently-asked-questions.html
+++ b/docs/frequently-asked-questions.html
@@ -1,64 +1,9 @@
-
-Frequently Asked Questions | Exposed
Exposed 0.58.0 Help
Frequently Asked Questions
Q: Squash is same as Exposed. Where is the difference?
Use QueryBuiler with false - if you want to inline statement arguments, true - to see '?' in query.
Q: Is it possible to use native sql / sql as a string?
A: It is not supported as part of the library, but it is possible to implement on top of it and use it like this:
-fun <T:Any> String.execAndMap(transform : (ResultSet) -> T) : List<T> {
- val result = arrayListOf<T>()
- TransactionManager.current().exec(this) { rs ->
- while (rs.next()) {
- result += transform(rs)
- }
- }
- return result
-}
-
-"select u.name, c.name from user u inner join city c where blah blah".execAndMap { rs ->
- rs.getString("u.name") to rs.getString("c.name")
-}
-
A: Implement DatabaseDialect interface and register it with Database.registerDialect(). If the implementation adds a lot of value consider contributing it as a PR to Exposed.
Q: Is it possible to create tables with cross / cyclic reference?
A: Yes, it's possible since Exposed 0.11.1 version
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/getting-started-with-exposed.html b/docs/getting-started-with-exposed.html
index 1e95395a92..67e6c20e7d 100644
--- a/docs/getting-started-with-exposed.html
+++ b/docs/getting-started-with-exposed.html
@@ -1,193 +1,9 @@
-
-Get started with Exposed, an ORM framework for Kotlin | Exposed
Exposed 0.58.0 Help
Get started with Exposed, an ORM framework for Kotlin
In this tutorial, you’ll learn how to use Exposed’s Domain-Specific Language (DSL) API to store and retrieve data in a relational database by building a simple console application.
By the end of this tutorial, you’ll be able to do the following:
Configure database connections using an in-memory database.
Define database tables using Exposed’s DSL.
Perform basic CRUD (Create, Read, Update, and Delete) operations on the database.
Prerequisites
Before starting this tutorial, ensure that you have the following installed on your machine:
We recommend that you install IntelliJ IDEA Ultimate which comes with built-in database tools and the Exposed plugin for code completion and inspections. However, you can use another IDE of your choice.
Create a new project
First, you will need a basic Kotlin project setup to build upon. You can download a pre-initialized project or follow the steps below to generate a new project with Gradle.
In a terminal window, navigate to the destination where you want to create your project and run the following commands to create a new folder and change directory into it:
- mkdir exposed-kotlin-app
- cd exposed-kotlin-app
-
Run the gradle init task to initialize a new Gradle project:
- gradle init
-
When prompted, select the following options:
1: Application project type.
2: Kotlin implementation language.
For the other questions, press enter to use the default values. The output will look like the following:
- Select type of build to generate:
- 1: Application
- 2: Library
- 3: Gradle plugin
- 4: Basic (build structure only)
- Enter selection (default: Application) [1..4]
- Select implementation language:
- 1: Java
- 2: Kotlin
- 3: Groovy
- 4: Scala
- 5: C++
- 6: Swift
- Enter selection (default: Java) [1..6] 2
- Enter target Java version (min: 7, default: 21):
- Project name (default: exposed-kotlin-app):
- Select application structure:
- 1: Single application project
- 2: Application and library project
- Enter selection (default: Single application project) [1..2]
- Select build script DSL:
- 1: Kotlin
- 2: Groovy
- Enter selection (default: Kotlin) [1..2]
- Select test framework:
- 1: kotlin.test
- 2: JUnit Jupiter
- Enter selection (default: kotlin.test) [1..2]
- Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no]
- > Task :init
- To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.8/samples/sample_building_kotlin_applications.html
- BUILD SUCCESSFUL in 28s
- 1 actionable task: 1 executed
-
Once the project has been initialized, open the project folder in your IDE. To open the project in IntelliJ IDEA, use the following command:
- idea .
-
Add dependencies
Before you start using Exposed, you need to provide dependencies to your project.
Navigate to the gradle/libs.versions.toml file and define the Exposed version and libraries:
The exposed-core module provides the foundational components and abstractions needed to work with databases in a type-safe manner and includes the DSL API.
The exposed-jdbc module is an extension of the exposed-core module that adds support for Java Database Connectivity (JDBC).
Navigate to the app/build.gradle.kts file and add the Exposed and H2 database modules into the dependencies block:
In intelliJ IDEA, click on the notification Gradle icon () on the right side of the editor to load Gradle changes.
Configure a database connection
Every database access using Exposed is started by obtaining a connection and creating a transaction. To configure the database connection, use the Database.connect() function.
Navigate to app/src/main/kotlin/org/example/ and open the App.kt file.
Replace the contents of the App.kt file with the following implementation:
The Database.connect() function creates an instance of a class that represents the database and takes two or more parameters. In this case, the connection URL and the driver.
jdbc:h2:mem:test is the database URL to connect to:
jdbc specifies that this is a JDBC connection.
h2 indicates that the database is an H2 database.
mem specifies that the database is in-memory, meaning the data will only exist in memory and will be lost when the application stops.
test is the name of the database.
org.h2.Driver specifies the H2 JDBC driver to be used for establishing the connection.
With this, you've added Exposed to your Kotlin project and configured a database connection. You're now ready to define your data model and engage with the database using Exposed's DSL API.
Define a table object
In Exposed, a database table is represented by an object inherited from the Table class. To define the table object, follow the steps below.
In the app/src/main/kotlin/org/example/ folder, create a new Task.kt file.
Open Task.kt and add the following table definition:
import org.jetbrains.exposed.sql.Table
-
-const val MAX_VARCHAR_LENGTH = 128
-
-object Tasks : Table("tasks") {
- val id = integer("id").autoIncrement()
- val title = varchar("name", MAX_VARCHAR_LENGTH)
- val description = varchar("description", MAX_VARCHAR_LENGTH)
- val isCompleted = bool("completed").default(false)
-}
-
In the Table constructor, passing the name tasks configures a custom name for the table. Keep in mind that if no custom name is specified, Exposed will generate one from the class name, which might lead to unexpected results.
Within the Tasks object, four columns are defined:
id of type Int is defined with the integer() method. The autoIncrement() function indicates that this column will be an auto-incrementing integer, typically used for primary keys.
title and description of type String are defined with the varchar() method.
isCompleted of type Boolean is defined with the bool() method. Using the default() function, you configure the default value to false.
At this point, you have defined a table with columns, which essentially creates the blueprint for the Tasks table.
To now create and populate the table within the database, you need to open a transaction.
Create and query a table
With Exposed’s DSL API, you can interact with a database using a type-safe syntax similar to SQL. Before you start executing database operations, you must open a transaction.
A transaction is represented by an instance of the Transaction class, within which you can define and manipulate data using its lambda function. Exposed will automatically manage the opening and closing of the transaction in the background, ensuring seamless operation.
Navigate back to the App.kt file and add the following transaction function:
package org.example
-
-import Tasks
-import org.jetbrains.exposed.sql.*
-import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
-import org.jetbrains.exposed.sql.transactions.transaction
-
-fun main() {
- Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
-
- transaction {
- SchemaUtils.create(Tasks)
-
- val taskId = Tasks.insert {
- it[title] = "Learn Exposed"
- it[description] = "Go through the Get started with Exposed tutorial"
- } get Tasks.id
-
- val secondTaskId = Tasks.insert {
- it[title] = "Read The Hobbit"
- it[description] = "Read the first two chapters of The Hobbit"
- it[isCompleted] = true
- } get Tasks.id
-
- println("Created new tasks with ids $taskId and $secondTaskId.")
-
- Tasks.select(Tasks.id.count(), Tasks.isCompleted).groupBy(Tasks.isCompleted).forEach {
- println("${it[Tasks.isCompleted]}: ${it[Tasks.id.count()]} ")
- }
- }
-}
Let's break down the code and go over each section.
First, you create the tasks table using the create() method from SchemaUtils. The SchemaUtils object holds utility methods for creating, altering, and dropping database objects.
Once the table has been created, you use the Table extension method insert() to add two new Task records.
val taskId = Tasks.insert {
- it[title] = "Learn Exposed"
- it[description] = "Go through the Get started with Exposed tutorial"
- } get Tasks.id
-
- val secondTaskId = Tasks.insert {
- it[title] = "Read The Hobbit"
- it[description] = "Read the first two chapters of The Hobbit"
- it[isCompleted] = true
- } get Tasks.id
Within the insert block, you set the values for each column by using the it parameter. Exposed will translate the functions into the following SQL queries:
- INSERT INTO TASKS (COMPLETED, DESCRIPTION, "name") VALUES (FALSE, 'Go through the Get started with Exposed tutorial', 'Learn Exposed')
-
- INSERT INTO TASKS (COMPLETED, DESCRIPTION, "name") VALUES (TRUE, 'Read the first two chapters of The Hobbit', 'Read The Hobbit')
-
Because the insert() function returns an InsertStatement, by using the get() method after the insert operation you retrieve the autoincremented id value of the newly added row.
With the select() extension function you then create a query to count the number of rows and to retrieve the isCompleted value for each row in the table.
Using groupBy() groups the results of the query by the isCompleted column, which means it will aggregate the rows based on whether they are completed or not. The expected SQL query looks like this:
- SELECT COUNT(TASKS.ID), TASKS.COMPLETED FROM TASKS GROUP BY TASKS.COMPLETED
-
It is important to note that the query will not be executed until you call a function that iterates through the result, such as forEach(). In this example, for each group we print out the isCompleted status and the corresponding count of tasks.
Before you test the code, it would be handy to be able to inspect the SQL statements and queries Exposed sends to the database. For this, you need to add a logger.
Enable logging
At the beginning of your transaction block, add the following line to enable SQL query logging:
In IntelliJ IDEA, click on the run button () to start the application.
The application will start in the Run tool window at the bottom of the IDE. There you will be able to see the SQL logs along with the printed results:
- SQL: SELECT VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE NAME = 'MODE'
- SQL: CREATE TABLE IF NOT EXISTS TASKS (ID INT AUTO_INCREMENT NOT NULL, "name" VARCHAR(128) NOT NULL, DESCRIPTION VARCHAR(128) NOT NULL, COMPLETED BOOLEAN DEFAULT FALSE NOT NULL)
- SQL: INSERT INTO TASKS (COMPLETED, DESCRIPTION, "name") VALUES (FALSE, 'Go through the Get started with Exposed tutorial', 'Learn Exposed')
- SQL: INSERT INTO TASKS (COMPLETED, DESCRIPTION, "name") VALUES (TRUE, 'Read the first two chapters of The Hobbit', 'Read The Hobbit')
- Created new tasks with ids 1 and 2.
- SQL: SELECT COUNT(TASKS.ID), TASKS.COMPLETED FROM TASKS GROUP BY TASKS.COMPLETED
- false: 1
- true: 1
-
- Process finished with exit code 0
-
Update and delete a task
Let’s extend the app’s functionality by updating and deleting the same task.
In the same transaction() function, add the following code to your implementation:
In the Tasks.update() function, you specify the condition to find the task with id equal to the one of the previously inserted task. If the condition is met, the isCompleted field of the found task is set to true.
Unlike the insert() function, update() returns the number of updated rows. To then retrieve the updated task, you use the select() function with the where condition to only select the tasks with id equal to taskId.
val updatedTask = Tasks.select(Tasks.isCompleted).where(Tasks.id eq taskId).single()
Using the single() extension function initiates the statement and retrieves the first result found.
The deleteWhere() function, on the other hand, deletes the task with the specified condition.
Tasks.deleteWhere { id eq secondTaskId }
Similarly to update(), it returns the number of rows that have been deleted.
In IntelliJ IDEA, click the rerun button () to restart the application.
You should now see the following result:
- SQL: SELECT VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE NAME = 'MODE'
- SQL: CREATE TABLE IF NOT EXISTS TASKS (ID INT AUTO_INCREMENT NOT NULL, "name" VARCHAR(128) NOT NULL, DESCRIPTION VARCHAR(128) NOT NULL, COMPLETED BOOLEAN DEFAULT FALSE NOT NULL)
- SQL: INSERT INTO TASKS (COMPLETED, DESCRIPTION, "name") VALUES (FALSE, 'Go through the Get started with Exposed tutorial', 'Learn Exposed')
- SQL: INSERT INTO TASKS (COMPLETED, DESCRIPTION, "name") VALUES (TRUE, 'Read the first two chapters of The Hobbit', 'Read The Hobbit')
- Created new tasks with ids 1 and 2.
- SQL: SELECT COUNT(TASKS.ID), TASKS.COMPLETED FROM TASKS GROUP BY TASKS.COMPLETED
- false: 1
- true: 1
- SQL: UPDATE TASKS SET COMPLETED=TRUE WHERE TASKS.ID = 1
- SQL: SELECT TASKS.COMPLETED FROM TASKS WHERE TASKS.ID = 1
- Updated task details: Tasks.completed=true
- SQL: DELETE FROM TASKS WHERE TASKS.ID = 2
- SQL: SELECT TASKS.ID, TASKS."name", TASKS.DESCRIPTION, TASKS.COMPLETED FROM TASKS
- Remaining tasks: [Tasks.id=1, Tasks.name=Learn Exposed, Tasks.description=Go through the Get started with Exposed tutorial, Tasks.completed=true]
-
- Process finished with exit code 0
-
Next steps
Great job! You have now implemented a simple console application that uses Exposed to fetch and modify task data from an in-memory database. Now that you’ve covered the basics, you are ready to dive deep into the DSL API.
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/home.html b/docs/home.html
index 5e08387437..e344c79b87 100644
--- a/docs/home.html
+++ b/docs/home.html
@@ -1,16 +1,9 @@
-
-Exposed Documentation | Exposed
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/migration-guide.html b/docs/migration-guide.html
index 1d8bd0674d..85059dbf85 100644
--- a/docs/migration-guide.html
+++ b/docs/migration-guide.html
@@ -1,80 +1,9 @@
-
-Migrating from 0.45.0 to 0.46.0 | Exposed
Exposed 0.58.0 Help
Migrating from 0.45.0 to 0.46.0
While Exposed provides migration support in the code itself (by using the @Deprecated annotation and ReplaceWith quickfix), this document serves as a reference point for the migration steps necessary to switch to the new query DSL.
SELECT Query DSL
Exposed's query DSL has been refactored to bring it closer to the syntax of a standard SQL SELECT statement.
The slice() function has been deprecated in favor of a new select() function that accepts the same variable amount of columns and creates a Query instance. If all columns should be selected, use selectAll() to create a Query instance.
The Query class now has the method where(), which can be chained to replace the old version of select { }.
Putting these changes together results in the following new DSL:
-// Example 1
-// before
-TestTable
- .slice(TestTable.columnA)
- .select { TestTable.columnA eq 1 }
-
-// after
-TestTable
- .select(TestTable.columnA)
- .where { TestTable.columnA eq 1 }
-
-// Example 2
-// before
-TestTable
- .slice(TestTable.columnA)
- .selectAll()
-
-// after
-TestTable
- .select(TestTable.columnA)
-
-// Example 3
-// before
-TestTable
- .select { TestTable.columnA eq 1 }
-
-// after
-TestTable
- .selectAll()
- .where { TestTable.columnA eq 1 }
-
-// Example 4 - no change
-TestTable.selectAll()
-
To be consistent with these changes, the functions selectBatched() and selectAllBatched() have also been deprecated. A new Query method, fetchBatchedResults(), should be used instead as a terminal operation on an existing Query:
-// Example 1
-// before
-TestTable
- .selectBatched(50) { TestTable.columnA eq 1 }
-
-// after
-TestTable
- .selectAll()
- .where { TestTable.columnA eq 1 }
- .fetchBatchedResults(50)
-
-// Example 2
-// before
-TestTable
- .slice(TestTable.columnA)
- .selectAllBatched(50)
-
-// after
-TestTable
- .select(TestTable.columnA)
- .fetchBatchedResults(50)
-
Lastly, adjustSlice() has been renamed to adjustSelect():
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/sql-functions.html b/docs/sql-functions.html
index 7d208d2341..8f0cdb6626 100644
--- a/docs/sql-functions.html
+++ b/docs/sql-functions.html
@@ -1,114 +1,9 @@
-
-SQL Functions | Exposed
Exposed 0.58.0 Help
SQL Functions
Exposed provides basic support for classic SQL functions. This topic consists of definitions for those functions, and their usage examples. It also explains how to define custom functions.
How to use functions
If you want to retrieve a function result from a query, you have to declare the function as a variable:
The concat() function returns a string value that concatenates the text representations of all non-null input values, separated by an optional separator.
These functions should be used in queries with groupBy.
Min/Max/Average
To get the minimum, maximum, and average values, use the .min().max() and .avg functions respectively. These functions can be applied to any comparable expression:
If you can't find your most loved function used in your database (as Exposed provides only basic support for classic SQL functions), you can define your own functions.
Since Exposed 0.15.1 there multiple options to define custom functions:
Function without parameters:
-val sqrt = FooTable.id.function("SQRT")
-
In SQL representation it will be SQRT(FooTable.id)
The CustomFunction class accepts a function name as a first parameter and the resulting column type as second. After that, you can provide any amount of parameters separated by a comma.
There are also shortcuts for string, long, and datetime functions:
Function that requires more complex query building:
All functions in Exposed extend the abstract class Function, which takes a column type and allows overriding toQueryBuilder(). This is what CustomFunction actually does, which can be leveraged to create more complex queries.
For example, Exposed provides a trim() function that removes leading and trailing whitespace from a String. In MySQL, this is just the default behavior as specifiers can be provided to limit the trim to either leading or trailing, as well as providing a specific substring other than spaces to remove. The custom function below supports this extended behavior:
Window functions allow calculations across a set of table rows that are related to the current row.
Existing aggregate functions (like sum(), avg()) can be used, as well as new rank and value functions:
cumeDist()
denseRank()
firstValue()
lag()
lastValue()
lead()
nthValue()
nTile()
percentRank()
rank()
rowNumber()
To use a window function, include the OVER clause by chaining .over() after the function call. A PARTITION BY and ORDER BY clause can be optionally chained using .partitionBy() and .orderBy(), which both take multiple arguments:
-FooTable.amount.sum().over().partitionBy(FooTable.year, FooTable.product).orderBy(FooTable.amount)
-
-rowNumber().over().partitionBy(FooTable.year, FooTable.product).orderBy(FooTable.amount)
-
-FooTable.amount.sum().over().orderBy(FooTable.year to SortOrder.DESC, FooTable.product to SortOrder.ASC_NULLS_FIRST)
-
Frame clause functions (like rows(), range(), and groups()) are also supported and take a WindowFrameBound option depending on the expected result:
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/starting-page-Home.json b/docs/starting-page-Home.json
index 1f257a7eb3..a6a266d768 100644
--- a/docs/starting-page-Home.json
+++ b/docs/starting-page-Home.json
@@ -1 +1 @@
-{"title":"Exposed Documentation","subtitle":"Exposed is a Kotlin SQL database library with two flavors: a lightweight ORM (using DAO) and type-safe SQL (using DSL).","tips":[{"title":"\n Get Started with Exposed\n ","description":"Learn how to create and query tables in Kotlin with Exposed DSL API","url":"getting-started-with-exposed.html","type":"learn"},{"title":"\n API Reference\n ","description":"External API Documentation","url":"https://jetbrains.github.io/Exposed/api/index.html","type":"server"}],"main":{"title":"Learn More","data":[{"title":"\n Adding Dependencies\n ","description":"Learn how to configure Exposed in the existing project using Gradle or Maven build systems","url":"exposed-modules.html"},{"title":"Working with tables","description":"Start creating your first tables and get familiar with the column types","url":"working-with-tables.html"},{"title":"\n Querying a Database\n ","description":"Learn how to write database queries using Exposed query DSL","url":"dsl-crud-operations.html"},{"title":"\n Introduction to ORM Entities\n ","description":"Learn how to perform basic CRUD (Create, Read, Update, Delete) operations using entities mapping","url":"dao-crud-operations.html"}]},"highlighted":{"title":"Releases and Contribution","data":[{"title":"\n Contributing to Exposed\n ","description":"Learn how to contribute to Exposed","url":"contributing.html"},{"title":"\n Migration Guide\n ","description":"Check how to update to the latest Exposed version","url":"migration-guide.html"}]},"prevNextLinks":{"nextPageUrl":"about.html","nextPageTitle":"Exposed, an ORM framework for Kotlin"}}
\ No newline at end of file
+{"title":"Exposed Documentation","subtitle":"Exposed is a Kotlin SQL database library with two flavors: a lightweight ORM (using DAO) and type-safe SQL (using DSL).","tips":[{"title":"\n Get Started with Exposed\n ","description":"Learn how to create and query tables in Kotlin with Exposed DSL API","url":"null","type":"learn"},{"title":"\n API Reference\n ","description":"External API Documentation","url":"https://jetbrains.github.io/Exposed/api/index.html","type":"server"}],"main":{"title":"Learn More","data":[{"title":"\n Adding Dependencies\n ","description":"Learn how to configure Exposed in the existing project using Gradle or Maven build systems","url":"null"},{"title":"Working with tables","description":"Start creating your first tables and get familiar with the column types","url":"null"},{"title":"\n Querying a Database\n ","description":"Learn how to write database queries using Exposed query DSL","url":"null"},{"title":"\n Introduction to ORM Entities\n ","description":"Learn how to perform basic CRUD (Create, Read, Update, Delete) operations using entities mapping","url":"null"}]},"highlighted":{"title":"Releases and Contribution","data":[{"title":"\n Contributing to Exposed\n ","description":"Learn how to contribute to Exposed","url":"contributing.html"},{"title":"\n Migration Guide\n ","description":"Check how to update to the latest Exposed version","url":"null"}]},"prevNextLinks":{"nextPageUrl":"contributing.html","nextPageTitle":"Contributing to Exposed"}}
\ No newline at end of file
diff --git a/docs/transactions.html b/docs/transactions.html
index fc542100d2..fcc61c6757 100644
--- a/docs/transactions.html
+++ b/docs/transactions.html
@@ -1,164 +1,9 @@
-
-Working with Transactions | Exposed
Exposed 0.58.0 Help
Working with Transactions
CRUD operations in Exposed must be called from within a transaction. Transactions encapsulate a set of DSL operations. To create and execute a transaction with default parameters, simply pass a function block to the transaction() function:
-transaction {
- // DSL/DAO operations go here
-}
-
Transactions are executed synchronously on the current thread, so they will block other parts of your application! If you need to execute a transaction asynchronously, consider running it on a separate thread.
Accessing returned values
Although you can modify variables from your code within the transaction block, transaction() supports returning a value directly, enabling immutability:
-val jamesList = transaction {
- Users.selectAll().where { Users.firstName eq "James" }.toList()
-}
-// jamesList is now a List<ResultRow> containing Users data
-
-// without eagerLoading
-val idsAndContent = transaction {
- Documents.selectAll().limit(10).map { it[Documents.id] to it[Documents.content] }
-}
-
-// with eagerLoading for text fields
-object Documents : Table() {
- //...
- val content = text("content", eagerLoading = true)
-}
-
-val documentsWithContent = transaction {
- Documents.selectAll().limit(10)
-}
-
Working with multiple databases
If you want to work with different databases, you have to store the database reference returned by Database.connect() and provide it to transaction() function as the first parameter. The transaction() block without parameters will work with the latest connected database.
Entities stick to a transaction that was used to load that entity. That means that all changes persist to the same database and what cross-database references are prohibited and will throw exceptions.
Setting default database
A transaction() block without parameters will use the default database. As before 0.10.1 this will be the latest connected database. It is also possible to set the default database explicitly.
-val db = Database.connect()
-TransactionManager.defaultDatabase = db
-
Using nested transactions
By default, a nested transaction() block shares the transaction resources of its parent transaction() block, so any effect on the child affects the parent:
Since Exposed 0.16.1 it is possible to use nested transactions as separate transactions by setting useNestedTransactions = true on the desired Database instance.
After that any exception or rollback operation that happens within a transaction block will not roll back the whole transaction but only the code inside the current transaction. Exposed uses SQL SAVEPOINT functionality to mark the current transaction at the beginning of a transaction() block and release it on exit.
Using SAVEPOINT could affect performance, so please read the documentation of the DBMS you use for more details.
In the modern world, non-blocking and asynchronous code is popular. Kotlin has Coroutines, which provide an imperative way to write asynchronous code. Most Kotlin frameworks (like Ktor) have built-in support for coroutines, while Exposed is mostly blocking.
Why?
Because Exposed interacts with databases using JDBC API, which was designed in an era of blocking APIs. Additionally, Exposed stores some values in thread-local variables while coroutines could (and will) be executed in different threads.
Starting from Exposed 0.15.1, bridge functions are available that give you a safe way to interact with Exposed within suspend blocks: newSuspendedTransaction() and Transaction.withSuspendTransaction(). These have the same parameters as a blocking transaction() but allow you to provide a CoroutineContext argument that explicitly specifies the CoroutineDispatcher in which the function will be executed. If context is not provided your code will be executed within the current CoroutineContext.
Here is an example that uses these three types of transactions:
-transaction {
- println("Transaction # ${this.id}") // Transaction # 1
- SchemaUtils.create(FooTable) // Table will be created on a current thread
-
- runBlocking {
- newSuspendedTransaction(Dispatchers.Default) {
- println("Transaction # ${this.id}") // Transaction # 2
- FooTable.insert { it[id] = 1 } // This insert will be executed in one of the Default dispatcher threads
-
- withSuspendTransaction {
- println("Transaction # ${this.id}") // Transaction # 2
- // This select also will be executed on some thread from Default dispatcher using the same transaction as its parent
- FooTable.selectAll().where { FooTable.id eq 1 }.single()[FooTable.id]
- }
- }
- }
-
- transaction {
- println("Transaction # ${this.id}") // Transaction # 1
- }
-
- runBlocking {
- val result = newSuspendedTransaction(Dispatchers.IO) {
- println("Transaction # ${this.id}") // Transaction # 3
- FooTable.selectAll().where { FooTable.id eq 1 }.single()[FooTable.id] // This select will be executed on some thread from IO dispatcher
- }
- println("Result: $result") // Result: 1
- }
-
- SchemaUtils.drop(Testing)
-}
-
Please note that such code remains blocking (as it still uses JDBC) and you should not try to share a transaction between multiple threads as it may lead to undefined behavior.
If you desire to execute some code asynchronously and use the result later, take a look at suspendedTransactionAsync():
This function will accept the same parameters as newSuspendedTransaction() above but returns its future result as an implementation of Deferred, which you can await on to achieve your result.
Advanced parameters and usage
For specific functionality, transactions can be created with the additional parameters: transactionIsolation, readOnly, and db:
-transaction (Connection.TRANSACTION_SERIALIZABLE, true, db = db) {
- // DSL/DAO operations go here
-}
-
transactionIsolation
The transactionIsolation parameter, defined in the SQL standard, specifies what is supposed to happen when multiple transactions execute concurrently on the database. This value does NOT affect Exposed operation directly, but is sent to the database, where it is expected to be obeyed. Allowable values are defined in java.sql.Connection and are as follows:
TRANSACTION_NONE: Transactions are not supported.
TRANSACTION_READ_UNCOMMITTED: The most lenient setting. Allows uncommitted changes from one transaction to affect a read in another transaction (a "dirty read").
TRANSACTION_READ_COMMITTED: This setting prevents dirty reads from occurring, but still allows non-repeatable reads to occur. A non-repeatable read is when a transaction ("Transaction A") reads a row from the database, another transaction ("Transaction B") changes the row, and Transaction A reads the row again, resulting in an inconsistency.
TRANSACTION_REPEATABLE_READ: The default setting for Exposed transactions. Prevents both dirty and non-repeatable reads, but still allows for phantom reads. A phantom read is when a transaction ("Transaction A") selects a list of rows through a WHERE clause, another transaction ("Transaction B") performs an INSERT or DELETE with a row that satisfies Transaction A's WHERE clause, and Transaction A selects using the same WHERE clause again, resulting in an inconsistency.
TRANSACTION_SERIALIZABLE: The strictest setting. Prevents dirty reads, non-repeatable reads, and phantom reads.
readOnly
The readOnly parameter indicates whether any database connection used by the transaction is in read-only mode, and is set to false by default. Much like with transactionIsolation, this value is not directly used by Exposed, but is simply relayed to the database.
db
The db parameter is optional and is used to select the database where the transaction should be settled (see the section above).
maxAttempts
Transactions also provide a property, maxAttempts, which sets the maximum number of attempts that should be made to perform a transaction block. If this value is set to 1 and an SQLException occurs inside the transaction block, the exception will be thrown without performing a retry. If this property is not set, any default value provided in DatabaseConfig will be used instead:
-val db = Database.connect(
- datasource = datasource,
- databaseConfig = DatabaseConfig {
- defaultMaxAttempts = 3
- }
-)
-
-// property set in transaction block overrides default DatabaseConfig
-transaction(db = db) {
- maxAttempts = 25
- // operation that may need multiple attempts
-}
-
If this property is set to a value greater than 1, minRetryDelay and maxRetryDelay can also be set in the transaction block to indicate the minimum and maximum number of milliseconds to wait before retrying.
queryTimeout
Another advanced property available in a transaction block is queryTimeout. This sets the number of seconds to wait for each statement in the block to execute before timing out:
-transaction {
- queryTimeout = 3
- try {
- // operation that may run for more than 3 seconds
- } catch (cause: ExposedSQLException) {
- // logic to perform if execution timed out
- }
-}
-
Statement Interceptors
DSL operations within a transaction create SQL statements, on which commands like Execute, Commit, and Rollback are issued. Exposed provides the StatementInterceptor interface that allows you to implement your own logic before and after these specific steps in a statement's lifecycle.
registerInterceptor() and unregisterInterceptor() can be used to enable and disable a custom interceptor in a single transaction.
To use a custom interceptor that acts on all transactions, implement the GlobalStatementInterceptor interface instead. Exposed uses the Java SPI ServiceLoader to discover and load any implementations of this interface. In this situation, a new file should be created in the resources folder named:
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/working-with-database.html b/docs/working-with-database.html
index fe94dcb73f..0a10be7cac 100644
--- a/docs/working-with-database.html
+++ b/docs/working-with-database.html
@@ -1,94 +1,9 @@
-
-Working with Databases | Exposed
Exposed 0.58.0 Help
Working with Databases
In Exposed, the Database class represents a database instance, and encapsulates the necessary connection details and configuration required to interact with a specific database.
Connecting to a Database
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:
-val db = Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
-
H2
In order to use H2, you need to add the H2 driver dependency:
By default, H2 closes the database when the last connection is closed. If you want to keep the database open, you can use the DB_CLOSE_DELAY=-1 option:
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/working-with-datasource.html b/docs/working-with-datasource.html
index 7653f33cd5..f454ef8549 100644
--- a/docs/working-with-datasource.html
+++ b/docs/working-with-datasource.html
@@ -1,42 +1,9 @@
-
-Working with DataSources | Exposed
Exposed 0.58.0 Help
Working with DataSources
It is also possible to provide a javax.sql.DataSource to the Database.connect() function. This allows you to use more advanced features like connection pooling, and lets you set configuration options like maximum number of connections, connection timeouts, etc.
-val db = Database.connect(dataSource)
-
Example with 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):
-val config = HikariConfig().apply {
- jdbcUrl = "jdbc:mysql://localhost/dbname"
- driverClassName = "com.mysql.cj.jdbc.Driver"
- username = "username"
- password = "password"
- maximumPoolSize = 6
- // as of version 0.46.0, if these options are set here, they do not need to be duplicated in DatabaseConfig
- isReadOnly = false
- transactionIsolation = "TRANSACTION_SERIALIZABLE"
-}
-
-// Gradle
-implementation "mysql:mysql-connector-java:8.0.33"
-implementation "com.zaxxer:HikariCP:4.0.3"
-
Then instantiate a HikariDataSource with this configuration class and provide it to Database.connect():
-val dataSource = HikariDataSource(config)
-
-Database.connect(
- datasource = dataSource,
- databaseConfig = DatabaseConfig {
- // set other parameters here
- }
-)
-
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/working-with-schema.html b/docs/working-with-schema.html
index 89010e1e43..6ec53ea5cb 100644
--- a/docs/working-with-schema.html
+++ b/docs/working-with-schema.html
@@ -1,26 +1,9 @@
-
-Working with Schema | Exposed
Exposed 0.58.0 Help
Working with Schema
A database schema defines how data is organized in a relational database. With Exposed, you can create and drop a new or an existing schema.
Define a schema
To define a schema in Exposed, use the Schema class:
- val schema = Schema("my_schema") // my_schema is the schema name.
-
Additionally, you can specify the schema owner by passing an authorization argument (some databases require the explicit owner) :
- val schema = Schema("my_schema", authorization = "owner")
-
Create a new schema
To create a new schema, use the .createSchema() method provided by SchemaUtils:
- SchemaUtils.createSchema(schema)
-
Set a default schema
If you have many schemas, and you want to set a default one, you can use the .setSchema() method from SchemaUtils:
- SchemaUtils.setSchema(schema)
-
Drop a schema
To drop a schema, use the .dropSchema() method provided by SchemaUtils:
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/working-with-sequence.html b/docs/working-with-sequence.html
index 244912868c..cd29925833 100644
--- a/docs/working-with-sequence.html
+++ b/docs/working-with-sequence.html
@@ -1,42 +1,9 @@
-
-Working with Sequence | Exposed
Exposed 0.58.0 Help
Working with Sequence
A sequence is a database object that automatically generates integer values in sequential order. It is particularly useful in generating unique identifiers and primary keys.
Define a sequence
To define a sequence in Exposed, use the Sequence class:
- val myseq = Sequence("my_sequence") // my_sequence is the sequence name.
-
Several parameters can be specified to control the properties of the sequence:
\ No newline at end of file
+
+
+
+You will be redirected shortly
+
+
Redirecting…
+Click here if you are not redirected.
+
+
diff --git a/docs/working-with-tables.html b/docs/working-with-tables.html
index 7c59d0dd44..0f49df37dc 100644
--- a/docs/working-with-tables.html
+++ b/docs/working-with-tables.html
@@ -1,175 +1,9 @@
-
-Working with tables | Exposed
Exposed 0.58.0 Help
Working with tables
In this topic, we will explain how to define, configure, and create tables. All examples use the H2 database to generate SQL.
Table Types
Table
In Exposed, the Table class is the core abstraction for defining database tables. This class provides methods to define various column types, constraints, and other table-specific properties.
Table is located in the org.jetbrains.exposed.sql package of the exposed-core module.
IdTable
Apart from the core Table class, Exposed provides the base IdTable class and its subclasses through the DAO API.
The IdTable class extends Table and is designed to simplify the definition of tables that use a standard id column as the primary key. These tables can be declared without explicitly including the id column, as IDs of the appropriate type are automatically generated when creating new table rows.
IdTable and its subclasses are located in the org.jetbrains.exposed.dao.id package of the exposed-core module.
A database table is represented by an object inherited from a Table class.
- object StarWarsFilms : Table() {}
-
Exposed supports a variety of column types, including integer, varchar, bool, and more. Each column is defined by calling the appropriate method on the Table object.
The following example defines a table with an auto-incrementing integer id column and custom columns sequel_id, name, and director:
import org.jetbrains.exposed.sql.Table
-
-const val MAX_VARCHAR_LENGTH = 50
-
-object StarWarsFilmsTable : Table() {
- val id = integer("id").autoIncrement()
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", MAX_VARCHAR_LENGTH)
- val director = varchar("director", MAX_VARCHAR_LENGTH)
-}
import org.jetbrains.exposed.dao.id.IntIdTable
-
-const val MAX_VARCHAR_LENGTH = 50
-
-object StarWarsFilmsTable : IntIdTable() {
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", MAX_VARCHAR_LENGTH)
- val director = varchar("director", MAX_VARCHAR_LENGTH)
-}
The IntIdTable class automatically generates an auto-incrementing integer id column, which serves as the primary key for the table. Therefore, there is no need to explicitly define the id column in the StarWarsFilmsTable object.
Creating the table with the above definition will result in the following SQL equivalent:
CREATE TABLE IF NOT EXISTS STARWARSFILMS
- (ID INT AUTO_INCREMENT NOT NULL,
- SEQUEL_ID INT NOT NULL,
- "name" VARCHAR(50) NOT NULL,
- DIRECTOR VARCHAR(50) NOT NULL)
CREATE TABLE IF NOT EXISTS STARWARSFILMS
- (ID INT AUTO_INCREMENT PRIMARY KEY,
- SEQUEL_ID INT NOT NULL,
- "name" VARCHAR(50) NOT NULL,
- DIRECTOR VARCHAR(50) NOT NULL);
Configuring a custom table name
By default, Exposed generates the table name from the full class name.
If the object name contains a 'Table' suffix, Exposed will omit the suffix from the generated table name:
object StarWarsFilmsTable : Table() {
- val id = integer("id").autoIncrement()
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", MAX_VARCHAR_LENGTH)
- val director = varchar("director", MAX_VARCHAR_LENGTH)
-}
-
CREATE TABLE IF NOT EXISTS STARWARSFILMS
- (ID INT AUTO_INCREMENT NOT NULL,
- SEQUEL_ID INT NOT NULL,
- "name" VARCHAR(50) NOT NULL,
- DIRECTOR VARCHAR(50) NOT NULL)
To configure a custom name for a table, which will be used in actual SQL queries, pass it to the name parameter of the Table constructor.
object CustomStarWarsFilmsTable : Table("all_star_wars_films") {
- val id = integer("id").autoIncrement()
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", MAX_VARCHAR_LENGTH)
- val director = varchar("director", MAX_VARCHAR_LENGTH)
-}
-
CREATE TABLE IF NOT EXISTS ALL_STAR_WARS_FILMS
- (ID INT AUTO_INCREMENT NOT NULL,
- SEQUEL_ID INT NOT NULL,
- "name" VARCHAR(50) NOT NULL,
- DIRECTOR VARCHAR(50) NOT NULL)
Some databases, like H2, fold unquoted identifiers to upper case. To keep table name case-sensitivity, manually quote the provided argument:
object StarWarsFilms : Table("\"all_star_wars_films\"") {
- val id = integer("id").autoIncrement()
- val sequelId = integer("sequel_id").uniqueIndex()
- val name = varchar("name", MAX_VARCHAR_LENGTH)
- val director = varchar("director", MAX_VARCHAR_LENGTH)
-}
-
CREATE TABLE IF NOT EXISTS "all_star_wars_films"
- (ID INT AUTO_INCREMENT NOT NULL,
- SEQUEL_ID INT NOT NULL,
- "name" VARCHAR(50) NOT NULL,
- DIRECTOR VARCHAR(50) NOT NULL)
Depending on what DBMS you use, the types of columns could be different in actual SQL queries.
Constraints
Nullable
The NOT NULL SQL constraint restricts the column to accept the null value. By default, Exposed applies this constraint to all the columns. To allow the column to be nullable, apply the .nullable() method to a definition of an appropriate column.
For example, to make the population column nullable, use the following code:
- // SQL: POPULATION INT NULL
- val population: Column<Int?> = integer("population").nullable()
-
Default
The DEFAULT SQL constraint provides the default value for the column. Exposed supports three methods for configuring default values:
Exposed also supports marking a column as databaseGenerated if the default value of the column is not known at the time of table creation and/or if it depends on other columns. It makes it possible to omit setting a value for the column when inserting a new record, without getting an error. The value for the column can be set by creating a TRIGGER or with a DEFAULT clause, for example.
For example:
- val name: Column<String> = varchar("name", 50).databaseGenerated()
-
Index
The INDEX SQL constraint makes traversing through tables quicker. Exposed supports the .index() method. It has six parameters, most of which are optional:
customIndexName: String? = null
A custom name for the index, which will be used in actual SQL queries.
Defines a condition used to create a partial index.
The simplest way to create an index is to use an extension function directly on a column. For example, to apply a non-unique INDEX constraint to the name column, use the following code:
- val name = varchar("name", 50).index()
-
If the customIndexName parameter is not set, the name of the index is determined by the table and column names.
Complex indexes
If you have a frequent query for two columns, Exposed can perform it more efficiently. It creates a tree from the first column with the references to the second one. For example, to create a non-unique complex index on the name and population columns, paste the following code:
- val indexName = index("indexName", false, *arrayOf(name, population))
- // or inside an init block within the table object
- init {
- index("indexName", isUnique = false, name, population)
- }
-
Index with a custom type
Exposed also supports creating an index with a custom type. For example, to retrieve data from the name column faster with a hash function for traversing, use the following code:
Operator expressions, like plus(), are also accepted by the functions parameter.
Some databases support creating a partial index by defining a filter expression to improve querying performance. The created index will only contain entries for the table rows that match this predicate:
- init {
- index(columns = arrayOf(name, flag)) { flag eq true }
- index(
- columns = arrayOf(
- name,
- population
- )
- ) { (name like "A%") and (population greaterEq 10) }
- }
-
Access indices
Once a table has been created, the list of its indices can be accessed using the property Table.indices. Table indices are represented by the data class Index, so its properties can be checked in the following way:
- Table.indices.map { it.indexName to it.createStatement().first() }
-
Unique
The UNIQUE SQL constraint restricts duplicates within this column. Exposed supports the .uniqueIndex() method which creates a unique index for the column. This method is the composition of UNIQUE and INDEX constraint, the quicker modification of UNIQUE constraint.
For example, to apply UNIQUE and INDEX constraint to the name column, use the following code:
- val name = varchar("name", 50).uniqueIndex()
-
Primary Key
The PRIMARY KEY SQL constraint applied to a column means each value in that column identifies the row. This constraint is the composition of NOT NULL and UNIQUE constraints. To change the column set, add columns, or change the primary key name to a custom one, override this field of the table class.
For example, to define the name column as the primary key, use the following code. The "Cities_name" string will be used as the constraint name in the actual SQL query, if provided; otherwise a name will be generated based on the table's name.
- override val primaryKey = PrimaryKey(name, name = "Cities_name")
-
- CONSTRAINT Cities_name PRIMARY KEY ("name")
-
It is also possible to define a primary key on a table using multiple columns:
- override val primaryKey = PrimaryKey(id, name)
-
- CONSTRAINT pk_Cities PRIMARY KEY (ID, "name")
-
Except for CompositeIdTable, each available class in Exposed that inherits from IdTable has the primaryKey field automatically defined. For example, the IntIdTable by default has an auto-incrementing integer column, id, which is defined as the primary key.
An IdTable that requires a primary key with multiple columns can be defined using CompositeIdTable. In this case, each column that is a component of the table's ID should be identified by .entityId():
- object Towns : CompositeIdTable("towns") {
- val areaCode = integer("area_code").autoIncrement().entityId()
- val latitude = decimal("latitude", 9, 6).entityId()
- val longitude = decimal("longitude", 9, 6).entityId()
- val name = varchar("name", 32)
-
- override val primaryKey = PrimaryKey(areaCode, latitude, longitude)
- }
-
If any of the key component columns have already been marked by .entityId() in another table, they can still be identified using addIdColumn(). This might be useful for key columns that reference another IdTable:
- object AreaCodes : IdTable<Int>("area_codes") {
- override val id = integer("code").entityId()
- override val primaryKey = PrimaryKey(id)
- }
-
- object Towns : CompositeIdTable("towns") {
- val areaCode = reference("area_code", AreaCodes)
- val latitude = decimal("latitude", 9, 6).entityId()
- val longitude = decimal("longitude", 9, 6).entityId()
- val name = varchar("name", 32)
-
- init {
- addIdColumn(areaCode)
- }
-
- override val primaryKey = PrimaryKey(areaCode, latitude, longitude)
- }
-
Foreign Key
The FOREIGN KEY SQL constraint links two tables. A foreign key is a column from one table that refers to the primary key or columns with a unique index from another table. To configure a foreign key on a column, use reference() or optReference() methods. The latter lets the foreign key accept a null value. To configure a foreign key on multiple columns, use the foreignKey() function directly within an init block.
reference() and optReference() methods have several parameters:
name: String
A name for the foreign key column, which will be used in actual SQL queries.
ref: Column<T>
A target column from another parent table.
onDelete: ReferenceOption? = null
An action for when a linked row from a parent table will be deleted.
onUpdate: ReferenceOption? = null
An action for when a value in a referenced column will be changed.
An option that restricts changes on a referenced column, and the default option for most dialects.
NO_ACTION
The same as RESTRICT in some, but not all, databases, and the default option for Oracle and SQL Server dialects.
CASCADE
An option that allows updating or deleting the referring rows.
SET_NULL
An option that sets the referring column values to null.
SET_DEFAULT
An option that sets the referring column values to the default value.
Consider the following Citizens table. This table has the name and city columns. If the Cities table has configured the name column as the primary key, the Citizens table can refer to it by its city column, which is a foreign key. To configure such reference and make it nullable, use the optReference() method:
- object Citizens : IntIdTable() {
- val name = varchar("name", 50)
- val city = optReference("city", Cities.name, onDelete = ReferenceOption.CASCADE)
- }
-
If any Cities row will be deleted, the appropriate Citizens row will be deleted too.
If instead the Cities table has configured multiple columns as the primary key (for example, both id and name columns as in the above section), the Citizens table can refer to it by using a table-level foreign key constraint. In this case, the Citizens table must have defined matching columns to store each component value of the Cities table's primary key:
- object Citizens : IntIdTable() {
- val name = varchar("name", 50)
- val cityId = integer("city_id")
- val cityName = varchar("city_name", 50)
-
- init {
- foreignKey(cityId, cityName, target = Cities.primaryKey)
- }
- }
-
In the above example, the order of the referencing columns in foreignKey() must match the order of columns defined in the target primary key. If this order is uncertain, the foreign key can be defined with explicit column associations instead:
- init {
- foreignKey(cityId to Cities.id, cityName to Cities.name)
- }
-
Check
The CHECK SQL constraint checks that all values in a column match some condition. Exposed supports the .check() method. You apply this method to a column and pass the appropriate condition to it.
For example, to check that the name column contains strings that begin with a capital letter, use the following code:
- // SQL: CONSTRAINT check_Cities_0 CHECK (REGEXP_LIKE("NAME", '^[A-Z].*', 'c')))
- val name = varchar("name", 50).check { it regexp "^[A-Z].*" }
-
Some databases, like older MySQL versions, may not support CHECK constraints. For more information, consult the relevant documentation.