|
1 | 1 | # Changelog |
2 | 2 |
|
| 3 | +## 2.0.0 |
| 4 | + |
| 5 | +### Summary |
| 6 | + |
| 7 | +Version 2.0 brings some significant bundle size and performance improvements. This library now uses [Chevrotain](https://github.com/SAP/chevrotain) instead of [antlr4](https://github.com/antlr/antlr4). With this change, everything related to parsing had to be re-written from scratch. Chevrotain uses pure javascript to handle lexing, parsing, and visiting the generated ast/cst as opposed to using a grammar file and generating a javascript parser based on the grammar. |
| 8 | + |
| 9 | +With this change, the data model was reviewed and analyzed, and there are some significant breaking changes to the data structures. Review the 🔥breaking changes🔥 below for a detailed description of each breaking change. |
| 10 | + |
| 11 | +#### Bundle Size |
| 12 | + |
| 13 | +To compare the bundle size, the following small program was written and then compiled using the default configuration of webpack, and the resulting webpack bundle was compared to determine the full size of the library. |
| 14 | + |
| 15 | +Minified, uncompressed: |
| 16 | + |
| 17 | +- Version 1.x: **545kb** |
| 18 | +- Version 2.0: **207kb** |
| 19 | + |
| 20 | +```javascript |
| 21 | +var soqlParser = require('soql-parser-js'); |
| 22 | + |
| 23 | +const query = soqlParser.parseQuery(`SELECT Id FROM Account WHERE Id = 'FOO'`); |
| 24 | +console.log('query', query); |
| 25 | +const soql = soqlParser.composeQuery(query); |
| 26 | +console.log('soql', soql); |
| 27 | +``` |
| 28 | + |
| 29 | +#### Benchmarks |
| 30 | + |
| 31 | +Performance testing was done by iterating the unit tests 60K times, here are the results: |
| 32 | + |
| 33 | +**Version 1.x parser** |
| 34 | + |
| 35 | +``` |
| 36 | +Library import (startup time): 0.8671 milliseconds |
| 37 | +Parsing: 58 X 1000 = 58000 iterations. |
| 38 | +Duration: 5.7648 seconds |
| 39 | +Average of 0.0994 milliseconds per query |
| 40 | +``` |
| 41 | + |
| 42 | +**Version 2.0 parser** |
| 43 | + |
| 44 | +``` |
| 45 | +Library import (startup time): 1.3793 milliseconds |
| 46 | +Parsing: 87 X 1000 = 87000 iterations. |
| 47 | +Duration: 3.6582 seconds |
| 48 | +Average of 0.0420 milliseconds per query |
| 49 | +``` |
| 50 | + |
| 51 | +### Breaking Changes 🔥 |
| 52 | + |
| 53 | +#### General Changes |
| 54 | + |
| 55 | +- The CLI was removed. |
| 56 | +- The `parseQuery()` `options` have changed. The only option allowed is `allowApexBindVariables` with will allow parsing queries with apex variables. |
| 57 | +- `rawValue` will always have a space between parameters `GROUPING(Id, BillingCountry)` |
| 58 | +- Some `literalType` values may have differing case from prior versions, regardless of the data input. |
| 59 | + - `TRUE`, `FALSE`, and all functions except those listed below will always be returned in uppercase, regardless of case of input. |
| 60 | + - **Exceptions**: |
| 61 | + - `toLabel`, `convertTimezone`, `convertCurrency` will always be in camelCase. |
| 62 | + - Added types for `DateLiteral` and `DateNLiteral` values. If you are using TypeScript, you can utilize these types. |
| 63 | +- A new `LiteralType` value was added for `APEX_BIND_VARIABLE`. |
| 64 | +- When composing functions in a where clause or group by clause, the `rawValue` will be preferred (if it exists) (no change here), but if rawValue is not provided, then the function will be composed using the `functionName` and `parameters`. |
| 65 | +- A new `LiteralType` value was added for `INTEGER_WITH_CURRENCY_PREFIX` and `DECIMAL_WITH_CURRENCY_PREFIX`. e.x. `USD500.01` |
| 66 | + |
| 67 | +#### Compose Query |
| 68 | + |
| 69 | +- `getComposedField()` is deprecated, you should now use `getField()`. `getComposedField()` will remain available for backward compatibility. |
| 70 | +- `getField()`/`getComposedField()` has the following changes: |
| 71 | + 1. `fn` property is has been deprecated (but still exists), you should now use `functionName` instead. |
| 72 | + 2. The `from` property has been removed for subqueries. The `relationshipName` is required to be populated to compose a subquery. |
| 73 | +- On the FormatOptions interface `fieldMaxLineLen` was renamed to `fieldMaxLineLength`. |
| 74 | +- Added support for `usingScope` - https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_using_scope.htm?search_text=format() |
| 75 | +- The `having` clause condition (e.x. `left`) now uses the `Condition` interface instead of having it's own structure. |
| 76 | + |
| 77 | +```diff |
| 78 | +export interface FormatOptions { |
| 79 | + numIndent?: number; |
| 80 | +- fieldMaxLineLen?: number; |
| 81 | ++ fieldMaxLineLength?: number; |
| 82 | + fieldSubqueryParensOnOwnLine?: boolean; |
| 83 | + whereClauseOperatorsIndented?: boolean; |
| 84 | + logging?: boolean; |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +#### Parse Query |
| 89 | + |
| 90 | +- `rawValue` will now be included on `Field` if `objectPrefix` is defined. |
| 91 | +- `alias` may be included on `Field`, if defined. |
| 92 | +- On `FieldFunctionExpression`, `fn` was renamed to `functionName`. this was done because all other usages of `fn` were `FunctionExp`, but it was a string in this case. |
| 93 | +- The `parameters` type on `FieldFunctionExpression` was modified to allow an array of varying types. |
| 94 | +- Removed `from` property from `FieldSubquery`. |
| 95 | +- `having` was removed from `QueryBase` and now lives as a property on `GroupByClause`. |
| 96 | +- On the `Condition` object, `literalType` may be an array. This will be an array if `value` is an array and there are variable types within the `value`. For example: `WHERE Foo IN ('a', null, 'b')` would produce `literalType: ['STRING', 'NULL', 'STRING']`. |
| 97 | +- The `GroupByClause` has the following modifications: |
| 98 | + - `field` is now optional, and will be populated only if the grouping is on a single field. |
| 99 | + - `type` has been renamed to `fn` and will be populated when `CUBE` and `ROLLUP` are used. |
| 100 | + - The `having` clause has been added as a top-level property to the `GroupByClause` and will be populated only if a `having` clause is present. |
| 101 | +- The `HavingCondition` interface has been removed and now uses the same `Condition` interface that the `WhereClause` uses, but without `valueQuery`. |
| 102 | + - The parser uses the same code to process both of these, so the `having` clause now supports the exact same capability as a `where` clause. |
| 103 | +- `FunctionExp` has the following modifications |
| 104 | + - `text` was renamed to `rawValue` to be more consistent with other places in the data model. |
| 105 | + - `name` was renamed to `functionName`. |
| 106 | + - `parameter` was renamed to `parameters` and the type was changed to `(string | FunctionExp)[]` to support nested functions. This will ALWAYS be an array now even if there is only one parameter. |
| 107 | + - `fn` was removed, as nested functionParameters are always stored as an entry in the `parameters` array. |
| 108 | + - Added support for `usingScope` - https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_using_scope.htm?search_text=format() |
| 109 | + |
| 110 | +```diff |
| 111 | +export type LiteralType = |
| 112 | + | 'STRING' |
| 113 | + | 'INTEGER' |
| 114 | + | 'DECIMAL' |
| 115 | ++ | 'INTEGER_WITH_CURRENCY_PREFIX' |
| 116 | ++ | 'DECIMAL_WITH_CURRENCY_PREFIX' |
| 117 | + | 'BOOLEAN' |
| 118 | + | 'NULL' |
| 119 | + | 'DATETIME' |
| 120 | + | 'DATE' |
| 121 | + | 'DATE_LITERAL' |
| 122 | + | 'DATE_N_LITERAL' |
| 123 | ++ | 'APEX_BIND_VARIABLE'; |
| 124 | + |
| 125 | ++ export type DateLiteral = |
| 126 | ++ | 'YESTERDAY' |
| 127 | ++ | 'TODAY' |
| 128 | ++ | 'TOMORROW' |
| 129 | ++ | 'LAST_WEEK' |
| 130 | ++ | 'THIS_WEEK' |
| 131 | ++ | 'NEXT_WEEK' |
| 132 | ++ | 'LAST_MONTH' |
| 133 | ++ | 'THIS_MONTH' |
| 134 | ++ | 'NEXT_MONTH' |
| 135 | ++ | 'LAST_90_DAYS' |
| 136 | ++ | 'NEXT_90_DAYS' |
| 137 | ++ | 'THIS_QUARTER' |
| 138 | ++ | 'LAST_QUARTER' |
| 139 | ++ | 'NEXT_QUARTER' |
| 140 | ++ | 'THIS_YEAR' |
| 141 | ++ | 'LAST_YEAR' |
| 142 | ++ | 'NEXT_YEAR' |
| 143 | ++ | 'THIS_FISCAL_QUARTER' |
| 144 | ++ | 'LAST_FISCAL_QUARTER' |
| 145 | ++ | 'NEXT_FISCAL_QUARTER' |
| 146 | ++ | 'THIS_FISCAL_YEAR' |
| 147 | ++ | 'LAST_FISCAL_YEAR' |
| 148 | ++ | 'NEXT_FISCAL_YEAR'; |
| 149 | + |
| 150 | ++ export type DateNLiteral = |
| 151 | ++ | 'YESTERDAY' |
| 152 | ++ | 'NEXT_N_DAYS' |
| 153 | ++ | 'LAST_N_DAYS' |
| 154 | ++ | 'N_DAYS_AGO' |
| 155 | ++ | 'NEXT_N_WEEKS' |
| 156 | ++ | 'LAST_N_WEEKS' |
| 157 | ++ | 'N_WEEKS_AGO' |
| 158 | ++ | 'NEXT_N_MONTHS' |
| 159 | ++ | 'LAST_N_MONTHS' |
| 160 | ++ | 'N_MONTHS_AGO' |
| 161 | ++ | 'NEXT_N_QUARTERS' |
| 162 | ++ | 'LAST_N_QUARTERS' |
| 163 | ++ | 'N_QUARTERS_AGO' |
| 164 | ++ | 'NEXT_N_YEARS' |
| 165 | ++ | 'LAST_N_YEARS' |
| 166 | ++ | 'N_YEARS_AGO' |
| 167 | ++ | 'NEXT_N_FISCAL_QUARTERS' |
| 168 | ++ | 'LAST_N_FISCAL_QUARTERS' |
| 169 | ++ | 'N_FISCAL_QUARTERS_AGO' |
| 170 | ++ | 'NEXT_N_FISCAL_YEARS' |
| 171 | ++ | 'LAST_N_FISCAL_YEARS' |
| 172 | ++ | 'N_FISCAL_YEARS_AGO'; |
| 173 | + |
| 174 | +export interface Field { |
| 175 | + type: 'Field'; |
| 176 | + field: string; |
| 177 | + objectPrefix?: string; |
| 178 | ++ rawValue?: string; |
| 179 | ++ alias?: string; |
| 180 | +} |
| 181 | + |
| 182 | +export interface FieldFunctionExpression { |
| 183 | + type: 'FieldFunctionExpression'; |
| 184 | +- fn: string; |
| 185 | ++ functionName: string; |
| 186 | +- parameters?: string[] | FieldFunctionExpression[]; |
| 187 | ++ parameters: (string | FieldFunctionExpression)[]; |
| 188 | + alias?: string; |
| 189 | + isAggregateFn?: boolean; |
| 190 | + rawValue?: string; |
| 191 | +} |
| 192 | + |
| 193 | +export interface FieldRelationship { |
| 194 | + type: 'FieldRelationship'; |
| 195 | + field: string; |
| 196 | + relationships: string[]; |
| 197 | + objectPrefix?: string; |
| 198 | + rawValue?: string; |
| 199 | ++ alias?: string; |
| 200 | +} |
| 201 | + |
| 202 | +export interface FieldSubquery { |
| 203 | + type: 'FieldSubquery'; |
| 204 | + subquery: Subquery; |
| 205 | +- from?: string; |
| 206 | +} |
| 207 | + |
| 208 | +export interface QueryBase { |
| 209 | + fields: FieldType[]; |
| 210 | + sObjectAlias?: string; |
| 211 | ++ usingScope?: string; |
| 212 | + where?: WhereClause; |
| 213 | + limit?: number; |
| 214 | + offset?: number; |
| 215 | + groupBy?: GroupByClause; |
| 216 | +- having?: HavingClause; |
| 217 | + orderBy?: OrderByClause | OrderByClause[]; |
| 218 | + withDataCategory?: WithDataCategoryClause; |
| 219 | + withSecurityEnforced?: boolean; |
| 220 | + for?: ForClause; |
| 221 | + update?: UpdateClause; |
| 222 | +} |
| 223 | + |
| 224 | +export interface WhereClause { |
| 225 | +- left: Condition; |
| 226 | ++ left: Condition & ValueQuery; |
| 227 | + right?: WhereClause; |
| 228 | + operator?: LogicalOperator; |
| 229 | +} |
| 230 | + |
| 231 | + |
| 232 | ++ export interface ValueQuery { |
| 233 | ++ valueQuery?: Query; |
| 234 | ++ } |
| 235 | + |
| 236 | +export interface Condition { |
| 237 | + openParen?: number; |
| 238 | + closeParen?: number; |
| 239 | + logicalPrefix?: LogicalPrefix; |
| 240 | + field?: string; |
| 241 | + fn?: FunctionExp; |
| 242 | + operator: Operator; |
| 243 | + value?: string | string[]; |
| 244 | +- valueQuery?: Query; |
| 245 | +- literalType?: LiteralType; |
| 246 | ++ literalType?: LiteralType | LiteralType[]; |
| 247 | + dateLiteralVariable?: number; |
| 248 | +} |
| 249 | + |
| 250 | +export interface GroupByClause { |
| 251 | +- field: string | string[]; |
| 252 | ++ field?: string | string[]; |
| 253 | +- type?: GroupByType; |
| 254 | ++ fn?: FunctionExp; |
| 255 | ++ having?: HavingClause; |
| 256 | +} |
| 257 | + |
| 258 | +export interface HavingClause { |
| 259 | ++ left: Condition; |
| 260 | + right?: HavingClause; |
| 261 | + operator?: LogicalOperator; |
| 262 | +} |
| 263 | + |
| 264 | +- export interface HavingCondition { |
| 265 | +- openParen?: number; |
| 266 | +- closeParen?: number; |
| 267 | +- field?: string; |
| 268 | +- fn?: FunctionExp; |
| 269 | +- operator: string; |
| 270 | +- value: string | number; |
| 271 | +- } |
| 272 | + |
| 273 | +export interface FunctionExp { |
| 274 | +- text?: string; |
| 275 | ++ rawValue?: string; |
| 276 | +- name?: string; |
| 277 | ++ functionName?: string; |
| 278 | + alias?: string; |
| 279 | +- parameter?: string | string[]; |
| 280 | ++ parameters?: (string | FunctionExp)[]; |
| 281 | + isAggregateFn?: boolean; |
| 282 | +- fn?: FunctionExp; |
| 283 | +} |
| 284 | + |
| 285 | +``` |
| 286 | + |
3 | 287 | ## 1.2.1 |
4 | 288 |
|
5 | 289 | - Queries with multiple consecutive left parens in a where clause were not correctly parsed. (#69) |
|
0 commit comments