Skip to content

Commit

Permalink
CASL-561 placeholders instead of inlined query values
Browse files Browse the repository at this point in the history
Signed-off-by: Jakub Amanowicz <[email protected]>
  • Loading branch information
Amaneusz committed Oct 25, 2024
1 parent c971dee commit a86d3fd
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class TagNormalizer private constructor() {
val i = normalizedTag.indexOf('=')
val key: String
val value: Any?
if (i > 1) {
if (i >= 1) {
if (normalizedTag[i - 1] == ':') { // :=
key = normalizedTag.substring(0, i - 1).trim()
val raw = normalizedTag.substring(i + 1).trim()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import kotlin.test.assertEquals
class TagNormalizerTest {

@Test
fun shouldRemoveNonAscii(){
fun shouldRemoveNonAscii() {
val tagsToBeClearedFromAscii = mapOf(
"p®¡©e=100£" to "pe=100", // regular tag
"~twice_¼=single_½" to "~twice_=single_", // starting with '~'
Expand All @@ -19,7 +19,7 @@ class TagNormalizerTest {
}

@Test
fun shouldLeaveNonAsciiAsIs(){
fun shouldLeaveNonAsciiAsIs() {
val tagsWithAsciiToBePreserved = listOf(
"@p®¡©e=100£", // starting with '@'
"ref_p®¡©e=100£", // starting with 'ref_'
Expand All @@ -32,13 +32,13 @@ class TagNormalizerTest {
}

@Test
fun shouldLowercase(){
fun shouldLowercase() {
val tag = "Some_Tag:=1235"
assertEquals(tag.lowercase(), TagNormalizer.normalizeTag(tag))
}

@Test
fun shouldNotLowercase(){
fun shouldNotLowercase() {
val tagsNotToBeLowercased = listOf(
"@Some_Tag:=1235",
"ref_Some_Tag:=1235",
Expand All @@ -53,7 +53,7 @@ class TagNormalizerTest {
}

@Test
fun shouldSplit(){
fun shouldSplit() {
val tagsToBeSplit = listOf(
"@some_tag:=1235",
"~some_tag:=1235",
Expand All @@ -72,7 +72,7 @@ class TagNormalizerTest {
}

@Test
fun shouldNotSplit(){
fun shouldNotSplit() {
val tagsNotToBeSplit = listOf(
"ref_some_tag:=1235",
"sourceIDsome_tag:=1235"
Expand All @@ -86,4 +86,20 @@ class TagNormalizerTest {
assertEquals(null, tagValue)
}
}
}

@Test
fun shouldSplitSingleCharTags() {
// Given
val tagToBeSplit = "a=b"

// When:
val normalized = TagNormalizer.normalizeTag(tagToBeSplit)

// And:
val (key, value) = TagNormalizer.splitNormalizedTag(normalized)

// Then
assertEquals("a", key)
assertEquals("b", value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ class PgType : JsEnum() {
@JvmField
@JsStatic
val BYTE_ARRAY = defIgnoreCase(PgType::class, "bytea")

@JvmField
@JsStatic
val JSON_PATH = defIgnoreCase(PgType::class, "jsonpath")
}

@Suppress("NON_EXPORTABLE_TYPE")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class WhereClauseBuilder(private val request: ReadFeatures) {

private fun whereMetadata() {
val metaQuery = request.query.metadata
if(metaQuery != null){
if (metaQuery != null) {
if (where.isNotEmpty()) {
where.append(" AND (")
} else {
Expand Down Expand Up @@ -165,7 +165,7 @@ class WhereClauseBuilder(private val request: ReadFeatures) {

private fun whereTags() {
val tagQuery = request.query.tags
if(tagQuery != null){
if (tagQuery != null) {
if (where.isNotEmpty()) {
where.append(" AND (")
} else {
Expand All @@ -188,49 +188,51 @@ class WhereClauseBuilder(private val request: ReadFeatures) {
private fun resolveSingleTagQuery(tagQuery: TagQuery) {
when (tagQuery) {
is TagExists -> {
where.append("$tagsAsJsonb ?? '${tagQuery.name}'")
val tagNamePlaceholder = placeholderForArg(tagQuery.name, PgType.STRING)
where.append("$tagsAsJsonb ?? $tagNamePlaceholder")
}

is TagValueIsNull -> {
where.append("${tagValue(tagQuery)} = null")
val tagValuePlaceholder = placeholderForArg(selectTagValue(tagQuery), PgType.STRING)
where.append("$tagValuePlaceholder IS NULL")
}

is TagValueIsBool -> {
if (tagQuery.value) {
where.append(tagValue(tagQuery, PgType.BOOLEAN))
where.append(selectTagValue(tagQuery, PgType.BOOLEAN))
} else {
where.append("not(${tagValue(tagQuery, PgType.BOOLEAN)}})")
where.append("not(${selectTagValue(tagQuery, PgType.BOOLEAN)})")
}
}

is TagValueIsDouble -> {
val valuePlaceholder = placeholderForArg(tagQuery.value, PgType.DOUBLE)
val queryValuePlaceholder = placeholderForArg(tagQuery.value, PgType.DOUBLE)
val doubleOp = resolveDoubleOp(
tagQuery.op,
tagValue(tagQuery, PgType.DOUBLE),
valuePlaceholder
selectTagValue(tagQuery, PgType.DOUBLE),
queryValuePlaceholder
)
where.append(doubleOp)
}

is TagValueIsString -> {
val valuePlaceholder = placeholderForArg(tagQuery.value, PgType.STRING)
val queryValuePlaceholder = placeholderForArg(tagQuery.value, PgType.STRING)
val stringEquals = resolveStringOp(
StringOp.EQUALS,
tagValue(tagQuery, PgType.STRING),
valuePlaceholder
selectTagValue(tagQuery, PgType.STRING),
queryValuePlaceholder
)
where.append(stringEquals)
}

is TagValueMatches -> {
val regex = tagQuery.regex
where.append("$tagsAsJsonb @?? '\$.${tagQuery.name} ? (@ like_regex \"${regex}\")'")
val jsonPathPlaceholder = placeholderForArg("\$.${tagQuery.name} ? (@ like_regex \"${tagQuery.regex}\")", PgType.STRING)
where.append("$tagsAsJsonb @?? $jsonPathPlaceholder::jsonpath")
}
}
}

private fun tagValue(tagQuery: TagQuery, castTo: PgType? = null): String {
private fun selectTagValue(tagQuery: TagQuery, castTo: PgType? = null): String {
return when (castTo) {
null -> "$tagsAsJsonb->'${tagQuery.name}'"
PgType.STRING -> "$tagsAsJsonb->>'${tagQuery.name}'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,27 @@ class ReadFeaturesByTagsTest : PgTestBase(NakshaCollection("read_by_tags_test"))
assertEquals(inputFeature.id, featuresWithFooTag[0]!!.id)
}

@Test
fun shouldReturnFeaturesByBoolean(){
// Given:
val enabledFeatureA = randomFeatureWithTags("flag:=true")
val enabledFeatureB = randomFeatureWithTags("flag:=true")
val disabledFeature = randomFeatureWithTags("flag:=false")

// When:
insertFeatures(enabledFeatureA, enabledFeatureB, disabledFeature)

// And:
val enabledFeatures = executeTagsQuery(
TagValueIsBool(name = "flag", value = true)
).features

// Then:
assertEquals(2, enabledFeatures.size)
val fetchedIds = enabledFeatures.map { it!!.id }
assertTrue(fetchedIds.containsAll(listOf(enabledFeatureA.id, enabledFeatureB.id)))
}

@Test
fun shouldReturnFeaturesByTagRegex() {
// Given:
Expand Down Expand Up @@ -149,6 +170,13 @@ class ReadFeaturesByTagsTest : PgTestBase(NakshaCollection("read_by_tags_test"))
// And:
insertFeatures(activeJohn, activeNick, inactiveJohn, oldAdmin, invalidUserWithoutId)

/*
SELECT gzip(bytea_agg(tuple_number)) AS rs FROM (
SELECT tuple_number FROM (
(SELECT tuple_number, id
FROM read_by_tags_test WHERE ( ( (naksha_tags(tags, flags) @?? $1::jsonpath AND $2) OR naksha_tags(tags, flags)->>'role' = $3) ))
) ORDER BY id, tuple_number) LIMIT 1000000;
*/

// When:
val activeJohnsOrAdmin = TagOr(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package naksha.psql
import naksha.base.Int64
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.SQLType
import java.util.ArrayList
import java.util.HashMap

Expand Down

0 comments on commit a86d3fd

Please sign in to comment.