Skip to content

Getting started on Kotlin JS with SQLDelight

First apply the gradle plugin in your project.

plugins {
  id("app.cash.sqldelight") version "2.0.0-alpha05"
}

repositories {
  google()
  mavenCentral()
}

sqldelight {
  databases {
    create("Database") {
      packageName.set("com.example")
    }
  }
}
plugins {
  id "app.cash.sqldelight" version "2.0.0-alpha05"
}

repositories {
  google()
  mavenCentral()
}

sqldelight {
  databases {
    Database { // This will be the name of the generated database class.
      packageName = "com.example"
    }
  }
}

Put your SQL statements in a .sq file under src/main/sqldelight. Typically the first statement in the SQL file creates a table.

-- src/main/sqldelight/com/example/sqldelight/hockey/data/Player.sq

CREATE TABLE hockeyPlayer (
  player_number INTEGER PRIMARY KEY NOT NULL,
  full_name TEXT NOT NULL
);

CREATE INDEX hockeyPlayer_full_name ON hockeyPlayer(full_name);

INSERT INTO hockeyPlayer (player_number, full_name)
VALUES (15, 'Ryan Getzlaf');

From this SQLDelight will generate a Database Kotlin class with an associated Schema object that can be used to create your database and run your statements on it. Doing this also requires a driver, which SQLDelight provides implementations of:

kotlin {
  sourceSets.jsMain.dependencies {
    implementation("app.cash.sqldelight:sqljs-driver:2.0.0-alpha05")
    implementation(npm("sql.js", "1.6.2"))
    implementation(devNpm("copy-webpack-plugin", "9.1.0"))
  }
}
kotlin {
  sourceSets.jsMain.dependencies {
    implementation "app.cash.sqldelight:sqljs-driver:2.0.0-alpha05"
    implementation npm("sql.js", "1.6.2")  
    implementation devNpm("copy-webpack-plugin", "9.1.0")
  }
}

Unlike on other platforms, the SqlJs driver can not be instantiated directly. The driver must be loaded asynchronously by calling the initSqlDriver function which returns a Promise<SqlDriver>.

// As a Promise
val promise: Promise<SqlDriver> = initSqlDriver(Database.Schema)
promise.then { driver -> /* ... */ }

// In a coroutine
suspend fun createDriver() {
    val driver: SqlDriver = initSqlDriver(Database.Schema).await()
    /* ... */
}

If building for browsers, some additional webpack configuration is also required.

// project/webpack.config.d/fs.js
config.resolve = {
    fallback: {
        fs: false,
        path: false,
        crypto: false,
    }
};

// project/webpack.config.d/wasm.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
config.plugins.push(
    new CopyWebpackPlugin({
        patterns: [
            '../../node_modules/sql.js/dist/sql-wasm.wasm'
        ]
    })
);

For browser testing with Karma, some similar configuration is required.

// project/karma.config.d/wasm.js
const path = require("path");
const abs = path.resolve("../../node_modules/sql.js/dist/sql-wasm.wasm")

config.files.push({
    pattern: abs,
    served: true,
    watched: false,
    included: false,
    nocache: false,
});

config.proxies["/sql-wasm.wasm"] = `/absolute${abs}`

SQL statements inside a .sq file can be labeled to have a typesafe function generated for them available at runtime.

selectAll:
SELECT *
FROM hockeyPlayer;

insert:
INSERT INTO hockeyPlayer(player_number, full_name)
VALUES (?, ?);

insertFullPlayerObject:
INSERT INTO hockeyPlayer(player_number, full_name)
VALUES ?;

Files with labeled statements in them will have a queries file generated from them that matches the .sq file name - putting the above sql into Player.sq generates PlayerQueries.kt. To get a reference to PlayerQueries you need to wrap the driver we made above:

// In reality the database and driver above should be created a single time
// and passed around using your favourite dependency injection/service
// locator/singleton pattern.
val database = Database(driver)

val playerQueries: PlayerQueries = database.playerQueries

println(playerQueries.selectAll().executeAsList())
// Prints [HockeyPlayer(15, "Ryan Getzlaf")]

playerQueries.insert(player_number = 10, full_name = "Corey Perry")
println(playerQueries.selectAll().executeAsList())
// Prints [HockeyPlayer(15, "Ryan Getzlaf"), HockeyPlayer(10, "Corey Perry")]

val player = HockeyPlayer(10, "Ronald McDonald")
playerQueries.insertFullPlayerObject(player)

And that's it! Check out the other pages on the sidebar for other functionality.