Skip to content

Commit

Permalink
v1.0.3 - SQXSearchQuery support for simple conditionals (#1)
Browse files Browse the repository at this point in the history
* SQXSearchQuery: Support non-coordinating top level clauses

Allows `SQXSearchQuery.fromJson()` to interpret structures without top
level coordinating clauses, such as filters that consist only of 'IN' or
"CONTAINS_ANY" or "IS_NULL".

Also adds support for `toJson()` variant that exports only the filter
conditions, and `toConditionString()` that exports only the filter
expression.

* v1.0.3 - Parser Support for Simple Conditionals
  • Loading branch information
mcnielsen authored Apr 17, 2020
1 parent b00b2be commit 70dc92e
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 24 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@al/core",
"version": "1.0.2",
"version": "1.0.3",
"description": "Nepal Core",
"main": "./dist/index.cjs.js",
"types": "./dist/index.d.ts",
Expand Down
53 changes: 33 additions & 20 deletions src/search-client/parser/query.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,24 +129,25 @@ export class SQXSearchQuery
item.name = rawData.hasOwnProperty("name") ? rawData["name"] : null;

let parser = new SQXParser();
let parsed = parser.fromJson(rawData);

item.select = parsed.select || null;
item.where = parsed.where || null;
item.order_by = parsed.order_by || null;
item.group_by = parsed.group_by || null;
item.group_by_permuted = parsed.group_by_permuted || null;
item.limit = parsed.limit || null;
item.having = parsed.having || null;
item.time_range = parsed.time_range || null;
if ( item.where === null ) {
if ( parsed.hasOwnProperty("and") && parsed.and instanceof SQXOperatorAnd ) {
item.where = new SQXClauseWhere();
item.where.condition = parsed.and;
} else if ( parsed.hasOwnProperty("or") && parsed.or instanceof SQXOperatorOr ) {
item.where = new SQXClauseWhere();
item.where.condition = parsed.or;
}

if ( 'where' in rawData || 'select' in rawData || 'group_by' in rawData || 'order_by' in rawData ) {
// This is a complete query expression, including at least a WHERE clause and possibly other coordinating clauses
let parsed = parser.fromJson(rawData);

item.select = parsed.select || null;
item.where = parsed.where || null;
item.order_by = parsed.order_by || null;
item.group_by = parsed.group_by || null;
item.group_by_permuted = parsed.group_by_permuted || null;
item.limit = parsed.limit || null;
item.having = parsed.having || null;
item.time_range = parsed.time_range || null;
} else if ( Object.keys( rawData ).length === 1 ) {
// This is (presumably) a conditional only expression, which implies "WHERE" but just consists of operators
item.where = new SQXClauseWhere();
item.where.fromJson( rawData, ( op ) => parser.importElementFromJson( op ) );
} else {
console.warn("Warning: could not interpret raw data as query or filter construct; are you sure it's actually a query?" );
}

return item;
Expand All @@ -163,8 +164,13 @@ export class SQXSearchQuery

/**
* Exports an instance into native search format JSON.
*
* @param filterOnly If true, returns only the conditional expression (filters), without the WHERE clause.
*/
public toJson():any {
public toJson( filterOnly:boolean = false ):any {
if ( filterOnly ) {
return this.where && this.where.condition ? this.where.condition.toJson() : null;
}
let raw = {};
let properties = [ this.select, this.group_by, this.group_by_permuted, this.order_by, this.limit, this.having, this.where, this.time_range ]
.filter( el => el !== null )
Expand All @@ -182,7 +188,7 @@ export class SQXSearchQuery
/**
* Exports an instance into SQL-like syntax
*/
public toQueryString():string {
public toQueryString( filterOnly:boolean = false ):string {
let clauses = [];
if ( this.select ) {
clauses.push( this.select.toQueryString() );
Expand All @@ -209,6 +215,13 @@ export class SQXSearchQuery
return clauses.join(" " );
}

/**
* Exports filter criteria in SQL-like syntax
*/
public toConditionString():string {
return this.where && this.where.condition ? this.where.condition.toQueryString() : "";
}

/**
* Retrieves the conditions of the WHERE clause, which will always be an operator (either a coordinating operator like AND or OR, or an actual value test or function).
* If no condition is already specified, a new one will be created.
Expand Down
40 changes: 37 additions & 3 deletions src/search-client/sqx.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('SQX Parser', () => {
'ORDER BY fake:property DESC LIMIT 5000',
/* Test value aliases/substitutions */
'SELECT * WHERE message_content IN ( "Windows/7/Sucks", "Liallo The Cat" )',
'WHERE NOT log:property = "Big Moustache" AND log:moustache = null'
'WHERE NOT log:property = "Big Moustache" AND log:moustache = null',
];

for ( let i = 0; i < validQueries.length; i++ ) {
Expand All @@ -67,15 +67,49 @@ describe('SQX Parser', () => {
} catch ( e ) {
errorCount++;
console.warn("WARNING: query did not reconstite as expected" );
console.log( " - Input: [%s]", original );
console.log( " - Output: [%s]", reconstituted );
console.log( ` - Input: ${original}` );
console.log( ` - Output: ${reconstituted}` );
}
}

expect( errorCount ).to.equal( 0 );

});

it("should interpret query fragments successfully", () => {
let errorCount = 0;
let fragmentQueries = [
"kevin = true",
'( kevin = true AND moustache = false ) OR clipper = 0',
'( anchovies = "definitely" AND kippers = "SMOKED" ) AND ( c < 0 OR b > 0 )'
];

for ( let i = 0; i < fragmentQueries.length; i++ ) {
let original = fragmentQueries[i], reconstituted;
let json;
let queryString;
try {
let query = SQXSearchQuery.fromConditionString( original );
console.log( query );
queryString = query.toConditionString();
json = query.toJson( true );
let reinterpreted = SQXSearchQuery.fromJson( json );
reconstituted = reinterpreted.toConditionString();
if ( original !== reconstituted ) {
throw new Error("Original and reconstituted queries don't match!" );
}
} catch ( e ) {
errorCount++;
console.warn("WARNING: query did not reconstite as expected" );
console.log( ` - Input: ${original}` );
console.log( ` - Intermediary`, JSON.stringify( json, null, 4 ) );
console.log( ` - Output: ${reconstituted}` );
}
}

expect( errorCount ).to.equal( 0 );
} );

it( "should identify errors in badly formed queries", () => {
let errorCount = 0;
let brokenQueries = [
Expand Down

0 comments on commit 70dc92e

Please sign in to comment.