Skip to content

Commit e3afb78

Browse files
committed
implements NOT
1 parent dcee58e commit e3afb78

File tree

9 files changed

+121
-19
lines changed

9 files changed

+121
-19
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ https://inabajunmr.github.io/swrql/pages/public/
2424
| OUTER JOIN ||
2525
| BETWEEN ||
2626
| IN | ✅ except for subquery |
27-
| NOT | Unsupported |
27+
| NOT | |
2828
| ANY/ALL | Unsupported |
2929
| Subquery | Unsupported |
3030
| LIKE | ✅ but can't use SQL wildcard. using only Regex. |

swrql/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ https://inabajunmr.github.io/swrql/pages/public/
2424
| OUTER JOIN ||
2525
| BETWEEN ||
2626
| IN/NOT IN | ✅ except for subquery |
27-
| NOT | Unsupported |
27+
| NOT | |
2828
| ANY/ALL | Unsupported |
2929
| Subquery | Unsupported |
3030
| LIKE | ✅ but can't use SQL wildcard. using only Regex. |

swrql/src/predicate/predicate.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,27 @@ test(`a like '^a.*$'`, () => {
7373
expect(sut.test(new Record({ a: 'abc' }))).toBe(true);
7474
expect(sut.test(new Record({ a: 'cba' }))).toBe(false);
7575
});
76+
77+
test('NOT a=1', () => {
78+
const sut = new SQLParser('SELECT * FROM abc WHERE NOT a=1;').parse().where;
79+
expect(sut.test(new Record({ a: '1' }))).toBe(false);
80+
expect(sut.test(new Record({ a: '2' }))).toBe(true);
81+
});
82+
83+
test(`NOT (a=1) AND b=1`, () => {
84+
const sut = new SQLParser(`SELECT * FROM abc WHERE NOT (a=1) AND b=1;`).parse()
85+
.where;
86+
expect(sut.test(new Record({ a: '1', b: '1' }))).toBe(false);
87+
expect(sut.test(new Record({ a: '2', b: '1' }))).toBe(true);
88+
expect(sut.test(new Record({ a: '1', b: '2' }))).toBe(false);
89+
expect(sut.test(new Record({ a: '2', b: '2' }))).toBe(false);
90+
});
91+
92+
test(`NOT (a=1 AND b=1)`, () => {
93+
const sut = new SQLParser(`SELECT * FROM abc WHERE NOT (a=1 AND b=1);`).parse()
94+
.where;
95+
expect(sut.test(new Record({ a: '1', b: '1' }))).toBe(false);
96+
expect(sut.test(new Record({ a: '2', b: '1' }))).toBe(true);
97+
expect(sut.test(new Record({ a: '1', b: '2' }))).toBe(true);
98+
expect(sut.test(new Record({ a: '2', b: '2' }))).toBe(true);
99+
});

swrql/src/predicate/predicate.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
OrToken,
1414
StringToken,
1515
Token,
16+
NotToken,
1617
} from '../sql/token';
1718

1819
/**
@@ -47,6 +48,10 @@ export class Predicate {
4748
}
4849

4950
private consumeComparativeOperator(operator: Token, stack: any[]) {
51+
if (operator === NotToken.TOKEN) {
52+
stack.push(!stack.pop());
53+
return;
54+
}
5055
if (
5156
operator === EqualToken.TOKEN ||
5257
operator === LikeToken.TOKEN ||

swrql/src/sql/lexer.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,6 @@ test('x IN (1,2,3)', () => {
244244
test('x NOT IN (1,2,3)', () => {
245245
const lexer = new SQLLexer('x NOT IN (1,2,3)');
246246
const actual = lexer.tokens();
247-
console.log(actual);
248247
expect(actual.length).toBe(14);
249248
expect(actual[0]).toStrictEqual(LParenToken.TOKEN);
250249
expect(actual[1]).toStrictEqual(new IdentifierToken('x'));

swrql/src/sql/lexer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
NumberToken,
1919
GroupByToken,
2020
OrToken,
21+
NotToken,
2122
} from './token';
2223

2324
export class SQLLexer {
@@ -58,7 +59,7 @@ export class SQLLexer {
5859
const b = r[i - 1];
5960
let not = false;
6061
let x = r[i - 1];
61-
if (b instanceof IdentifierToken && b.literal.toUpperCase() === 'NOT') {
62+
if (b instanceof NotToken) {
6263
not = true;
6364
x = r[i - 2];
6465
}

swrql/src/sql/parser.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
GreaterThanToken,
88
IdentifierToken,
99
LikeToken,
10+
NotToken,
1011
NumberToken,
1112
OrToken,
1213
StringToken,
@@ -352,6 +353,71 @@ test('SELECT * FROM abc WHERE a >= 1;', () => {
352353
expect(actual.where.tokens[2]).toStrictEqual(GreaterThanOrEqualToken.TOKEN);
353354
});
354355

356+
test('SELECT * FROM abc WHERE NOT a >= 1;', () => {
357+
const parser = new SQLParser('SELECT * FROM abc WHERE NOT a >= 1;');
358+
const actual = parser.parse();
359+
expect(actual.fields[0]).toStrictEqual(new SelectField('*'));
360+
expect(actual.fields.length).toBe(1);
361+
expect(actual.tables[0]).toContain('abc');
362+
expect(actual.where.tokens).toHaveLength(4);
363+
expect(actual.where.tokens[0]).toStrictEqual(new IdentifierToken('a'));
364+
expect(actual.where.tokens[1]).toStrictEqual(new NumberToken('1'));
365+
expect(actual.where.tokens[2]).toStrictEqual(GreaterThanOrEqualToken.TOKEN);
366+
expect(actual.where.tokens[3]).toStrictEqual(NotToken.TOKEN);
367+
});
368+
369+
test('SELECT * FROM abc WHERE NOT a = 1 AND b=1;', () => {
370+
const parser = new SQLParser('SELECT * FROM abc WHERE NOT a = 1 AND b=1;');
371+
const actual = parser.parse();
372+
expect(actual.fields[0]).toStrictEqual(new SelectField('*'));
373+
expect(actual.fields.length).toBe(1);
374+
expect(actual.tables[0]).toContain('abc');
375+
expect(actual.where.tokens).toHaveLength(8);
376+
expect(actual.where.tokens[0]).toStrictEqual(new IdentifierToken('a'));
377+
expect(actual.where.tokens[1]).toStrictEqual(new NumberToken('1'));
378+
expect(actual.where.tokens[2]).toStrictEqual(EqualToken.TOKEN);
379+
expect(actual.where.tokens[3]).toStrictEqual(NotToken.TOKEN);
380+
expect(actual.where.tokens[4]).toStrictEqual(new IdentifierToken('b'));
381+
expect(actual.where.tokens[5]).toStrictEqual(new NumberToken('1'));
382+
expect(actual.where.tokens[6]).toStrictEqual(EqualToken.TOKEN);
383+
expect(actual.where.tokens[7]).toStrictEqual(AndToken.TOKEN);
384+
385+
});
386+
387+
test('SELECT * FROM abc WHERE NOT (a = 1 AND b=1);', () => {
388+
const parser = new SQLParser('SELECT * FROM abc WHERE NOT (a = 1 AND b=1);');
389+
const actual = parser.parse();
390+
expect(actual.fields[0]).toStrictEqual(new SelectField('*'));
391+
expect(actual.fields.length).toBe(1);
392+
expect(actual.tables[0]).toContain('abc');
393+
expect(actual.where.tokens).toHaveLength(8);
394+
expect(actual.where.tokens[0]).toStrictEqual(new IdentifierToken('a'));
395+
expect(actual.where.tokens[1]).toStrictEqual(new NumberToken('1'));
396+
expect(actual.where.tokens[2]).toStrictEqual(EqualToken.TOKEN);
397+
expect(actual.where.tokens[3]).toStrictEqual(new IdentifierToken('b'));
398+
expect(actual.where.tokens[4]).toStrictEqual(new NumberToken('1'));
399+
expect(actual.where.tokens[5]).toStrictEqual(EqualToken.TOKEN);
400+
expect(actual.where.tokens[6]).toStrictEqual(AndToken.TOKEN);
401+
expect(actual.where.tokens[7]).toStrictEqual(NotToken.TOKEN);
402+
});
403+
404+
test('SELECT * FROM abc WHERE NOT (a = 1) AND b=1;', () => {
405+
const parser = new SQLParser('SELECT * FROM abc WHERE NOT (a = 1) AND b=1;');
406+
const actual = parser.parse();
407+
expect(actual.fields[0]).toStrictEqual(new SelectField('*'));
408+
expect(actual.fields.length).toBe(1);
409+
expect(actual.tables[0]).toContain('abc');
410+
expect(actual.where.tokens).toHaveLength(8);
411+
expect(actual.where.tokens[0]).toStrictEqual(new IdentifierToken('a'));
412+
expect(actual.where.tokens[1]).toStrictEqual(new NumberToken('1'));
413+
expect(actual.where.tokens[2]).toStrictEqual(EqualToken.TOKEN);
414+
expect(actual.where.tokens[3]).toStrictEqual(NotToken.TOKEN);
415+
expect(actual.where.tokens[4]).toStrictEqual(new IdentifierToken('b'));
416+
expect(actual.where.tokens[5]).toStrictEqual(new NumberToken('1'));
417+
expect(actual.where.tokens[6]).toStrictEqual(EqualToken.TOKEN);
418+
expect(actual.where.tokens[7]).toStrictEqual(AndToken.TOKEN);
419+
});
420+
355421
test(`SELECT a,b,c FROM abc WHERE a=1 AND b='abc';`, () => {
356422
const parser = new SQLParser(`SELECT a,b,c FROM abc WHERE a=1 AND b='abc';`);
357423
const actual = parser.parse();

swrql/src/sql/shunting-yard.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
LessThanToken,
1111
LikeToken,
1212
LParenToken,
13+
NotToken,
1314
NumberToken,
1415
OrToken,
1516
RParenToken,
@@ -27,20 +28,22 @@ export function getInputPriority(t: Token): number {
2728
return 29;
2829
case RParenToken.TOKEN:
2930
return 1;
30-
case EqualToken.TOKEN:
31+
case NotToken.TOKEN:
3132
return 21;
33+
case EqualToken.TOKEN:
34+
return 22;
3235
case GreaterThanToken.TOKEN:
33-
return 21;
36+
return 22;
3437
case GreaterThanOrEqualToken.TOKEN:
35-
return 21;
38+
return 22;
3639
case LessThanToken.TOKEN:
37-
return 21;
40+
return 22;
3841
case LessThanOrEqualToken.TOKEN:
39-
return 21;
42+
return 22;
4043
case DiamondToken.TOKEN:
41-
return 21;
44+
return 22;
4245
case LikeToken.TOKEN:
43-
return 21;
46+
return 22;
4447
default:
4548
if (
4649
t instanceof IdentifierToken ||
@@ -61,21 +64,23 @@ export function getStackPriority(t: Token): number {
6164
case OrToken.TOKEN:
6265
return 10;
6366
case LParenToken.TOKEN:
64-
return 1;
65-
case EqualToken.TOKEN:
67+
return 2;
68+
case NotToken.TOKEN:
6669
return 22;
70+
case EqualToken.TOKEN:
71+
return 23;
6772
case GreaterThanToken.TOKEN:
68-
return 22;
73+
return 23;
6974
case GreaterThanOrEqualToken.TOKEN:
70-
return 22;
75+
return 23;
7176
case LessThanToken.TOKEN:
72-
return 22;
77+
return 23;
7378
case LessThanOrEqualToken.TOKEN:
74-
return 22;
79+
return 23;
7580
case DiamondToken.TOKEN:
76-
return 22;
81+
return 23;
7782
case LikeToken.TOKEN:
78-
return 22;
83+
return 23;
7984
case EOFToken.TOKEN:
8085
return 0;
8186
default:

swrql/src/sql/token.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export class IdentifierToken extends Token {
4242
return OnToken.TOKEN;
4343
case 'LIKE':
4444
return LikeToken.TOKEN;
45+
case 'NOT':
46+
return NotToken.TOKEN;
4547
}
4648

4749
if (this.literal.match(/^[0-9]/)) {

0 commit comments

Comments
 (0)