Skip to content

Commit f85597c

Browse files
authored
Merge pull request #45 from paustint/feature-39
added formatting options
2 parents 260bab0 + c8ec7d7 commit f85597c

File tree

7 files changed

+424
-56
lines changed

7 files changed

+424
-56
lines changed

README.md

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ SOQL Parser JS will parse a SOQL query string into an object that is easy to wor
99

1010
This works in the browser as long as npm is used to install the package with dependencies and the browser supports ES6 or a transpiler is used.
1111

12-
*Warning*: antlr4 is a very large library and is required for the parser to function, so be aware of this prior to including in your browser bundles.
12+
*Warning*: antlr4 is a large library and is required for the parser to function, use in the browser with care.
1313

1414
## Examples
1515
For an example of the parser, check out the [example application](https://paustint.github.io/soql-parser-js/).
@@ -134,6 +134,12 @@ Options:
134134
export interface SoqlComposeConfig {
135135
logging: boolean; // default=false
136136
format: boolean; // default=false
137+
formatOptions?: {
138+
numIndent?: number; // default=1
139+
fieldMaxLineLen?: number; // default=60
140+
fieldSubqueryParensOnOwnLine?: boolean; // default=true
141+
whereClauseOperatorsIndented?: boolean; // default=false
142+
}
137143
}
138144
```
139145

@@ -188,6 +194,86 @@ This yields an object with the following structure:
188194
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
189195
```
190196

197+
### Format Query
198+
This function is provided as a convenience and will parse a query and compose the query with formatting options provided.
199+
```typescript
200+
import { formatQuery } from 'soql-parser-js';
201+
202+
const query = `SELECT Id, Name, AccountNumber, AccountSource, AnnualRevenue, BillingAddress, BillingCity, BillingCountry, BillingGeocodeAccuracy, ShippingStreet, Sic, SicDesc, Site, SystemModstamp, TickerSymbol, Type, Website, (SELECT Id, Name, AccountId, Amount, CampaignId, CloseDate, CreatedById, Type FROM Opportunities), (SELECT Id, Name, AccountNumber, AccountSource, AnnualRevenue, BillingAddress, Website FROM ChildAccounts) FROM Account WHERE Name LIKE 'a%' OR Name LIKE 'b%' OR Name LIKE 'c%'`;
203+
204+
const formattedQuery1 = formatQuery(query);
205+
const formattedQuery2 = formatQuery(query, { fieldMaxLineLen: 20, fieldSubqueryParensOnOwnLine: false, whereClauseOperatorsIndented: true });
206+
const formattedQuery3 = formatQuery(query, { fieldSubqueryParensOnOwnLine: true, whereClauseOperatorsIndented: true });
207+
208+
209+
```
210+
211+
```sql
212+
-- formattedQuery1
213+
SELECT Id, Name, AccountNumber, AccountSource, AnnualRevenue,
214+
BillingAddress, BillingCity, BillingCountry, BillingGeocodeAccuracy,
215+
ShippingStreet, Sic, SicDesc, Site, SystemModstamp, TickerSymbol, Type,
216+
Website,
217+
(
218+
SELECT Id, Name, AccountId, Amount, CampaignId, CloseDate,
219+
CreatedById, Type
220+
FROM Opportunities
221+
),
222+
(
223+
SELECT Id, Name, AccountNumber, AccountSource, AnnualRevenue,
224+
BillingAddress, Website
225+
FROM ChildAccounts
226+
)
227+
FROM Account
228+
WHERE Name LIKE 'a%'
229+
OR Name LIKE 'b%'
230+
OR Name LIKE 'c%'
231+
232+
-- formattedQuery2
233+
SELECT Id, Name,
234+
AccountNumber, AccountSource,
235+
AnnualRevenue, BillingAddress,
236+
BillingCity, BillingCountry,
237+
BillingGeocodeAccuracy, ShippingStreet,
238+
Sic, SicDesc, Site,
239+
SystemModstamp, TickerSymbol, Type,
240+
Website,
241+
(SELECT Id, Name,
242+
AccountId, Amount, CampaignId,
243+
CloseDate, CreatedById, Type
244+
FROM Opportunities),
245+
(SELECT Id, Name,
246+
AccountNumber, AccountSource,
247+
AnnualRevenue, BillingAddress,
248+
Website
249+
FROM ChildAccounts)
250+
FROM Account
251+
WHERE Name LIKE 'a%'
252+
OR Name LIKE 'b%'
253+
OR Name LIKE 'c%'
254+
255+
256+
-- formattedQuery3
257+
SELECT Id, Name, AccountNumber, AccountSource, AnnualRevenue,
258+
BillingAddress, BillingCity, BillingCountry, BillingGeocodeAccuracy,
259+
ShippingStreet, Sic, SicDesc, Site, SystemModstamp, TickerSymbol, Type,
260+
Website,
261+
(
262+
SELECT Id, Name, AccountId, Amount, CampaignId, CloseDate,
263+
CreatedById, Type
264+
FROM Opportunities
265+
),
266+
(
267+
SELECT Id, Name, AccountNumber, AccountSource, AnnualRevenue,
268+
BillingAddress, Website
269+
FROM ChildAccounts
270+
)
271+
FROM Account
272+
WHERE Name LIKE 'a%'
273+
OR Name LIKE 'b%'
274+
OR Name LIKE 'c%'
275+
```
276+
191277
### Options
192278

193279
```typescript
@@ -199,6 +285,16 @@ export interface SoqlQueryConfig {
199285

200286
export interface SoqlComposeConfig {
201287
logging: boolean; // default=false
288+
format: boolean; // default=false
289+
formatOptions?: FormatOptions;
290+
}
291+
292+
export interface FormatOptions {
293+
numIndent?: number; // default=1
294+
fieldMaxLineLen?: number; // default=60
295+
fieldSubqueryParensOnOwnLine?: boolean; // default=true
296+
whereClauseOperatorsIndented?: boolean; // default=false
297+
logging?: boolean; // default=false
202298
}
203299
```
204300

debug/test.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
11
var soqlParserJs = require('./lib');
22

33
const query = `
4-
SELECT Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Account.Name, (SELECT Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Contact.LastName FROM Account.Contacts) FROM Account
4+
SELECT Id, Name, AccountNumber, AccountSource, AnnualRevenue, BillingAddress, BillingCity, BillingCountry, BillingGeocodeAccuracy, ShippingStreet, Sic, SicDesc, Site, SystemModstamp, TickerSymbol, Type, Website, (SELECT Id, Name, AccountId, Amount, CampaignId, CloseDate, CreatedById, Type FROM Opportunities), (SELECT Id, Name, AccountNumber, AccountSource, AnnualRevenue, BillingAddress, Website FROM ChildAccounts) FROM Account WHERE Name LIKE 'a%' OR Name LIKE 'b%' OR Name LIKE 'c%'
55
`;
6-
6+
// SELECT Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Account.Name, (SELECT Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Contact.LastName FROM Account.Contacts), baz, (SELECT Id FROM account WHERE Boo.baz = 'bar'), bax, bar FROM Account
77
const parsedQuery = soqlParserJs.parseQuery(query, { logging: true });
88
console.log(JSON.stringify(parsedQuery, null, 2));
99

10-
const composedQuery = soqlParserJs.composeQuery(parsedQuery, { logging: true, format: true });
11-
console.log(composedQuery);
10+
// const composedQuery = soqlParserJs.composeQuery(parsedQuery, {
11+
// logging: true,
12+
// format: true,
13+
// formatOptions: { fieldMaxLineLen: 20, fieldSubqueryParensOnOwnLine: true, whereClauseOperatorsIndented: true },
14+
// });
15+
// console.log(composedQuery);
16+
// soqlParserJs.formatQuery(parsedQuery);
17+
const formattedQuery1 = soqlParserJs.formatQuery(query);
18+
const formattedQuery3 = soqlParserJs.formatQuery(query, {
19+
fieldMaxLineLen: 20,
20+
fieldSubqueryParensOnOwnLine: false,
21+
whereClauseOperatorsIndented: true,
22+
});
23+
const formattedQuery2 = soqlParserJs.formatQuery(query, {
24+
fieldSubqueryParensOnOwnLine: true,
25+
whereClauseOperatorsIndented: true,
26+
});
27+
console.log(formattedQuery1);
28+
console.log(formattedQuery3);
29+
console.log(formattedQuery2);
1230

1331
// SELECT Id, Name FROM Account WHERE Id IN (SELECT AccountId FROM Opportunity WHERE StageName = 'Closed Lost')
1432
// SELECT Id FROM Account WHERE Id NOT IN (SELECT AccountId FROM Opportunity WHERE IsClosed = false)

lib/SoqlComposer.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ import {
1010
WithDataCategoryClause,
1111
} from './models/SoqlQuery.model';
1212
import * as utils from './utils';
13-
import { FieldData, Formatter } from './SoqlFormatter';
13+
import { FieldData, Formatter, FormatOptions } from './SoqlFormatter';
1414

1515
export interface SoqlComposeConfig {
1616
logging: boolean; // default=false
1717
format: boolean; // default=false
18+
formatOptions?: FormatOptions;
1819
}
1920

2021
export function composeQuery(soql: Query, config: Partial<SoqlComposeConfig> = {}): string {
@@ -49,9 +50,9 @@ export class Compose {
4950
this.format = config.format;
5051
this.query = '';
5152

52-
this.formatter = new Formatter({
53-
active: this.format,
53+
this.formatter = new Formatter(this.format, {
5454
logging: this.logging,
55+
...config.formatOptions,
5556
});
5657

5758
this.start();
@@ -88,7 +89,7 @@ export class Compose {
8889
subquery => subquery.sObjectRelationshipName === field.text.replace(this.subqueryFieldReplaceRegex, '')
8990
);
9091
if (subquery) {
91-
fieldData.fields[i].text = `(${this.parseQuery(subquery, true)})`;
92+
fieldData.fields[i].text = this.formatter.formatSubquery(this.parseQuery(subquery, true));
9293
fieldData.fields[i].isSubquery = true;
9394
}
9495
}
@@ -101,7 +102,7 @@ export class Compose {
101102
output += `${field.prefix}${field.text}${field.suffix}`;
102103
});
103104

104-
output += this.formatter.formatClause('FROM', isSubquery);
105+
output += this.formatter.formatClause('FROM');
105106

106107
if (query.sObjectRelationshipName) {
107108
const sObjectPrefix = query.sObjectPrefix || [];
@@ -113,56 +114,56 @@ export class Compose {
113114
this.log(output);
114115

115116
if (query.where) {
116-
output += this.formatter.formatClause('WHERE', isSubquery);
117-
output += ` ${this.parseWhereClause(query.where, isSubquery)}`;
117+
output += this.formatter.formatClause('WHERE');
118+
output += ` ${this.parseWhereClause(query.where)}`;
118119
this.log(output);
119120
}
120121

121122
// TODO: add WITH support https://github.com/paustint/soql-parser-js/issues/18
122123

123124
if (query.groupBy) {
124-
output += this.formatter.formatClause('GROUP BY', isSubquery);
125+
output += this.formatter.formatClause('GROUP BY');
125126
output += ` ${this.parseGroupByClause(query.groupBy)}`;
126127
this.log(output);
127128
if (query.having) {
128-
output += this.formatter.formatClause('HAVING', isSubquery);
129+
output += this.formatter.formatClause('HAVING');
129130
output += ` ${this.parseHavingClause(query.having)}`;
130131
this.log(output);
131132
}
132133
}
133134

134135
if (query.orderBy) {
135-
output += this.formatter.formatClause('ORDER BY', isSubquery);
136+
output += this.formatter.formatClause('ORDER BY');
136137
output += ` ${this.parseOrderBy(query.orderBy)}`;
137138
this.log(output);
138139
}
139140

140141
if (utils.isNumber(query.limit)) {
141-
output += this.formatter.formatClause('LIMIT', isSubquery);
142+
output += this.formatter.formatClause('LIMIT');
142143
output += ` ${query.limit}`;
143144
this.log(output);
144145
}
145146

146147
if (utils.isNumber(query.offset)) {
147-
output += this.formatter.formatClause('OFFSET', isSubquery);
148+
output += this.formatter.formatClause('OFFSET');
148149
output += ` ${query.offset}`;
149150
this.log(output);
150151
}
151152

152153
if (query.withDataCategory) {
153-
output += this.formatter.formatClause('WITH DATA CATEGORY', isSubquery);
154+
output += this.formatter.formatClause('WITH DATA CATEGORY');
154155
output += ` ${this.parseWithDataCategory(query.withDataCategory)}`;
155156
this.log(output);
156157
}
157158

158159
if (query.for) {
159-
output += this.formatter.formatClause('FOR', isSubquery);
160+
output += this.formatter.formatClause('FOR');
160161
output += ` ${query.for}`;
161162
this.log(output);
162163
}
163164

164165
if (query.update) {
165-
output += this.formatter.formatClause('UPDATE', isSubquery);
166+
output += this.formatter.formatClause('UPDATE');
166167
output += ` ${query.update}`;
167168
this.log(output);
168169
}
@@ -207,7 +208,7 @@ export class Compose {
207208
return `${(fn.text || '').replace(/,/g, ', ')} ${fn.alias || ''}`.trim();
208209
}
209210

210-
private parseWhereClause(where: WhereClause, isSubquery: boolean): string {
211+
private parseWhereClause(where: WhereClause): string {
211212
let output = '';
212213
if (where.left) {
213214
output +=
@@ -218,17 +219,22 @@ export class Compose {
218219
output += where.left.fn ? this.parseFn(where.left.fn) : where.left.field;
219220
output += ` ${where.left.operator} `;
220221
output += where.left.valueQuery
221-
? `(${this.parseQuery(where.left.valueQuery, true)})`
222+
? this.formatter.formatSubquery(this.parseQuery(where.left.valueQuery), 1, true)
222223
: utils.getAsArrayStr(where.left.value);
223224
output +=
224225
utils.isNumber(where.left.closeParen) && where.left.closeParen > 0
225226
? new Array(where.left.closeParen).fill(')').join('')
226227
: '';
227228
}
228229
if (where.right) {
229-
return `${output}${this.formatter.formatAddNewLine(' ', isSubquery)}${utils.get(
230-
where.operator
231-
)} ${this.parseWhereClause(where.right, isSubquery)}`.trim();
230+
const formattedData = this.formatter.formatWhereClauseOperators(
231+
utils.get(where.operator),
232+
this.parseWhereClause(where.right)
233+
);
234+
return `${output}${formattedData}`.trim();
235+
// return `${output}${this.formatter.formatAddNewLine(' ')}${utils.get(where.operator)} ${this.parseWhereClause(
236+
// where.right
237+
// )}`.trim();
232238
} else {
233239
return output.trim();
234240
}
@@ -259,7 +265,7 @@ export class Compose {
259265

260266
private parseOrderBy(orderBy: OrderByClause | OrderByClause[]): string {
261267
if (Array.isArray(orderBy)) {
262-
return orderBy.map(ob => this.parseOrderBy(ob)).join(', ');
268+
return this.formatter.formatOrderByArray(orderBy.map(ob => this.parseOrderBy(ob)));
263269
} else {
264270
let output = `${utils.get(orderBy.field, ' ')}`;
265271
output += orderBy.fn ? this.parseFn(orderBy.fn) : '';

0 commit comments

Comments
 (0)