Skip to content

Commit

Permalink
Update README file ; add --quoted and --uppercase options
Browse files Browse the repository at this point in the history
  • Loading branch information
arkanovicz committed Jun 20, 2022
1 parent 21a2f75 commit d74687e
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 80 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ kddl [OPTIONS] > [output_file]

Options:
```
--input, -i -> mandatory; input file or JDBC URL (with credentials)
--format, -f -> mandatory; output format { Value should be one of [kddl, plantuml, postgresql] }
--driver, -d -> optional; jdbc driver, needed when input is a JDBC URL { String }
--help, -h -> Usage info
-i, --input -> mandatory; input file or JDBC URL (with credentials)
-f, --format -> mandatory; output format (value should be one of [kddl, plantuml, postgresql])
-d, --driver -> jdbc driver, needed when input is a JDBC URL (classname, must be present in the classpath)
-q, --quoted -> quoted identifiers
-u, --uppercase -> uppercase identifiers
-h, --help -> Usage info
```

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
}

group = "com.republicate.kddl"
version = "0.6.1"
version = "0.7"

repositories {
mavenCentral()
Expand Down
7 changes: 5 additions & 2 deletions src/commonMain/kotlin/com/republicate/kddl/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.republicate.kddl.plantuml.PlantUMLFormatter
import com.republicate.kddl.postgresql.PostgreSQLFormatter
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
import kotlinx.cli.required

val argParser = ArgParser("kddl")
Expand All @@ -21,6 +22,8 @@ fun main(args: Array<String>) {
val input by argParser.option(ArgType.String, shortName = "i", description = "input file or url").required()
val format by argParser.option(ArgType.Choice<Format>(), shortName = "f", description = "output format").required()
val driver by argParser.option(ArgType.String, shortName = "d", description = "jdbc driver")
val uppercase by argParser.option(ArgType.Boolean, shortName = "u", description = "uppercase identifiers").default(false)
val quoted by argParser.option(ArgType.Boolean, shortName = "q", description = "quoted identifiers").default(false)
argParser.parse(args)

val tree = when {
Expand All @@ -36,8 +39,8 @@ fun main(args: Array<String>) {
val formatter = when (format) {
Format.KDDL -> KDDLFormatter()
Format.PLANTUML -> PlantUMLFormatter()
Format.POSTGRESQL -> PostgreSQLFormatter()
Format.HYPERSQL -> HyperSQLFormatter()
Format.POSTGRESQL -> PostgreSQLFormatter(quoted=quoted, uppercase=uppercase)
Format.HYPERSQL -> HyperSQLFormatter(quoted=quoted, uppercase=uppercase)
else -> throw IllegalArgumentException("invalid format")
}
val ret = formatter.format(tree)
Expand Down
75 changes: 43 additions & 32 deletions src/commonMain/kotlin/com/republicate/kddl/SQLFormatter.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package com.republicate.kddl

abstract class SQLFormatter: Formatter {
import com.republicate.kddl.Formatter.Companion.EOL

abstract class SQLFormatter(val quoted: Boolean, val uppercase: Boolean): Formatter {

open val supportsEnums = false
open val supportsInheritance = false
open val scopedObjectNames = false
open fun defineEnum(field: ASTField) = ""
open fun defineInheritedView(table: ASTTable) = ""
open fun setSchema(schema: String) = "SET SCHEMA $schema$END"

val END = ";${Formatter.EOL}"
val Q = "\""
val END = ";${EOL}"
val Q = if (quoted) "\"" else ""
val upper = Regex("[A-Z]")
val transform: (String)->String = if (uppercase) {
{ camelToSnake(it).uppercase() }
} else {
{ camelToSnake(it) }
}

private val typeMap = mapOf(
"datetime" to "timestamp",
Expand All @@ -23,7 +31,7 @@ abstract class SQLFormatter: Formatter {
open fun mapType(type: String) = typeMap[type]

// CB TODO - make it configurable
fun camelToSnake(camel : String) : String {
private fun camelToSnake(camel : String) : String {
val ret = StringBuilder()
var pos = 0
upper.findAll(camel).forEach {
Expand All @@ -38,20 +46,20 @@ abstract class SQLFormatter: Formatter {
}

override fun format(asm: ASTDatabase, indent: String): String {
val ret = StringBuilder("-- database ${asm.name}${Formatter.EOL}")
val ret = StringBuilder("-- database ${asm.name}${EOL}")
// TODO postgresql options
ret.append(
asm.schemas.map {
format(it.value, indent)
}.joinToString(separator = Formatter.EOL)
}.joinToString(separator = EOL)
)
return ret.toString()
}

override fun format(asm: ASTSchema, indent: String): String {
val ret = StringBuilder()
val schemaName = camelToSnake(asm.name)
ret.append("${Formatter.EOL}-- schema $schemaName${Formatter.EOL}")
val schemaName = transform(asm.name)
ret.append("${EOL}-- schema $schemaName${EOL}")
ret.append("DROP SCHEMA IF EXISTS $schemaName CASCADE$END")
ret.append("CREATE SCHEMA $schemaName")
// incorrect
Expand All @@ -67,13 +75,13 @@ abstract class SQLFormatter: Formatter {
it.type.startsWith("enum(")
}.map {
defineEnum(it)
}.joinToString(separator = Formatter.EOL))
}.joinToString(separator = EOL))
}
ret.append(Formatter.EOL)
ret.append(EOL)
ret.append(
asm.tables.values.map {
format(it, indent)
}.joinToString(separator = Formatter.EOL)
}.joinToString(separator = EOL)
)
asm.tables.values.flatMap{ it.foreignKeys }/*.filter {
!it.isFieldLink()
Expand All @@ -92,15 +100,15 @@ abstract class SQLFormatter: Formatter {
field -> ASTField(tbl, field.name, field.type)
}.toSet()
val fk = ASTForeignKey(tbl, fkFields, parent, true, true, true)
ret.append(format(fk, indent)).append(Formatter.EOL)
ret.append(format(fk, indent)).append(EOL)
}
}
return ret.toString()
}

override fun format(asm: ASTTable, indent: String): String {
val ret = StringBuilder()
var tableName = camelToSnake(asm.name)
var tableName = transform(asm.name)
var viewName : String? = null;

if (asm.parent != null) {
Expand All @@ -113,50 +121,50 @@ abstract class SQLFormatter: Formatter {

for (field in asm.fields.values.filter { it.primaryKey }) {
if (firstField) firstField = false else ret.append(",")
ret.append(Formatter.EOL)
ret.append(EOL)
ret.append(format(field, " "))
}

// duplicate inherited primary key fields
if (asm.parent != null) {
for (field in asm.parent.getPrimaryKey()) {
if (firstField) firstField = false else ret.append(",")
ret.append(Formatter.EOL)
ret.append(EOL)
ret.append(format(field, " "))
}
}

for (field in asm.fields.values.filter { !it.primaryKey }) {
if (firstField) firstField = false else ret.append(",")
ret.append(Formatter.EOL)
ret.append(EOL)
ret.append(format(field, " "))
}

// DB TODO double inheritance is not handled
if (asm.children.isNotEmpty()) {
if (!supportsInheritance) throw Error("inheritance not supported")
if (firstField) firstField = false else ret.append(",")
ret.append(Formatter.EOL)
ret.append(EOL)
ret.append(" class varchar(30)")
}

if (asm.parent == null) {
val pkFields = asm.getPrimaryKey().map { camelToSnake(it.name) }.joinToString(",")
val pkFields = asm.getPrimaryKey().map { transform(it.name) }.joinToString(",")
if (pkFields.isNotEmpty()) {
if (firstField) firstField = false else ret.append(",")
ret.append(Formatter.EOL)
ret.append(EOL)
ret.append(" PRIMARY KEY ($pkFields)")
}
} else {
val pkFields = asm.parent.getPrimaryKey().map { camelToSnake(it.name) }.joinToString(",")
val pkFields = asm.parent.getPrimaryKey().map { transform(it.name) }.joinToString(",")
if (pkFields.isNotEmpty()) {
if (firstField) firstField = false else ret.append(",")
ret.append(Formatter.EOL)
ret.append(EOL)
ret.append(" PRIMARY KEY ($pkFields)")
}
}

ret.append("${Formatter.EOL})$END${Formatter.EOL}")
ret.append("${EOL})$END${EOL}")

if (asm.parent != null) {
ret.append(defineInheritedView(asm))
Expand All @@ -168,9 +176,9 @@ abstract class SQLFormatter: Formatter {
override fun format(asm: ASTField, indent: String): String {
val ret = StringBuilder(indent)
asm.apply {
ret.append(camelToSnake(name))
ret.append(transform(name))
if (type.isEmpty()) throw RuntimeException("Missing type for ${asm.table.schema.name}.${asm.table.name}.${asm.name}")
else if (type.startsWith("enum(")) ret.append(" enum_${camelToSnake(name)}")
else if (type.startsWith("enum(")) ret.append(" enum_${transform(name)}")
else ret.append(" ${mapType(type) ?: type}")
if (nonNull) ret.append(" NOT NULL")
// CB TODO - review 'unique' upstream calculation. A field should not be systematically
Expand All @@ -197,20 +205,23 @@ abstract class SQLFormatter: Formatter {
val src = asm.from
val ret = StringBuilder()
val srcName =
if (src.parent == null) "$Q${camelToSnake(src.name)}$Q"
else "${Q}base_${camelToSnake(src.name)}$Q"
if (src.parent == null) "$Q${transform(src.name)}$Q"
else "${Q}base_${transform(src.name)}$Q"
val fkName =
if (scopedObjectNames) "$Q${transform(asm.fields.first().name.removeSuffix(suffix))}$Q"
else "$Q${transform(src.name)}_${transform(asm.fields.first().name.removeSuffix(suffix))}_fk$Q"
ret.append("ALTER TABLE $srcName")
ret.append(" ADD CONSTRAINT $Q${camelToSnake(asm.fields.first().name.removeSuffix(suffix))}$Q")
ret.append(" FOREIGN KEY (${camelToSnake(asm.fields.map{it.name}.joinToString(","))})")
ret.append(" ADD CONSTRAINT $fkName")
ret.append(" FOREIGN KEY (${transform(asm.fields.map{it.name}.joinToString(","))})")
ret.append(" REFERENCES ")

if (asm.towards.schema.name != src.schema.name) {
ret.append("$Q${camelToSnake(asm.towards.schema.name)}$Q.")
ret.append("$Q${transform(asm.towards.schema.name)}$Q.")
}
val dstName =
if (asm.towards.parent == null) "$Q${camelToSnake(asm.towards.name)}$Q"
else "${Q}base_${camelToSnake(asm.towards.name)}$Q"
ret.append("$dstName (${camelToSnake(asm.towards.getOrCreatePrimaryKey().map{it.name}.joinToString(","))})")
if (asm.towards.parent == null) "$Q${transform(asm.towards.name)}$Q"
else "${Q}base_${transform(asm.towards.name)}$Q"
ret.append("$dstName (${transform(asm.towards.getOrCreatePrimaryKey().map{it.name}.joinToString(","))})")
if (asm.cascade) {
ret.append(" ON DELETE CASCADE")
}
Expand Down
2 changes: 1 addition & 1 deletion src/commonMain/kotlin/com/republicate/kddl/format.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class SemanticException(message: String? = null, cause: Throwable? = null) : Exc

interface Formatter {
companion object {
val EOL: String = "\n"
const val EOL: String = "\n"
}
fun format(asm: ASTDatabase, indent: String = ""): String
fun format(asm: ASTSchema, indent: String): String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,14 @@ package com.republicate.kddl.hypersql

import com.republicate.kddl.SQLFormatter

class HyperSQLFormatter: SQLFormatter() {
class HyperSQLFormatter(quoted: Boolean, uppercase: Boolean): SQLFormatter(quoted, uppercase) {

override fun mapType(type: String): String? {
return when (type) {
"serial" -> "integer generated by default as identity"
// "bigserial" TODO
else -> super.mapType(type)
}
}

}
Loading

0 comments on commit d74687e

Please sign in to comment.