Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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