[Editor] Enable displaying tables in Database pane
This commit is contained in:
@@ -0,0 +1,73 @@
|
|||||||
|
package com.bartlomiejpluta.base.editor.database.component
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.editor.database.model.SQLColumn
|
||||||
|
import com.bartlomiejpluta.base.editor.database.model.SQLElement
|
||||||
|
import com.bartlomiejpluta.base.editor.database.model.SQLTable
|
||||||
|
import javafx.scene.control.ContextMenu
|
||||||
|
import javafx.scene.control.cell.TextFieldTreeCell
|
||||||
|
import tornadofx.action
|
||||||
|
import tornadofx.item
|
||||||
|
|
||||||
|
class SQLElementCell(
|
||||||
|
renameElement: (element: SQLElement, name: String) -> SQLElement,
|
||||||
|
deleteElement: (element: SQLElement) -> Unit
|
||||||
|
) : TextFieldTreeCell<SQLElement>() {
|
||||||
|
private val tableMenu = ContextMenu().apply {
|
||||||
|
item("Rename") {
|
||||||
|
action {
|
||||||
|
treeView.isEditable = true
|
||||||
|
startEdit()
|
||||||
|
treeView.isEditable = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item("Delete") {
|
||||||
|
action {
|
||||||
|
deleteElement(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val columnMenu = ContextMenu().apply {
|
||||||
|
item("Rename") {
|
||||||
|
action {
|
||||||
|
treeView.isEditable = true
|
||||||
|
startEdit()
|
||||||
|
treeView.isEditable = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item("Delete") {
|
||||||
|
action {
|
||||||
|
deleteElement(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
converter = SQLElementStringConverter(this, renameElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateItem(item: SQLElement?, empty: Boolean) {
|
||||||
|
super.updateItem(item, empty)
|
||||||
|
|
||||||
|
if (empty || item == null) {
|
||||||
|
text = null
|
||||||
|
graphic = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
text = when (item) {
|
||||||
|
is SQLColumn -> "${item.name}${if (item.nullable) "?" else ""}"
|
||||||
|
else -> item.name
|
||||||
|
}
|
||||||
|
|
||||||
|
graphic = item.icon
|
||||||
|
|
||||||
|
contextMenu = when (item) {
|
||||||
|
is SQLTable -> tableMenu
|
||||||
|
is SQLColumn -> columnMenu
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.bartlomiejpluta.base.editor.database.component
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.editor.database.model.SQLElement
|
||||||
|
import javafx.scene.control.TreeCell
|
||||||
|
import javafx.util.StringConverter
|
||||||
|
|
||||||
|
class SQLElementStringConverter(
|
||||||
|
private val cell: TreeCell<SQLElement>,
|
||||||
|
private val rename: (item: SQLElement, newName: String) -> SQLElement
|
||||||
|
) : StringConverter<SQLElement>() {
|
||||||
|
override fun toString(item: SQLElement?): String = item?.name ?: ""
|
||||||
|
|
||||||
|
// Disclaimer:
|
||||||
|
// This is actually the only place where we have an access to both actual element
|
||||||
|
// with untouched name and new element name so that's why we should call `rename()` function right here.
|
||||||
|
override fun fromString(string: String?) = string?.let { rename(cell.item, it) }
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package com.bartlomiejpluta.base.editor.database.model
|
|
||||||
|
|
||||||
class Column(val table: Table, val name: String, val type: ColumnType, val nullable: Boolean, val primary: Boolean)
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.bartlomiejpluta.base.editor.database.model
|
package com.bartlomiejpluta.base.editor.database.model
|
||||||
|
|
||||||
enum class ColumnType {
|
enum class ColumnType {
|
||||||
INT,
|
INTEGER,
|
||||||
BOOLEAN,
|
BOOLEAN,
|
||||||
TINYINT,
|
TINYINT,
|
||||||
SMALLINT,
|
SMALLINT,
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.bartlomiejpluta.base.editor.database.model
|
||||||
|
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon
|
||||||
|
import java.sql.Connection
|
||||||
|
|
||||||
|
class SQLColumn(
|
||||||
|
val table: SQLTable,
|
||||||
|
name: String,
|
||||||
|
val type: ColumnType,
|
||||||
|
val nullable: Boolean,
|
||||||
|
val primary: Boolean
|
||||||
|
) : SQLElement {
|
||||||
|
override var name: String = name
|
||||||
|
private set(value) {
|
||||||
|
field = value.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rename(connection: Connection, newName: String) {
|
||||||
|
connection.prepareStatement("ALTER TABLE ${table.name} ALTER COLUMN $name RENAME TO $newName").execute()
|
||||||
|
name = newName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(connection: Connection) {
|
||||||
|
connection.prepareStatement("ALTER TABLE ${table.name} DROP COLUMN $name").execute()
|
||||||
|
table.columns.remove(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val icon = when (type) {
|
||||||
|
ColumnType.INTEGER -> FontIcon("fa-hashtag")
|
||||||
|
ColumnType.BOOLEAN -> FontIcon("fa-toggle-on")
|
||||||
|
ColumnType.TINYINT -> FontIcon("fa-hashtag")
|
||||||
|
ColumnType.SMALLINT -> FontIcon("fa-hashtag")
|
||||||
|
ColumnType.BIGINT -> FontIcon("fa-hashtag")
|
||||||
|
ColumnType.DECIMAL -> FontIcon("fa-usd")
|
||||||
|
ColumnType.DOUBLE -> FontIcon("fa-hashtag")
|
||||||
|
ColumnType.REAL -> FontIcon("fa-hashtag")
|
||||||
|
ColumnType.TIME -> FontIcon("fa-clock")
|
||||||
|
ColumnType.TIME_WITH_TIME_ZONE -> FontIcon("fa-clock")
|
||||||
|
ColumnType.DATE -> FontIcon("fa-calendar")
|
||||||
|
ColumnType.TIMESTAMP -> FontIcon("fa-calendar")
|
||||||
|
ColumnType.TIMESTAMP_WITH_TIME_ZONE -> FontIcon("fa-calendar")
|
||||||
|
ColumnType.VARCHAR -> FontIcon("fa-font")
|
||||||
|
ColumnType.VARCHAR_IGNORECASE -> FontIcon("fa-font")
|
||||||
|
ColumnType.CHAR -> FontIcon("fa-header")
|
||||||
|
ColumnType.ARRAY -> FontIcon("fa-list-ol")
|
||||||
|
ColumnType.GEOMETRY -> FontIcon("fa-globe")
|
||||||
|
ColumnType.JSON -> FontIcon("fa-code")
|
||||||
|
else -> FontIcon("fa-cube")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.bartlomiejpluta.base.editor.database.model
|
||||||
|
|
||||||
|
import javafx.scene.Node
|
||||||
|
import tornadofx.observableListOf
|
||||||
|
import java.sql.Connection
|
||||||
|
|
||||||
|
class SQLDatabase : SQLElement {
|
||||||
|
override val name = "Database"
|
||||||
|
val tables = observableListOf<SQLTable>()
|
||||||
|
|
||||||
|
fun addTable(name: String) {
|
||||||
|
tables.add(SQLTable(this, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rename(connection: Connection, newName: String) {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(connection: Connection) {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val icon: Node? = null
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.bartlomiejpluta.base.editor.database.model
|
||||||
|
|
||||||
|
import javafx.scene.Node
|
||||||
|
import java.sql.Connection
|
||||||
|
|
||||||
|
interface SQLElement {
|
||||||
|
val name: String
|
||||||
|
|
||||||
|
fun rename(connection: Connection, newName: String)
|
||||||
|
|
||||||
|
fun delete(connection: Connection)
|
||||||
|
|
||||||
|
val icon: Node?
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.bartlomiejpluta.base.editor.database.model
|
||||||
|
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon
|
||||||
|
import tornadofx.observableListOf
|
||||||
|
import java.sql.Connection
|
||||||
|
|
||||||
|
class SQLTable(val database: SQLDatabase, name: String) : SQLElement {
|
||||||
|
override var name: String = name
|
||||||
|
private set(value) {
|
||||||
|
field = value.toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
val columns = observableListOf<SQLColumn>()
|
||||||
|
|
||||||
|
fun addColumn(name: String, type: ColumnType, nullable: Boolean, primary: Boolean) {
|
||||||
|
val column = SQLColumn(this, name, type, nullable, primary)
|
||||||
|
columns += column
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun rename(connection: Connection, newName: String) {
|
||||||
|
connection.prepareStatement("ALTER TABLE $name RENAME TO $newName").execute()
|
||||||
|
name = newName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun delete(connection: Connection) {
|
||||||
|
connection.prepareStatement("DROP TABLE $name;").execute()
|
||||||
|
database.tables.remove(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val icon = FontIcon("fa-table")
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package com.bartlomiejpluta.base.editor.database.model
|
|
||||||
|
|
||||||
class Table(val name: String) {
|
|
||||||
private val mutableColumns = mutableListOf<Column>()
|
|
||||||
val columns: List<Column>
|
|
||||||
get() = mutableColumns
|
|
||||||
|
|
||||||
fun addColumn(name: String, type: ColumnType, nullable: Boolean, primary: Boolean) {
|
|
||||||
val column = Column(this, name, type, nullable, primary)
|
|
||||||
mutableColumns += column
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.bartlomiejpluta.base.editor.database.service
|
package com.bartlomiejpluta.base.editor.database.service
|
||||||
|
|
||||||
import com.bartlomiejpluta.base.editor.database.model.Table
|
import com.bartlomiejpluta.base.editor.database.model.SQLDatabase
|
||||||
|
import java.sql.Connection
|
||||||
|
|
||||||
interface DatabaseService {
|
interface DatabaseService {
|
||||||
val tables: List<Table>
|
val database: SQLDatabase
|
||||||
|
fun run(op: Connection.() -> Unit)
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.bartlomiejpluta.base.editor.database.service
|
package com.bartlomiejpluta.base.editor.database.service
|
||||||
|
|
||||||
import com.bartlomiejpluta.base.editor.database.model.ColumnType
|
import com.bartlomiejpluta.base.editor.database.model.ColumnType
|
||||||
import com.bartlomiejpluta.base.editor.database.model.Table
|
import com.bartlomiejpluta.base.editor.database.model.SQLDatabase
|
||||||
import com.bartlomiejpluta.base.editor.project.context.ProjectContext
|
import com.bartlomiejpluta.base.editor.project.context.ProjectContext
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -9,26 +9,21 @@ import java.sql.Connection
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
class H2DatabaseService : DatabaseService {
|
class H2DatabaseService : DatabaseService {
|
||||||
|
override val database: SQLDatabase
|
||||||
@Autowired
|
|
||||||
private lateinit var projectContext: ProjectContext
|
|
||||||
|
|
||||||
override val tables: List<Table>
|
|
||||||
get() {
|
get() {
|
||||||
val tables = connection {
|
val db = SQLDatabase()
|
||||||
val tables = mutableListOf<Table>()
|
|
||||||
|
run {
|
||||||
val results = prepareStatement("SHOW TABLES").executeQuery()
|
val results = prepareStatement("SHOW TABLES").executeQuery()
|
||||||
while (results.next()) {
|
while (results.next()) {
|
||||||
if (results.getString("TABLE_SCHEMA") == "PUBLIC") {
|
if (results.getString("TABLE_SCHEMA") == "PUBLIC") {
|
||||||
tables.add(Table(results.getString("TABLE_NAME")))
|
db.addTable(results.getString("TABLE_NAME"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tables
|
db.tables.forEach { table ->
|
||||||
} ?: emptyList()
|
run {
|
||||||
|
|
||||||
tables.forEach { table ->
|
|
||||||
connection {
|
|
||||||
val results = prepareStatement("SHOW COLUMNS FROM ${table.name}").executeQuery()
|
val results = prepareStatement("SHOW COLUMNS FROM ${table.name}").executeQuery()
|
||||||
while (results.next()) {
|
while (results.next()) {
|
||||||
table.addColumn(
|
table.addColumn(
|
||||||
@@ -41,11 +36,15 @@ class H2DatabaseService : DatabaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tables
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified T> connection(op: Connection.() -> T) =
|
@Autowired
|
||||||
|
private lateinit var projectContext: ProjectContext
|
||||||
|
|
||||||
|
override fun run(op: Connection.() -> Unit) {
|
||||||
projectContext.project?.database?.connection?.use(op)
|
projectContext.project?.database?.connection?.use(op)
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseType(type: String) = ColumnType.valueOf(type.replace(" ", "_").substringBefore("("))
|
private fun parseType(type: String) = ColumnType.valueOf(type.replace(" ", "_").substringBefore("("))
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package com.bartlomiejpluta.base.editor.database.view.list
|
||||||
|
|
||||||
|
import com.bartlomiejpluta.base.editor.database.component.SQLElementCell
|
||||||
|
import com.bartlomiejpluta.base.editor.database.model.SQLDatabase
|
||||||
|
import com.bartlomiejpluta.base.editor.database.model.SQLElement
|
||||||
|
import com.bartlomiejpluta.base.editor.database.model.SQLTable
|
||||||
|
import com.bartlomiejpluta.base.editor.database.service.DatabaseService
|
||||||
|
import com.bartlomiejpluta.base.editor.file.model.InMemoryStringFileNode
|
||||||
|
import com.bartlomiejpluta.base.editor.main.controller.MainController
|
||||||
|
import com.bartlomiejpluta.base.editor.project.context.ProjectContext
|
||||||
|
import javafx.scene.control.TreeItem
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon
|
||||||
|
import tornadofx.*
|
||||||
|
import java.sql.Connection
|
||||||
|
|
||||||
|
class TablesListView : View() {
|
||||||
|
private val mainController: MainController by di()
|
||||||
|
private val projectContext: ProjectContext by di()
|
||||||
|
private val databaseService: DatabaseService by di()
|
||||||
|
|
||||||
|
private var database: SQLDatabase? = null
|
||||||
|
|
||||||
|
private val treeView = treeview<SQLElement> {
|
||||||
|
isShowRoot = false
|
||||||
|
|
||||||
|
setCellFactory {
|
||||||
|
SQLElementCell(this@TablesListView::renameElement, this@TablesListView::deleteElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
projectContext.projectProperty.addListener { _, _, project ->
|
||||||
|
project?.let { refresh() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val root = vbox {
|
||||||
|
toolbar {
|
||||||
|
button("SQL Script", graphic = FontIcon("fa-code")) {
|
||||||
|
action {
|
||||||
|
mainController.openScript(
|
||||||
|
fsNode = InMemoryStringFileNode("script", "java", "SELECT * FROM myTable;"),
|
||||||
|
execute = { code -> databaseService.run { executeScript(code, this) } },
|
||||||
|
saveable = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this += treeView
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refresh() {
|
||||||
|
databaseService.database.let {
|
||||||
|
treeView.root = TreeItem(it)
|
||||||
|
database = it
|
||||||
|
}
|
||||||
|
|
||||||
|
treeView.populate {
|
||||||
|
when (val value = it.value) {
|
||||||
|
is SQLDatabase -> value.tables
|
||||||
|
is SQLTable -> value.columns
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
treeView.root.expandTo(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun executeScript(sql: String, conn: Connection) {
|
||||||
|
val stmt = conn.prepareStatement(sql)
|
||||||
|
stmt.execute()
|
||||||
|
val rs = stmt.resultSet
|
||||||
|
val meta = stmt.metaData
|
||||||
|
|
||||||
|
if (meta != null) {
|
||||||
|
for (i in 1..meta.columnCount) {
|
||||||
|
print(meta.getColumnLabel(i))
|
||||||
|
print(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rs != null) {
|
||||||
|
while (rs.next()) {
|
||||||
|
for (i in 1..meta.columnCount) {
|
||||||
|
print(rs.getObject(i))
|
||||||
|
print(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renameElement(element: SQLElement, newName: String): SQLElement {
|
||||||
|
databaseService.run { element.rename(this, newName) }
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteElement(element: SQLElement) {
|
||||||
|
databaseService.run(element::delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user