-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(fieldParser): support to write native sql queries (#16)
* feat(fieldParser): support to write native sql queries * commit badge --------- Co-authored-by: Verissimo Ribeiro <[email protected]> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
- Loading branch information
1 parent
2803d85
commit 7fcd52c
Showing
29 changed files
with
1,501 additions
and
321 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
120 changes: 120 additions & 0 deletions
120
src/main/kotlin/io/github/verissimor/lib/fieldparser/FieldParser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package io.github.verissimor.lib.fieldparser | ||
|
||
import io.github.verissimor.lib.fieldparser.domain.CombineOperator | ||
import io.github.verissimor.lib.fieldparser.domain.CombineOperator.AND | ||
import io.github.verissimor.lib.fieldparser.domain.CombineOperator.OR | ||
import io.github.verissimor.lib.fieldparser.domain.FieldType | ||
import io.github.verissimor.lib.fieldparser.domain.FieldType.Companion.toFieldType | ||
import io.github.verissimor.lib.fieldparser.domain.FieldType.ENUMERATED | ||
import io.github.verissimor.lib.fieldparser.domain.FieldType.INSTANT | ||
import io.github.verissimor.lib.fieldparser.domain.FieldType.LOCAL_DATE | ||
import io.github.verissimor.lib.fieldparser.domain.FieldType.NUMBER | ||
import io.github.verissimor.lib.fieldparser.domain.FieldType.UUID | ||
import io.github.verissimor.lib.fieldparser.domain.FilterOperator | ||
import io.github.verissimor.lib.fieldparser.domain.ParsedField | ||
import io.github.verissimor.lib.fieldparser.domain.ValueParser.Companion.parseStringIntoList | ||
import org.slf4j.Logger | ||
import org.slf4j.LoggerFactory | ||
import java.lang.reflect.Field | ||
|
||
object FieldParser { | ||
|
||
private val log: Logger = LoggerFactory.getLogger(this::class.java) | ||
|
||
fun parseFields(params: Map<String, List<String>?>, clazz: Class<*>): List<ParsedField> { | ||
return params.mapNotNull { (field, value) -> | ||
|
||
val parsedField = parseField(field, value, clazz) | ||
parsedField.validate() | ||
|
||
if (parsedField.getStringOrNull() == null && !parsedField.filterOperator.allowNullableValue) { | ||
log.debug("Ignoring parameter $field - value is null (you can use _is_null)") | ||
return@mapNotNull null | ||
} | ||
|
||
if (parsedField.fieldClass == null) { | ||
log.debug("Ignoring parameter $field - field not found") | ||
return@mapNotNull null | ||
} | ||
|
||
parsedField | ||
} | ||
} | ||
|
||
private fun parseField(field: String, value: List<String>?, clazz: Class<*>): ParsedField { | ||
val group: Int = parseGroup(field) | ||
val combineOperator: CombineOperator = if (field.startsWith("or__")) OR else AND | ||
val normalized = normalize(field, group) | ||
|
||
val filterOperator = fieldToFilterOperator(normalized) | ||
val resolvedFieldName = resolveFieldName(normalized, filterOperator) | ||
val fieldClass: Field? = fieldToClass(resolvedFieldName, clazz) | ||
|
||
val resolvedOperator = overloadFilterOperator(filterOperator, value, fieldClass) | ||
|
||
return ParsedField(resolvedOperator, resolvedFieldName, fieldClass, value, group, combineOperator) | ||
} | ||
|
||
private fun normalize(field: String, group: Int) = field.trim() | ||
.replace("[]", "") // remove array format of a few js libraries | ||
.replace("__$group", "") // remove the group | ||
.let { if (it.startsWith("or__")) it.replace("or__", "") else it } // remove the or | ||
|
||
private fun fieldToFilterOperator(field: String): FilterOperator { | ||
val type = FilterOperator.values() | ||
.sortedByDescending { it.suffix.length } | ||
.firstOrNull { field.lowercase().endsWith(it.suffix) } ?: FilterOperator.EQUAL | ||
|
||
return type | ||
} | ||
|
||
private fun overloadFilterOperator( | ||
filterOperator: FilterOperator, | ||
value: List<String>?, | ||
fieldClass: Field? | ||
): FilterOperator { | ||
val shouldTryOverload = value != null && filterOperator == FilterOperator.EQUAL | ||
val fieldType: FieldType? = fieldClass.toFieldType() | ||
|
||
if (shouldTryOverload && value!!.size == 2 && (fieldType == LOCAL_DATE || fieldType == INSTANT)) { | ||
return FilterOperator.BETWEEN | ||
} | ||
|
||
if (shouldTryOverload && value!!.size > 1) { | ||
return FilterOperator.IN | ||
} | ||
|
||
val shouldTrySplitComma = value!!.size == 1 && listOf(ENUMERATED, NUMBER, LOCAL_DATE, UUID).contains(fieldType) | ||
if (shouldTryOverload && shouldTrySplitComma && value.firstOrNull()!!.contains(",")) { | ||
val list = parseStringIntoList(value.firstOrNull()!!) | ||
if (list?.isNotEmpty() == true) | ||
return FilterOperator.IN | ||
} | ||
|
||
return filterOperator | ||
} | ||
|
||
private fun resolveFieldName(field: String, type: FilterOperator?) = | ||
type?.let { field.replace(it.suffix, "") } ?: field | ||
|
||
private fun fieldToClass(field: String, root: Class<*>): Field? { | ||
var resultField: Field? = null | ||
field.split(".") | ||
.forEach { fieldP -> | ||
resultField = if (resultField == null) { | ||
root.declaredFields.firstOrNull { it.name == fieldP } | ||
} else { | ||
resultField?.type?.declaredFields?.firstOrNull { it.name == fieldP } | ||
} | ||
} | ||
|
||
return resultField | ||
} | ||
|
||
internal fun parseGroup(field: String): Int { | ||
val regex = "__(\\d+)$".toRegex() | ||
val matchResult = regex.find(field) | ||
|
||
return matchResult?.groupValues?.get(1)?.toIntOrNull() ?: 0 | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
src/main/kotlin/io/github/verissimor/lib/fieldparser/domain/CombineOperator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package io.github.verissimor.lib.fieldparser.domain | ||
|
||
enum class CombineOperator { | ||
AND, OR; | ||
} |
50 changes: 50 additions & 0 deletions
50
src/main/kotlin/io/github/verissimor/lib/fieldparser/domain/FieldType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package io.github.verissimor.lib.fieldparser.domain | ||
|
||
import io.github.verissimor.lib.fieldparser.domain.FilterOperator.BETWEEN | ||
import io.github.verissimor.lib.fieldparser.domain.FilterOperator.GREATER_THAN | ||
import io.github.verissimor.lib.fieldparser.domain.FilterOperator.GREATER_THAN_EQUAL | ||
import io.github.verissimor.lib.fieldparser.domain.FilterOperator.LESS_THAN | ||
import io.github.verissimor.lib.fieldparser.domain.FilterOperator.LESS_THAN_EQUAL | ||
import java.lang.reflect.Field | ||
import java.math.BigDecimal | ||
import java.time.Instant | ||
import java.time.LocalDate | ||
|
||
enum class FieldType { | ||
ENUMERATED, | ||
NUMBER, | ||
LOCAL_DATE, | ||
INSTANT, | ||
BOOLEAN, | ||
UUID, | ||
GENERIC; | ||
|
||
companion object { | ||
val comparisonOperators = listOf(GREATER_THAN, GREATER_THAN_EQUAL, LESS_THAN, LESS_THAN_EQUAL, BETWEEN) | ||
val comparisonTypes = listOf(NUMBER, LOCAL_DATE, INSTANT) | ||
|
||
fun Field?.toFieldType(): FieldType? = when { | ||
this == null -> null | ||
this.type?.superclass?.name == "java.lang.Enum" -> ENUMERATED | ||
this.type == LocalDate::class.java -> LOCAL_DATE | ||
this.type == Instant::class.java -> INSTANT | ||
this.type == Boolean::class.java || | ||
// this solves conflicts between kotlin/java | ||
this.type.name == "java.lang.Boolean" -> BOOLEAN | ||
|
||
this.type == java.util.UUID::class.java -> UUID | ||
|
||
this.type == Int::class.java || | ||
this.type == Long::class.java || | ||
this.type == BigDecimal::class.java || | ||
this.type.isAssignableFrom(Number::class.java) || | ||
// this solves conflicts between kotlin/java | ||
this.type.name == "java.lang.Integer" || | ||
this.type.name == "java.math.BigDecimal" || | ||
this.type.name == "java.lang.Long" || | ||
this.type.isAssignableFrom(java.lang.Number::class.java) -> NUMBER | ||
|
||
else -> GENERIC | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
src/main/kotlin/io/github/verissimor/lib/fieldparser/domain/ParsedField.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package io.github.verissimor.lib.fieldparser.domain | ||
|
||
import io.github.verissimor.lib.fieldparser.domain.FieldType.Companion.comparisonOperators | ||
import io.github.verissimor.lib.fieldparser.domain.FieldType.Companion.comparisonTypes | ||
import io.github.verissimor.lib.fieldparser.domain.FieldType.Companion.toFieldType | ||
import io.github.verissimor.lib.fieldparser.domain.FilterOperator.BETWEEN | ||
import java.lang.reflect.Field | ||
|
||
data class ParsedField( | ||
val filterOperator: FilterOperator, | ||
val resolvedFieldName: String, | ||
val fieldClass: Field?, | ||
val sourceValue: List<String>?, | ||
val group: Int, | ||
val combineOperator: CombineOperator, | ||
) : ValueParser(resolvedFieldName, sourceValue) { | ||
|
||
fun getFieldType(): FieldType? = fieldClass.toFieldType() | ||
|
||
fun validate() { | ||
if (comparisonOperators.contains(filterOperator) && !comparisonTypes.contains(getFieldType())) { | ||
error("The field $resolvedFieldName is not compatible with operator $filterOperator") | ||
} | ||
|
||
if (filterOperator == BETWEEN && getListStringOrNull()?.size != 2) { | ||
error("The field $resolvedFieldName uses the $filterOperator which requires 2 parameters $sourceValue") | ||
} | ||
} | ||
} |
Oops, something went wrong.