Skip to content

Commit

Permalink
Merge pull request #31 from paustint/feature-18
Browse files Browse the repository at this point in the history
multiple bugfixes
  • Loading branch information
paustint authored Oct 14, 2018
2 parents 20be43f + 7b1beee commit 283371e
Show file tree
Hide file tree
Showing 9 changed files with 389 additions and 28 deletions.
4 changes: 0 additions & 4 deletions debug/cli.js

This file was deleted.

5 changes: 4 additions & 1 deletion debug/test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
var soqlParserJs = require('../dist');

const query = `
SELECT Account.Name, (SELECT Contact.LastName FROM Account.Contacts) FROM Account
SELECT Title FROM FAQ__kav WHERE PublishStatus='online' and Language = 'en_US' and KnowledgeArticleVersion = 'ka230000000PCiy' UPDATE VIEWSTAT
`;

const parsedQuery = soqlParserJs.parseQuery(query, { logging: true });

console.log(JSON.stringify(parsedQuery, null, 2));

// SELECT amount, FORMAT(amount) Amt, convertCurrency(amount) editDate, FORMAT(convertCurrency(amount)) convertedCurrency FROM Opportunity where id = '12345'
// SELECT FORMAT(MIN(closedate)) Amt FROM opportunity
24 changes: 24 additions & 0 deletions docs/src/components/sample-queries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,30 @@ export default class SampleQueries extends React.Component<ISampleQueriesProps,
num: 31,
soql: `SELECT LeadSource, COUNT(Name) FROM Lead GROUP BY LeadSource HAVING COUNT(Name) > 100 and LeadSource > 'Phone'`,
},
{
key: 32,
num: 32,
soql: `SELECT Title FROM KnowledgeArticleVersion WHERE PublishStatus='online' WITH DATA CATEGORY Geography__c ABOVE usa__c`,
},
{
key: 33,
num: 33,
soql: `SELECT Title FROM Question WHERE LastReplyDate > 2005-10-08T01:02:03Z WITH DATA CATEGORY Geography__c AT (usa__c, uk__c)`,
},
{
key: 34,
num: 34,
soql: `SELECT UrlName FROM KnowledgeArticleVersion WHERE PublishStatus='draft' WITH DATA CATEGORY Geography__c AT usa__c AND Product__c ABOVE_OR_BELOW mobile_phones__c`,
},
{ key: 35, num: 35, soql: `SELECT Name, ID FROM Contact LIMIT 1 FOR VIEW` },
{ key: 36, num: 36, soql: `SELECT Name, ID FROM Contact LIMIT 1 FOR REFERENCE` },
{ key: 37, num: 37, soql: `SELECT Id FROM Account LIMIT 2 FOR UPDATE UPDATE TRACKING` },
{
key: 38,
num: 38,
soql: `SELECT amount, FORMAT(amount) Amt, convertCurrency(amount) editDate, FORMAT(convertCurrency(amount)) convertedCurrency FROM Opportunity where id = '12345'`,
},
{ key: 39, num: 39, soql: `SELECT FORMAT(MIN(closedate)) Amt FROM opportunity` },
];
};

Expand Down
33 changes: 31 additions & 2 deletions lib/SoqlComposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
HavingClause,
OrderByClause,
TypeOfField,
WithDataCategoryClause,
ForClause,
} from './models/SoqlQuery.model';
import * as utils from './utils';

Expand Down Expand Up @@ -72,8 +74,8 @@ export class Compose {
output += ` ${utils.get(query.sObjectPrefix, '.')}${query.sObject}${utils.get(query.sObjectAlias, '', ' ')}`;
this.log(output);

if (query.whereClause) {
output += ` WHERE ${this.parseWhereClause(query.whereClause)}`;
if (query.where) {
output += ` WHERE ${this.parseWhereClause(query.where)}`;
this.log(output);
}

Expand Down Expand Up @@ -103,6 +105,21 @@ export class Compose {
this.log(output);
}

if (query.withDataCategory) {
output += ` WITH DATA CATEGORY ${this.parseWithDataCategory(query.withDataCategory)}`;
this.log(output);
}

if (query.for) {
output += ` FOR ${query.for}`;
this.log(output);
}

if (query.update) {
output += ` UPDATE ${query.update}`;
this.log(output);
}

// TODO: add FOR support https://github.com/paustint/soql-parser-js/issues/19

return output;
Expand Down Expand Up @@ -195,4 +212,16 @@ export class Compose {
return output.trim();
}
}

private parseWithDataCategory(withDataCategory: WithDataCategoryClause): string {
return withDataCategory.conditions
.map(condition => {
const params =
condition.parameters.length > 1
? `(${condition.parameters.join(', ')})`
: `${condition.parameters.join(', ')}`;
return `${condition.groupName} ${condition.selector} ${params}`;
})
.join(' AND ');
}
}
40 changes: 34 additions & 6 deletions lib/SoqlListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ import {
OrderByClause,
Query,
WhereClause,
WithDataCategoryCondition,
GroupSelector,
ForClause,
TypeOfFieldCondition,
UpdateClause,
} from './models/SoqlQuery.model';
import { SoqlQueryConfig } from './SoqlParser';

export type currItem = 'field' | 'typeof' | 'from' | 'where' | 'groupby' | 'orderby' | 'having';
export type currItem = 'field' | 'typeof' | 'from' | 'where' | 'groupby' | 'orderby' | 'having' | 'withDataCategory';

export interface Context {
isSubQuery: boolean;
Expand All @@ -29,7 +34,7 @@ export class SoqlQuery implements Query {
subqueries: Query[];
sObject: string;
sObjectAlias?: string;
whereClause?: WhereClause;
where?: WhereClause;
limit?: number;
offset?: number;
groupBy?: GroupByClause;
Expand Down Expand Up @@ -166,6 +171,9 @@ export class Listener implements SOQLListener {
if (this.config.logging) {
console.log('enterData_category_group_name:', ctx.text);
}
this.context.tempData.conditions.push({
groupName: ctx.text,
});
}
exitData_category_group_name(ctx: Parser.Data_category_group_nameContext) {
if (this.config.logging) {
Expand All @@ -176,6 +184,8 @@ export class Listener implements SOQLListener {
if (this.config.logging) {
console.log('enterData_category_name:', ctx.text);
}
const condition = utils.getLastItem<WithDataCategoryCondition>(this.context.tempData.conditions);
condition.parameters.push(ctx.text);
}
exitData_category_name(ctx: Parser.Data_category_nameContext) {
if (this.config.logging) {
Expand Down Expand Up @@ -350,7 +360,8 @@ export class Listener implements SOQLListener {
console.log('enterFunction_name:', ctx.text);
}
if (this.context.currentItem === 'field') {
this.context.tempData.name = ctx.text;
const currFn: FunctionExp = this.context.tempData.fn || this.context.tempData;
currFn.name = ctx.text;
}
if (this.context.currentItem === 'having') {
this.context.tempData.currConditionOperation.left.fn.name = ctx.text;
Expand Down Expand Up @@ -467,7 +478,7 @@ export class Listener implements SOQLListener {
console.log('exitWhere_clause:', ctx.text);
}

this.getSoqlQuery().whereClause = this.context.tempData.data;
this.getSoqlQuery().where = this.context.tempData.data;
this.context.tempData = null;
}
enterGroupby_clause(ctx: Parser.Groupby_clauseContext) {
Expand Down Expand Up @@ -623,6 +634,7 @@ export class Listener implements SOQLListener {
console.log('enterFunction_call_spec:', ctx.text);
}
if (this.context.currentItem === 'field') {
// If nested function, init nested fn operator
this.context.tempData = {};
}
if (this.context.currentItem === 'having') {
Expand Down Expand Up @@ -655,7 +667,11 @@ export class Listener implements SOQLListener {
}
// COUNT(ID) or Count()
if (this.context.currentItem === 'field') {
this.context.tempData.text = ctx.text;
if (this.context.tempData.text) {
this.context.tempData.fn = {};
}
const currFn: FunctionExp = this.context.tempData.fn || this.context.tempData;
currFn.text = ctx.text;
}
if (this.context.currentItem === 'having') {
this.context.tempData.currConditionOperation.left.fn = {
Expand Down Expand Up @@ -760,7 +776,7 @@ export class Listener implements SOQLListener {
if (this.config.logging) {
console.log('enterTypeof_then_clause:', ctx.text);
}
const whenThenClause = this.context.tempData.typeOf.conditions[this.context.tempData.typeOf.conditions.length - 1];
const whenThenClause = utils.getLastItem<TypeOfFieldCondition>(this.context.tempData.typeOf.conditions);
whenThenClause.fieldList = ctx.getChild(1).text.split(',');
}
exitTypeof_then_clause(ctx: Parser.Typeof_then_clauseContext) {
Expand Down Expand Up @@ -1072,11 +1088,17 @@ export class Listener implements SOQLListener {
if (this.config.logging) {
console.log('enterWith_data_category_clause:', ctx.text);
}
this.context.currentItem = 'withDataCategory';
this.context.tempData = {
conditions: [],
};
}
exitWith_data_category_clause(ctx: Parser.With_data_category_clauseContext) {
if (this.config.logging) {
console.log('exitWith_data_category_clause:', ctx.text);
}
this.getSoqlQuery().withDataCategory = this.context.tempData;
this.context.tempData = null;
}
enterData_category_spec_list(ctx: Parser.Data_category_spec_listContext) {
if (this.config.logging) {
Expand All @@ -1102,6 +1124,8 @@ export class Listener implements SOQLListener {
if (this.config.logging) {
console.log('enterData_category_parameter_list:', ctx.text);
}
const condition = utils.getLastItem<WithDataCategoryCondition>(this.context.tempData.conditions);
condition.parameters = [];
}
exitData_category_parameter_list(ctx: Parser.Data_category_parameter_listContext) {
if (this.config.logging) {
Expand All @@ -1112,6 +1136,8 @@ export class Listener implements SOQLListener {
if (this.config.logging) {
console.log('enterData_category_selector:', ctx.text);
}
const condition = utils.getLastItem<WithDataCategoryCondition>(this.context.tempData.conditions);
condition.selector = ctx.text.toUpperCase() as GroupSelector;
}
exitData_category_selector(ctx: Parser.Data_category_selectorContext) {
if (this.config.logging) {
Expand Down Expand Up @@ -1249,6 +1275,7 @@ export class Listener implements SOQLListener {
if (this.config.logging) {
console.log('enterFor_value:', ctx.text);
}
this.getSoqlQuery().for = ctx.text.toUpperCase() as ForClause;
}
exitFor_value(ctx: Parser.For_valueContext) {
if (this.config.logging) {
Expand All @@ -1264,5 +1291,6 @@ export class Listener implements SOQLListener {
if (this.config.logging) {
console.log('exitUpdate_value:', ctx.text);
}
this.getSoqlQuery().update = ctx.text as UpdateClause;
}
}
25 changes: 22 additions & 3 deletions lib/models/SoqlQuery.model.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
export type LogicalOperator = 'AND' | 'OR';
export type Operator = '=' | '<=' | '>=' | '>' | '<' | 'LIKE' | 'IN' | 'NOT IN' | 'INCLUDES' | 'EXCLUDES';
export type TypeOfFieldConditionType = 'WHEN' | 'ELSE';
export type GroupSelector = 'ABOVE' | 'AT' | 'BELOW' | 'ABOVE_OR_BELOW';
export type LogicalPrefix = 'NOT';
export type ForClause = 'VIEW' | 'UPDATE' | 'REFERENCE';
export type UpdateClause = 'TRACKING' | 'VIEWSTAT';

export interface Query {
fields: Field[];
subqueries: Query[];
sObject: string;
sObjectAlias?: string;
sObjectPrefix?: string;
whereClause?: WhereClause;
where?: WhereClause;
limit?: number;
offset?: number;
groupBy?: GroupByClause;
having?: HavingClause;
orderBy?: OrderByClause | OrderByClause[];
withDataCategory?: WithDataCategoryClause;
for?: ForClause;
update?: UpdateClause;
}

export interface SelectStatement {
Expand All @@ -34,7 +42,7 @@ export interface TypeOfField {
}

export interface TypeOfFieldCondition {
type: 'WHEN' | 'ELSE';
type: TypeOfFieldConditionType;
objectType?: string; // not present when ELSE
fieldList: string[];
}
Expand All @@ -48,7 +56,7 @@ export interface WhereClause {
export interface Condition {
openParen?: number;
closeParen?: number;
logicalPrefix?: 'NOT';
logicalPrefix?: LogicalPrefix;
field: string;
operator: Operator;
value: string | string[];
Expand Down Expand Up @@ -86,4 +94,15 @@ export interface FunctionExp {
name?: string; // Count
alias?: string;
parameter?: string | string[];
fn?: FunctionExp; // used for nested functions FORMAT(MIN(CloseDate))
}

export interface WithDataCategoryClause {
conditions: WithDataCategoryCondition[];
}

export interface WithDataCategoryCondition {
groupName: string;
selector: GroupSelector;
parameters: string[];
}
4 changes: 4 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export function getIfTrue(val: boolean | null | undefined, returnStr: string): s
return isBoolean(val) && val ? returnStr : '';
}

export function getLastItem<T>(arr: T[]): T {
return arr[arr.length - 1];
}

export function getAsArrayStr(val: string | string[], alwaysParens: boolean = false): string {
if (isArray(val)) {
if (val.length > 0) {
Expand Down
Loading

0 comments on commit 283371e

Please sign in to comment.