diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..1bec35e
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml
new file mode 100644
index 0000000..1b2f6e3
--- /dev/null
+++ b/.idea/dbnavigator.xml
@@ -0,0 +1,409 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 08237f1..f16dea7 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index cecc3c0..d1453cd 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -12,7 +12,9 @@ repositories {
dependencies {
testImplementation(kotlin("test"))
implementation("org.xerial:sqlite-jdbc:3.40.1.0")
+ implementation ("mysql:mysql-connector-java:8.0.33")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.22")
+ implementation("org.postgresql:postgresql:42.6.0")
}
tasks.test {
diff --git a/orm.db b/orm.db
index f7f36c4..aab6b0b 100644
Binary files a/orm.db and b/orm.db differ
diff --git a/src/main/kotlin/ormapping/Main.kt b/src/main/kotlin/ormapping/Main.kt
index a636865..bba8689 100644
--- a/src/main/kotlin/ormapping/Main.kt
+++ b/src/main/kotlin/ormapping/Main.kt
@@ -1,11 +1,13 @@
package ormapping
-import ormapping.command.CommandExecutor
+import ormapping.command.*
import ormapping.connection.DatabaseConfig
import ormapping.connection.SQLiteConnection
import ormapping.entity.Entity
import ormapping.table.Table
+import ormapping.sql.*
+// Model danych
data class Employee(
var id: Int,
@@ -13,8 +15,20 @@ data class Employee(
) : Entity
object Employees : Table("employees", Employee::class) {
- var id = integer("id").primaryKey()
- var name = varchar("name", 255)
+ val id = integer("id").primaryKey()
+ val name = varchar("name", 255)
+}
+
+data class Department(
+ var id: Int,
+ var employee_id: Int,
+ var department_name: String,
+) : Entity
+
+object Departments : Table("departments", Department::class) {
+ val id = integer("id").primaryKey()
+ val employeeId = integer("employee_id")
+ val departmentName = varchar("department_name", 255)
}
fun main() {
@@ -23,58 +37,203 @@ fun main() {
)
val connection = SQLiteConnection.create(config)
val executor = CommandExecutor(connection)
-
- // 1. Tworzymy dwóch pracowników
- val employee1 = Employee(-1, "Jan Kowalski")
+
+ println("=== Test 1: Tworzenie tabel ===")
+ try {
+ val createEmployeesTable = executor.createTable()
+ .fromTable(Employees)
+
+
+ println("Wygenerowane polecenie SQL:")
+ println(createEmployeesTable.build())
+
+ val createDepartmentsTable = executor.createTable()
+ .fromTable(Departments)
+
+ println("Wygenerowane polecenie SQL:")
+ println(createDepartmentsTable.build())
+
+ executor.executeSQL(createEmployeesTable)
+ executor.executeSQL(createDepartmentsTable)
+
+ println("Tabele zostały utworzone.")
+ } catch (e: Exception) {
+ println("Błąd podczas tworzenia tabel: ${e.message}")
+ }
+
+ println("\n=== Test 2: Wstawianie danych ===")
+ val employee1 = Employee(1, "Jan Kowalski")
val employee2 = Employee(2, "Anna Nowak")
- println("Utworzeni pracownicy:")
- println("Employee 1: $employee1")
- println("Employee 2: $employee2")
- println()
-
- // 2. Zapisujemy ich do bazy
- executor.persist(Employees, employee1, employee2)
- println("Pracownicy zostali zapisani do bazy")
- println()
-
- // 3. Odczytujemy ich z bazy i wyświetlamy
- val found1 = executor.find(Employees, 1)
- val found2 = executor.find(Employees, 2)
- println("Odczytani z bazy pracownicy:")
- println("Found 1: $found1")
- println("Found 2: $found2")
- println()
-
- // 4. Usuwamy pierwszego pracownika
- executor.delete(Employees, 1)
- println("Usunięto pracownika 1")
- println()
-
- // 5. Modyfikujemy drugiego pracownika
- found2?.let {
- it.name = "Anna Kowalska" // zmiana nazwiska
- executor.update(Employees, it)
- println("Zmodyfikowano pracownika 2")
+ val department1 = Department(1, 1, "HR")
+ val department2 = Department(2, 2, "IT")
+
+ try {
+ executor.persist(Employees, employee1, employee2)
+ executor.persist(Departments, department1, department2)
+ println("Dane zostały wstawione.")
+ } catch (e: Exception) {
+ println("Błąd podczas wstawiania danych: ${e.message}")
+ }
+
+ println("\n=== Test 3: Sprawdzenie zapisanych danych przez SQL DSL (WHERE) ===")
+ try {
+ val selectBuilder = executor.createSelect()
+ .select("*")
+ .from(Employees)
+ .where("id IN (1, 2)")
+
+ val sql = selectBuilder.build()
+ println("Generated SQL Command:\n$sql")
+
+ val selectCommand = executor.executeSQL(selectBuilder) as SelectCommand
+ println("Wyniki zapytania SELECT:")
+ selectCommand.printResults()
+ } catch (e: Exception) {
+ println("Błąd podczas SELECT: ${e.message}")
+ }
+
+ println("\n=== Test 4: Zapytania JOIN ===")
+ try {
+ val selectBuilderLeftJoin = executor.createSelect()
+ .select(Employees.id, Employees.name, Departments.departmentName)
+ .from(Employees)
+ .leftJoin(Departments, Employees.id , Departments.employeeId)
+ .where("departments.department_name IS NOT NULL")
+
+ val sqlLeftJoin = selectBuilderLeftJoin.build()
+ println("Generated SQL (LEFT JOIN):\n$sqlLeftJoin")
+
+ val selectBuilderInnerJoin = executor.createSelect()
+ .select(Employees.id, Employees.name, Departments.departmentName)
+ .from(Employees)
+ .innerJoin(Departments, Employees.id, Departments.employeeId)
+ .where("departments.department_name = 'IT'")
+
+ val sqlInnerJoin = selectBuilderInnerJoin.build()
+ println("Generated SQL (INNER JOIN):\n$sqlInnerJoin")
+
+ } catch (e: Exception) {
+ println("Błąd przy testach JOIN: ${e.message}")
}
- println()
-
- // 6. Próbujemy odczytać obu pracowników (jeden powinien być null)
- val afterDelete1 = executor.find(Employees, 1)
- val afterUpdate2 = executor.find(Employees, 2)
- println("Stan po usunięciu/modyfikacji:")
- println("Employee 1 (powinien być null): $afterDelete1")
- println("Employee 2 (zmodyfikowany): $afterUpdate2")
- println()
-
- // 7. Usuwamy drugiego pracownika
- executor.delete(Employees, 2)
- println("Usunięto pracownika 2")
- println()
-
- // 8. Próbujemy odczytać obu pracowników (oba null)
- val final1 = executor.find(Employees, 1)
- val final2 = executor.find(Employees, 2)
- println("Stan końcowy (oba powinny być null):")
- println("Employee 1: $final1")
- println("Employee 2: $final2")
+
+ println("\n=== Test 5: GROUP BY, HAVING, ORDER BY ===")
+ try {
+ val selectGroupByHaving = executor.createSelect()
+ .select(Departments.departmentName, "COUNT(*) AS employee_count")
+ .from(Departments)
+ .groupBy(Departments.departmentName)
+ .having("employee_count > 1")
+
+ val sqlGroupByHaving = selectGroupByHaving.build()
+ println("Generated SQL (GROUP BY, HAVING):\n$sqlGroupByHaving")
+
+ val selectOrderBy = executor.createSelect()
+ .select(Employees.id, Employees.name)
+ .from(Employees)
+ .orderBy("name ASC", "id DESC")
+
+ val sqlOrderBy = selectOrderBy.build()
+ println("Generated SQL (ORDER BY):\n$sqlOrderBy")
+
+ } catch (e: Exception) {
+ println("Błąd przy testach GROUP BY / HAVING / ORDER BY: ${e.message}")
+ }
+
+ println("\n=== Test 6: UNION i UNION ALL ===")
+ try {
+ // Drugie zapytanie SELECT
+ val selectUnion = executor.createSelect()
+ .select(Employees.id, Employees.name)
+ .from(Employees)
+ .where("id > 2")
+ .build()
+
+ // Budowanie zapytania UNION
+ val unionBuilder = executor.createSelect()
+ .select(Employees.id, Employees.name)
+ .from(Employees)
+ .where("id <= 2")
+ .union(selectUnion) // Dodaj drugie zapytanie jako część UNION
+
+ // Generowanie finalnego SQL
+ val sqlUnion = unionBuilder.build()
+ println("Generated SQL (UNION, UNION ALL):\n$sqlUnion")
+
+ } catch (e: Exception) {
+ println("Błąd przy testach UNION: ${e.message}")
+ }
+ println("\n=== Test 7: Funkcje agregujące ===")
+ try {
+ val selectAggregate = executor.createSelect()
+ .select(
+ SelectBuilder().count("id") + " AS total_employees",
+ SelectBuilder().sum("id") + " AS total_id",
+ SelectBuilder().avg("id") + " AS avg_id"
+ )
+ .from(Employees)
+
+ val sqlAggregate = selectAggregate.build()
+ println("Generated SQL (Aggregate Functions):\n$sqlAggregate")
+
+ } catch (e: Exception) {
+ println("Błąd przy testach funkcji agregujących: ${e.message}")
+ }
+
+ println("\n=== Test 8: Podzapytania (Subqueries) ===")
+ try {
+ val subQuery = executor.createSelect()
+ .select("AVG(id)")
+ .from(Employees)
+ .build()
+
+ val selectSubQuery = executor.createSelect()
+ .select(Employees.id, Employees.name)
+ .from(Employees)
+ .where("id > ($subQuery)")
+
+ val sqlSubQuery = selectSubQuery.build()
+ println("Generated SQL (Subquery):\n$sqlSubQuery")
+
+ } catch (e: Exception) {
+ println("Błąd przy testach podzapytań: ${e.message}")
+ }
+
+ println("\n=== Test 9: Usuwanie danych ===")
+ try {
+ val deleteBuilder = executor.createDelete()
+ .from(Employees)
+ .where("id = 1")
+
+ val sqlDelete = deleteBuilder.build()
+ println("Generated SQL Command:\n$sqlDelete")
+
+ val deleteCommand = executor.executeSQL(deleteBuilder) as DeleteCommand
+ println("Usunięto rekordów: ${deleteCommand.getAffectedRows()}")
+ } catch (e: Exception) {
+ println("Błąd podczas usuwania danych: ${e.message}")
+ }
+
+ println("\n=== Test 10: Aktualizacja danych ===")
+ try {
+ val updatedEmployee = Employee(2, "Anna Kowalska")
+ executor.update(Employees, updatedEmployee)
+ println("Dane zostały zaktualizowane.")
+ } catch (e: Exception) {
+ println("Błąd podczas aktualizacji danych: ${e.message}")
+ }
+
+ println("\n=== Test 11: Usuwanie tabel ===")
+ try {
+ val dropEmployeesTable = executor.dropTable(Employees)
+ val dropDepartmentsTable = executor.dropTable(Departments)
+
+ executor.executeSQL(dropEmployeesTable)
+ executor.executeSQL(dropDepartmentsTable)
+
+ println("Tabele zostały usunięte.")
+ } catch (e: Exception) {
+ println("Błąd podczas usuwania tabel: ${e.message}")
+ }
+
+ connection.close()
}
diff --git a/src/main/kotlin/ormapping/command/Command.kt b/src/main/kotlin/ormapping/command/Command.kt
new file mode 100644
index 0000000..3274bf7
--- /dev/null
+++ b/src/main/kotlin/ormapping/command/Command.kt
@@ -0,0 +1,16 @@
+// Command.kt
+package ormapping.command
+
+import ormapping.connection.DatabaseConnection
+
+/**
+ * Bazowa klasa abstrakcyjna dla wzorca Command
+ * Wszystkie konkretne komendy muszą dziedziczyć po tej klasie
+ */
+abstract class Command {
+ /**
+ * Metoda wykonująca komendę
+ * @param connection Połączenie z bazą danych
+ */
+ abstract fun execute(connection: DatabaseConnection)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/ormapping/command/CommandExecutor.kt b/src/main/kotlin/ormapping/command/CommandExecutor.kt
index 5e5ba99..6fbb81e 100644
--- a/src/main/kotlin/ormapping/command/CommandExecutor.kt
+++ b/src/main/kotlin/ormapping/command/CommandExecutor.kt
@@ -1,6 +1,8 @@
package ormapping.command
+
import ormapping.connection.DatabaseConnection
+import ormapping.sql.*
import ormapping.entity.Entity
import ormapping.table.CascadeType
import ormapping.table.Relation
@@ -15,6 +17,27 @@ import kotlin.reflect.full.memberProperties
class CommandExecutor(
private val connection: DatabaseConnection,
) {
+ fun createSelect(): SelectBuilder = SelectBuilder()
+
+ fun createDelete(): DeleteBuilder = DeleteBuilder()
+
+ fun createTable(): CreateTableBuilder = CreateTableBuilder()
+
+ fun dropTable(table: Table<*>): DropTableBuilder {
+ return DropTableBuilder(connection.getDialect(), table, this)
+ }
+ // Metoda wykonująca zbudowane zapytanie
+ fun executeSQL(builder: SQLBuilder): SQLCommand {
+ val sql = builder.build()
+ return when (builder) {
+ is SelectBuilder -> SelectCommand(sql)
+ is DeleteBuilder -> DeleteCommand(sql)
+ is CreateTableBuilder -> CreateTableCommand(sql)
+ is DropTableBuilder -> DropTableCommand(sql)
+ else -> throw IllegalArgumentException("Unknown builder type")
+ }.also { it.execute(connection) }
+ }
+
fun find(table: Table, value: Any): T? {
val primaryKeyColumns = table.primaryKey
if (primaryKeyColumns.isEmpty()) {
diff --git a/src/main/kotlin/ormapping/command/SQLCommand.kt b/src/main/kotlin/ormapping/command/SQLCommand.kt
new file mode 100644
index 0000000..17fec1b
--- /dev/null
+++ b/src/main/kotlin/ormapping/command/SQLCommand.kt
@@ -0,0 +1,63 @@
+// SQLCommand.kt
+package ormapping.command
+
+import ormapping.connection.DatabaseConnection
+import java.sql.ResultSet
+
+abstract class SQLCommand(protected val sql: String) : Command() {
+ abstract override fun execute(connection: DatabaseConnection)
+}
+
+class SelectCommand(sql: String) : SQLCommand(sql) {
+ private lateinit var resultSet: ResultSet
+
+ override fun execute(connection: DatabaseConnection) {
+ resultSet = connection.getConnection().prepareStatement(sql).executeQuery()
+ }
+
+ fun getResults(): ResultSet = resultSet
+
+ fun printResults() {
+ while (resultSet.next()) {
+ val metaData = resultSet.metaData
+ val columnCount = metaData.columnCount
+
+ for (i in 1..columnCount) {
+ val columnName = metaData.getColumnName(i)
+ val value = resultSet.getString(i)
+ print("$columnName: $value | ")
+ }
+ println()
+ }
+ }
+}
+
+class DeleteCommand(sql: String) : SQLCommand(sql) {
+ private var affectedRows: Int = 0
+
+ override fun execute(connection: DatabaseConnection) {
+ affectedRows = connection.getConnection().prepareStatement(sql).executeUpdate()
+ }
+
+ fun getAffectedRows(): Int = affectedRows
+}
+
+class CreateTableCommand(sql: String) : SQLCommand(sql) {
+ private var success: Boolean = false
+
+ override fun execute(connection: DatabaseConnection) {
+ success = connection.getConnection().prepareStatement(sql).execute()
+ }
+
+ fun isSuccess(): Boolean = success
+}
+
+class DropTableCommand(sql: String) : SQLCommand(sql) {
+ private var success: Boolean = false
+
+ override fun execute(connection: DatabaseConnection) {
+ success = connection.getConnection().prepareStatement(sql).execute()
+ }
+
+ fun isSuccess(): Boolean = success
+}
\ No newline at end of file
diff --git a/src/main/kotlin/ormapping/sql/CreateTableBuilder.kt b/src/main/kotlin/ormapping/sql/CreateTableBuilder.kt
new file mode 100644
index 0000000..0d24b3b
--- /dev/null
+++ b/src/main/kotlin/ormapping/sql/CreateTableBuilder.kt
@@ -0,0 +1,131 @@
+package ormapping.sql
+
+import ormapping.table.Column
+import ormapping.table.Table
+import java.math.BigDecimal
+import java.time.LocalDate
+import kotlin.reflect.KClass
+
+/**
+ * Builder, który na podstawie struktury obiektu Table<*>
+ * generuje polecenie CREATE TABLE ... w SQL.
+ */
+class CreateTableBuilder : SQLBuilder {
+ private var tableName: String = ""
+ private val columns = mutableListOf()
+ private val constraints = mutableListOf()
+
+ /**
+ * Odczytuje metadane z obiektu [table] i tworzy definicje kolumn, kluczy obcych etc.
+ */
+ fun fromTable(table: Table<*>): CreateTableBuilder {
+ // Ustawiamy nazwę tabeli
+ tableName = table._name
+
+ // Dodajemy kolumny
+ table.columns.forEach { col ->
+ val sqlType = mapKClassToSQLType(col.type, col)
+ val colConstraints = buildColumnConstraints(col)
+ // Składamy definicję kolumny np. "id INTEGER PRIMARY KEY NOT NULL"
+ column(col.name, sqlType, *colConstraints.toTypedArray())
+ }
+
+ // Jeżeli mamy klucze obce, można je też tu przetwarzać i dodawać do constraints
+ table.foreignKeys.forEach { fk ->
+ // Dla uproszczenia: zakładamy, że jest tylko jedna kolumna w kluczu (lub bierzemy pierwszą).
+ val targetColumn = fk.targetColumns.firstOrNull() ?: return@forEach
+
+ // FOREIGN KEY (orders_customer_id) REFERENCES customers(id)
+ val referencingColumn = "${fk.targetTable}_$targetColumn"
+ val referenceDefinition = "${fk.targetTable}($targetColumn)"
+ foreignKey(referencingColumn, referenceDefinition)
+ }
+
+ return this
+ }
+
+ /**
+ * Ręczne ustawienie nazwy tabeli (jeśli chcemy).
+ */
+ fun name(name: String): CreateTableBuilder {
+ tableName = name
+ return this
+ }
+
+ /**
+ * Dodaje definicję kolumny w stylu:
+ * "id INTEGER PRIMARY KEY",
+ * "name VARCHAR(255) NOT NULL"
+ */
+ fun column(name: String, type: String, vararg modifiers: String): CreateTableBuilder {
+ val definition = listOf(type, *modifiers).joinToString(" ")
+ columns.add("$name $definition")
+ return this
+ }
+
+ /**
+ * Dodaje constraint PRIMARY KEY.
+ * Można go użyć do definicji wielokolumnowych kluczy głównych,
+ * np. primaryKey("col1", "col2").
+ */
+ fun primaryKey(vararg columns: String): CreateTableBuilder {
+ constraints.add("PRIMARY KEY (${columns.joinToString(", ")})")
+ return this
+ }
+
+ /**
+ * Dodaje constraint FOREIGN KEY (column) REFERENCES reference.
+ */
+ fun foreignKey(column: String, reference: String): CreateTableBuilder {
+ constraints.add("FOREIGN KEY ($column) REFERENCES $reference")
+ return this
+ }
+
+ /**
+ * Na końcu składamy instrukcję CREATE TABLE.
+ */
+ override fun build(): String = buildString {
+ append("CREATE TABLE $tableName (\n")
+ append(columns.joinToString(",\n"))
+ if (constraints.isNotEmpty()) {
+ append(",\n")
+ append(constraints.joinToString(",\n"))
+ }
+ append("\n)")
+ }
+
+ /**
+ * Mapa typów z `KClass` na konkretny typ SQL (dla prostych przypadków).
+ */
+ private fun mapKClassToSQLType(kClass: KClass<*>, column: Column<*>): String {
+ return when (kClass) {
+ Int::class -> "INTEGER"
+ String::class -> {
+ // jeżeli mamy ustawioną długość > 0, to generujemy VARCHAR, w przeciwnym razie TEXT
+ if (column.length > 0) {
+ "VARCHAR(${column.length})"
+ } else {
+ "TEXT"
+ }
+ }
+ Boolean::class -> "BOOLEAN"
+ BigDecimal::class -> {
+ // Używamy precision i scale w definicji np. DECIMAL(10,2)
+ "DECIMAL(${column.precision},${column.scale})"
+ }
+ LocalDate::class -> "DATE"
+ else -> "TEXT" // fallback, można rzucić wyjątek albo inaczej obsłużyć
+ }
+ }
+
+ /**
+ * Zbiera listę constraintów dla pojedynczej kolumny (PRIMARY KEY, NOT NULL, UNIQUE).
+ */
+ private fun buildColumnConstraints(column: Column<*>): List {
+ val constraints = mutableListOf()
+ if (column.primaryKey) constraints.add("PRIMARY KEY")
+ if (!column.nullable) constraints.add("NOT NULL")
+ if (column.unique) constraints.add("UNIQUE")
+ return constraints
+ }
+}
diff --git a/src/main/kotlin/ormapping/sql/DeleteBuilder.kt b/src/main/kotlin/ormapping/sql/DeleteBuilder.kt
new file mode 100644
index 0000000..69544f0
--- /dev/null
+++ b/src/main/kotlin/ormapping/sql/DeleteBuilder.kt
@@ -0,0 +1,91 @@
+package ormapping.sql
+
+import ormapping.table.Column
+import ormapping.table.Table
+
+/**
+ * Builder, który generuje instrukcję DELETE FROM ...
+ * z opcjonalnym WHERE oraz (w pewnych dialektach) CASCADE.
+ */
+class DeleteBuilder : SQLBuilder {
+ private var tableName: String = ""
+ private val conditions = mutableListOf()
+ private var cascade = false
+
+ /**
+ * Ustawia tabelę (na podstawie obiektu `Table<*>`),
+ * z której chcemy usuwać rekordy.
+ */
+ fun from(table: Table<*>): DeleteBuilder {
+ this.tableName = table._name
+ return this
+ }
+
+ /**
+ * Ustawia tabelę (za pomocą nazwy w postaci String),
+ * z której chcemy usuwać rekordy.
+ */
+ fun from(tableName: String): DeleteBuilder {
+ this.tableName = tableName
+ return this
+ }
+
+ /**
+ * Dodaje warunek do klauzuli WHERE (jako String).
+ * Można dodać wiele warunków, będą łączone klauzulą AND.
+ */
+ fun where(condition: String): DeleteBuilder {
+ conditions.add(condition)
+ return this
+ }
+
+ /**
+ * Przeciążona metoda `where`, pozwala budować
+ * proste warunki typu `id = 5` na podstawie kolumny z Table.
+ *
+ * Przykład użycia:
+ * .where(Employees.id, "=", 1)
+ */
+ fun where(column: Column<*>, operator: String, value: Any?): DeleteBuilder {
+ val condition = buildString {
+ append(column.name)
+ append(" ")
+ append(operator)
+ append(" ")
+ // Jeżeli wartość jest Stringiem, dodaj cudzysłowy
+ if (value is String) {
+ append("'$value'")
+ } else {
+ append(value)
+ }
+ }
+ conditions.add(condition)
+ return this
+ }
+
+ /**
+ * Ustawia kasowanie kaskadowe (zależne od dialektu SQL).
+ * W standardzie SQL CASCADE pojawia się głównie przy DROP TABLE
+ * albo przy usuwaniu rekordów, do których istnieją klucze obce
+ * zdefiniowane z CASCADE DELETE. Traktuj opcjonalnie.
+ */
+ fun cascade(): DeleteBuilder {
+ cascade = true
+ return this
+ }
+
+ /**
+ * Składa finalną komendę SQL, np:
+ * DELETE FROM employees WHERE id = 1 [CASCADE]
+ */
+ override fun build(): String = buildString {
+ append("DELETE FROM $tableName")
+ if (conditions.isNotEmpty()) {
+ append(" WHERE ")
+ append(conditions.joinToString(" AND "))
+ }
+ if (cascade) {
+ append(" CASCADE")
+ }
+ }
+}
diff --git a/src/main/kotlin/ormapping/sql/DropTableBuilder.kt b/src/main/kotlin/ormapping/sql/DropTableBuilder.kt
new file mode 100644
index 0000000..108adf4
--- /dev/null
+++ b/src/main/kotlin/ormapping/sql/DropTableBuilder.kt
@@ -0,0 +1,74 @@
+package ormapping.sql
+
+import ormapping.command.CommandExecutor
+import ormapping.connection.DatabaseConnection
+import ormapping.dialect.SQLDialect
+import ormapping.table.Table
+
+/**
+ * Builder, który generuje instrukcję:
+ * DROP TABLE [IF EXISTS] tableName [CASCADE]
+ */
+class DropTableBuilder(
+ private val dialect: SQLDialect,
+ table: Table<*>,
+ private val executor: CommandExecutor
+) : SQLBuilder {
+
+ private var tableName: String = table._name
+ private var ifExists = false
+ private var cascade = false
+
+ /**
+ * Ustawia tabelę na podstawie obiektu `Table<*>`.
+ */
+ fun fromTable(table: Table<*>): DropTableBuilder {
+ this.tableName = table._name
+ return this
+ }
+
+ /**
+ * Ustawia tabelę na podstawie nazwy w postaci String.
+ */
+ fun from(tableName: String): DropTableBuilder {
+ this.tableName = tableName
+ return this
+ }
+
+ /**
+ * Dodaje klauzulę IF EXISTS (jeśli dialekt ją wspiera).
+ * Przykład: DROP TABLE IF EXISTS ...
+ */
+ fun ifExists(): DropTableBuilder {
+ this.ifExists = true
+ return this
+ }
+
+ /**
+ * Dodaje klauzulę CASCADE (jeśli dialekt ją wspiera).
+ * Przykład: DROP TABLE ... CASCADE
+ */
+ fun cascade(): DropTableBuilder {
+ this.cascade = true
+ return this
+ }
+
+ /**
+ * Buduje finalne zapytanie w stylu:
+ * DROP TABLE [IF EXISTS] tableName [CASCADE]
+ */
+ override fun build(): String = buildString {
+ append("DROP TABLE ")
+ if (ifExists) {
+ // Dialekt np. PostgreSQL wspiera IF EXISTS,
+ // inne bazy mogą to ignorować lub rzucić błąd.
+ append("IF EXISTS ")
+ }
+ append(tableName)
+ if (cascade) {
+ // W PostgreSQL: "CASCADE";
+ // w innych dialektach może to nie działać lub działać inaczej.
+ append(" CASCADE")
+ }
+ }
+}
diff --git a/src/main/kotlin/ormapping/sql/SQLBuilder.kt b/src/main/kotlin/ormapping/sql/SQLBuilder.kt
new file mode 100644
index 0000000..5f76ed5
--- /dev/null
+++ b/src/main/kotlin/ormapping/sql/SQLBuilder.kt
@@ -0,0 +1,6 @@
+// SQLBuilder.kt
+package ormapping.sql
+
+interface SQLBuilder {
+ fun build(): String
+}
\ No newline at end of file
diff --git a/src/main/kotlin/ormapping/sql/SelectBuilder.kt b/src/main/kotlin/ormapping/sql/SelectBuilder.kt
new file mode 100644
index 0000000..8c900e0
--- /dev/null
+++ b/src/main/kotlin/ormapping/sql/SelectBuilder.kt
@@ -0,0 +1,154 @@
+package ormapping.sql
+
+import ormapping.table.Column
+import ormapping.table.Table
+
+class SelectBuilder : SQLBuilder {
+ private val columns = mutableListOf()
+ private val tables = mutableListOf()
+ private val joins = mutableListOf()
+ private val conditions = mutableListOf()
+ private val groupBy = mutableListOf()
+ private val orderBy = mutableListOf()
+ private var distinct = false
+ private var limit: Int? = null
+ private val having = mutableListOf()
+ private val unions = mutableListOf()
+ private val tableAliases = mutableMapOf() // Mapowanie nazwy kolumny -> aliasu tabeli
+
+ fun union(query: String): SelectBuilder {
+ unions.add("UNION $query")
+ return this
+ }
+
+ fun unionAll(query: String): SelectBuilder {
+ unions.add("UNION ALL $query")
+ return this
+ }
+
+ fun having(condition: String): SelectBuilder {
+ having.add(condition)
+ return this
+ }
+
+ fun select(vararg cols: Any): SelectBuilder {
+ columns.addAll(cols.map {
+ when (it) {
+ is Column<*> -> {
+ "${it.table._name}.${it.name}"
+ }
+ is String -> it
+ else -> throw IllegalArgumentException("Unsupported column type: $it")
+ }
+ })
+ return this
+ }
+
+ fun from(table: Table<*>, alias: String? = null): SelectBuilder {
+ val tableAlias = alias ?: table._name
+ tables.add("${table._name} ${alias.orEmpty()}".trim())
+ table.columns.forEach { column -> tableAliases[column.name] = tableAlias } // Rejestracja aliasów
+ return this
+ }
+
+ fun innerJoin(table: Table<*>, columnLeft: Column<*>, columnRight: Column<*>): SelectBuilder {
+ val joinStatement = "INNER JOIN ${table._name} ON ${columnLeft.table._name}.${columnLeft.name} = ${columnRight.table._name}.${columnRight.name}".trim()
+ joins.add(joinStatement)
+// table.columns.forEach { column -> tableAliases[column.name] = alias ?: table._name } // Rejestracja aliasów
+ table.columns.forEach { column ->
+ tableAliases[column.name] = table._name
+ }
+
+ return this
+ }
+
+ fun leftJoin(table: Table<*>, columnLeft: Column<*>, columnRight: Column<*>): SelectBuilder {
+ val joinStatement = "LEFT JOIN ${table._name} ON ${columnLeft.table._name}.${columnLeft.name} = ${columnRight.table._name}.${columnRight.name}".trim()
+ joins.add(joinStatement)
+// table.columns.forEach { column -> tableAliases[column.name] = alias ?: table._name } // Rejestracja aliasów
+ table.columns.forEach { column ->
+ tableAliases[column.name] = table._name
+ }
+
+ return this
+ }
+
+ fun where(vararg conditions: Any): SelectBuilder {
+ this.conditions.addAll(conditions.map {
+ when (it) {
+ is Column<*> -> {
+ val tableName = tableAliases[it.name] ?: throw IllegalArgumentException("Alias for column ${it.name} not set")
+ "$tableName.${it.name}"
+ }
+ is String -> it
+ else -> throw IllegalArgumentException("Unsupported condition type: $it")
+ }
+ })
+ return this
+ }
+
+ fun groupBy(vararg cols: Any): SelectBuilder {
+ groupBy.addAll(cols.map {
+ when (it) {
+ is Column<*> -> {
+ val tableName = tableAliases[it.name] ?: throw IllegalArgumentException("Alias for column ${it.name} not set")
+ "$tableName.${it.name}"
+ }
+ is String -> it
+ else -> throw IllegalArgumentException("Unsupported column type in GROUP BY: $it")
+ }
+ })
+ return this
+ }
+
+ fun orderBy(vararg cols: Any): SelectBuilder {
+ orderBy.addAll(cols.map {
+ when (it) {
+ is Column<*> -> {
+ val tableName = tableAliases[it.name] ?: throw IllegalArgumentException("Alias for column ${it.name} not set")
+ "$tableName.${it.name}"
+ }
+ is String -> it
+ else -> throw IllegalArgumentException("Unsupported column type in ORDER BY: $it")
+ }
+ })
+ return this
+ }
+
+ fun distinct(): SelectBuilder {
+ distinct = true
+ return this
+ }
+
+ fun limit(value: Int): SelectBuilder {
+ limit = value
+ return this
+ }
+
+ fun count(column: String = "*"): String = "COUNT($column)"
+ fun max(column: String): String = "MAX($column)"
+ fun min(column: String): String = "MIN($column)"
+ fun avg(column: String): String = "AVG($column)"
+ fun sum(column: String): String = "SUM($column)"
+
+ override fun build(): String {
+ if (tables.isEmpty()) throw IllegalStateException("FROM clause is required")
+ return buildString {
+ append("SELECT ")
+ if (distinct) append("DISTINCT ")
+ append(columns.joinToString(", ").ifEmpty { "*" })
+ append(" FROM ")
+ append(tables.joinToString(", "))
+ if (joins.isNotEmpty()) append(" ").append(joins.joinToString(" "))
+ if (conditions.isNotEmpty()) append(" WHERE ").append(conditions.joinToString(" AND "))
+ if (groupBy.isNotEmpty()) append(" GROUP BY ").append(groupBy.joinToString(", "))
+ if (having.isNotEmpty()) append(" HAVING ").append(having.joinToString(" AND "))
+ if (orderBy.isNotEmpty()) append(" ORDER BY ").append(orderBy.joinToString(", "))
+ limit?.let { append(" LIMIT $it") }
+ if (unions.isNotEmpty()) {
+ append(" ")
+ append(unions.joinToString(" "))
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/ormapping/table/Column.kt b/src/main/kotlin/ormapping/table/Column.kt
index a194cc9..0c9ef9d 100644
--- a/src/main/kotlin/ormapping/table/Column.kt
+++ b/src/main/kotlin/ormapping/table/Column.kt
@@ -10,8 +10,11 @@ class Column(
var primaryKey: Boolean = false,
var nullable: Boolean = false,
var unique: Boolean = false,
- private val length: Int = 0,
- private val scale: Int = 0,
- private val precision: Int = 0,
-)
+ val length: Int = 0,
+ val scale: Int = 0,
+ val precision: Int = 0,
+
+) {
+ lateinit var table: Table<*>
+}
diff --git a/src/main/kotlin/ormapping/table/Table.kt b/src/main/kotlin/ormapping/table/Table.kt
index 25714ea..f297f85 100644
--- a/src/main/kotlin/ormapping/table/Table.kt
+++ b/src/main/kotlin/ormapping/table/Table.kt
@@ -28,30 +28,40 @@ abstract class Table(
private val _primaryKey = mutableListOf>()
val primaryKey: List>
get() = _primaryKey.toList()
+
+ fun addForeignKey(fk: ForeignKey) {
+ _foreignKeys.add(fk)
+ }
fun integer(name: String): Column = Column(name, Int::class).also {
_columns.add(it)
+ it.table = this
}
fun varchar(name: String, length: Int): Column = Column(name, String::class, length = length).also {
_columns.add(it)
+ it.table = this
}
fun text(name: String): Column = Column(name, String::class).also {
_columns.add(it)
+ it.table = this
}
fun boolean(name: String): Column = Column(name, Boolean::class).also {
_columns.add(it)
+ it.table = this
}
fun date(name: String): Column = Column(name, LocalDate::class).also {
_columns.add(it)
+ it.table = this
}
fun decimal(name: String, precision: Int, scale: Int): Column =
Column(name, BigDecimal::class, precision = precision, scale = scale).also {
_columns.add(it)
+ it.table = this
}