soql-parser-js
Advanced tools
Changelog
4.0.0
April 13, 20201
💥 Breaking Changes 💥
Release 4.x has changed the way the groupBy
and having
clauses are parsed. (#149)
Previously, the groupBy
clause only allowed multiple entries for fields, but not functions.
The groupBy
and orderBy
are now always returned as arrays from parsed queries to normalize the returned data structure.
For backwards compatibility, a single groupBy
or orderBy
object is allowed to be passed in to composeQuery()
, but a parsed query will always return an array.
The Query
object now has
-groupBy?: GroupByClause;
+groupBy?: GroupByClause | GroupByClause[]; // a parsed query will always be undefined or an array
+having?: HavingClause;
orderBy?: OrderByClause | OrderByClause[]; // a parsed query will always be undefined or an array
Each groupBy clause
field
or fn
propertytype GroupByClause = GroupByFieldClause | GroupByFnClause;
-interface GroupByOptionalFieldsClause {
- having?: HavingClause;
-}
-interface GroupByFieldClause extends GroupByOptionalFieldsClause {
+interface GroupByFieldClause {
- field: string | string[];
+ field: string;
}
-interface GroupByFnClause extends GroupByOptionalFieldsClause {
+interface GroupByFnClause {
fn: FunctionExp;
}
Here are a few examples of how the groupBy
is parsed or expected when composing a query:
SELECT UserId, CALENDAR_MONTH(LoginTime) month FROM LoginHistory WHERE NetworkId != NULL GROUP BY UserId, CALENDAR_MONTH(LoginTime)
{
fields: [
{
type: 'Field',
field: 'UserId',
},
{
type: 'FieldFunctionExpression',
functionName: 'CALENDAR_MONTH',
rawValue: 'CALENDAR_MONTH(LoginTime)',
parameters: ['LoginTime'],
alias: 'month',
},
],
sObject: 'LoginHistory',
where: {
left: {
field: 'NetworkId',
operator: '!=',
literalType: 'NULL',
value: 'NULL',
},
},
groupBy: [
{ field: 'UserId' },
{
fn: {
functionName: 'CALENDAR_MONTH',
rawValue: 'CALENDAR_MONTH(LoginTime)',
parameters: ['LoginTime'],
},
},
],
}
SELECT ProductCode FROM Product2 GROUP BY ProductCode HAVING COUNT(Id) > 1 ORDER BY COUNT(Id) DESC
{
fields: [{ type: 'Field', field: 'ProductCode' }],
sObject: 'Product2',
groupBy: [{
field: 'ProductCode',
}],
having: {
left: {
operator: '>',
value: '1',
literalType: 'INTEGER',
fn: { rawValue: 'COUNT(Id)', functionName: 'COUNT', parameters: ['Id'] },
},
},
orderBy: [{
fn: { rawValue: 'COUNT(Id)', functionName: 'COUNT', parameters: ['Id'] },
order: 'DESC',
}],
}
SELECT SBQQ__Product__r.Name foo, SBQQ__Quote__c foo1 FROM SBQQ__Quoteline__c GROUP BY SBQQ__Quote__c, SBQQ__Product__r.Name
{
fields: [
{
type: 'FieldRelationship',
field: 'Name',
relationships: ['SBQQ__Product__r'],
rawValue: 'SBQQ__Product__r.Name',
alias: 'foo',
},
{
type: 'Field',
field: 'SBQQ__Quote__c',
alias: 'foo1',
},
],
sObject: 'SBQQ__Quoteline__c',
groupBy: [{ field: 'SBQQ__Quote__c' }, { field: 'SBQQ__Product__r.Name' }],
}
Changelog
3.2.0
March 27, 2021
A number of improvements to the formatter have been made with this release.
whereClauseOperatorsIndented
has been deprecated and will always be applied.newLineAfterKeywords
has been added and will ensure that there is always a new line after any keyword. (#137)TYPEOF
fields will now always be included on their own line be default, or will span multiple lines, split by keywords if newLineAfterKeywords
is set to true. (#135)SELECT Id, TYPEOF What WHEN Account THEN Phone, NumberOfEmployees WHEN Opportunity THEN Amount, CloseDate ELSE Name, Email END, Name FROM Event
formatOptions: { newLineAfterKeywords: true, fieldMaxLineLength: 1 },
SELECT
Id,
TYPEOF What
WHEN
Account
THEN
Phone, NumberOfEmployees
WHEN
Opportunity
THEN
Amount, CloseDate
ELSE
Name, Email
END,
Name
FROM
Event
Changelog
3.0.2
March 6, 2021
isString
from node utilsChanges also released to 2.5.6
Changelog
3.0.1
January 7, 20201
getFlattenedFields
did not properly handle the alias for an aggregate function within an aggregate query. (#131)Changelog
3.0.0
October 14, 2020
🔥 Breaking Changes 🔥
This version changes the WHERE
clause structure when using the NOT
operator t0 fix issue #122, and has implemented stricter type definitions.
The NOT
operator is now treated as a LogicalOperator
and will be set in the operator
field between left
and right
.
In cases where this is populated, the preceding left
condition will either be set to null
or will at most have the openParens
field populated.
The logicalPrefix
property has been removed from Condition
.
Example of the change in structure for queries using NOT - SELECT Id FROM Account WHERE NOT Id = '2'
{
"fields": [
{
"type": "Field",
"field": "Id"
}
],
"sObject": "Account",
"where": {
- "left": {
- "logicalPrefix": "NOT",
- "field": "Id",
- "operator": "=",
- "value": "'2'",
- "literalType": "STRING"
- }
+ "left": null
+ "operator": "NOT",
+ "right": {
+ "left": {
+ "field": "Id",
+ "operator": "=",
+ "value": "'2'",
+ "literalType": "STRING"
+ }
}
}
If you are using Typescript in strict mode, you may encounter some breaking changes to your types depending on how you pre-checked for the presence of fields.
Field
and FieldRelationship
are now made up of two types, one with and one without alias.
Condition
is now made up of multiple individual interfaces that represent different data types based on what data is populated.
OrderByClause
is now made up of multiple individual interfaces that represent different data types based on what data is populated.
GroupByClause
is now made up of multiple individual interfaces that represent different data types based on what data is populated.
HavingClause
is now made up of multiple individual interfaces that represent different data types based on what data is populated.
Previously you could have just done null/undefined checks in Typescript strict mode.
Now, to avoid using the any
type, you can use the newly introduced utility methods that provide type detection and type narrowing.
hasAlias()
isFieldSubquery()
isGroupByField()
isGroupByFn()
isHavingClauseWithRightCondition()
bisNegationCondition()
isOrderByField()
isOrderByFn()
isString()
isSubquery()
isValueCondition()
isValueFunctionCondition()
isValueQueryCondition()
isValueWithDateLiteralCondition()
isValueWithDateNLiteralCondition()
isWhereClauseWithRightCondition()
isWhereOrHavingClauseWithRightCondition()
Here is a summary of the core changes, view the Readme
for the comprehensive types.
export type FieldType =
| Field
+ | FieldWithAlias
| FieldFunctionExpression
| FieldRelationship
+ | FieldRelationshipWithAlias
| FieldSubquery
| FieldTypeOf;
-export interface WhereClause {
- left: Condition & ValueQuery;
- right?: WhereClause;
- operator?: LogicalOperator;
-}
+export type WhereClause = WhereClauseWithoutOperator | WhereClauseWithRightCondition;
-export interface Condition {
- openParen?: number;
- closeParen?: number;
- logicalPrefix?: LogicalPrefix;
- field?: string;
- fn?: FunctionExp;
- operator: Operator;
- value?: string | string[];
- literalType?: LiteralType | LiteralType[]; // If populated with STRING on compose, the value(s) will be wrapped in "'" if they are not already. - All other values ignored
- dateLiteralVariable?: number | number[]; // not required for compose, will be populated if SOQL is parsed
-}
+export type Condition =
+ | ValueCondition
+ | ValueWithDateLiteralCondition
+ | ValueWithDateNLiteralCondition
+ | ValueFunctionCondition
+ | NegationCondition;
-export interface OrderByClause {
- field?: string;
- fn?: FunctionExp;
- order?: OrderByCriterion;
- nulls?: NullsOrder;
-}
+export type OrderByClause = OrderByFieldClause | OrderByFnClause;
-export interface GroupByClause {
- field?: string | string[];
- fn?: FunctionExp;
- having?: HavingClause;
-}
+export type GroupByClause = GroupByFieldClause | GroupByFnClause;
-export interface HavingClause {
- left: Condition;
- right?: HavingClause;
- operator?: LogicalOperator;
-}
+export type HavingClause = HavingClauseWithoutOperator | HavingClauseWithRightCondition;
Changelog
2.5.4
April 12, 2020
getFlattenedFields
returns incorrect results if relationship field is grouped and you are grouping for only one field (#113)