aboutsummaryrefslogtreecommitdiff
path: root/database/core
diff options
context:
space:
mode:
Diffstat (limited to 'database/core')
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/Column.kt27
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/Constraint.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/DBType.kt16
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/InsertStatement.kt4
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/Query.kt30
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/ResultRow.kt17
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/Table.kt16
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/UniqueConstraint.kt4
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBDouble.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBEnum.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBInstant.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBInt.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBString.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBUlid.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBUuid.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/ANDExpression.kt5
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/Clause.kt6
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/ClauseBuilder.kt22
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/ColumnOperand.kt6
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/EqualsClause.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/IntoSelectable.kt5
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/LessThanEqualsExpression.kt15
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/LessThanExpression.kt15
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/LikeClause.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/ListClause.kt8
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/ListExpression.kt22
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/Operand.kt7
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryComponent.kt19
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryGenerator.kt8
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/Selectable.kt17
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/StringOperand.kt5
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/TypedOperand.kt7
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/ValuedOperand.kt15
33 files changed, 266 insertions, 50 deletions
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/Column.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/Column.kt
index d1294f7..c21a159 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/Column.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/Column.kt
@@ -1,10 +1,31 @@
package moe.nea.ledger.database
-class Column<T> @Deprecated("Use Table.column instead") constructor(
+import moe.nea.ledger.database.sql.IntoSelectable
+import moe.nea.ledger.database.sql.Selectable
+import java.sql.PreparedStatement
+
+class Column<T, Raw> @Deprecated("Use Table.column instead") constructor(
val table: Table,
val name: String,
- val type: DBType<T>
-) {
+ val type: DBType<T, Raw>
+) : IntoSelectable<T> {
+ override fun asSelectable() = object : Selectable<T, Raw> {
+ override fun asSql(): String {
+ return qualifiedSqlName
+ }
+
+ override val dbType: DBType<T, Raw>
+ get() = this@Column.type
+
+ override fun guessColumn(): Column<T, Raw> {
+ return this@Column
+ }
+
+ override fun appendToStatement(stmt: PreparedStatement, startIndex: Int): Int {
+ return startIndex
+ }
+ }
+
val sqlName get() = "`$name`"
val qualifiedSqlName get() = table.sqlName + "." + sqlName
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/Constraint.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/Constraint.kt
index 9f7c9ef..729c6b8 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/Constraint.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/Constraint.kt
@@ -1,6 +1,6 @@
package moe.nea.ledger.database
interface Constraint {
- val affectedColumns: Collection<Column<*>>
+ val affectedColumns: Collection<Column<*, *>>
fun asSQL(): String
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/DBType.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/DBType.kt
index 86ff544..622aff3 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/DBType.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/DBType.kt
@@ -1,9 +1,19 @@
package moe.nea.ledger.database
+import moe.nea.ledger.database.sql.ClauseBuilder
import java.sql.PreparedStatement
import java.sql.ResultSet
-interface DBType<T> {
+
+interface DBType<
+ /**
+ * Mapped type of this db type. Represents the Java type this db type accepts for saving to the database.
+ */
+ T,
+ /**
+ * Phantom marker type representing how this db type is presented to the actual DB. Is used by APIs such as [ClauseBuilder] to allow for rough typechecking.
+ */
+ RawType> {
val dbType: String
fun get(result: ResultSet, index: Int): T
@@ -12,8 +22,8 @@ interface DBType<T> {
fun <R> mapped(
from: (R) -> T,
to: (T) -> R,
- ): DBType<R> {
- return object : DBType<R> {
+ ): DBType<R, RawType> {
+ return object : DBType<R, RawType> {
override fun getName(): String {
return "Mapped(${this@DBType.getName()})"
}
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/InsertStatement.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/InsertStatement.kt
index 7871ba8..25bef22 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/InsertStatement.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/InsertStatement.kt
@@ -1,7 +1,7 @@
package moe.nea.ledger.database
-class InsertStatement(val properties: MutableMap<Column<*>, Any>) {
- operator fun <T : Any> set(key: Column<T>, value: T) {
+class InsertStatement(val properties: MutableMap<Column<*, *>, Any>) {
+ operator fun <T : Any> set(key: Column<T, *>, value: T) {
properties[key] = value
}
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/Query.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/Query.kt
index 7829fbb..a23c878 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/Query.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/Query.kt
@@ -3,19 +3,22 @@ package moe.nea.ledger.database
import moe.nea.ledger.database.sql.ANDExpression
import moe.nea.ledger.database.sql.BooleanExpression
import moe.nea.ledger.database.sql.Clause
+import moe.nea.ledger.database.sql.IntoSelectable
import moe.nea.ledger.database.sql.Join
import moe.nea.ledger.database.sql.SQLQueryComponent
import moe.nea.ledger.database.sql.SQLQueryGenerator.concatToFilledPreparedStatement
+import moe.nea.ledger.database.sql.Selectable
import java.sql.Connection
class Query(
val connection: Connection,
- val selectedColumns: MutableList<Column<*>>,
+ val selectedColumns: MutableList<Selectable<*, *>>,
var table: Table,
var limit: UInt? = null,
var skip: UInt? = null,
val joins: MutableList<Join> = mutableListOf(),
val conditions: MutableList<BooleanExpression> = mutableListOf(),
+ var distinct: Boolean = false,
// var order: OrderClause?= null,
) : Iterable<ResultRow> {
fun join(table: Table, on: Clause): Query {
@@ -28,8 +31,10 @@ class Query(
return this
}
- fun select(vararg columns: Column<*>): Query {
- selectedColumns.addAll(columns)
+ fun select(vararg columns: IntoSelectable<*>): Query {
+ for (column in columns) {
+ this.selectedColumns.add(column.asSelectable())
+ }
return this
}
@@ -39,16 +44,29 @@ class Query(
return this
}
+ fun distinct(): Query {
+ this.distinct = true
+ return this
+ }
+
fun limit(limit: UInt): Query {
this.limit = limit
return this
}
override fun iterator(): Iterator<ResultRow> {
- val columnSelections = selectedColumns.joinToString { it.qualifiedSqlName }
val elements = mutableListOf(
- SQLQueryComponent.standalone("SELECT $columnSelections FROM ${table.sqlName}"),
+ SQLQueryComponent.standalone("SELECT"),
)
+ if (distinct)
+ elements.add(SQLQueryComponent.standalone("DISTINCT"))
+ selectedColumns.forEachIndexed { idx, it ->
+ elements.add(it)
+ if (idx != selectedColumns.lastIndex) {
+ elements.add(SQLQueryComponent.standalone(","))
+ }
+ }
+ elements.add(SQLQueryComponent.standalone("FROM ${table.sqlName}"))
elements.addAll(joins)
if (conditions.any()) {
elements.add(SQLQueryComponent.standalone("WHERE"))
@@ -84,7 +102,7 @@ class Query(
}
hasAdvanced = false
return ResultRow(selectedColumns.withIndex().associate {
- it.value to it.value.type.get(results, it.index + 1)
+ it.value to it.value.dbType.get(results, it.index + 1)
})
}
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/ResultRow.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/ResultRow.kt
index d92f913..7b57abd 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/ResultRow.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/ResultRow.kt
@@ -1,9 +1,22 @@
package moe.nea.ledger.database
-class ResultRow(val columnValues: Map<Column<*>, *>) {
- operator fun <T> get(column: Column<T>): T {
+import moe.nea.ledger.database.sql.Selectable
+
+class ResultRow(val selectableValues: Map<Selectable<*, *>, *>) {
+ val columnValues = selectableValues.mapNotNull {
+ val col = it.key.guessColumn() ?: return@mapNotNull null
+ col to it.value
+ }.toMap()
+
+ operator fun <T> get(column: Column<T, *>): T {
val value = columnValues[column]
?: error("Invalid column ${column.name}. Only ${columnValues.keys.joinToString { it.name }} are available.")
return value as T
}
+
+ operator fun <T> get(column: Selectable<T, *>): T {
+ val value = selectableValues[column]
+ ?: error("Invalid selectable ${column}. Only ${selectableValues.keys} are available.")
+ return value as T
+ }
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/Table.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/Table.kt
index c136f48..a462813 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/Table.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/Table.kt
@@ -4,15 +4,15 @@ import java.sql.Connection
abstract class Table(val name: String) {
val sqlName get() = "`$name`"
- protected val _mutable_columns: MutableList<Column<*>> = mutableListOf()
+ protected val _mutable_columns: MutableList<Column<*, *>> = mutableListOf()
protected val _mutable_constraints: MutableList<Constraint> = mutableListOf()
- val columns: List<Column<*>> get() = _mutable_columns
+ val columns: List<Column<*, *>> get() = _mutable_columns
val constraints get() = _mutable_constraints
- protected fun unique(vararg columns: Column<*>) {
+ protected fun unique(vararg columns: Column<*, *>) {
_mutable_constraints.add(UniqueConstraint(columns.toList()))
}
- protected fun <T> column(name: String, type: DBType<T>): Column<T> {
+ protected fun <T, R> column(name: String, type: DBType<T, R>): Column<T, R> {
@Suppress("DEPRECATION") val column = Column(this, name, type)
_mutable_columns.add(column)
return column
@@ -39,7 +39,7 @@ abstract class Table(val name: String) {
fun createIfNotExists(
connection: Connection,
- filteredColumns: List<Column<*>> = columns
+ filteredColumns: List<Column<*, *>> = columns
) {
val properties = mutableListOf<String>()
for (column in filteredColumns) {
@@ -57,7 +57,7 @@ abstract class Table(val name: String) {
fun alterTableAddColumns(
connection: Connection,
- newColumns: List<Column<*>>
+ newColumns: List<Column<*, *>>
) {
for (column in newColumns) {
connection.prepareAndLog("ALTER TABLE $sqlName ADD ${column.sqlName} ${column.type.dbType}")
@@ -88,7 +88,7 @@ abstract class Table(val name: String) {
val statement =
connection.prepareAndLog("INSERT OR ${onConflict.asSql()} INTO $sqlName ($columnNames) VALUES ($valueNames)")
for ((index, column) in columns.withIndex()) {
- (column as Column<Any>).type.set(statement, index + 1, insert.properties[column]!!)
+ (column as Column<Any, *>).type.set(statement, index + 1, insert.properties[column]!!)
}
statement.execute()
}
@@ -98,6 +98,6 @@ abstract class Table(val name: String) {
}
fun selectAll(connection: Connection): Query {
- return Query(connection, columns.toMutableList(), this)
+ return Query(connection, columns.mapTo(mutableListOf()) { it.asSelectable() }, this)
}
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/UniqueConstraint.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/UniqueConstraint.kt
index 32e9f79..31ef06c 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/UniqueConstraint.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/UniqueConstraint.kt
@@ -1,11 +1,11 @@
package moe.nea.ledger.database
-class UniqueConstraint(val columns: List<Column<*>>) : Constraint {
+class UniqueConstraint(val columns: List<Column<*, *>>) : Constraint {
init {
require(columns.isNotEmpty())
}
- override val affectedColumns: Collection<Column<*>>
+ override val affectedColumns: Collection<Column<*, *>>
get() = columns
override fun asSQL(): String {
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBDouble.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBDouble.kt
index 6828308..9840df2 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBDouble.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBDouble.kt
@@ -4,7 +4,7 @@ import moe.nea.ledger.database.DBType
import java.sql.PreparedStatement
import java.sql.ResultSet
-object DBDouble : DBType<Double> {
+object DBDouble : DBType<Double, Double> {
override val dbType: String
get() = "DOUBLE"
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBEnum.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBEnum.kt
index 3cce8bc..78ac578 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBEnum.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBEnum.kt
@@ -6,7 +6,7 @@ import java.sql.ResultSet
class DBEnum<T : Enum<T>>(
val type: Class<T>,
-) : DBType<T> {
+) : DBType<T, String> {
companion object {
inline operator fun <reified T : Enum<T>> invoke(): DBEnum<T> {
return DBEnum(T::class.java)
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBInstant.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBInstant.kt
index a9f36ef..2cf1882 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBInstant.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBInstant.kt
@@ -5,7 +5,7 @@ import java.sql.PreparedStatement
import java.sql.ResultSet
import java.time.Instant
-object DBInstant : DBType<Instant> {
+object DBInstant : DBType<Instant, Long> {
override val dbType: String
get() = "INTEGER"
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBInt.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBInt.kt
index 9cf2aa0..b5406e1 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBInt.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBInt.kt
@@ -4,7 +4,7 @@ import moe.nea.ledger.database.DBType
import java.sql.PreparedStatement
import java.sql.ResultSet
-object DBInt : DBType<Long> {
+object DBInt : DBType<Long, Long> {
override val dbType: String
get() = "INTEGER"
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBString.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBString.kt
index 3994b7d..4406627 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBString.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBString.kt
@@ -4,7 +4,7 @@ import moe.nea.ledger.database.DBType
import java.sql.PreparedStatement
import java.sql.ResultSet
-object DBString : DBType<String> {
+object DBString : DBType<String, String> {
override val dbType: String
get() = "TEXT"
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBUlid.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBUlid.kt
index 33db65f..1fcc9d8 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBUlid.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBUlid.kt
@@ -5,7 +5,7 @@ import moe.nea.ledger.database.DBType
import java.sql.PreparedStatement
import java.sql.ResultSet
-object DBUlid : DBType<ULIDWrapper> {
+object DBUlid : DBType<ULIDWrapper, String> {
override val dbType: String
get() = "TEXT"
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBUuid.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBUuid.kt
index ede385f..eaea440 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBUuid.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/columns/DBUuid.kt
@@ -6,7 +6,7 @@ import java.sql.PreparedStatement
import java.sql.ResultSet
import java.util.UUID
-object DBUuid : DBType<UUID> {
+object DBUuid : DBType<UUID, String> {
override val dbType: String
get() = "TEXT"
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ANDExpression.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ANDExpression.kt
index 6ea1b64..43d5a53 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ANDExpression.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ANDExpression.kt
@@ -10,7 +10,10 @@ data class ANDExpression(
}
override fun asSql(): String {
- return (elements + SQLQueryComponent.standalone("TRUE")).joinToString(" AND ", "(", ")") { it.asSql() }
+ elements.singleOrNull()?.let {
+ return "(" + it.asSql() + ")"
+ }
+ return elements.joinToString(" AND ", "(", ")") { it.asSql() }
}
override fun appendToStatement(stmt: PreparedStatement, startIndex: Int): Int {
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Clause.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Clause.kt
index 2921d80..205e566 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Clause.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Clause.kt
@@ -1,10 +1,12 @@
package moe.nea.ledger.database.sql
+/**
+ * Directly constructing [clauses][Clause] is discouraged. Instead [Clause.invoke] should be used.
+ */
interface Clause : BooleanExpression {
companion object {
- operator fun invoke(builder: ClauseBuilder.() -> Clause): Clause {
+ operator fun <T> invoke(builder: ClauseBuilder.() -> T): T {
return builder(ClauseBuilder())
}
}
-
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ClauseBuilder.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ClauseBuilder.kt
index 2b141f0..cb0ddfc 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ClauseBuilder.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ClauseBuilder.kt
@@ -1,11 +1,25 @@
package moe.nea.ledger.database.sql
import moe.nea.ledger.database.Column
+import moe.nea.ledger.database.DBType
class ClauseBuilder {
- fun <T> column(column: Column<T>): Operand<T> = ColumnOperand(column)
+ // TODO: should we match on T AND R? maybe allow explicit upcasting
+ fun <T, R> column(column: Column<T, R>): ColumnOperand<T, R> = ColumnOperand(column)
fun string(string: String): StringOperand = StringOperand(string)
- infix fun Operand<*>.eq(operand: Operand<*>) = EqualsClause(this, operand)
- infix fun Operand<*>.like(op: StringOperand) = LikeClause(this, op)
- infix fun Operand<*>.like(op: String) = LikeClause(this, string(op))
+ fun <T, R> value(dbType: DBType<T, R>, value: T): Operand<T, R> = ValuedOperand(dbType, value)
+ infix fun <T> Operand<*, T>.eq(operand: Operand<*, T>): Clause = EqualsClause(this, operand)
+ infix fun <T, R> TypedOperand<T, R>.eq(value: T): Clause = EqualsClause(this, value(dbType, value))
+ infix fun Operand<*, String>.like(op: StringOperand): Clause = LikeClause(this, op)
+ infix fun Operand<*, String>.like(op: String): Clause = LikeClause(this, string(op))
+ infix fun <T> Operand<*, T>.lt(op: Operand<*, T>): BooleanExpression = LessThanExpression(this, op)
+ infix fun <T> Operand<*, T>.le(op: Operand<*, T>): BooleanExpression = LessThanEqualsExpression(this, op)
+ infix fun <T> Operand<*, T>.gt(op: Operand<*, T>): BooleanExpression = op lt this
+ infix fun <T> Operand<*, T>.ge(op: Operand<*, T>): BooleanExpression = op le this
+ infix fun <T> Operand<*, T>.inList(list: ListExpression<*, T>): Clause = ListClause(this, list)
+ infix fun <T, R> TypedOperand<T, R>.inList(list: List<T>): Clause = this inList list(dbType, list)
+ fun <T, R> list(dbType: DBType<T, R>, vararg values: T): ListExpression<T, R> = list(dbType, values.toList())
+ fun <T, R> list(dbType: DBType<T, R>, values: List<T>): ListExpression<T, R> = ListExpression(values, dbType)
+ infix fun BooleanExpression.and(clause: BooleanExpression): BooleanExpression = ANDExpression(listOf(this, clause))
+ infix fun BooleanExpression.or(clause: BooleanExpression): BooleanExpression = ORExpression(listOf(this, clause))
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ColumnOperand.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ColumnOperand.kt
index 9150aa7..430d592 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ColumnOperand.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ColumnOperand.kt
@@ -1,9 +1,13 @@
package moe.nea.ledger.database.sql
import moe.nea.ledger.database.Column
+import moe.nea.ledger.database.DBType
import java.sql.PreparedStatement
-data class ColumnOperand<T>(val column: Column<T>) : Operand<T> {
+data class ColumnOperand<T, Raw>(val column: Column<T, Raw>) : TypedOperand<T, Raw> {
+ override val dbType: DBType<T, Raw>
+ get() = column.type
+
override fun asSql(): String {
return column.qualifiedSqlName
}
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/EqualsClause.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/EqualsClause.kt
index c6c482a..cfe72ef 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/EqualsClause.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/EqualsClause.kt
@@ -2,7 +2,7 @@ package moe.nea.ledger.database.sql
import java.sql.PreparedStatement
-data class EqualsClause(val left: Operand<*>, val right: Operand<*>) : Clause { // TODO: typecheck this somehow
+data class EqualsClause(val left: Operand<*, *>, val right: Operand<*, *>) : Clause {
override fun asSql(): String {
return left.asSql() + " = " + right.asSql()
}
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/IntoSelectable.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/IntoSelectable.kt
new file mode 100644
index 0000000..3775387
--- /dev/null
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/IntoSelectable.kt
@@ -0,0 +1,5 @@
+package moe.nea.ledger.database.sql
+
+interface IntoSelectable<T> {
+ fun asSelectable(): Selectable<T, *>
+} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/LessThanEqualsExpression.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/LessThanEqualsExpression.kt
new file mode 100644
index 0000000..4820c97
--- /dev/null
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/LessThanEqualsExpression.kt
@@ -0,0 +1,15 @@
+package moe.nea.ledger.database.sql
+
+import java.sql.PreparedStatement
+
+class LessThanEqualsExpression(val lhs: Operand<*, *>, val rhs: Operand<*, *>) :
+ BooleanExpression {
+ override fun asSql(): String {
+ return "${lhs.asSql()} <= ${rhs.asSql()}"
+ }
+
+ override fun appendToStatement(stmt: PreparedStatement, startIndex: Int): Int {
+ val next = lhs.appendToStatement(stmt, startIndex)
+ return rhs.appendToStatement(stmt, next)
+ }
+}
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/LessThanExpression.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/LessThanExpression.kt
new file mode 100644
index 0000000..4609ac1
--- /dev/null
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/LessThanExpression.kt
@@ -0,0 +1,15 @@
+package moe.nea.ledger.database.sql
+
+import java.sql.PreparedStatement
+
+class LessThanExpression(val lhs: Operand<*, *>, val rhs: Operand<*, *>) :
+ BooleanExpression {
+ override fun asSql(): String {
+ return "${lhs.asSql()} < ${rhs.asSql()}"
+ }
+
+ override fun appendToStatement(stmt: PreparedStatement, startIndex: Int): Int {
+ val next = lhs.appendToStatement(stmt, startIndex)
+ return rhs.appendToStatement(stmt, next)
+ }
+}
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/LikeClause.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/LikeClause.kt
index f84c5cc..1122329 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/LikeClause.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/LikeClause.kt
@@ -2,7 +2,7 @@ package moe.nea.ledger.database.sql
import java.sql.PreparedStatement
-data class LikeClause<T>(val left: Operand<T>, val right: StringOperand) : Clause {
+data class LikeClause<T>(val left: Operand<T, String>, val right: StringOperand) : Clause {
//TODO: check type safety with this one
override fun asSql(): String {
return "(" + left.asSql() + " LIKE " + right.asSql() + ")"
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ListClause.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ListClause.kt
new file mode 100644
index 0000000..d240472
--- /dev/null
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ListClause.kt
@@ -0,0 +1,8 @@
+package moe.nea.ledger.database.sql
+
+class ListClause<R>(
+ val lhs: Operand<*, R>,
+ val list: ListExpression<*, R>,
+) : Clause, SQLQueryComponent by SQLQueryComponent.composite(
+ lhs, SQLQueryComponent.standalone("IN"), list
+) \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ListExpression.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ListExpression.kt
new file mode 100644
index 0000000..e1522d0
--- /dev/null
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ListExpression.kt
@@ -0,0 +1,22 @@
+package moe.nea.ledger.database.sql
+
+import moe.nea.ledger.database.DBType
+import java.sql.PreparedStatement
+
+data class ListExpression<T, R>(
+ val elements: List<T>,
+ val dbType: DBType<T, R>
+) : Operand<List<T>, List<R>> {
+ override fun asSql(): String {
+ return elements.joinToString(prefix = "(", postfix = ")") { "?" }
+ }
+
+ override fun appendToStatement(stmt: PreparedStatement, startIndex: Int): Int {
+ var index = startIndex
+ for (element in elements) {
+ dbType.set(stmt, index, element)
+ index++
+ }
+ return index
+ }
+}
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Operand.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Operand.kt
index 9937414..b085103 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Operand.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Operand.kt
@@ -1,5 +1,10 @@
package moe.nea.ledger.database.sql
-interface Operand<T> : SQLQueryComponent {
+interface Operand<T,
+ /**
+ * The db sided type (or a rough equivalence).
+ * @see moe.nea.ledger.database.DBType Raw type parameter
+ */
+ Raw> : SQLQueryComponent {
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryComponent.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryComponent.kt
index 10b2be6..77d63d3 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryComponent.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryComponent.kt
@@ -11,6 +11,25 @@ interface SQLQueryComponent {
fun appendToStatement(stmt: PreparedStatement, startIndex: Int): Int
companion object {
+ fun composite(vararg elements: SQLQueryComponent): SQLQueryComponent {
+ return object : SQLQueryComponent {
+ override fun asSql(): String {
+ return elements.joinToString(" ") { it.asSql() }
+ }
+
+ override fun appendToStatement(stmt: PreparedStatement, startIndex: Int): Int {
+ var index = startIndex
+ for (element in elements) {
+ val lastIndex = index
+ index = element.appendToStatement(stmt, index)
+ require(lastIndex <= index) { "$element just tried to go back in time $index < $lastIndex" }
+ }
+ return index
+
+ }
+ }
+ }
+
fun standalone(sql: String): SQLQueryComponent {
return object : SQLQueryComponent {
override fun asSql(): String {
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryGenerator.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryGenerator.kt
index be81ff2..2eb54fd 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryGenerator.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryGenerator.kt
@@ -6,14 +6,14 @@ import java.sql.PreparedStatement
object SQLQueryGenerator {
fun List<SQLQueryComponent>.concatToFilledPreparedStatement(connection: Connection): PreparedStatement {
- var query = ""
+ val query = StringBuilder()
for (element in this) {
if (query.isNotEmpty()) {
- query += " "
+ query.append(" ")
}
- query += element.asSql()
+ query.append(element.asSql())
}
- val statement = connection.prepareAndLog(query)
+ val statement = connection.prepareAndLog(query.toString())
var index = 1
for (element in this) {
val nextIndex = element.appendToStatement(statement, index)
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Selectable.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Selectable.kt
new file mode 100644
index 0000000..8241a9d
--- /dev/null
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Selectable.kt
@@ -0,0 +1,17 @@
+package moe.nea.ledger.database.sql
+
+import moe.nea.ledger.database.Column
+import moe.nea.ledger.database.DBType
+
+/**
+ * Something that can be selected. Like a column, or an expression thereof
+ */
+interface Selectable<T, Raw> : SQLQueryComponent, IntoSelectable<T> {
+ override fun asSelectable(): Selectable<T, Raw> {
+ return this
+ }
+
+ val dbType: DBType<T, Raw>
+ fun guessColumn(): Column<T, *>?
+}
+
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/StringOperand.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/StringOperand.kt
index 24f4e78..b8d3690 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/StringOperand.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/StringOperand.kt
@@ -2,7 +2,10 @@ package moe.nea.ledger.database.sql
import java.sql.PreparedStatement
-data class StringOperand(val value: String) : Operand<String> {
+/**
+ * As opposed to just any [Operand<*, String>][Operand], this string operand represents a string operand that is part of the query, as opposed to potentially the state of a column.
+ */
+data class StringOperand(val value: String) : Operand<String, String> {
override fun asSql(): String {
return "?"
}
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/TypedOperand.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/TypedOperand.kt
new file mode 100644
index 0000000..8a1f723
--- /dev/null
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/TypedOperand.kt
@@ -0,0 +1,7 @@
+package moe.nea.ledger.database.sql
+
+import moe.nea.ledger.database.DBType
+
+interface TypedOperand<T, Raw> : Operand<T, Raw> {
+ val dbType: DBType<T, Raw>
+}
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ValuedOperand.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ValuedOperand.kt
new file mode 100644
index 0000000..714b4b5
--- /dev/null
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/ValuedOperand.kt
@@ -0,0 +1,15 @@
+package moe.nea.ledger.database.sql
+
+import moe.nea.ledger.database.DBType
+import java.sql.PreparedStatement
+
+class ValuedOperand<T, R>(val dbType: DBType<T, R>, val value: T) : Operand<T, R> {
+ override fun asSql(): String {
+ return "?"
+ }
+
+ override fun appendToStatement(stmt: PreparedStatement, startIndex: Int): Int {
+ dbType.set(stmt, startIndex, value)
+ return startIndex + 1
+ }
+}