{"id":20239129,"url":"https://github.com/edvin/kdbc","last_synced_at":"2025-07-25T20:41:47.015Z","repository":{"id":90980565,"uuid":"47409136","full_name":"edvin/kdbc","owner":"edvin","description":"SQL DSL for Kotlin","archived":false,"fork":false,"pushed_at":"2017-12-07T14:50:52.000Z","size":197,"stargazers_count":44,"open_issues_count":2,"forks_count":6,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-04T22:43:39.159Z","etag":null,"topics":["kotlin","sql","sql-dsl","sql-queries","sql-query","sql-query-builder"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edvin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-12-04T14:36:50.000Z","updated_at":"2023-07-17T17:30:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"43b364d9-65f3-488d-9907-a54ddeef4c1a","html_url":"https://github.com/edvin/kdbc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edvin%2Fkdbc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edvin%2Fkdbc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edvin%2Fkdbc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edvin%2Fkdbc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edvin","download_url":"https://codeload.github.com/edvin/kdbc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248281414,"owners_count":21077423,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kotlin","sql","sql-dsl","sql-queries","sql-query","sql-query-builder"],"created_at":"2024-11-14T08:37:34.563Z","updated_at":"2025-04-10T19:36:08.689Z","avatar_url":"https://github.com/edvin.png","language":"Kotlin","readme":"# SQL DSL for Kotlin\n\nKDBC provides type safe SQL queries for Kotlin. Features:\n\n- 100% control of executed SQL\n- Column selections and joins can be easily reused in multiple queries\n- Explicit, but convenient O/R mapping\n- Optional DDL generation\n\n## Binaries\n\nUntil this is released on Maven Central, you can use [JitPack](https://jitpack.io/) to build a snapshot as a dependency using Gradle or Maven:\n\n**Gradle**\n\n```groovy\nrepositories {\n     maven { url 'https://jitpack.io' }\n}\ndependencies {\n    compile 'com.github.edvin:kdbc:master-SNAPSHOT'\n}  \n```\n\n**Maven**\n\n```groovy \n\u003crepositories\u003e\n    \u003crepository\u003e\n        \u003cid\u003ejitpack.io\u003c/id\u003e\n        \u003curl\u003ehttps://jitpack.io\u003c/url\u003e\n    \u003c/repository\u003e\n\u003c/repositories\u003e\n\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.edvin\u003c/groupId\u003e\n    \u003cartifactId\u003ekdbc\u003c/artifactId\u003e\n    \u003cversion\u003emaster-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Usage \n\nTo query or update a table you need a `Table` object that represents the database table.\n\n```kotlin\nclass CUSTOMER : Table() {\n    val id = column\u003cInt\u003e(\"id)\n    val name = column\u003cString\u003e(\"name)\n    val zip = column\u003cString\u003e(\"zip)\n    val city = column\u003cString\u003e(\"city\")\n}\n```\n\nYou will probably also have a corresponding domain object:\n\n```kotlin\ndata class Customer(\n    var id: Int,\n    var name: String,\n    var zip: String,\n    var city: String\n)\n```\n\n\nA Query is encapsulated in a class. Every table you mention in the\nquery needs an alias, defined by instantiating one or more `Table` instances.\n\nYou override the `get` function to tell the query how to turn a result set\ninto your domain object. You don't need to work with the `ResultSet` directly,\nthe table aliases can be used to extract the sql column values in a type safe manner.\n\n```kotlin\nclass SelectCustomer : Query\u003cCustomer\u003e {\n    val c = CUSTOMER()\n\n    init {\n        select(c.id, c.name, c.zip, c.city)\n        from(c)\n    }\n\n    override fun get() = Customer(c.id(), c.name(), c.zip(), c.city())\n}\n```\n\nNotice how we call `alias.columnName()` to extract the value for the current column for the current row.\n\nTo execute the query you instantiate the query class and call one of the execute actions `first()`, `firstOrNull()`, `list()`.\n\n```kotlin\nval allCustomers = SelectCustomer().list()\n```\n\nThe query code we wrote in the init block can be reused for multiple queries. Let's add a `byId()` function to our `SelectCustomer` query class:\n\n```kotlin\nfun byId(id: Int) = first {\n    where {\n        c.id `=` id\n    }\n}\n```\n\nWe use the table alias `c` to construct the SQL `WHERE c.id = :id` in a type safe manner. We can now get a specific customer:\n\n```kotlin\nval customer = SelectCustomer().byId(42)\n```\n\n### Insert and Update\n\nThese query classes normally takes one or more input parameters, and can extend `Insert`, `Update` or `Delete` instead of `Query`. There really isn't\nmuch of a difference, expect that the three first doesn't require a type parameter, like `Query` does.\n\nThe following `InsertCustomer` query takes a `customer` as a parameter, sets up a customer table alias and sets the name column to the\nname property of the input `Customer` object.\n\n```kotlin\nclass InsertCustomer(customer: Customer) : Insert() {\n    val c = CUSTOMER()\n\n    init {\n        insert(c) {\n            c.name `=` customer.name\n        }\n        generatedKeys {\n            customer.id = getInt(1)\n        }\n    }\n}\n```\n\nThe insert returns a generated key for the `id` column. This is the first and only generated key, and we assign it to the `id` property of the input `Customer` object inside the `generatedKeys` block.\nThis block is consulted after the insert is executed:\n\n```kotlin\nInsertCustomer(customer).execute()\n```\n\n## Joins\n\nLet's do a join! We'll introduce a `STATE` table and `State` domain object:\n\n```kotlin\nclass STATE : Table() {\n    val id = column\u003cUUID\u003e(\"id)\n    val code = column\u003cString\u003e(\"code\")\n    val name = column\u003cString\u003e(\"name)\n}\n\ndata class State(\n    var id: UUID,\n    var code: String,\n    var name: String\n)\n```\n\nWe modify our Customer to include a state:\n\n```kotlin\ndata class Customer(\n    var id: Int,\n    var name: String,\n    var zip: String,\n    var city: String,\n    var state: State\n)\n\nclass CUSTOMER : Table() {\n    val id = column\u003cInt\u003e(\"id\")\n    val name = column\u003cString\u003e(\"name\")\n    val zip = column\u003cString\u003e(\"zip\")\n    val city = column\u003cString\u003e(\"city\")\n    val state = column\u003cUUID\u003e(\"state\")\n}\n```\n\nLet's modify our `SelectCustomer` query so it joins `State` and populates the complete `Customer`\ntogether with the `State`. Notice that since we want all columns in both tables, we just\nmention the alias once instead of mentioning all the columns.\n\n```kotlin\nclass SelectCustomer : Query\u003cCustomer\u003e {\n    val c = CUSTOMER()\n    val s = STATE()\n\n    init {\n        select(c, s)\n        from(c)\n        join (s) on {\n            s.id `=` c.state\n        }\n    }\n\n    override fun get() {\n        val state = State(s.id(), s.code(), s.name())\n        return Customer(c.id(), c.name(), c.zip(), c.city(), state)\n    }\n}\n```\n\nIf you use `State` and/or `Customer` from other queries as well, consider\ncreating a secondary constructor that accepts the table object. That way the `get` mapper function would look like:\n\n```kotlin\noverride fun get() = Customer(c, State(s))\n```\n\nThis example showcases some of the corner stones of KDBC:\n\n*You are 100% in control of what is fetched from your database, and you\nconstruct your domain objects explicitly.*\n\n## Custom column definitions\n\nLet's revisit the first column we made, the `ID` property of our `CUSTOMER` table object:\n\n```kotlin\nclass CUSTOMER : Table() {\n    val id = column\u003cInt\u003e(\"id\")\n}\n```\n\n`getString()` operates on a `ResultSet` and `it` represents the column name.\nWhen you don't supply a `getter` function, KDBC tries to do the right thing by using the `getXXX` function\nof the `ResultSet` class, based on the type of the `column`. For example, a `column\u003cInt\u003e()` will\ndo `getInt(it)` and a `column\u003cString\u003e()` will do `getString(it)`. There are defaults for all known\nSQL data types, but you can easily call any function on the `ResultSet` object if you have a custom\nrequirement.\n\n## Dynamic queries\n\nSome times you want to pass multiple parameters to a search function and some of them might be nullable.\n\nConsider the following function that can search for customers with a certain name, and optionally of atleast a given age.\n\n```kotlin\nfun search(name: String, minAge: Int?) = list {\n    where {\n        upper(c.name) like upper(\"%$name%\")\n        if (minAge != null) {\n            and {\n                c.age gte minAge\n            }\n        }\n    }\n}\n\n```\n\u003e Yes, `name` is parameterized in the underlying prepared statement. SQL injection is not welcome here! :)\n\n## DDL\n\nThe `column()` delegate also takes an optional `ddl` parameter. This is a string that can be used to\ngenerate DDL, which can be automatically executed to create your database table.\n\nThe following example is taken from the test suite of KDBC:\n\n```kotlin\nclass CUSTOMER : Table() {\n    val id = column\u003cInt\u003e(\"id\", \"integer not null primary key auto_increment\")\n    val name = column\u003cString\u003e(\"name\", \"text\")\n}\n```\n\u003e Customer definition with DDL\n\nThe DDL is then used when the test suite fires up:\n\n```kotlin\nval dataSource = JdbcDataSource()\ndataSource.setURL(\"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1\")\nKDBC.setDataSource(dataSource)\nCUSTOMER().create()\n```\n\nA `DataSource` is generated and configured as the default data source via `KDBC.setDataSource()`. Then\nwe call `CUSTOMER().create()`, which generates the DDL and executes it to construct our table.\n\n## Connection handling\n\nFor simple use against a single database, calling `KDBC.setDataSource` might be fine. There are more advanced strategies for choosing the connection\non a per query basis as well.\n\n### Query.connection()\n\nEvery `Query` instance has a setter called `connection(connection)` which will configure the connection to be used for the query.\n\n```kotlin\nInsertCustomer(customer).connection(connection).execute()\n```\n\u003e Specify connection explicitly per query\n\n`Query` also takes an optional connection parameter in its constructor as an alternative:\n\n```kotlin\nInsertCustomer(connection, customer).execute()\n```\n\u003e Connection specified in query constructor\n\n### Connection factory\n\nThe connection factory is in charge of supplying a connection to any Query that hasn't been assigned a connection as execution time.\n`KDBC.setDataSource()` actually calls `KDBC.setConnectionFactory` under the covers. It's implementation is simple:\n\n```kotlin\nfun setDataSource(dataSource: DataSource) {\n    setConnectionFactory { dataSource.connection }\n}\n```\n\nThe function passed to `setConnectionFactory` receives the query as it's single argument and is required to return a connection. The\nfollowing implementation inspects an annotation set on each query and assigns a connection based on that:\n\n```kotlin\n@Target(AnnotationTarget.CLASS)\nannotation class Database(val pool: String)\n\nKDBC.setConnectionFactory { query -\u003e\n    val db = query.javaClass.getAnnotation(Database::class.java)\n    when (db.pool) {\n        \"pool1\" -\u003e pool1.connection\n        \"pool2\" -\u003e pool2.connection\n        else -\u003e throw IllegalArgumentException(\"Don't know how to create connection for pool ${db.pool}\")\n    }\n}\n```\n\nThis is merely a suggestion and you can use whatever strategy you like, for example by inspecting the package the Query is in or even the presence of an interface etc.\n\n## Transactions\n\nIf the current connection has `autoCommit = true`, each query will be committed upon completion. This is the default for a manually created\n`java.sql.Connection`. A connection pool may change this behavior. For example, a JavaEE application will control transactions according to\nthe JavaEE life cycle.\n\nKDBC has means to manually control the transaction context as well. To run multiple queries in the same transactions, wrap them in a `transaction` block:\n\n```kotlin\ntransaction {\n    CreateCustomer(customer).execute()\n    CreateOrder(order).execute()\n}\n```\n\nTo create a new connection that will participate in the same transaction, nest another `transaction` block inside the existing one.\n\nThe `transaction` block takes an optional `transactionType` parameter.\n\nThis is by default set to `TransactionType.REQUIRED` which indicates that the transaction can participate in an already active transaction or create it's own if no transaction is active.\n\nChanging to TransactionType.REQUIRES_NEW will temporarily suspend any active transactions and resume them after the code inside the `transaction` block completes.\n\nIf no connection is specified for the queries inside the block, the connection retrieved for the first query executed inside the transaction block will be used for all subsequent queries.\n\n## Batch statements\n\nIf your database supports it, you can do batch updates or inserts just by wrapping the code in `batch` and give it a list of objects to iterate over as it's single argument:\n\n```kotlin\nclass InsertCustomersInBatch(customers: List\u003cCustomer\u003e) : Insert() {\n    val c = CUSTOMER()\n\n    init {\n        batch(customers) { customer -\u003e\n            insert(c) {\n                c.name `=` customer.name\n            }\n            // If database supports generated keys, retrieve them here\n            generatedKeys {\n                customer.id = getInt(1)\n            }\n        }\n    }\n}\n```\n\n## Textual SQL\n\nIf you come across an unsupported native SQL command or for some other reason need to enter arbitrary SQL, you can use the `append` call or simply `+`:\n\n```kotlin\n+ \"custom sql here\"\n```\n\n## Adding native support for new syntax\n\nIt is pretty easy to extend the framework to support custom native SQL commands. There are two steps to it:\n\n1. Extend the `Expr` class\n2. Define a function to create the Expr class\n\nTake a look in [expression.kt](https://github.com/edvin/kdbc/blob/master/src/main/kotlin/kdbc/expression.kt) to see how the existing expressions are implemented.\n\nPull requests for native functions for all popular databases are welcome!\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedvin%2Fkdbc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedvin%2Fkdbc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedvin%2Fkdbc/lists"}