Migrate some SQL sanitizer tests to java (#7148)
This commit is contained in:
parent
c6bbf28eac
commit
d4b29cf521
|
@ -50,7 +50,7 @@ WHITESPACE = [ \t\r\n]+
|
||||||
}
|
}
|
||||||
|
|
||||||
// max length of the sanitized statement - SQLs longer than this will be trimmed
|
// max length of the sanitized statement - SQLs longer than this will be trimmed
|
||||||
private static final int LIMIT = 32 * 1024;
|
static final int LIMIT = 32 * 1024;
|
||||||
|
|
||||||
private final StringBuilder builder = new StringBuilder();
|
private final StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
|
|
@ -1,148 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright The OpenTelemetry Authors
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.api.db
|
|
||||||
|
|
||||||
import spock.lang.Specification
|
|
||||||
import spock.lang.Unroll
|
|
||||||
|
|
||||||
class RedisCommandSanitizerTest extends Specification {
|
|
||||||
@Unroll
|
|
||||||
def "should sanitize #expected"() {
|
|
||||||
when:
|
|
||||||
def sanitized = RedisCommandSanitizer.create(true).sanitize(command, args)
|
|
||||||
|
|
||||||
then:
|
|
||||||
sanitized == expected
|
|
||||||
|
|
||||||
where:
|
|
||||||
command | args | expected
|
|
||||||
// Connection
|
|
||||||
"AUTH" | ["password"] | "AUTH ?"
|
|
||||||
"HELLO" | ["3", "AUTH", "username", "password"] | "HELLO 3 AUTH ? ?"
|
|
||||||
"HELLO" | ["3"] | "HELLO 3"
|
|
||||||
// Hashes
|
|
||||||
"HMSET" | ["hash", "key1", "value1", "key2", "value2"] | "HMSET hash key1 ? key2 ?"
|
|
||||||
"HSET" | ["hash", "key1", "value1", "key2", "value2"] | "HSET hash key1 ? key2 ?"
|
|
||||||
"HSETNX" | ["hash", "key", "value"] | "HSETNX hash key ?"
|
|
||||||
// HyperLogLog
|
|
||||||
"PFADD" | ["hll", "a", "b", "c"] | "PFADD hll ? ? ?"
|
|
||||||
// Keys
|
|
||||||
"MIGRATE" | ["127.0.0.1", "4242", "key", "0", "5000", "AUTH", "password"] | "MIGRATE 127.0.0.1 4242 key 0 5000 AUTH ?"
|
|
||||||
"RESTORE" | ["key", "42", "value"] | "RESTORE key 42 ?"
|
|
||||||
// Lists
|
|
||||||
"LINSERT" | ["list", "BEFORE", "value1", "value2"] | "LINSERT list BEFORE ? ?"
|
|
||||||
"LPOS" | ["list", "value"] | "LPOS list ?"
|
|
||||||
"LPUSH" | ["list", "value1", "value2"] | "LPUSH list ? ?"
|
|
||||||
"LPUSHX" | ["list", "value1", "value2"] | "LPUSHX list ? ?"
|
|
||||||
"LREM" | ["list", "2", "value"] | "LREM list ? ?"
|
|
||||||
"LSET" | ["list", "2", "value"] | "LSET list ? ?"
|
|
||||||
"RPUSH" | ["list", "value1", "value2"] | "RPUSH list ? ?"
|
|
||||||
"RPUSHX" | ["list", "value1", "value2"] | "RPUSHX list ? ?"
|
|
||||||
// Pub/Sub
|
|
||||||
"PUBLISH" | ["channel", "message"] | "PUBLISH channel ?"
|
|
||||||
// Scripting
|
|
||||||
"EVAL" | ["script", "2", "key1", "key2", "value"] | "EVAL script 2 key1 key2 ?"
|
|
||||||
"EVALSHA" | ["sha1", "0", "value1", "value2"] | "EVALSHA sha1 0 ? ?"
|
|
||||||
// Sets
|
|
||||||
"SADD" | ["set", "value1", "value2"] | "SADD set ? ?"
|
|
||||||
"SISMEMBER" | ["set", "value"] | "SISMEMBER set ?"
|
|
||||||
"SMISMEMBER" | ["set", "value1", "value2"] | "SMISMEMBER set ? ?"
|
|
||||||
"SMOVE" | ["set1", "set2", "value"] | "SMOVE set1 set2 ?"
|
|
||||||
"SREM" | ["set", "value1", "value2"] | "SREM set ? ?"
|
|
||||||
// Server
|
|
||||||
"CONFIG" | ["SET", "masterpassword", "password"] | "CONFIG SET masterpassword ?"
|
|
||||||
// Sorted Sets
|
|
||||||
"ZADD" | ["sset", "1", "value1", "2", "value2"] | "ZADD sset ? ? ? ?"
|
|
||||||
"ZCOUNT" | ["sset", "1", "10"] | "ZCOUNT sset ? ?"
|
|
||||||
"ZINCRBY" | ["sset", "1", "value"] | "ZINCRBY sset ? ?"
|
|
||||||
"ZLEXCOUNT" | ["sset", "1", "10"] | "ZLEXCOUNT sset ? ?"
|
|
||||||
"ZMSCORE" | ["sset", "value1", "value2"] | "ZMSCORE sset ? ?"
|
|
||||||
"ZRANGEBYLEX" | ["sset", "1", "10"] | "ZRANGEBYLEX sset ? ?"
|
|
||||||
"ZRANGEBYSCORE" | ["sset", "1", "10"] | "ZRANGEBYSCORE sset ? ?"
|
|
||||||
"ZRANK" | ["sset", "value"] | "ZRANK sset ?"
|
|
||||||
"ZREM" | ["sset", "value1", "value2"] | "ZREM sset ? ?"
|
|
||||||
"ZREMRANGEBYLEX" | ["sset", "1", "10"] | "ZREMRANGEBYLEX sset ? ?"
|
|
||||||
"ZREMRANGEBYSCORE" | ["sset", "1", "10"] | "ZREMRANGEBYSCORE sset ? ?"
|
|
||||||
"ZREVRANGEBYLEX" | ["sset", "1", "10"] | "ZREVRANGEBYLEX sset ? ?"
|
|
||||||
"ZREVRANGEBYSCORE" | ["sset", "1", "10"] | "ZREVRANGEBYSCORE sset ? ?"
|
|
||||||
"ZREVRANK" | ["sset", "value"] | "ZREVRANK sset ?"
|
|
||||||
"ZSCORE" | ["sset", "value"] | "ZSCORE sset ?"
|
|
||||||
// Streams
|
|
||||||
"XADD" | ["stream", "*", "key1", "value1", "key2", "value2"] | "XADD stream * key1 ? key2 ?"
|
|
||||||
// Strings
|
|
||||||
"APPEND" | ["key", "value"] | "APPEND key ?"
|
|
||||||
"GETSET" | ["key", "value"] | "GETSET key ?"
|
|
||||||
"MSET" | ["key1", "value1", "key2", "value2"] | "MSET key1 ? key2 ?"
|
|
||||||
"MSETNX" | ["key1", "value1", "key2", "value2"] | "MSETNX key1 ? key2 ?"
|
|
||||||
"PSETEX" | ["key", "10000", "value"] | "PSETEX key 10000 ?"
|
|
||||||
"SET" | ["key", "value"] | "SET key ?"
|
|
||||||
"SETEX" | ["key", "10", "value"] | "SETEX key 10 ?"
|
|
||||||
"SETNX" | ["key", "value"] | "SETNX key ?"
|
|
||||||
"SETRANGE" | ["key", "42", "value"] | "SETRANGE key ? ?"
|
|
||||||
}
|
|
||||||
|
|
||||||
@Unroll
|
|
||||||
def "should keep all arguments of #command"() {
|
|
||||||
given:
|
|
||||||
def args = ["arg1", "arg 2"]
|
|
||||||
|
|
||||||
when:
|
|
||||||
def sanitized = RedisCommandSanitizer.create(true).sanitize(command, args)
|
|
||||||
|
|
||||||
then:
|
|
||||||
sanitized == command + " " + args.join(" ")
|
|
||||||
|
|
||||||
where:
|
|
||||||
command << [
|
|
||||||
// Cluster
|
|
||||||
"CLUSTER", "READONLY", "READWRITE",
|
|
||||||
// Connection
|
|
||||||
"CLIENT", "ECHO", "PING", "QUIT", "SELECT",
|
|
||||||
// Geo
|
|
||||||
"GEOADD", "GEODIST", "GEOHASH", "GEOPOS", "GEORADIUS", "GEORADIUSBYMEMBER",
|
|
||||||
// Hashes
|
|
||||||
"HDEL", "HEXISTS", "HGET", "HGETALL", "HINCRBY", "HINCRBYFLOAT", "HKEYS", "HLEN", "HMGET",
|
|
||||||
"HSCAN", "HSTRLEN", "HVALS",
|
|
||||||
// HyperLogLog
|
|
||||||
"PFCOUNT", "PFMERGE",
|
|
||||||
// Keys
|
|
||||||
"DEL", "DUMP", "EXISTS", "EXPIRE", "EXPIREAT", "KEYS", "MOVE", "OBJECT", "PERSIST", "PEXPIRE",
|
|
||||||
"PEXPIREAT", "PTTL", "RANDOMKEY", "RENAME", "RENAMENX", "RESTORE", "SCAN", "SORT", "TOUCH",
|
|
||||||
"TTL", "TYPE", "UNLINK", "WAIT",
|
|
||||||
// Lists
|
|
||||||
"BLMOVE", "BLPOP", "BRPOP", "BRPOPLPUSH", "LINDEX", "LLEN", "LMOVE", "LPOP", "LRANGE",
|
|
||||||
"LTRIM", "RPOP", "RPOPLPUSH",
|
|
||||||
// Pub/Sub
|
|
||||||
"PSUBSCRIBE", "PUBSUB", "PUNSUBSCRIBE", "SUBSCRIBE", "UNSUBSCRIBE",
|
|
||||||
// Server
|
|
||||||
"ACL", "BGREWRITEAOF", "BGSAVE", "COMMAND", "DBSIZE", "DEBUG", "FLUSHALL", "FLUSHDB", "INFO",
|
|
||||||
"LASTSAVE", "LATENCY", "LOLWUT", "MEMORY", "MODULE", "MONITOR", "PSYNC", "REPLICAOF", "ROLE",
|
|
||||||
"SAVE", "SHUTDOWN", "SLAVEOF", "SLOWLOG", "SWAPDB", "SYNC", "TIME",
|
|
||||||
// Sets
|
|
||||||
"SCARD", "SDIFF", "SDIFFSTORE", "SINTER", "SINTERSTORE", "SMEMBERS", "SPOP", "SRANDMEMBER",
|
|
||||||
"SSCAN", "SUNION", "SUNIONSTORE",
|
|
||||||
// Sorted Sets
|
|
||||||
"BZPOPMAX", "BZPOPMIN", "ZCARD", "ZINTER", "ZINTERSTORE", "ZPOPMAX", "ZPOPMIN", "ZRANGE",
|
|
||||||
"ZREMRANGEBYRANK", "ZREVRANGE", "ZSCAN", "ZUNION", "ZUNIONSTORE",
|
|
||||||
// Streams
|
|
||||||
"XACK", "XCLAIM", "XDEL", "XGROUP", "XINFO", "XLEN", "XPENDING", "XRANGE", "XREAD",
|
|
||||||
"XREADGROUP", "XREVRANGE", "XTRIM",
|
|
||||||
// Strings
|
|
||||||
"BITCOUNT", "BITFIELD", "BITOP", "BITPOS", "DECR", "DECRBY", "GET", "GETBIT", "GETRANGE",
|
|
||||||
"INCR", "INCRBY", "INCRBYFLOAT", "MGET", "SETBIT", "STRALGO", "STRLEN",
|
|
||||||
// Transactions
|
|
||||||
"DISCARD", "EXEC", "MULTI", "UNWATCH", "WATCH"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def "should mask all arguments of an unknown command"() {
|
|
||||||
when:
|
|
||||||
def sanitized = RedisCommandSanitizer.create(true).sanitize("NEWAUTH", ["password", "secret"])
|
|
||||||
|
|
||||||
then:
|
|
||||||
sanitized == "NEWAUTH ? ?"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,225 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright The OpenTelemetry Authors
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.api.db
|
|
||||||
|
|
||||||
import spock.lang.Specification
|
|
||||||
import spock.lang.Unroll
|
|
||||||
|
|
||||||
class SqlStatementSanitizerTest extends Specification {
|
|
||||||
|
|
||||||
def "normalize #originalSql"() {
|
|
||||||
setup:
|
|
||||||
def actualSanitized = SqlStatementSanitizer.create(true).sanitize(originalSql)
|
|
||||||
|
|
||||||
expect:
|
|
||||||
actualSanitized.getFullStatement() == sanitizedSql
|
|
||||||
|
|
||||||
where:
|
|
||||||
originalSql | sanitizedSql
|
|
||||||
// Numbers
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD=1234" | "SELECT * FROM TABLE WHERE FIELD=?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = 1234" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD>=-1234" | "SELECT * FROM TABLE WHERE FIELD>=?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD<-1234" | "SELECT * FROM TABLE WHERE FIELD<?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD <.1234" | "SELECT * FROM TABLE WHERE FIELD <?"
|
|
||||||
"SELECT 1.2" | "SELECT ?"
|
|
||||||
"SELECT -1.2" | "SELECT ?"
|
|
||||||
"SELECT -1.2e-9" | "SELECT ?"
|
|
||||||
"SELECT 2E+9" | "SELECT ?"
|
|
||||||
"SELECT +0.2" | "SELECT ?"
|
|
||||||
"SELECT .2" | "SELECT ?"
|
|
||||||
"7" | "?"
|
|
||||||
".7" | "?"
|
|
||||||
"-7" | "?"
|
|
||||||
"+7" | "?"
|
|
||||||
"SELECT 0x0af764" | "SELECT ?"
|
|
||||||
"SELECT 0xdeadBEEF" | "SELECT ?"
|
|
||||||
"SELECT * FROM \"TABLE\"" | "SELECT * FROM \"TABLE\""
|
|
||||||
|
|
||||||
// Not numbers but could be confused as such
|
|
||||||
"SELECT A + B" | "SELECT A + B"
|
|
||||||
"SELECT -- comment" | "SELECT -- comment"
|
|
||||||
"SELECT * FROM TABLE123" | "SELECT * FROM TABLE123"
|
|
||||||
"SELECT FIELD2 FROM TABLE_123 WHERE X<>7" | "SELECT FIELD2 FROM TABLE_123 WHERE X<>?"
|
|
||||||
|
|
||||||
// Semi-nonsensical almost-numbers to elide or not
|
|
||||||
"SELECT --83--...--8e+76e3E-1" | "SELECT ?"
|
|
||||||
"SELECT DEADBEEF" | "SELECT DEADBEEF"
|
|
||||||
"SELECT 123-45-6789" | "SELECT ?"
|
|
||||||
"SELECT 1/2/34" | "SELECT ?/?/?"
|
|
||||||
|
|
||||||
// Basic ' strings
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = ''" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = 'words and spaces'" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = ' an escaped '' quote mark inside'" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = '\\\\'" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = '\"inside doubles\"'" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = '\"\$\$\$\$\"'" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = 'a single \" doublequote inside'" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
|
|
||||||
// Some databases allow using dollar-quoted strings
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \$\$\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \$\$words and spaces\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \$\$quotes '\" inside\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \$\$\"''\"\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \$\$\\\\\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
|
|
||||||
// Unicode, including a unicode identifier with a trailing number
|
|
||||||
"SELECT * FROM TABLE\u09137 WHERE FIELD = '\u0194'" | "SELECT * FROM TABLE\u09137 WHERE FIELD = ?"
|
|
||||||
|
|
||||||
// whitespace normalization
|
|
||||||
"SELECT * \t\r\nFROM TABLE WHERE FIELD1 = 12344 AND FIELD2 = 5678" | "SELECT * FROM TABLE WHERE FIELD1 = ? AND FIELD2 = ?"
|
|
||||||
|
|
||||||
// hibernate/jpa query language
|
|
||||||
"FROM TABLE WHERE FIELD=1234" | "FROM TABLE WHERE FIELD=?"
|
|
||||||
}
|
|
||||||
|
|
||||||
def "normalize couchbase #originalSql"() {
|
|
||||||
setup:
|
|
||||||
def actualSanitized = SqlStatementSanitizer.create(true).sanitize(originalSql, SqlDialect.COUCHBASE)
|
|
||||||
|
|
||||||
expect:
|
|
||||||
actualSanitized.getFullStatement() == sanitizedSql
|
|
||||||
|
|
||||||
where:
|
|
||||||
originalSql | sanitizedSql
|
|
||||||
// Some databases support/encourage " instead of ' with same escape rules
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \"\"" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \"words and spaces'\"" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \" an escaped \"\" quote mark inside\"" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \"\\\\\"" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \"'inside singles'\"" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \"'\$\$\$\$'\"" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
"SELECT * FROM TABLE WHERE FIELD = \"a single ' singlequote inside\"" | "SELECT * FROM TABLE WHERE FIELD = ?"
|
|
||||||
}
|
|
||||||
|
|
||||||
@Unroll
|
|
||||||
def "should simplify #sql"() {
|
|
||||||
expect:
|
|
||||||
SqlStatementSanitizer.create(true).sanitize(sql) == expected
|
|
||||||
|
|
||||||
where:
|
|
||||||
sql | expected
|
|
||||||
// Select
|
|
||||||
'SELECT x, y, z FROM schema.table' | SqlStatementInfo.create(sql, 'SELECT', 'schema.table')
|
|
||||||
'SELECT x, y, z FROM `schema table`' | SqlStatementInfo.create(sql, 'SELECT', 'schema table')
|
|
||||||
'SELECT x, y, z FROM "schema table"' | SqlStatementInfo.create(sql, 'SELECT', 'schema table')
|
|
||||||
'WITH subquery as (select a from b) SELECT x, y, z FROM table' | SqlStatementInfo.create(sql, 'SELECT', null)
|
|
||||||
'SELECT x, y, (select a from b) as z FROM table' | SqlStatementInfo.create(sql, 'SELECT', null)
|
|
||||||
'select delete, insert into, merge, update from table' | SqlStatementInfo.create(sql, 'SELECT', 'table')
|
|
||||||
'select col /* from table2 */ from table' | SqlStatementInfo.create(sql, 'SELECT', 'table')
|
|
||||||
'select col from table join anotherTable' | SqlStatementInfo.create(sql, 'SELECT', null)
|
|
||||||
'select col from (select * from anotherTable)' | SqlStatementInfo.create(sql, 'SELECT', null)
|
|
||||||
'select col from (select * from anotherTable) alias' | SqlStatementInfo.create(sql, 'SELECT', null)
|
|
||||||
'select col from table1 union select col from table2' | SqlStatementInfo.create(sql, 'SELECT', null)
|
|
||||||
'select col from table where col in (select * from anotherTable)' | SqlStatementInfo.create(sql, 'SELECT', null)
|
|
||||||
'select col from table1, table2' | SqlStatementInfo.create(sql, 'SELECT', null)
|
|
||||||
'select col from table1 t1, table2 t2' | SqlStatementInfo.create(sql, 'SELECT', null)
|
|
||||||
'select col from table1 as t1, table2 as t2' | SqlStatementInfo.create(sql, 'SELECT', null)
|
|
||||||
'select col from table where col in (1, 2, 3)' | SqlStatementInfo.create('select col from table where col in (?, ?, ?)', 'SELECT', 'table')
|
|
||||||
'select col from table order by col, col2' | SqlStatementInfo.create(sql, 'SELECT', 'table')
|
|
||||||
'select ąś∂ń© from źćļńĶ order by col, col2' | SqlStatementInfo.create(sql, 'SELECT', 'źćļńĶ')
|
|
||||||
'select 12345678' | SqlStatementInfo.create('select ?', 'SELECT', null)
|
|
||||||
'/* update comment */ select * from table1' | SqlStatementInfo.create(sql, 'SELECT', 'table1')
|
|
||||||
'select /*((*/abc from table' | SqlStatementInfo.create(sql, 'SELECT', 'table')
|
|
||||||
'SeLeCT * FrOm TAblE' | SqlStatementInfo.create(sql, 'SELECT', 'TAblE')
|
|
||||||
// hibernate/jpa
|
|
||||||
'FROM schema.table' | SqlStatementInfo.create(sql, 'SELECT', 'schema.table')
|
|
||||||
'/* update comment */ from table1' | SqlStatementInfo.create(sql, 'SELECT', 'table1')
|
|
||||||
// Insert
|
|
||||||
' insert into table where lalala' | SqlStatementInfo.create(sql, 'INSERT', 'table')
|
|
||||||
'insert insert into table where lalala' | SqlStatementInfo.create(sql, 'INSERT', 'table')
|
|
||||||
'insert into db.table where lalala' | SqlStatementInfo.create(sql, 'INSERT', 'db.table')
|
|
||||||
'insert into `db table` where lalala' | SqlStatementInfo.create(sql, 'INSERT', 'db table')
|
|
||||||
'insert into "db table" where lalala' | SqlStatementInfo.create(sql, 'INSERT', 'db table')
|
|
||||||
'insert without i-n-t-o' | SqlStatementInfo.create(sql, 'INSERT', null)
|
|
||||||
// Delete
|
|
||||||
'delete from table where something something' | SqlStatementInfo.create(sql, 'DELETE', 'table')
|
|
||||||
'delete from `my table` where something something' | SqlStatementInfo.create(sql, 'DELETE', 'my table')
|
|
||||||
'delete from "my table" where something something' | SqlStatementInfo.create(sql, 'DELETE', 'my table')
|
|
||||||
'delete from 12345678' | SqlStatementInfo.create('delete from ?', 'DELETE', null)
|
|
||||||
'delete (((' | SqlStatementInfo.create('delete (((', 'DELETE', null)
|
|
||||||
// Update
|
|
||||||
'update table set answer=42' | SqlStatementInfo.create('update table set answer=?', 'UPDATE', 'table')
|
|
||||||
'update `my table` set answer=42' | SqlStatementInfo.create('update `my table` set answer=?', 'UPDATE', 'my table')
|
|
||||||
'update "my table" set answer=42' | SqlStatementInfo.create('update "my table" set answer=?', 'UPDATE', 'my table')
|
|
||||||
'update /*table' | SqlStatementInfo.create(sql, 'UPDATE', null)
|
|
||||||
// Merge
|
|
||||||
'merge into table' | SqlStatementInfo.create(sql, 'MERGE', 'table')
|
|
||||||
'merge into `my table`' | SqlStatementInfo.create(sql, 'MERGE', 'my table')
|
|
||||||
'merge into "my table"' | SqlStatementInfo.create(sql, 'MERGE', 'my table')
|
|
||||||
'merge table (into is optional in some dbs)' | SqlStatementInfo.create(sql, 'MERGE', 'table')
|
|
||||||
'merge (into )))' | SqlStatementInfo.create(sql, 'MERGE', null)
|
|
||||||
// Unknown operation
|
|
||||||
'and now for something completely different' | SqlStatementInfo.create(sql, null, null)
|
|
||||||
'' | SqlStatementInfo.create(sql, null, null)
|
|
||||||
null | SqlStatementInfo.create(sql, null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
def "very long SELECT statements don't cause problems"() {
|
|
||||||
given:
|
|
||||||
def sb = new StringBuilder("SELECT * FROM table WHERE")
|
|
||||||
for (int i = 0; i < 2000; i++) {
|
|
||||||
sb.append(" column").append(i).append("=123 and")
|
|
||||||
}
|
|
||||||
def query = sb.toString()
|
|
||||||
|
|
||||||
expect:
|
|
||||||
def sanitizedQuery = query.replace('=123', '=?').substring(0, AutoSqlSanitizer.LIMIT)
|
|
||||||
SqlStatementSanitizer.create(true).sanitize(query) == SqlStatementInfo.create(sanitizedQuery, "SELECT", "table")
|
|
||||||
}
|
|
||||||
|
|
||||||
def "lots and lots of ticks don't cause stack overflow or long runtimes"() {
|
|
||||||
setup:
|
|
||||||
String s = "'"
|
|
||||||
for (int i = 0; i < 10000; i++) {
|
|
||||||
assert SqlStatementSanitizer.create(true).sanitize(s) != null
|
|
||||||
s += "'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def "very long numbers don't cause a problem"() {
|
|
||||||
setup:
|
|
||||||
String s = ""
|
|
||||||
for (int i = 0; i < 10000; i++) {
|
|
||||||
s += String.valueOf(i)
|
|
||||||
}
|
|
||||||
assert "?" == SqlStatementSanitizer.create(true).sanitize(s).getFullStatement()
|
|
||||||
}
|
|
||||||
|
|
||||||
def "very long numbers at end of table name don't cause problem"() {
|
|
||||||
setup:
|
|
||||||
String s = "A"
|
|
||||||
for (int i = 0; i < 10000; i++) {
|
|
||||||
s += String.valueOf(i)
|
|
||||||
}
|
|
||||||
assert s.substring(0, AutoSqlSanitizer.LIMIT) == SqlStatementSanitizer.create(true).sanitize(s).getFullStatement()
|
|
||||||
}
|
|
||||||
|
|
||||||
def "test 32k truncation"() {
|
|
||||||
setup:
|
|
||||||
StringBuffer s = new StringBuffer()
|
|
||||||
for (int i = 0; i < 10000; i++) {
|
|
||||||
s.append("SELECT * FROM TABLE WHERE FIELD = 1234 AND ")
|
|
||||||
}
|
|
||||||
String sanitized = SqlStatementSanitizer.create(true).sanitize(s.toString()).getFullStatement()
|
|
||||||
System.out.println(sanitized.length())
|
|
||||||
assert sanitized.length() <= AutoSqlSanitizer.LIMIT
|
|
||||||
assert !sanitized.contains("1234")
|
|
||||||
}
|
|
||||||
|
|
||||||
def "random bytes don't cause exceptions or timeouts"() {
|
|
||||||
setup:
|
|
||||||
Random r = new Random(0)
|
|
||||||
for (int i = 0; i < 1000; i++) {
|
|
||||||
StringBuffer sb = new StringBuffer()
|
|
||||||
for (int c = 0; c < 1000; c++) {
|
|
||||||
sb.append((char) r.nextInt((int) Character.MAX_VALUE))
|
|
||||||
}
|
|
||||||
SqlStatementSanitizer.create(true).sanitize(sb.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,303 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.api.db;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||||
|
|
||||||
|
class RedisCommandSanitizerTest {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ArgumentsSource(SanitizeArgs.class)
|
||||||
|
void shouldSanitizeExpected(String command, List<String> args, String expected) {
|
||||||
|
String result = RedisCommandSanitizer.create(true).sanitize(command, args);
|
||||||
|
assertThat(result).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ArgumentsSource(KeepArguments.class)
|
||||||
|
void shouldKeepAllArguments(String command) {
|
||||||
|
List<String> args = list("arg1", "arg 2");
|
||||||
|
String result = RedisCommandSanitizer.create(true).sanitize(command, args);
|
||||||
|
assertThat(result).isEqualTo(command + " " + String.join(" ", args));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void maskAllArgsOfUnknownCommand() {
|
||||||
|
String result =
|
||||||
|
RedisCommandSanitizer.create(true).sanitize("NEWAUTH", list("password", "secret"));
|
||||||
|
assertThat(result).isEqualTo("NEWAUTH ? ?");
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SanitizeArgs implements ArgumentsProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
|
||||||
|
return Stream.of(
|
||||||
|
// Connection
|
||||||
|
Arguments.of("AUTH", list("password"), "AUTH ?"),
|
||||||
|
Arguments.of("HELLO", list("3", "AUTH", "username", "password"), "HELLO 3 AUTH ? ?"),
|
||||||
|
Arguments.of("HELLO", list("3"), "HELLO 3"),
|
||||||
|
// Hashes
|
||||||
|
Arguments.of(
|
||||||
|
"HMSET",
|
||||||
|
list("hash", "key1", "value1", "key2", "value2"),
|
||||||
|
"HMSET hash key1 ? key2 ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"HSET", list("hash", "key1", "value1", "key2", "value2"), "HSET hash key1 ? key2 ?"),
|
||||||
|
Arguments.of("HSETNX", list("hash", "key", "value"), "HSETNX hash key ?"),
|
||||||
|
// HyperLogLog
|
||||||
|
Arguments.of("PFADD", list("hll", "a", "b", "c"), "PFADD hll ? ? ?"),
|
||||||
|
// Keys
|
||||||
|
Arguments.of(
|
||||||
|
"MIGRATE",
|
||||||
|
list("127.0.0.1", "4242", "key", "0", "5000", "AUTH", "password"),
|
||||||
|
"MIGRATE 127.0.0.1 4242 key 0 5000 AUTH ?"),
|
||||||
|
Arguments.of("RESTORE", list("key", "42", "value"), "RESTORE key 42 ?"),
|
||||||
|
// Lists
|
||||||
|
Arguments.of(
|
||||||
|
"LINSERT", list("list", "BEFORE", "value1", "value2"), "LINSERT list BEFORE ? ?"),
|
||||||
|
Arguments.of("LPOS", list("list", "value"), "LPOS list ?"),
|
||||||
|
Arguments.of("LPUSH", list("list", "value1", "value2"), "LPUSH list ? ?"),
|
||||||
|
Arguments.of("LPUSHX", list("list", "value1", "value2"), "LPUSHX list ? ?"),
|
||||||
|
Arguments.of("LREM", list("list", "2", "value"), "LREM list ? ?"),
|
||||||
|
Arguments.of("LSET", list("list", "2", "value"), "LSET list ? ?"),
|
||||||
|
Arguments.of("RPUSH", list("list", "value1", "value2"), "RPUSH list ? ?"),
|
||||||
|
Arguments.of("RPUSHX", list("list", "value1", "value2"), "RPUSHX list ? ?"),
|
||||||
|
// Pub/Sub
|
||||||
|
Arguments.of("PUBLISH", list("channel", "message"), "PUBLISH channel ?"),
|
||||||
|
// Scripting
|
||||||
|
Arguments.of(
|
||||||
|
"EVAL", list("script", "2", "key1", "key2", "value"), "EVAL script 2 key1 key2 ?"),
|
||||||
|
Arguments.of("EVALSHA", list("sha1", "0", "value1", "value2"), "EVALSHA sha1 0 ? ?"),
|
||||||
|
// Sets),
|
||||||
|
Arguments.of("SADD", list("set", "value1", "value2"), "SADD set ? ?"),
|
||||||
|
Arguments.of("SISMEMBER", list("set", "value"), "SISMEMBER set ?"),
|
||||||
|
Arguments.of("SMISMEMBER", list("set", "value1", "value2"), "SMISMEMBER set ? ?"),
|
||||||
|
Arguments.of("SMOVE", list("set1", "set2", "value"), "SMOVE set1 set2 ?"),
|
||||||
|
Arguments.of("SREM", list("set", "value1", "value2"), "SREM set ? ?"),
|
||||||
|
// Server
|
||||||
|
Arguments.of(
|
||||||
|
"CONFIG", list("SET", "masterpassword", "password"), "CONFIG SET masterpassword ?"),
|
||||||
|
// Sorted Sets
|
||||||
|
Arguments.of("ZADD", list("sset", "1", "value1", "2", "value2"), "ZADD sset ? ? ? ?"),
|
||||||
|
Arguments.of("ZCOUNT", list("sset", "1", "10"), "ZCOUNT sset ? ?"),
|
||||||
|
Arguments.of("ZINCRBY", list("sset", "1", "value"), "ZINCRBY sset ? ?"),
|
||||||
|
Arguments.of("ZLEXCOUNT", list("sset", "1", "10"), "ZLEXCOUNT sset ? ?"),
|
||||||
|
Arguments.of("ZMSCORE", list("sset", "value1", "value2"), "ZMSCORE sset ? ?"),
|
||||||
|
Arguments.of("ZRANGEBYLEX", list("sset", "1", "10"), "ZRANGEBYLEX sset ? ?"),
|
||||||
|
Arguments.of("ZRANGEBYSCORE", list("sset", "1", "10"), "ZRANGEBYSCORE sset ? ?"),
|
||||||
|
Arguments.of("ZRANK", list("sset", "value"), "ZRANK sset ?"),
|
||||||
|
Arguments.of("ZREM", list("sset", "value1", "value2"), "ZREM sset ? ?"),
|
||||||
|
Arguments.of("ZREMRANGEBYLEX", list("sset", "1", "10"), "ZREMRANGEBYLEX sset ? ?"),
|
||||||
|
Arguments.of("ZREMRANGEBYSCORE", list("sset", "1", "10"), "ZREMRANGEBYSCORE sset ? ?"),
|
||||||
|
Arguments.of("ZREVRANGEBYLEX", list("sset", "1", "10"), "ZREVRANGEBYLEX sset ? ?"),
|
||||||
|
Arguments.of("ZREVRANGEBYSCORE", list("sset", "1", "10"), "ZREVRANGEBYSCORE sset ? ?"),
|
||||||
|
Arguments.of("ZREVRANK", list("sset", "value"), "ZREVRANK sset ?"),
|
||||||
|
Arguments.of("ZSCORE", list("sset", "value"), "ZSCORE sset ?"),
|
||||||
|
// Streams
|
||||||
|
Arguments.of(
|
||||||
|
"XADD",
|
||||||
|
list("stream", "*", "key1", "value1", "key2", "value2"),
|
||||||
|
"XADD stream * key1 ? key2 ?"),
|
||||||
|
// Strings
|
||||||
|
Arguments.of("APPEND", list("key", "value"), "APPEND key ?"),
|
||||||
|
Arguments.of("GETSET", list("key", "value"), "GETSET key ?"),
|
||||||
|
Arguments.of("MSET", list("key1", "value1", "key2", "value2"), "MSET key1 ? key2 ?"),
|
||||||
|
Arguments.of("MSETNX", list("key1", "value1", "key2", "value2"), "MSETNX key1 ? key2 ?"),
|
||||||
|
Arguments.of("PSETEX", list("key", "10000", "value"), "PSETEX key 10000 ?"),
|
||||||
|
Arguments.of("SET", list("key", "value"), "SET key ?"),
|
||||||
|
Arguments.of("SETEX", list("key", "10", "value"), "SETEX key 10 ?"),
|
||||||
|
Arguments.of("SETNX", list("key", "value"), "SETNX key ?"),
|
||||||
|
Arguments.of("SETRANGE", list("key", "42", "value"), "SETRANGE key ? ?"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class KeepArguments implements ArgumentsProvider {
|
||||||
|
@Override
|
||||||
|
public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
|
||||||
|
return Stream.of(
|
||||||
|
// Cluster
|
||||||
|
"CLUSTER",
|
||||||
|
"READONLY",
|
||||||
|
"READWRITE",
|
||||||
|
// Connection
|
||||||
|
"CLIENT",
|
||||||
|
"ECHO",
|
||||||
|
"PING",
|
||||||
|
"QUIT",
|
||||||
|
"SELECT",
|
||||||
|
// Geo
|
||||||
|
"GEOADD",
|
||||||
|
"GEODIST",
|
||||||
|
"GEOHASH",
|
||||||
|
"GEOPOS",
|
||||||
|
"GEORADIUS",
|
||||||
|
"GEORADIUSBYMEMBER",
|
||||||
|
// Hashes
|
||||||
|
"HDEL",
|
||||||
|
"HEXISTS",
|
||||||
|
"HGET",
|
||||||
|
"HGETALL",
|
||||||
|
"HINCRBY",
|
||||||
|
"HINCRBYFLOAT",
|
||||||
|
"HKEYS",
|
||||||
|
"HLEN",
|
||||||
|
"HMGET",
|
||||||
|
"HSCAN",
|
||||||
|
"HSTRLEN",
|
||||||
|
"HVALS",
|
||||||
|
// HyperLogLog
|
||||||
|
"PFCOUNT",
|
||||||
|
"PFMERGE",
|
||||||
|
// Keys
|
||||||
|
"DEL",
|
||||||
|
"DUMP",
|
||||||
|
"EXISTS",
|
||||||
|
"EXPIRE",
|
||||||
|
"EXPIREAT",
|
||||||
|
"KEYS",
|
||||||
|
"MOVE",
|
||||||
|
"OBJECT",
|
||||||
|
"PERSIST",
|
||||||
|
"PEXPIRE",
|
||||||
|
"PEXPIREAT",
|
||||||
|
"PTTL",
|
||||||
|
"RANDOMKEY",
|
||||||
|
"RENAME",
|
||||||
|
"RENAMENX",
|
||||||
|
"RESTORE",
|
||||||
|
"SCAN",
|
||||||
|
"SORT",
|
||||||
|
"TOUCH",
|
||||||
|
"TTL",
|
||||||
|
"TYPE",
|
||||||
|
"UNLINK",
|
||||||
|
"WAIT",
|
||||||
|
// Lists
|
||||||
|
"BLMOVE",
|
||||||
|
"BLPOP",
|
||||||
|
"BRPOP",
|
||||||
|
"BRPOPLPUSH",
|
||||||
|
"LINDEX",
|
||||||
|
"LLEN",
|
||||||
|
"LMOVE",
|
||||||
|
"LPOP",
|
||||||
|
"LRANGE",
|
||||||
|
"LTRIM",
|
||||||
|
"RPOP",
|
||||||
|
"RPOPLPUSH",
|
||||||
|
// Pub/Sub
|
||||||
|
"PSUBSCRIBE",
|
||||||
|
"PUBSUB",
|
||||||
|
"PUNSUBSCRIBE",
|
||||||
|
"SUBSCRIBE",
|
||||||
|
"UNSUBSCRIBE",
|
||||||
|
// Server
|
||||||
|
"ACL",
|
||||||
|
"BGREWRITEAOF",
|
||||||
|
"BGSAVE",
|
||||||
|
"COMMAND",
|
||||||
|
"DBSIZE",
|
||||||
|
"DEBUG",
|
||||||
|
"FLUSHALL",
|
||||||
|
"FLUSHDB",
|
||||||
|
"INFO",
|
||||||
|
"LASTSAVE",
|
||||||
|
"LATENCY",
|
||||||
|
"LOLWUT",
|
||||||
|
"MEMORY",
|
||||||
|
"MODULE",
|
||||||
|
"MONITOR",
|
||||||
|
"PSYNC",
|
||||||
|
"REPLICAOF",
|
||||||
|
"ROLE",
|
||||||
|
"SAVE",
|
||||||
|
"SHUTDOWN",
|
||||||
|
"SLAVEOF",
|
||||||
|
"SLOWLOG",
|
||||||
|
"SWAPDB",
|
||||||
|
"SYNC",
|
||||||
|
"TIME",
|
||||||
|
// Sets
|
||||||
|
"SCARD",
|
||||||
|
"SDIFF",
|
||||||
|
"SDIFFSTORE",
|
||||||
|
"SINTER",
|
||||||
|
"SINTERSTORE",
|
||||||
|
"SMEMBERS",
|
||||||
|
"SPOP",
|
||||||
|
"SRANDMEMBER",
|
||||||
|
"SSCAN",
|
||||||
|
"SUNION",
|
||||||
|
"SUNIONSTORE",
|
||||||
|
// Sorted Sets
|
||||||
|
"BZPOPMAX",
|
||||||
|
"BZPOPMIN",
|
||||||
|
"ZCARD",
|
||||||
|
"ZINTER",
|
||||||
|
"ZINTERSTORE",
|
||||||
|
"ZPOPMAX",
|
||||||
|
"ZPOPMIN",
|
||||||
|
"ZRANGE",
|
||||||
|
"ZREMRANGEBYRANK",
|
||||||
|
"ZREVRANGE",
|
||||||
|
"ZSCAN",
|
||||||
|
"ZUNION",
|
||||||
|
"ZUNIONSTORE",
|
||||||
|
// Streams
|
||||||
|
"XACK",
|
||||||
|
"XCLAIM",
|
||||||
|
"XDEL",
|
||||||
|
"XGROUP",
|
||||||
|
"XINFO",
|
||||||
|
"XLEN",
|
||||||
|
"XPENDING",
|
||||||
|
"XRANGE",
|
||||||
|
"XREAD",
|
||||||
|
"XREADGROUP",
|
||||||
|
"XREVRANGE",
|
||||||
|
"XTRIM",
|
||||||
|
// Strings
|
||||||
|
"BITCOUNT",
|
||||||
|
"BITFIELD",
|
||||||
|
"BITOP",
|
||||||
|
"BITPOS",
|
||||||
|
"DECR",
|
||||||
|
"DECRBY",
|
||||||
|
"GET",
|
||||||
|
"GETBIT",
|
||||||
|
"GETRANGE",
|
||||||
|
"INCR",
|
||||||
|
"INCRBY",
|
||||||
|
"INCRBYFLOAT",
|
||||||
|
"MGET",
|
||||||
|
"SETBIT",
|
||||||
|
"STRALGO",
|
||||||
|
"STRLEN",
|
||||||
|
// Transactions
|
||||||
|
"DISCARD",
|
||||||
|
"EXEC",
|
||||||
|
"MULTI",
|
||||||
|
"UNWATCH",
|
||||||
|
"WATCH")
|
||||||
|
.map(Arguments::of);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> list(String... args) {
|
||||||
|
return Arrays.asList(args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,324 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.api.db;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||||
|
|
||||||
|
public class SqlStatementSanitizerTest {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ArgumentsSource(SqlArgs.class)
|
||||||
|
void sanitizeSql(String original, String expected) {
|
||||||
|
SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(original);
|
||||||
|
assertThat(result.getFullStatement()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ArgumentsSource(CouchbaseArgs.class)
|
||||||
|
void normalizeCouchbase(String original, String expected) {
|
||||||
|
SqlStatementInfo result =
|
||||||
|
SqlStatementSanitizer.create(true).sanitize(original, SqlDialect.COUCHBASE);
|
||||||
|
assertThat(result.getFullStatement()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ArgumentsSource(SimplifyArgs.class)
|
||||||
|
void simplifySql(String original, Function<String, SqlStatementInfo> expecter) {
|
||||||
|
SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(original);
|
||||||
|
String expected = expecter.apply(original).getFullStatement();
|
||||||
|
assertThat(result.getFullStatement()).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void veryLongSelectStatementsAreOk() {
|
||||||
|
StringBuilder sb = new StringBuilder("SELECT * FROM table WHERE");
|
||||||
|
for (int i = 0; i < 2000; i++) {
|
||||||
|
sb.append(" column").append(i).append("=123 and");
|
||||||
|
}
|
||||||
|
String query = sb.toString();
|
||||||
|
|
||||||
|
String sanitizedQuery = query.replace("=123", "=?").substring(0, AutoSqlSanitizer.LIMIT);
|
||||||
|
SqlStatementInfo expected = SqlStatementInfo.create(sanitizedQuery, "SELECT", "table");
|
||||||
|
|
||||||
|
SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(query);
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lotsOfTicksDontCauseStackOverflowOrLongRuntimes() {
|
||||||
|
String s = "'";
|
||||||
|
SqlStatementSanitizer sanitizer = SqlStatementSanitizer.create(true);
|
||||||
|
for (int i = 0; i < 10000; i++) {
|
||||||
|
assertThat(sanitizer.sanitize(s)).isNotNull();
|
||||||
|
s += "'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void veryLongNumbersAreOk() {
|
||||||
|
String s = "";
|
||||||
|
for (int i = 0; i < 10000; i++) {
|
||||||
|
s += String.valueOf(i);
|
||||||
|
}
|
||||||
|
SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(s);
|
||||||
|
assertThat(result.getFullStatement()).isEqualTo("?");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void veryLongNumbersAtEndOfTableAreOk() {
|
||||||
|
String s = "A";
|
||||||
|
for (int i = 0; i < 10000; i++) {
|
||||||
|
s += String.valueOf(i);
|
||||||
|
}
|
||||||
|
SqlStatementInfo result = SqlStatementSanitizer.create(true).sanitize(s);
|
||||||
|
assertThat(result.getFullStatement()).isEqualTo(s.substring(0, AutoSqlSanitizer.LIMIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test32kTruncation() {
|
||||||
|
StringBuffer s = new StringBuffer();
|
||||||
|
for (int i = 0; i < 10000; i++) {
|
||||||
|
s.append("SELECT * FROM TABLE WHERE FIELD = 1234 AND ");
|
||||||
|
}
|
||||||
|
String sanitized = SqlStatementSanitizer.create(true).sanitize(s.toString()).getFullStatement();
|
||||||
|
assertThat(sanitized.length()).isLessThanOrEqualTo(AutoSqlSanitizer.LIMIT);
|
||||||
|
assertThat(sanitized).doesNotContain("1234");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void randomBytesDontCauseExceptionsOrTimeouts() {
|
||||||
|
Random r = new Random(0);
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
for (int c = 0; c < 1000; c++) {
|
||||||
|
sb.append((char) r.nextInt(Character.MAX_VALUE));
|
||||||
|
}
|
||||||
|
SqlStatementSanitizer.create(true).sanitize(sb.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SqlArgs implements ArgumentsProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("SELECT * FROM TABLE WHERE FIELD=1234", "SELECT * FROM TABLE WHERE FIELD=?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = 1234", "SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD>=-1234", "SELECT * FROM TABLE WHERE FIELD>=?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD<-1234", "SELECT * FROM TABLE WHERE FIELD<?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD <.1234", "SELECT * FROM TABLE WHERE FIELD <?"),
|
||||||
|
Arguments.of("SELECT 1.2", "SELECT ?"),
|
||||||
|
Arguments.of("SELECT -1.2", "SELECT ?"),
|
||||||
|
Arguments.of("SELECT -1.2e-9", "SELECT ?"),
|
||||||
|
Arguments.of("SELECT 2E+9", "SELECT ?"),
|
||||||
|
Arguments.of("SELECT +0.2", "SELECT ?"),
|
||||||
|
Arguments.of("SELECT .2", "SELECT ?"),
|
||||||
|
Arguments.of("7", "?"),
|
||||||
|
Arguments.of(".7", "?"),
|
||||||
|
Arguments.of("-7", "?"),
|
||||||
|
Arguments.of("+7", "?"),
|
||||||
|
Arguments.of("SELECT 0x0af764", "SELECT ?"),
|
||||||
|
Arguments.of("SELECT 0xdeadBEEF", "SELECT ?"),
|
||||||
|
Arguments.of("SELECT * FROM \"TABLE\"", "SELECT * FROM \"TABLE\""),
|
||||||
|
|
||||||
|
// Not numbers but could be confused as such
|
||||||
|
Arguments.of("SELECT A + B", "SELECT A + B"),
|
||||||
|
Arguments.of("SELECT -- comment", "SELECT -- comment"),
|
||||||
|
Arguments.of("SELECT * FROM TABLE123", "SELECT * FROM TABLE123"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT FIELD2 FROM TABLE_123 WHERE X<>7", "SELECT FIELD2 FROM TABLE_123 WHERE X<>?"),
|
||||||
|
|
||||||
|
// Semi-nonsensical almost-numbers to elide or not
|
||||||
|
Arguments.of("SELECT --83--...--8e+76e3E-1", "SELECT ?"),
|
||||||
|
Arguments.of("SELECT DEADBEEF", "SELECT DEADBEEF"),
|
||||||
|
Arguments.of("SELECT 123-45-6789", "SELECT ?"),
|
||||||
|
Arguments.of("SELECT 1/2/34", "SELECT ?/?/?"),
|
||||||
|
|
||||||
|
// Basic ' strings
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ''", "SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = 'words and spaces'",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ' an escaped '' quote mark inside'",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = '\\\\'", "SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = '\"inside doubles\"'",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = '\"$$$$\"'",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = 'a single \" doublequote inside'",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
|
||||||
|
// Some databases allow using dollar-quoted strings
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = $$$$", "SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = $$words and spaces$$",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = $$quotes '\" inside$$",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = $$\"''\"$$",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = $$\\\\$$", "SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
|
||||||
|
// Unicode, including a unicode identifier with a trailing number
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLEओ7 WHERE FIELD = 'ɣ'", "SELECT * FROM TABLEओ7 WHERE FIELD = ?"),
|
||||||
|
|
||||||
|
// whitespace normalization
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * \t\r\nFROM TABLE WHERE FIELD1 = 12344 AND FIELD2 = 5678",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD1 = ? AND FIELD2 = ?"),
|
||||||
|
|
||||||
|
// hibernate/jpa query language
|
||||||
|
Arguments.of("FROM TABLE WHERE FIELD=1234", "FROM TABLE WHERE FIELD=?"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CouchbaseArgs implements ArgumentsProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
|
||||||
|
return Stream.of(
|
||||||
|
// Some databases support/encourage " instead of ' with same escape rules
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = \"\"", "SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = \"words and spaces'\"",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = \" an escaped \"\" quote mark inside\"",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = \"\\\\\"", "SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = \"'inside singles'\"",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = \"'$$$$'\"",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"),
|
||||||
|
Arguments.of(
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = \"a single ' singlequote inside\"",
|
||||||
|
"SELECT * FROM TABLE WHERE FIELD = ?"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SimplifyArgs implements ArgumentsProvider {
|
||||||
|
|
||||||
|
static Function<String, SqlStatementInfo> expect(String operation, String table) {
|
||||||
|
return sql -> SqlStatementInfo.create(sql, operation, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Function<String, SqlStatementInfo> expect(String sql, String operation, String table) {
|
||||||
|
return ignored -> SqlStatementInfo.create(sql, operation, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
|
||||||
|
return Stream.of(
|
||||||
|
// Select
|
||||||
|
Arguments.of("SELECT x, y, z FROM schema.table", expect("SELECT", "schema.table")),
|
||||||
|
Arguments.of("SELECT x, y, z FROM `schema table`", expect("SELECT", "schema table")),
|
||||||
|
Arguments.of("SELECT x, y, z FROM \"schema table\"", expect("SELECT", "schema table")),
|
||||||
|
Arguments.of(
|
||||||
|
"WITH subquery as (select a from b) SELECT x, y, z FROM table",
|
||||||
|
expect("SELECT", null)),
|
||||||
|
Arguments.of("SELECT x, y, (select a from b) as z FROM table", expect("SELECT", null)),
|
||||||
|
Arguments.of(
|
||||||
|
"select delete, insert into, merge, update from table", expect("SELECT", "table")),
|
||||||
|
Arguments.of("select col /* from table2 */ from table", expect("SELECT", "table")),
|
||||||
|
Arguments.of("select col from table join anotherTable", expect("SELECT", null)),
|
||||||
|
Arguments.of("select col from (select * from anotherTable)", expect("SELECT", null)),
|
||||||
|
Arguments.of(
|
||||||
|
"select col from (select * from anotherTable) alias", expect("SELECT", null)),
|
||||||
|
Arguments.of(
|
||||||
|
"select col from table1 union select col from table2", expect("SELECT", null)),
|
||||||
|
Arguments.of(
|
||||||
|
"select col from table where col in (select * from anotherTable)",
|
||||||
|
expect("SELECT", null)),
|
||||||
|
Arguments.of("select col from table1, table2", expect("SELECT", null)),
|
||||||
|
Arguments.of("select col from table1 t1, table2 t2", expect("SELECT", null)),
|
||||||
|
Arguments.of("select col from table1 as t1, table2 as t2", expect("SELECT", null)),
|
||||||
|
Arguments.of(
|
||||||
|
"select col from table where col in (1, 2, 3)",
|
||||||
|
expect("select col from table where col in (?, ?, ?)", "SELECT", "table")),
|
||||||
|
Arguments.of("select col from table order by col, col2", expect("SELECT", "table")),
|
||||||
|
Arguments.of("select ąś∂ń© from źćļńĶ order by col, col2", expect("SELECT", "źćļńĶ")),
|
||||||
|
Arguments.of("select 12345678", expect("select ?", "SELECT", null)),
|
||||||
|
Arguments.of("/* update comment */ select * from table1", expect("SELECT", "table1")),
|
||||||
|
Arguments.of("select /*((*/abc from table", expect("SELECT", "table")),
|
||||||
|
Arguments.of("SeLeCT * FrOm TAblE", expect("SELECT", "table")),
|
||||||
|
|
||||||
|
// hibernate/jpa
|
||||||
|
Arguments.of("FROM schema.table", expect("SELECT", "schema.table")),
|
||||||
|
Arguments.of("/* update comment */ from table1", expect("SELECT", "table1")),
|
||||||
|
|
||||||
|
// Insert
|
||||||
|
Arguments.of(" insert into table where lalala", expect("INSERT", "table")),
|
||||||
|
Arguments.of("insert insert into table where lalala", expect("INSERT", "table")),
|
||||||
|
Arguments.of("insert into db.table where lalala", expect("INSERT", "db.table")),
|
||||||
|
Arguments.of("insert into `db table` where lalala", expect("INSERT", "db table")),
|
||||||
|
Arguments.of("insert into \"db table\" where lalala", expect("INSERT", "db table")),
|
||||||
|
Arguments.of("insert without i-n-t-o", expect("INSERT", null)),
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
Arguments.of("delete from table where something something", expect("DELETE", "table")),
|
||||||
|
Arguments.of(
|
||||||
|
"delete from `my table` where something something", expect("DELETE", "my table")),
|
||||||
|
Arguments.of(
|
||||||
|
"delete from \"my table\" where something something", expect("DELETE", "my table")),
|
||||||
|
Arguments.of("delete from 12345678", expect("delete from ?", "DELETE", null)),
|
||||||
|
Arguments.of("delete (((", expect("delete (((", "DELETE", null)),
|
||||||
|
|
||||||
|
// Update
|
||||||
|
Arguments.of(
|
||||||
|
"update table set answer=42", expect("update table set answer=?", "UPDATE", "table")),
|
||||||
|
Arguments.of(
|
||||||
|
"update `my table` set answer=42",
|
||||||
|
expect("update `my table` set answer=?", "UPDATE", "my table")),
|
||||||
|
Arguments.of(
|
||||||
|
"update \"my table\" set answer=42",
|
||||||
|
expect("update \"my table\" set answer=?", "UPDATE", "my table")),
|
||||||
|
Arguments.of("update /*table", expect("UPDATE", null)),
|
||||||
|
|
||||||
|
// Merge
|
||||||
|
Arguments.of("merge into table", expect("MERGE", "table")),
|
||||||
|
Arguments.of("merge into `my table`", expect("MERGE", "my table")),
|
||||||
|
Arguments.of("merge into \"my table\"", expect("MERGE", "my table")),
|
||||||
|
Arguments.of("merge table (into is optional in some dbs)", expect("MERGE", "table")),
|
||||||
|
Arguments.of("merge (into )))", expect("MERGE", null)),
|
||||||
|
|
||||||
|
// Unknown operation
|
||||||
|
Arguments.of("and now for something completely different", expect(null, null)),
|
||||||
|
Arguments.of("", expect(null, null)),
|
||||||
|
Arguments.of(null, expect(null, null)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue