Skip to content

Commit e4cdbee

Browse files
authored
Merge pull request #1090 from sproctor/fix-escaping
only escape filters inside logical expressions and `in` lists
2 parents 40ccabc + 97a85ba commit e4cdbee

File tree

4 files changed

+49
-29
lines changed

4 files changed

+49
-29
lines changed

Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/filter/FilterOperation.kt

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ package io.github.jan.supabase.postgrest.query.filter
1111
*/
1212
data class FilterOperation(val column: String, val operator: FilterOperator, val value: Any?)
1313

14-
fun FilterOperation.escapedValue(): String =
14+
fun FilterOperation.escapedValue(isInLogicalExpression: Boolean): String =
1515
when (operator) {
1616
FilterOperator.EQ,
1717
FilterOperator.NEQ,
@@ -23,8 +23,15 @@ fun FilterOperation.escapedValue(): String =
2323
FilterOperator.ILIKE,
2424
FilterOperator.MATCH,
2525
FilterOperator.IMATCH,
26-
FilterOperator.IS ->
27-
escapeValue(value)
26+
FilterOperator.IS,
27+
FilterOperator.FTS,
28+
FilterOperator.WFTS,
29+
FilterOperator.PHFTS,
30+
FilterOperator.PLFTS ->
31+
if (isInLogicalExpression)
32+
escapeValue(value)
33+
else
34+
value.toString()
2835
FilterOperator.IN ->
2936
if (value is List<*>) {
3037
encodeAsList(value)
@@ -35,9 +42,15 @@ fun FilterOperation.escapedValue(): String =
3542
FilterOperator.CD,
3643
FilterOperator.OV ->
3744
when (value) {
38-
is List<*> -> encodeOverlapAsArray(value)
39-
is Pair<*, *> -> encodeOverlapAsRange(value)
40-
else -> escapeValue(value)
45+
is List<*> ->
46+
encodeOverlapAsArray(value)
47+
is Pair<*, *> ->
48+
encodeOverlapAsRange(value)
49+
else ->
50+
if (isInLogicalExpression)
51+
escapeValue(value)
52+
else
53+
value.toString()
4154
}
4255
FilterOperator.SL,
4356
FilterOperator.SR,
@@ -49,35 +62,30 @@ fun FilterOperation.escapedValue(): String =
4962
is List<*> -> encodeAsRange(value)
5063
else -> escapeValue(value)
5164
}
52-
FilterOperator.FTS,
53-
FilterOperator.WFTS,
54-
FilterOperator.PHFTS,
55-
FilterOperator.PLFTS ->
56-
escapeValue(value) // Do these need special handling?
5765
}
5866

5967
private fun encodeAsList(values: List<*>): String =
6068
values.joinToString(",", prefix = "(", postfix = ")") { escapeValue(it) }
6169

6270
private fun encodeAsRange(range: Pair<*, *>): String =
63-
"(${escapeValue(range.first)},${escapeValue(range.second)})"
71+
"(${range.first},${range.second})"
6472

6573
private fun encodeAsRange(range: List<*>): String =
66-
"(${escapeValue(range[0])},${escapeValue(range[1])})"
74+
"(${range[0]},${range[1]})"
6775

6876
private fun encodeOverlapAsRange(range: Pair<*, *>): String =
69-
"[${escapeValue(range.first)},${escapeValue(range.second)}]"
77+
"[${range.first},${range.second}]"
7078

7179
private fun encodeOverlapAsArray(values: List<*>): String =
72-
values.joinToString(",", prefix = "{", postfix = "}") { escapeValue(it) }
80+
values.joinToString(",", prefix = "{", postfix = "}")
7381

7482
private val quotedCharacters = listOf(",", ".", ":", "(", ")")
7583

7684
internal fun escapeValue(value: Any?): String {
7785
val asString = value.toString()
7886
.replace("\\", "\\\\")
7987
.replace("\"", "\\\"")
80-
return if (value !is Number && quotedCharacters.any { asString.contains(it) }) {
88+
return if (quotedCharacters.any { asString.contains(it) }) {
8189
"\"$asString\""
8290
} else {
8391
asString

Postgrest/src/commonMain/kotlin/io/github/jan/supabase/postgrest/query/filter/PostgrestFilterBuilder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class PostgrestFilterBuilder(
2828
*/
2929
fun filterNot(operation: FilterOperation) {
3030
val columnValue = params[operation.column] ?: emptyList()
31-
_params[operation.column] = columnValue + listOf("not.${operation.operator.identifier}.${operation.escapedValue()}")
31+
_params[operation.column] = columnValue + listOf("not.${operation.operator.identifier}.${operation.escapedValue(isInLogicalExpression)}")
3232
}
3333

3434
/**
@@ -43,7 +43,7 @@ class PostgrestFilterBuilder(
4343
*/
4444
fun filter(operation: FilterOperation) {
4545
val columnValue = params[operation.column] ?: emptyList()
46-
_params[operation.column] = columnValue + listOf("${operation.operator.identifier}.${operation.escapedValue()}")
46+
_params[operation.column] = columnValue + listOf("${operation.operator.identifier}.${operation.escapedValue(isInLogicalExpression)}")
4747
}
4848

4949
/**

Postgrest/src/commonTest/kotlin/PostgrestFilterBuilderTest.kt

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,27 @@ class PostgrestFilterBuilderTest {
3434
}
3535

3636
@Test
37-
fun eq_reserved() {
37+
fun eq_with_reserved() {
3838
val filter = filterToString {
39-
eq("id", "2004-09-16T23:59:58.75")
39+
eq("time", "2004-09-16T23:59:58.75")
4040
}
41-
assertEquals("id=eq.\"2004-09-16T23:59:58.75\"", filter)
41+
assertEquals("time=eq.2004-09-16T23:59:58.75", filter)
4242
}
4343

4444
@Test
45-
fun eq_quoted() {
45+
fun eq_with_quotes() {
4646
val filter = filterToString {
47-
eq("id", "Hello, \"World\"")
47+
eq("message", "Hello, \"World\"")
4848
}
49-
assertEquals("id=eq.\"Hello,+\\\"World\\\"\"", filter)
49+
assertEquals("message=eq.Hello,+\"World\"", filter)
50+
}
51+
52+
@Test
53+
fun in_quoted() {
54+
val filter = filterToString {
55+
isIn("message", listOf("\"Hello, World\"", "Goodbye.", "Greetings"))
56+
}
57+
assertEquals("message=in.(\"\\\"Hello,+World\\\"\",\"Goodbye.\",Greetings)", filter)
5058
}
5159

5260
@Test
@@ -148,15 +156,15 @@ class PostgrestFilterBuilderTest {
148156
@Test
149157
fun contained() {
150158
val filter = filterToString {
151-
contained("id", listOf(1,2,3))
159+
contained("id", listOf(1, 2, 3))
152160
}
153161
assertEquals("id=cd.{1,2,3}", filter)
154162
}
155163

156164
@Test
157165
fun overlaps() {
158166
val filter = filterToString {
159-
overlaps("id", listOf(1,2,3))
167+
overlaps("id", listOf(1, 2, 3))
160168
}
161169
assertEquals("id=ov.{1,2,3}", filter)
162170
}
@@ -293,15 +301,19 @@ class PostgrestFilterBuilderTest {
293301

294302
@Test
295303
fun propertyConversionWithSerialName() {
296-
if(CurrentPlatformTarget in listOf(PlatformTarget.JVM, PlatformTarget.ANDROID)) {
304+
if (CurrentPlatformTarget in listOf(PlatformTarget.JVM, PlatformTarget.ANDROID)) {
297305
assertEquals("created_at", PropertyConversionMethod.SERIAL_NAME(TestData::createdAt))
298306
} else {
299307
assertFails { PropertyConversionMethod.SERIAL_NAME(TestData::createdAt) }
300308
}
301309
}
302310

303311
private fun filterToString(builder: PostgrestFilterBuilder.() -> Unit): String {
304-
return PostgrestFilterBuilder(PropertyConversionMethod.NONE).apply(block = builder).params.mapValues { (_, value) -> listOf(value.first()) }.let {
312+
return PostgrestFilterBuilder(PropertyConversionMethod.NONE).apply(block = builder).params.mapValues { (_, value) ->
313+
listOf(
314+
value.first()
315+
)
316+
}.let {
305317
parametersOf(it).formUrlEncode()
306318
}.decodeURLQueryComponent()
307319
}

Realtime/src/commonMain/kotlin/io/github/jan/supabase/realtime/PostgresChangeFilter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class PostgresChangeFilter(private val event: String, private val schema: String
3535
FilterOperator.LT,
3636
FilterOperator.LTE,
3737
FilterOperator.IN ->
38-
filter.escapedValue()
38+
filter.escapedValue(false)
3939
else -> throw UnsupportedOperationException("Unsupported filter operator: ${filter.operator}")
4040
}
4141
this.filter = "${filter.column}=${filter.operator.name.lowercase()}.$filterValue"

0 commit comments

Comments
 (0)