{"id":15138536,"url":"https://github.com/darkredz/zeko-sql-builder","last_synced_at":"2025-04-05T20:05:46.411Z","repository":{"id":42035065,"uuid":"253306508","full_name":"darkredz/Zeko-SQL-Builder","owner":"darkredz","description":"Zeko SQL Builder is a high-performance lightweight SQL query library written for Kotlin language","archived":false,"fork":false,"pushed_at":"2025-02-14T15:38:50.000Z","size":558,"stargazers_count":108,"open_issues_count":4,"forks_count":7,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-05T20:05:31.046Z","etag":null,"topics":["hikaricp","jasync-sql","jdbc","kotlin","query-builder","querydsl","rdbms","sql","vertx","zeko"],"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/darkredz.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":"2020-04-05T18:41:16.000Z","updated_at":"2025-03-16T21:21:05.000Z","dependencies_parsed_at":"2024-05-02T07:50:27.147Z","dependency_job_id":"2bc5168a-6330-4dae-8023-33e2643f5e98","html_url":"https://github.com/darkredz/Zeko-SQL-Builder","commit_stats":{"total_commits":175,"total_committers":1,"mean_commits":175.0,"dds":0.0,"last_synced_commit":"a70f6d207d77048f690d0d728e22091fe152dda8"},"previous_names":[],"tags_count":40,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkredz%2FZeko-SQL-Builder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkredz%2FZeko-SQL-Builder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkredz%2FZeko-SQL-Builder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkredz%2FZeko-SQL-Builder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/darkredz","download_url":"https://codeload.github.com/darkredz/Zeko-SQL-Builder/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247393568,"owners_count":20931812,"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":["hikaricp","jasync-sql","jdbc","kotlin","query-builder","querydsl","rdbms","sql","vertx","zeko"],"created_at":"2024-09-26T07:40:49.444Z","updated_at":"2025-04-05T20:05:46.388Z","avatar_url":"https://github.com/darkredz.png","language":"Kotlin","readme":"# Zeko SQL Builder\n![alt Zeko SQL Builder](./logo.svg \"Zeko lightweight SQL Builder\")\n\n\u003cp align=\"left\"\u003e\n    \u003ca href=\"https://search.maven.org/search?q=g:%22io.zeko%22\"\u003e\n        \u003cimg src=\"https://img.shields.io/maven-central/v/io.zeko/zeko-sql-builder.svg?label=Maven%20Central\" alt=\"Maven Central\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"LICENSE\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/license-Apache%202-blue.svg?maxAge=2592000\" alt=\"Apache License 2\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/KotlinBy/awesome-kotlin\"\u003e\n        \u003cimg src=\"https://kotlin.link/awesome-kotlin.svg\" alt=\"Awesome Kotlin Badge\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\nZeko SQL Builder is a high-performance lightweight SQL library written for Kotlin language. It is designed to be flexible, portable, and fun to use. This library provides handy SQL wrapping DSL and a RDB client which is an abstraction on top on JDBC (currently supports HikariCP and Vert.x JDBC driver client).\n\nThis library is open source and available under the Apache 2.0 license. Please leave a star if you've found this library helpful!\n\n## Features\n- No configuration files, no XML, no YAML, no annotations, lightweight, easy to use\n- Fast startup \u0026 performance\n- No dependencies ([Jasync SQL](https://github.com/jasync-sql/jasync-sql), [Hikari-CP](https://github.com/brettwooldridge/HikariCP) \u0026 [Vert.x JDBC module](https://vertx.io/docs/vertx-jdbc-client/kotlin/) are optional)\n- Flexible queries, gain control over the generated SQL as you wish\n- No implicit fetching. Just generating SQL through the DSL\n- No magic. No proxies, interceptors, reflections\n- No overheads, mainly string manipulation\n- Extensible, [add your own extensions](#custom-Extensions) to support more operators, database dialects, SQL functions\n \n## Getting Started\nThis library is very easy-to-use. After reading this short documentation, you will have learnt enough.\nThere's 3 kinds of flavours in writing queries with the library.\n\n## SQL Query Builder\nThe query builder dsl currently supports standard ANSI sql which had been tested on database dialects\n such as PostgreSQL, MySQL, MariaDB, Apache Ignite and SQLite. \n \n\n## Examples\nSimplest example, this only generates SQL without any database connection \u0026 execution.\n\n#### Simple Query \n```kotlin\nimport io.zeko.db.sql.Query\n\nQuery().fields(\"*\").from(\"user\").toSql()\nQuery().fields(\"id\", \"name\", \"age\").from(\"user\").toSql()\n```\n\nOutputs:\n```sql\nSELECT * FROM user\nSELECT id, name, age FROM user\n```\n\n#### Query DSL with where conditions and subquery\n```kotlin\nimport io.zeko.db.sql.Query\nimport io.zeko.db.sql.dsl.*\nimport io.zeko.db.sql.operators.*\n\nQuery().fields(\"id\", \"name\", \"age\")\n    .from(\"user\")\n    .where(\"id\" inList arrayOf(1, 12, 18, 25, 55))\n    .toSql()\n\nval names = listOf(\"Leng\", \"Bat's Man\")\nQuery().fields(\"id\", \"name\", \"age\")\n    .from(\"user\")\n    .where(\"name\" inList names)\n    .toSql()\n\nQuery().fields(\"id\", \"name\", \"age\")\n    .from(\"user\")\n    .where(\n        \"name\" eq \"Bat Man\" and\n        (\"id\" greater 1) and\n        sub((\"name\" like \"%bat\") or (\"name\" like \"man%\")) and\n        isNotNull(\"nickname\")\n    ).toSql()\n\n//Subquery\nQuery().fields(\"id\", \"name\", \"age\")\n    .from(\n        Query().fields(\"*\").from(\"user\").where(\"age\" less 50).limit(10, 0)\n    )\n    .where(\"name\" like \"Bat Man\")\n    .toSql()\n```\n\nOutputs:\n```sql\nSELECT id, name, age FROM user WHERE name IN (?,?)\nSELECT id, name, age FROM user WHERE id IN (1,12,18,25,55)\nSELECT id, name, age FROM user WHERE name = ? AND id \u003e 1 AND ( name LIKE ? OR name LIKE ? ) AND nickname IS NOT NULL\nSELECT id, name, age FROM (SELECT * FROM user WHERE age \u003c 50 LIMIT 10 OFFSET 0) WHERE name LIKE ?\n```\n\n\n#### Table Joins and aggregation functions\n```kotlin\nQuery().fields(\"*\")\n   .from(\"user\")\n   .leftJoin(\"address\").on(\"user_id = user.id\")\n   .toSql()\n\n// With column alias, group by and having\nQuery()\n    .table(\"user\").fields(\"id\", \"name\")\n    .table(\"role\").fields(\"id\", \"role_name\", \"user.id = user_id\")\n    .table(\"address\").fields(\"id\", \"street1\", \"street2\", \"user.id = user_id\")\n    .from(\"user\")\n    .leftJoin(\"address\").on(\"user_id = user.id\")\n    .leftJoin(\"user_has_role\").on(\"user_id = user.id\")\n    .leftJoin(\"role\").on(\"id = user_has_role.role_id\")\n    .where(\n        \"user.status\" greater 0 or\n        (\"user.id\" notInList arrayOf(1, 2, 3))\n    )\n    .groupBy(\"role.id\", \"role.name\")\n    .having(\n        sumGt(\"role.id\", 2),\n        count(\"role.id\") less 10\n    )\n    .order(\"user.id\")\n    .limit(10, 20)\n    .toSql()\n\n// Join with subquery\nQuery().fields(\"*\")\n    .from(\"user\")\n    .innerJoin(\n        Query().fields(\"id\", \"user_id\", \"(total_savings - total_spendings) as balance\").from(\"report\"),\n        \"user_wallet\"\n    )\n    .on(\"user_wallet.user_id = user.id\")\n    .toSql()\n```\n\nOutputs\n```sql\nSELECT * FROM user LEFT JOIN address ON (address.user_id = user.id )\nSELECT user.id as `user-id`, user.name as `user-name`, role.id as `role-id`, role.role_name as `role-role_name`, user.id as `role-user_id`, address.id as `address-id`, address.street1 as `address-street1`, address.street2 as `address-street2`, user.id as `address-user_id` FROM user LEFT JOIN address ON (address.user_id = user.id ) LEFT JOIN user_has_role ON (user_has_role.user_id = user.id ) LEFT JOIN role ON (role.id = user_has_role.role_id ) WHERE user.status \u003e 0 OR user.id NOT IN (1,2,3) GROUP BY role.id, role.name HAVING SUM( role.id ) \u003e 2 AND COUNT( role.id ) \u003c 10 ORDER BY user.id ASC LIMIT 10 OFFSET 20\nSELECT * FROM user INNER JOIN ( SELECT id, user_id, (total_savings - total_spendings) as balance FROM report ) as user_wallet ON ( user_wallet.user_id = user.id )\n```\n\n#### MySQL Fulltext search\n```kotlin\nQuery().fields(\"*\").from(\"user\").where(\"name\" match \"smit\").toSql()\nQuery().fields(\"*\").from(\"user\")\n    .where(listOf(\"name\", \"nickname\") match \"smit\")\n    .toSql()\n```\n\n#### Unions\n```kotlin\nimport io.zeko.db.sql.extensions.common.*\n\nQuery().fields(\"id\", \"name\").from(\"user\")\n    .where(\"name\" eq \"Leng\")\n    .union(\n        Query().fields(\"id\", \"first_name\").from(\"customer\")\n    )\n    .order(\"first_name\")\n    .toSql()\n```\n\nOutputs\n```sql\nSELECT id, name FROM user WHERE name = ? UNION ( SELECT id, first_name FROM customer ) ORDER BY first_name ASC\n```\n\n## Different style of writing query\nYou can mixed all 3 together if needed.\n\n#### Standard DSL\n```kotlin\nQuery().fields(\"id\", \"name\", \"age\")\n    .from(\"user\")\n    .where(\n        \"name\" eq \"Bat Man\" and\n        (\"id\" greater 1) and\n        sub((\"name\" like \"%bat\") or (\"name\" like \"man%\")) and\n        (\"nickname\" isNotNull true)\n    ).toSql()\n```\n\n#### Static function call\n```kotlin\nQuery().fields(\"id\", \"name\", \"age\")\n    .from(\"user\")\n    .where(\n        eq(\"name\", \"Bat Man\"),\n        greater(\"id\", 1),\n        sub(like(\"name\", \"%bat\") or like(\"name\", \"man%\")),\n        isNotNull(\"nickname\")\n    ).toSql()\n```\n\n#### Raw strings\n```kotlin\nQuery().fields(\"id\", \"name\", \"age\")\n    .from(\"user\")\n    .where(\n        \"name = 'Bat Man'\",\n        \"id \u003e 1\",\n        \"(name LIKE '%bat' OR name LIKE 'man%'\",\n        \"nickname IS NOT NULL\"\n    ).toSql()\n```\n\n## Insert, Update, Merge and Delete statements\nZeko sql builder only supports DML at the moment. To build any insert, update and delete statement, you have to create an Entity class for your table.\n\n```kotlin\nimport io.zeko.model.Entity\n\nclass User : Entity {\n    constructor(map: Map\u003cString, Any?\u003e) : super(map)\n    constructor(vararg props: Pair\u003cString, Any?\u003e) : super(*props)\n    var id: Int?     by map\n    var age: Int?     by map\n    var name: String? by map\n    var roleId: Int? by map\n    var role: List\u003cRole\u003e? by map\n    var address: List\u003cAddress\u003e? by map\n}\n```\nGenerate SQL, for more [examples refer to the test cases](https://github.com/darkredz/Zeko-SQL-Builder/blob/master/src/test/kotlin/io/zeko/db/sql/UpdateStatementSpec.kt)\n```kotlin\nUpdate(User(\"id\" to 1, \"name\" to \"O'Connor\")).toSql()\n// UPDATE users SET id = 1, name = 'O''Connor'\n\nInsert(User().apply {\n    name = \"Leng\"\n    age = 21\n}).toSql()\n// INSERT INTO user ( name, age ) VALUES ( 'Leng', 21 )\n\nUpdate(User().apply {\n    name = \"Leng\"\n}).where(\n    \"id\" greater 100,\n    \"age\" eq 60\n).toSql()\n// UPDATE users SET name = 'Leng' WHERE id \u003e 100 AND age = 60\n\nInsert(User(), \"id\", \"name\").select(\n    Query().fields(\"user_id\", \"fullname\")\n            .from(\"customer\")\n            .where(\"status\" eq 0)\n).toSql()\n// INSERT INTO users ( id, name ) SELECT user_id, fullname FROM customer WHERE status = 0\n\n Delete(User()).where(\n    \"id\" greater 100,\n    \"age\" less 80,\n    \"name\" eq \"Leng\"\n).toSql()\n// DELETE FROM users WHERE id \u003e 100 AND age \u003c 80 AND name = ?\n\n\nMerge(User().apply {\n    id = 2\n    name = \"Leng\"\n}).toSql()\n// INSERT INTO user ( id, name ) VALUES ( 2, 'Leng' )\n```\n\n#### Parameterize Query for Insert/Update/Merge\nPass true as the second parameter to Insert/Update/Merge will generate SQL with *?*\n\nUse *Entity::toParams()* method to get the list of values to be used with your DB client for a prepared statement DML \n\n```kotlin\nval user = User().apply {\n               name = \"Leng\"\n               age = 21\n           }\nInsert(user, true).toSql()\n// INSERT INTO user ( name, age ) VALUES ( ?, ? )\n\nprintln(user.toParams())\n// 'Leng', 21\n```\n\nIf your entity has properties with custom classes such as Enum, override toParams() method to return the value type needed to store in your RDBMS:\n```kotlin\nenum class RoleType(val type: Int) {\n    ADMIN(1),\n    NORMAL_USER(2),\n    MODERATOR(3)\n}\n\nclass User : Entity {\n    // ... code as User class above ...\n    var roleType: RoleType?     by map\n    // Override toParams to convert role type Enum into int \n    // to be stored in the Table role_type column (TinyInt/Int)\n    override fun toParams(valueHandler: ((String, Any?) -\u003e Any?)?): List\u003cAny?\u003e {\n        return super.toParams { key, value -\u003e\n            when (key) {\n                \"roleType\" -\u003e (value as RoleType).type\n                else -\u003e value\n            }\n        }\n    }\n}\n\nval user = User().apply {\n               name = \"Leng\"\n               roleType = RoleType.ADMIN\n           }\nInsert(user, true).toSql()\n// INSERT INTO user ( name, role_type ) VALUES ( ?, ? )\n\nprintln(user.toParams())\n// 'Leng', 1\n```\n\n## Where Expression\nQuery expression (where) allowed conditions are:\n```\neq - (=)\nneq - (!=)\nisNull()\nisNotNull()\nless - (\u003c)\nlessEq - (\u003c=)\ngreater - (\u003e)\ngreaterEq - (\u003e=)\nlike - (=~)\nnotLike - (!~)\nregexp\nnotRegexp\ninList\nnotInList\nbetween\nmatch (MySQL MATCH AGAINST) \nsub - (This is use to group nested conditions)\n```\n\n## Aggregation functions\nQuery aggregation allowed functions are:\n```\nsum()\ncount()\navg()\nmin()\nmax()\n\n// to compare equal to value\nsumEq()\ncountEq()\navgEq()\nminEq()\nmaxEq()\n\n// to compare greater than value\nsumGt()\ncountGt()\navgGt()\nminGt()\nmaxGt()\n\n// to compare less than value\nsumLt()\ncountLt()\navgLt()\nminLt()\nmaxLt()\n```\nOr use agg(funcName, field, operator, value) to add in your desired aggregation function\n\n#### Add your own aggregation functions\n```kotlin\n// Postgres square of the correlation coefficient\nfun regr_r2(field: String, value: Double): QueryBlock {\n    return agg(\"regr_r2\", field, \"=\", value)\n}\n\nfun regr_r2_gt(field: String, value: Double): QueryBlock {\n    return agg(\"regr_r2\", field, \"\u003e\", value)\n}\n```\n\n## Custom Extensions\nYou can add your own custom queries extensions for you desired database dialects. \nIt is made simple and possible in Kotlin language. \n\nFor example, you can add in FOR UPDATE statements which is used in MySQL by simply defining a method extension in your package and import to use it.\n```kotlin\nfun Query.forUpdate(): Query {\n    // Adds query block after LIMIT section\n    this.addExpressionAfter(CustomPart.LIMIT, QueryBlock(\"FOR UPDATE\"))\n    return this\n}\n\nQuery().fields(\"*\").from(\"user\")\n    .where(\"name\" eq \"Leng\")\n    .limit(1)\n    .forUpdate()\n    .toSql()\n\n// Outputs: SELECT * FROM user WHERE name = ? LIMIT 1 OFFSET 0 FOR UPDATE\n```\n\nAvailable SQL section for customization are defined at [CustomPart enum class](https://github.com/darkredz/Zeko-SQL-Builder/blob/master/src/main/kotlin/io/zeko/db/sql/CustomPart.kt)\n```\n    SELECT, FIELD, FROM, JOIN, WHERE, GROUP_BY, HAVING, ORDER, LIMIT\n```\n\nFor more examples such as [UNION](https://github.com/darkredz/Zeko-SQL-Builder/blob/master/src/main/kotlin/io/zeko/db/sql/extensions/common/declarations.kt#L18) refer to the [predefined extensions source code](https://github.com/darkredz/Zeko-SQL-Builder/blob/master/src/main/kotlin/io/zeko/db/sql/extensions/common/declarations.kt)\nand the test cases for [custom extensions](https://github.com/darkredz/Zeko-SQL-Builder/blob/master/src/test/kotlin/io/zeko/db/sql/QueryCustomExpressionSpec.kt)\n\n## More Examples\nLook at the test cases for more [SQL code samples](https://github.com/darkredz/Zeko-SQL-Builder/tree/dev/src/test/kotlin/io/zeko/db/sql)\n\n## Query Dialects\nThe Query class is used for MySQL dialect by default. \nTo use it with other RDBMS such as Postgres, MariaDB and SQLite, you can use [ANSIQuery](https://github.com/darkredz/Zeko-SQL-Builder/blob/master/src/main/kotlin/io/zeko/db/sql/ANSIQuery.kt) instead of Query class.\nOr extend it to set your intended dialect column escape character.\n\nExample: Apache Ignite query class - [IgniteQuery](https://github.com/darkredz/Zeko-SQL-Builder/blob/master/src/main/kotlin/io/zeko/db/sql/IgniteQuery.kt)\n\n## Database Connection\nZeko SQL Builder provides a standard way to connect to your DB to execute the queries. \nCurrently the DB connection pool is an abstraction on top of [HikariCP](https://github.com/brettwooldridge/HikariCP) and Vert.x JDBC client module.\n\n## Creating DB Connection Pool and Session\nFirst create a JasyncDBPool, HikariDBPool or VertxDBPool, you can refer to the [Vert.x JDBC client page](https://vertx.io/docs/vertx-jdbc-client/java/#_configuration) for the config. \nThese classes are using the same configuration properties but not necessarily dependant on the Vert.x module. \n\nJasyncDBPool will automatically parse the jdbc url to populate the pool configuration if a JsonObject is passed with url field so you don't have to. \nYou could still pass in a [ConnectionPoolConfigurationBuilder](https://github.com/jasync-sql/jasync-sql/wiki/Configuring-and-Managing-Connections) to its constructor for more control.\n\nThe pool object can be wrap into a class as a singleton. \nThe connection pool and session are composed with suspend methods where you should be running them inside a coroutine.\n\n```kotlin\nimport io.zeko.db.sql.connections.*\nimport io.vertx.kotlin.core.json.json\nimport io.vertx.kotlin.core.json.obj\n\nclass DB {\n    private var pool: HikariDBPool\n    private var pool2: JasyncDBPool\n\n    constructor() {\n        val mysqlConfig = json {\n            obj(\n                \"url\" to \"jdbc:mysql://localhost:3306/zeko_test?user=root\u0026password=12345\",\n                \"max_pool_size\" to 30\n            )\n        }\n\n        pool = HikariDBPool(config)\n        // This is require if you want your Insert statement to return the newly inserted keys\n        pool.setInsertStatementMode(Statement.RETURN_GENERATED_KEYS)\n        pool2 = JasyncDBPool(config)\n    }\n\n    suspend fun session(): DBSession = HikariDBSession(pool, pool.createConnection())\n    suspend fun session2(): DBSession = JasyncDBSession(pool2, pool2.createConnection())\n}\n```\n\nIn your model code, you can call the DB instance you just created and start execute queries\nThe once block will execute the query and then close the connection at the end of the block.\n```kotlin\n    suspend fun queryMe(statusFlag: Int) {\n        val sql = Query().fields(\"*\").from(\"user\").where(\"status\" eq statusFlag).toSql()\n\n        db.session().once { conn -\u003e\n            val result = conn.queryPrepared(sql, listOf(statusFlag)) as java.sql.ResultSet\n        }\n    }\n```\nIf you are using VertxDBPool you should cast the result to as io.vertx.ext.sql.ResultSet\nIf you are using JasyncDBPool you should cast the result to as [com.github.jasync.sql.db.QueryResult](https://github.com/jasync-sql/jasync-sql/wiki/Executing-Statements#query)\n\n#### Retries, \nThe DB session allows you to retry the same statements. \nCalling retry(2) instead of once will execute the code block additional 2 times if it failed\n```kotlin\n    suspend fun queryMe(statusFlag: Int) {\n        val sql = Query().fields(\"*\").from(\"user\").where(\"status\" eq statusFlag).toSql()\n\n        db.session().retry(2) { conn -\u003e\n            val result = conn.queryPrepared(sql, listOf(statusFlag)) as java.sql.ResultSet\n        }\n    }\n```\n\nThe retry can be delayed by passing a second parameter as miliseconds. This will delay 500ms before the retry will be executed\n```kotlin\ndb.session().retry(2, 500) { conn -\u003e\n    val result = conn.queryPrepared(sql, listOf(statusFlag)) as java.sql.ResultSet\n}\n```\n\n#### Transactions\nTransactions has the same parameters as retry(numRetries, delay), the only difference is the queries will be executed as a transaction block. \nAny exceptions will result in a rollback automatically. Connection is closed as well at the end of the block.\nIf you do not want the connection to be close, call transactionOpen instead.\n```kotlin\ndb.session().transaction { conn -\u003e\n    val result = conn.queryPrepared(sql, listOf(statusFlag)) as java.sql.ResultSet\n    val sqlInsert = \"\"\"INSERT INTO user (name, email) VALUES (?, ?)\"\"\"\n    val ids = conn.insert(sqlInsert, arrayListOf(\n                    \"User \" + System.currentTimeMillis(),\n                    \"abc@gmail.com\"\n            )) as List\u003cInt\u003e\n}\n```\n\nThe example insert returns a List of inserted IDs, this can only work if you have set beforehand:\n```kotlin\npool.setInsertStatementMode(Statement.RETURN_GENERATED_KEYS)\n```\nNote: Not all database works with this, for instance Apache Ignite will throw exception since it does not support this SQL feature.\nJasync driver on the other hand [only works with MySQL](https://github.com/jasync-sql/jasync-sql/wiki/FAQ#q-i-inserted-a-row-how-do-i-get-an-auto-incremented-id) (this high performance non-blocking driver is for MySQL and PostgreSQL only)\n\n#### Duplicate Entries\nIf you have inserted/updated a duplicated entry due to unique index or primary key, the DB client will throw DuplicateKeyException by default.\n```kotlin\nimport io.zeko.db.sql.exceptions.DuplicateKeyException\n\ntry {\n    conn.insert(sqlInsert, listOf(\"User Name\", \"abc@gmail.com\"))\n} catch (err: DuplicateKeyException) {\n    if (err.equals(\"email\")) {\n        println(\"email column: Email address is already registered\")\n    }\n}\n```\n\n#### More controls on connection\nTo execute the queries with more control you can get the underlying connection object by calling rawConnection:\n```kotlin\n    val sess = db.session()\n    val conn = sess.rawConnection() as java.sql.Connection\n    // For Vert.x jdbc client, it will be:\n    // sess.rawConnection() as io.vertx.ext.sql.SQLConnection\n    \n    // Now you can do whatever you want, though the transaction{} block actually does this automatically\n    conn.autoCommit = false\n    lateinit var rows: List\u003cUser\u003e\n\n    try {\n        val sql = \"SELECT * FROM user WHERE status = ? ORDER BY id ASC\"\n        rows = sess.queryPrepared(sql, listOf(1), { User(it) }) as List\u003cUser\u003e\n        conn.commit()\n    } catch (err: Exception) {\n        conn.rollback()\n    } finally {\n        conn.close()\n    }\n```\n\n## Result Set Mapping to POJO/Entity\nThe query method accepts a lambda where you can process the raw data map to the POJO/entity class you need.\nIn this case User class is just a class with map delegate to its properties\n```kotlin\nclass User : Entity {\n    constructor(map: Map\u003cString, Any?\u003e) : super(map)\n    constructor(vararg props: Pair\u003cString, Any?\u003e) : super(*props)\n    var id: Int?     by map\n    var name: String? by map\n}\n\nsuspend fun queryUsers(statusFlag: Int): List\u003cUser\u003e {\n    lateinit var rows: List\u003cUser\u003e\n    db.session().once { conn -\u003e\n        val sql = \"SELECT * FROM user WHERE status = ? ORDER BY id ASC\"\n        rows = sess.queryPrepared(sql, listOf(1), { User(it) }) as List\u003cUser\u003e\n    }\n}\nreturn rows\n```\n\n## Entity Property Type Mapping\nThere are times when working with different databases or different DB drivers, the java type conversion might be different from what you need.\nFor instance, with a MySQL DATETIME column, Jasync returns Joda LocalDateTime, Hikari gives java.sql.Timestamp, Vert.x JDBC client return as String(auto converted to UTC timezone)\nWhile with TINYINT, Hikari and Vert.x return Boolean, Jasync returns Byte.\n\nIn order to map the property to the type you need, override propTypeMapping with a map of property names to its relevant type.\n```kotlin\nimport io.zeko.model.Entity\nimport io.zeko.model.Type\n\nclass User : Entity {\n    constructor(map: Map\u003cString, Any?\u003e) : super(map)\n    constructor(vararg props: Pair\u003cString, Any?\u003e) : super(*props)\n\n    override fun propTypeMapping() = mapOf(\n        \"status\" to Type.INT,\n        \"subscribed\" to Type.BOOL,\n        \"lastAccessAt\" to Type.ZONEDATETIME_UTC, //gives ZonedDateTime in UTC zone\n        \"createdAt\" to Type.DATETIME,\n        \"dob\" to Type.DATE  //gives LocalDate\n    )\n\n    var id: Int?     by map\n    var firstName: String? by map\n    var lastName: String? by map\n    var subscribed: Boolean? by map\n    var status: Int? by map\n    var dob: LocalDate? by map\n    var lastAccessAt: ZonedDateTime? by map\n    var createdAt: LocalDateTime? by map\n}\n```\n\n## Logging\nYou can use any logging framework of your preference, just create a log provider class by implementing the DBLogger Interface.\nExample using Vert.x logger to log queries, retries blocks and sql errors.\n```kotlin\nimport io.zeko.db.sql.connections.*\nimport io.vertx.core.*\nimport io.vertx.core.logging.LoggerFactory\n\nclass BootstrapVerticle : AbstractVerticle() {\n    override fun start() {\n        val logger = VertxDBLog(LoggerFactory.getLogger(\"app\")).setParamsLogLevel(DBLogLevel.OFF)\n        //...some db setup code\n        val db = JasyncDBSession(pool, pool.createConnection()).setQueryLogger(logger)\n    }\n}\n\nclass VertxDBLog(val logger: Logger) : DBLogger {\n    var paramsLevel: DBLogLevel = DBLogLevel.DEBUG\n    var sqlLevel: DBLogLevel = DBLogLevel.DEBUG\n\n    override fun logQuery(sql: String, params: List\u003cAny?\u003e?) {\n        if (sqlLevel.level \u003e= DBLogLevel.DEBUG.level) {\n            logger.debug(\"[SQL] $sql $params\")\n        }\n        if (paramsLevel.level \u003e= DBLogLevel.DEBUG.level \u0026\u0026 params != null) {\n            logger.debug(\"[SQL_PARAM] $params\")\n        }\n    }\n    override fun logRetry(numRetriesLeft: Int, err: Exception) { logger.warn(\"[SQL_RETRY:$numRetriesLeft] $err\") }\n    override fun logUnsupportedSql(err: Exception) { logger.warn(\"[SQL_UNSUPPORTED] $err\") }\n    override fun logError(err: Exception) { logger.error(\"[SQL_ERROR] $err\") }\n    override fun getParamsLogLevel(): DBLogLevel { return paramsLevel }\n    override fun getSqlLogLevel(): DBLogLevel { return sqlLevel }\n    override fun setParamsLogLevel(level: DBLogLevel): DBLogger { return this }\n    override fun setSqlLogLevel(level: DBLogLevel): DBLogger { return this }\n    override fun setLogLevels(sqlLevel: DBLogLevel, paramsLevel: DBLogLevel): DBLogger { return this }\n}\n```\n\n## Data Mapper\nIf you are using [Zeko Data Mapper](https://github.com/darkredz/Zeko-Data-Mapper), you can write code as below to automatically map the objects to nested entities.\n\nExample entities with User, Address and Role\n```kotlin\nimport io.zeko.model.Entity\n\nclass User : Entity {\n    constructor(map: Map\u003cString, Any?\u003e) : super(map)\n    var id: Int?     by map\n    var age: Int?     by map\n    var name: String? by map\n    var role_id: String? by map\n    var role: List\u003cRole\u003e? by map\n    var address: List\u003cAddress\u003e? by map\n}\n\nclass Address : Entity {\n    constructor(map: MutableMap\u003cString, Any?\u003e) : super(map)\n    var id: Int? by map\n    var user_id: Int? by map\n    var street1: String? by map\n    var street2: String? by map\n}\n\nclass Role : Entity {\n    constructor(map: MutableMap\u003cString, Any?\u003e) : super(map)\n    var id: Int?     by map\n    var role_name: String? by map\n    var user_id: Int? by map\n}\n```\n\nExecute join queries where User has address and has many roles\n```kotlin\n    import io.zeko.db.sql.Query\n    import io.zeko.db.sql.dsl.*\n    import io.zeko.db.sql.aggregations.*\n    import io.zeko.db.sql.operators.*\n    import io.zeko.model.declarations.toMaps\n    import io.zeko.model.DataMapper\n    import io.zeko.model.TableInfo\n\n    suspend fun mysqlQuery(): List\u003cUser\u003e {\n        val query = Query()\n                .table(\"user\").fields(\"id\", \"name\")\n                .table(\"role\").fields(\"id\", \"role_name\", \"user.id = user_id\")\n                .table(\"address\").fields(\"id\", \"street1\", \"street2\", \"user.id = user_id\")\n                .from(\"user\")\n                .leftJoin(\"address\").on(\"user_id = user.id\")\n                .leftJoin(\"user_has_role\").on(\"user_id = user.id\")\n                .leftJoin(\"role\").on(\"id = user_has_role.role_id\")\n                .where(\n                    between(\"user.id\", 0, 1000)\n                )\n                .orderAsc(\"user.name\")\n\n        val (sql, columns) = query.compile(true)\n\n        lateinit var rows: List\u003cUser\u003e\n        db.session().once {\n            val result = it.query(sql, columns)\n            rows = DataMapper().mapStruct(structUserProfile(), result) as List\u003cUser\u003e\n        }\n        return rows\n    }\n```\n\n```kotlin\n    fun structUserProfile(): LinkedHashMap\u003cString, TableInfo\u003e {\n        val tables = linkedMapOf\u003cString, TableInfo\u003e()\n        tables[\"user\"] = TableInfo(key = \"id\", dataClassHandler = { User(it) })\n        tables[\"role\"] = TableInfo(key = \"id\", dataClassHandler = { Role(it) }, move_under = \"user\", foreign_key = \"user_id\", many_to_many = true, remove = listOf(\"user_id\"))\n        tables[\"address\"] = TableInfo(key = \"id\", dataClassHandler = { Address(it) }, move_under = \"user\", foreign_key = \"user_id\", many_to_one = true, remove = listOf(\"user_id\"))\n        return tables\n    }\n```\nExample output json encode\n```json\n[{\n        \"id\": 1,\n        \"name\": \"Bat Man\",\n        \"role\": [\n            {\n                \"role_id\": 2,\n                \"type\": \"super admin\"\n            }\n        ],\n        \"address\": [\n            {\n                \"id\": 1,\n                \"street1\": \"Jalan SS16/1\",\n                \"street2\": \"Taman Tun\"\n            }\n        ]\n}]\n```\n\n## Performance\nThis is a simple benchmark with a rate of 2500 QPS on the query builder with result mapped across all three DB drivers(30 max connections pool)\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"./zeko-sql-builder-benchmark.jpeg\" alt=\"Zeko Query Builder Benchmark\" /\u003e\n\u003c/p\u003e\nHardware: MacBook Pro (13-inch, 2018, Four Thunderbolt 3 Ports) 2.3 GHz Intel Core i5 8 GB 2133 MHz LPDDR3\n\n\n## Download\nAdd this to your maven pom.xml\n\n    \u003cdependency\u003e\n      \u003cgroupId\u003eio.zeko\u003c/groupId\u003e\n      \u003cartifactId\u003ezeko-sql-builder\u003c/artifactId\u003e\n      \u003cversion\u003e1.4.0\u003c/version\u003e\n    \u003c/dependency\u003e\n    \n    \u003c!-- Jasync Mysql driver if needed --\u003e\n    \u003cdependency\u003e\n       \u003cgroupId\u003ecom.github.jasync-sql\u003c/groupId\u003e\n       \u003cartifactId\u003ejasync-mysql\u003c/artifactId\u003e\n       \u003cversion\u003e1.2.3\u003c/version\u003e\n    \u003c/dependency\u003e\n    \u003c!-- Hikari Mysql connection pool if needed --\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003ecom.zaxxer\u003c/groupId\u003e\n        \u003cartifactId\u003eHikariCP\u003c/artifactId\u003e\n        \u003cversion\u003e5.0.1\u003c/version\u003e\n    \u003c/dependency\u003e\n    \u003c!-- Vertx jdbc client if needed --\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.vertx\u003c/groupId\u003e\n        \u003cartifactId\u003evertx-jdbc-client\u003c/artifactId\u003e\n        \u003cversion\u003e4.1.1\u003c/version\u003e\n    \u003c/dependency\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.jetbrains.kotlinx\u003c/groupId\u003e\n        \u003cartifactId\u003ekotlinx-coroutines-core\u003c/artifactId\u003e\n        \u003cversion\u003e1.3.3\u003c/version\u003e\n    \u003c/dependency\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarkredz%2Fzeko-sql-builder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdarkredz%2Fzeko-sql-builder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarkredz%2Fzeko-sql-builder/lists"}