diff --git a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt index cfbb026..2d33793 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/Application.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/Application.kt @@ -54,6 +54,13 @@ fun Application.module() { } routing { + if (Constants.devMode) { + get("/fixup") { + call.respondText("received") + val fixer by inject() + fixer.addParentsToStops() + } + } get("/update") { val key = call.parameters["key"] if (key != Constants.updateKey) { @@ -66,8 +73,11 @@ fun Application.module() { ?: "https://opendata.transport.vic.gov.au/dataset/3f4e292e-7f8a-4ffe-831f-1953be0fe448/resource/${datasetUuid}/download/gtfs.zip" call.respondText("received") launch(context = Dispatchers.IO) { + val fixer by inject() val importer by inject() importer.import(datasetUrl) + + fixer.addParentsToStops() } } @@ -123,7 +133,7 @@ fun Application.module() { } get("/route_stops/{route_id}") { val routeId = call.parameters["route_id"]!! - val useParent = call.queryParameters["parent"] in listOf("true", "1") + val useParent = call.queryParameters["parent"] !in listOf("false", "0") val stops = withContext(Dispatchers.IO) { val routeDao by inject() if (useParent) diff --git a/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt new file mode 100644 index 0000000..d3d307e --- /dev/null +++ b/server/src/main/kotlin/moe/lava/banksia/server/GtfsDataFixer.kt @@ -0,0 +1,43 @@ +package moe.lava.banksia.server + +import moe.lava.banksia.room.Database +import moe.lava.banksia.room.entity.StopEntity +import moe.lava.banksia.util.log +import java.security.MessageDigest + +class GtfsDataFixer( + private val database: Database, +) { + suspend fun addParentsToStops() { + val dao = database.stopDao + val stops = dao.getAllParentless() + stops + .groupBy { it.name.split("/")[0] } + .filter { (_, stops) -> stops.size > 1 } + .forEach { (name, stops) -> + val avgLat = stops.map { it.lat }.average() + val avgLng = stops.map { it.lng }.average() + val hash = name.sha256().substring(0, 7) + val parentId = "bsia:df1:$hash" + val parent = StopEntity( + id = parentId, + name = name, + lat = avgLat, + lng = avgLng, + parent = "", + hasWheelChairBoarding = stops.all { it.hasWheelChairBoarding }, + level = "", + platformCode = "", + ) + log("datafixer", "inserting ${parentId} for ${stops.size} children") + dao.insertAll(parent) + database.stopDao.updateParents(stops.map { it.id }, parentId) + } + } +} + +private fun String.sha256() = + MessageDigest + .getInstance("SHA-256") + .digest(this.toByteArray()) + .joinToString("") { "%02x".format(it) } diff --git a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt index d51379e..6ee4365 100644 --- a/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt +++ b/server/src/main/kotlin/moe/lava/banksia/server/di/ServerModules.kt @@ -1,6 +1,7 @@ package moe.lava.banksia.server.di import io.ktor.client.HttpClient +import moe.lava.banksia.server.GtfsDataFixer import moe.lava.banksia.server.GtfsImporter import moe.lava.banksia.server.gtfs.GtfsParser import moe.lava.banksia.server.gtfsrt.GtfsrtService @@ -12,5 +13,6 @@ val ServerModules = module { singleOf(::GtfsParser) singleOf(::GtfsrtService) + singleOf(::GtfsDataFixer) singleOf(::GtfsImporter) } diff --git a/shared/schemas/moe.lava.banksia.room.Database/11.json b/shared/schemas/moe.lava.banksia.room.Database/11.json new file mode 100644 index 0000000..6fc2976 --- /dev/null +++ b/shared/schemas/moe.lava.banksia.room.Database/11.json @@ -0,0 +1,498 @@ +{ + "formatVersion": 1, + "database": { + "version": 11, + "identityHash": "c4be3d0c2a25f8c5c33132646a070d0e", + "entities": [ + { + "tableName": "Route", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `type` INTEGER NOT NULL, `number` TEXT, `name` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT" + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Service", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `days` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "days", + "columnName": "days", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Service_days", + "unique": false, + "columnNames": [ + "days" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Service_days` ON `${TABLE_NAME}` (`days`)" + } + ] + }, + { + "tableName": "ServiceException", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`serviceId` TEXT NOT NULL, `date` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`serviceId`, `date`))", + "fields": [ + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "serviceId", + "date" + ] + }, + "indices": [ + { + "name": "index_ServiceException_serviceId", + "unique": false, + "columnNames": [ + "serviceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_serviceId` ON `${TABLE_NAME}` (`serviceId`)" + }, + { + "name": "index_ServiceException_type", + "unique": false, + "columnNames": [ + "type" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ServiceException_type` ON `${TABLE_NAME}` (`type`)" + } + ] + }, + { + "tableName": "Shape", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `path` BLOB NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "path", + "columnName": "path", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "Stop", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lat", + "columnName": "lat", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lng", + "columnName": "lng", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "TEXT" + }, + { + "fieldPath": "hasWheelChairBoarding", + "columnName": "hasWheelChairBoarding", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "platformCode", + "columnName": "platformCode", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Stop_parent", + "unique": false, + "columnNames": [ + "parent" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `${TABLE_NAME}` (`parent`)" + } + ], + "foreignKeys": [ + { + "table": "Stop", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "parent" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "StopTime", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tripId` TEXT NOT NULL, `stopId` TEXT NOT NULL, `arrivalTime` INTEGER NOT NULL, `departureTime` INTEGER NOT NULL, `headsign` TEXT, `pickupType` INTEGER NOT NULL, `dropOffType` INTEGER NOT NULL, PRIMARY KEY(`tripId`, `stopId`), FOREIGN KEY(`tripId`) REFERENCES `Trip`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`stopId`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tripId", + "columnName": "tripId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "stopId", + "columnName": "stopId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "arrivalTime", + "columnName": "arrivalTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "departureTime", + "columnName": "departureTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "headsign", + "columnName": "headsign", + "affinity": "TEXT" + }, + { + "fieldPath": "pickupType", + "columnName": "pickupType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dropOffType", + "columnName": "dropOffType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "tripId", + "stopId" + ] + }, + "indices": [ + { + "name": "index_StopTime_tripId", + "unique": false, + "columnNames": [ + "tripId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_tripId` ON `${TABLE_NAME}` (`tripId`)" + }, + { + "name": "index_StopTime_stopId", + "unique": false, + "columnNames": [ + "stopId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_StopTime_stopId` ON `${TABLE_NAME}` (`stopId`)" + } + ], + "foreignKeys": [ + { + "table": "Trip", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "tripId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Stop", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "stopId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Trip", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `routeId` TEXT NOT NULL, `serviceId` TEXT NOT NULL, `shapeId` TEXT, `tripHeadsign` TEXT NOT NULL, `directionId` TEXT NOT NULL, `blockId` TEXT NOT NULL, `wheelchairAccessible` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`routeId`) REFERENCES `Route`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`serviceId`) REFERENCES `Service`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`shapeId`) REFERENCES `Shape`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "routeId", + "columnName": "routeId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceId", + "columnName": "serviceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shapeId", + "columnName": "shapeId", + "affinity": "TEXT" + }, + { + "fieldPath": "tripHeadsign", + "columnName": "tripHeadsign", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "blockId", + "columnName": "blockId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wheelchairAccessible", + "columnName": "wheelchairAccessible", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Trip_shapeId", + "unique": false, + "columnNames": [ + "shapeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_shapeId` ON `${TABLE_NAME}` (`shapeId`)" + }, + { + "name": "index_Trip_serviceId", + "unique": false, + "columnNames": [ + "serviceId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `${TABLE_NAME}` (`serviceId`)" + }, + { + "name": "index_Trip_routeId", + "unique": false, + "columnNames": [ + "routeId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Trip_routeId` ON `${TABLE_NAME}` (`routeId`)" + } + ], + "foreignKeys": [ + { + "table": "Route", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "routeId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Service", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "serviceId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Shape", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "shapeId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "VersionMetadata", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` TEXT NOT NULL, `lastUpdated` INTEGER NOT NULL, PRIMARY KEY(`type`))", + "fields": [ + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "type" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c4be3d0c2a25f8c5c33132646a070d0e')" + ] + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt index df10a58..e1060bb 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/model/Stop.kt @@ -8,7 +8,7 @@ data class Stop( val id: String, val name: String, val pos: Point, - val parent: String, + val parent: String?, val hasWheelChairBoarding: Boolean, val level: String, val platformCode: String, diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt index 31a5579..7c39ebf 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/Database.kt @@ -3,7 +3,11 @@ package moe.lava.banksia.room import androidx.room.AutoMigration import androidx.room.RoomDatabase import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.room.util.foreignKeyCheck +import androidx.sqlite.SQLiteConnection import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import androidx.sqlite.execSQL import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import moe.lava.banksia.room.converter.RouteTypeConverter @@ -26,7 +30,7 @@ import moe.lava.banksia.room.entity.VersionMetadataEntity import androidx.room.Database as DatabaseAnnotation @DatabaseAnnotation( - version = 10, + version = 11, entities = [ RouteEntity::class, ServiceEntity::class, @@ -59,7 +63,21 @@ abstract class Database : RoomDatabase() { base.fallbackToDestructiveMigration(true) .setDriver(BundledSQLiteDriver()) .setQueryCoroutineContext(Dispatchers.IO) + .addMigrations(MIGRATION_10_11) // .fallbackToDestructiveMigration(true) .build() } } + +val MIGRATION_10_11 = object : Migration(10, 11) { + override fun migrate(connection: SQLiteConnection) { + connection.execSQL("CREATE TABLE IF NOT EXISTS `_new_Stop` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `lat` REAL NOT NULL, `lng` REAL NOT NULL, `parent` TEXT, `hasWheelChairBoarding` INTEGER NOT NULL, `level` TEXT NOT NULL, `platformCode` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`parent`) REFERENCES `Stop`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED)") + connection.execSQL("INSERT INTO `_new_Stop` (`id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode`) SELECT `id`,`name`,`lat`,`lng`,`parent`,`hasWheelChairBoarding`,`level`,`platformCode` FROM `Stop`") + connection.execSQL("UPDATE `_new_Stop` SET `parent` = NULL WHERE `parent` == \"\"") + connection.execSQL("DROP TABLE `Stop`") + connection.execSQL("ALTER TABLE `_new_Stop` RENAME TO `Stop`") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Stop_parent` ON `Stop` (`parent`)") + connection.execSQL("CREATE INDEX IF NOT EXISTS `index_Trip_serviceId` ON `Trip` (`serviceId`)") + foreignKeyCheck(connection, "Stop") + } +} diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt index 0174f0f..94ce892 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/RouteDao.kt @@ -37,13 +37,22 @@ interface RouteDao { """) suspend fun stops(id: String): List + // I vibecoded this, sorry @Query(""" - SELECT Stop.* FROM Stop - INNER JOIN Stop Child ON Child.parent == Stop.id - INNER JOIN StopTime ON StopTime.stopId == Child.id - INNER JOIN Trip ON Trip.id == StopTime.tripId - WHERE Trip.routeId == :id - GROUP BY Stop.id + WITH Tree AS ( + SELECT Stop.* FROM Stop + INNER JOIN StopTime ON StopTime.stopId == Stop.id + INNER JOIN Trip ON Trip.id == StopTime.tripId + WHERE Trip.routeId == :id + GROUP BY Stop.id + + UNION ALL + + SELECT s.* + FROM Stop s + INNER JOIN Tree t ON s.id = t.parent + ) + SELECT DISTINCT * FROM Tree WHERE parent IS NULL; """) suspend fun stopsParent(id: String): List } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt index f6b2ef2..869ae29 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopDao.kt @@ -12,6 +12,13 @@ interface StopDao { @Query("SELECT * FROM Stop") suspend fun getAll(): List + @Query(""" + SELECT * FROM Stop + WHERE platformCode <> "" + AND parent == "" + """) + suspend fun getAllParentless(): List + @Query("SELECT * FROM Stop WHERE id == :id") suspend fun get(id: String): StopEntity? @@ -29,4 +36,7 @@ interface StopDao { @Query("DELETE FROM Stop") suspend fun deleteAll() + + @Query("UPDATE Stop SET parent = :parent WHERE id IN (:ids)") + suspend fun updateParents(ids: List, parent: String) } diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt index cd377cc..82e0e4b 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/dao/StopTimeDao.kt @@ -27,7 +27,7 @@ interface StopTimeDao { INNER JOIN Trip ON Trip.serviceId == Service.id LEFT JOIN ServiceException ON ServiceException.serviceId == Service.id AND ServiceException.date == :date WHERE StopTime.tripId == Trip.id - AND StopTime.stopId == :stopId + AND StopTime.stopId IN (SELECT Stop.id FROM Stop WHERE Stop.parent == :stopId OR Stop.id == :stopId) AND ServiceException.type IS NULL """) suspend fun getForStopDated(stopId: String, days: Int, date: Int): List diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt index 9c6cf15..9ce7bfb 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/StopEntity.kt @@ -2,17 +2,30 @@ package moe.lava.banksia.room.entity import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.ForeignKey.Companion.SET_NULL import androidx.room.PrimaryKey import moe.lava.banksia.model.Stop import moe.lava.banksia.util.Point -@Entity("Stop") +@Entity( + "Stop", + foreignKeys = [ + ForeignKey( + StopEntity::class, + parentColumns = ["id"], + childColumns = ["parent"], + onDelete = SET_NULL, + deferred = true, + ), + ] +) data class StopEntity( @PrimaryKey val id: String, val name: String, val lat: Double, val lng: Double, - @ColumnInfo(index = true) val parent: String, + @ColumnInfo(index = true) val parent: String?, val hasWheelChairBoarding: Boolean, val level: String, val platformCode: String, diff --git a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt index 3753d44..12bda02 100644 --- a/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt +++ b/shared/src/commonMain/kotlin/moe/lava/banksia/room/entity/TripEntity.kt @@ -15,7 +15,7 @@ import moe.lava.banksia.model.Trip ForeignKey(ServiceEntity::class, parentColumns = ["id"], childColumns = ["serviceId"], onDelete = CASCADE), ForeignKey(ShapeEntity::class, parentColumns = ["id"], childColumns = ["shapeId"], onDelete = CASCADE), ], - indices = [Index("shapeId")], + indices = [Index("shapeId"), Index("serviceId")], ) data class TripEntity( @PrimaryKey val id: String,