Skip to content

Commit

Permalink
fix non-nullable parameters parsing edgecase
Browse files Browse the repository at this point in the history
  • Loading branch information
adelsz committed Dec 25, 2021
1 parent 307d094 commit 5f3e509
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 21 deletions.
41 changes: 36 additions & 5 deletions packages/example/src/books/books.queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface IInsertBooksQuery {
result: IInsertBooksResult;
}

const insertBooksIR: any = {"name":"InsertBooks","params":[{"name":"books","codeRefs":{"defined":{"a":94,"b":98,"line":6,"col":9},"used":[{"a":211,"b":215,"line":9,"col":8}]},"transform":{"type":"pick_array_spread","keys":[{"name":"rank","required":true},{"name":"name","required":true},{"name":"authorId","required":true},{"name":"categories","required":false}]},"required":false}],"usedParamSet":{"books":true},"statement":{"body":"INSERT INTO books (rank, name, author_id, categories)\nVALUES :books RETURNING id as book_id","loc":{"a":149,"b":239,"line":8,"col":0}}};
const insertBooksIR: any = {"name":"InsertBooks","params":[{"name":"books","codeRefs":{"defined":{"a":95,"b":99,"line":7,"col":9},"used":[{"a":212,"b":216,"line":10,"col":8}]},"transform":{"type":"pick_array_spread","keys":[{"name":"rank","required":true},{"name":"name","required":true},{"name":"authorId","required":true},{"name":"categories","required":false}]},"required":false}],"usedParamSet":{"books":true},"statement":{"body":"INSERT INTO books (rank, name, author_id, categories)\nVALUES :books RETURNING id as book_id","loc":{"a":150,"b":240,"line":9,"col":0}}};

/**
* Query generated from SQL:
Expand All @@ -88,7 +88,7 @@ export interface IUpdateBooksCustomQuery {
result: IUpdateBooksCustomResult;
}

const updateBooksCustomIR: any = {"name":"UpdateBooksCustom","params":[{"name":"rank","required":false,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":325,"b":328,"line":17,"col":20},{"a":371,"b":374,"line":18,"col":23}]}},{"name":"id","required":true,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":437,"b":439,"line":22,"col":12}]}}],"usedParamSet":{"rank":true,"id":true},"statement":{"body":"UPDATE books\nSET\n rank = (\n CASE WHEN (:rank::int IS NOT NULL)\n THEN :rank\n ELSE rank\n END\n )\nWHERE id = :id!","loc":{"a":275,"b":439,"line":14,"col":0}}};
const updateBooksCustomIR: any = {"name":"UpdateBooksCustom","params":[{"name":"rank","required":false,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":326,"b":329,"line":18,"col":20},{"a":372,"b":375,"line":19,"col":23}]}},{"name":"id","required":true,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":438,"b":440,"line":23,"col":12}]}}],"usedParamSet":{"rank":true,"id":true},"statement":{"body":"UPDATE books\nSET\n rank = (\n CASE WHEN (:rank::int IS NOT NULL)\n THEN :rank\n ELSE rank\n END\n )\nWHERE id = :id!","loc":{"a":276,"b":440,"line":15,"col":0}}};

/**
* Query generated from SQL:
Expand Down Expand Up @@ -123,7 +123,7 @@ export interface IUpdateBooksQuery {
result: IUpdateBooksResult;
}

const updateBooksIR: any = {"name":"UpdateBooks","params":[{"name":"name","required":false,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":520,"b":523,"line":30,"col":12}]}},{"name":"rank","required":false,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":538,"b":541,"line":31,"col":12}]}},{"name":"id","required":true,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":555,"b":557,"line":32,"col":12}]}}],"usedParamSet":{"name":true,"rank":true,"id":true},"statement":{"body":"UPDATE books\n \nSET\n name = :name,\n rank = :rank\nWHERE id = :id!","loc":{"a":469,"b":557,"line":27,"col":0}}};
const updateBooksIR: any = {"name":"UpdateBooks","params":[{"name":"name","required":false,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":521,"b":524,"line":31,"col":12}]}},{"name":"rank","required":false,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":539,"b":542,"line":32,"col":12}]}},{"name":"id","required":true,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":556,"b":558,"line":33,"col":12}]}}],"usedParamSet":{"name":true,"rank":true,"id":true},"statement":{"body":"UPDATE books\n \nSET\n name = :name,\n rank = :rank\nWHERE id = :id!","loc":{"a":470,"b":558,"line":28,"col":0}}};

/**
* Query generated from SQL:
Expand All @@ -139,6 +139,37 @@ const updateBooksIR: any = {"name":"UpdateBooks","params":[{"name":"name","requi
export const updateBooks = new PreparedQuery<IUpdateBooksParams,IUpdateBooksResult>(updateBooksIR);


/** 'UpdateBooksRankNotNull' parameters type */
export interface IUpdateBooksRankNotNullParams {
id: number;
name: string | null | void;
rank: number;
}

/** 'UpdateBooksRankNotNull' return type */
export type IUpdateBooksRankNotNullResult = void;

/** 'UpdateBooksRankNotNull' query type */
export interface IUpdateBooksRankNotNullQuery {
params: IUpdateBooksRankNotNullParams;
result: IUpdateBooksRankNotNullResult;
}

const updateBooksRankNotNullIR: any = {"name":"UpdateBooksRankNotNull","params":[{"name":"rank","required":true,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":628,"b":632,"line":40,"col":12}]}},{"name":"name","required":false,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":647,"b":650,"line":41,"col":12}]}},{"name":"id","required":true,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":664,"b":666,"line":42,"col":12}]}}],"usedParamSet":{"rank":true,"name":true,"id":true},"statement":{"body":"UPDATE books\nSET\n rank = :rank!,\n name = :name\nWHERE id = :id!","loc":{"a":599,"b":666,"line":38,"col":0}}};

/**
* Query generated from SQL:
* ```
* UPDATE books
* SET
* rank = :rank!,
* name = :name
* WHERE id = :id!
* ```
*/
export const updateBooksRankNotNull = new PreparedQuery<IUpdateBooksRankNotNullParams,IUpdateBooksRankNotNullResult>(updateBooksRankNotNullIR);


/** 'GetBooksByAuthorName' parameters type */
export interface IGetBooksByAuthorNameParams {
authorName: string;
Expand All @@ -159,7 +190,7 @@ export interface IGetBooksByAuthorNameQuery {
result: IGetBooksByAuthorNameResult;
}

const getBooksByAuthorNameIR: any = {"name":"GetBooksByAuthorName","params":[{"name":"authorName","required":true,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":705,"b":715,"line":37,"col":44}]}}],"usedParamSet":{"authorName":true},"statement":{"body":"SELECT b.* FROM books b\nINNER JOIN authors a ON a.id = b.author_id\nWHERE a.first_name || ' ' || a.last_name = :authorName!","loc":{"a":594,"b":715,"line":35,"col":0}}};
const getBooksByAuthorNameIR: any = {"name":"GetBooksByAuthorName","params":[{"name":"authorName","required":true,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":814,"b":824,"line":47,"col":44}]}}],"usedParamSet":{"authorName":true},"statement":{"body":"SELECT b.* FROM books b\nINNER JOIN authors a ON a.id = b.author_id\nWHERE a.first_name || ' ' || a.last_name = :authorName!","loc":{"a":703,"b":824,"line":45,"col":0}}};

/**
* Query generated from SQL:
Expand Down Expand Up @@ -189,7 +220,7 @@ export interface IAggregateEmailsAndTestQuery {
result: IAggregateEmailsAndTestResult;
}

const aggregateEmailsAndTestIR: any = {"name":"AggregateEmailsAndTest","params":[{"name":"testAges","required":false,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":807,"b":814,"line":40,"col":53}]}}],"usedParamSet":{"testAges":true},"statement":{"body":"SELECT array_agg(email) as emails, array_agg(age) = :testAges as ageTest FROM users","loc":{"a":754,"b":836,"line":40,"col":0}}};
const aggregateEmailsAndTestIR: any = {"name":"AggregateEmailsAndTest","params":[{"name":"testAges","required":false,"transform":{"type":"scalar"},"codeRefs":{"used":[{"a":916,"b":923,"line":50,"col":53}]}}],"usedParamSet":{"testAges":true},"statement":{"body":"SELECT array_agg(email) as emails, array_agg(age) = :testAges as ageTest FROM users","loc":{"a":863,"b":945,"line":50,"col":0}}};

/**
* Query generated from SQL:
Expand Down
10 changes: 10 additions & 0 deletions packages/example/src/books/books.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* @name FindBookById */
SELECT * FROM books WHERE id = :id;


/*
@name InsertBooks
@param books -> ((rank!, name!, authorId!, categories)...)
Expand Down Expand Up @@ -31,6 +32,15 @@ SET
rank = :rank
WHERE id = :id!;

/*
@name UpdateBooksRankNotNull
*/
UPDATE books
SET
rank = :rank!,
name = :name
WHERE id = :id!;

/* @name GetBooksByAuthorName */
SELECT b.* FROM books b
INNER JOIN authors a ON a.id = b.author_id
Expand Down
21 changes: 15 additions & 6 deletions packages/example/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ import {
getBooksByAuthorName,
insertBooks,
updateBooks,
updateBooksCustom
updateBooksCustom,
updateBooksRankNotNull,
} from './books/books.queries';
import { getAllComments } from './comments/comments.queries';
import {
insertNotification,
insertNotifications
insertNotifications,
} from './notifications/notifications';
import {
sendNotifications,
thresholdFrogs
thresholdFrogs,
} from './notifications/notifications.queries';

// tslint:disable:no-console
Expand Down Expand Up @@ -43,21 +44,29 @@ async function main() {
authorId: 1,
name: 'A Brief History of Time: From the Big Bang to Black Holes',
rank: 1,
categories: ["novel", "science-fiction"]
categories: ['novel', 'science-fiction'],
},
],
},
client,
);
console.log(`Inserted book ID: ${insertedBookId}`);

const {0: insertedBook} = await findBookById.run({id: insertedBookId}, client);
expect(insertedBook.categories).toEqual(["novel", "science-fiction"]);
const { 0: insertedBook } = await findBookById.run(
{ id: insertedBookId },
client,
);
expect(insertedBook.categories).toEqual(['novel', 'science-fiction']);

await updateBooks.run({ id: 2, rank: 12, name: 'Another title' }, client);

await updateBooksCustom.run({ id: 2, rank: 13 }, client);

await updateBooksRankNotNull.run(
{ id: 2, rank: 12, name: 'Another title' },
client,
);

const books = await getBooksByAuthorName.run(
{
authorName: 'Carl Sagan',
Expand Down
2 changes: 1 addition & 1 deletion packages/query/src/loader/sql/grammar/SQLLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ OPEN_COMMENT: '/*' -> mode(COMMENT);
SID: ID -> type(ID);
S_REQUIRED_MARK: '!';
WORD: [a-zA-Z_0-9]+;
SPECIAL: [\-+*/<>=~!@#%^&|`?$(){},.[\]"]+ -> type(WORD);
SPECIAL: [\-+*/<>=~@#%^&|`?$(){},.[\]"]+ -> type(WORD);
EOF_STATEMENT: ';';
WSL : [ \t\r\n]+ -> skip;
// parse strings and recognize escaped quotes
Expand Down
2 changes: 1 addition & 1 deletion packages/query/src/loader/sql/grammar/SQLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ statement
statementBody
: word (ignoredComment | param | word)*;

word: WORD | ID | STRING;
word: WORD | ID | STRING | S_REQUIRED_MARK;

param: PARAM_MARK paramId;

Expand Down
2 changes: 1 addition & 1 deletion packages/query/src/loader/sql/parser/SQLLexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export class SQLLexer extends Lexer {
"\x10\x02\x02\x12\x02\x07\x14\x02\b\x16\x02\t\x18\x02\n\x1A\x02\x16\x1C" +
"\x02\x02\x1E\x02\v \x02\f\"\x02\r$\x02\x0E&\x02\x0F(\x02\x10*\x02\x11" +
",\x02\x12.\x02\x130\x02\x142\x02\x15\x04\x02\x03\x07\x05\x02C\\aac|\x06" +
"\x022;C\\aac|\t\x02#(*1>B]]_`bb}\x80\x05\x02\v\f\x0F\x0F\"\"\x03\x02^" +
"\x022;C\\aac|\t\x02$(*1>B]]_`bb}\x80\x05\x02\v\f\x0F\x0F\"\"\x03\x02^" +
"^\x02\xA5\x02\b\x03\x02\x02\x02\x02\n\x03\x02\x02\x02\x02\f\x03\x02\x02" +
"\x02\x02\x0E\x03\x02\x02\x02\x02\x10\x03\x02\x02\x02\x02\x12\x03\x02\x02" +
"\x02\x02\x14\x03\x02\x02\x02\x02\x16\x03\x02\x02\x02\x02\x18\x03\x02\x02" +
Expand Down
15 changes: 8 additions & 7 deletions packages/query/src/loader/sql/parser/SQLParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ export class SQLParser extends Parser {
this.state = 82;
this._errHandler.sync(this);
_la = this._input.LA(1);
while ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << SQLParser.ID) | (1 << SQLParser.OPEN_COMMENT) | (1 << SQLParser.WORD) | (1 << SQLParser.STRING) | (1 << SQLParser.PARAM_MARK))) !== 0)) {
while ((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << SQLParser.ID) | (1 << SQLParser.OPEN_COMMENT) | (1 << SQLParser.S_REQUIRED_MARK) | (1 << SQLParser.WORD) | (1 << SQLParser.STRING) | (1 << SQLParser.PARAM_MARK))) !== 0)) {
{
this.state = 80;
this._errHandler.sync(this);
Expand All @@ -353,6 +353,7 @@ export class SQLParser extends Parser {
}
break;
case SQLParser.ID:
case SQLParser.S_REQUIRED_MARK:
case SQLParser.WORD:
case SQLParser.STRING:
{
Expand Down Expand Up @@ -394,7 +395,7 @@ export class SQLParser extends Parser {
{
this.state = 85;
_la = this._input.LA(1);
if (!((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << SQLParser.ID) | (1 << SQLParser.WORD) | (1 << SQLParser.STRING))) !== 0))) {
if (!((((_la) & ~0x1F) === 0 && ((1 << _la) & ((1 << SQLParser.ID) | (1 << SQLParser.S_REQUIRED_MARK) | (1 << SQLParser.WORD) | (1 << SQLParser.STRING))) !== 0))) {
this._errHandler.recoverInline(this);
} else {
if (this._input.LA(1) === Token.EOF) {
Expand Down Expand Up @@ -451,22 +452,21 @@ export class SQLParser extends Parser {
public paramId(): ParamIdContext {
let _localctx: ParamIdContext = new ParamIdContext(this._ctx, this.state);
this.enterRule(_localctx, 16, SQLParser.RULE_paramId);
let _la: number;
try {
this.enterOuterAlt(_localctx, 1);
{
this.state = 90;
this.match(SQLParser.ID);
this.state = 92;
this._errHandler.sync(this);
_la = this._input.LA(1);
if (_la === SQLParser.S_REQUIRED_MARK) {
switch ( this.interpreter.adaptivePredict(this._input, 6, this._ctx) ) {
case 1:
{
this.state = 91;
this.match(SQLParser.S_REQUIRED_MARK);
}
break;
}

}
}
catch (re) {
Expand Down Expand Up @@ -837,7 +837,7 @@ export class SQLParser extends Parser {
"\x03\x11\x03\x12\x03\x12\x05\x12\x89\n\x12\x03\x13\x03\x13\x03\x14\x03" +
"\x14\x03\x14\x03F\x02\x02\x15\x02\x02\x04\x02\x06\x02\b\x02\n\x02\f\x02" +
"\x0E\x02\x10\x02\x12\x02\x14\x02\x16\x02\x18\x02\x1A\x02\x1C\x02\x1E\x02" +
" \x02\"\x02$\x02&\x02\x02\x04\x03\x02\x0E\x0E\x05\x02\x03\x03\x06\x06" +
" \x02\"\x02$\x02&\x02\x02\x04\x03\x02\x0E\x0E\x05\x02\x03\x03\x05\x06" +
"\t\t\x02\x88\x02/\x03\x02\x02\x02\x045\x03\x02\x02\x02\x068\x03\x02\x02" +
"\x02\bB\x03\x02\x02\x02\nK\x03\x02\x02\x02\fN\x03\x02\x02\x02\x0EW\x03" +
"\x02\x02\x02\x10Y\x03\x02\x02\x02\x12\\\x03\x02\x02\x02\x14`\x03\x02\x02" +
Expand Down Expand Up @@ -1145,6 +1145,7 @@ export class WordContext extends ParserRuleContext {
public WORD(): TerminalNode | undefined { return this.tryGetToken(SQLParser.WORD, 0); }
public ID(): TerminalNode | undefined { return this.tryGetToken(SQLParser.ID, 0); }
public STRING(): TerminalNode | undefined { return this.tryGetToken(SQLParser.STRING, 0); }
public S_REQUIRED_MARK(): TerminalNode | undefined { return this.tryGetToken(SQLParser.S_REQUIRED_MARK, 0); }
constructor(parent: ParserRuleContext | undefined, invokingState: number) {
super(parent, invokingState);
}
Expand Down
33 changes: 33 additions & 0 deletions packages/query/src/preprocessor-sql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,39 @@ test('(SQL) no params', () => {
expect(mappingResult).toEqual(expectedResult);
});

test('(SQL) two scalar params, one forced as non-null', () => {
const query = `
/*
@name UpdateBooksRankNotNull
*/
UPDATE books
SET
rank = :rank!,
name = :name
WHERE id = :id!;`;

const fileAST = parseSQLQuery(query);
const parameters = {
rank: 123,
name: 'name',
id: 'id',
};

const expectedInterpolationResult = {
query:
'UPDATE books\n SET\n rank = $1,\n name = $2\n WHERE id = $3',
mapping: [],
bindings: [123, 'name', 'id'],
};

const interpolationResult = processSQLQueryAST(
fileAST.queries[0],
parameters,
);

expect(interpolationResult).toEqual(expectedInterpolationResult);
});

test('(SQL) two scalar params', () => {
const query = `
/* @name selectSomeUsers */
Expand Down

1 comment on commit 5f3e509

@vercel
Copy link

@vercel vercel bot commented on 5f3e509 Dec 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.