{"id":16560050,"url":"https://github.com/dzikoysk/sqiffy","last_synced_at":"2025-04-21T15:30:43.834Z","repository":{"id":83926738,"uuid":"589226903","full_name":"dzikoysk/sqiffy","owner":"dzikoysk","description":"Experimental compound SQL framework with type-safe DSL API generated at compile-time from annotation based schema diff","archived":false,"fork":false,"pushed_at":"2025-01-12T17:03:12.000Z","size":553,"stargazers_count":43,"open_issues_count":11,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-01T14:12:13.133Z","etag":null,"topics":["annotation-processor","compile-time","dsl","exposed","kotlin","ksp","migration","sql"],"latest_commit_sha":null,"homepage":"https://sqiffy.reposilite.com","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/dzikoysk.png","metadata":{"files":{"readme":".github/README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"dzikoysk"}},"created_at":"2023-01-15T13:57:03.000Z","updated_at":"2025-01-21T10:13:20.000Z","dependencies_parsed_at":"2023-05-15T10:00:18.272Z","dependency_job_id":"ec5aeffb-0916-4413-90fc-51ef694f25dd","html_url":"https://github.com/dzikoysk/sqiffy","commit_stats":{"total_commits":119,"total_committers":4,"mean_commits":29.75,"dds":"0.025210084033613467","last_synced_commit":"70fe6075dd0e24e3facaee4c588cf4ed0e0ebfbc"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzikoysk%2Fsqiffy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzikoysk%2Fsqiffy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzikoysk%2Fsqiffy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzikoysk%2Fsqiffy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dzikoysk","download_url":"https://codeload.github.com/dzikoysk/sqiffy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250080492,"owners_count":21371516,"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":["annotation-processor","compile-time","dsl","exposed","kotlin","ksp","migration","sql"],"created_at":"2024-10-11T20:27:54.826Z","updated_at":"2025-04-21T15:30:43.825Z","avatar_url":"https://github.com/dzikoysk.png","language":"Kotlin","funding_links":["https://github.com/sponsors/dzikoysk"],"categories":[],"sub_categories":[],"readme":"# Sqiffy [![CI](https://github.com/dzikoysk/sqiffy/actions/workflows/gradle.yml/badge.svg)](https://github.com/dzikoysk/sqiffy/actions/workflows/gradle.yml) ![Maven Central](https://img.shields.io/maven-central/v/com.dzikoysk.sqiffy/sqiffy)\n\n**sqiffy** _(or just squiffy 🍹)_ - Experimental compound **SQ**L framework with type-safe DSL API generated at compile-time from scheme d**iff**.\nIt is dedicated for applications, plugins \u0026 libraries responsible for internal database management.\n\nTable of contents:\n1. [What it does?](#what-it-does)\n2. [Supported](#supported)\n3. [How to use](#how-to-use)\n4. [Comparison with alternatives](#comparison-with-alternatives)\n\n### What it does?\n\n1. User defines versioned table definition using `@Defintion` annotation \n2. Sqiffy's annotation processor (KSP) at compile-time:\n   1. Converts table definitions into versioned changelog, similar to [Liquibase](https://github.com/liquibase/liquibase)\n   2. Generates up-to-date entity data classes for Kotlin with [KotlinPoet](https://github.com/square/kotlinpoet)\n   3. Creates bindings for [Exposed (\u003cins\u003eDSL\u003c/ins\u003e)](https://github.com/JetBrains/Exposed) framework\n   4. Validates schemes and bindings to eliminate typos and invalid operations\n3. When application starts, you can run set of prepared versioned migrations against current database state\n\n### Supported databases\n\n| Database                                                                                                       | Support          | Notes                                                                                                                                                                                                                                                                                                             |\n|----------------------------------------------------------------------------------------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| [PostgreSQL](https://www.postgresql.org/), [Embedded PostgreSQL](https://github.com/zonkyio/embedded-postgres) | Full support     | Main target of the library.                                                                                                                                                                                                                                                                                       |\n| [MariaDB](https://mariadb.org/), [MySQL](https://www.mysql.com/)                                               | Supported        | All operations should be supported, but some of the features might not be available.                                                                                                                                                                                                                              |\n| [SQLite](https://www.sqlite.org/index.html)                                                                    | Work in progress | SQLite does not provide several crucial schema update queries \u0026 type system is flexible. Because of that, schema updates are based on top of the modifications applied to `sqlite_master`, but the stability of this solution is unknown. See [#2](https://github.com/dzikoysk/sqiffy/issues/2) for more details. |\n| [H2 (MySQL mode)](http://www.h2database.com/html/features.html#compatibility)                                  | Unstable         | Such as SQLite, H2 implements SQL standard on their own \u0026 some of the compatibility features are just a fake mocks. In most cases, it's just better to use other databases (or their embedded variants).                                                                                                          |\n\n### How to use\n\nGradle _(kts)_:\n\n```kotlin\nplugins {\n    id(\"com.google.devtools.ksp\") version \"1.9.22-1.0.17\" // for Kotlin 1.9.22\n}\n\ndependencies {\n    val sqiffy = \"1.0.0-alpha.68\"\n    ksp(\"com.dzikoysk.sqiffy:sqiffy-symbol-processor:$sqiffy\") // annotation processor\n    implementation(\"com.dzikoysk.sqiffy:sqiffy:$sqiffy\") // core library \u0026 implementation\n}\n```\n\nDescribe your table using versioned definitions:\n\n```kotlin\nobject UserAndGuildScenarioVersions {\n    const val V_1_0_0 = \"1.0.0\"\n    const val V_1_0_1 = \"1.0.1\"\n    const val V_1_0_2 = \"1.0.2\"\n}\n\n@EnumDefinition(name = \"role\", mappedTo = \"Role\", [\n    EnumVersion(\n        version = V_1_0_0,\n        operation = ADD_VALUES,\n        values = [\"ADMIN\", \"USER\"]\n    ),\n    EnumVersion(\n        version = V_1_0_1,\n        operation = ADD_VALUES,\n        values = [\"MODERATOR\", \"SPECTATOR\"]\n    )\n])\nobject RoleDefinition\n\n@Definition([\n    DefinitionVersion(\n        version = V_1_0_0,\n        name = \"users_table\",\n        properties = [\n            Property(name = \"id\", type = SERIAL),\n            Property(name = \"uuid\", type = UUID_TYPE),\n            Property(name = \"name\", type = VARCHAR, details = \"12\"),\n            Property(name = \"role\", type = ENUM, enumDefinition = RoleDefinition::class)\n        ],\n        constraints = [\n            Constraint(type = PRIMARY_KEY, name = \"pk_id\", on = [\"id\"]),\n        ],\n        indices = [\n            Index(type = INDEX, name = \"idx_id\", columns = [\"id\"]),\n            Index(type = UNIQUE_INDEX, name = \"uq_name\", columns = [\"name\"])\n        ]\n    ),\n    DefinitionVersion(\n        version = V_1_0_1,\n        properties = [\n            Property(operation = RETYPE, name = \"name\", type = VARCHAR, details = \"24\"),\n            Property(operation = ADD, name = \"display_name\", type = VARCHAR, details = \"48\", nullable = true),\n        ],\n        indices = [\n            Index(operation = REMOVE_INDEX, type = INDEX, name = \"idx_id\"),\n            Index(type = INDEX, name = \"idx_id\", columns = [\"id\"])\n        ]\n    ),\n    DefinitionVersion(\n        version = V_1_0_2,\n        properties = [\n            Property(operation = RENAME, name = \"display_name\", rename = \"displayName\")\n        ]\n    )\n])\nobject UserDefinition\n\n@Definition([\n    DefinitionVersion(\n        version = V_1_0_0,\n        name = \"guilds_table\",\n        properties = [\n            Property(name = \"id\", type = SERIAL),\n            Property(name = \"name\", type = VARCHAR, details = \"24\"),\n            Property(name = \"owner\", type = INT)\n        ],\n        constraints = [\n            Constraint(type = FOREIGN_KEY, on = [\"id\"], name = \"fk_id\", referenced = UserDefinition::class, references = \"id\")\n        ]\n    ),\n    DefinitionVersion(\n        version = V_1_0_1,\n        constraints = [\n            Constraint(REMOVE_CONSTRAINT, type = FOREIGN_KEY, name = \"fk_id\")\n        ]\n    ),\n    DefinitionVersion(\n        version = V_1_0_2,\n        constraints = [\n            Constraint(type = FOREIGN_KEY, on = [\"id\"], name = \"fk_id\", referenced = UserDefinition::class, references = \"id\")\n        ]\n    )\n])\nobject GuildDefinition\n```\n\nBuild your project, so KSP can generate classes on top of the specified changelog. \nIn this case it'll generate:\n\n* `User`, `Guild` data class\n* `UnidentifiedUser`, `UnidentifiedGuild` data class without autogenerated keys (like e.g. serial id)\n* `UserTableNames`, `GuildTableNames` object with table \u0026 column names\n* `Role` enum that is based on linked in scheme `@EnumDefinition`\n* `UserTable`, `GuildTable` implementation of `Table` object for built-in DSL\n* SQL migrations between each version\n\nThen, you can simply connect to the database \u0026 run migrations:\n\n```kotlin\nthis.database = Sqiffy.createDatabase(\n    dataSource = createDataSource(),\n    logger = Slf4JSqiffyLogger(LoggerFactory.getLogger(SqiffyDatabase::class.java))\n)\n\nval changeLog = database.generateChangeLog(UserDefinition::class, GuildDefinition::class)\ndatabase.runMigrations(changeLog = changeLog)\n\n// [..] use database\n\ndatabase.close()\n```\n\nYou can also execute queries using generated DSL:\n\n```kotlin\nval userToInsert = UnidentifiedUser(\n    name = \"Panda\",\n    displayName = \"Only Panda\",\n    uuid = UUID.randomUUID(),\n    role = Role.MODERATOR\n)\n\nval insertedUserWithDsl = database\n    .insert(UserTable) {\n        it[UserTable.uuid] = userToInsert.uuid\n        it[UserTable.name] = userToInsert.name\n        it[UserTable.displayName] = userToInsert.displayName\n        it[UserTable.role] = userToInsert.role\n    }\n    .map { userToInsert.withId(id = it[UserTable.id]) }\n    .first()\n\nval guildToInsert = UnidentifiedGuild(\n    name = \"MONKE\",\n    owner = insertedUserWithDsl.id\n)\n\nval insertedGuild = database\n    .insert(GuildTable) {\n        it[GuildTable.name] = guildToInsert.name\n        it[GuildTable.owner] = guildToInsert.owner\n    }\n    .map { guildToInsert.withId(id = it[GuildTable.id]) }\n    .first()\n\nprintln(\"Inserted user: $insertedUserWithDsl\")\n\nval userFromDatabaseUsingDsl = database.select(UserTable)\n    .where { UserTable.uuid eq insertedUserWithDsl.uuid }\n    .map {\n        User(\n            id = it[UserTable.id],\n            name = it[UserTable.name],\n            uuid = it[UserTable.uuid],\n            displayName = it[UserTable.displayName],\n            role = it[UserTable.role]\n        )\n    }\n    .firstOrNull()\n\nprintln(\"Loaded user: $userFromDatabaseUsingDsl\")\n\nval joinedData = database.select(UserTable)\n    .join(INNER, UserTable.id, GuildTable.owner)\n    .where { GuildTable.owner eq insertedGuild.owner }\n    .map { it[UserTable.name] to it[GuildTable.name] }\n    .first()\n\nprintln(joinedData)\n```\n\nOr you can use generated names to execute manually, using e.g. JDBI:\n\n```kotlin\nval userFromDatabaseUsingRawJdbi = database.getJdbi().withHandle\u003cUser, Exception\u003e { handle -\u003e\n    handle\n        .select(multiline(\"\"\"\n            SELECT *\n            FROM \"${UserTableNames.TABLE}\" \n            WHERE \"${UserTableNames.NAME}\" = :nameToMatch\n        \"\"\"))\n        .bind(\"nameToMatch\", \"Panda\")\n        .mapTo\u003cUser\u003e()\n        .firstOrNull()\n}\n\nprintln(\"Loaded user: $userFromDatabaseUsingRawJdbi\")\n```\n\n### Comparison with alternatives\n\nThe comparison shows differences between multiple approaches to database management,\nthere's no \"best\" approach, it's all about your preferences and needs. \nSqiffy combines some known mechanisms to address issues of other approaches within the ecosystem of bundled applications shared among multiple users.\n\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003cth\u003eApproach\u003c/th\u003e\n        \u003cth\u003eEasy to use\u003c/th\u003e\n        \u003cth\u003eControl over the schema\u003c/th\u003e\n        \u003cth\u003eOne source of truth\u003c/th\u003e\n        \u003cth\u003eMultiple dialects\u003c/th\u003e\n        \u003cth\u003eAuto-migrations\u003c/th\u003e\n        \u003cth\u003eType-safe\u003c/th\u003e\n        \u003cth\u003eCompile-time validation\u003c/th\u003e\n        \u003cth\u003eDSL\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cb\u003eRaw\u003c/b\u003e\n            \u003cp\u003e\n                You want to avoid complex libraries and use raw SQL.\n                Because of that, it increases amount of code you have to write, \n                and it's error-prone.\n            \u003c/p\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cb\u003eSQL wrapper\u003c/b\u003e\n            \u003cp\u003e\n                Libraries like JDBI simplifies interaction with raw SQL and entities, \n                but there's still much to do around it manually.\n            \u003c/p\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cb\u003eORM\u003c/b\u003e\n            \u003cp\u003e\n                ORM libraries promise to handle all the database stuff for you,\n                but you're party losing control over the implementation and it may turn against you.\n            \u003c/p\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cb\u003eDSL\u003c/b\u003e\n            \u003cp\u003e\n                Libraries like Exposed DSL provides very convenient type-safe API and basic scheme generators, \n                but you partly lose control over the schema and you may encounter several issues with their API that doesn't cover all your needs.\n            \u003c/p\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cb\u003eDSL/ORM + Liquibase/Flyway\u003c/b\u003e\n            \u003cp\u003e\n                Migrations are a must-have for any database management system,\n                but it's not easy to implement them in a type-safe way and implement them for multiple dialects \u0026 users.\n            \u003c/p\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n        \u003ctd\u003e✗\u003c/td\u003e\n        \u003ctd\u003eN/A\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cb\u003eJOOQ\u003c/b\u003e\n            \u003cp\u003e\n                JOOQ defines a new category,\n                and while it's pretty good escape from regular DSL and uncontrolled ORMs, \n                it targets enterprise products with an existing databases controlled by 3rd party sources, so\n                it's not that good for any kind of bundled application.\n            \u003c/p\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003eN/A\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e½\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\n            \u003cb\u003eSqiffy\u003c/b\u003e\n            \u003cp\u003e\n                Combines several features mentioned above as opt-in and handles bundled database schema changelog.\n            \u003c/p\u003e\n        \u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n        \u003ctd\u003e✓\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\n```\n✓ - Yes\n✗ - No\n½ - Partially or not exactly matching our target (bundled apps with swappable database \u0026 dialects)\nN/A - Not applicable or given library is not responsible for this feature\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdzikoysk%2Fsqiffy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdzikoysk%2Fsqiffy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdzikoysk%2Fsqiffy/lists"}