I'm attempting to use Entity Framework Core with Npgsql to perform a raw SQL query where the WHERE clause part is pulled in from the database, with the actual 'value' supplied by a user. As a concrete example:
This part of the SQL is static
SELECT TITLE as TitleField,
GEOM as GeomField,
GEOM_X as XField,
GEOM_Y as YField
FROM fdw_public."MY_TABLE"
Then an administrator can set a WHERE clause up in the database, for example:
COLUMNNAME LIKE '%{search}%'
or
OTHER_COLUMNNAME = '{search}' AND COLUMNNAME = 'Something Static';
This isn't known at compile time and could be any valid WHERE clause.
The {search} part is a placeholder. I trust the clause coming from the database, this is only controlled by the application owner and if they want to break their own table, that's fine.
The end result is this SQL
SELECT TITLE as TitleField,
GEOM as GeomField,
GEOM_X as XField,
GEOM_Y as YField
FROM fdw_public."MY_TABLE"
WHERE COLUMNNAME LIKE '%{search}%'
The {search} part however is the user input (from a web application), so this needs parameterizing.
The following works, but is obviously very open to SQL Injection
var sql = $@"SELECT TITLE as TitleField,
GEOM as GeomField,
XFIELD as XField,
YFIELD as YField
FROM fdw_public."MY_TABLE"
WHERE {searchDefinition.WhereClause.Replace("{search}", searchTerm)}";
var dbResults = _context.DatabaseSearchResults.FromSqlRaw(sql).ToList();
So I attempted to use a parameter for the whole where clause
var sql = $@"SELECT TITLE as TitleField,
GEOM as GeomField,
XFIELD as XField,
YFIELD as YField
FROM fdw_public."MY_TABLE"
WHERE @whereClause";
var whereClauseParam = new Npgsql.NpgsqlParameter("@whereClause", searchDefinition.WhereClause.Replace("{search}", searchTerm));
var dbResults = _context.DatabaseSearchResults.FromSqlRaw(sql,whereClauseParam).ToList();
But this throws the following exception from Npgsql
42804: argument of WHERE must be type boolean, not type text
This does makes sense to me, as it feels wrong paramterizing ann entire clause, but I can't figure out a better way round it.
Ideally I need to parametrize just the search term, like this
var sql = $@"SELECT TITLE as TitleField,
GEOM as GeomField,
XFIELD as XField,
YFIELD as YField
FROM fdw_public."MY_TABLE"
WHERE {searchDefinition.WhereClause.Replace("{search}","@p0")}";
var searchTermParam = new Npgsql.NpgsqlParameter("p0", searchTerm);
var dbResults = _context.DatabaseSearchResults.FromSqlRaw(sql, searchTermParam).ToList();
But again, this doesn't work, understandably, as it is being interpreted literally.
I feel this may involve a change in how I'm doing this completely, or at worse, falling back to sanitizing the search string using some well known sanitization, but I don't want to do this, I want to use parameters.
Either way, to sum up, the requirement is this
- The WHERE clause must come from the database but it CAN be trusted
- The WHERE clause from the db can be any valid WHERE with a {search} placeholder, allowing us to do things like wildcarding (the admins are expected to know the data types and whether the value should be wrapped in quotes or not etc.)
- The search term must be sanitized