diff --git a/.gitignore b/.gitignore index aa697a3..efff912 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ *.iml out gen + +dependency-reduced-pom.xml \ No newline at end of file diff --git a/README.md b/README.md index 73591d9..014ba8a 100644 --- a/README.md +++ b/README.md @@ -17,19 +17,21 @@ MySQL Kotlin wrapper based on HikariCP #### From Kotlin ``` kotlin val kuery = Kuery[file: File] +val kuery = Kuery[config: KueryConfig] ``` #### From Java ``` java final Kuery kuery = Kuery.get(file: File); +final Kuery kuery = Kuery.get(config: KueryConfig); ``` ### 1-1. To initialize and shutdown a database use these two methods -``` kotlin +``` java kuery.enable() ``` and -``` kotlin +``` java kuery.disable() ``` @@ -45,23 +47,54 @@ val resource = kuery.resource() ``` java final Connection resource = kuery.resource(); ``` -*[Database#resource](https://github.com/KDatabases/Core/blob/master/src/main/kotlin/com.sxtanna/database/base/Database.kt#L96) will throw an IllegalStateException if it's unable to create a resource a/o the database isn't enabled* +*[Database#resource](https://github.com/KDatabases/Core/blob/master/src/main/kotlin/com.sxtanna/database/base/Database.kt#L96) will throw an IllegalStateException if it's unable to into a resource a/o the database isn't enabled* -### 2-1. Or you could utilize the Database's ability to automatically manage the connection with the *invoke* methods +### 2-1. Or you could utilize the Database's ability to automatically manage the connection with various sql functions -#### From Kotlin -``` kotlin -kuery { - createTable("Table", "UUID" co Char(36, true), "Name" co VarChar(36)) +#### Creating a Table +##### From Kotlin +``` java +kuery { + create("User", "name" co VarChar.of(255, true)) // deprecated in favour of the cacheable builders +ex. + Create.table("User").co("name", VarChar(255, true))() } ``` -#### From Java +##### From Java ``` java -kuery.execute(task -> task.createTable("Users", Column.tableColumns(Column.col("UUID", new SqlType.Char(36, true)), Column.col("Name", new SqlType.VarChar(16))))); +kuery.execute(task -> { + task.execute(Create.table("User").co("name", new VarChar(255, true))); +}); ``` -*Yes, I know the Java version is ugly AF, but this can be slightly fixed with static imports* + +##### Storing a Create Statement (syntax is nearly the same for Kotlin and Java) +``` java +val createUser = Create.table("User").co("name", VarChar.of(255, true)) + +vs + +final CreateBuilder createUser = Create.table("User").co("name", VarChar.of(255, true)); +``` +#### Using them however is quite different +##### From Kotlin +``` java +kuery { + createUser() +} +``` +##### From Java ``` java -kuery.execute(task -> task.createTable("Users", tableColumns(col("UUID", new Char(36, true)), col("Name", new VarChar(16))))); +kuery.execute(task -> { + task.execute(createUser); +}); ``` + +## More examples soon. +#### For Java examples. +- [JKueryTest](src/test/java/com/sxtanna/database/tests/JKueryTest.java) +- [JKueryTestOrm](src/test/java/com/sxtanna/database/tests/JKueryTestOrm.java) +#### For Kotlin examples. +- [KKueryTest](src/test/kotlin/com/sxtanna/database/tests/KKueryTest.kt) +- [KKueryTestOrm](src/test/kotlin/com/sxtanna/database/tests/KKueryTestOrm.kt) diff --git a/pom.xml b/pom.xml index f8f7ea8..d5a1234 100644 --- a/pom.xml +++ b/pom.xml @@ -5,29 +5,29 @@ Super com.sxtanna.database - 1.3.1 + 2.0 ../Super/pom.xml 4.0.0 Kuery - 1.3.4 + 2.0 jar ${project.artifactId} MySQL Kotlin wrapper based on HikariCP - https://github.com/KDatabases/Keury + https://github.com/KDatabases/Kuery - git://github.com/KDatabases/Keury.git - scm:git:git://github.com/KDatabases/Keury.git + git://github.com/KDatabases/Kuery.git + scm:git:git://github.com/KDatabases/Kuery.git com.sxtanna.database Core - 1.2 + 1.3 com.zaxxer @@ -44,6 +44,12 @@ mysql-connector-java ${versionMySQLConnector} + + + com.sxtanna.database + Tests + 1.0 + diff --git a/src/main/kotlin/com/sxtanna/database/Kuery.kt b/src/main/kotlin/com/sxtanna/database/Kuery.kt index 1b11fdc..bd0bff0 100644 --- a/src/main/kotlin/com/sxtanna/database/Kuery.kt +++ b/src/main/kotlin/com/sxtanna/database/Kuery.kt @@ -4,8 +4,9 @@ import com.sxtanna.database.base.Database import com.sxtanna.database.config.DatabaseConfigManager import com.sxtanna.database.config.KueryConfig import com.sxtanna.database.ext.loadOrSave +import com.sxtanna.database.struct.base.Creator import com.sxtanna.database.task.KueryTask -import com.sxtanna.database.type.SqlObject +import com.sxtanna.database.type.base.SqlObject import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariDataSource import java.io.File @@ -13,13 +14,13 @@ import java.sql.Connection import java.sql.ResultSet import kotlin.reflect.KClass -class Kuery(override val config : KueryConfig) : Database() { +class Kuery private constructor(override val config : KueryConfig) : Database() { override val name : String = "Kuery" lateinit var pool : HikariDataSource private set - val debug get() = config.debug ?: false + val debug by lazy { config.debug ?: false } val creators = mutableMapOf, ResultSet.() -> SqlObject>() override fun load() { @@ -60,11 +61,18 @@ class Kuery(override val config : KueryConfig) : Database addCreator(creator : Creator) { + creators[creator.clazz] = creator + } + companion object : DatabaseConfigManager { @JvmStatic - override fun get(file : File) : Kuery = Kuery(getConfig(file)) + override fun get(file : File) = super.get(file) + + @JvmStatic + override fun get(config : KueryConfig) = Kuery(config) @JvmStatic override fun getConfig(file : File) = file.loadOrSave(KueryConfig.DEFAULT) diff --git a/src/main/kotlin/com/sxtanna/database/struct/obj/annotations.kt b/src/main/kotlin/com/sxtanna/database/ext/annotations.kt similarity index 98% rename from src/main/kotlin/com/sxtanna/database/struct/obj/annotations.kt rename to src/main/kotlin/com/sxtanna/database/ext/annotations.kt index 1f4050b..b6f6fa2 100644 --- a/src/main/kotlin/com/sxtanna/database/struct/obj/annotations.kt +++ b/src/main/kotlin/com/sxtanna/database/ext/annotations.kt @@ -1,4 +1,4 @@ -package com.sxtanna.database.struct.obj +package com.sxtanna.database.ext import com.sxtanna.database.struct.obj.SqlType.* import com.sxtanna.database.struct.obj.SqlType.Char diff --git a/src/main/kotlin/com/sxtanna/database/ext/extensions.kt b/src/main/kotlin/com/sxtanna/database/ext/extensions.kt deleted file mode 100644 index cbcf1d9..0000000 --- a/src/main/kotlin/com/sxtanna/database/ext/extensions.kt +++ /dev/null @@ -1,81 +0,0 @@ -@file:JvmName("Kext") -package com.sxtanna.database.ext - -import com.sxtanna.database.struct.Duo -import com.sxtanna.database.type.JsonObject -import java.math.BigInteger -import java.sql.ResultSet -import java.util.* - -/** - * Special infix function for [Duo] - * - * @param value The value to create with this Duo - * @sample createColumns - */ -infix fun String.co(value : T) = Duo(this, value) - - -/** - * Convenience method for reading a UUID from a ResultSet - * - * @param column The column name - * @return The UUID read from the column - */ -fun ResultSet.getUniqueID(column : String) : UUID = UUID.fromString(getString(column)) - -/** - * Convenience method for reading a UUID from a ResultSet - * - * @param column The column name - * @return The UUID read from the column - */ -fun ResultSet.getBigInteger(column : String) : BigInteger = BigInteger(getString(column)) - -/** - * Convenience method for reading Objects from a Json String - * - * @param column The column name - * @return The Object read from the column - */ -inline fun ResultSet.getJson(column : String) : E { - return gson.fromJson(getString(column), E::class.java) -} - - -/** - * Invoke this block for every result in this set - * - * @param block The action - * @sample sayUserNames - */ -inline fun ResultSet.whileNext(block: ResultSet.() -> Unit) { - while (this.next()) this.block() -} - -/** - * Use this block to map each result to an object - * - * @param mapper The result mapper - * @sample getUserNames - */ -inline fun ResultSet.mapWhileNext(mapper: ResultSet.() -> O): List { - val output = mutableListOf() - this.whileNext { output.add(this.mapper()) } - - return output -} - - -private fun createColumns() : Array> { - return Duo.valueColumns("One" co 1, "Two" co 2, "True" co true) -} - -private fun getUserNames(resultSet : ResultSet) { - val names : List = resultSet.mapWhileNext { getString("Username") } - for (name in names) println("Found user $name") -} - -private fun sayUserNames(resultSet : ResultSet) { - resultSet.whileNext { println("Found user ${getString("Username")}") } -} \ No newline at end of file diff --git a/src/main/kotlin/com/sxtanna/database/ext/data.kt b/src/main/kotlin/com/sxtanna/database/ext/kdata.kt similarity index 82% rename from src/main/kotlin/com/sxtanna/database/ext/data.kt rename to src/main/kotlin/com/sxtanna/database/ext/kdata.kt index 8256b28..62772a2 100644 --- a/src/main/kotlin/com/sxtanna/database/ext/data.kt +++ b/src/main/kotlin/com/sxtanna/database/ext/kdata.kt @@ -1,6 +1,10 @@ +@file:JvmName("KData") + package com.sxtanna.database.ext +import com.sxtanna.database.struct.obj.Sort import com.sxtanna.database.struct.obj.SqlType +import com.sxtanna.database.struct.obj.Target import kotlin.reflect.KProperty1 /** @@ -39,6 +43,11 @@ const val LONG_TEXT_SIZE = NORM_MAX_UNSIGN /** * Represents all rows in a select statement */ +@get:JvmName("allRows") val ALL_ROWS = arrayOf("*") +@get:JvmName("noSort") +val NO_SORTS = arrayOf() +@get:JvmName("noWhere") +val NO_WHERE = arrayOf() typealias Adapter = KProperty1<*, *>.() -> SqlType \ No newline at end of file diff --git a/src/main/kotlin/com/sxtanna/database/ext/kext.kt b/src/main/kotlin/com/sxtanna/database/ext/kext.kt new file mode 100644 index 0000000..a8e5db0 --- /dev/null +++ b/src/main/kotlin/com/sxtanna/database/ext/kext.kt @@ -0,0 +1,71 @@ +@file:JvmName("Kext") + +package com.sxtanna.database.ext + +import com.sxtanna.database.struct.base.Creator +import com.sxtanna.database.struct.base.Duo +import com.sxtanna.database.struct.obj.Sort +import com.sxtanna.database.struct.obj.Target +import com.sxtanna.database.task.builder.CreateBuilder +import com.sxtanna.database.task.builder.InsertBuilder +import com.sxtanna.database.task.builder.SelectBuilder +import com.sxtanna.database.task.builder.UpdateBuilder +import com.sxtanna.database.type.base.SqlObject +import java.sql.ResultSet +import java.util.function.Function +import java.util.function.Supplier + + +@JvmSynthetic +fun attempt(catch : Boolean = false, block : () -> O) : O? { + try { + return block() + } + catch (e : Exception) {} + return null +} + +@JvmOverloads +fun attempt(catch : Boolean = false, block : Supplier) : O? = attempt(catch) { block.get() } + + +fun Boolean.value(input : String?) = if (this) input ?: "" else "" + +/** + * Special infix function for [Duo] + * + * @param value The value to into with this Duo + * @sample createColumns + */ +infix fun String.co(value : T) = Duo(this, value) + + +fun create(clazz : Class, creator : Function) = object : Creator(clazz.kotlin) { + override fun apply(t : ResultSet) : O = creator.apply(t) +} + + +fun data(vararg any : Any) = any + +fun sorts(vararg sorts : Sort) = sorts + +fun targets(vararg targets : Target) = targets + + +//region Build Function Creators +@JvmSynthetic +inline fun createTable(name : String = T::class.simpleName!!) = CreateBuilder.from(name) + +@JvmSynthetic +inline fun selectFrom(table : String = T::class.simpleName!!, noinline block : SelectBuilder.() -> Unit = {}) = SelectBuilder(T::class, table).apply(block) + +@JvmSynthetic +inline fun insertInto(table : String = T::class.simpleName!!, noinline block : InsertBuilder.() -> Unit = {}) = InsertBuilder(T::class, table).apply(block) + +@JvmSynthetic +inline fun updateIn(table : String = T::class.simpleName!!, noinline block : UpdateBuilder.() -> Unit = {}) = UpdateBuilder(T::class, table).apply(block) +//endregion + + +private fun createColumns() = Duo.valueColumns("One" co 1, "Two" co 2, "True" co true) + diff --git a/src/main/kotlin/com/sxtanna/database/ext/krs.kt b/src/main/kotlin/com/sxtanna/database/ext/krs.kt new file mode 100644 index 0000000..e8de81f --- /dev/null +++ b/src/main/kotlin/com/sxtanna/database/ext/krs.kt @@ -0,0 +1,111 @@ +@file:JvmName("Krs") + +package com.sxtanna.database.ext + +import java.math.BigInteger +import java.sql.ResultSet +import java.util.* +import java.util.UUID.fromString +import java.util.function.Consumer +import java.util.function.Function + +/** + * Read a [UUID] from a [ResultSet] + * + * @param column The column name + * @return The UUID read from the column + */ +@JvmSynthetic +@JvmName("gUUID") +fun ResultSet.getUUID(column : String) = getUUID(this, column) + +/** + * Read a [BigInteger] from a [ResultSet] + * + * @param column The column name + * @return The UUID read from the column + */ +@JvmSynthetic +@JvmName("gBINT") +fun ResultSet.getBigInt(column : String) = getBigInt(this, column) + +/** + * Read any object [J] from the [ResultSet] + * + * **Row data must actually contain serialized object** + */ +@JvmSynthetic +inline fun ResultSet.getJson(column : String) = getJson(this, J::class.java, column) + +/** + * Read an Enum constant from a [ResultSet] + */ +@JvmSynthetic +inline fun > ResultSet.getEnum(column : String) = getEnum(this, E::class.java, column) + + +/** + * Execute this block of code for every result in the set + * @sample sayUserNames + */ +@JvmSynthetic +inline fun ResultSet.whileNext(block : ResultSet.() -> Unit) { + while (this.next()) this.block() +} + +/** + * Map each result in the set to an object and return a list of them + * @sample getUserNames + */ +@JvmSynthetic +inline fun ResultSet.mapWhileNext(mapper : ResultSet.() -> O) : List { + val output = mutableListOf() + this.whileNext { output.add(this.mapper()) } + + return output +} + + +/** + * Convenience methods for accessing ResultSet data without having to worry about catching exceptions + */ + +fun getInt(rs : ResultSet, column : String) = attempt { rs.getInt(column) } + +fun getLong(rs : ResultSet, column : String) = attempt { rs.getLong(column) } + +fun getString(rs : ResultSet, column : String) = attempt { rs.getString(column) } + +fun getDouble(rs : ResultSet, column : String) = attempt { rs.getDouble(column) } + +fun getUUID(rs : ResultSet, column : String) : UUID { + val uuid = rs.getString(column) + return checkNotNull(fromString(uuid)) { "Bad UUID $uuid at column $column" } +} + +fun getBigInt(rs : ResultSet, column : String) = BigInteger(rs.getString(column)) + +fun getJson(rs : ResultSet, clazz : Class, column : String) : J { + val data = rs.getString(column) + return checkNotNull(attempt { gson.fromJson(data, clazz) }) { "Json data $data at column $column failed to be deserialized" } +} + +fun > getEnum(rs : ResultSet, clazz : Class, column : String) : E { + val name = rs.getString(column) + return checkNotNull(clazz.enumConstants.find { it.name == name }) { "Enum value $name at column $column doesn't exist" } +} + + +fun whileNext(rs : ResultSet, block : Consumer) = rs.whileNext { block.accept(this) } + +fun mapWhileNext(rs : ResultSet, block : Function) = rs.mapWhileNext { block.apply(this) } + + +private fun getUserNames(resultSet : ResultSet) { + val names = resultSet.mapWhileNext { getString("Username") } + for (name in names) println("Found user $name") +} + +private fun sayUserNames(resultSet : ResultSet) { + resultSet.whileNext { println("Found user ${getString("Username")}") } +} \ No newline at end of file diff --git a/src/main/kotlin/com/sxtanna/database/struct/Resolver.kt b/src/main/kotlin/com/sxtanna/database/struct/Resolver.kt index 7b1adc4..3e6e0ed 100644 --- a/src/main/kotlin/com/sxtanna/database/struct/Resolver.kt +++ b/src/main/kotlin/com/sxtanna/database/struct/Resolver.kt @@ -23,13 +23,14 @@ object Resolver { private val adapters = mutableMapOf, Adapter>() + init { - adapters[Char::class] = { SqlType.Char(1, isPrimaryKey(), isNotNull()) } - adapters[UUID::class] = { SqlType.Char(36, isPrimaryKey(), isNotNull()) } + this[Char::class] = { SqlType.Char(1, isPrimaryKey(), isNotNull()) } + this[UUID::class] = { SqlType.Char(36, isPrimaryKey(), isNotNull()) } - adapters[Boolean::class] = { SqlType.Bool(isPrimaryKey(), isNotNull()) } + this[Boolean::class] = { SqlType.Bool(isPrimaryKey(), isNotNull()) } - adapters[Enum::class] = { + this[Enum::class] = { if (isSerialized()) SqlType.VarChar(VARCHAR_SIZE, isPrimaryKey(), isNotNull()) else { @Suppress("UNCHECKED_CAST") @@ -37,14 +38,14 @@ object Resolver { } } - adapters[Timestamp::class] = { + this[Timestamp::class] = { val time = findAnnotation