summaryrefslogtreecommitdiff
path: root/src/main/kotlin/io/dico/parcels2/storage/exposed/IdTables.kt
blob: 8428b3abdceecfcabc0bec70871099be291606b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "unused", "MemberVisibilityCanBePrivate")

package io.dico.parcels2.storage.exposed

import io.dico.parcels2.ParcelId
import io.dico.parcels2.ParcelWorldId
import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.storage.toByteArray
import io.dico.parcels2.storage.toUUID
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.UpdateBuilder
import org.joda.time.DateTime
import java.util.UUID

abstract class IdTransactionsTable<TableT : IdTransactionsTable<TableT, QueryObj>, QueryObj>(tableName: String, columnName: String)
    : Table(tableName) {
    val id = integer(columnName).autoIncrement().primaryKey()

    @Suppress("UNCHECKED_CAST")
    inline val table: TableT
        get() = this as TableT

    internal inline fun getId(where: SqlExpressionBuilder.(TableT) -> Op<Boolean>): Int? {
        return select { where(table) }.firstOrNull()?.let { it[id] }
    }

    internal inline fun getOrInitId(getId: () -> Int?, noinline body: TableT.(UpdateBuilder<*>) -> Unit, objName: () -> String): Int {
        return getId() ?: table.insertIgnore(body)[id] ?: getId()
        ?: throw ExposedDatabaseException("This should not happen - failed to insert ${objName()} and get its number")
    }

    abstract fun getId(obj: QueryObj): Int?
    abstract fun getOrInitId(obj: QueryObj): Int
    fun getItem(id: Int): QueryObj? = select { this@IdTransactionsTable.id eq id }.firstOrNull()?.let { getItem(it) }
    abstract fun getItem(row: ResultRow): QueryObj?

    fun getId(obj: QueryObj, init: Boolean): Int? = if (init) getOrInitId(obj) else getId(obj)
}

object WorldsT : IdTransactionsTable<WorldsT, ParcelWorldId>("parcels_worlds", "world_id") {
    val name = varchar("name", 50)
    val uid = binary("uid", 16).nullable()
    val creation_time = datetime("creation_time").nullable()
    val index_name = uniqueIndexR("index_name", name)
    val index_uid = uniqueIndexR("index_uid", uid)

    internal inline fun getId(worldName: String, binaryUid: ByteArray?): Int? = getId { (name eq worldName).let { if (binaryUid == null) it else it or (uid eq binaryUid) } }
    internal inline fun getId(worldName: String, uid: UUID?): Int? = getId(worldName, uid?.toByteArray())
    internal inline fun getOrInitId(worldName: String, worldUid: UUID?): Int = worldUid?.toByteArray().let { binaryUid ->
        return getOrInitId(
            { getId(worldName, binaryUid) },
            { it[name] = worldName; it[uid] = binaryUid },
            { "world named $worldName" })
    }

    override fun getId(world: ParcelWorldId): Int? = getId(world.name, world.uid)
    override fun getOrInitId(world: ParcelWorldId): Int = getOrInitId(world.name, world.uid)

    override fun getItem(row: ResultRow): ParcelWorldId {
        return ParcelWorldId(row[name], row[uid]?.toUUID())
    }

    fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
        val id = getId(worldId) ?: return null
        return select { WorldsT.id eq id }.firstOrNull()?.let { it[WorldsT.creation_time] }
    }

    fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) {
        val id = getOrInitId(worldId)
        update({ WorldsT.id eq id }) {
            it[WorldsT.creation_time] = time
        }
    }
}

object ParcelsT : IdTransactionsTable<ParcelsT, ParcelId>("parcels", "parcel_id") {
    val world_id = integer("world_id").references(WorldsT.id)
    val px = integer("px")
    val pz = integer("pz")
    val owner_id = integer("owner_id").references(ProfilesT.id).nullable()
    val sign_oudated = bool("sign_outdated").default(false)
    val claim_time = datetime("claim_time").nullable()
    val index_location = uniqueIndexR("index_location", world_id, px, pz)

    private inline fun getId(worldId: Int, parcelX: Int, parcelZ: Int): Int? = getId { world_id.eq(worldId) and px.eq(parcelX) and pz.eq(parcelZ) }
    private inline fun getId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int? = WorldsT.getId(worldName, worldUid)?.let { getId(it, parcelX, parcelZ) }
    private inline fun getOrInitId(worldName: String, worldUid: UUID?, parcelX: Int, parcelZ: Int): Int {
        val worldId = WorldsT.getOrInitId(worldName, worldUid)
        return getOrInitId(
            { getId(worldId, parcelX, parcelZ) },
            { it[world_id] = worldId; it[px] = parcelX; it[pz] = parcelZ },
            { "parcel at $worldName($parcelX, $parcelZ)" })
    }

    override fun getId(parcel: ParcelId): Int? = getId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z)
    override fun getOrInitId(parcel: ParcelId): Int = getOrInitId(parcel.worldId.name, parcel.worldId.uid, parcel.x, parcel.z)

    private inline fun getRow(id: Int): ResultRow? = select { ParcelsT.id eq id }.firstOrNull()
    fun getRow(parcel: ParcelId): ResultRow? = getId(parcel)?.let { getRow(it) }

    override fun getItem(row: ResultRow): ParcelId? {
        val worldId = row[world_id]
        val world = WorldsT.getItem(worldId) ?: return null
        return ParcelId(world, row[px], row[pz])
    }
}

object ProfilesT : IdTransactionsTable<ProfilesT, PlayerProfile>("parcels_profiles", "owner_id") {
    val uuid = binary("uuid", 16).nullable()
    val name = varchar("name", 32).nullable()

    // MySQL dialect MUST permit multiple null values for this to work. Server SQL does not allow this. That dialect is shit anyway.
    val uuid_constraint = uniqueIndexR("uuid_constraint", uuid)
    val index_pair = uniqueIndexR("index_pair", uuid, name)


    private inline fun getId(binaryUuid: ByteArray) = getId { uuid eq binaryUuid }
    private inline fun getId(uuid: UUID) = getId(uuid.toByteArray())
    private inline fun getId(nameIn: String) = getId { uuid.isNull() and (name.lowerCase() eq nameIn.toLowerCase()) }
    private inline fun getRealId(nameIn: String) = getId { uuid.isNotNull() and (name.lowerCase() eq nameIn.toLowerCase()) }

    private inline fun getOrInitId(uuid: UUID, name: String?) = uuid.toByteArray().let { binaryUuid ->
        getOrInitId(
            { getId(binaryUuid) },
            { it[this@ProfilesT.uuid] = binaryUuid; it[this@ProfilesT.name] = name },
            { "profile(uuid = $uuid, name = $name)" })
    }

    private inline fun getOrInitId(name: String) = getOrInitId(
        { getId(name) },
        { it[ProfilesT.name] = name },
        { "owner(name = $name)" })


    override fun getId(profile: PlayerProfile): Int? = when (profile) {
        is PlayerProfile.Real -> getId(profile.uuid)
        is PlayerProfile.Fake -> getId(profile.name)
        is PlayerProfile.Unresolved -> getRealId(profile.name)
        else -> throw IllegalArgumentException()
    }

    override fun getOrInitId(profile: PlayerProfile): Int = when (profile) {
        is PlayerProfile.Real -> getOrInitId(profile.uuid, profile.nameOrBukkitName)
        is PlayerProfile.Fake -> getOrInitId(profile.name)
        else -> throw IllegalArgumentException() // Unresolved profiles cannot be added to the database
    }

    override fun getItem(row: ResultRow): PlayerProfile {
        return PlayerProfile(row[uuid]?.toUUID(), row[name])
    }

    fun getRealItem(id: Int): PlayerProfile.Real? {
        return getItem(id) as? PlayerProfile.Real
    }

    /*
    fun updatePlayerProfile(profile: PlayerProfile.Real) {
        update({ uuid eq profile.uuid.toByteArray() }) {
            it[name] = profile.nameOrBukkitName
        }
    }*/

}

// val ParcelsWithOptionsT = ParcelsT.join(ParcelOptionsT, JoinType.INNER, onColumn = ParcelsT.id, otherColumn = ParcelOptionsT.parcel_id)