How to properly escape parameters for JSONB deep query in node-postgres - jsonb

When bypassing an ORM and doing direct queries to node-postgres, there are a nice pile of weird edge issues to keep in mind. For example, you have probably already encountered the fact that camelCaseColumns have to be in double-quotes, and also parameterized type casting…
client.query(`SELECT id, "authorFirstName", "authorLastName" FROM books WHERE isbn = $1::int`, [1444723448]`)
client.query(`SELECT id FROM books WHERE "authorLastName" = $1::string`, ['King']`)
JSON and JSONB types add another aspect of weirdness. The important thing to keep in mind is, "$1" is not merely a variable placeholder; it is an indicator of a discrete unit of information.
Given a table where characters is a column of type JSONB, this will not work…
client.query(
`SELECT id FROM books WHERE characters #> ([ {'name': $1::string} ])`,
['Roland Deschain']
)
This fails because the unit of information is the JSON object, not a string you're inserting into a blob of text.

This is a little more clear when one looks at a simpler SELECT and an UPDATE…
const userData = await client.query(
`SELECT characters FROM books WHERE id = $1::uuid`,
[ some_book_id ]
)
const newCharacters = JSON.stringify([
...userData[0].characters,
{ name: 'Jake' },
{ name: 'Eddie' },
{ name: 'Odetta' }
])
await this.databaseService.executeQuery(
`UPDATE books SET characters = $1::jsonb WHERE id = $2::uuid`,
[ newCharacters, some_book_id ]
)
The deep search query should be formed thusly:
const searchBundle = JSON.stringify([
{'name': 'Roland Deschain'}
])
client.query(
`SELECT id FROM books WHERE characters #> ($1::jsonb)`,
[searchBundle]
)

Related

How to search for substring in a list of strings in episerver find

I have a list of strings like this
"Users": [
"usrName|Fullname|False|0|False|False",
"usrName|Fullname|False|0|False|False",
"usrName|Fullname|False|0|False|False",
"usrName|Fullname|False|0|False|False",
"usrName|Fullname|False|0|False|False",
]
In my episerver/optimizely code I want to match items. I have written this line of code
searchResult.Filter(x => x.Users.MatchContained(k=> k.Split('|')[3], "0"));
I am trying to get all Users where after splitiing on 'pipe' I get 0 at index 3. I am not getting the result, infact I get an exception.
What am I doing wrong here?
Since you left out A LOT of details these are my assumptions
Users is an IList<string> Users
You are trying to get all Users within that list/array where index 3 equals 0
What we don't know is, among others
Is there more than one page instance that have the Users instance filled?
Anyway, this cannot be solved using any Find API with the current system design. Instead you need to rely on linq to parse the result, but then the Find implementation may not be necessary.
var searchClient = SearchClient.Instance;
var search = searchClient.Search<BlogPage>();
var result = search.GetContentResult();
var usersResult = result
.SelectMany(x => x.Users)
.Where(x => x.Split('|')[3].Equals("0"));
This would create and return an object similar to this, since I'm using SelectMany the array would contain all users throughout the systems where there are matched pages from the search result.
If you for whatever reason would like to keep or see some page properties there are alternative approaches where you construct a new object within a select and remove any object where there where not matched users
var searchClient = SearchClient.Instance;
var search = searchClient.Search<BlogPage>();
var result = search.GetContentResult();
var usersResult = result
.Select(p => new
{
PageName = p.Name,
ContentLink = p.ContentLink.ToString(),
Users = p.Users.Where(x => x.Split('|')[3].Equals("0"))
})
.Where(x => x.Users.Any());
If you like to keep using Find for this kind of implementations you must store the data in a better way than strings with delimiters.

How to declare strings in enum

With graphql, enum can make a predefined list of elements but strings don't work.
For example:
enum Dias {
lunes
martes
miércoles
jueves
viernes
sábado
domingo
}
This returns an error GraphQLError: Syntax Error: Cannot parse the unexpected character "\u00E9".
how is it possible to make a predefined list of strings?
Edit: to give more context, I want to reflect the database schema, which is like this (with mongoose):
dias: {
type: String,
enum: ['lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo'],
lowercase: true,
required: true
}
Your syntax is correct, the only issue is that the "é" and "á" characters are not supported. The specification outlines rules for naming operations, fields, etc. The supported pattern is:
/[_A-Za-z][_0-9A-Za-z]*/
Furthermore:
Names in GraphQL are limited to this ASCII subset of possible characters to support interoperation with as many other systems as possible.
So, unfortunately, you will have to convert those accented characters to valid ones in order for your schema to be considered valid.
EDIT: You could create a custom scalar. Here's a function that takes a name, description and an array and returns a custom scalar:
const makeCustomEnumScalar = (name, description, validValues) => {
const checkValue = (value) => {
const coerced = String(value)
if (!validValues.includes(coerced)) {
throw new TypeError(`${coerced} is not a valid value for scalar ${name}`)
}
return coerced
}
return new GraphQLScalarType({
name,
description,
serialize: checkValue,
parseValue: checkValue,
parseLiteral: (ast) => checkValue(ast.value),
})
}
Now you can do something like:
const DayOfWeek = makeCustomEnumScalar('Day of Week', 'day of week enum', [
'lunes',
'martes',
'miércoles',
'jueves',
'viernes',
'sábado',
'domingo'
])
Add it to your resolvers:
const resolvers = {
DayOfWeek,
// Query, Mutation, etc.
}
And your type definitions:
scalar DayOfWeek
And then you can use it like any other scalar. If an invalid value is provided as an input or output by a query, then GraphQL will throw an error like with an enum. The only caveat is that if you're entering the values directly into your query (as opposed to using variables), you'll still need to wrap them in double quotes.

How do I query across child and parent fields using a multimatch type query in ElasticSearch?

Example:
Parent doc
id: 1
field1: politics
field2: donkeys
Child doc
parent_id: 1
field1: prose
I would like to be able to search for the words 'politics donkey prose' (as an AND query, but not caring which fields any of the words match) and have it match the parent document. Is this possible? Or do I need to start rolling up the children as a big field within the parent (very undesirable because there can be many children)?
I am preferably looking for the solution in Java, but I will take it any way I can get it!
I think this might do it:
String parent_fields = "f1,f2";
String child_fields = "f3,f4";
BoolQueryBuilder allQueries = QueryBuilders.boolQuery();
for( String term : terms )
{
BoolQueryBuilder booly = QueryBuilders.boolQuery();
for( String field : parent_fields.split( "," ) )
{
booly.should( QueryBuilders.termQuery( field, term ) );
}
for( String field : child_fields.split( "," ) )
{
booly.should( QueryBuilders.topChildrenQuery( child_type, QueryBuilders.termQuery( field, term ) ) );
}
allBoolies.must( booly );
}

LINQ Lamba Select all from table where field contains all elements in list

I need a Linq statement that will select all from a table where a field contains all elements in a list<String> while searching other fields for the entire string regardless of words.
It's basically just a inclusive word search on a field where all words need to be in the record and string search on other fields.
Ie I have a lookup screen that allows the user to search AccountID or Detail for the entire search string, or search clientID for words inclusive words, I'll expand this to the detail field if I can figure out the ClientId component.
The complexity is that the AccountId and Detail are being searched as well which Basically stops me from doing the foreach in the second example due to the "ors".
Example 1, this gives me an the following error when I do query.Count() afterwards:
query.Count(); 'query.Count()' threw an exception of type 'System.NotSupportedException' int {System.NotSupportedException}
+base {"Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator."} System.SystemException {System.NotSupportedException}
var StrList = searchStr.Split(' ').ToList();
query = query.Where(p => p.AccountID.Contains(searchStr) || StrList.All(x => p.clientID.Contains(x)) || p.Detail.Contains(searchStr));
Example 2, this gives me an any search word type result:
var StrList = searchStr.Split(' ').ToList();
foreach (var item in StrList)
query = query.Where(p => p.AccountID.Contains(searchStr) || p.clientID.Contains(item) || p.Detail.Contains(searchStr));
Update
I have a table with 3 fields, AccountID, ClientId, Details
Records
Id, AccountID, CLientId, Details
1, "123223", "bobo and co", "this client suxs"
2, "654355", "Jesses hair", "we like this client and stuff"
3, "456455", "Microsoft", "We love Mircosoft"
Search examples
searchStr = "232"
Returns Record 1;
searchStr = "bobo hair"
Returns no records
searchStr = "bobo and"
Returns Record 1
searchStr = "123 bobo and"
Returns returns nothing
The idea here is:
if the client enters a partial AccountId it returns stuff,
if the client wants to search for a ClientId they can type and cancel down clients by search terms, ie word list. due to the large number of clients the ClientID Will need to contain all words (in any order)
I know this seems strange but it's just a simple interface to find accounts in a powerful way.
I think there are 2 solutions to your problem.
One is to count the results in memory like this:
int count = query.ToList().Count();
The other one is to not use All in your query:
var query2 = query;
foreach (var item in StrList)
{
query2 = query2.Where(p => p.clientID.Contains(item));
}
var result = query2.Union(query.Where(p => p.AccountID.Contains(searchStr) || p.Detail.Contains(searchStr)));
The Union at the end acts like an OR between the 2 queries.

LINQ return records where string[] values match Comma Delimited String Field

I am trying to select some records using LINQ for Entities (EF4 Code First).
I have a table called Monitoring with a field called AnimalType which has values such as
"Lion,Tiger,Goat"
"Snake,Lion,Horse"
"Rattlesnake"
"Mountain Lion"
I want to pass in some values in a string array (animalValues) and have the rows returned from the Monitorings table where one or more values in the field AnimalType match the one or more values from the animalValues. The following code ALMOST works as I wanted but I've discovered a major flaw with the approach I've taken.
public IQueryable<Monitoring> GetMonitoringList(string[] animalValues)
{
var result = from m in db.Monitorings
where animalValues.Any(c => m.AnimalType.Contains(c))
select m;
return result;
}
To explain the problem, if I pass in animalValues = { "Lion", "Tiger" } I find that three rows are selected due to the fact that the 4th record "Mountain Lion" contains the word "Lion" which it regards as a match.
This isn't what I wanted to happen. I need "Lion" to only match "Lion" and not "Mountain Lion".
Another example is if I pass in "Snake" I get rows which include "Rattlesnake". I'm hoping somebody has a better bit of LINQ code that will allow for matches that match the exact comma delimited value and not just a part of it as in "Snake" matching "Rattlesnake".
This is a kind of hack that will do the work:
public IQueryable<Monitoring> GetMonitoringList(string[] animalValues)
{
var values = animalValues.Select(x => "," + x + ",");
var result = from m in db.Monitorings
where values.Any(c => ("," + m.AnimalType + ",").Contains(c))
select m;
return result;
}
This way, you will have
",Lion,Tiger,Goat,"
",Snake,Lion,Horse,"
",Rattlesnake,"
",Mountain Lion,"
And check for ",Lion," and "Mountain Lion" won't match.
It's dirty, I know.
Because the data in your field is comma delimited you really need to break those entries up individually. Since SQL doesn't really support a way to split strings, the option that I've come up with is to execute two queries.
The first query uses the code you started with to at least get you in the ballpark and minimize the amount of data you're retrieving. It converts it to a List<> to actually execute the query and bring the results into memory which will allow access to more extension methods like Split().
The second query uses the subset of data in memory and joins it with your database table to then pull out the exact matches:
public IQueryable<Monitoring> GetMonitoringList(string[] animalValues)
{
// execute a query that is greedy in its matches, but at least
// it's still only a subset of data. The ToList()
// brings the data into memory, so to speak
var subsetData = (from m in db.Monitorings
where animalValues.Any(c => m.AnimalType.Contains(c))
select m).ToList();
// given that subset of data in the List<>, join it against the DB again
// and get the exact matches this time
var result = from data in subsetData
join m in db.Monitorings on data.ID equals m.ID
where data.AnimalType.Split(',').Intersect(animalValues).Any ()
select m;
return result;
}

Resources