Skip to content

Commit

Permalink
Merge pull request #40 from paustint/feature-36
Browse files Browse the repository at this point in the history
Add code formatting #36
  • Loading branch information
paustint authored Oct 21, 2018
2 parents 9abf521 + ebc3ac0 commit 92c577b
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 28 deletions.
6 changes: 4 additions & 2 deletions debug/test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
var soqlParserJs = require('../dist');

const query = `
SELECT Account.Name, (SELECT Contact.LastName FROM Account.Contact.Foo.Bars) FROM Account
SELECT Id, Name FROM Account WHERE Id IN (SELECT AccountId FROM Contact WHERE LastName LIKE 'apple%') AND Id IN (SELECT AccountId FROM Opportunity WHERE isClosed = false)
`;

const parsedQuery = soqlParserJs.parseQuery(query, { logging: true });

console.log(JSON.stringify(parsedQuery, null, 2));

const composedQuery = soqlParserJs.composeQuery(parsedQuery, { logging: true, format: true });
console.log(composedQuery);

// SELECT Id, Name FROM Account WHERE Id IN (SELECT AccountId FROM Opportunity WHERE StageName = 'Closed Lost')
// SELECT Id FROM Account WHERE Id NOT IN (SELECT AccountId FROM Opportunity WHERE IsClosed = false)
// SELECT Id, Name FROM Account WHERE Id IN (SELECT AccountId FROM Contact WHERE LastName LIKE 'apple%') AND Id IN (SELECT AccountId FROM Opportunity WHERE isClosed = false)
95 changes: 69 additions & 26 deletions lib/SoqlComposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,25 @@ import {
WithDataCategoryClause,
} from './models/SoqlQuery.model';
import * as utils from './utils';
import { FieldData, Formatter } from './SoqlFormatter';

export interface SoqlComposeConfig {
logging: boolean; // default=false
format: boolean;
}

export function composeQuery(soql: Query, config: Partial<SoqlComposeConfig> = {}): string {
config.format = config.format ? true : false;
if (config.logging) {
console.time('parser');
console.log('Parsing Query:', soql);
console.time('composer');
console.log('Composing Query:', soql);
console.log('Format output:', config.format);
}

const query = new Compose(soql, config).query;

if (config.logging) {
console.timeEnd('parser');
console.timeEnd('composer');
}

return query;
Expand All @@ -35,12 +39,21 @@ export class Compose {
private subqueryFieldReplaceRegex = /^{|}$/g;

public logging: boolean = false;
public format: boolean = false;
public query: string;
public formatter: Formatter;

constructor(private soql: Query, config: Partial<SoqlComposeConfig> = {}) {
const { logging } = config;
this.logging = logging;
this.format = config.format;
this.query = '';

this.formatter = new Formatter({
active: this.format,
logging: this.logging,
});

this.start();
}

Expand All @@ -54,22 +67,42 @@ export class Compose {
}
}

private parseQuery(query: Query): string {
let output = `SELECT`;
// Parse Fields
const fields = this.parseFields(query.fields);
private parseQuery(query: Query, isSubquery: boolean = false): string {
const fieldData: FieldData = {
fields: this.parseFields(query.fields).map(field => ({
text: field,
isSubquery: false,
prefix: '',
suffix: '',
})),
isSubquery,
lineBreaks: [],
};

let output = `SELECT `;

// Replace subquery fields with parsed subqueries
fields.forEach((field, i) => {
if (field.match(this.subqueryFieldRegex)) {
fieldData.fields.forEach((field, i) => {
if (field.text.match(this.subqueryFieldRegex)) {
const subquery = query.subqueries.find(
subquery => subquery.sObjectRelationshipName === field.replace(this.subqueryFieldReplaceRegex, '')
subquery => subquery.sObjectRelationshipName === field.text.replace(this.subqueryFieldReplaceRegex, '')
);
if (subquery) {
fields[i] = `(${this.parseQuery(subquery)})`;
fieldData.fields[i].text = `(${this.parseQuery(subquery, true)})`;
fieldData.fields[i].isSubquery = true;
}
}
});
output += ` ${fields.join(', ').trim()} FROM`;

// Format fields based on configuration
this.formatter.formatFields(fieldData);

fieldData.fields.forEach(field => {
output += `${field.prefix}${field.text}${field.suffix}`;
});

output += this.formatter.formatClause('FROM', isSubquery);

if (query.sObjectRelationshipName) {
const sObjectPrefix = query.sObjectPrefix || [];
sObjectPrefix.push(query.sObjectRelationshipName);
Expand All @@ -80,53 +113,60 @@ export class Compose {
this.log(output);

if (query.where) {
output += ` WHERE ${this.parseWhereClause(query.where)}`;
output += this.formatter.formatClause('WHERE', isSubquery);
output += ` ${this.parseWhereClause(query.where, isSubquery)}`;
this.log(output);
}

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

if (query.groupBy) {
output += ` GROUP BY ${this.parseGroupByClause(query.groupBy)}`;
output += this.formatter.formatClause('GROUP BY', isSubquery);
output += ` ${this.parseGroupByClause(query.groupBy)}`;
this.log(output);
if (query.having) {
output += ` HAVING ${this.parseHavingClause(query.having)}`;
output += this.formatter.formatClause('HAVING', isSubquery);
output += ` ${this.parseHavingClause(query.having)}`;
this.log(output);
}
}

if (query.orderBy) {
output += ` ORDER BY ${this.parseOrderBy(query.orderBy)}`;
output += this.formatter.formatClause('ORDER BY', isSubquery);
output += ` ${this.parseOrderBy(query.orderBy)}`;
this.log(output);
}

if (utils.isNumber(query.limit)) {
output += ` LIMIT ${query.limit}`;
output += this.formatter.formatClause('LIMIT', isSubquery);
output += ` ${query.limit}`;
this.log(output);
}

if (utils.isNumber(query.offset)) {
output += ` OFFSET ${query.offset}`;
output += this.formatter.formatClause('OFFSET', isSubquery);
output += ` ${query.offset}`;
this.log(output);
}

if (query.withDataCategory) {
output += ` WITH DATA CATEGORY ${this.parseWithDataCategory(query.withDataCategory)}`;
output += this.formatter.formatClause('WITH DATA CATEGORY', isSubquery);
output += ` ${this.parseWithDataCategory(query.withDataCategory)}`;
this.log(output);
}

if (query.for) {
output += ` FOR ${query.for}`;
output += this.formatter.formatClause('FOR', isSubquery);
output += ` ${query.for}`;
this.log(output);
}

if (query.update) {
output += ` UPDATE ${query.update}`;
output += this.formatter.formatClause('UPDATE', isSubquery);
output += ` ${query.update}`;
this.log(output);
}

// TODO: add FOR support https://github.com/paustint/soql-parser-js/issues/19

return output;
}

Expand Down Expand Up @@ -163,7 +203,7 @@ export class Compose {
return `${(fn.text || '').replace(/,/g, ', ')} ${fn.alias || ''}`.trim();
}

private parseWhereClause(where: WhereClause): string {
private parseWhereClause(where: WhereClause, isSubquery: boolean): string {
let output = '';
if (where.left) {
output +=
Expand All @@ -174,15 +214,18 @@ export class Compose {
output += where.left.fn ? this.parseFn(where.left.fn) : where.left.field;
output += ` ${where.left.operator} `;
output += where.left.valueQuery
? `(${this.parseQuery(where.left.valueQuery)})`
? `(${this.parseQuery(where.left.valueQuery, true)})`
: utils.getAsArrayStr(where.left.value);
output +=
utils.isNumber(where.left.closeParen) && where.left.closeParen > 0
? new Array(where.left.closeParen).fill(')').join('')
: '';
}
if (where.right) {
return `${output} ${utils.get(where.operator)} ${this.parseWhereClause(where.right)}`.trim();
return `${output}${this.formatter.formatAddNewLine()}${utils.get(where.operator)} ${this.parseWhereClause(
where.right,
isSubquery
)}`.trim();
} else {
return output.trim();
}
Expand Down
10 changes: 10 additions & 0 deletions test/SoqlParser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { parseQuery, composeQuery } from '../lib';
import { expect } from 'chai';
import 'mocha';
import testCases from './TestCases';
import testCasesForFormat from './TestCasesForFormat';
import { formatQuery } from '../lib/SoqlFormatter';

const replacements = [
{ matching: / and /i, replace: ' AND ' },
Expand Down Expand Up @@ -31,3 +33,11 @@ describe('compose queries', () => {
});
});
});
describe('format queries', () => {
testCasesForFormat.forEach(testCase => {
it(`should format query - test case ${testCase.testCase} - ${testCase.soql}`, () => {
const formattedQuery = formatQuery(testCase.soql);
expect(formattedQuery).equal(testCase.formattedSoql);
});
});
});

0 comments on commit 92c577b

Please sign in to comment.