Skip to content

Commit fd2d44b

Browse files
authored
Merge pull request #68 from paustint/feature-65
Make compose functions public
2 parents fa93268 + 1ed9309 commit fd2d44b

File tree

8 files changed

+248
-90
lines changed

8 files changed

+248
-90
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 1.2.0
4+
5+
- Changed compose methods to public to allow external access (#65)
6+
- Fixed lodash security vulnerability
7+
- Updated README to reflect new changes and other minor changes
8+
39
## 1.1.1
410

511
- Removed files that accidentally got included with release with update of `release-it`

README.md

Lines changed: 150 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,43 +6,90 @@
66

77
## Description
88

9+
:sunny: This library allows parsing Salesforce SOQL queries using JavaScript or Typescript. Works in the browser and node. :sunny:
10+
911
SOQL Parser JS provides the following capabilities:
1012

1113
1. Parse a SOQL query into a usable data structure.
12-
2. Turn a parsed query data structure back into well a SOQL query with various format options.
13-
3. Check if a SOQL query is syntactically valid (**note**: some cases may be structurally sound but not allowed by SFDC).
14+
2. Turn a parsed data structure back into a well formed SOQL query with various format options.
15+
3. Check if a SOQL query is syntactically valid (**note**: some cases may be structurally valid but not allowed by Salesforce, e.x. invalid field name).
1416

1517
This library is written in Typescript and all type definitions are included with the library for your benefit if you choose to use Typescript or use VSCode's automatic type checking.
1618

17-
_Warning_: antlr4 is dependency for this library and is a rather large library (~600 KB) and is required for the parser to function, use in the browser with care.
19+
:warning: antlr4 is dependency for this library and is a rather large library (~600 KB) and is required for the parser to function. Consider using dynamic imports to achieve lazy loading.
1820

1921
## Examples
2022

21-
For an example of the parser, check out the [example application](https://paustint.github.io/soql-parser-js/).
23+
Want to try it out? [Check out the demo](https://paustint.github.io/soql-parser-js/).
2224

23-
Have a look through the unit tests for many more examples.
25+
Look through the [unit tests](./test/TestCases.ts) for additional examples.
2426

2527
# Usage
2628

2729
## Parsing
2830

29-
Parsing a SOQL query can be completed by calling `parseQuery(soqlQueryString, options)` and a `Query` data structure will be returned;
31+
Parsing a SOQL query can be completed by calling `parseQuery(soqlQueryString, options)`. A `Query` data structure will be returned;
3032

3133
#### Typescript / ES6
3234

3335
```typescript
34-
import { parseQuery } from 'soql-parser-js'; // TS / ES6 imports
36+
import { parseQuery } from 'soql-parser-js';
3537
// var soqlParserJs = require('soql-parser-js'); // node's require format - usage: soqlParserJs.parseQuery()
3638

37-
const soql =
38-
'SELECT UserId, COUNT(Id) from LoginHistory WHERE LoginTime > 2010-09-20T22:16:30.000Z AND LoginTime < 2010-09-21T22:16:30.000Z GROUP BY UserId';
39+
const soql = `
40+
SELECT UserId, COUNT(Id)
41+
FROM LoginHistory
42+
WHERE LoginTime > 2010-09-20T22:16:30.000Z
43+
AND LoginTime < 2010-09-21T22:16:30.000Z
44+
GROUP BY UserId';
45+
`;
3946

4047
const soqlQuery = parseQuery(soql);
41-
// const soqlQuery = soqlParserJs.parseQuery(soql); // using require()
4248

4349
console.log(JSON.stringify(soqlQuery, null, 2));
4450
```
4551

52+
**Results**
53+
54+
```json
55+
{
56+
"fields": [
57+
{
58+
"type": "Field",
59+
"field": "UserId"
60+
},
61+
{
62+
"type": "FieldFunctionExpression",
63+
"rawValue": "COUNT(Id)",
64+
"fn": "COUNT",
65+
"isAggregateFn": true,
66+
"parameters": ["Id"]
67+
}
68+
],
69+
"sObject": "LoginHistory",
70+
"where": {
71+
"left": {
72+
"field": "LoginTime",
73+
"operator": ">",
74+
"value": "2010-09-20T22:16:30.000Z",
75+
"literalType": "DATETIME"
76+
},
77+
"operator": "AND",
78+
"right": {
79+
"left": {
80+
"field": "LoginTime",
81+
"operator": "<",
82+
"value": "2010-09-21T22:16:30.000Z",
83+
"literalType": "DATETIME"
84+
}
85+
}
86+
},
87+
"groupBy": {
88+
"field": "UserId"
89+
}
90+
}
91+
```
92+
4693
#### Options
4794

4895
```typescript
@@ -54,13 +101,13 @@ export interface SoqlQueryConfig {
54101

55102
## Composing
56103

57-
Composing a query will turn a Query object back to a SOQL query. The exact same data structure returned from `parseQuery()` can be used,
58-
but there are many use-cases where you may need to build your own data structure to compose a query.
104+
Composing a query will turn a Query object back to a SOQL query string. The exact same data structure returned from `parseQuery()` can be used,
105+
but depending on your use-case, you may need to build your own data structure to compose a query.
59106
These examples show building your own Query object with the minimum required fields.
60107

61-
**Note:** For some operators, they may be converted to upper case (e.x. NOT, AND)
108+
:page_facing_up: **Note:** Some operators may be converted to upper case (e.x. NOT, AND)
62109

63-
**Note:** There are a number of fields populated on the Query object when `parseQuery()` is called that are not required to compose a query. Look at the examples below and the comments in the data model for more information.
110+
:page_facing_up: **Note:** There are a number of fields populated on the Query object when `parseQuery()` is called that are not required to compose a query. Look at the examples below and the comments in the data model for more information.
64111

65112
**The base query object is shaped like this:**
66113

@@ -84,7 +131,7 @@ The easiest way to build the fields is to call the utility function `getComposed
84131

85132
### Example
86133

87-
This is the query that will be composed
134+
This is the query that will be composed programmatically
88135

89136
```sql
90137
SELECT Id, Name, FORMAT(Amount) MyFormattedAmount,
@@ -139,7 +186,7 @@ const soqlQuery = {
139186
operator: '=',
140187
value: 'Closed Won',
141188
// literalType is optional, but if set to STRING and our value is not already wrapped in "'", they will be added
142-
// All other literalType values are ignored in composing a query
189+
// All other literalType values are ignored when composing a query
143190
literalType: 'STRING',
144191
},
145192
},
@@ -152,8 +199,9 @@ const composedQuery = composeQuery(soqlQuery, { format: true });
152199
console.log(composedQuery);
153200
```
154201

155-
In the above examples, we made use of `getComposedField(input: string | ComposeFieldInput)` to help easily compose the fields. The input expects a string or one of the following shapes of data below. An error will be thrown if the data passed in is not one of the following shapes:
156-
and will return a `FieldType` object.
202+
In the example above, we made use of `getComposedField(input: string | ComposeFieldInput)` to compose the fields. The input expects a string or one of the following data structures listed below. An error will be thrown if the data passed in is not one of the following shapes.
203+
204+
This returns a `FieldType` object.
157205

158206
```typescript
159207
export interface ComposeField {
@@ -183,9 +231,53 @@ export interface ComposeFieldTypeof {
183231
}
184232
```
185233

234+
### Composing a partial query
235+
236+
If you need to compose just a part of a query instead of the entire query, you can create an instance of the Compose class directly.
237+
238+
For example, if you just need the "WHERE" clause from a query as a string, you can do the following:
239+
240+
```typescript
241+
import { Compose, getComposedField, parseQuery } from 'soql-parser-js';
242+
243+
const soql = `SELECT Id FROM Account WHERE Name = 'Foo'`;
244+
const parsedQuery = parseQuery(soql);
245+
246+
// Results of Parsed Query:
247+
// const parsedQuery = {
248+
// fields: [
249+
// {
250+
// type: 'Field',
251+
// field: 'Id',
252+
// },
253+
// ],
254+
// sObject: 'Account',
255+
// where: {
256+
// left: {
257+
// field: 'Name',
258+
// operator: '=',
259+
// value: "'Foo'",
260+
// literalType: 'STRING',
261+
// },
262+
// },
263+
// };
264+
265+
// Create a new instance of the compose class and set the autoCompose to false to avoid composing the entire query
266+
const composer = new Compose(parsedQuery, { autoCompose: false });
267+
268+
269+
const whereClause = composer.parseWhereClause(parsedQuery.where);
270+
271+
console.log(whereClause);
272+
// Name = 'Foo'
273+
274+
}
275+
276+
```
277+
186278
## Checking if a Query is Valid
187279

188-
This will parse the AST tree to confirm the syntax is valid, but will not parse the tree into a data structure.
280+
This will parse a Query to confirm valid syntax, but will not parse into the Query data structure, which will have a small performance gain.
189281
This method is faster than parsing the full query.
190282

191283
Options:
@@ -204,25 +296,13 @@ const soql =
204296

205297
const isValid = isQueryValid(soql);
206298

207-
console.log('isValid', isValid);
208-
```
209-
210-
#### Node
211-
212-
```javascript
213-
var soqlParserJs = require('soql-parser-js');
214-
215-
const soql =
216-
'SELECT UserId, COUNT(Id) from LoginHistory WHERE LoginTime > 2010-09-20T22:16:30.000Z AND LoginTime < 2010-09-21T22:16:30.000Z GROUP BY UserId';
217-
218-
const isValid = isQueryValid(soql);
219-
220-
console.log('isValid', isValid);
299+
console.log(isValid);
221300
```
222301

223302
## Format Query
224303

225-
This function is provided as a convenience and just calls parse and compose under the hood.
304+
This function is provided as a convenience and just calls parse and compose.
305+
[Check out the demo](https://paustint.github.io/soql-parser-js/) to see the outcome of the various format options.
226306

227307
```typescript
228308
import { formatQuery } from 'soql-parser-js';
@@ -317,6 +397,7 @@ export interface SoqlComposeConfig {
317397
logging: boolean; // default=false
318398
format: boolean; // default=false
319399
formatOptions?: FormatOptions;
400+
autoCompose: boolean; // default=true
320401
}
321402

322403
export interface FormatOptions {
@@ -333,18 +414,19 @@ export interface FormatOptions {
333414
The following utility functions are available:
334415

335416
1. `getComposedField(input: string | ComposeFieldInput)`
336-
1. Convenience method to construct fields in the correct data format. See example usage in the Compose example.
337-
1. `isSubquery(query: Query | Subquery)`
338-
1. Returns true if the data passed in is a subquery
339-
1. `getFlattenedFields(query: Query)`
340-
1. This provides a list of fields that are stringified and flattened in order to access data from a returned API call from Salesforce. Refer to `tests/publicUtils.spec.ts` for usage examples.
417+
1. Convenience method to construct fields in the correct data format. See example usage in the Compose example.
418+
2. `isSubquery(query: Query | Subquery)`
419+
1. Returns true if the data passed in is a subquery
420+
3. `getFlattenedFields(query: Query)`
421+
1. Flatten a Salesforce record based on the parsed SOQL Query. this is useful if you have relationships in your query and want to show the results in a table, using `.` dot notation for the relationship fields.
422+
2. Refer to `tests/publicUtils.spec.ts` for usage examples.
341423

342424
## Data Models
343425

344-
### Query
345-
346426
These are all available for import in your typescript projects
347427

428+
### Query
429+
348430
```typescript
349431
export type LogicalOperator = 'AND' | 'OR';
350432
export type Operator = '=' | '!=' | '<=' | '>=' | '>' | '<' | 'LIKE' | 'IN' | 'NOT IN' | 'INCLUDES' | 'EXCLUDES';
@@ -498,6 +580,33 @@ export interface WithDataCategoryCondition {
498580
}
499581
```
500582

583+
### Compose Class
584+
585+
You only need to interact with the compose class if you want to compose part of a SOQL query
586+
587+
```typescript
588+
// Instance Properties
589+
logging: boolean; // default to false
590+
format: boolean; // default to false
591+
query: string;
592+
formatter: Formatter;
593+
594+
// Instance Methods:
595+
constructor(private soql: Query, config?: Partial<SoqlComposeConfig>)
596+
start(): void
597+
// Pass in part of the parsed query to get the string representation for a given segment of a query
598+
parseQuery(query: Query | Subquery): string
599+
parseFields(fields: FieldType[]): string[]
600+
parseTypeOfField(typeOfField: FieldTypeOf): string
601+
parseFn(fn: FunctionExp): string
602+
parseWhereClause(where: WhereClause): string
603+
parseGroupByClause(groupBy: GroupByClause): string
604+
parseHavingClause(having: HavingClause): string
605+
parseOrderBy(orderBy: OrderByClause | OrderByClause[]): string
606+
parseWithDataCategory(withDataCategory: WithDataCategoryClause): string
607+
608+
```
609+
501610
### Utils
502611

503612
```typescript

0 commit comments

Comments
 (0)