Create Schema
Create Schema
Learn how to define your database schema using SQL files with SQLiteNow annotations.
Directory Structure
SQLiteNow expects your SQL files to be organized in a specific directory structure within your commonMain
source set:
src/commonMain/sql/SampleDatabase/
├── schema/ # CREATE TABLE, CREATE INDEX statements
├── queries/ # SELECT, INSERT, UPDATE, DELETE queries
├── init/ # Initial data (optional)
└── migration/ # Migration scripts (optional)
Basic Table Definition
Create your table definitions in the schema/
directory. By default, SQLiteNow converts column names to camelCase
properties. You can customize property names using annotations:
File: schema/Person.sql
CREATE TABLE Person
(
id INTEGER PRIMARY KEY NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
-- @@field=phone @@propertyName=userPhone
phone TEXT,
created_at TEXT NOT NULL DEFAULT current_timestamp
);
CREATE INDEX idx_person_email ON Person (email);
This generates a Person
data class with:
id: Long
firstName: String
lastName: String
email: String
userPhone: String?
(custom name via annotation)createdAt: String
Note: In current version when working with field-level annotations (such as @@propertyName
etc),
you must use @@field
annotation to target specific column. It is a good idea to keep field-level annotations
as close to the column definition as possible, but it is not required to do so.
Custom Types with Adapters
For complex types that don’t map directly to SQLite types, use the @@adapter
annotation:
CREATE TABLE Person
(
id INTEGER PRIMARY KEY NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
-- @@field=birth_date @@adapter
-- @@propertyType=kotlinx.datetime.LocalDate
birth_date TEXT,
-- @@field=created_at @@adapter
-- @@propertyType=kotlinx.datetime.LocalDateTime
created_at TEXT NOT NULL DEFAULT current_timestamp,
);
This generates a Person
data class with custom types:
birthDate: kotlinx.datetime.LocalDate?
createdAt: kotlinx.datetime.LocalDateTime
Forced nullability Control
CREATE TABLE Person
(
id INTEGER PRIMARY KEY NOT NULL,
-- Column is NOT NULL but property will be nullable
-- @@field=phone @@nullable
phone TEXT NOT NULL,
-- Column allows NULL but property will be non-null
-- @@field=bio @@nonNull
bio TEXT
);
Available Annotations
Annotation | Purpose | Example |
---|---|---|
@@name=Name |
Custom class name | @@name=PersonEntity |
@@field=column_name |
Target a specific column | @@field=user_name |
@@propertyName=name |
Custom property name | @@propertyName=myUserName |
@@propertyType=Type |
Custom property type | @@propertyType=kotlinx.datetime.LocalDate |
@@adapter |
Request type adapter generation | @@adapter |
@@nullable |
Make property nullable | @@nullable |
@@nonNull |
Make property non-null | @@nonNull |
Example
Here’s a more complete example:
CREATE TABLE Person
(
id INTEGER PRIMARY KEY NOT NULL,
-- @@field=first_name @@propertyName=myFirstName
first_name TEXT NOT NULL,
-- @@field=last_name @@propertyName=myLastName
last_name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
phone TEXT,
-- @@field=birth_date @@adapter
-- @@propertyType=kotlinx.datetime.LocalDate
birth_date TEXT,
-- @@field=created_at @@adapter
-- @@propertyType=kotlinx.datetime.LocalDateTime
created_at TEXT NOT NULL DEFAULT current_timestamp
);
-- Indexes
CREATE INDEX idx_person_name ON Person (last_name, first_name);
CREATE INDEX idx_person_email ON Person (email);
This generates a Person
data class with:
id: Long
myFirstName: String
(custom property name)myLastName: String
(custom property name)email: String
phone: String?
birthDate: kotlinx.datetime.LocalDate?
(custom type with adapter)createdAt: kotlinx.datetime.LocalDateTime
(custom type with adapter)
Type Adapters
When you use @@adapter
, SQLiteNow generates adapter data classes that you must provide when creating the database.
Based on the example above, this generates:
class SampleDatabase(
dbName: String,
migration: DatabaseMigrations,
private val personAdapters: PersonAdapters,
// ... other adapters if needed
) : SqliteNowDatabase(dbName = dbName, migration = migration) {
data class PersonAdapters(
val sqlColumnToBirthDate: (String?) -> LocalDate?,
val birthDateToSqlColumn: (LocalDate?) -> String?,
val sqlColumnToCreatedAt: (String) -> LocalDateTime,
)
}
You provide the adapter implementations when creating the database:
val db = SampleDatabase(
dbName = "sample.db",
migration = VersionBasedDatabaseMigrations(),
personAdapters = SampleDatabase.PersonAdapters(
sqlColumnToBirthDate = { it?.let { LocalDate.parse(it) } },
birthDateToSqlColumn = { it?.toString() },
sqlColumnToCreatedAt = { LocalDateTime.parse(it) }
)
)
Code generator scans through all CREATE TABLE/VIEW definitions and all SELECT queries within
a specific namespace (e.g. Person) and generate adapter parameters that developer must to provide
for all columns that have @@adapter
annotation. Some adapter parameters may be merged if they have
the identical function signature (to make adapters list less verbose).
Next Steps
Query Data - Once you’ve defined your schema, learn how to interact with your tables.